@f-ewald/components 0.6.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.
Files changed (75) hide show
  1. package/README.md +101 -44
  2. package/custom-elements.json +2570 -877
  3. package/dist/calendar-entry.d.ts +33 -0
  4. package/dist/calendar-entry.d.ts.map +1 -0
  5. package/dist/calendar-entry.js +64 -0
  6. package/dist/calendar-entry.js.map +1 -0
  7. package/dist/calendar-month.d.ts +67 -0
  8. package/dist/calendar-month.d.ts.map +1 -0
  9. package/dist/calendar-month.js +455 -0
  10. package/dist/calendar-month.js.map +1 -0
  11. package/dist/calendar-year.d.ts +43 -0
  12. package/dist/calendar-year.d.ts.map +1 -0
  13. package/dist/calendar-year.js +136 -0
  14. package/dist/calendar-year.js.map +1 -0
  15. package/dist/dropdown-button.d.ts +37 -0
  16. package/dist/dropdown-button.d.ts.map +1 -0
  17. package/dist/dropdown-button.js +219 -0
  18. package/dist/dropdown-button.js.map +1 -0
  19. package/dist/form-select.d.ts +27 -2
  20. package/dist/form-select.d.ts.map +1 -1
  21. package/dist/form-select.js +440 -27
  22. package/dist/form-select.js.map +1 -1
  23. package/dist/frame-box.d.ts +22 -0
  24. package/dist/frame-box.d.ts.map +1 -0
  25. package/dist/frame-box.js +68 -0
  26. package/dist/frame-box.js.map +1 -0
  27. package/dist/icon-button.d.ts +27 -0
  28. package/dist/icon-button.d.ts.map +1 -0
  29. package/dist/icon-button.js +83 -0
  30. package/dist/icon-button.js.map +1 -0
  31. package/dist/icons.d.ts +1 -0
  32. package/dist/icons.d.ts.map +1 -1
  33. package/dist/icons.js +1 -0
  34. package/dist/icons.js.map +1 -1
  35. package/dist/index.d.ts +6 -1
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +6 -1
  38. package/dist/index.js.map +1 -1
  39. package/dist/map-circle.d.ts +3 -0
  40. package/dist/map-circle.d.ts.map +1 -1
  41. package/dist/map-circle.js +3 -0
  42. package/dist/map-circle.js.map +1 -1
  43. package/dist/mcp-server.d.ts +3 -0
  44. package/dist/mcp-server.d.ts.map +1 -0
  45. package/dist/mcp-server.js +82 -0
  46. package/dist/mcp-server.js.map +1 -0
  47. package/dist/popover-panel.d.ts +3 -0
  48. package/dist/popover-panel.d.ts.map +1 -1
  49. package/dist/popover-panel.js +11 -0
  50. package/dist/popover-panel.js.map +1 -1
  51. package/dist/tokens.css +4 -0
  52. package/dist/tokens.d.ts.map +1 -1
  53. package/dist/tokens.js +3 -0
  54. package/dist/tokens.js.map +1 -1
  55. package/dist/utils/calendar.d.ts +77 -0
  56. package/dist/utils/calendar.d.ts.map +1 -0
  57. package/dist/utils/calendar.js +135 -0
  58. package/dist/utils/calendar.js.map +1 -0
  59. package/docs/calendar-entry.md +53 -0
  60. package/docs/calendar-month.md +77 -0
  61. package/docs/calendar-year.md +53 -0
  62. package/docs/dropdown-button.md +61 -0
  63. package/docs/form-select.md +21 -3
  64. package/docs/frame-box.md +43 -0
  65. package/docs/icon-button.md +52 -0
  66. package/docs/map-circle.md +4 -0
  67. package/docs/mcp-evaluation.md +18 -11
  68. package/docs/popover-panel.md +5 -1
  69. package/llms.txt +194 -28
  70. package/package.json +19 -4
  71. package/dist/map-point.d.ts +0 -31
  72. package/dist/map-point.d.ts.map +0 -1
  73. package/dist/map-point.js +0 -83
  74. package/dist/map-point.js.map +0 -1
  75. package/docs/map-point.md +0 -44
@@ -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";
@@ -15,6 +15,19 @@ import { tokens } from "./tokens.js";
15
15
  * `change` event carrying `{ value }` are wanted (e.g. a task's status
16
16
  * picker).
