@f-ewald/components 0.7.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -47
- package/custom-elements.json +2236 -966
- package/dist/calendar-entry.d.ts +33 -0
- package/dist/calendar-entry.d.ts.map +1 -0
- package/dist/calendar-entry.js +64 -0
- package/dist/calendar-entry.js.map +1 -0
- package/dist/calendar-month.d.ts +67 -0
- package/dist/calendar-month.d.ts.map +1 -0
- package/dist/calendar-month.js +455 -0
- package/dist/calendar-month.js.map +1 -0
- package/dist/calendar-year.d.ts +43 -0
- package/dist/calendar-year.d.ts.map +1 -0
- package/dist/calendar-year.js +136 -0
- package/dist/calendar-year.js.map +1 -0
- package/dist/form-select.d.ts +21 -2
- package/dist/form-select.d.ts.map +1 -1
- package/dist/form-select.js +431 -27
- package/dist/form-select.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/map-circle.d.ts +3 -0
- package/dist/map-circle.d.ts.map +1 -1
- package/dist/map-circle.js +3 -0
- package/dist/map-circle.js.map +1 -1
- package/dist/tokens.css +4 -0
- package/dist/tokens.d.ts.map +1 -1
- package/dist/tokens.js +3 -0
- package/dist/tokens.js.map +1 -1
- package/dist/utils/calendar.d.ts +77 -0
- package/dist/utils/calendar.d.ts.map +1 -0
- package/dist/utils/calendar.js +135 -0
- package/dist/utils/calendar.js.map +1 -0
- package/docs/calendar-entry.md +53 -0
- package/docs/calendar-month.md +77 -0
- package/docs/calendar-year.md +53 -0
- package/docs/form-select.md +15 -3
- package/docs/map-circle.md +4 -0
- package/llms.txt +117 -28
- package/package.json +10 -2
- package/dist/map-point.d.ts +0 -31
- package/dist/map-point.d.ts.map +0 -1
- package/dist/map-point.js +0 -83
- package/dist/map-point.js.map +0 -1
- package/docs/map-point.md +0 -44
package/dist/form-select.js
CHANGED
|
@@ -4,7 +4,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
import { LitElement, css, html, nothing } from "lit";
|
|
7
|
+
import { LitElement, css, html, nothing, } from "lit";
|
|
8
8
|
import { customElement, property, state } from "lit/decorators.js";
|
|
9
9
|
import { iconChevronRight } from "./icons.js";
|
|
10
10
|
import { tokens } from "./tokens.js";
|
|
@@ -21,6 +21,13 @@ import { tokens } from "./tokens.js";
|
|
|
21
21
|
* bar, a status picker) keep shrink-to-fit auto-width unchanged. To make an
|
|
22
22
|
* instance full-width, size the host itself: `form-select { width: 100%; }`.
|
|
23
23
|
*
|
|
24
|
+
* Set `searchable` to replace the button trigger with an editable combobox
|
|
25
|
+
* that filters the predefined options by case-insensitive label infix. Typed
|
|
26
|
+
* text is only a query: `value` changes exclusively when an actual option is
|
|
27
|
+
* selected, and an uncommitted query is discarded when the list closes.
|
|
28
|
+
* Each option may also provide a pre-rendered `icon` and square `iconSize`;
|
|
29
|
+
* iconless options reserve no leading space.
|
|
30
|
+
*
|
|
24
31
|
* @element form-select
|
|
25
32
|
* @fires change - Fired with `{ value: string }` when a different option is picked.
|
|
26
33
|
*/
|
|
@@ -35,11 +42,25 @@ let FormSelect = class FormSelect extends LitElement {
|
|
|
35
42
|
this.label = "";
|
|
36
43
|
/** Disables the trigger, preventing the popover from opening. */
|
|
37
44
|
this.disabled = false;
|
|
45
|
+
/**
|
|
46
|
+
* Enables editable, case-insensitive infix filtering by option label.
|
|
47
|
+
* Typed text never becomes the selected `value`.
|
|
48
|
+
*/
|
|
49
|
+
this.searchable = false;
|
|
38
50
|
this._open = false;
|
|
39
51
|
this._activeIndex = -1;
|
|
52
|
+
this._query = null;
|
|
53
|
+
this.#listboxPointerDown = false;
|
|
54
|
+
this.#suppressNextBlur = false;
|
|
55
|
+
this.#restoringSearchFocus = false;
|
|
56
|
+
this.#searchSelection = null;
|
|
57
|
+
this.#isComposing = false;
|
|
58
|
+
this.#compositionJustEnded = false;
|
|
59
|
+
this.#compositionEndTimer = null;
|
|
60
|
+
this.#blurTimer = null;
|
|
40
61
|
this.#onWindowMousedown = (e) => {
|
|
41
62
|
if (!e.composedPath().includes(this))
|
|
42
|
-
this
|
|
63
|
+
this.#close();
|
|
43
64
|
};
|
|
44
65
|
}
|
|
45
66
|
static { this.styles = [
|
|
@@ -78,12 +99,56 @@ let FormSelect = class FormSelect extends LitElement {
|
|
|
78
99
|
border-color: var(--ui-primary, #4f46e5);
|
|
79
100
|
box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));
|
|
80
101
|
}
|
|
102
|
+
.search-trigger {
|
|
103
|
+
display: flex;
|
|
104
|
+
align-items: center;
|
|
105
|
+
justify-content: space-between;
|
|
106
|
+
gap: 0.35rem;
|
|
107
|
+
width: 100%;
|
|
108
|
+
box-sizing: border-box;
|
|
109
|
+
color: var(--ui-text, #0f172a);
|
|
110
|
+
background: var(--ui-surface, #fff);
|
|
111
|
+
border: 1px solid var(--ui-border, #e2e8f0);
|
|
112
|
+
border-radius: var(--ui-radius-sm, 0.25rem);
|
|
113
|
+
cursor: text;
|
|
114
|
+
}
|
|
115
|
+
.search-trigger:hover:not(.disabled) {
|
|
116
|
+
background: var(--ui-surface-muted, #f8fafc);
|
|
117
|
+
}
|
|
118
|
+
.search-trigger:focus-within {
|
|
119
|
+
border-color: var(--ui-primary, #4f46e5);
|
|
120
|
+
box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));
|
|
121
|
+
}
|
|
122
|
+
.search-trigger.disabled {
|
|
123
|
+
cursor: not-allowed;
|
|
124
|
+
opacity: 0.6;
|
|
125
|
+
}
|
|
126
|
+
.search-trigger.has-icon input.search-input {
|
|
127
|
+
padding-left: 0;
|
|
128
|
+
}
|
|
129
|
+
input.search-input {
|
|
130
|
+
min-width: 0;
|
|
131
|
+
flex: 1;
|
|
132
|
+
box-sizing: border-box;
|
|
133
|
+
padding: 0.3rem 0 0.3rem 0.5rem;
|
|
134
|
+
color: inherit;
|
|
135
|
+
background: transparent;
|
|
136
|
+
border: 0;
|
|
137
|
+
outline: 0;
|
|
138
|
+
font: inherit;
|
|
139
|
+
}
|
|
81
140
|
.chevron {
|
|
82
141
|
display: flex;
|
|
142
|
+
flex: 0 0 auto;
|
|
143
|
+
margin-right: 0.5rem;
|
|
83
144
|
color: var(--ui-text-muted, #64748b);
|
|
145
|
+
pointer-events: none;
|
|
84
146
|
transform: rotate(90deg);
|
|
85
147
|
transition: transform 0.1s ease;
|
|
86
148
|
}
|
|
149
|
+
button.trigger .chevron {
|
|
150
|
+
margin-right: 0;
|
|
151
|
+
}
|
|
87
152
|
:host([open]) .chevron {
|
|
88
153
|
transform: rotate(-90deg);
|
|
89
154
|
}
|
|
@@ -95,15 +160,26 @@ let FormSelect = class FormSelect extends LitElement {
|
|
|
95
160
|
min-width: 100%;
|
|
96
161
|
max-height: 40vh;
|
|
97
162
|
overflow-y: auto;
|
|
163
|
+
scrollbar-gutter: stable;
|
|
98
164
|
margin: 2px 0 0;
|
|
99
165
|
padding: 4px 0;
|
|
100
166
|
list-style: none;
|
|
101
167
|
white-space: nowrap;
|
|
168
|
+
color: var(--ui-text, #0f172a);
|
|
102
169
|
background: var(--ui-surface, #fff);
|
|
103
170
|
border: 1px solid var(--ui-border, #e2e8f0);
|
|
104
171
|
border-radius: var(--ui-radius-sm, 0.25rem);
|
|
105
172
|
box-shadow: var(--ui-shadow, 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1));
|
|
106
173
|
}
|
|
174
|
+
ul.options::-webkit-scrollbar {
|
|
175
|
+
width: 0.75rem;
|
|
176
|
+
}
|
|
177
|
+
ul.options::-webkit-scrollbar-thumb {
|
|
178
|
+
background: var(--ui-border, #e2e8f0);
|
|
179
|
+
border: 0.2rem solid transparent;
|
|
180
|
+
border-radius: 999px;
|
|
181
|
+
background-clip: padding-box;
|
|
182
|
+
}
|
|
107
183
|
li {
|
|
108
184
|
padding: 0.35rem 0.6rem;
|
|
109
185
|
cursor: pointer;
|
|
@@ -116,34 +192,136 @@ let FormSelect = class FormSelect extends LitElement {
|
|
|
116
192
|
font-weight: 600;
|
|
117
193
|
color: var(--ui-primary, #4f46e5);
|
|
118
194
|
}
|
|
195
|
+
.option-content {
|
|
196
|
+
display: inline-flex;
|
|
197
|
+
min-width: 0;
|
|
198
|
+
align-items: center;
|
|
199
|
+
gap: 0.4rem;
|
|
200
|
+
}
|
|
201
|
+
li .option-content {
|
|
202
|
+
display: flex;
|
|
203
|
+
width: 100%;
|
|
204
|
+
}
|
|
205
|
+
.option-icon {
|
|
206
|
+
display: inline-flex;
|
|
207
|
+
width: var(--form-select-icon-size);
|
|
208
|
+
height: var(--form-select-icon-size);
|
|
209
|
+
flex: 0 0 var(--form-select-icon-size);
|
|
210
|
+
align-items: center;
|
|
211
|
+
justify-content: center;
|
|
212
|
+
}
|
|
213
|
+
.option-icon > svg {
|
|
214
|
+
width: 100%;
|
|
215
|
+
height: 100%;
|
|
216
|
+
}
|
|
217
|
+
.selected-icon {
|
|
218
|
+
margin-left: 0.5rem;
|
|
219
|
+
}
|
|
220
|
+
.option-label {
|
|
221
|
+
min-width: 0;
|
|
222
|
+
overflow: hidden;
|
|
223
|
+
text-overflow: ellipsis;
|
|
224
|
+
white-space: nowrap;
|
|
225
|
+
}
|
|
226
|
+
li.no-options {
|
|
227
|
+
padding: 0.35rem 0.6rem;
|
|
228
|
+
color: var(--ui-text-muted, #64748b);
|
|
229
|
+
cursor: default;
|
|
230
|
+
font-style: italic;
|
|
231
|
+
}
|
|
119
232
|
`,
|
|
120
233
|
]; }
|
|
234
|
+
#listboxPointerDown;
|
|
235
|
+
#suppressNextBlur;
|
|
236
|
+
#restoringSearchFocus;
|
|
237
|
+
#searchSelection;
|
|
238
|
+
#isComposing;
|
|
239
|
+
#compositionJustEnded;
|
|
240
|
+
#compositionEndTimer;
|
|
241
|
+
#blurTimer;
|
|
121
242
|
disconnectedCallback() {
|
|
122
243
|
super.disconnectedCallback();
|
|
123
244
|
window.removeEventListener("mousedown", this.#onWindowMousedown, true);
|
|
245
|
+
if (this.#blurTimer)
|
|
246
|
+
clearTimeout(this.#blurTimer);
|
|
247
|
+
if (this.#compositionEndTimer)
|
|
248
|
+
clearTimeout(this.#compositionEndTimer);
|
|
249
|
+
this.#listboxPointerDown = false;
|
|
250
|
+
this.#suppressNextBlur = false;
|
|
251
|
+
this.#searchSelection = null;
|
|
252
|
+
this.#isComposing = false;
|
|
253
|
+
this.#compositionJustEnded = false;
|
|
254
|
+
this.#close();
|
|
124
255
|
}
|
|
125
256
|
updated(changed) {
|
|
126
|
-
if (
|
|
257
|
+
if (changed.has("_open")) {
|
|
258
|
+
this.toggleAttribute("open", this._open);
|
|
259
|
+
if (this._open) {
|
|
260
|
+
window.addEventListener("mousedown", this.#onWindowMousedown, true);
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
window.removeEventListener("mousedown", this.#onWindowMousedown, true);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if ((changed.has("disabled") && this.disabled) || changed.has("searchable")) {
|
|
267
|
+
this.#close();
|
|
127
268
|
return;
|
|
128
|
-
this.toggleAttribute("open", this._open);
|
|
129
|
-
if (this._open) {
|
|
130
|
-
window.addEventListener("mousedown", this.#onWindowMousedown, true);
|
|
131
269
|
}
|
|
132
|
-
|
|
133
|
-
|
|
270
|
+
if (changed.has("options") && this._open) {
|
|
271
|
+
this._activeIndex = this.#initialActiveIndex();
|
|
272
|
+
}
|
|
273
|
+
else if (changed.has("value") && this._open && this._query === null) {
|
|
274
|
+
this._activeIndex = this.#initialActiveIndex();
|
|
275
|
+
}
|
|
276
|
+
if ((changed.has("_activeIndex") || changed.has("_query") || changed.has("options")) &&
|
|
277
|
+
this._open &&
|
|
278
|
+
this._activeIndex >= 0) {
|
|
279
|
+
this.renderRoot
|
|
280
|
+
.querySelector(`#form-select-option-${this._activeIndex}`)
|
|
281
|
+
?.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
134
282
|
}
|
|
135
283
|
}
|
|
136
284
|
#onWindowMousedown;
|
|
137
285
|
#toggle() {
|
|
138
286
|
if (this.disabled)
|
|
139
287
|
return;
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
288
|
+
if (this._open)
|
|
289
|
+
this.#close();
|
|
290
|
+
else
|
|
291
|
+
this.#open();
|
|
144
292
|
}
|
|
145
|
-
#
|
|
293
|
+
#open() {
|
|
294
|
+
if (this.disabled)
|
|
295
|
+
return;
|
|
296
|
+
this._query = null;
|
|
297
|
+
this._open = true;
|
|
298
|
+
this._activeIndex = this.#initialActiveIndex();
|
|
299
|
+
}
|
|
300
|
+
#close() {
|
|
146
301
|
this._open = false;
|
|
302
|
+
this._query = null;
|
|
303
|
+
this._activeIndex = -1;
|
|
304
|
+
this.#searchSelection = null;
|
|
305
|
+
}
|
|
306
|
+
#initialActiveIndex() {
|
|
307
|
+
const options = this.#visibleOptions();
|
|
308
|
+
if (options.length === 0)
|
|
309
|
+
return -1;
|
|
310
|
+
if (this._query !== null)
|
|
311
|
+
return 0;
|
|
312
|
+
const selectedIndex = options.findIndex((option) => option.value === this.value);
|
|
313
|
+
return selectedIndex >= 0 ? selectedIndex : 0;
|
|
314
|
+
}
|
|
315
|
+
#visibleOptions() {
|
|
316
|
+
if (!this.searchable || this._query === null)
|
|
317
|
+
return this.options;
|
|
318
|
+
const needle = this._query.trim().toLocaleLowerCase();
|
|
319
|
+
if (!needle)
|
|
320
|
+
return this.options;
|
|
321
|
+
return this.options.filter((option) => option.label.toLocaleLowerCase().includes(needle));
|
|
322
|
+
}
|
|
323
|
+
#select(option) {
|
|
324
|
+
this.#close();
|
|
147
325
|
if (option.value === this.value)
|
|
148
326
|
return;
|
|
149
327
|
this.value = option.value;
|
|
@@ -165,43 +343,253 @@ let FormSelect = class FormSelect extends LitElement {
|
|
|
165
343
|
}
|
|
166
344
|
else if (e.key === "Escape" && this._open) {
|
|
167
345
|
e.preventDefault();
|
|
168
|
-
this
|
|
346
|
+
this.#close();
|
|
169
347
|
}
|
|
170
348
|
}
|
|
171
349
|
#moveActive(delta) {
|
|
172
|
-
|
|
350
|
+
const options = this.#visibleOptions();
|
|
351
|
+
if (options.length === 0)
|
|
173
352
|
return;
|
|
174
|
-
const n =
|
|
353
|
+
const n = options.length;
|
|
175
354
|
this._activeIndex = (this._activeIndex + delta + n) % n;
|
|
176
355
|
}
|
|
177
356
|
#confirmActive() {
|
|
178
|
-
const
|
|
357
|
+
const options = this.#visibleOptions();
|
|
358
|
+
const option = options[this._activeIndex] ?? options[0];
|
|
179
359
|
if (option)
|
|
180
360
|
this.#select(option);
|
|
181
361
|
}
|
|
362
|
+
#onSearchFocus(e) {
|
|
363
|
+
if (this.disabled)
|
|
364
|
+
return;
|
|
365
|
+
if (this.#blurTimer) {
|
|
366
|
+
clearTimeout(this.#blurTimer);
|
|
367
|
+
this.#blurTimer = null;
|
|
368
|
+
}
|
|
369
|
+
if (!this._open)
|
|
370
|
+
this.#open();
|
|
371
|
+
if (!this.#restoringSearchFocus)
|
|
372
|
+
e.currentTarget.select();
|
|
373
|
+
}
|
|
374
|
+
#onSearchClick(e) {
|
|
375
|
+
if (!this._open)
|
|
376
|
+
this.#open();
|
|
377
|
+
if (this._query === null)
|
|
378
|
+
e.currentTarget.select();
|
|
379
|
+
}
|
|
380
|
+
#onSearchMousedown(e) {
|
|
381
|
+
if (e.button !== 0)
|
|
382
|
+
e.preventDefault();
|
|
383
|
+
}
|
|
384
|
+
#onSearchInput(e) {
|
|
385
|
+
this._query = e.currentTarget.value;
|
|
386
|
+
this._open = true;
|
|
387
|
+
const options = this.#visibleOptions();
|
|
388
|
+
this._activeIndex = options.length > 0 ? 0 : -1;
|
|
389
|
+
}
|
|
390
|
+
#onSearchKeydown(e) {
|
|
391
|
+
if (e.isComposing || e.keyCode === 229 || this.#isComposing || this.#compositionJustEnded) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (e.key === "ArrowDown" || e.key === "ArrowUp") {
|
|
395
|
+
e.preventDefault();
|
|
396
|
+
if (!this._open) {
|
|
397
|
+
this.#open();
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
this.#moveActive(e.key === "ArrowDown" ? 1 : -1);
|
|
401
|
+
}
|
|
402
|
+
else if (e.key === "Enter" && this._open) {
|
|
403
|
+
e.preventDefault();
|
|
404
|
+
this.#confirmActive();
|
|
405
|
+
}
|
|
406
|
+
else if (e.key === "Escape" && this._open) {
|
|
407
|
+
e.preventDefault();
|
|
408
|
+
this.#close();
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
#onSearchBlur() {
|
|
412
|
+
if (this.#blurTimer)
|
|
413
|
+
clearTimeout(this.#blurTimer);
|
|
414
|
+
this.#blurTimer = setTimeout(() => {
|
|
415
|
+
this.#blurTimer = null;
|
|
416
|
+
if (!this._open)
|
|
417
|
+
return;
|
|
418
|
+
if (!this.#listboxPointerDown && !this.#suppressNextBlur)
|
|
419
|
+
this.#close();
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
#focusSearchInput(e) {
|
|
423
|
+
if (e.button !== 0 ||
|
|
424
|
+
this.disabled ||
|
|
425
|
+
e.composedPath()[0] instanceof HTMLInputElement) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
e.preventDefault();
|
|
429
|
+
const input = this.renderRoot.querySelector("input.search-input");
|
|
430
|
+
if (!this._open)
|
|
431
|
+
this.#open();
|
|
432
|
+
input?.focus();
|
|
433
|
+
input?.select();
|
|
434
|
+
}
|
|
435
|
+
#onCompositionStart() {
|
|
436
|
+
if (this.#compositionEndTimer)
|
|
437
|
+
clearTimeout(this.#compositionEndTimer);
|
|
438
|
+
this.#compositionEndTimer = null;
|
|
439
|
+
this.#compositionJustEnded = false;
|
|
440
|
+
this.#isComposing = true;
|
|
441
|
+
}
|
|
442
|
+
#onCompositionEnd() {
|
|
443
|
+
this.#isComposing = false;
|
|
444
|
+
this.#compositionJustEnded = true;
|
|
445
|
+
if (this.#compositionEndTimer)
|
|
446
|
+
clearTimeout(this.#compositionEndTimer);
|
|
447
|
+
this.#compositionEndTimer = setTimeout(() => {
|
|
448
|
+
this.#compositionJustEnded = false;
|
|
449
|
+
this.#compositionEndTimer = null;
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
#onListboxMousedown(e) {
|
|
453
|
+
if (e.button !== 0)
|
|
454
|
+
return;
|
|
455
|
+
this.#listboxPointerDown = true;
|
|
456
|
+
this.#suppressNextBlur = true;
|
|
457
|
+
const input = this.renderRoot.querySelector("input.search-input");
|
|
458
|
+
this.#searchSelection = input
|
|
459
|
+
? [input.selectionStart, input.selectionEnd]
|
|
460
|
+
: null;
|
|
461
|
+
window.addEventListener("mouseup", () => {
|
|
462
|
+
this.#listboxPointerDown = false;
|
|
463
|
+
if (this._open) {
|
|
464
|
+
const searchInput = this.renderRoot.querySelector("input.search-input");
|
|
465
|
+
if (searchInput) {
|
|
466
|
+
this.#restoringSearchFocus = true;
|
|
467
|
+
searchInput.focus({ preventScroll: true });
|
|
468
|
+
this.#restoringSearchFocus = false;
|
|
469
|
+
const [start, end] = this.#searchSelection ?? [null, null];
|
|
470
|
+
if (start !== null && end !== null)
|
|
471
|
+
searchInput.setSelectionRange(start, end);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
this.#searchSelection = null;
|
|
475
|
+
setTimeout(() => {
|
|
476
|
+
this.#suppressNextBlur = false;
|
|
477
|
+
});
|
|
478
|
+
}, { capture: true, once: true });
|
|
479
|
+
}
|
|
480
|
+
#onOptionMousedown(e, option) {
|
|
481
|
+
if (e.button !== 0)
|
|
482
|
+
return;
|
|
483
|
+
const optionElement = e.currentTarget;
|
|
484
|
+
const listbox = optionElement.parentElement;
|
|
485
|
+
if (listbox) {
|
|
486
|
+
const scrollbarWidth = listbox.offsetWidth - listbox.clientWidth;
|
|
487
|
+
const bounds = listbox.getBoundingClientRect();
|
|
488
|
+
const isRtl = getComputedStyle(listbox).direction === "rtl";
|
|
489
|
+
const inScrollbarGutter = isRtl
|
|
490
|
+
? e.clientX <= bounds.left + scrollbarWidth
|
|
491
|
+
: e.clientX >= bounds.right - scrollbarWidth;
|
|
492
|
+
if (scrollbarWidth > 0 && inScrollbarGutter)
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
e.preventDefault();
|
|
496
|
+
this.#select(option);
|
|
497
|
+
}
|
|
498
|
+
#optionIconSize(option) {
|
|
499
|
+
const size = option.iconSize ?? 16;
|
|
500
|
+
return Number.isFinite(size) && size > 0 ? size : 16;
|
|
501
|
+
}
|
|
502
|
+
#renderOptionIcon(option, className = "option-icon") {
|
|
503
|
+
if (!option.icon)
|
|
504
|
+
return nothing;
|
|
505
|
+
return html `
|
|
506
|
+
<span
|
|
507
|
+
class=${className}
|
|
508
|
+
style=${`--form-select-icon-size: ${this.#optionIconSize(option)}px`}
|
|
509
|
+
aria-hidden="true"
|
|
510
|
+
>
|
|
511
|
+
${option.icon}
|
|
512
|
+
</span>
|
|
513
|
+
`;
|
|
514
|
+
}
|
|
515
|
+
#renderOptionContent(option) {
|
|
516
|
+
return html `
|
|
517
|
+
<span class="option-content">
|
|
518
|
+
${this.#renderOptionIcon(option)}
|
|
519
|
+
<span class="option-label">${option.label}</span>
|
|
520
|
+
</span>
|
|
521
|
+
`;
|
|
522
|
+
}
|
|
182
523
|
renderListbox() {
|
|
183
524
|
if (!this._open)
|
|
184
525
|
return nothing;
|
|
526
|
+
const options = this.#visibleOptions();
|
|
185
527
|
return html `
|
|
186
|
-
<ul
|
|
187
|
-
|
|
528
|
+
<ul
|
|
529
|
+
id="form-select-listbox"
|
|
530
|
+
class="options"
|
|
531
|
+
role="listbox"
|
|
532
|
+
@mousedown=${(e) => this.#onListboxMousedown(e)}
|
|
533
|
+
>
|
|
534
|
+
${options.map((o, i) => html `
|
|
188
535
|
<li
|
|
536
|
+
id=${`form-select-option-${i}`}
|
|
189
537
|
role="option"
|
|
190
538
|
aria-selected=${o.value === this.value}
|
|
191
539
|
class=${i === this._activeIndex ? "active" : ""}
|
|
192
|
-
@mousedown=${(e) =>
|
|
193
|
-
e.preventDefault();
|
|
194
|
-
this.#select(o);
|
|
195
|
-
}}
|
|
540
|
+
@mousedown=${(e) => this.#onOptionMousedown(e, o)}
|
|
196
541
|
>
|
|
197
|
-
${o
|
|
542
|
+
${this.#renderOptionContent(o)}
|
|
198
543
|
</li>
|
|
199
544
|
`)}
|
|
545
|
+
${options.length === 0
|
|
546
|
+
? html `<li class="no-options" role="presentation"><span role="status">No options found</span></li>`
|
|
547
|
+
: nothing}
|
|
200
548
|
</ul>
|
|
201
549
|
`;
|
|
202
550
|
}
|
|
203
|
-
|
|
204
|
-
const
|
|
551
|
+
renderSearchTrigger(current) {
|
|
552
|
+
const displayValue = this._query ?? current?.label ?? this.value;
|
|
553
|
+
const showSelectedIcon = this._query === null && Boolean(current?.icon);
|
|
554
|
+
const activeDescendant = this._open && this._activeIndex >= 0
|
|
555
|
+
? `form-select-option-${this._activeIndex}`
|
|
556
|
+
: nothing;
|
|
557
|
+
return html `
|
|
558
|
+
<div
|
|
559
|
+
class="search-trigger ${this.disabled ? "disabled" : ""} ${showSelectedIcon
|
|
560
|
+
? "has-icon"
|
|
561
|
+
: ""}"
|
|
562
|
+
@mousedown=${(e) => this.#focusSearchInput(e)}
|
|
563
|
+
>
|
|
564
|
+
${showSelectedIcon && current
|
|
565
|
+
? this.#renderOptionIcon(current, "option-icon selected-icon")
|
|
566
|
+
: nothing}
|
|
567
|
+
<input
|
|
568
|
+
type="text"
|
|
569
|
+
class="search-input"
|
|
570
|
+
role="combobox"
|
|
571
|
+
aria-autocomplete="list"
|
|
572
|
+
aria-expanded=${this._open}
|
|
573
|
+
aria-controls="form-select-listbox"
|
|
574
|
+
aria-activedescendant=${activeDescendant}
|
|
575
|
+
aria-label=${this.label || "Select an option"}
|
|
576
|
+
autocomplete="off"
|
|
577
|
+
?disabled=${this.disabled}
|
|
578
|
+
.value=${displayValue}
|
|
579
|
+
@mousedown=${(e) => this.#onSearchMousedown(e)}
|
|
580
|
+
@focus=${(e) => this.#onSearchFocus(e)}
|
|
581
|
+
@click=${(e) => this.#onSearchClick(e)}
|
|
582
|
+
@input=${(e) => this.#onSearchInput(e)}
|
|
583
|
+
@compositionstart=${() => this.#onCompositionStart()}
|
|
584
|
+
@compositionend=${() => this.#onCompositionEnd()}
|
|
585
|
+
@keydown=${(e) => this.#onSearchKeydown(e)}
|
|
586
|
+
@blur=${() => this.#onSearchBlur()}
|
|
587
|
+
/>
|
|
588
|
+
<span class="chevron">${iconChevronRight(14)}</span>
|
|
589
|
+
</div>
|
|
590
|
+
`;
|
|
591
|
+
}
|
|
592
|
+
renderButtonTrigger(current) {
|
|
205
593
|
return html `
|
|
206
594
|
<button
|
|
207
595
|
type="button"
|
|
@@ -213,9 +601,19 @@ let FormSelect = class FormSelect extends LitElement {
|
|
|
213
601
|
@click=${() => this.#toggle()}
|
|
214
602
|
@keydown=${(e) => this.#onTriggerKeydown(e)}
|
|
215
603
|
>
|
|
216
|
-
|
|
604
|
+
${current
|
|
605
|
+
? this.#renderOptionContent(current)
|
|
606
|
+
: html `<span class="option-label">${this.value}</span>`}
|
|
217
607
|
<span class="chevron">${iconChevronRight(14)}</span>
|
|
218
608
|
</button>
|
|
609
|
+
`;
|
|
610
|
+
}
|
|
611
|
+
render() {
|
|
612
|
+
const current = this.options.find((o) => o.value === this.value);
|
|
613
|
+
return html `
|
|
614
|
+
${this.searchable
|
|
615
|
+
? this.renderSearchTrigger(current)
|
|
616
|
+
: this.renderButtonTrigger(current)}
|
|
219
617
|
${this.renderListbox()}
|
|
220
618
|
`;
|
|
221
619
|
}
|
|
@@ -232,12 +630,18 @@ __decorate([
|
|
|
232
630
|
__decorate([
|
|
233
631
|
property({ type: Boolean })
|
|
234
632
|
], FormSelect.prototype, "disabled", void 0);
|
|
633
|
+
__decorate([
|
|
634
|
+
property({ type: Boolean, reflect: true })
|
|
635
|
+
], FormSelect.prototype, "searchable", void 0);
|
|
235
636
|
__decorate([
|
|
236
637
|
state()
|
|
237
638
|
], FormSelect.prototype, "_open", void 0);
|
|
238
639
|
__decorate([
|
|
239
640
|
state()
|
|
240
641
|
], FormSelect.prototype, "_activeIndex", void 0);
|
|
642
|
+
__decorate([
|
|
643
|
+
state()
|
|
644
|
+
], FormSelect.prototype, "_query", void 0);
|
|
241
645
|
FormSelect = __decorate([
|
|
242
646
|
customElement("form-select")
|
|
243
647
|
], FormSelect);
|