@bluevolt-tech/lumen 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/_shared/date-range-validator.d.ts +26 -1
- package/dist/components/_shared/dropdown-controller.d.ts +45 -0
- package/dist/components/bv-date-range-input/bv-date-range-input.d.ts +7 -1
- package/dist/components/bv-date-range-input/bv-date-range-input.js +241 -96
- package/dist/components/bv-date-range-picker/bv-date-range-picker.d.ts +61 -8
- package/dist/components/bv-date-range-picker/bv-date-range-picker.js +180 -81
- package/dist/components/bv-filter-dropdown/bv-filter-dropdown.js +18 -10
- package/dist/components/bv-input-dropdown/bv-input-dropdown.js +18 -10
- package/dist/components/bv-search-picker/bv-search-picker.d.ts +222 -0
- package/dist/components/bv-search-picker/bv-search-picker.js +1458 -0
- package/dist/components/bv-segmented-control/bv-segmented-control.d.ts +7 -1
- package/dist/components/bv-segmented-control/bv-segmented-control.js +21 -2
- package/dist/components/bv-select/bv-select.js +18 -10
- package/dist/components/bv-split-button/bv-split-button.js +18 -10
- package/dist/components/bv-table/bv-table.js +1 -0
- package/dist/components/bv-tooltip/bv-tooltip.js +1 -0
- package/dist/storybook/stories/screens/cet-report.data.js +834 -31
- package/dist/themes/base.css +1 -1
- package/package.json +5 -1
- package/themes/base.css +1 -1
|
@@ -6,7 +6,7 @@ export interface DateRangeChangeDetail {
|
|
|
6
6
|
preset: string;
|
|
7
7
|
}
|
|
8
8
|
export declare class BvDateRangePicker extends HTMLElement {
|
|
9
|
-
static readonly observedAttributes: readonly ["start", "end", "preset", "min", "max", "max-range-years", "validate-order", "validate-format", "show-range-limit-banner", "error-min", "error-max", "error-order", "error-format", "range-limit-message", "range-limit-heading"];
|
|
9
|
+
static readonly observedAttributes: readonly ["start", "end", "preset", "min", "max", "max-range-years", "validate-order", "validate-format", "show-range-limit-banner", "error-min", "error-max", "error-order", "error-format", "range-limit-message", "range-limit-heading", "error-min-severity", "error-max-severity", "error-format-severity", "error-order-severity", "range-limit-severity"];
|
|
10
10
|
private _rendered;
|
|
11
11
|
private _today;
|
|
12
12
|
private _start;
|
|
@@ -26,12 +26,22 @@ export declare class BvDateRangePicker extends HTMLElement {
|
|
|
26
26
|
private _startInput;
|
|
27
27
|
private _endInput;
|
|
28
28
|
private _applyBtn;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Unified alert banner (BTN-10312). Shows AT MOST ONE error at a time. When
|
|
31
|
+
* multiple errors are simultaneously active, the banner reflects the
|
|
32
|
+
* most-recently-edited field (see `_lastEditedField`).
|
|
33
|
+
*/
|
|
34
|
+
private _alertBannerEl;
|
|
33
35
|
private _leftDayMap;
|
|
34
36
|
private _rightDayMap;
|
|
37
|
+
/**
|
|
38
|
+
* BTN-10312 — tracks which field the user most-recently interacted with.
|
|
39
|
+
* When both `startError` and `endError` are active, the banner shows the
|
|
40
|
+
* error corresponding to this field. Set to `null` when no field has been
|
|
41
|
+
* touched (initial render, or external attribute set); the resolver falls
|
|
42
|
+
* back to a stable precedence in that case.
|
|
43
|
+
*/
|
|
44
|
+
private _lastEditedField;
|
|
35
45
|
constructor();
|
|
36
46
|
get start(): string;
|
|
37
47
|
set start(v: string);
|
|
@@ -41,6 +51,16 @@ export declare class BvDateRangePicker extends HTMLElement {
|
|
|
41
51
|
set preset(v: string);
|
|
42
52
|
connectedCallback(): void;
|
|
43
53
|
attributeChangedCallback(name: string, _old: string | null, val: string | null): void;
|
|
54
|
+
/**
|
|
55
|
+
* BTN-10312 — public reset of the picker's internal draft state to the
|
|
56
|
+
* currently committed `start` / `end` / `preset` attributes. Called by
|
|
57
|
+
* `bv-date-range-input` when the popup closes without Apply (Cancel,
|
|
58
|
+
* Escape, outside-click) so the next open shows the last-committed value
|
|
59
|
+
* cleanly, with no leftover error banners from the discarded edit.
|
|
60
|
+
*
|
|
61
|
+
* The external value is not modified — this only clears the draft.
|
|
62
|
+
*/
|
|
63
|
+
resetDraft(): void;
|
|
44
64
|
private _render;
|
|
45
65
|
private _buildCalendarShell;
|
|
46
66
|
private _navigate;
|
|
@@ -57,12 +77,45 @@ export declare class BvDateRangePicker extends HTMLElement {
|
|
|
57
77
|
private _syncPresets;
|
|
58
78
|
private _syncValidity;
|
|
59
79
|
private _readConfig;
|
|
60
|
-
|
|
61
|
-
|
|
80
|
+
/**
|
|
81
|
+
* BTN-10312 — apply the `.invalid` + `aria-invalid` state to input fields
|
|
82
|
+
* whose field-level error is currently blocking (severity `'error'`).
|
|
83
|
+
* Non-blocking severities (warning/primary/success) do not paint the input
|
|
84
|
+
* red so the visual matches the semantic ("not a fatal error").
|
|
85
|
+
*/
|
|
86
|
+
private _paintInputInvalidState;
|
|
87
|
+
private _paintOne;
|
|
88
|
+
/**
|
|
89
|
+
* BTN-10312 — single alert banner. Picks the "active" error following the
|
|
90
|
+
* precedence:
|
|
91
|
+
*
|
|
92
|
+
* 1. If a form error is active, show it (form errors don't coexist with
|
|
93
|
+
* field errors by validator design).
|
|
94
|
+
* 2. If both field errors are active, show the one for
|
|
95
|
+
* `_lastEditedField` (falling back to `start` if `null`).
|
|
96
|
+
* 3. Else show whichever single field error is active.
|
|
97
|
+
* 4. Else hide the banner.
|
|
98
|
+
*
|
|
99
|
+
* `show-range-limit-banner="false"` still suppresses the range-limit
|
|
100
|
+
* banner specifically (existing consumer contract from BTN-10191).
|
|
101
|
+
*/
|
|
102
|
+
private _applyAlertBanner;
|
|
103
|
+
/**
|
|
104
|
+
* Pick which error the banner should reflect. See `_applyAlertBanner` for
|
|
105
|
+
* the precedence rules.
|
|
106
|
+
*/
|
|
107
|
+
private _resolveActiveError;
|
|
62
108
|
private _resolveFieldError;
|
|
109
|
+
/**
|
|
110
|
+
* BTN-10312 — interpolate `{field}` (always lowercase) and `{Field}` (always
|
|
111
|
+
* capitalized) in a user-provided template. Before this change, the historical
|
|
112
|
+
* behavior was that `{field}` in custom copy resolved lowercase while the
|
|
113
|
+
* built-in defaults capitalized inline — inconsistent. Now the split is
|
|
114
|
+
* explicit: `{field}` = lowercase, `{Field}` = capitalized.
|
|
115
|
+
*/
|
|
116
|
+
private _interpolateField;
|
|
63
117
|
private _resolveOrderError;
|
|
64
118
|
private _applyApplyState;
|
|
65
|
-
private _applyBannerState;
|
|
66
119
|
private _applyCalendarDisabledState;
|
|
67
120
|
private _resetDayDisabled;
|
|
68
121
|
private _setupDateMask;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// css-inline:/home/runner/work/BV_lumen/BV_lumen/components/bv-date-range-picker/bv-date-range-picker.css
|
|
2
|
-
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";
|
|
2
|
+
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";
|
|
3
3
|
|
|
4
4
|
// components/_shared/adopt-styles.ts
|
|
5
5
|
var _sheetCache = /* @__PURE__ */ new Map();
|
|
@@ -20,6 +20,13 @@ function adoptStyles(shadow, css) {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
// components/_shared/date-range-validator.ts
|
|
23
|
+
var ALLOWED_SEVERITIES = ["error", "warning", "primary", "success"];
|
|
24
|
+
function normalizeSeverity(value) {
|
|
25
|
+
if (value && ALLOWED_SEVERITIES.includes(value)) {
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
return "error";
|
|
29
|
+
}
|
|
23
30
|
function validateDateRange(input, config = {}) {
|
|
24
31
|
const { start, end } = input;
|
|
25
32
|
const { min, max, maxRangeYears, validateOrder, validateFormat } = config;
|
|
@@ -50,7 +57,11 @@ function validateDateRange(input, config = {}) {
|
|
|
50
57
|
}
|
|
51
58
|
}
|
|
52
59
|
const valid = !startError && !endError && !formError;
|
|
53
|
-
const
|
|
60
|
+
const startSev = startError ? severityOfFieldError(startError, config) : null;
|
|
61
|
+
const endSev = endError ? severityOfFieldError(endError, config) : null;
|
|
62
|
+
const formSev = formError ? severityOfFormError(formError, config) : null;
|
|
63
|
+
const hasBlockingError = startSev === "error" || endSev === "error" || formSev === "error";
|
|
64
|
+
const applyEnabled = !hasBlockingError && !!startDate && !!endDate;
|
|
54
65
|
return {
|
|
55
66
|
valid,
|
|
56
67
|
applyEnabled,
|
|
@@ -59,6 +70,15 @@ function validateDateRange(input, config = {}) {
|
|
|
59
70
|
...formError && { formError }
|
|
60
71
|
};
|
|
61
72
|
}
|
|
73
|
+
function severityOfFieldError(err, config) {
|
|
74
|
+
if (err === "format") return normalizeSeverity(config.formatSeverity);
|
|
75
|
+
if (err === "min") return normalizeSeverity(config.minSeverity);
|
|
76
|
+
return normalizeSeverity(config.maxSeverity);
|
|
77
|
+
}
|
|
78
|
+
function severityOfFormError(err, config) {
|
|
79
|
+
if (err === "order") return normalizeSeverity(config.orderSeverity);
|
|
80
|
+
return normalizeSeverity(config.rangeLimitSeverity);
|
|
81
|
+
}
|
|
62
82
|
function parseIso(s) {
|
|
63
83
|
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s);
|
|
64
84
|
if (!m) return null;
|
|
@@ -385,7 +405,13 @@ var BvDateRangePicker = class extends HTMLElement {
|
|
|
385
405
|
"error-order",
|
|
386
406
|
"error-format",
|
|
387
407
|
"range-limit-message",
|
|
388
|
-
"range-limit-heading"
|
|
408
|
+
"range-limit-heading",
|
|
409
|
+
// BTN-10312 — per-error severity (default error = blocks Apply + red banner)
|
|
410
|
+
"error-min-severity",
|
|
411
|
+
"error-max-severity",
|
|
412
|
+
"error-format-severity",
|
|
413
|
+
"error-order-severity",
|
|
414
|
+
"range-limit-severity"
|
|
389
415
|
];
|
|
390
416
|
_rendered = false;
|
|
391
417
|
_today;
|
|
@@ -409,12 +435,22 @@ var BvDateRangePicker = class extends HTMLElement {
|
|
|
409
435
|
_startInput = null;
|
|
410
436
|
_endInput = null;
|
|
411
437
|
_applyBtn = null;
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
438
|
+
/**
|
|
439
|
+
* Unified alert banner (BTN-10312). Shows AT MOST ONE error at a time. When
|
|
440
|
+
* multiple errors are simultaneously active, the banner reflects the
|
|
441
|
+
* most-recently-edited field (see `_lastEditedField`).
|
|
442
|
+
*/
|
|
443
|
+
_alertBannerEl = null;
|
|
416
444
|
_leftDayMap = /* @__PURE__ */ new Map();
|
|
417
445
|
_rightDayMap = /* @__PURE__ */ new Map();
|
|
446
|
+
/**
|
|
447
|
+
* BTN-10312 — tracks which field the user most-recently interacted with.
|
|
448
|
+
* When both `startError` and `endError` are active, the banner shows the
|
|
449
|
+
* error corresponding to this field. Set to `null` when no field has been
|
|
450
|
+
* touched (initial render, or external attribute set); the resolver falls
|
|
451
|
+
* back to a stable precedence in that case.
|
|
452
|
+
*/
|
|
453
|
+
_lastEditedField = null;
|
|
418
454
|
constructor() {
|
|
419
455
|
super();
|
|
420
456
|
const now = /* @__PURE__ */ new Date();
|
|
@@ -449,10 +485,12 @@ var BvDateRangePicker = class extends HTMLElement {
|
|
|
449
485
|
if (name === "start") {
|
|
450
486
|
this._start = val ?? "";
|
|
451
487
|
this._startRawInvalid = "";
|
|
488
|
+
this._lastEditedField = null;
|
|
452
489
|
}
|
|
453
490
|
if (name === "end") {
|
|
454
491
|
this._end = val ?? "";
|
|
455
492
|
this._endRawInvalid = "";
|
|
493
|
+
this._lastEditedField = null;
|
|
456
494
|
}
|
|
457
495
|
if (name === "preset") this._preset = val ?? "";
|
|
458
496
|
if (!this._rendered) return;
|
|
@@ -463,6 +501,30 @@ var BvDateRangePicker = class extends HTMLElement {
|
|
|
463
501
|
}
|
|
464
502
|
this._syncValidity();
|
|
465
503
|
}
|
|
504
|
+
/**
|
|
505
|
+
* BTN-10312 — public reset of the picker's internal draft state to the
|
|
506
|
+
* currently committed `start` / `end` / `preset` attributes. Called by
|
|
507
|
+
* `bv-date-range-input` when the popup closes without Apply (Cancel,
|
|
508
|
+
* Escape, outside-click) so the next open shows the last-committed value
|
|
509
|
+
* cleanly, with no leftover error banners from the discarded edit.
|
|
510
|
+
*
|
|
511
|
+
* The external value is not modified — this only clears the draft.
|
|
512
|
+
*/
|
|
513
|
+
resetDraft() {
|
|
514
|
+
this._start = this.getAttribute("start") ?? "";
|
|
515
|
+
this._end = this.getAttribute("end") ?? "";
|
|
516
|
+
this._preset = this.getAttribute("preset") ?? "";
|
|
517
|
+
this._startRawInvalid = "";
|
|
518
|
+
this._endRawInvalid = "";
|
|
519
|
+
this._picking = false;
|
|
520
|
+
this._hover = "";
|
|
521
|
+
this._lastEditedField = null;
|
|
522
|
+
if (!this._rendered) return;
|
|
523
|
+
this._syncCalendars();
|
|
524
|
+
this._syncInputs();
|
|
525
|
+
this._syncPresets();
|
|
526
|
+
this._syncValidity();
|
|
527
|
+
}
|
|
466
528
|
_render() {
|
|
467
529
|
if (this._rendered) return;
|
|
468
530
|
this._rendered = true;
|
|
@@ -546,31 +608,11 @@ var BvDateRangePicker = class extends HTMLElement {
|
|
|
546
608
|
this._applyBtn = applyBtn;
|
|
547
609
|
actions.append(cancelBtn, applyBtn);
|
|
548
610
|
footer.append(inputsWrap, actions);
|
|
549
|
-
const errorsWrap = document.createElement("div");
|
|
550
|
-
errorsWrap.className = "error-messages";
|
|
551
|
-
const startErrorEl = document.createElement("div");
|
|
552
|
-
startErrorEl.className = "input-error input-error--start";
|
|
553
|
-
startErrorEl.setAttribute("role", "alert");
|
|
554
|
-
startErrorEl.hidden = true;
|
|
555
|
-
this._startErrorEl = startErrorEl;
|
|
556
|
-
const endErrorEl = document.createElement("div");
|
|
557
|
-
endErrorEl.className = "input-error input-error--end";
|
|
558
|
-
endErrorEl.setAttribute("role", "alert");
|
|
559
|
-
endErrorEl.hidden = true;
|
|
560
|
-
this._endErrorEl = endErrorEl;
|
|
561
|
-
const formErrorEl = document.createElement("div");
|
|
562
|
-
formErrorEl.className = "form-error";
|
|
563
|
-
formErrorEl.setAttribute("role", "alert");
|
|
564
|
-
formErrorEl.hidden = true;
|
|
565
|
-
this._formErrorEl = formErrorEl;
|
|
566
|
-
errorsWrap.append(startErrorEl, endErrorEl, formErrorEl);
|
|
567
|
-
footer.insertBefore(errorsWrap, actions);
|
|
568
611
|
const bannerWrap = document.createElement("div");
|
|
569
612
|
bannerWrap.className = "banner-wrap";
|
|
570
613
|
const banner = document.createElement("bv-alert-banner");
|
|
571
|
-
banner.setAttribute("variant", "warning");
|
|
572
614
|
banner.hidden = true;
|
|
573
|
-
this.
|
|
615
|
+
this._alertBannerEl = banner;
|
|
574
616
|
bannerWrap.appendChild(banner);
|
|
575
617
|
main.append(calsWrap, bannerWrap, footer);
|
|
576
618
|
picker.append(sidebar, main);
|
|
@@ -743,6 +785,7 @@ var BvDateRangePicker = class extends HTMLElement {
|
|
|
743
785
|
this._preset = "custom";
|
|
744
786
|
this._picking = true;
|
|
745
787
|
this._hover = "";
|
|
788
|
+
this._lastEditedField = "start";
|
|
746
789
|
} else {
|
|
747
790
|
if (iso < this._start) {
|
|
748
791
|
this._end = this._start;
|
|
@@ -753,6 +796,7 @@ var BvDateRangePicker = class extends HTMLElement {
|
|
|
753
796
|
this._picking = false;
|
|
754
797
|
this._hover = "";
|
|
755
798
|
this._preset = "custom";
|
|
799
|
+
this._lastEditedField = "end";
|
|
756
800
|
}
|
|
757
801
|
this._startRawInvalid = "";
|
|
758
802
|
this._endRawInvalid = "";
|
|
@@ -777,6 +821,7 @@ var BvDateRangePicker = class extends HTMLElement {
|
|
|
777
821
|
this._hover = "";
|
|
778
822
|
this._startRawInvalid = "";
|
|
779
823
|
this._endRawInvalid = "";
|
|
824
|
+
this._lastEditedField = null;
|
|
780
825
|
if (start) {
|
|
781
826
|
const parts = start.split("-");
|
|
782
827
|
const y = parseInt(parts[0], 10);
|
|
@@ -792,6 +837,7 @@ var BvDateRangePicker = class extends HTMLElement {
|
|
|
792
837
|
this._syncValidity();
|
|
793
838
|
}
|
|
794
839
|
_onInputBlur(input, field) {
|
|
840
|
+
this._lastEditedField = field;
|
|
795
841
|
const raw = input.value;
|
|
796
842
|
if (raw === "") {
|
|
797
843
|
if (field === "start") {
|
|
@@ -855,9 +901,9 @@ var BvDateRangePicker = class extends HTMLElement {
|
|
|
855
901
|
},
|
|
856
902
|
cfg
|
|
857
903
|
);
|
|
858
|
-
this.
|
|
904
|
+
this._paintInputInvalidState();
|
|
905
|
+
this._applyAlertBanner(cfg);
|
|
859
906
|
this._applyApplyState();
|
|
860
|
-
this._applyBannerState();
|
|
861
907
|
this._applyCalendarDisabledState();
|
|
862
908
|
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) {
|
|
863
909
|
this.dispatchEvent(
|
|
@@ -879,88 +925,140 @@ var BvDateRangePicker = class extends HTMLElement {
|
|
|
879
925
|
max,
|
|
880
926
|
maxRangeYears: Number.isFinite(maxRangeYears) ? maxRangeYears : void 0,
|
|
881
927
|
validateOrder: this.hasAttribute("validate-order"),
|
|
882
|
-
validateFormat: this.hasAttribute("validate-format")
|
|
928
|
+
validateFormat: this.hasAttribute("validate-format"),
|
|
929
|
+
minSeverity: normalizeSeverity(this.getAttribute("error-min-severity")),
|
|
930
|
+
maxSeverity: normalizeSeverity(this.getAttribute("error-max-severity")),
|
|
931
|
+
formatSeverity: normalizeSeverity(this.getAttribute("error-format-severity")),
|
|
932
|
+
orderSeverity: normalizeSeverity(this.getAttribute("error-order-severity")),
|
|
933
|
+
rangeLimitSeverity: normalizeSeverity(this.getAttribute("range-limit-severity"))
|
|
883
934
|
};
|
|
884
935
|
}
|
|
885
|
-
|
|
936
|
+
/**
|
|
937
|
+
* BTN-10312 — apply the `.invalid` + `aria-invalid` state to input fields
|
|
938
|
+
* whose field-level error is currently blocking (severity `'error'`).
|
|
939
|
+
* Non-blocking severities (warning/primary/success) do not paint the input
|
|
940
|
+
* red so the visual matches the semantic ("not a fatal error").
|
|
941
|
+
*/
|
|
942
|
+
_paintInputInvalidState() {
|
|
886
943
|
const v = this._validity;
|
|
887
|
-
this.
|
|
888
|
-
this.
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
944
|
+
const cfg = this._readConfig();
|
|
945
|
+
this._paintOne(this._startInput, v.startError ? severityOfFieldError(v.startError, cfg) : null);
|
|
946
|
+
this._paintOne(this._endInput, v.endError ? severityOfFieldError(v.endError, cfg) : null);
|
|
947
|
+
}
|
|
948
|
+
_paintOne(input, sev) {
|
|
949
|
+
if (!input) return;
|
|
950
|
+
if (sev === "error") {
|
|
951
|
+
input.classList.add("invalid");
|
|
952
|
+
input.setAttribute("aria-invalid", "true");
|
|
953
|
+
} else {
|
|
954
|
+
input.classList.remove("invalid");
|
|
955
|
+
input.removeAttribute("aria-invalid");
|
|
897
956
|
}
|
|
898
957
|
}
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
958
|
+
/**
|
|
959
|
+
* BTN-10312 — single alert banner. Picks the "active" error following the
|
|
960
|
+
* precedence:
|
|
961
|
+
*
|
|
962
|
+
* 1. If a form error is active, show it (form errors don't coexist with
|
|
963
|
+
* field errors by validator design).
|
|
964
|
+
* 2. If both field errors are active, show the one for
|
|
965
|
+
* `_lastEditedField` (falling back to `start` if `null`).
|
|
966
|
+
* 3. Else show whichever single field error is active.
|
|
967
|
+
* 4. Else hide the banner.
|
|
968
|
+
*
|
|
969
|
+
* `show-range-limit-banner="false"` still suppresses the range-limit
|
|
970
|
+
* banner specifically (existing consumer contract from BTN-10191).
|
|
971
|
+
*/
|
|
972
|
+
_applyAlertBanner(cfg) {
|
|
973
|
+
if (!this._alertBannerEl) return;
|
|
974
|
+
const pickerEl = this.shadowRoot.querySelector(".picker");
|
|
975
|
+
const active = this._resolveActiveError();
|
|
976
|
+
if (!active) {
|
|
977
|
+
this._alertBannerEl.hidden = true;
|
|
978
|
+
this._alertBannerEl.removeAttribute("heading");
|
|
979
|
+
this._alertBannerEl.removeAttribute("message");
|
|
980
|
+
pickerEl == null ? void 0 : pickerEl.classList.remove("picker--with-banner");
|
|
981
|
+
return;
|
|
908
982
|
}
|
|
909
|
-
if (
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
983
|
+
if (active.kind === "form" && active.error === "range-limit" && this.getAttribute("show-range-limit-banner") === "false") {
|
|
984
|
+
this._alertBannerEl.hidden = true;
|
|
985
|
+
pickerEl == null ? void 0 : pickerEl.classList.remove("picker--with-banner");
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
const variant = active.kind === "field" ? severityOfFieldError(active.error, cfg) : severityOfFormError(active.error, cfg);
|
|
989
|
+
this._alertBannerEl.setAttribute("variant", variant);
|
|
990
|
+
if (active.kind === "form" && active.error === "range-limit") {
|
|
991
|
+
const yearsAttr = this.getAttribute("max-range-years") ?? "";
|
|
992
|
+
const heading = this.getAttribute("range-limit-heading") || (yearsAttr ? `Date range exceeds ${yearsAttr} years` : "Date range exceeds the maximum");
|
|
993
|
+
const message = this.getAttribute("range-limit-message") || "Only CSV download is available for this date range. Live report generation is not supported.";
|
|
994
|
+
this._alertBannerEl.setAttribute("heading", heading.replace(/\{N\}/g, yearsAttr));
|
|
995
|
+
this._alertBannerEl.setAttribute("message", message.replace(/\{N\}/g, yearsAttr));
|
|
996
|
+
} else {
|
|
997
|
+
this._alertBannerEl.removeAttribute("heading");
|
|
998
|
+
const message = active.kind === "field" ? this._resolveFieldError(active.error, active.field) : this._resolveOrderError();
|
|
999
|
+
this._alertBannerEl.setAttribute("message", message);
|
|
1000
|
+
}
|
|
1001
|
+
this._alertBannerEl.hidden = false;
|
|
1002
|
+
pickerEl == null ? void 0 : pickerEl.classList.add("picker--with-banner");
|
|
1003
|
+
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Pick which error the banner should reflect. See `_applyAlertBanner` for
|
|
1006
|
+
* the precedence rules.
|
|
1007
|
+
*/
|
|
1008
|
+
_resolveActiveError() {
|
|
1009
|
+
const v = this._validity;
|
|
1010
|
+
if (v.formError) return { kind: "form", error: v.formError };
|
|
1011
|
+
if (v.startError && v.endError) {
|
|
1012
|
+
const winner = this._lastEditedField ?? "start";
|
|
1013
|
+
return {
|
|
1014
|
+
kind: "field",
|
|
1015
|
+
error: winner === "start" ? v.startError : v.endError,
|
|
1016
|
+
field: winner
|
|
1017
|
+
};
|
|
917
1018
|
}
|
|
1019
|
+
if (v.startError) return { kind: "field", error: v.startError, field: "start" };
|
|
1020
|
+
if (v.endError) return { kind: "field", error: v.endError, field: "end" };
|
|
1021
|
+
return null;
|
|
918
1022
|
}
|
|
919
1023
|
_resolveFieldError(err, field) {
|
|
920
1024
|
if (err === "format") {
|
|
921
|
-
|
|
1025
|
+
const custom2 = this.getAttribute("error-format");
|
|
1026
|
+
if (custom2) return this._interpolateField(custom2, field);
|
|
1027
|
+
return `Enter a valid ${field} date`;
|
|
922
1028
|
}
|
|
923
1029
|
if (err === "min") {
|
|
924
1030
|
const minIso = this.getAttribute("min") ?? "";
|
|
925
1031
|
const minDisplay = formatDisplay(minIso);
|
|
926
1032
|
const custom2 = this.getAttribute("error-min");
|
|
927
|
-
if (custom2) return
|
|
1033
|
+
if (custom2) return this._interpolateField(custom2, field).replace(/\{min\}/g, minDisplay);
|
|
928
1034
|
return `${capitalize(field)} date must be on or after ${minDisplay}`;
|
|
929
1035
|
}
|
|
930
1036
|
const maxAttr = this.getAttribute("max") ?? "";
|
|
931
1037
|
const resolvedMax = maxAttr === "today" ? isoToday() : maxAttr;
|
|
932
1038
|
const maxDisplay = formatDisplay(resolvedMax);
|
|
933
1039
|
const custom = this.getAttribute("error-max");
|
|
934
|
-
if (custom) return
|
|
1040
|
+
if (custom) return this._interpolateField(custom, field).replace(/\{max\}/g, maxDisplay);
|
|
935
1041
|
if (maxAttr === "today") return `${capitalize(field)} date cannot be in the future`;
|
|
936
1042
|
return `${capitalize(field)} date cannot be after ${maxDisplay}`;
|
|
937
1043
|
}
|
|
1044
|
+
/**
|
|
1045
|
+
* BTN-10312 — interpolate `{field}` (always lowercase) and `{Field}` (always
|
|
1046
|
+
* capitalized) in a user-provided template. Before this change, the historical
|
|
1047
|
+
* behavior was that `{field}` in custom copy resolved lowercase while the
|
|
1048
|
+
* built-in defaults capitalized inline — inconsistent. Now the split is
|
|
1049
|
+
* explicit: `{field}` = lowercase, `{Field}` = capitalized.
|
|
1050
|
+
*/
|
|
1051
|
+
_interpolateField(template, field) {
|
|
1052
|
+
return template.replace(/\{Field\}/g, capitalize(field)).replace(/\{field\}/g, field);
|
|
1053
|
+
}
|
|
938
1054
|
_resolveOrderError() {
|
|
939
|
-
return this.getAttribute("error-order")
|
|
1055
|
+
return this.getAttribute("error-order") || "Start date must be on or before end date";
|
|
940
1056
|
}
|
|
941
1057
|
_applyApplyState() {
|
|
942
1058
|
if (this._applyBtn) {
|
|
943
1059
|
this._applyBtn.disabled = !this._validity.applyEnabled;
|
|
944
1060
|
}
|
|
945
1061
|
}
|
|
946
|
-
_applyBannerState() {
|
|
947
|
-
if (!this._bannerEl) return;
|
|
948
|
-
const pickerEl = this.shadowRoot.querySelector(".picker");
|
|
949
|
-
const bannerAllowed = this.getAttribute("show-range-limit-banner") !== "false";
|
|
950
|
-
const show = this._validity.formError === "range-limit" && bannerAllowed;
|
|
951
|
-
if (show) {
|
|
952
|
-
const yearsAttr = this.getAttribute("max-range-years") ?? "";
|
|
953
|
-
const heading = this.getAttribute("range-limit-heading") ?? (yearsAttr ? `Date range exceeds ${yearsAttr} years` : "Date range exceeds the maximum");
|
|
954
|
-
const message = this.getAttribute("range-limit-message") ?? "Only CSV download is available for this date range. Live report generation is not supported.";
|
|
955
|
-
this._bannerEl.setAttribute("heading", heading.replace("{N}", yearsAttr));
|
|
956
|
-
this._bannerEl.setAttribute("message", message.replace("{N}", yearsAttr));
|
|
957
|
-
this._bannerEl.hidden = false;
|
|
958
|
-
pickerEl == null ? void 0 : pickerEl.classList.add("picker--with-banner");
|
|
959
|
-
} else {
|
|
960
|
-
this._bannerEl.hidden = true;
|
|
961
|
-
pickerEl == null ? void 0 : pickerEl.classList.remove("picker--with-banner");
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
1062
|
_applyCalendarDisabledState() {
|
|
965
1063
|
const minIso = this.getAttribute("min") || "";
|
|
966
1064
|
const maxAttr = this.getAttribute("max") || "";
|
|
@@ -996,6 +1094,7 @@ var BvDateRangePicker = class extends HTMLElement {
|
|
|
996
1094
|
var _a;
|
|
997
1095
|
const ev = e;
|
|
998
1096
|
if ((_a = ev.inputType) == null ? void 0 : _a.startsWith("delete")) return;
|
|
1097
|
+
this._lastEditedField = field;
|
|
999
1098
|
const digits = input.value.replace(/[^\d]/g, "");
|
|
1000
1099
|
let formatted;
|
|
1001
1100
|
if (digits.length <= 2) {
|
|
@@ -21,8 +21,7 @@ function adoptStyles(shadow, css) {
|
|
|
21
21
|
|
|
22
22
|
// components/_shared/dropdown-controller.ts
|
|
23
23
|
function bindDropdownController(host, options = {}) {
|
|
24
|
-
|
|
25
|
-
if (!shadow) {
|
|
24
|
+
if (!host.shadowRoot) {
|
|
26
25
|
throw new Error("bindDropdownController requires the host to have an open shadow root.");
|
|
27
26
|
}
|
|
28
27
|
const syncExpanded = () => {
|
|
@@ -37,24 +36,34 @@ function bindDropdownController(host, options = {}) {
|
|
|
37
36
|
syncExpanded();
|
|
38
37
|
host.dispatchEvent(new CustomEvent("bv-open", { bubbles: true, composed: true }));
|
|
39
38
|
};
|
|
40
|
-
const
|
|
39
|
+
const closeWithReason = (reason) => {
|
|
40
|
+
var _a;
|
|
41
41
|
if (!host.hasAttribute("open")) return;
|
|
42
|
+
if (options.canClose && !options.canClose(reason)) return;
|
|
42
43
|
host.removeAttribute("open");
|
|
43
44
|
syncExpanded();
|
|
45
|
+
host.dispatchEvent(
|
|
46
|
+
new CustomEvent("bv-dropdown-close", {
|
|
47
|
+
bubbles: true,
|
|
48
|
+
composed: true,
|
|
49
|
+
detail: { reason }
|
|
50
|
+
})
|
|
51
|
+
);
|
|
52
|
+
(_a = options.onAfterClose) == null ? void 0 : _a.call(options);
|
|
44
53
|
};
|
|
54
|
+
const close = () => closeWithReason("programmatic");
|
|
45
55
|
const toggle = () => {
|
|
46
|
-
if (host.hasAttribute("open"))
|
|
56
|
+
if (host.hasAttribute("open")) closeWithReason("programmatic");
|
|
47
57
|
else open();
|
|
48
58
|
};
|
|
49
59
|
const offClick = (e) => {
|
|
50
60
|
if (!host.hasAttribute("open")) return;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
close();
|
|
61
|
+
if (e.composedPath().includes(host)) return;
|
|
62
|
+
closeWithReason("outside-click");
|
|
54
63
|
};
|
|
55
64
|
document.addEventListener("click", offClick);
|
|
56
65
|
const onKeyDown = (e) => {
|
|
57
|
-
if (e.key === "Escape")
|
|
66
|
+
if (e.key === "Escape") closeWithReason("escape");
|
|
58
67
|
};
|
|
59
68
|
host.addEventListener("keydown", onKeyDown);
|
|
60
69
|
const observer = new MutationObserver(syncExpanded);
|
|
@@ -185,9 +194,8 @@ var BvFilterDropdown = class extends HTMLElement {
|
|
|
185
194
|
chevron.className = "trigger-chevron";
|
|
186
195
|
chevron.innerHTML = CHEVRON_DOWN;
|
|
187
196
|
trigger.append(label, chevron);
|
|
188
|
-
trigger.addEventListener("click", (
|
|
197
|
+
trigger.addEventListener("click", () => {
|
|
189
198
|
var _a;
|
|
190
|
-
e.stopPropagation();
|
|
191
199
|
if (this.disabled) return;
|
|
192
200
|
(_a = this._dropdown) == null ? void 0 : _a.toggle();
|
|
193
201
|
});
|