@bluevolt-tech/lumen 2.2.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/dropdown-controller.d.ts +16 -0
- package/dist/components/bv-date-range-input/bv-date-range-input.js +8 -2
- package/dist/components/bv-filter-dropdown/bv-filter-dropdown.js +8 -2
- package/dist/components/bv-input-dropdown/bv-input-dropdown.js +8 -2
- 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 +8 -2
- package/dist/components/bv-split-button/bv-split-button.js +8 -2
- 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
|
@@ -50,6 +50,22 @@ export interface DropdownController {
|
|
|
50
50
|
* the state. The controller is the single source of truth; consuming
|
|
51
51
|
* components must NOT duplicate this sync in their own attributeChangedCallback.
|
|
52
52
|
* - `bv-open` is dispatched from the host when opening via `open()`/`toggle()`.
|
|
53
|
+
* - `bv-dropdown-close` is dispatched from the host on every successful close,
|
|
54
|
+
* with `detail: { reason }` matching the `CloseReason` union — consumers can
|
|
55
|
+
* use it to close sibling dropdowns or release focus/scroll locks. The name
|
|
56
|
+
* is prefixed to avoid colliding with `bv-modal` / `bv-course-detail-modal`,
|
|
57
|
+
* which dispatch a bare `bv-close` on dismissal — a plain `bv-close` here
|
|
58
|
+
* would bubble into ancestor `bv-close` listeners (e.g. a modal that
|
|
59
|
+
* contains a dropdown) and fire spuriously on every dropdown dismissal.
|
|
60
|
+
*
|
|
61
|
+
* **Consumers MUST NOT call `e.stopPropagation()` on the trigger click**
|
|
62
|
+
* (BTN-10408). The outside-click listener attached here on `document` uses
|
|
63
|
+
* `composedPath().includes(host)` to skip clicks that originated inside its
|
|
64
|
+
* own host — so it already ignores the trigger click without needing the
|
|
65
|
+
* event to be swallowed. Stopping propagation on the trigger prevents the
|
|
66
|
+
* `document`-level listeners attached by *sibling* controllers from
|
|
67
|
+
* receiving the click at all, which is why opening one dropdown while
|
|
68
|
+
* another is open used to leave both visible.
|
|
53
69
|
*
|
|
54
70
|
* The controller mutates the `open` attribute on the host. Consumers should
|
|
55
71
|
* drive their visual state (`.panel` visibility, focus management) from that
|
|
@@ -42,6 +42,13 @@ function bindDropdownController(host, options = {}) {
|
|
|
42
42
|
if (options.canClose && !options.canClose(reason)) return;
|
|
43
43
|
host.removeAttribute("open");
|
|
44
44
|
syncExpanded();
|
|
45
|
+
host.dispatchEvent(
|
|
46
|
+
new CustomEvent("bv-dropdown-close", {
|
|
47
|
+
bubbles: true,
|
|
48
|
+
composed: true,
|
|
49
|
+
detail: { reason }
|
|
50
|
+
})
|
|
51
|
+
);
|
|
45
52
|
(_a = options.onAfterClose) == null ? void 0 : _a.call(options);
|
|
46
53
|
};
|
|
47
54
|
const close = () => closeWithReason("programmatic");
|
|
@@ -1303,9 +1310,8 @@ var BvDateRangeInput = class extends HTMLElement {
|
|
|
1303
1310
|
icon.className = "trigger-icon";
|
|
1304
1311
|
icon.innerHTML = CALENDAR_ICON;
|
|
1305
1312
|
trigger.append(display, icon);
|
|
1306
|
-
trigger.addEventListener("click", (
|
|
1313
|
+
trigger.addEventListener("click", () => {
|
|
1307
1314
|
var _a;
|
|
1308
|
-
e.stopPropagation();
|
|
1309
1315
|
(_a = this._dropdown) == null ? void 0 : _a.toggle();
|
|
1310
1316
|
});
|
|
1311
1317
|
const popup = document.createElement("div");
|
|
@@ -42,6 +42,13 @@ function bindDropdownController(host, options = {}) {
|
|
|
42
42
|
if (options.canClose && !options.canClose(reason)) return;
|
|
43
43
|
host.removeAttribute("open");
|
|
44
44
|
syncExpanded();
|
|
45
|
+
host.dispatchEvent(
|
|
46
|
+
new CustomEvent("bv-dropdown-close", {
|
|
47
|
+
bubbles: true,
|
|
48
|
+
composed: true,
|
|
49
|
+
detail: { reason }
|
|
50
|
+
})
|
|
51
|
+
);
|
|
45
52
|
(_a = options.onAfterClose) == null ? void 0 : _a.call(options);
|
|
46
53
|
};
|
|
47
54
|
const close = () => closeWithReason("programmatic");
|
|
@@ -187,9 +194,8 @@ var BvFilterDropdown = class extends HTMLElement {
|
|
|
187
194
|
chevron.className = "trigger-chevron";
|
|
188
195
|
chevron.innerHTML = CHEVRON_DOWN;
|
|
189
196
|
trigger.append(label, chevron);
|
|
190
|
-
trigger.addEventListener("click", (
|
|
197
|
+
trigger.addEventListener("click", () => {
|
|
191
198
|
var _a;
|
|
192
|
-
e.stopPropagation();
|
|
193
199
|
if (this.disabled) return;
|
|
194
200
|
(_a = this._dropdown) == null ? void 0 : _a.toggle();
|
|
195
201
|
});
|
|
@@ -42,6 +42,13 @@ function bindDropdownController(host, options = {}) {
|
|
|
42
42
|
if (options.canClose && !options.canClose(reason)) return;
|
|
43
43
|
host.removeAttribute("open");
|
|
44
44
|
syncExpanded();
|
|
45
|
+
host.dispatchEvent(
|
|
46
|
+
new CustomEvent("bv-dropdown-close", {
|
|
47
|
+
bubbles: true,
|
|
48
|
+
composed: true,
|
|
49
|
+
detail: { reason }
|
|
50
|
+
})
|
|
51
|
+
);
|
|
45
52
|
(_a = options.onAfterClose) == null ? void 0 : _a.call(options);
|
|
46
53
|
};
|
|
47
54
|
const close = () => closeWithReason("programmatic");
|
|
@@ -343,9 +350,8 @@ var BvInputDropdown = class extends HTMLElement {
|
|
|
343
350
|
trigger.setAttribute("aria-haspopup", "listbox");
|
|
344
351
|
trigger.setAttribute("aria-expanded", "false");
|
|
345
352
|
trigger.setAttribute("part", "trigger");
|
|
346
|
-
trigger.addEventListener("click", (
|
|
353
|
+
trigger.addEventListener("click", () => {
|
|
347
354
|
var _a;
|
|
348
|
-
e.stopPropagation();
|
|
349
355
|
if (!this.disabled) (_a = this._dropdown) == null ? void 0 : _a.toggle();
|
|
350
356
|
});
|
|
351
357
|
const triggerLabel = document.createElement("span");
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import '../bv-searchbox/bv-searchbox.ts';
|
|
2
|
+
import '../bv-segmented-control/bv-segmented-control.ts';
|
|
3
|
+
import '../bv-toggle/bv-toggle.ts';
|
|
4
|
+
import '../bv-button/bv-button.ts';
|
|
5
|
+
export type SearchPickerMode = 'include' | 'exclude';
|
|
6
|
+
export interface SearchPickerItem {
|
|
7
|
+
id: string;
|
|
8
|
+
name: string;
|
|
9
|
+
inactive?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Detail on the `bv-search` event.
|
|
13
|
+
*
|
|
14
|
+
* Note: `showInactive` is intentionally NOT on this payload — read it directly
|
|
15
|
+
* off the picker (`picker.showInactive`) inside the handler. It changes via its
|
|
16
|
+
* own event (`bv-show-inactive-change`), so mixing it into `bv-search` would
|
|
17
|
+
* emit an update on every keystroke rather than only when it actually changes.
|
|
18
|
+
*/
|
|
19
|
+
export interface SearchPickerSearchDetail {
|
|
20
|
+
/** Trimmed user query. Only emitted past `min-query-length`. */
|
|
21
|
+
query: string;
|
|
22
|
+
/** Monotonic id the consumer echoes back via `results-request-id` so the picker can drop stale responses. */
|
|
23
|
+
requestId: number;
|
|
24
|
+
}
|
|
25
|
+
export interface SearchPickerBasketChangeDetail {
|
|
26
|
+
/** Staged mode at the moment of the change (before Apply). */
|
|
27
|
+
mode: SearchPickerMode;
|
|
28
|
+
/** Staged basket at the moment of the change (before Apply). Consumer sees every add/remove/clear. */
|
|
29
|
+
basket: SearchPickerItem[];
|
|
30
|
+
}
|
|
31
|
+
export interface SearchPickerModeChangeDetail {
|
|
32
|
+
/** Staged mode after the toggle (before Apply). */
|
|
33
|
+
mode: SearchPickerMode;
|
|
34
|
+
}
|
|
35
|
+
export interface SearchPickerShowInactiveChangeDetail {
|
|
36
|
+
showInactive: boolean;
|
|
37
|
+
}
|
|
38
|
+
export interface SearchPickerApplyDetail {
|
|
39
|
+
/** Committed mode. */
|
|
40
|
+
mode: SearchPickerMode;
|
|
41
|
+
/** Committed basket. */
|
|
42
|
+
basket: SearchPickerItem[];
|
|
43
|
+
}
|
|
44
|
+
export interface SearchPickerClearedDetail {
|
|
45
|
+
/** Consumer-supplied string identifying why the basket was cleared (e.g. `'category-changed'`, `'group-changed'`). */
|
|
46
|
+
reason: string;
|
|
47
|
+
}
|
|
48
|
+
export declare class BvSearchPicker extends HTMLElement {
|
|
49
|
+
static readonly observedAttributes: readonly ["label", "entity-label", "search-placeholder", "inactive-toggle-label", "mode", "min-query-length", "debounce-ms", "disabled", "loading", "show-inactive", "open", "results", "results-total", "results-request-id", "basket", "label-all", "label-count", "label-except", "label-only-these", "label-all-except", "label-empty-basket", "label-clear-all", "label-min-chars", "label-no-results", "label-results-cap", "label-cleared"];
|
|
50
|
+
private _rendered;
|
|
51
|
+
private _query;
|
|
52
|
+
/**
|
|
53
|
+
* BTN-10322 — staged basket edited inside the panel. Kept separate from the
|
|
54
|
+
* committed `basket` attribute so the trigger pill doesn't flicker mid-edit
|
|
55
|
+
* (add/remove doesn't touch it) and so a cascade reset (`reset()`) has a
|
|
56
|
+
* clean staged copy to reseed from. Committed on Apply, AND automatically
|
|
57
|
+
* whenever the panel closes any other way (Escape / outside-click / trigger
|
|
58
|
+
* toggle) — see `onAfterClose` in `_render()`. Closing is what "confirms"
|
|
59
|
+
* the edit; Apply is just the explicit, no-need-to-move-your-mouse way to
|
|
60
|
+
* do the same thing.
|
|
61
|
+
*/
|
|
62
|
+
private _stagedBasket;
|
|
63
|
+
/** BTN-10322 — staged mode edited inside the panel. Committed on close, same as `_stagedBasket`. */
|
|
64
|
+
private _stagedMode;
|
|
65
|
+
/** BTN-10322 — monotonic id emitted with `bv-search`. The consumer echoes it via `results-request-id`; stale responses drop silently. */
|
|
66
|
+
private _searchRequestId;
|
|
67
|
+
/** BTN-10322 — timer handle for the internal debounce on `bv-search`. */
|
|
68
|
+
private _searchDebounceHandle;
|
|
69
|
+
/**
|
|
70
|
+
* BTN-10322 — the reason to display in the cascade cleared affordance,
|
|
71
|
+
* or `null` when the banner is hidden. Set by `reset({ reason })`,
|
|
72
|
+
* cleared when the user next opens the panel or explicitly dismisses.
|
|
73
|
+
*/
|
|
74
|
+
private _clearedReason;
|
|
75
|
+
private _labelEl;
|
|
76
|
+
private _triggerEl;
|
|
77
|
+
private _panelEl;
|
|
78
|
+
private _pillEl;
|
|
79
|
+
private _pillTextEl;
|
|
80
|
+
private _pillClearEl;
|
|
81
|
+
private _modeControlEl;
|
|
82
|
+
private _inactiveToggleEl;
|
|
83
|
+
private _searchEl;
|
|
84
|
+
private _resultsRowsEl;
|
|
85
|
+
private _resultsEmptyEl;
|
|
86
|
+
private _resultsLimitNoteEl;
|
|
87
|
+
private _basketHeaderLabelEl;
|
|
88
|
+
private _basketClearAllEl;
|
|
89
|
+
private _basketListEl;
|
|
90
|
+
private _basketEmptyEl;
|
|
91
|
+
private _resultsListEl;
|
|
92
|
+
private _clearedAffordanceEl;
|
|
93
|
+
private _clearedAffordanceMsgEl;
|
|
94
|
+
private _dropdown;
|
|
95
|
+
constructor();
|
|
96
|
+
get mode(): SearchPickerMode;
|
|
97
|
+
/**
|
|
98
|
+
* Setting `mode` from outside is treated as a commit — it aligns the staged
|
|
99
|
+
* mode inside the panel with the new committed value. Useful for initialising
|
|
100
|
+
* from URL params or reverting from a saved report configuration. If the panel
|
|
101
|
+
* is currently open, the segmented control snaps to the new mode immediately;
|
|
102
|
+
* the user is expected to see this as an intentional external change, not an
|
|
103
|
+
* accidental override of their in-flight edit.
|
|
104
|
+
*/
|
|
105
|
+
set mode(v: SearchPickerMode);
|
|
106
|
+
get entityLabel(): string;
|
|
107
|
+
set entityLabel(v: string);
|
|
108
|
+
get minQueryLength(): number;
|
|
109
|
+
set minQueryLength(v: number);
|
|
110
|
+
get disabled(): boolean;
|
|
111
|
+
set disabled(v: boolean);
|
|
112
|
+
get loading(): boolean;
|
|
113
|
+
set loading(v: boolean);
|
|
114
|
+
get showInactive(): boolean;
|
|
115
|
+
set showInactive(v: boolean);
|
|
116
|
+
get results(): SearchPickerItem[];
|
|
117
|
+
set results(v: SearchPickerItem[]);
|
|
118
|
+
get resultsTotal(): number | null;
|
|
119
|
+
set resultsTotal(v: number | null);
|
|
120
|
+
get debounceMs(): number;
|
|
121
|
+
set debounceMs(v: number);
|
|
122
|
+
get basket(): SearchPickerItem[];
|
|
123
|
+
set basket(v: SearchPickerItem[]);
|
|
124
|
+
connectedCallback(): void;
|
|
125
|
+
disconnectedCallback(): void;
|
|
126
|
+
attributeChangedCallback(name: string, _old: string | null, _val: string | null): void;
|
|
127
|
+
/**
|
|
128
|
+
* BTN-10322 — request-id guard. When `results` (or friends) are set from
|
|
129
|
+
* the consumer's async search, `results-request-id` must match the most
|
|
130
|
+
* recent id emitted by our own `bv-search`. Any mismatch means the
|
|
131
|
+
* response arrived out of order (user typed faster than the network) and
|
|
132
|
+
* we drop it silently to avoid rendering stale rows.
|
|
133
|
+
*
|
|
134
|
+
* `results-request-id` unset entirely is fine — consumers that don't
|
|
135
|
+
* bother with the guard behave as before (no correlation).
|
|
136
|
+
*/
|
|
137
|
+
private _syncResultsWithGuard;
|
|
138
|
+
private _render;
|
|
139
|
+
private _positionPanel;
|
|
140
|
+
private _syncLabel;
|
|
141
|
+
private _syncModeOptions;
|
|
142
|
+
private _syncSearchPlaceholder;
|
|
143
|
+
private _syncInactiveToggleLabel;
|
|
144
|
+
private _syncMode;
|
|
145
|
+
private _syncDisabled;
|
|
146
|
+
private _syncShowInactive;
|
|
147
|
+
private _syncResults;
|
|
148
|
+
private _syncResultsEmptyText;
|
|
149
|
+
private _syncResultsLimitNote;
|
|
150
|
+
private _syncBasket;
|
|
151
|
+
private _syncBasketHeaderLabel;
|
|
152
|
+
private _syncBasketRowColors;
|
|
153
|
+
private _syncBasketEmptyText;
|
|
154
|
+
private _syncTriggerSummary;
|
|
155
|
+
private _interp;
|
|
156
|
+
private _addBasket;
|
|
157
|
+
private _removeBasket;
|
|
158
|
+
private _clearAllStaged;
|
|
159
|
+
/**
|
|
160
|
+
* The trigger's pill-clear (`x`) is an explicit user action that means "back
|
|
161
|
+
* to the neutral state". Unlike the basket's "Clear all" (which only touches
|
|
162
|
+
* staged), this commits immediately — the pill lives on the trigger, which
|
|
163
|
+
* only reflects committed state. Dispatches `bv-apply` because a commit
|
|
164
|
+
* happened.
|
|
165
|
+
*/
|
|
166
|
+
private _clearCommitted;
|
|
167
|
+
private _stagedDiffersFromCommitted;
|
|
168
|
+
private _commitStaged;
|
|
169
|
+
private _apply;
|
|
170
|
+
private _dispatchBasketChange;
|
|
171
|
+
private _dispatchModeChange;
|
|
172
|
+
private _dispatchShowInactiveChange;
|
|
173
|
+
private _dispatchApply;
|
|
174
|
+
private _dispatchSearch;
|
|
175
|
+
private _scheduleSearchDispatch;
|
|
176
|
+
/**
|
|
177
|
+
* BTN-10322 — Arrow Up/Down navigation between result rows; Enter or Space
|
|
178
|
+
* adds the currently-focused row to the staged basket. Home/End jump to
|
|
179
|
+
* first/last. Escape delegates to the shared dropdown-controller (already
|
|
180
|
+
* wired on the host).
|
|
181
|
+
*
|
|
182
|
+
* `role="option"` rows use `aria-selected` only to communicate focus
|
|
183
|
+
* position to assistive tech — clicking / Enter also fires the add flow,
|
|
184
|
+
* which then removes the row from the results list.
|
|
185
|
+
*/
|
|
186
|
+
private _onResultsKeyDown;
|
|
187
|
+
/**
|
|
188
|
+
* BTN-10322 — count of the currently committed basket. Consumers can read
|
|
189
|
+
* this to render an external counter badge without duplicating state.
|
|
190
|
+
*/
|
|
191
|
+
get count(): number;
|
|
192
|
+
/**
|
|
193
|
+
* BTN-10322 — cascade reset from outside the panel. This is THE canonical
|
|
194
|
+
* way for a consumer to clear the picker in response to an upstream filter
|
|
195
|
+
* change (e.g. CET Report when Categories or Groups change). It:
|
|
196
|
+
*
|
|
197
|
+
* - clears the committed `basket` and the staged edit,
|
|
198
|
+
* - clears `results` + `resultsTotal` (post-cascade the last search's
|
|
199
|
+
* rows are stale — leaving them visible next to the cleared banner
|
|
200
|
+
* is confusing),
|
|
201
|
+
* - clears the internal search query,
|
|
202
|
+
* - surfaces the "cleared" affordance banner with `{ reason }` interpolated
|
|
203
|
+
* into the copy,
|
|
204
|
+
* - dispatches `bv-cleared` with `{ reason }`.
|
|
205
|
+
*
|
|
206
|
+
* Does NOT dispatch `bv-apply` — this is not a user commit, it's an
|
|
207
|
+
* out-of-band cascade. The consumer that triggered it already knows the
|
|
208
|
+
* basket was cleared.
|
|
209
|
+
*
|
|
210
|
+
* The affordance auto-hides the next time the user opens the panel or
|
|
211
|
+
* dismisses it explicitly via the banner's close button.
|
|
212
|
+
*
|
|
213
|
+
* **Prefer this over `element.basket = []`** — the setter is silent (no
|
|
214
|
+
* bv-cleared, no visible affordance) and is intended for state restoration
|
|
215
|
+
* from URL/deep-link, not for cascade reset.
|
|
216
|
+
*/
|
|
217
|
+
reset({ reason }: {
|
|
218
|
+
reason: string;
|
|
219
|
+
}): void;
|
|
220
|
+
private _syncClearedAffordance;
|
|
221
|
+
private _dismissClearedAffordance;
|
|
222
|
+
}
|