@unofficialbox/box-open-elements 0.3.0 → 0.4.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/actions/link-button.d.ts +9 -0
- package/dist/components/actions/link-button.js +55 -4
- package/dist/components/collections/carousel.d.ts +7 -0
- package/dist/components/collections/carousel.js +71 -4
- package/dist/components/collections/datalist-item.d.ts +6 -0
- package/dist/components/collections/datalist-item.js +39 -1
- package/dist/components/collections/draggable-list.js +1 -1
- package/dist/components/collections/grid-view.js +7 -3
- package/dist/components/feedback/alert.d.ts +2 -0
- package/dist/components/feedback/alert.js +30 -1
- package/dist/components/feedback/badge.d.ts +10 -0
- package/dist/components/feedback/badge.js +70 -4
- package/dist/components/feedback/chip.d.ts +5 -0
- package/dist/components/feedback/chip.js +74 -2
- package/dist/components/feedback/error-mask.d.ts +2 -0
- package/dist/components/feedback/error-mask.js +23 -0
- package/dist/components/feedback/help-text.js +7 -0
- package/dist/components/feedback/spinner.d.ts +4 -0
- package/dist/components/feedback/spinner.js +30 -2
- package/dist/components/feedback/toast.d.ts +5 -0
- package/dist/components/feedback/toast.js +59 -11
- package/dist/components/forms/category-selector.d.ts +11 -0
- package/dist/components/forms/category-selector.js +211 -17
- package/dist/components/forms/checkbox.d.ts +6 -0
- package/dist/components/forms/checkbox.js +58 -1
- package/dist/components/forms/combobox.d.ts +30 -1
- package/dist/components/forms/combobox.js +348 -44
- package/dist/components/forms/date-field.d.ts +7 -0
- package/dist/components/forms/date-field.js +73 -1
- package/dist/components/forms/dropdown.d.ts +5 -0
- package/dist/components/forms/dropdown.js +32 -3
- package/dist/components/forms/radio-group.d.ts +9 -8
- package/dist/components/forms/radio-group.js +43 -6
- package/dist/components/forms/search-field.d.ts +8 -0
- package/dist/components/forms/search-field.js +55 -13
- package/dist/components/forms/select.d.ts +18 -8
- package/dist/components/forms/select.js +104 -14
- package/dist/components/forms/text-field.d.ts +13 -0
- package/dist/components/forms/text-field.js +136 -1
- package/dist/components/forms/time-field.d.ts +11 -0
- package/dist/components/forms/time-field.js +51 -0
- package/dist/components/identity/avatar.d.ts +4 -0
- package/dist/components/identity/avatar.js +65 -1
- package/dist/components/identity/contact-datalist-item.d.ts +8 -0
- package/dist/components/identity/contact-datalist-item.js +71 -2
- package/dist/components/layout/nav-sidebar.js +23 -0
- package/dist/components/layout/sidebar-toggle-button.d.ts +3 -0
- package/dist/components/layout/sidebar-toggle-button.js +21 -2
- package/dist/components/navigation/accordion.d.ts +3 -0
- package/dist/components/navigation/accordion.js +17 -2
- package/dist/components/overlays/guide-tooltip.d.ts +51 -0
- package/dist/components/overlays/guide-tooltip.js +361 -0
- package/dist/components/overlays/tooltip.d.ts +10 -0
- package/dist/components/overlays/tooltip.js +74 -11
- package/dist/foundations/overlay/positioning.js +5 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +1 -1
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { BaseElement } from "../../core/index.js";
|
|
2
2
|
import { boeMotionDuration, boeMotionEasing } from "../../foundations/motion/index.js";
|
|
3
|
+
import { boeOverlay, boeRadius } from "../../foundations/geometry/index.js";
|
|
4
|
+
import { boeNeutralInteractiveStyles } from "../../foundations/tokens/index.js";
|
|
5
|
+
import { parsePlacement, trackAnchor, } from "../../foundations/overlay/index.js";
|
|
3
6
|
const DEFAULT_TAG_NAME = "box-category-selector";
|
|
4
7
|
const escapeHtml = (value) => value
|
|
5
8
|
.replaceAll("&", "&")
|
|
@@ -59,6 +62,50 @@ const categorySelectorStyles = `
|
|
|
59
62
|
color: var(--boe-token-text-text-secondary, #6f6f6f);
|
|
60
63
|
font-size: 0.86rem;
|
|
61
64
|
}
|
|
65
|
+
|
|
66
|
+
/* Overflow "More" menu (shown when options exceed max-links). */
|
|
67
|
+
[part="more-menu"] {
|
|
68
|
+
position: fixed;
|
|
69
|
+
inset-block-start: 0;
|
|
70
|
+
inset-inline-start: 0;
|
|
71
|
+
z-index: 40;
|
|
72
|
+
margin: 0;
|
|
73
|
+
padding: ${boeOverlay.padding};
|
|
74
|
+
list-style: none;
|
|
75
|
+
min-inline-size: 10rem;
|
|
76
|
+
max-block-size: 16rem;
|
|
77
|
+
overflow-y: auto;
|
|
78
|
+
border: ${boeOverlay.border};
|
|
79
|
+
border-radius: ${boeOverlay.radius};
|
|
80
|
+
background: var(--boe-token-surface-surface, #ffffff);
|
|
81
|
+
box-shadow: ${boeOverlay.shadow};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
[part="more-menu"][hidden] {
|
|
85
|
+
display: none;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
[part="more-item"] {
|
|
89
|
+
appearance: none;
|
|
90
|
+
display: block;
|
|
91
|
+
inline-size: 100%;
|
|
92
|
+
text-align: start;
|
|
93
|
+
border: 0;
|
|
94
|
+
background: transparent;
|
|
95
|
+
color: var(--boe-token-text-text, #222222);
|
|
96
|
+
font: inherit;
|
|
97
|
+
font-size: 0.84rem;
|
|
98
|
+
padding: 6px 10px;
|
|
99
|
+
border-radius: ${boeRadius.med};
|
|
100
|
+
cursor: pointer;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
[part="more-item"][aria-checked="true"] {
|
|
104
|
+
color: var(--boe-token-surface-surface-brand, #0061d5);
|
|
105
|
+
font-weight: 700;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
${boeNeutralInteractiveStyles('[part="more-item"]')}
|
|
62
109
|
`;
|
|
63
110
|
/**
|
|
64
111
|
* A single-select category picker rendered as a horizontal radiogroup of pills —
|
|
@@ -69,12 +116,28 @@ const categorySelectorStyles = `
|
|
|
69
116
|
*/
|
|
70
117
|
export class BoxCategorySelectorElement extends BaseElement {
|
|
71
118
|
static get observedAttributes() {
|
|
72
|
-
return ["label", "options", "value"];
|
|
119
|
+
return ["label", "options", "value", "max-links"];
|
|
73
120
|
}
|
|
74
121
|
valueInternal = "";
|
|
75
122
|
lastOptionsJson = "";
|
|
76
123
|
hadFocus = false;
|
|
77
124
|
rootEl;
|
|
125
|
+
moreOpen = false;
|
|
126
|
+
positionCleanup = null;
|
|
127
|
+
onDocumentPointerDown = (event) => {
|
|
128
|
+
if (event.composedPath().includes(this)) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
this.closeMore();
|
|
132
|
+
};
|
|
133
|
+
/** Max inline categories before the rest collapse into a "More" menu (0 = no limit). */
|
|
134
|
+
get maxLinks() {
|
|
135
|
+
const raw = Number(this.getAttribute("max-links"));
|
|
136
|
+
return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : 0;
|
|
137
|
+
}
|
|
138
|
+
set maxLinks(value) {
|
|
139
|
+
this.setAttribute("max-links", String(value));
|
|
140
|
+
}
|
|
78
141
|
get label() {
|
|
79
142
|
return this.getAttribute("label") ?? "Categories";
|
|
80
143
|
}
|
|
@@ -116,6 +179,14 @@ export class BoxCategorySelectorElement extends BaseElement {
|
|
|
116
179
|
selectableOptions() {
|
|
117
180
|
return this.options.filter(option => !option.disabled);
|
|
118
181
|
}
|
|
182
|
+
splitOptions() {
|
|
183
|
+
const options = this.options;
|
|
184
|
+
const max = this.maxLinks;
|
|
185
|
+
if (max <= 0 || options.length <= max) {
|
|
186
|
+
return { visible: options, overflow: [] };
|
|
187
|
+
}
|
|
188
|
+
return { visible: options.slice(0, max), overflow: options.slice(max) };
|
|
189
|
+
}
|
|
119
190
|
select(value) {
|
|
120
191
|
if (!value || value === this.valueInternal) {
|
|
121
192
|
return;
|
|
@@ -158,20 +229,108 @@ export class BoxCategorySelectorElement extends BaseElement {
|
|
|
158
229
|
`;
|
|
159
230
|
this.rootEl = this.shadowRoot.querySelector('[part="root"]');
|
|
160
231
|
}
|
|
232
|
+
resolvedPlacement() {
|
|
233
|
+
return parsePlacement(this.getAttribute("placement")) ?? { side: "bottom", align: "end" };
|
|
234
|
+
}
|
|
235
|
+
openMore() {
|
|
236
|
+
const moreBtn = this.rootEl.querySelector('[data-more]');
|
|
237
|
+
const menu = this.rootEl.querySelector('[part="more-menu"]');
|
|
238
|
+
if (!moreBtn || !menu || this.moreOpen) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
this.moreOpen = true;
|
|
242
|
+
menu.hidden = false;
|
|
243
|
+
moreBtn.setAttribute("aria-expanded", "true");
|
|
244
|
+
document.addEventListener("pointerdown", this.onDocumentPointerDown);
|
|
245
|
+
this.positionCleanup = trackAnchor(moreBtn, menu, {
|
|
246
|
+
placement: this.resolvedPlacement(),
|
|
247
|
+
offset: 4,
|
|
248
|
+
});
|
|
249
|
+
menu.querySelector('[part="more-item"]')?.focus();
|
|
250
|
+
}
|
|
251
|
+
closeMore(restoreFocus = false) {
|
|
252
|
+
if (!this.moreOpen) {
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
this.moreOpen = false;
|
|
256
|
+
const moreBtn = this.rootEl.querySelector('[data-more]');
|
|
257
|
+
const menu = this.rootEl.querySelector('[part="more-menu"]');
|
|
258
|
+
if (menu) {
|
|
259
|
+
menu.hidden = true;
|
|
260
|
+
}
|
|
261
|
+
moreBtn?.setAttribute("aria-expanded", "false");
|
|
262
|
+
document.removeEventListener("pointerdown", this.onDocumentPointerDown);
|
|
263
|
+
this.positionCleanup?.();
|
|
264
|
+
this.positionCleanup = null;
|
|
265
|
+
if (restoreFocus) {
|
|
266
|
+
moreBtn?.focus();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
disconnectedCallback() {
|
|
270
|
+
document.removeEventListener("pointerdown", this.onDocumentPointerDown);
|
|
271
|
+
this.positionCleanup?.();
|
|
272
|
+
this.positionCleanup = null;
|
|
273
|
+
super.disconnectedCallback?.();
|
|
274
|
+
}
|
|
161
275
|
setupListeners() {
|
|
162
276
|
this.rootEl.addEventListener("click", event => {
|
|
163
|
-
const
|
|
277
|
+
const target = event.target;
|
|
278
|
+
const moreBtn = target.closest('[data-more]');
|
|
279
|
+
if (moreBtn && this.rootEl.contains(moreBtn)) {
|
|
280
|
+
if (this.moreOpen) {
|
|
281
|
+
this.closeMore();
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
this.openMore();
|
|
285
|
+
}
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const menuItem = target.closest('[part="more-item"]');
|
|
289
|
+
if (menuItem && this.rootEl.contains(menuItem)) {
|
|
290
|
+
this.select(menuItem.dataset.value ?? "");
|
|
291
|
+
this.closeMore(true);
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
const button = target.closest('[part~="pill"]');
|
|
164
295
|
if (!button || !this.rootEl.contains(button)) {
|
|
165
296
|
return;
|
|
166
297
|
}
|
|
167
298
|
this.select(button.dataset.value ?? "");
|
|
168
299
|
});
|
|
169
300
|
this.rootEl.addEventListener("keydown", event => {
|
|
170
|
-
const
|
|
301
|
+
const keyboardEvent = event;
|
|
302
|
+
const target = event.target;
|
|
303
|
+
// Menu item keys: Escape closes and returns focus; Arrow moves within.
|
|
304
|
+
const menuItem = target.closest('[part="more-item"]');
|
|
305
|
+
if (menuItem && this.rootEl.contains(menuItem)) {
|
|
306
|
+
const items = Array.from(this.rootEl.querySelectorAll('[part="more-item"]:not(:disabled)'));
|
|
307
|
+
const index = items.indexOf(menuItem);
|
|
308
|
+
if (keyboardEvent.key === "Escape") {
|
|
309
|
+
keyboardEvent.preventDefault();
|
|
310
|
+
this.closeMore(true);
|
|
311
|
+
}
|
|
312
|
+
else if (keyboardEvent.key === "ArrowDown") {
|
|
313
|
+
keyboardEvent.preventDefault();
|
|
314
|
+
items[(index + 1) % items.length]?.focus();
|
|
315
|
+
}
|
|
316
|
+
else if (keyboardEvent.key === "ArrowUp") {
|
|
317
|
+
keyboardEvent.preventDefault();
|
|
318
|
+
items[(index - 1 + items.length) % items.length]?.focus();
|
|
319
|
+
}
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const button = target.closest('[part~="pill"]');
|
|
171
323
|
if (!button || !this.rootEl.contains(button)) {
|
|
172
324
|
return;
|
|
173
325
|
}
|
|
174
|
-
|
|
326
|
+
// "More" button: open the overflow menu.
|
|
327
|
+
if (button.dataset.more) {
|
|
328
|
+
if (["Enter", " ", "ArrowDown"].includes(keyboardEvent.key)) {
|
|
329
|
+
keyboardEvent.preventDefault();
|
|
330
|
+
this.openMore();
|
|
331
|
+
}
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
175
334
|
const value = button.dataset.value ?? "";
|
|
176
335
|
switch (keyboardEvent.key) {
|
|
177
336
|
case "ArrowRight":
|
|
@@ -221,29 +380,48 @@ export class BoxCategorySelectorElement extends BaseElement {
|
|
|
221
380
|
this.lastOptionsJson = "";
|
|
222
381
|
return;
|
|
223
382
|
}
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
383
|
+
const { visible, overflow } = this.splitOptions();
|
|
384
|
+
const selectedInOverflow = overflow.some(option => option.value === this.valueInternal);
|
|
385
|
+
const cacheKey = `${JSON.stringify(options)}|${this.maxLinks}`;
|
|
386
|
+
if (cacheKey !== this.lastOptionsJson || !this.rootEl.querySelector('[part="group"]')) {
|
|
387
|
+
const pillMarkup = (option) => `
|
|
388
|
+
<button
|
|
389
|
+
type="button"
|
|
390
|
+
part="pill"
|
|
391
|
+
role="radio"
|
|
392
|
+
data-value="${escapeHtml(option.value)}"
|
|
393
|
+
${option.disabled ? "disabled" : ""}
|
|
394
|
+
>${escapeHtml(option.label)}</button>
|
|
395
|
+
`;
|
|
396
|
+
const moreButton = overflow.length
|
|
397
|
+
? `<button type="button" part="pill more" data-more="true" aria-haspopup="menu" aria-expanded="false">More</button>`
|
|
398
|
+
: "";
|
|
399
|
+
const moreMenu = overflow.length
|
|
400
|
+
? `<ul part="more-menu" role="menu" aria-label="More categories" hidden>${overflow
|
|
401
|
+
.map(option => `
|
|
402
|
+
<li role="none"><button
|
|
231
403
|
type="button"
|
|
232
|
-
part="
|
|
233
|
-
role="
|
|
404
|
+
part="more-item"
|
|
405
|
+
role="menuitemradio"
|
|
234
406
|
data-value="${escapeHtml(option.value)}"
|
|
235
407
|
${option.disabled ? "disabled" : ""}
|
|
236
|
-
>${escapeHtml(option.label)}</button>
|
|
408
|
+
>${escapeHtml(option.label)}</button></li>
|
|
237
409
|
`)
|
|
238
|
-
|
|
410
|
+
.join("")}</ul>`
|
|
411
|
+
: "";
|
|
412
|
+
this.rootEl.innerHTML = `
|
|
413
|
+
<div part="group" role="radiogroup" aria-label="${escapeHtml(this.label)}">
|
|
414
|
+
${visible.map(pillMarkup).join("")}
|
|
415
|
+
${moreButton}
|
|
239
416
|
</div>
|
|
417
|
+
${moreMenu}
|
|
240
418
|
`;
|
|
241
|
-
this.lastOptionsJson =
|
|
419
|
+
this.lastOptionsJson = cacheKey;
|
|
242
420
|
}
|
|
243
421
|
const group = this.rootEl.querySelector('[part="group"]');
|
|
244
422
|
group?.setAttribute("aria-label", this.label);
|
|
245
423
|
const tabbableValue = this.valueInternal || this.selectableOptions()[0]?.value || "";
|
|
246
|
-
this.rootEl.querySelectorAll('[part~="pill"]').forEach(node => {
|
|
424
|
+
this.rootEl.querySelectorAll('[part~="pill"]:not([data-more])').forEach(node => {
|
|
247
425
|
const button = node;
|
|
248
426
|
const optionValue = button.dataset.value ?? "";
|
|
249
427
|
const option = options.find(entry => entry.value === optionValue);
|
|
@@ -252,6 +430,22 @@ export class BoxCategorySelectorElement extends BaseElement {
|
|
|
252
430
|
button.setAttribute("aria-checked", String(isChecked));
|
|
253
431
|
button.tabIndex = optionValue === tabbableValue && !option?.disabled ? 0 : -1;
|
|
254
432
|
});
|
|
433
|
+
// "More" reflects an active overflow selection and shows its label.
|
|
434
|
+
const moreBtn = this.rootEl.querySelector('[data-more]');
|
|
435
|
+
if (moreBtn) {
|
|
436
|
+
const selectedOption = overflow.find(option => option.value === this.valueInternal);
|
|
437
|
+
moreBtn.textContent = selectedOption ? selectedOption.label : "More";
|
|
438
|
+
moreBtn.setAttribute("part", selectedInOverflow ? "pill more pill-checked" : "pill more");
|
|
439
|
+
moreBtn.setAttribute("aria-expanded", this.moreOpen ? "true" : "false");
|
|
440
|
+
// Roving tabindex: the More button is the tabbable entry when it holds the
|
|
441
|
+
// active (overflow) selection; otherwise a visible pill is.
|
|
442
|
+
moreBtn.tabIndex = selectedInOverflow ? 0 : -1;
|
|
443
|
+
}
|
|
444
|
+
// Reflect the checked overflow option in the menu.
|
|
445
|
+
this.rootEl.querySelectorAll('[part="more-item"]').forEach(node => {
|
|
446
|
+
const button = node;
|
|
447
|
+
button.setAttribute("aria-checked", String(button.dataset.value === this.valueInternal));
|
|
448
|
+
});
|
|
255
449
|
if (this.hadFocus && this.valueInternal) {
|
|
256
450
|
const active = Array.from(this.rootEl.querySelectorAll('[part~="pill"]')).find(node => node.dataset.value === this.valueInternal);
|
|
257
451
|
active?.focus();
|
|
@@ -7,7 +7,13 @@ export declare class BoxCheckboxElement extends FormAssociatedElement {
|
|
|
7
7
|
private valueInternal;
|
|
8
8
|
private inputEl;
|
|
9
9
|
private labelEl;
|
|
10
|
+
private descriptionEl;
|
|
11
|
+
private fieldEl;
|
|
10
12
|
private errorEl;
|
|
13
|
+
private descriptionId;
|
|
14
|
+
/** Supporting text shown as an indented subsection below the label. */
|
|
15
|
+
get description(): string;
|
|
16
|
+
set description(value: string);
|
|
11
17
|
get checked(): boolean;
|
|
12
18
|
set checked(value: boolean);
|
|
13
19
|
get indeterminate(): boolean;
|
|
@@ -42,10 +42,38 @@ const checkboxStyles = `
|
|
|
42
42
|
|
|
43
43
|
${boeFocusVisibleStyles('[part="input"]')}
|
|
44
44
|
|
|
45
|
+
[part="text"] {
|
|
46
|
+
display: inline-flex;
|
|
47
|
+
flex-direction: column;
|
|
48
|
+
gap: 2px;
|
|
49
|
+
min-inline-size: 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
45
52
|
[part="label"] {
|
|
46
53
|
font-weight: 400;
|
|
47
54
|
}
|
|
48
55
|
|
|
56
|
+
/* Supporting subsection below the label (BUE checkbox description). */
|
|
57
|
+
[part="description"] {
|
|
58
|
+
font-size: 0.82rem;
|
|
59
|
+
font-weight: 400;
|
|
60
|
+
line-height: 1.4;
|
|
61
|
+
color: var(--boe-token-text-text-secondary, #6f6f6f);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
[part="description"][hidden] {
|
|
65
|
+
display: none;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/* Top-align the box against multi-line content only when a description shows. */
|
|
69
|
+
[part="field"][data-has-description="true"] {
|
|
70
|
+
align-items: start;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
[part="field"][data-has-description="true"] [part="input"] {
|
|
74
|
+
margin-block-start: 1px;
|
|
75
|
+
}
|
|
76
|
+
|
|
49
77
|
:host([disabled]) [part="field"] {
|
|
50
78
|
opacity: ${boeControl.disabledOpacity};
|
|
51
79
|
cursor: not-allowed;
|
|
@@ -58,6 +86,7 @@ export class BoxCheckboxElement extends FormAssociatedElement {
|
|
|
58
86
|
return [
|
|
59
87
|
...FormAssociatedElement.formObservedAttributes,
|
|
60
88
|
"checked",
|
|
89
|
+
"description",
|
|
61
90
|
"disabled",
|
|
62
91
|
"indeterminate",
|
|
63
92
|
"label",
|
|
@@ -69,7 +98,17 @@ export class BoxCheckboxElement extends FormAssociatedElement {
|
|
|
69
98
|
valueInternal = DEFAULT_VALUE;
|
|
70
99
|
inputEl;
|
|
71
100
|
labelEl;
|
|
101
|
+
descriptionEl;
|
|
102
|
+
fieldEl;
|
|
72
103
|
errorEl;
|
|
104
|
+
descriptionId = `box-checkbox-desc-${Math.random().toString(36).slice(2, 10)}`;
|
|
105
|
+
/** Supporting text shown as an indented subsection below the label. */
|
|
106
|
+
get description() {
|
|
107
|
+
return this.getAttribute("description") ?? "";
|
|
108
|
+
}
|
|
109
|
+
set description(value) {
|
|
110
|
+
this.setAttribute("description", value);
|
|
111
|
+
}
|
|
73
112
|
get checked() {
|
|
74
113
|
return this.checkedInternal;
|
|
75
114
|
}
|
|
@@ -180,12 +219,17 @@ export class BoxCheckboxElement extends FormAssociatedElement {
|
|
|
180
219
|
type="checkbox"
|
|
181
220
|
part="input"
|
|
182
221
|
/>
|
|
183
|
-
<span part="
|
|
222
|
+
<span part="text">
|
|
223
|
+
<span part="label"></span>
|
|
224
|
+
<span part="description" id="${this.descriptionId}" hidden></span>
|
|
225
|
+
</span>
|
|
184
226
|
</label>
|
|
185
227
|
${formErrorMessageMarkup()}
|
|
186
228
|
`;
|
|
187
229
|
this.inputEl = this.shadowRoot.querySelector('[part="input"]');
|
|
188
230
|
this.labelEl = this.shadowRoot.querySelector('[part="label"]');
|
|
231
|
+
this.descriptionEl = this.shadowRoot.querySelector('[part="description"]');
|
|
232
|
+
this.fieldEl = this.shadowRoot.querySelector('[part="field"]');
|
|
189
233
|
this.errorEl = this.shadowRoot.querySelector('[part="error-message"]');
|
|
190
234
|
}
|
|
191
235
|
setupListeners() {
|
|
@@ -237,6 +281,19 @@ export class BoxCheckboxElement extends FormAssociatedElement {
|
|
|
237
281
|
}
|
|
238
282
|
this.inputEl.setAttribute("aria-label", this.label);
|
|
239
283
|
this.labelEl.textContent = this.label;
|
|
284
|
+
const description = this.description;
|
|
285
|
+
if (description) {
|
|
286
|
+
this.descriptionEl.hidden = false;
|
|
287
|
+
this.descriptionEl.textContent = description;
|
|
288
|
+
this.inputEl.setAttribute("aria-describedby", this.descriptionId);
|
|
289
|
+
this.fieldEl.dataset.hasDescription = "true";
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
this.descriptionEl.hidden = true;
|
|
293
|
+
this.descriptionEl.textContent = "";
|
|
294
|
+
this.inputEl.removeAttribute("aria-describedby");
|
|
295
|
+
this.fieldEl.removeAttribute("data-has-description");
|
|
296
|
+
}
|
|
240
297
|
this.applyInvalidState(this.inputEl, this.errorEl);
|
|
241
298
|
}
|
|
242
299
|
}
|
|
@@ -3,19 +3,34 @@ import type { FormValue } from "../../core/index.js";
|
|
|
3
3
|
type BoxComboboxOption = {
|
|
4
4
|
label: string;
|
|
5
5
|
value: string;
|
|
6
|
+
/** Secondary line rendered under the label (lightweight custom render). */
|
|
7
|
+
description?: string;
|
|
8
|
+
/** Options sharing a group render under a divider header. */
|
|
9
|
+
group?: string;
|
|
10
|
+
disabled?: boolean;
|
|
6
11
|
};
|
|
7
12
|
export declare class BoxComboboxElement extends FormAssociatedElement {
|
|
8
13
|
static get observedAttributes(): string[];
|
|
9
14
|
private valueInternal;
|
|
15
|
+
private open;
|
|
16
|
+
private activeId;
|
|
17
|
+
private filterText;
|
|
18
|
+
private positionCleanup;
|
|
19
|
+
private listboxId;
|
|
10
20
|
private inputEl;
|
|
11
21
|
private labelEl;
|
|
12
|
-
private
|
|
22
|
+
private controlEl;
|
|
23
|
+
private listboxEl;
|
|
13
24
|
private errorEl;
|
|
25
|
+
private readonly onDocumentPointerDown;
|
|
14
26
|
private findOptionByLabel;
|
|
15
27
|
private findOptionByValue;
|
|
16
28
|
private getDisplayValue;
|
|
29
|
+
/** Resolve typed text to an option value when it matches a label (free-text otherwise). */
|
|
17
30
|
private resolveInputValue;
|
|
18
31
|
private commitInputValue;
|
|
32
|
+
/** Options visible for the current filter text (null = show all). */
|
|
33
|
+
private visibleOptions;
|
|
19
34
|
get disabled(): boolean;
|
|
20
35
|
set disabled(value: boolean);
|
|
21
36
|
get label(): string;
|
|
@@ -24,13 +39,27 @@ export declare class BoxComboboxElement extends FormAssociatedElement {
|
|
|
24
39
|
set options(value: BoxComboboxOption[]);
|
|
25
40
|
get placeholder(): string;
|
|
26
41
|
set placeholder(value: string);
|
|
42
|
+
get placement(): string;
|
|
43
|
+
set placement(value: string);
|
|
44
|
+
private resolvedPlacement;
|
|
27
45
|
get value(): string;
|
|
28
46
|
set value(nextValue: string);
|
|
29
47
|
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
30
48
|
protected getFormValue(): FormValue;
|
|
31
49
|
protected restoreFormValue(value: FormValue): void;
|
|
50
|
+
disconnectedCallback(): void;
|
|
51
|
+
private stopTracking;
|
|
52
|
+
private commitValue;
|
|
53
|
+
private optionId;
|
|
54
|
+
private openListbox;
|
|
55
|
+
private closeListbox;
|
|
56
|
+
private optionEls;
|
|
57
|
+
private setActive;
|
|
58
|
+
private moveActive;
|
|
59
|
+
private selectActive;
|
|
32
60
|
protected renderTemplate(): void;
|
|
33
61
|
protected setupListeners(): void;
|
|
62
|
+
private renderOptions;
|
|
34
63
|
protected update(): void;
|
|
35
64
|
}
|
|
36
65
|
export declare const defineBoxComboboxElement: (tagName?: string) => typeof BoxComboboxElement;
|