@helixui/library 3.3.0 → 3.3.1-next.117
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/custom-elements.json +182 -13
- package/dist/components/hx-accordion/hx-accordion-item.d.ts +35 -0
- package/dist/components/hx-accordion/hx-accordion-item.d.ts.map +1 -1
- package/dist/components/hx-checkbox/hx-checkbox.d.ts +153 -1
- package/dist/components/hx-checkbox/hx-checkbox.d.ts.map +1 -1
- package/dist/components/hx-checkbox/hx-checkbox.styles.d.ts.map +1 -1
- package/dist/components/hx-checkbox/index.js +1 -1
- package/dist/components/hx-checkbox-group/hx-checkbox-group.d.ts +151 -2
- package/dist/components/hx-checkbox-group/hx-checkbox-group.d.ts.map +1 -1
- package/dist/components/hx-checkbox-group/index.js +1 -1
- package/dist/components/hx-color-picker/hx-color-picker.d.ts +163 -1
- package/dist/components/hx-color-picker/hx-color-picker.d.ts.map +1 -1
- package/dist/components/hx-color-picker/hx-color-picker.styles.d.ts.map +1 -1
- package/dist/components/hx-color-picker/index.js +1 -1
- package/dist/components/hx-combobox/hx-combobox.d.ts +311 -2
- package/dist/components/hx-combobox/hx-combobox.d.ts.map +1 -1
- package/dist/components/hx-combobox/index.js +1 -1
- package/dist/components/hx-date-picker/hx-date-picker.d.ts +182 -56
- package/dist/components/hx-date-picker/hx-date-picker.d.ts.map +1 -1
- package/dist/components/hx-date-picker/hx-date-picker.styles.d.ts.map +1 -1
- package/dist/components/hx-date-picker/index.js +1 -1
- package/dist/components/hx-dialog/hx-dialog.d.ts +240 -0
- package/dist/components/hx-dialog/hx-dialog.d.ts.map +1 -1
- package/dist/components/hx-dialog/index.js +1 -1
- package/dist/components/hx-dropdown/hx-dropdown.d.ts +80 -0
- package/dist/components/hx-dropdown/hx-dropdown.d.ts.map +1 -1
- package/dist/components/hx-dropdown/index.js +1 -1
- package/dist/components/hx-field/hx-field.d.ts +109 -0
- package/dist/components/hx-field/hx-field.d.ts.map +1 -1
- package/dist/components/hx-field/index.js +1 -1
- package/dist/components/hx-popover/hx-popover.d.ts +91 -0
- package/dist/components/hx-popover/hx-popover.d.ts.map +1 -1
- package/dist/components/hx-popover/index.js +1 -1
- package/dist/components/hx-radio-group/hx-radio-group.d.ts +152 -1
- package/dist/components/hx-radio-group/hx-radio-group.d.ts.map +1 -1
- package/dist/components/hx-radio-group/hx-radio.d.ts +14 -0
- package/dist/components/hx-radio-group/hx-radio.d.ts.map +1 -1
- package/dist/components/hx-radio-group/index.js +1 -1
- package/dist/components/hx-select/hx-select.d.ts +303 -2
- package/dist/components/hx-select/hx-select.d.ts.map +1 -1
- package/dist/components/hx-select/hx-select.styles.d.ts.map +1 -1
- package/dist/components/hx-select/index.js +1 -1
- package/dist/components/hx-side-nav/hx-nav-item.styles.d.ts.map +1 -1
- package/dist/components/hx-side-nav/index.js +1 -1
- package/dist/components/hx-switch/hx-switch.d.ts +78 -1
- package/dist/components/hx-switch/hx-switch.d.ts.map +1 -1
- package/dist/components/hx-switch/hx-switch.styles.d.ts.map +1 -1
- package/dist/components/hx-switch/index.js +1 -1
- package/dist/components/hx-toggle-button/hx-toggle-button.d.ts +110 -0
- package/dist/components/hx-toggle-button/hx-toggle-button.d.ts.map +1 -1
- package/dist/components/hx-toggle-button/hx-toggle-button.styles.d.ts.map +1 -1
- package/dist/components/hx-toggle-button/index.js +1 -1
- package/dist/components/hx-tooltip/hx-tooltip.d.ts +52 -0
- package/dist/components/hx-tooltip/hx-tooltip.d.ts.map +1 -1
- package/dist/components/hx-tooltip/index.js +1 -1
- package/dist/css/helix-all.css +98 -1
- package/dist/css/helix-forms.css +98 -1
- package/dist/css/hx-checkbox.css +18 -0
- package/dist/css/hx-color-picker.css +25 -0
- package/dist/css/hx-date-picker.css +2 -1
- package/dist/css/hx-select.css +19 -0
- package/dist/css/hx-switch.css +17 -0
- package/dist/css/hx-toggle-button.css +17 -0
- package/dist/css/index.css +1 -1
- package/dist/css/manifest.json +2 -1
- package/dist/index.js +15 -15
- package/dist/shared/aria-flatten-DY6v2vah.js +22 -0
- package/dist/shared/aria-flatten-DY6v2vah.js.map +1 -0
- package/dist/shared/aria-idref-Q0yiSR3p.js +104 -0
- package/dist/shared/aria-idref-Q0yiSR3p.js.map +1 -0
- package/dist/shared/hx-accordion-ZVzgDzTG.js.map +1 -1
- package/dist/shared/hx-checkbox-BdgoUeWi.js +696 -0
- package/dist/shared/hx-checkbox-BdgoUeWi.js.map +1 -0
- package/dist/shared/hx-checkbox-group-LWezHrvS.js +496 -0
- package/dist/shared/hx-checkbox-group-LWezHrvS.js.map +1 -0
- package/dist/shared/hx-color-picker-DVhZl88b.js +1221 -0
- package/dist/shared/hx-color-picker-DVhZl88b.js.map +1 -0
- package/dist/shared/hx-combobox-DvlezcDV.js +1359 -0
- package/dist/shared/hx-combobox-DvlezcDV.js.map +1 -0
- package/dist/shared/{hx-date-picker-2iRG1p74.js → hx-date-picker-N-0aG5XL.js} +542 -206
- package/dist/shared/hx-date-picker-N-0aG5XL.js.map +1 -0
- package/dist/shared/hx-dialog-DzB7VytW.js +717 -0
- package/dist/shared/hx-dialog-DzB7VytW.js.map +1 -0
- package/dist/shared/{hx-dropdown-LyaRc8Rf.js → hx-dropdown-DJWlF94E.js} +130 -77
- package/dist/shared/hx-dropdown-DJWlF94E.js.map +1 -0
- package/dist/shared/{hx-field-B3Qo8OLS.js → hx-field-zw0U1KVi.js} +99 -38
- package/dist/shared/hx-field-zw0U1KVi.js.map +1 -0
- package/dist/shared/{hx-nav-item-xqRPOCWX.js → hx-nav-item-CODtUlew.js} +13 -9
- package/dist/shared/{hx-nav-item-xqRPOCWX.js.map → hx-nav-item-CODtUlew.js.map} +1 -1
- package/dist/shared/{hx-popover-B-FP3-wW.js → hx-popover-CHxWY_cd.js} +123 -66
- package/dist/shared/hx-popover-CHxWY_cd.js.map +1 -0
- package/dist/shared/hx-radio-CeGzARNk.js +822 -0
- package/dist/shared/hx-radio-CeGzARNk.js.map +1 -0
- package/dist/shared/hx-select-DrcS-YRJ.js +1089 -0
- package/dist/shared/hx-select-DrcS-YRJ.js.map +1 -0
- package/dist/shared/hx-switch-BX_8uNUs.js +540 -0
- package/dist/shared/hx-switch-BX_8uNUs.js.map +1 -0
- package/dist/shared/{hx-toggle-button-iLiYrMbD.js → hx-toggle-button-Dcz9IlUm.js} +226 -65
- package/dist/shared/hx-toggle-button-Dcz9IlUm.js.map +1 -0
- package/dist/shared/{hx-tooltip-nYOv9OLu.js → hx-tooltip-DVqtKPCD.js} +68 -46
- package/dist/shared/hx-tooltip-DVqtKPCD.js.map +1 -0
- package/dist/utils/aria-flatten.d.ts +56 -0
- package/dist/utils/aria-flatten.d.ts.map +1 -0
- package/dist/utils/aria-idref.d.ts +127 -0
- package/dist/utils/aria-idref.d.ts.map +1 -0
- package/figma-inventory.json +66 -3
- package/package.json +2 -2
- package/dist/shared/hx-checkbox-D7xma9YH.js +0 -524
- package/dist/shared/hx-checkbox-D7xma9YH.js.map +0 -1
- package/dist/shared/hx-checkbox-group-C9n315Ju.js +0 -323
- package/dist/shared/hx-checkbox-group-C9n315Ju.js.map +0 -1
- package/dist/shared/hx-color-picker-uRc865FJ.js +0 -882
- package/dist/shared/hx-color-picker-uRc865FJ.js.map +0 -1
- package/dist/shared/hx-combobox-DDzqNKEW.js +0 -924
- package/dist/shared/hx-combobox-DDzqNKEW.js.map +0 -1
- package/dist/shared/hx-date-picker-2iRG1p74.js.map +0 -1
- package/dist/shared/hx-dialog-DRN_1-Y-.js +0 -514
- package/dist/shared/hx-dialog-DRN_1-Y-.js.map +0 -1
- package/dist/shared/hx-dropdown-LyaRc8Rf.js.map +0 -1
- package/dist/shared/hx-field-B3Qo8OLS.js.map +0 -1
- package/dist/shared/hx-popover-B-FP3-wW.js.map +0 -1
- package/dist/shared/hx-radio-CJvNU2yP.js +0 -621
- package/dist/shared/hx-radio-CJvNU2yP.js.map +0 -1
- package/dist/shared/hx-select-C8fEHQhC.js +0 -807
- package/dist/shared/hx-select-C8fEHQhC.js.map +0 -1
- package/dist/shared/hx-switch-BrZFaRue.js +0 -420
- package/dist/shared/hx-switch-BrZFaRue.js.map +0 -1
- package/dist/shared/hx-toggle-button-iLiYrMbD.js.map +0 -1
- package/dist/shared/hx-tooltip-nYOv9OLu.js.map +0 -1
|
@@ -0,0 +1,1359 @@
|
|
|
1
|
+
import { css as P, html as f, nothing as u } from "lit";
|
|
2
|
+
import { property as h, state as c, query as U, customElement as G } from "lit/decorators.js";
|
|
3
|
+
import { F as W } from "./FormMixin-B8PXk5RQ.js";
|
|
4
|
+
import { classMap as H } from "lit/directives/class-map.js";
|
|
5
|
+
import { ifDefined as L } from "lit/directives/if-defined.js";
|
|
6
|
+
import { repeat as X } from "lit/directives/repeat.js";
|
|
7
|
+
import { b as K } from "./forced-colors-CTEDFRGa.js";
|
|
8
|
+
import { s as J, i as Q, r as z } from "./aria-idref-Q0yiSR3p.js";
|
|
9
|
+
import { H as Y } from "./helix-element-BNEYeiys.js";
|
|
10
|
+
import { c as Z } from "./id-counter-DuX8vsui.js";
|
|
11
|
+
const ee = P`
|
|
12
|
+
:host {
|
|
13
|
+
display: block;
|
|
14
|
+
}
|
|
15
|
+
:host([disabled]) {
|
|
16
|
+
opacity: var(--hx-opacity-disabled, 0.5);
|
|
17
|
+
pointer-events: none;
|
|
18
|
+
}
|
|
19
|
+
* {
|
|
20
|
+
box-sizing: border-box;
|
|
21
|
+
}
|
|
22
|
+
.field {
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: column;
|
|
25
|
+
gap: var(--hx-space-1, 0.25rem);
|
|
26
|
+
font-family: var(--hx-combobox-font-family, var(--hx-font-family-sans, sans-serif));
|
|
27
|
+
position: relative;
|
|
28
|
+
}
|
|
29
|
+
.field__label {
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: baseline;
|
|
32
|
+
gap: var(--hx-space-1, 0.25rem);
|
|
33
|
+
font-size: var(--hx-font-size-sm, 0.875rem);
|
|
34
|
+
font-weight: var(--hx-font-weight-medium, 500);
|
|
35
|
+
color: var(--hx-combobox-label-color, var(--hx-color-text-strong, #202b39));
|
|
36
|
+
line-height: var(--hx-line-height-normal, 1.5);
|
|
37
|
+
}
|
|
38
|
+
.field__required-marker {
|
|
39
|
+
color: var(--hx-combobox-error-color, var(--hx-color-error-text, #c92a2a));
|
|
40
|
+
font-weight: var(--hx-font-weight-bold, 700);
|
|
41
|
+
}
|
|
42
|
+
.field__input-wrapper {
|
|
43
|
+
position: relative;
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
border: var(--hx-border-width-thin, 1px) solid
|
|
47
|
+
var(--hx-combobox-border-color, var(--hx-color-border-strong, #66787b));
|
|
48
|
+
border-radius: var(--hx-combobox-border-radius, var(--hx-border-radius-md, 0.375rem));
|
|
49
|
+
background-color: var(--hx-combobox-bg, var(--hx-color-surface-default, #ffffff));
|
|
50
|
+
transition:
|
|
51
|
+
border-color var(--hx-transition-fast, 150ms ease),
|
|
52
|
+
box-shadow var(--hx-transition-fast, 150ms ease);
|
|
53
|
+
}
|
|
54
|
+
.field__input-wrapper:focus-within {
|
|
55
|
+
border-color: var(--hx-combobox-focus-ring-color, var(--hx-focus-ring-color, #0f7078));
|
|
56
|
+
box-shadow: 0 0 0 var(--hx-focus-ring-width, 2px)
|
|
57
|
+
color-mix(
|
|
58
|
+
in srgb,
|
|
59
|
+
var(--hx-combobox-focus-ring-color, var(--hx-focus-ring-color, #0f7078))
|
|
60
|
+
calc(var(--hx-focus-ring-opacity, 0.25) * 100%),
|
|
61
|
+
transparent
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
.field--error .field__input-wrapper {
|
|
65
|
+
border-color: var(--hx-combobox-error-color, var(--hx-color-error-500, #e5493e));
|
|
66
|
+
}
|
|
67
|
+
.field--error .field__input-wrapper:focus-within {
|
|
68
|
+
border-color: var(--hx-combobox-error-color, var(--hx-color-error-500, #e5493e));
|
|
69
|
+
box-shadow: 0 0 0 var(--hx-focus-ring-width, 2px)
|
|
70
|
+
color-mix(
|
|
71
|
+
in srgb,
|
|
72
|
+
var(--hx-combobox-error-color, var(--hx-color-error-500, #e5493e))
|
|
73
|
+
calc(var(--hx-focus-ring-opacity, 0.25) * 100%),
|
|
74
|
+
transparent
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
.field__prefix,
|
|
78
|
+
.field__suffix {
|
|
79
|
+
display: flex;
|
|
80
|
+
align-items: center;
|
|
81
|
+
padding: 0 var(--hx-space-2, 0.5rem);
|
|
82
|
+
color: var(--hx-color-text-muted, #4a5362);
|
|
83
|
+
flex-shrink: 0;
|
|
84
|
+
}
|
|
85
|
+
.field__input {
|
|
86
|
+
flex: 1;
|
|
87
|
+
min-width: 0;
|
|
88
|
+
min-height: var(--hx-input-height-md, var(--hx-size-10, 2.5rem));
|
|
89
|
+
border: none;
|
|
90
|
+
background: transparent;
|
|
91
|
+
outline: none;
|
|
92
|
+
font-family: inherit;
|
|
93
|
+
font-size: var(--hx-font-size-md, 1rem);
|
|
94
|
+
line-height: var(--hx-line-height-normal, 1.5);
|
|
95
|
+
color: var(--hx-combobox-color, var(--hx-color-text-strong, #202b39));
|
|
96
|
+
padding: var(--hx-space-2, 0.5rem) var(--hx-space-3, 0.75rem);
|
|
97
|
+
}
|
|
98
|
+
.field__input::placeholder {
|
|
99
|
+
color: var(--hx-color-text-placeholder, #66787b);
|
|
100
|
+
}
|
|
101
|
+
.field__input--sm {
|
|
102
|
+
min-height: var(--hx-input-height-sm, var(--hx-size-8, 2rem));
|
|
103
|
+
font-size: var(--hx-font-size-sm, 0.875rem);
|
|
104
|
+
padding: var(--hx-space-1, 0.25rem) var(--hx-space-3, 0.75rem);
|
|
105
|
+
}
|
|
106
|
+
.field__input--lg {
|
|
107
|
+
min-height: var(--hx-input-height-lg, var(--hx-size-12, 3rem));
|
|
108
|
+
font-size: var(--hx-font-size-lg, 1.125rem);
|
|
109
|
+
padding: var(--hx-space-3, 0.75rem) var(--hx-space-4, 1rem);
|
|
110
|
+
}
|
|
111
|
+
.field__clear-button,
|
|
112
|
+
.field__loading-indicator {
|
|
113
|
+
display: flex;
|
|
114
|
+
align-items: center;
|
|
115
|
+
justify-content: center;
|
|
116
|
+
margin-inline-end: var(--hx-space-2, 0.5rem);
|
|
117
|
+
flex-shrink: 0;
|
|
118
|
+
color: var(--hx-color-text-placeholder, #66787b);
|
|
119
|
+
}
|
|
120
|
+
.field__clear-button {
|
|
121
|
+
width: 1.25rem;
|
|
122
|
+
height: 1.25rem;
|
|
123
|
+
border: none;
|
|
124
|
+
background: transparent;
|
|
125
|
+
cursor: pointer;
|
|
126
|
+
padding: 0;
|
|
127
|
+
border-radius: var(--hx-border-radius-full, 9999px);
|
|
128
|
+
transition: color var(--hx-transition-fast, 150ms ease);
|
|
129
|
+
}
|
|
130
|
+
.field__clear-button:hover {
|
|
131
|
+
color: var(--hx-color-text-strong, #202b39);
|
|
132
|
+
}
|
|
133
|
+
.field__clear-button:focus-visible {
|
|
134
|
+
outline: var(--hx-focus-ring-width, 2px) solid
|
|
135
|
+
var(--hx-combobox-focus-ring-color, var(--hx-focus-ring-color, #0f7078));
|
|
136
|
+
outline-offset: var(--hx-focus-ring-offset, 2px);
|
|
137
|
+
}
|
|
138
|
+
.field__loading-indicator {
|
|
139
|
+
width: 1rem;
|
|
140
|
+
height: 1rem;
|
|
141
|
+
}
|
|
142
|
+
.field__loading-spinner {
|
|
143
|
+
width: 1rem;
|
|
144
|
+
height: 1rem;
|
|
145
|
+
border: 2px solid currentColor;
|
|
146
|
+
border-top-color: transparent;
|
|
147
|
+
border-radius: 50%;
|
|
148
|
+
animation: hx-spin 0.7s linear infinite;
|
|
149
|
+
}
|
|
150
|
+
@keyframes hx-spin {
|
|
151
|
+
to {
|
|
152
|
+
transform: rotate(360deg);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
@media (prefers-reduced-motion: reduce) {
|
|
156
|
+
.field__loading-spinner {
|
|
157
|
+
animation: none;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
.field__listbox {
|
|
161
|
+
position: absolute;
|
|
162
|
+
top: calc(100% + var(--hx-space-1, 0.25rem));
|
|
163
|
+
left: 0;
|
|
164
|
+
right: 0;
|
|
165
|
+
z-index: var(--hx-z-index-dropdown, 1000);
|
|
166
|
+
background-color: var(--hx-combobox-listbox-bg, var(--hx-color-surface-default, #ffffff));
|
|
167
|
+
border: var(--hx-border-width-thin, 1px) solid
|
|
168
|
+
var(--hx-combobox-border-color, var(--hx-color-border-strong, #66787b));
|
|
169
|
+
border-radius: var(--hx-combobox-border-radius, var(--hx-border-radius-md, 0.375rem));
|
|
170
|
+
box-shadow: var(
|
|
171
|
+
--hx-combobox-listbox-shadow,
|
|
172
|
+
0 4px 16px color-mix(in srgb, var(--hx-color-neutral-900, #0d1825) 12%, transparent)
|
|
173
|
+
);
|
|
174
|
+
max-height: var(--hx-combobox-listbox-max-height, 16rem);
|
|
175
|
+
overflow: hidden;
|
|
176
|
+
display: flex;
|
|
177
|
+
flex-direction: column;
|
|
178
|
+
}
|
|
179
|
+
.field__listbox[hidden] {
|
|
180
|
+
display: none;
|
|
181
|
+
}
|
|
182
|
+
.field__options {
|
|
183
|
+
overflow-y: auto;
|
|
184
|
+
flex: 1;
|
|
185
|
+
padding: var(--hx-space-1, 0.25rem) 0;
|
|
186
|
+
}
|
|
187
|
+
.field__option {
|
|
188
|
+
display: flex;
|
|
189
|
+
align-items: center;
|
|
190
|
+
gap: var(--hx-space-2, 0.5rem);
|
|
191
|
+
padding: var(--hx-space-2, 0.5rem) var(--hx-space-3, 0.75rem);
|
|
192
|
+
font-size: var(--hx-font-size-md, 1rem);
|
|
193
|
+
color: var(--hx-combobox-color, var(--hx-color-text-strong, #202b39));
|
|
194
|
+
cursor: pointer;
|
|
195
|
+
user-select: none;
|
|
196
|
+
-webkit-user-select: none;
|
|
197
|
+
transition: background-color var(--hx-transition-fast, 150ms ease);
|
|
198
|
+
}
|
|
199
|
+
.field__option:hover {
|
|
200
|
+
background-color: var(--hx-combobox-option-hover-bg, var(--hx-color-primary-50, #ebf8f8));
|
|
201
|
+
}
|
|
202
|
+
.field__option--selected {
|
|
203
|
+
background-color: var(--hx-combobox-option-selected-bg, var(--hx-color-primary-100, #dbf0f0));
|
|
204
|
+
font-weight: var(--hx-font-weight-medium, 500);
|
|
205
|
+
}
|
|
206
|
+
.field__option--focused {
|
|
207
|
+
background-color: var(--hx-combobox-option-hover-bg, var(--hx-color-primary-50, #ebf8f8));
|
|
208
|
+
outline: var(--hx-focus-ring-width, 2px) solid
|
|
209
|
+
var(--hx-combobox-focus-ring-color, var(--hx-focus-ring-color, #0f7078));
|
|
210
|
+
outline-offset: var(--hx-combobox-option-focus-ring-offset, -2px);
|
|
211
|
+
}
|
|
212
|
+
.field__option--focused.field__option--selected {
|
|
213
|
+
background-color: var(--hx-combobox-option-selected-bg, var(--hx-color-primary-100, #dbf0f0));
|
|
214
|
+
}
|
|
215
|
+
.field__option--disabled {
|
|
216
|
+
opacity: var(--hx-opacity-disabled, 0.5);
|
|
217
|
+
cursor: not-allowed;
|
|
218
|
+
pointer-events: none;
|
|
219
|
+
}
|
|
220
|
+
.field__option-label {
|
|
221
|
+
flex: 1;
|
|
222
|
+
white-space: nowrap;
|
|
223
|
+
overflow: hidden;
|
|
224
|
+
text-overflow: ellipsis;
|
|
225
|
+
}
|
|
226
|
+
.field__no-options {
|
|
227
|
+
padding: var(--hx-space-3, 0.75rem);
|
|
228
|
+
text-align: center;
|
|
229
|
+
color: var(--hx-color-text-placeholder, #66787b);
|
|
230
|
+
font-size: var(--hx-font-size-sm, 0.875rem);
|
|
231
|
+
}
|
|
232
|
+
.field__sr-only {
|
|
233
|
+
position: absolute;
|
|
234
|
+
width: 1px;
|
|
235
|
+
height: 1px;
|
|
236
|
+
padding: 0;
|
|
237
|
+
margin: -1px;
|
|
238
|
+
overflow: hidden;
|
|
239
|
+
clip: rect(0, 0, 0, 0);
|
|
240
|
+
white-space: nowrap;
|
|
241
|
+
border: 0;
|
|
242
|
+
}
|
|
243
|
+
.field__help-text,
|
|
244
|
+
.field__error {
|
|
245
|
+
font-size: var(--hx-font-size-xs, 0.75rem);
|
|
246
|
+
line-height: var(--hx-line-height-normal, 1.5);
|
|
247
|
+
}
|
|
248
|
+
.field__help-text {
|
|
249
|
+
color: var(--hx-color-text-muted, #4a5362);
|
|
250
|
+
}
|
|
251
|
+
.field__error {
|
|
252
|
+
color: var(--hx-combobox-error-color, var(--hx-color-error-text, #c92a2a));
|
|
253
|
+
}
|
|
254
|
+
@media (prefers-reduced-motion: reduce) {
|
|
255
|
+
.field__input-wrapper,
|
|
256
|
+
.field__option,
|
|
257
|
+
.field__clear-button,
|
|
258
|
+
.field__chip-remove {
|
|
259
|
+
transition: none;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
@media (forced-colors: active) {
|
|
263
|
+
.field__input-wrapper {
|
|
264
|
+
forced-color-adjust: none;
|
|
265
|
+
background-color: Field;
|
|
266
|
+
color: FieldText;
|
|
267
|
+
border: 2px solid ButtonText;
|
|
268
|
+
}
|
|
269
|
+
.field__input {
|
|
270
|
+
color: FieldText;
|
|
271
|
+
}
|
|
272
|
+
.field__input::placeholder {
|
|
273
|
+
color: GrayText;
|
|
274
|
+
}
|
|
275
|
+
.field__input-wrapper:focus-within {
|
|
276
|
+
border-color: Highlight;
|
|
277
|
+
box-shadow: none;
|
|
278
|
+
}
|
|
279
|
+
.field__listbox {
|
|
280
|
+
forced-color-adjust: none;
|
|
281
|
+
background-color: Canvas;
|
|
282
|
+
border: 2px solid CanvasText;
|
|
283
|
+
box-shadow: none;
|
|
284
|
+
}
|
|
285
|
+
.field__option {
|
|
286
|
+
color: CanvasText;
|
|
287
|
+
}
|
|
288
|
+
.field__option:hover {
|
|
289
|
+
background-color: Highlight;
|
|
290
|
+
color: HighlightText;
|
|
291
|
+
}
|
|
292
|
+
.field__option--selected {
|
|
293
|
+
background-color: Highlight;
|
|
294
|
+
color: HighlightText;
|
|
295
|
+
}
|
|
296
|
+
.field__option--focused {
|
|
297
|
+
outline-color: Highlight;
|
|
298
|
+
background-color: Highlight;
|
|
299
|
+
color: HighlightText;
|
|
300
|
+
}
|
|
301
|
+
.field__option--disabled {
|
|
302
|
+
color: GrayText;
|
|
303
|
+
opacity: 1;
|
|
304
|
+
}
|
|
305
|
+
.field__chip {
|
|
306
|
+
forced-color-adjust: none;
|
|
307
|
+
background-color: Highlight;
|
|
308
|
+
color: HighlightText;
|
|
309
|
+
border: 1px solid HighlightText;
|
|
310
|
+
}
|
|
311
|
+
.field__chip-remove:focus-visible {
|
|
312
|
+
outline-color: Highlight;
|
|
313
|
+
}
|
|
314
|
+
.field__clear-button:focus-visible {
|
|
315
|
+
outline-color: Highlight;
|
|
316
|
+
}
|
|
317
|
+
.field--error .field__input-wrapper {
|
|
318
|
+
border-color: LinkText;
|
|
319
|
+
}
|
|
320
|
+
:host([disabled]) {
|
|
321
|
+
opacity: 1;
|
|
322
|
+
}
|
|
323
|
+
:host([disabled]) .field__input-wrapper {
|
|
324
|
+
border-color: GrayText;
|
|
325
|
+
color: GrayText;
|
|
326
|
+
}
|
|
327
|
+
:host([disabled]) .field__input {
|
|
328
|
+
color: GrayText;
|
|
329
|
+
}
|
|
330
|
+
.field__label {
|
|
331
|
+
color: CanvasText;
|
|
332
|
+
}
|
|
333
|
+
.field__help-text {
|
|
334
|
+
color: GrayText;
|
|
335
|
+
}
|
|
336
|
+
.field__error {
|
|
337
|
+
color: LinkText;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
:host([multiple]) .field__input-wrapper {
|
|
341
|
+
flex-wrap: wrap;
|
|
342
|
+
padding: var(--hx-space-1, 0.25rem);
|
|
343
|
+
gap: var(--hx-space-1, 0.25rem);
|
|
344
|
+
align-items: center;
|
|
345
|
+
}
|
|
346
|
+
:host([multiple]) .field__input {
|
|
347
|
+
min-width: 8rem;
|
|
348
|
+
padding: var(--hx-space-1, 0.25rem) var(--hx-space-2, 0.5rem);
|
|
349
|
+
flex-shrink: 1;
|
|
350
|
+
}
|
|
351
|
+
.field__chip {
|
|
352
|
+
display: inline-flex;
|
|
353
|
+
align-items: center;
|
|
354
|
+
gap: var(--hx-space-1, 0.25rem);
|
|
355
|
+
padding: 0 var(--hx-space-1, 0.25rem) 0 var(--hx-space-2, 0.5rem);
|
|
356
|
+
height: 1.5rem;
|
|
357
|
+
background-color: var(--hx-combobox-chip-bg, var(--hx-color-primary-100, #dbf0f0));
|
|
358
|
+
color: var(--hx-combobox-chip-color, var(--hx-color-primary-800, #07494a));
|
|
359
|
+
border-radius: var(--hx-border-radius-full, 9999px);
|
|
360
|
+
font-size: var(--hx-font-size-sm, 0.875rem);
|
|
361
|
+
white-space: nowrap;
|
|
362
|
+
max-width: 12rem;
|
|
363
|
+
flex-shrink: 0;
|
|
364
|
+
}
|
|
365
|
+
.field__chip-label {
|
|
366
|
+
overflow: hidden;
|
|
367
|
+
text-overflow: ellipsis;
|
|
368
|
+
max-width: 8rem;
|
|
369
|
+
}
|
|
370
|
+
.field__chip-remove {
|
|
371
|
+
display: inline-flex;
|
|
372
|
+
align-items: center;
|
|
373
|
+
justify-content: center;
|
|
374
|
+
flex-shrink: 0;
|
|
375
|
+
width: 1rem;
|
|
376
|
+
height: 1rem;
|
|
377
|
+
border: none;
|
|
378
|
+
background: none;
|
|
379
|
+
cursor: pointer;
|
|
380
|
+
padding: 0;
|
|
381
|
+
color: inherit;
|
|
382
|
+
opacity: 0.7;
|
|
383
|
+
border-radius: 50%;
|
|
384
|
+
line-height: 1;
|
|
385
|
+
transition: opacity var(--hx-transition-fast, 150ms ease);
|
|
386
|
+
}
|
|
387
|
+
.field__chip-remove:hover {
|
|
388
|
+
opacity: 1;
|
|
389
|
+
background-color: var(--hx-combobox-chip-remove-hover-bg, var(--hx-color-primary-200, #bce1e1));
|
|
390
|
+
}
|
|
391
|
+
.field__chip-remove:focus-visible {
|
|
392
|
+
outline: var(--hx-focus-ring-width, 2px) solid
|
|
393
|
+
var(--hx-combobox-focus-ring-color, var(--hx-focus-ring-color, #0f7078));
|
|
394
|
+
outline-offset: var(--hx-focus-ring-offset, 2px);
|
|
395
|
+
opacity: 1;
|
|
396
|
+
}
|
|
397
|
+
`;
|
|
398
|
+
var te = Object.defineProperty, ie = Object.getOwnPropertyDescriptor, l = (e, t, i, r) => {
|
|
399
|
+
for (var o = r > 1 ? void 0 : r ? ie(t, i) : t, a = e.length - 1, n; a >= 0; a--)
|
|
400
|
+
(n = e[a]) && (o = (r ? n(t, i, o) : n(o)) || o);
|
|
401
|
+
return r && o && te(t, i, o), o;
|
|
402
|
+
};
|
|
403
|
+
const re = Z("hx-combobox");
|
|
404
|
+
function y(e) {
|
|
405
|
+
if (e.getAttribute("aria-hidden") === "true" || e.hasAttribute("hidden"))
|
|
406
|
+
return "";
|
|
407
|
+
let t = "";
|
|
408
|
+
const i = document.createTreeWalker(e, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT, {
|
|
409
|
+
acceptNode(o) {
|
|
410
|
+
if (o.nodeType === Node.ELEMENT_NODE) {
|
|
411
|
+
const a = o;
|
|
412
|
+
return a.getAttribute("aria-hidden") === "true" || a.hasAttribute("hidden") ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_SKIP;
|
|
413
|
+
}
|
|
414
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
let r = i.nextNode();
|
|
418
|
+
for (; r; )
|
|
419
|
+
t += r.textContent ?? "", r = i.nextNode();
|
|
420
|
+
return t.replace(/\s+/g, " ").trim();
|
|
421
|
+
}
|
|
422
|
+
let s = class extends W(Y) {
|
|
423
|
+
constructor() {
|
|
424
|
+
super(...arguments), this._id = re(), this._listboxId = `${this._id}-listbox`, this._helpTextId = `${this._id}-help`, this._errorId = `${this._id}-error`, this._labelId = `${this._id}-label`, this._liveRegionId = `${this._id}-live`, this._consumerDescId = `${this._id}-consumer-desc`, this.label = "", this.placeholder = "", this.value = "", this.required = !1, this.disabled = !1, this.name = "", this.error = "", this.helpText = "", this.size = "md", this.multiple = !1, this.clearable = !1, this.loading = !1, this.filterDebounce = 0, this.accessibleLabel = null, this.labelNoOptions = "No options found", this.labelRequired = "Please select an option.", this.labelRemoveOption = (e) => `Remove ${e}`, this._options = [], this._filterText = "", this._open = !1, this._focusedOptionIndex = -1, this._hasErrorSlot = !1, this._hasHelpSlot = !1, this._filterAnnouncement = "", this._labelSource = "none", this._labelSlotText = "", this._hasLabelSlot = !1, this._supportsIdrefRefs = !0, this._invalid = !1, this._announcedError = "", this._debounceTimer = null, this._ariaMirror = null, this._helpSlotTextObserver = null, this._errorSlotTextObserver = null, this._hostDescribedByObserver = null, this._consumerLabelledBy = null, this._consumerDescribedBy = null, this._slottedLabelEls = [], this._labelSlotTextObserver = null, this._externalRefsObserver = null, this._handleOutsideClick = (e) => {
|
|
425
|
+
this._open && !e.composedPath().includes(this) && this._closeDropdown();
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
// ─── Multiple Selection ───
|
|
429
|
+
// P0-1: Derive selected values Set from the comma-separated value property
|
|
430
|
+
/** @internal */
|
|
431
|
+
get _selectedValuesSet() {
|
|
432
|
+
return !this.multiple || !this.value ? /* @__PURE__ */ new Set() : new Set(this.value.split(",").filter(Boolean));
|
|
433
|
+
}
|
|
434
|
+
// ─── Filtered options ───
|
|
435
|
+
/** @internal */
|
|
436
|
+
get _filteredOptions() {
|
|
437
|
+
if (!this._filterText) return this._options;
|
|
438
|
+
const e = this._filterText.toLowerCase();
|
|
439
|
+
return this._options.filter((t) => t.label.toLowerCase().includes(e));
|
|
440
|
+
}
|
|
441
|
+
// ─── Lifecycle ───
|
|
442
|
+
connectedCallback() {
|
|
443
|
+
super.connectedCallback();
|
|
444
|
+
const e = this.constructor;
|
|
445
|
+
this._supportsIdrefRefs = e.__testSupportsIdrefRefsOverride !== null ? e.__testSupportsIdrefRefsOverride : J(this._internals), this._hostDescribedByObserver = new MutationObserver((t) => {
|
|
446
|
+
let i = !1;
|
|
447
|
+
for (const r of t) {
|
|
448
|
+
if (r.attributeName !== "aria-describedby") continue;
|
|
449
|
+
const o = r.oldValue, a = this.getAttribute("aria-describedby");
|
|
450
|
+
o !== null && a === null && (this._consumerDescribedBy = null, i = !0);
|
|
451
|
+
}
|
|
452
|
+
i && this._syncHostAriaSemantics();
|
|
453
|
+
}), this._hostDescribedByObserver.observe(this, {
|
|
454
|
+
attributes: !0,
|
|
455
|
+
attributeFilter: ["aria-describedby"],
|
|
456
|
+
attributeOldValue: !0
|
|
457
|
+
}), this._syncHostAriaSemantics(), this._ariaMirror = Q(this, () => {
|
|
458
|
+
this._syncHostAriaSemantics();
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
disconnectedCallback() {
|
|
462
|
+
var e, t, i, r, o, a;
|
|
463
|
+
super.disconnectedCallback(), typeof document < "u" && document.removeEventListener("click", this._handleOutsideClick), this._debounceTimer !== null && clearTimeout(this._debounceTimer), this._open && (this._open = !1), (e = this._ariaMirror) == null || e.disconnect(), this._ariaMirror = null, (t = this._helpSlotTextObserver) == null || t.disconnect(), this._helpSlotTextObserver = null, (i = this._errorSlotTextObserver) == null || i.disconnect(), this._errorSlotTextObserver = null, (r = this._labelSlotTextObserver) == null || r.disconnect(), this._labelSlotTextObserver = null, (o = this._hostDescribedByObserver) == null || o.disconnect(), this._hostDescribedByObserver = null, (a = this._externalRefsObserver) == null || a.disconnect(), this._externalRefsObserver = null;
|
|
464
|
+
}
|
|
465
|
+
updated(e) {
|
|
466
|
+
super.updated(e), e.has("value") && this._updateFormValue(), e.has("label") && this._refreshLabelSource(), this._syncHostAriaSemantics(), e.has("error") && (e.get("error") && this.error ? (this._announcedError = "", requestAnimationFrame(() => {
|
|
467
|
+
this._announcedError = this.error;
|
|
468
|
+
})) : this._announcedError = this.error);
|
|
469
|
+
}
|
|
470
|
+
willUpdate(e) {
|
|
471
|
+
super.willUpdate(e), (e.has("error") || !this.hasUpdated) && (this._announcedError = this.error ?? "");
|
|
472
|
+
}
|
|
473
|
+
firstUpdated(e) {
|
|
474
|
+
super.firstUpdated(e), this._seedSlotStateSync(), !this.label && !this.accessibleLabel && !this._hasLabelSlot && !this.getAttribute("aria-label") && this.getAttribute("aria-labelledby");
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Round-5 F2 (P2): synchronous slot-state seed. Mirrors the side effects of
|
|
478
|
+
* the three `_handle*SlotChange` handlers (label / help-text / error) but
|
|
479
|
+
* is driven by direct `slot.assignedNodes()` reads so we can populate the
|
|
480
|
+
* state BEFORE the microtask `slotchange` events fire after the first
|
|
481
|
+
* render. Idempotent — calling it later is a no-op when state already
|
|
482
|
+
* matches the slot contents.
|
|
483
|
+
*
|
|
484
|
+
* Also installs the label slot text observer so consumer in-place text
|
|
485
|
+
* mutations on slotted label nodes are tracked even if the slotchange
|
|
486
|
+
* event has not yet fired.
|
|
487
|
+
* @internal
|
|
488
|
+
*/
|
|
489
|
+
_seedSlotStateSync() {
|
|
490
|
+
const e = this.shadowRoot;
|
|
491
|
+
if (!e) return;
|
|
492
|
+
const t = e.querySelector('slot[name="label"]');
|
|
493
|
+
if (t) {
|
|
494
|
+
const o = this._readLabelSlotState(t);
|
|
495
|
+
this._hasLabelSlot = o.hasUsefulName, this._slottedLabelEls = o.elements, this._labelSlotText = o.text, this._installLabelSlotTextObserver(o.elements), this._refreshLabelSource();
|
|
496
|
+
}
|
|
497
|
+
const i = e.querySelector('slot[name="help-text"]');
|
|
498
|
+
i && (this._hasHelpSlot = this._readHelpSlotStateSync(i), this._installHelpSlotTextObserver(i));
|
|
499
|
+
const r = e.querySelector('slot[name="error"]');
|
|
500
|
+
r && (this._hasErrorSlot = this._readErrorSlotStateSync(r), this._installErrorSlotTextObserver(r));
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Reads the label slot's assigned nodes and computes the discriminated
|
|
504
|
+
* naming state. An empty whitespace-only slot does NOT count as a useful
|
|
505
|
+
* name.
|
|
506
|
+
*
|
|
507
|
+
* Round-4 F1 (P2): aggregates ALL assigned elements (not just the first) and
|
|
508
|
+
* concatenates `textContent` from every assigned node — element OR text —
|
|
509
|
+
* trimmed and space-joined per AccName 1.2 text-flatten rules. This preserves
|
|
510
|
+
* composed labels such as
|
|
511
|
+
* `<svg slot="label" aria-hidden="true">…</svg><span slot="label">Patient</span>`
|
|
512
|
+
* or `<span slot="label">First</span><span slot="label">name</span>` on both
|
|
513
|
+
* the modern (`internals.ariaLabelledByElements`) path and the no-IDL-ref
|
|
514
|
+
* fallback (`aria-label`) path.
|
|
515
|
+
*
|
|
516
|
+
* Round-6 F2 (P2): per AccName 1.2 §4.3.10, `aria-hidden="true"` elements
|
|
517
|
+
* contribute zero to the accessible name. They are still tracked in
|
|
518
|
+
* `elements` (so the modern `internals.ariaLabelledByElements` path projects
|
|
519
|
+
* the FULL visible label — icon + text — for AT that walks IDL refs), but
|
|
520
|
+
* they are skipped during text flattening. `hasUsefulName` is gated on the
|
|
521
|
+
* flattened text length, so a slot containing ONLY decorative elements (or
|
|
522
|
+
* empty wrappers) is NOT considered usable: `_labelSource` falls through to
|
|
523
|
+
* the next naming source and the firstUpdated() devWarn fires for unnamed
|
|
524
|
+
* controls. A composed `<svg aria-hidden><span>Patient</span>` slot still
|
|
525
|
+
* resolves: text = "Patient" → usable; both elements still project on the
|
|
526
|
+
* modern path.
|
|
527
|
+
* @internal
|
|
528
|
+
*/
|
|
529
|
+
_readLabelSlotState(e) {
|
|
530
|
+
const t = e.assignedNodes({ flatten: !0 }), i = [], r = [];
|
|
531
|
+
for (const a of t)
|
|
532
|
+
if (a.nodeType === Node.ELEMENT_NODE) {
|
|
533
|
+
const n = a;
|
|
534
|
+
if (i.push(n), n.getAttribute("aria-hidden") === "true") continue;
|
|
535
|
+
const p = y(n);
|
|
536
|
+
p && r.push(p);
|
|
537
|
+
} else if (a.nodeType === Node.TEXT_NODE) {
|
|
538
|
+
const n = (a.textContent ?? "").trim();
|
|
539
|
+
n && r.push(n);
|
|
540
|
+
}
|
|
541
|
+
const o = r.join(" ").replace(/\s+/g, " ").trim();
|
|
542
|
+
return {
|
|
543
|
+
// Round-6 F2 (P2): gate on flattened TEXT, not element presence. A slot
|
|
544
|
+
// with only decorative/aria-hidden elements (or empty wrappers) yields
|
|
545
|
+
// text === '' and is NOT a usable name.
|
|
546
|
+
hasUsefulName: o.length > 0,
|
|
547
|
+
elements: i,
|
|
548
|
+
text: o
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
/**
|
|
552
|
+
* Round-6 F1 (P2): re-evaluate the help-text slot's "has meaningful content"
|
|
553
|
+
* state from its current effective text. Mirrors the slotchange-handler
|
|
554
|
+
* logic but is invocable from the in-place mutation observer so that
|
|
555
|
+
* clearing `textContent` on the same slotted node flips `_hasHelpSlot`
|
|
556
|
+
* back to `false` (without this, the help wrapper stays visible, the help
|
|
557
|
+
* id stays in the inner input's `aria-describedby`, and AT keeps announcing
|
|
558
|
+
* stale guidance).
|
|
559
|
+
* @internal
|
|
560
|
+
*/
|
|
561
|
+
_readHelpSlotStateSync(e) {
|
|
562
|
+
const t = e.assignedNodes({ flatten: !0 });
|
|
563
|
+
for (const i of t)
|
|
564
|
+
if (i.nodeType === Node.TEXT_NODE) {
|
|
565
|
+
if ((i.textContent ?? "").trim().length > 0) return !0;
|
|
566
|
+
} else if (i.nodeType === Node.ELEMENT_NODE && y(i).length > 0)
|
|
567
|
+
return !0;
|
|
568
|
+
return !1;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Round-6 F1 (P2): re-evaluate the error slot's "has meaningful content"
|
|
572
|
+
* state from its current effective text. Mirrors the slotchange-handler
|
|
573
|
+
* logic but is invocable from the in-place mutation observer so that
|
|
574
|
+
* clearing `textContent` on the same slotted node flips `_hasErrorSlot`
|
|
575
|
+
* back to `false` (without this, the combobox stays in its error state
|
|
576
|
+
* indefinitely: `aria-invalid` stays `true`, the error id stays in
|
|
577
|
+
* `aria-describedby`, and help text stays hidden).
|
|
578
|
+
* @internal
|
|
579
|
+
*/
|
|
580
|
+
_readErrorSlotStateSync(e) {
|
|
581
|
+
const t = e.assignedNodes({ flatten: !0 });
|
|
582
|
+
for (const i of t)
|
|
583
|
+
if (i.nodeType === Node.TEXT_NODE) {
|
|
584
|
+
if ((i.textContent ?? "").trim().length > 0) return !0;
|
|
585
|
+
} else if (i.nodeType === Node.ELEMENT_NODE && y(i).length > 0)
|
|
586
|
+
return !0;
|
|
587
|
+
return !1;
|
|
588
|
+
}
|
|
589
|
+
// ─── Inner-input ARIA sync (W3C APG editable combobox) ───
|
|
590
|
+
/**
|
|
591
|
+
* Resolves consumer-supplied label/description IDREFs on the host and
|
|
592
|
+
* writes the canonical combobox ARIA onto the **inner `<input>`** per W3C
|
|
593
|
+
* APG editable combobox pattern. The inner input owns `role="combobox"`
|
|
594
|
+
* (replacing its implicit textbox role) and all combobox state ARIA so
|
|
595
|
+
* AT sees a single announced + focused surface.
|
|
596
|
+
*
|
|
597
|
+
* Cross-shadow naming uses a belt-and-suspenders strategy:
|
|
598
|
+
*
|
|
599
|
+
* 1. **Modern path** (`_supportsIdrefRefs === true`): consumer-resolved
|
|
600
|
+
* label/description elements are written onto
|
|
601
|
+
* `internals.ariaLabelledByElements` / `internals.ariaDescribedByElements`
|
|
602
|
+
* on the host. AT that walks up from the focused inner input for naming
|
|
603
|
+
* finds them through ElementInternals. Host-level `aria-labelledby` /
|
|
604
|
+
* `aria-describedby` attributes are LEFT IN PLACE so AT walking up the
|
|
605
|
+
* DOM also sees them. The text content of the resolved elements is also
|
|
606
|
+
* flattened onto the inner input as `aria-label` / `aria-description`
|
|
607
|
+
* so AT that does not walk up still announces the right name.
|
|
608
|
+
*
|
|
609
|
+
* 2. **Legacy fallback** (`_supportsIdrefRefs === false`): host attrs are
|
|
610
|
+
* stripped (AccName precedence cleanup) and the resolved-element text
|
|
611
|
+
* is flattened onto the inner input as `aria-label` /
|
|
612
|
+
* `aria-description`. Live updates to the consumer-referenced elements
|
|
613
|
+
* do not propagate, but the initial accessible name resolves on every
|
|
614
|
+
* AT — closing the cross-shadow naming gap that broke this on engines
|
|
615
|
+
* without IDL element references.
|
|
616
|
+
*
|
|
617
|
+
* Writing `aria-labelledby="<light-DOM id>"` directly on the shadow-DOM
|
|
618
|
+
* inner input is INTENTIONALLY avoided: light-DOM ids do not resolve from
|
|
619
|
+
* inside a shadow root, so that pattern leaves the control AT-anonymous.
|
|
620
|
+
* Internal label ids (in the same shadow root) ARE written via
|
|
621
|
+
* `aria-labelledby`.
|
|
622
|
+
* @internal
|
|
623
|
+
*/
|
|
624
|
+
/**
|
|
625
|
+
* Round-7 F1 (P2): (re)install a `MutationObserver` against the deduped
|
|
626
|
+
* union of consumer-resolved label/description elements. Watches
|
|
627
|
+
* `characterData`, `childList`, and `subtree` so any in-place text
|
|
628
|
+
* mutation on the referenced light-DOM nodes triggers a fresh sync —
|
|
629
|
+
* keeping the inner input's flattened `aria-label` and the synthesized
|
|
630
|
+
* description span aligned with the live consumer text. Blanket
|
|
631
|
+
* disconnect-and-reinstall on every sync; if the consumer retracts both
|
|
632
|
+
* IDREF chains the union is empty and the observer is disconnected.
|
|
633
|
+
* @internal
|
|
634
|
+
*/
|
|
635
|
+
_installExternalRefsObserver(e) {
|
|
636
|
+
if (this._externalRefsObserver && (this._externalRefsObserver.disconnect(), this._externalRefsObserver = null), e.length === 0) return;
|
|
637
|
+
const t = new Set(e), i = new MutationObserver(() => {
|
|
638
|
+
this._syncHostAriaSemantics();
|
|
639
|
+
});
|
|
640
|
+
for (const r of t)
|
|
641
|
+
i.observe(r, {
|
|
642
|
+
characterData: !0,
|
|
643
|
+
subtree: !0,
|
|
644
|
+
childList: !0,
|
|
645
|
+
attributes: !0,
|
|
646
|
+
attributeFilter: ["aria-hidden", "hidden"]
|
|
647
|
+
});
|
|
648
|
+
this._externalRefsObserver = i;
|
|
649
|
+
}
|
|
650
|
+
_syncHostAriaSemantics() {
|
|
651
|
+
var R, F, B, M;
|
|
652
|
+
const e = this._internals, t = this._input;
|
|
653
|
+
if (!t) {
|
|
654
|
+
const d = !e.validity.valid || !!(this.error || this._hasErrorSlot);
|
|
655
|
+
this._invalid = d;
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
const i = this.getAttribute("aria-label"), r = i !== null && i.trim() || "", o = ((R = this.shadowRoot) == null ? void 0 : R.getElementById(this._labelId)) ?? null, a = this._slottedLabelEls, n = ((F = this.shadowRoot) == null ? void 0 : F.getElementById(this._helpTextId)) ?? null, p = ((B = this.shadowRoot) == null ? void 0 : B.getElementById(this._errorId)) ?? null, S = this.getAttribute("aria-labelledby");
|
|
659
|
+
this._consumerLabelledBy = S;
|
|
660
|
+
const x = this.getAttribute("aria-describedby");
|
|
661
|
+
this._consumerDescribedBy = x;
|
|
662
|
+
const V = this._consumerLabelledBy, q = this._consumerDescribedBy, T = z(this, V), N = T.length > 0, A = z(this, q);
|
|
663
|
+
this._installExternalRefsObserver([...T, ...A]);
|
|
664
|
+
const m = !!(this.error || this._hasErrorSlot), j = !e.validity.valid || m;
|
|
665
|
+
this._invalid = j;
|
|
666
|
+
const _ = typeof this.accessibleLabel == "string" && this.accessibleLabel.trim().length > 0 ? this.accessibleLabel : null, w = (d) => d.getAttribute("aria-hidden") !== "true" && !d.hasAttribute("hidden"), v = [];
|
|
667
|
+
_ || (v.push(...T.filter(w)), !N && !r && (this._labelSource === "slot" && a.length > 0 ? v.push(...a.filter(w)) : this._labelSource === "string" && o && v.push(o)));
|
|
668
|
+
const E = [...A.filter(w)];
|
|
669
|
+
if (n && !m && (this.helpText || this._hasHelpSlot) && E.push(n), p && m && E.push(p), this._supportsIdrefRefs) {
|
|
670
|
+
const d = e;
|
|
671
|
+
d.ariaLabelledByElements = v.length > 0 ? v : null, d.ariaDescribedByElements = E.length > 0 ? E : null, _ ? e.ariaLabel = _ : e.ariaLabel = null;
|
|
672
|
+
}
|
|
673
|
+
const $ = (d) => d.filter(w).map((C) => y(C)).filter((C) => C.length > 0).join(" ");
|
|
674
|
+
let b = null, O = null, D = "";
|
|
675
|
+
if (!_ && N && (D = $(T)), _)
|
|
676
|
+
b = _;
|
|
677
|
+
else if (D)
|
|
678
|
+
b = D;
|
|
679
|
+
else if (r)
|
|
680
|
+
b = r;
|
|
681
|
+
else if (this._labelSource === "slot") {
|
|
682
|
+
if (this._labelSlotText)
|
|
683
|
+
b = this._labelSlotText;
|
|
684
|
+
else if (a.length > 0) {
|
|
685
|
+
const d = $(a);
|
|
686
|
+
d && (b = d);
|
|
687
|
+
}
|
|
688
|
+
} else this._labelSource === "string" && (o != null && o.id ? O = o.id : this.label && (b = this.label));
|
|
689
|
+
O ? (t.getAttribute("aria-labelledby") !== O && t.setAttribute("aria-labelledby", O), t.hasAttribute("aria-label") && t.removeAttribute("aria-label")) : b ? (t.getAttribute("aria-label") !== b && t.setAttribute("aria-label", b), t.hasAttribute("aria-labelledby") && t.removeAttribute("aria-labelledby")) : (t.hasAttribute("aria-label") && t.removeAttribute("aria-label"), t.hasAttribute("aria-labelledby") && t.removeAttribute("aria-labelledby"));
|
|
690
|
+
const I = ((M = this.shadowRoot) == null ? void 0 : M.getElementById(this._consumerDescId)) ?? null, k = $(A);
|
|
691
|
+
I && I.textContent !== k && (I.textContent = k);
|
|
692
|
+
const g = [];
|
|
693
|
+
if (k && I && g.push(this._consumerDescId), n && !m && (this.helpText || this._hasHelpSlot) && g.push(this._helpTextId), p && m && g.push(this._errorId), g.length > 0) {
|
|
694
|
+
const d = g.join(" ");
|
|
695
|
+
t.getAttribute("aria-describedby") !== d && t.setAttribute("aria-describedby", d);
|
|
696
|
+
} else t.hasAttribute("aria-describedby") && t.removeAttribute("aria-describedby");
|
|
697
|
+
t.hasAttribute("aria-description") && t.removeAttribute("aria-description");
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* (Re-)installs the mutation observer over the current set of assigned
|
|
701
|
+
* help-text-slot nodes.
|
|
702
|
+
* @internal
|
|
703
|
+
*/
|
|
704
|
+
_installHelpSlotTextObserver(e) {
|
|
705
|
+
var i;
|
|
706
|
+
if ((i = this._helpSlotTextObserver) == null || i.disconnect(), !e) {
|
|
707
|
+
this._helpSlotTextObserver = null;
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
const t = new MutationObserver(() => {
|
|
711
|
+
this._hasHelpSlot = this._readHelpSlotStateSync(e), this._syncHostAriaSemantics();
|
|
712
|
+
});
|
|
713
|
+
e.assignedNodes().forEach((r) => {
|
|
714
|
+
if (r.nodeType !== Node.ELEMENT_NODE) {
|
|
715
|
+
t.observe(r, {
|
|
716
|
+
characterData: !0,
|
|
717
|
+
childList: !0,
|
|
718
|
+
subtree: !0
|
|
719
|
+
});
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
t.observe(r, {
|
|
723
|
+
characterData: !0,
|
|
724
|
+
childList: !0,
|
|
725
|
+
subtree: !0,
|
|
726
|
+
attributes: !0,
|
|
727
|
+
attributeFilter: ["aria-hidden", "hidden"]
|
|
728
|
+
});
|
|
729
|
+
}), this._helpSlotTextObserver = t;
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* (Re-)installs the mutation observer over the current set of assigned
|
|
733
|
+
* error-slot nodes.
|
|
734
|
+
* @internal
|
|
735
|
+
*/
|
|
736
|
+
_installErrorSlotTextObserver(e) {
|
|
737
|
+
var i;
|
|
738
|
+
if ((i = this._errorSlotTextObserver) == null || i.disconnect(), !e) {
|
|
739
|
+
this._errorSlotTextObserver = null;
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
const t = new MutationObserver(() => {
|
|
743
|
+
this._hasErrorSlot = this._readErrorSlotStateSync(e), this._syncHostAriaSemantics();
|
|
744
|
+
});
|
|
745
|
+
e.assignedNodes().forEach((r) => {
|
|
746
|
+
if (r.nodeType !== Node.ELEMENT_NODE) {
|
|
747
|
+
t.observe(r, {
|
|
748
|
+
characterData: !0,
|
|
749
|
+
childList: !0,
|
|
750
|
+
subtree: !0
|
|
751
|
+
});
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
t.observe(r, {
|
|
755
|
+
characterData: !0,
|
|
756
|
+
childList: !0,
|
|
757
|
+
subtree: !0,
|
|
758
|
+
attributes: !0,
|
|
759
|
+
attributeFilter: ["aria-hidden", "hidden"]
|
|
760
|
+
});
|
|
761
|
+
}), this._errorSlotTextObserver = t;
|
|
762
|
+
}
|
|
763
|
+
// ─── Form Integration ───
|
|
764
|
+
/** @internal */
|
|
765
|
+
_updateFormValue() {
|
|
766
|
+
this._internals.setFormValue(this.value || null);
|
|
767
|
+
}
|
|
768
|
+
/** @internal */
|
|
769
|
+
_updateValidity() {
|
|
770
|
+
this.required && !this.value ? this._internals.setValidity(
|
|
771
|
+
{ valueMissing: !0 },
|
|
772
|
+
this.error || this.labelRequired,
|
|
773
|
+
this._input
|
|
774
|
+
) : this._internals.setValidity({}), this._syncHostAriaSemantics();
|
|
775
|
+
}
|
|
776
|
+
/** @internal */
|
|
777
|
+
_onFormReset() {
|
|
778
|
+
this.value = "", this._filterText = "", this._internals.setFormValue(null), this._resetInteractionState();
|
|
779
|
+
}
|
|
780
|
+
/** @internal */
|
|
781
|
+
// P1-6: Correct signature per WHATWG spec — includes mode param and all state types
|
|
782
|
+
_onFormStateRestore(e, t) {
|
|
783
|
+
typeof e == "string" && (this.value = e);
|
|
784
|
+
}
|
|
785
|
+
/** @internal */
|
|
786
|
+
_onFormDisabled(e) {
|
|
787
|
+
this.disabled = e;
|
|
788
|
+
}
|
|
789
|
+
// ─── Option Syncing from Slot ───
|
|
790
|
+
/** @internal */
|
|
791
|
+
_handleSlotChange() {
|
|
792
|
+
this._readOptions();
|
|
793
|
+
}
|
|
794
|
+
/** @internal */
|
|
795
|
+
_parseOption(e) {
|
|
796
|
+
var t;
|
|
797
|
+
return { value: e.value, label: ((t = e.textContent) == null ? void 0 : t.trim()) ?? e.value, disabled: e.disabled };
|
|
798
|
+
}
|
|
799
|
+
/** @internal */
|
|
800
|
+
_readOptions() {
|
|
801
|
+
var i;
|
|
802
|
+
const e = (i = this.shadowRoot) == null ? void 0 : i.querySelector('slot[name="option"]');
|
|
803
|
+
if (!e) return;
|
|
804
|
+
const t = [];
|
|
805
|
+
for (const r of e.assignedElements({ flatten: !0 }))
|
|
806
|
+
if (r instanceof HTMLOptionElement)
|
|
807
|
+
t.push(this._parseOption(r));
|
|
808
|
+
else if (r instanceof HTMLOptGroupElement)
|
|
809
|
+
for (const o of Array.from(r.children))
|
|
810
|
+
o instanceof HTMLOptionElement && t.push(this._parseOption(o));
|
|
811
|
+
this._options = t;
|
|
812
|
+
}
|
|
813
|
+
// ─── Slot Change Handlers ───
|
|
814
|
+
/**
|
|
815
|
+
* Tracks the slotted label so projected `<span slot="label">` content joins
|
|
816
|
+
* the accessible-name chain.
|
|
817
|
+
* @internal
|
|
818
|
+
*/
|
|
819
|
+
_handleLabelSlotChange(e) {
|
|
820
|
+
if (!(e.target instanceof HTMLSlotElement)) return;
|
|
821
|
+
const t = this._readLabelSlotState(e.target);
|
|
822
|
+
this._hasLabelSlot = t.hasUsefulName, this._slottedLabelEls = t.elements, this._labelSlotText = t.text, this._installLabelSlotTextObserver(t.elements), this._refreshLabelSource(), this._syncHostAriaSemantics();
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* (Re-)installs the mutation observer over the current set of slotted label
|
|
826
|
+
* elements. Round-4 F2 (P2): mirrors the round-23 P2 pattern from
|
|
827
|
+
* `_helpSlotTextObserver` / `_errorSlotTextObserver`. On any descendant text
|
|
828
|
+
* change we re-read the slot state (to refresh `_labelSlotText` and the
|
|
829
|
+
* element list) and re-sync host ARIA so the inner input's `aria-label`
|
|
830
|
+
* tracks the live label text.
|
|
831
|
+
* @internal
|
|
832
|
+
*/
|
|
833
|
+
_installLabelSlotTextObserver(e) {
|
|
834
|
+
var i;
|
|
835
|
+
if ((i = this._labelSlotTextObserver) == null || i.disconnect(), e.length === 0) {
|
|
836
|
+
this._labelSlotTextObserver = null;
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
const t = new MutationObserver(() => {
|
|
840
|
+
const r = [];
|
|
841
|
+
for (const a of e) {
|
|
842
|
+
if (a.getAttribute("aria-hidden") === "true") continue;
|
|
843
|
+
const n = y(a);
|
|
844
|
+
n && r.push(n);
|
|
845
|
+
}
|
|
846
|
+
const o = r.join(" ").replace(/\s+/g, " ").trim();
|
|
847
|
+
this._labelSlotText = o, this._hasLabelSlot = o.length > 0, this._refreshLabelSource(), this._syncHostAriaSemantics();
|
|
848
|
+
});
|
|
849
|
+
for (const r of e)
|
|
850
|
+
t.observe(r, {
|
|
851
|
+
characterData: !0,
|
|
852
|
+
childList: !0,
|
|
853
|
+
subtree: !0,
|
|
854
|
+
attributes: !0,
|
|
855
|
+
attributeFilter: ["aria-hidden", "hidden"]
|
|
856
|
+
});
|
|
857
|
+
this._labelSlotTextObserver = t;
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Recomputes the discriminated label source.
|
|
861
|
+
* @internal
|
|
862
|
+
*/
|
|
863
|
+
_refreshLabelSource() {
|
|
864
|
+
this.label ? this._labelSource = "string" : this._hasLabelSlot ? this._labelSource = "slot" : this._labelSource = "none";
|
|
865
|
+
}
|
|
866
|
+
/** @internal */
|
|
867
|
+
_handleErrorSlotChange(e) {
|
|
868
|
+
e.target instanceof HTMLSlotElement && (this._hasErrorSlot = this._readErrorSlotStateSync(e.target), this._installErrorSlotTextObserver(e.target), this._syncHostAriaSemantics());
|
|
869
|
+
}
|
|
870
|
+
/** @internal */
|
|
871
|
+
_handleHelpSlotChange(e) {
|
|
872
|
+
e.target instanceof HTMLSlotElement && (this._hasHelpSlot = this._readHelpSlotStateSync(e.target), this._installHelpSlotTextObserver(e.target), this._syncHostAriaSemantics());
|
|
873
|
+
}
|
|
874
|
+
// ─── Dropdown Control ───
|
|
875
|
+
/** @internal */
|
|
876
|
+
_openDropdown() {
|
|
877
|
+
this.disabled || this._open || (this._open = !0, this._focusedOptionIndex = -1, typeof document < "u" && document.addEventListener("click", this._handleOutsideClick), this.dispatchEvent(new CustomEvent("hx-show", { bubbles: !0, composed: !0 })));
|
|
878
|
+
}
|
|
879
|
+
/** @internal */
|
|
880
|
+
_closeDropdown() {
|
|
881
|
+
this._open && (this._open = !1, this._focusedOptionIndex = -1, this._handleInteractionBlur(), typeof document < "u" && document.removeEventListener("click", this._handleOutsideClick), this.dispatchEvent(new CustomEvent("hx-hide", { bubbles: !0, composed: !0 })));
|
|
882
|
+
}
|
|
883
|
+
// ─── Input Handling ───
|
|
884
|
+
/** @internal */
|
|
885
|
+
_handleInput(e) {
|
|
886
|
+
const t = e.target;
|
|
887
|
+
this._filterText = t.value, this._open || this._openDropdown(), this._focusedOptionIndex = -1, this.filterDebounce > 0 ? (this._debounceTimer !== null && clearTimeout(this._debounceTimer), this._debounceTimer = setTimeout(() => {
|
|
888
|
+
this._emitInput(), this._announceFilterResults();
|
|
889
|
+
}, this.filterDebounce)) : (this._emitInput(), this._announceFilterResults());
|
|
890
|
+
}
|
|
891
|
+
/** @internal */
|
|
892
|
+
_announceFilterResults() {
|
|
893
|
+
const e = this._filteredOptions.length;
|
|
894
|
+
this._filterAnnouncement = e === 0 ? "No matching options" : `${e} ${e === 1 ? "option" : "options"} available`;
|
|
895
|
+
}
|
|
896
|
+
/** @internal */
|
|
897
|
+
_emitInput() {
|
|
898
|
+
this.dispatchEvent(
|
|
899
|
+
new CustomEvent("hx-input", {
|
|
900
|
+
bubbles: !0,
|
|
901
|
+
composed: !0,
|
|
902
|
+
detail: { value: this._filterText }
|
|
903
|
+
})
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
/** @internal */
|
|
907
|
+
_handleFocus() {
|
|
908
|
+
this._openDropdown();
|
|
909
|
+
}
|
|
910
|
+
// ─── Keyboard Navigation ───
|
|
911
|
+
/** @internal */
|
|
912
|
+
_handleKeydown(e) {
|
|
913
|
+
if (this.disabled) return;
|
|
914
|
+
const t = this._filteredOptions, i = t.map((r, o) => ({ o: r, i: o })).filter(({ o: r }) => !r.disabled).map(({ i: r }) => r);
|
|
915
|
+
switch (e.key) {
|
|
916
|
+
case "ArrowDown": {
|
|
917
|
+
if (e.preventDefault(), !this._open) {
|
|
918
|
+
this._openDropdown(), this._focusedOptionIndex = i.length > 0 ? i[0] ?? 0 : -1;
|
|
919
|
+
break;
|
|
920
|
+
}
|
|
921
|
+
const r = i.find((o) => o > this._focusedOptionIndex);
|
|
922
|
+
this._focusedOptionIndex = r !== void 0 ? r : i[0] ?? this._focusedOptionIndex;
|
|
923
|
+
break;
|
|
924
|
+
}
|
|
925
|
+
case "ArrowUp": {
|
|
926
|
+
if (e.preventDefault(), !this._open) {
|
|
927
|
+
this._openDropdown();
|
|
928
|
+
const a = i[i.length - 1];
|
|
929
|
+
this._focusedOptionIndex = a !== void 0 ? a : -1;
|
|
930
|
+
break;
|
|
931
|
+
}
|
|
932
|
+
const r = [...i].reverse().find((a) => a < this._focusedOptionIndex), o = i[i.length - 1];
|
|
933
|
+
this._focusedOptionIndex = r !== void 0 ? r : o ?? this._focusedOptionIndex;
|
|
934
|
+
break;
|
|
935
|
+
}
|
|
936
|
+
case "Enter": {
|
|
937
|
+
if (e.preventDefault(), !this._open) {
|
|
938
|
+
this._openDropdown();
|
|
939
|
+
break;
|
|
940
|
+
}
|
|
941
|
+
if (this._focusedOptionIndex >= 0 && this._focusedOptionIndex < t.length) {
|
|
942
|
+
const r = t[this._focusedOptionIndex];
|
|
943
|
+
r && this._selectOption(r);
|
|
944
|
+
}
|
|
945
|
+
break;
|
|
946
|
+
}
|
|
947
|
+
case "Escape": {
|
|
948
|
+
e.preventDefault(), this._closeDropdown(), this._filterText = "", this._input && (this._input.value = "");
|
|
949
|
+
break;
|
|
950
|
+
}
|
|
951
|
+
case "Tab": {
|
|
952
|
+
this._closeDropdown();
|
|
953
|
+
break;
|
|
954
|
+
}
|
|
955
|
+
// P1-1: Home/End keyboard navigation for option list
|
|
956
|
+
case "Home": {
|
|
957
|
+
e.preventDefault(), this._open || this._openDropdown(), this._focusedOptionIndex = i.length > 0 ? i[0] ?? -1 : -1;
|
|
958
|
+
break;
|
|
959
|
+
}
|
|
960
|
+
case "End": {
|
|
961
|
+
e.preventDefault(), this._open || this._openDropdown(), this._focusedOptionIndex = i.length > 0 ? i[i.length - 1] ?? -1 : -1;
|
|
962
|
+
break;
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
// ─── Selection ───
|
|
967
|
+
// P0-1: Handle both single and multiple selection modes
|
|
968
|
+
/** @internal */
|
|
969
|
+
_selectOption(e) {
|
|
970
|
+
if (!e.disabled) {
|
|
971
|
+
if (this.multiple) {
|
|
972
|
+
const t = this._selectedValuesSet, i = new Set(t);
|
|
973
|
+
i.has(e.value) ? i.delete(e.value) : i.add(e.value), this.value = [...i].join(",");
|
|
974
|
+
} else
|
|
975
|
+
this.value = e.value, this._closeDropdown();
|
|
976
|
+
this._handleInteractionInput(), this._filterText = "", this._input && (this._input.value = ""), this._dispatchChange();
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
// P0-1: Remove a single value from multi-selection
|
|
980
|
+
/** @internal */
|
|
981
|
+
_removeValue(e) {
|
|
982
|
+
const t = this._selectedValuesSet;
|
|
983
|
+
t.delete(e), this.value = [...t].join(","), this._dispatchChange();
|
|
984
|
+
}
|
|
985
|
+
// ─── Clear ───
|
|
986
|
+
/** @internal */
|
|
987
|
+
_handleClear(e) {
|
|
988
|
+
e.stopPropagation(), this.value = "", this._filterText = "", this._input && (this._input.value = "", this._input.focus()), this._internals.setFormValue(null), this._updateValidity(), this.dispatchEvent(new CustomEvent("hx-clear", { bubbles: !0, composed: !0 }));
|
|
989
|
+
}
|
|
990
|
+
// ─── Event Dispatchers ───
|
|
991
|
+
/** @internal */
|
|
992
|
+
_dispatchChange() {
|
|
993
|
+
this.dispatchEvent(
|
|
994
|
+
new CustomEvent("hx-change", {
|
|
995
|
+
bubbles: !0,
|
|
996
|
+
composed: !0,
|
|
997
|
+
detail: { value: this.value }
|
|
998
|
+
})
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
// ─── Public Methods ───
|
|
1002
|
+
/**
|
|
1003
|
+
* Routes programmatic focus to the inner text input. Per W3C APG editable
|
|
1004
|
+
* combobox pattern, the inner `<input>` carries `role="combobox"` (replacing
|
|
1005
|
+
* its implicit textbox role) so it IS the canonical announced + focused
|
|
1006
|
+
* surface. Shadow DOM focus causes `document.activeElement` to report the
|
|
1007
|
+
* host externally, but the input retains focus inside the shadow root and
|
|
1008
|
+
* AT announces its role/state.
|
|
1009
|
+
*/
|
|
1010
|
+
focus(e) {
|
|
1011
|
+
var t;
|
|
1012
|
+
(t = this._input) == null || t.focus(e);
|
|
1013
|
+
}
|
|
1014
|
+
// ─── Render Helpers ───
|
|
1015
|
+
/** @internal */
|
|
1016
|
+
_optionId(e) {
|
|
1017
|
+
return `${this._id}-option-${e}`;
|
|
1018
|
+
}
|
|
1019
|
+
/** @internal */
|
|
1020
|
+
get _displayValue() {
|
|
1021
|
+
if (this.multiple || !this.value) return "";
|
|
1022
|
+
const e = this._options.find((t) => t.value === this.value);
|
|
1023
|
+
return e ? e.label : this.value;
|
|
1024
|
+
}
|
|
1025
|
+
/** @internal */
|
|
1026
|
+
_renderOptions() {
|
|
1027
|
+
const e = this._filteredOptions;
|
|
1028
|
+
return e.length === 0 ? f`
|
|
1029
|
+
<slot name="empty-label">
|
|
1030
|
+
<div class="field__no-options">${this.labelNoOptions}</div>
|
|
1031
|
+
</slot>
|
|
1032
|
+
` : X(
|
|
1033
|
+
e,
|
|
1034
|
+
(t) => t.value,
|
|
1035
|
+
(t, i) => {
|
|
1036
|
+
const r = this.multiple ? this._selectedValuesSet.has(t.value) : t.value === this.value, o = i === this._focusedOptionIndex;
|
|
1037
|
+
return f`
|
|
1038
|
+
<div
|
|
1039
|
+
id=${this._optionId(i)}
|
|
1040
|
+
part="option"
|
|
1041
|
+
role="option"
|
|
1042
|
+
class=${H({
|
|
1043
|
+
field__option: !0,
|
|
1044
|
+
"field__option--selected": r,
|
|
1045
|
+
"field__option--focused": o,
|
|
1046
|
+
"field__option--disabled": t.disabled
|
|
1047
|
+
})}
|
|
1048
|
+
aria-selected=${this.multiple ? r ? "true" : "false" : r ? "true" : u}
|
|
1049
|
+
aria-disabled=${t.disabled ? "true" : u}
|
|
1050
|
+
@click=${() => this._selectOption(t)}
|
|
1051
|
+
>
|
|
1052
|
+
<span class="field__option-label">${t.label}</span>
|
|
1053
|
+
</div>
|
|
1054
|
+
`;
|
|
1055
|
+
}
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
// ─── Main Render ───
|
|
1059
|
+
render() {
|
|
1060
|
+
const e = !!this.error || this._hasErrorSlot, t = !!this.helpText || this._hasHelpSlot, i = this.clearable && !!this.value && !this.disabled, r = {
|
|
1061
|
+
field: !0,
|
|
1062
|
+
"field--error": e,
|
|
1063
|
+
"field--disabled": this.disabled,
|
|
1064
|
+
"field--required": this.required,
|
|
1065
|
+
"field--open": this._open
|
|
1066
|
+
}, o = {
|
|
1067
|
+
field__input: !0,
|
|
1068
|
+
[`field__input--${this.size}`]: !0
|
|
1069
|
+
}, a = this._open && this._focusedOptionIndex >= 0 ? this._optionId(this._focusedOptionIndex) : "";
|
|
1070
|
+
return f`
|
|
1071
|
+
<div part="field" class=${H(r)}>
|
|
1072
|
+
<!-- Label -->
|
|
1073
|
+
<slot name="label" @slotchange=${this._handleLabelSlotChange}>
|
|
1074
|
+
${this.label ? f`<label id=${this._labelId} for=${this._id} part="label" class="field__label">
|
|
1075
|
+
${this.label}
|
|
1076
|
+
${this.required ? f`<span class="field__required-marker" aria-hidden="true">*</span>` : u}
|
|
1077
|
+
</label>` : u}
|
|
1078
|
+
</slot>
|
|
1079
|
+
|
|
1080
|
+
<!-- Input Wrapper -->
|
|
1081
|
+
<div part="trigger" class="field__input-wrapper">
|
|
1082
|
+
<!-- Prefix Slot -->
|
|
1083
|
+
<slot name="prefix" class="field__prefix"></slot>
|
|
1084
|
+
|
|
1085
|
+
<!-- P0-1: Selected value chips for multiple mode -->
|
|
1086
|
+
${this.multiple && this._selectedValuesSet.size > 0 ? [...this._selectedValuesSet].map((n) => {
|
|
1087
|
+
const p = this._options.find((x) => x.value === n), S = p ? p.label : n;
|
|
1088
|
+
return f`
|
|
1089
|
+
<span class="field__chip">
|
|
1090
|
+
<span class="field__chip-label">${S}</span>
|
|
1091
|
+
<button
|
|
1092
|
+
type="button"
|
|
1093
|
+
class="field__chip-remove"
|
|
1094
|
+
aria-label=${this.labelRemoveOption(S)}
|
|
1095
|
+
@click=${(x) => {
|
|
1096
|
+
x.stopPropagation(), this._removeValue(n);
|
|
1097
|
+
}}
|
|
1098
|
+
>
|
|
1099
|
+
<svg
|
|
1100
|
+
width="8"
|
|
1101
|
+
height="8"
|
|
1102
|
+
viewBox="0 0 8 8"
|
|
1103
|
+
fill="none"
|
|
1104
|
+
aria-hidden="true"
|
|
1105
|
+
focusable="false"
|
|
1106
|
+
>
|
|
1107
|
+
<path
|
|
1108
|
+
d="M1 1L7 7M7 1L1 7"
|
|
1109
|
+
stroke="currentColor"
|
|
1110
|
+
stroke-width="1.5"
|
|
1111
|
+
stroke-linecap="round"
|
|
1112
|
+
/>
|
|
1113
|
+
</svg>
|
|
1114
|
+
</button>
|
|
1115
|
+
</span>
|
|
1116
|
+
`;
|
|
1117
|
+
}) : u}
|
|
1118
|
+
|
|
1119
|
+
<!--
|
|
1120
|
+
Text input — W3C APG editable combobox (option I).
|
|
1121
|
+
role="combobox" REPLACES the implicit textbox role; all combobox
|
|
1122
|
+
state ARIA lives here so AT sees one canonical surface.
|
|
1123
|
+
aria-label / aria-labelledby / aria-describedby are written
|
|
1124
|
+
imperatively by _syncHostAriaSemantics after consumer IDREF
|
|
1125
|
+
resolution and are not bound here.
|
|
1126
|
+
-->
|
|
1127
|
+
<input
|
|
1128
|
+
part="input"
|
|
1129
|
+
type="text"
|
|
1130
|
+
id=${this._id}
|
|
1131
|
+
role="combobox"
|
|
1132
|
+
class=${H(o)}
|
|
1133
|
+
.value=${this._filterText || (this._open ? "" : this._displayValue)}
|
|
1134
|
+
placeholder=${L(this.placeholder || void 0)}
|
|
1135
|
+
?disabled=${this.disabled}
|
|
1136
|
+
?required=${this.required}
|
|
1137
|
+
name=${L(this.name || void 0)}
|
|
1138
|
+
autocomplete="off"
|
|
1139
|
+
aria-haspopup="listbox"
|
|
1140
|
+
aria-autocomplete="list"
|
|
1141
|
+
aria-controls=${this._listboxId}
|
|
1142
|
+
aria-expanded=${this._open ? "true" : "false"}
|
|
1143
|
+
aria-activedescendant=${L(a || void 0)}
|
|
1144
|
+
aria-required=${this.required ? "true" : "false"}
|
|
1145
|
+
aria-invalid=${this._invalid ? "true" : "false"}
|
|
1146
|
+
aria-busy=${this.loading ? "true" : u}
|
|
1147
|
+
aria-disabled=${this.disabled ? "true" : u}
|
|
1148
|
+
@input=${this._handleInput}
|
|
1149
|
+
@focus=${this._handleFocus}
|
|
1150
|
+
@keydown=${this._handleKeydown}
|
|
1151
|
+
/>
|
|
1152
|
+
|
|
1153
|
+
<!-- Loading Indicator -->
|
|
1154
|
+
${this.loading ? f`
|
|
1155
|
+
<div part="loading-indicator" class="field__loading-indicator" aria-hidden="true">
|
|
1156
|
+
<div class="field__loading-spinner"></div>
|
|
1157
|
+
</div>
|
|
1158
|
+
` : u}
|
|
1159
|
+
|
|
1160
|
+
<!-- Clear Button -->
|
|
1161
|
+
${i ? f`
|
|
1162
|
+
<button
|
|
1163
|
+
part="clear-button"
|
|
1164
|
+
type="button"
|
|
1165
|
+
class="field__clear-button"
|
|
1166
|
+
aria-label=${`Clear ${this.label || this.accessibleLabel || "selection"}`}
|
|
1167
|
+
tabindex="0"
|
|
1168
|
+
@click=${this._handleClear}
|
|
1169
|
+
>
|
|
1170
|
+
<svg
|
|
1171
|
+
width="12"
|
|
1172
|
+
height="12"
|
|
1173
|
+
viewBox="0 0 12 12"
|
|
1174
|
+
fill="none"
|
|
1175
|
+
aria-hidden="true"
|
|
1176
|
+
focusable="false"
|
|
1177
|
+
>
|
|
1178
|
+
<path
|
|
1179
|
+
d="M1 1L11 11M11 1L1 11"
|
|
1180
|
+
stroke="currentColor"
|
|
1181
|
+
stroke-width="1.5"
|
|
1182
|
+
stroke-linecap="round"
|
|
1183
|
+
/>
|
|
1184
|
+
</svg>
|
|
1185
|
+
</button>
|
|
1186
|
+
` : u}
|
|
1187
|
+
|
|
1188
|
+
<!-- Suffix Slot -->
|
|
1189
|
+
<slot name="suffix" class="field__suffix"></slot>
|
|
1190
|
+
</div>
|
|
1191
|
+
|
|
1192
|
+
<!-- Listbox -->
|
|
1193
|
+
<div
|
|
1194
|
+
part="listbox"
|
|
1195
|
+
role="listbox"
|
|
1196
|
+
id=${this._listboxId}
|
|
1197
|
+
class="field__listbox"
|
|
1198
|
+
aria-label=${L(this.label || this.accessibleLabel || void 0)}
|
|
1199
|
+
aria-multiselectable=${this.multiple ? "true" : u}
|
|
1200
|
+
?hidden=${!this._open}
|
|
1201
|
+
>
|
|
1202
|
+
<div class="field__options">${this._renderOptions()}</div>
|
|
1203
|
+
</div>
|
|
1204
|
+
|
|
1205
|
+
<!-- Hidden slot (options read from here) -->
|
|
1206
|
+
<slot name="option" @slotchange=${this._handleSlotChange} style="display:none;"></slot>
|
|
1207
|
+
|
|
1208
|
+
<!--
|
|
1209
|
+
Persistent error live region. role="alert" is set from first paint
|
|
1210
|
+
so the WAI-ARIA contract for live updates is honoured.
|
|
1211
|
+
-->
|
|
1212
|
+
<div
|
|
1213
|
+
part="error"
|
|
1214
|
+
class="field__error"
|
|
1215
|
+
id=${this._errorId}
|
|
1216
|
+
role="alert"
|
|
1217
|
+
?hidden=${!e}
|
|
1218
|
+
>
|
|
1219
|
+
<slot name="error" @slotchange=${this._handleErrorSlotChange}
|
|
1220
|
+
>${this._announcedError}</slot
|
|
1221
|
+
>
|
|
1222
|
+
</div>
|
|
1223
|
+
|
|
1224
|
+
<!--
|
|
1225
|
+
Persistent help-text container. Rendered whenever the property OR
|
|
1226
|
+
the slot has content; hidden when an error is present.
|
|
1227
|
+
-->
|
|
1228
|
+
<div
|
|
1229
|
+
part="help-text"
|
|
1230
|
+
class="field__help-text"
|
|
1231
|
+
id=${this._helpTextId}
|
|
1232
|
+
?hidden=${!t || e}
|
|
1233
|
+
>
|
|
1234
|
+
<slot name="help-text" @slotchange=${this._handleHelpSlotChange}>${this.helpText}</slot>
|
|
1235
|
+
</div>
|
|
1236
|
+
|
|
1237
|
+
<!-- Filter results live region -->
|
|
1238
|
+
<div id=${this._liveRegionId} aria-live="polite" aria-atomic="true" class="field__sr-only">
|
|
1239
|
+
${this._filterAnnouncement}
|
|
1240
|
+
</div>
|
|
1241
|
+
|
|
1242
|
+
<!--
|
|
1243
|
+
Round-5 F1 (P1): synthesized in-shadow mirror of the consumer-
|
|
1244
|
+
resolved description text. Its id is appended to the inner input's
|
|
1245
|
+
aria-describedby chain so AT picks the consumer description up
|
|
1246
|
+
through the standard described-by channel without needing
|
|
1247
|
+
aria-description (which W3C AccName drops whenever
|
|
1248
|
+
aria-describedby is also present). Same-root id resolves from
|
|
1249
|
+
inside the shadow tree; consumer light-DOM ids do not. The text
|
|
1250
|
+
content is updated by _syncHostAriaSemantics on every sync.
|
|
1251
|
+
-->
|
|
1252
|
+
<span id=${this._consumerDescId} class="field__sr-only" aria-hidden="false"></span>
|
|
1253
|
+
</div>
|
|
1254
|
+
`;
|
|
1255
|
+
}
|
|
1256
|
+
};
|
|
1257
|
+
s.styles = [ee, K];
|
|
1258
|
+
s.formAssociated = !0;
|
|
1259
|
+
s.__testSupportsIdrefRefsOverride = null;
|
|
1260
|
+
l([
|
|
1261
|
+
h({ type: String })
|
|
1262
|
+
], s.prototype, "label", 2);
|
|
1263
|
+
l([
|
|
1264
|
+
h({ type: String })
|
|
1265
|
+
], s.prototype, "placeholder", 2);
|
|
1266
|
+
l([
|
|
1267
|
+
h({ type: String, reflect: !0 })
|
|
1268
|
+
], s.prototype, "value", 2);
|
|
1269
|
+
l([
|
|
1270
|
+
h({ type: Boolean, reflect: !0 })
|
|
1271
|
+
], s.prototype, "required", 2);
|
|
1272
|
+
l([
|
|
1273
|
+
h({ type: Boolean, reflect: !0 })
|
|
1274
|
+
], s.prototype, "disabled", 2);
|
|
1275
|
+
l([
|
|
1276
|
+
h({ type: String, reflect: !0 })
|
|
1277
|
+
], s.prototype, "name", 2);
|
|
1278
|
+
l([
|
|
1279
|
+
h({ type: String })
|
|
1280
|
+
], s.prototype, "error", 2);
|
|
1281
|
+
l([
|
|
1282
|
+
h({ type: String, attribute: "help-text" })
|
|
1283
|
+
], s.prototype, "helpText", 2);
|
|
1284
|
+
l([
|
|
1285
|
+
h({ type: String, attribute: "hx-size", reflect: !0 })
|
|
1286
|
+
], s.prototype, "size", 2);
|
|
1287
|
+
l([
|
|
1288
|
+
h({ type: Boolean, reflect: !0 })
|
|
1289
|
+
], s.prototype, "multiple", 2);
|
|
1290
|
+
l([
|
|
1291
|
+
h({ type: Boolean, reflect: !0 })
|
|
1292
|
+
], s.prototype, "clearable", 2);
|
|
1293
|
+
l([
|
|
1294
|
+
h({ type: Boolean, reflect: !0 })
|
|
1295
|
+
], s.prototype, "loading", 2);
|
|
1296
|
+
l([
|
|
1297
|
+
h({ type: Number, attribute: "filter-debounce" })
|
|
1298
|
+
], s.prototype, "filterDebounce", 2);
|
|
1299
|
+
l([
|
|
1300
|
+
h({ type: String, attribute: "accessible-label" })
|
|
1301
|
+
], s.prototype, "accessibleLabel", 2);
|
|
1302
|
+
l([
|
|
1303
|
+
h({ type: String, attribute: "label-no-options" })
|
|
1304
|
+
], s.prototype, "labelNoOptions", 2);
|
|
1305
|
+
l([
|
|
1306
|
+
h({ type: String, attribute: "label-required" })
|
|
1307
|
+
], s.prototype, "labelRequired", 2);
|
|
1308
|
+
l([
|
|
1309
|
+
h({ attribute: !1 })
|
|
1310
|
+
], s.prototype, "labelRemoveOption", 2);
|
|
1311
|
+
l([
|
|
1312
|
+
c()
|
|
1313
|
+
], s.prototype, "_options", 2);
|
|
1314
|
+
l([
|
|
1315
|
+
c()
|
|
1316
|
+
], s.prototype, "_filterText", 2);
|
|
1317
|
+
l([
|
|
1318
|
+
c()
|
|
1319
|
+
], s.prototype, "_open", 2);
|
|
1320
|
+
l([
|
|
1321
|
+
c()
|
|
1322
|
+
], s.prototype, "_focusedOptionIndex", 2);
|
|
1323
|
+
l([
|
|
1324
|
+
c()
|
|
1325
|
+
], s.prototype, "_hasErrorSlot", 2);
|
|
1326
|
+
l([
|
|
1327
|
+
c()
|
|
1328
|
+
], s.prototype, "_hasHelpSlot", 2);
|
|
1329
|
+
l([
|
|
1330
|
+
c()
|
|
1331
|
+
], s.prototype, "_filterAnnouncement", 2);
|
|
1332
|
+
l([
|
|
1333
|
+
c()
|
|
1334
|
+
], s.prototype, "_labelSource", 2);
|
|
1335
|
+
l([
|
|
1336
|
+
c()
|
|
1337
|
+
], s.prototype, "_labelSlotText", 2);
|
|
1338
|
+
l([
|
|
1339
|
+
c()
|
|
1340
|
+
], s.prototype, "_hasLabelSlot", 2);
|
|
1341
|
+
l([
|
|
1342
|
+
c()
|
|
1343
|
+
], s.prototype, "_supportsIdrefRefs", 2);
|
|
1344
|
+
l([
|
|
1345
|
+
c()
|
|
1346
|
+
], s.prototype, "_invalid", 2);
|
|
1347
|
+
l([
|
|
1348
|
+
c()
|
|
1349
|
+
], s.prototype, "_announcedError", 2);
|
|
1350
|
+
l([
|
|
1351
|
+
U(".field__input")
|
|
1352
|
+
], s.prototype, "_input", 2);
|
|
1353
|
+
s = l([
|
|
1354
|
+
G("hx-combobox")
|
|
1355
|
+
], s);
|
|
1356
|
+
export {
|
|
1357
|
+
s as H
|
|
1358
|
+
};
|
|
1359
|
+
//# sourceMappingURL=hx-combobox-DvlezcDV.js.map
|