17
17
  *
18
+ * The trigger fills its host's width (`justify-content: space-between`
19
+ * pushes the chevron to the far edge), but the host itself stays
20
+ * `display: inline-block` — so usages that never size the host (a filter
21
+ * bar, a status picker) keep shrink-to-fit auto-width unchanged. To make an
22
+ * instance full-width, size the host itself: `form-select { width: 100%; }`.
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
+ *
18
31
  * @element form-select
19
32
  * @fires change - Fired with `{ value: string }` when a different option is picked.
20
33
  */
@@ -29,11 +42,25 @@ let FormSelect = class FormSelect extends LitElement {
29
42
  this.label = "";
30
43
  /** Disables the trigger, preventing the popover from opening. */
31
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;
32
50
  this._open = false;
33
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;
34
61
  this.#onWindowMousedown = (e) => {
35
62
  if (!e.composedPath().includes(this))
36
- this._open = false;
63
+ this.#close();
37
64
  };
38
65
  }
39
66
  static { this.styles = [
@@ -48,7 +75,10 @@ let FormSelect = class FormSelect extends LitElement {
48
75
  button.trigger {
49
76
  display: flex;
50
77
  align-items: center;
78
+ justify-content: space-between;
51
79
  gap: 0.35rem;
80
+ width: 100%;
81
+ box-sizing: border-box;
52
82
  font: inherit;
53
83
  color: var(--ui-text, #0f172a);
54
84
  background: var(--ui-surface, #fff);
@@ -69,12 +99,56 @@ let FormSelect = class FormSelect extends LitElement {
69
99
  border-color: var(--ui-primary, #4f46e5);
70
100
  box-shadow: var(--ui-focus-ring, 0 0 0 3px rgb(79 70 229 / 0.35));
71
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
+ }
72
140
  .chevron {
73
141
  display: flex;
142
+ flex: 0 0 auto;
143
+ margin-right: 0.5rem;
74
144
  color: var(--ui-text-muted, #64748b);
145
+ pointer-events: none;
75
146
  transform: rotate(90deg);
76
147
  transition: transform 0.1s ease;
77
148
  }
149
+ button.trigger .chevron {
150
+ margin-right: 0;
151
+ }
78
152
  :host([open]) .chevron {
79
153
  transform: rotate(-90deg);
80
154
  }
@@ -86,15 +160,26 @@ let FormSelect = class FormSelect extends LitElement {
86
160
  min-width: 100%;
87
161
  max-height: 40vh;
88
162
  overflow-y: auto;
163
+ scrollbar-gutter: stable;
89
164
  margin: 2px 0 0;
90
165
  padding: 4px 0;
91
166
  list-style: none;
92
167
  white-space: nowrap;
168
+ color: var(--ui-text, #0f172a);
93
169
  background: var(--ui-surface, #fff);
94
170
  border: 1px solid var(--ui-border, #e2e8f0);
95
171
  border-radius: var(--ui-radius-sm, 0.25rem);
96
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));
97
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
+ }
98
183
  li {
99
184
  padding: 0.35rem 0.6rem;
100
185
  cursor: pointer;
@@ -107,34 +192,136 @@ let FormSelect = class FormSelect extends LitElement {
107
192
  font-weight: 600;
108
193
  color: var(--ui-primary, #4f46e5);
109
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
+ }
110
232
  `,
111
233
  ]; }
234
+ #listboxPointerDown;
235
+ #suppressNextBlur;
236
+ #restoringSearchFocus;
237
+ #searchSelection;
238
+ #isComposing;
239
+ #compositionJustEnded;
240
+ #compositionEndTimer;
241
+ #blurTimer;
112
242
  disconnectedCallback() {
113
243
  super.disconnectedCallback();
114
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();
115
255
  }
116
256
  updated(changed) {
117
- if (!changed.has("_open"))
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();
118
268
  return;
119
- this.toggleAttribute("open", this._open);
120
- if (this._open) {
121
- window.addEventListener("mousedown", this.#onWindowMousedown, true);
122
269
  }
123
- else {
124
- window.removeEventListener("mousedown", this.#onWindowMousedown, true);
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" });
125
282
  }
126
283
  }
127
284
  #onWindowMousedown;
128
285
  #toggle() {
129
286
  if (this.disabled)
130
287
  return;
131
- this._open = !this._open;
132
- if (this._open) {
133
- this._activeIndex = Math.max(0, this.options.findIndex((o) => o.value === this.value));
134
- }
288
+ if (this._open)
289
+ this.#close();
290
+ else
291
+ this.#open();
135
292
  }
136
- #select(option) {
293
+ #open() {
294
+ if (this.disabled)
295
+ return;
296
+ this._query = null;
297
+ this._open = true;
298
+ this._activeIndex = this.#initialActiveIndex();
299
+ }
300
+ #close() {
137
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();
138
325
  if (option.value === this.value)
139
326
  return;
140
327
  this.value = option.value;
@@ -156,43 +343,253 @@ let FormSelect = class FormSelect extends LitElement {
156
343
  }
157
344
  else if (e.key === "Escape" && this._open) {
158
345
  e.preventDefault();
159
- this._open = false;
346
+ this.#close();
160
347
  }
161
348
  }
162
349
  #moveActive(delta) {
163
- if (this.options.length === 0)
350
+ const options = this.#visibleOptions();
351
+ if (options.length === 0)
164
352
  return;
165
- const n = this.options.length;
353
+ const n = options.length;
166
354
  this._activeIndex = (this._activeIndex + delta + n) % n;
167
355
  }
168
356
  #confirmActive() {
169
- const option = this.options[this._activeIndex];
357
+ const options = this.#visibleOptions();
358
+ const option = options[this._activeIndex] ?? options[0];
170
359
  if (option)
171
360
  this.#select(option);
172
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
+ }
173
523
  renderListbox() {
174
524
  if (!this._open)
175
525
  return nothing;
526
+ const options = this.#visibleOptions();
176
527
  return html `
177
- <ul class="options" role="listbox">
178
- ${this.options.map((o, i) => html `
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 `
179
535
  <li
536
+ id=${`form-select-option-${i}`}
180
537
  role="option"
181
538
  aria-selected=${o.value === this.value}
182
539
  class=${i === this._activeIndex ? "active" : ""}
183
- @mousedown=${(e) => {
184
- e.preventDefault();
185
- this.#select(o);
186
- }}
540
+ @mousedown=${(e) => this.#onOptionMousedown(e, o)}
187
541
  >
188
- ${o.label}
542
+ ${this.#renderOptionContent(o)}
189
543
  </li>
190
544
  `)}
545
+ ${options.length === 0
546
+ ? html `<li class="no-options" role="presentation"><span role="status">No options found</span></li>`
547
+ : nothing}
191
548
  </ul>
192
549
  `;
193
550
  }
194
- render() {
195
- const current = this.options.find((o) => o.value === this.value);
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) {
196
593
  return html `
197
594
  <button
198
595
  type="button"
@@ -204,9 +601,19 @@ let FormSelect = class FormSelect extends LitElement {
204
601
  @click=${() => this.#toggle()}
205
602
  @keydown=${(e) => this.#onTriggerKeydown(e)}
206
603
  >
207
- <span>${current?.label ?? this.value}</span>
604
+ ${current
605
+ ? this.#renderOptionContent(current)
606
+ : html `<span class="option-label">${this.value}</span>`}
208
607
  <span class="chevron">${iconChevronRight(14)}</span>
209
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)}
210
617
  ${this.renderListbox()}
211
618
  `;
212
619
  }
@@ -223,12 +630,18 @@ __decorate([
223
630
  __decorate([
224
631
  property({ type: Boolean })
225
632
  ], FormSelect.prototype, "disabled", void 0);
633
+ __decorate([
634
+ property({ type: Boolean, reflect: true })
635
+ ], FormSelect.prototype, "searchable", void 0);
226
636
  __decorate([
227
637
  state()
228
638
  ], FormSelect.prototype, "_open", void 0);
229
639
  __decorate([
230
640
  state()
231
641
  ], FormSelect.prototype, "_activeIndex", void 0);
642
+ __decorate([
643
+ state()
644
+ ], FormSelect.prototype, "_query", void 0);
232
645
  FormSelect = __decorate([
233
646
  customElement("form-select")
234
647
  ], FormSelect);