@rcarls/rc-combobox 0.1.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 ADDED
@@ -0,0 +1,72 @@
1
+ # `@rcarls/rc-combobox`
2
+
3
+ An editable ARIA combobox built with Lit 3. It extends `rc-select` with a text
4
+ input, live filtering, keyboard navigation, and optional allow-create behavior
5
+ while still preserving a native slotted `<select>` for form submission.
6
+
7
+ ## Installation
8
+
9
+ PowerShell:
10
+
11
+ ```powershell
12
+ yarn.cmd add @rcarls/rc-combobox
13
+ ```
14
+
15
+ Bash/zsh:
16
+
17
+ ```bash
18
+ yarn add @rcarls/rc-combobox
19
+ ```
20
+
21
+ ## Import
22
+
23
+ ```ts
24
+ import '@rcarls/rc-combobox/define';
25
+ ```
26
+
27
+ ## Basic Usage
28
+
29
+ ```html
30
+ <label>
31
+ Fruit
32
+ <rc-combobox placeholder="Search fruit">
33
+ <select slot="select" name="fruit">
34
+ <option value="apple">Apple</option>
35
+ <option value="banana">Banana</option>
36
+ <option value="cherry">Cherry</option>
37
+ </select>
38
+ </rc-combobox>
39
+ </label>
40
+ ```
41
+
42
+ ## Allow Create
43
+
44
+ ```html
45
+ <rc-combobox allowcreate placeholder="Add tag">
46
+ <select slot="select" name="tags" multiple></select>
47
+ </rc-combobox>
48
+ ```
49
+
50
+ Cancel `rc-combobox-create` to validate or replace the default insertion.
51
+
52
+ ## API
53
+
54
+ | Property | Type | Description |
55
+ | --- | --- | --- |
56
+ | `allowCreate` | `boolean` | Shows a create option for unmatched input. |
57
+ | `filterStrategy` | `'prefix' \| 'contains' \| function` | Controls option filtering. |
58
+ | `multiple` | `boolean` | Inherited from `rc-select`; supports chip rendering. |
59
+ | `placeholder` | `string` | Input placeholder when no value is selected. |
60
+
61
+ ## Events
62
+
63
+ | Event | Detail | Description |
64
+ | --- | --- | --- |
65
+ | `rc-combobox-create` | `{ text: string }` | Cancelable event fired before a new option is inserted. |
66
+ | `rc-select-change` | `{ value: string \| string[] }` | Inherited selection-change event. |
67
+
68
+ ## Accessibility
69
+
70
+ - Input uses `role="combobox"` with `aria-autocomplete="list"`.
71
+ - Popup navigation uses `aria-activedescendant`.
72
+ - The slotted native `<select>` remains the form value source.
@@ -0,0 +1,388 @@
1
+ {
2
+ "schemaVersion": "1.0.0",
3
+ "readme": "",
4
+ "modules": [
5
+ {
6
+ "kind": "javascript-module",
7
+ "path": "src/define.ts",
8
+ "declarations": [],
9
+ "exports": [
10
+ {
11
+ "kind": "custom-element-definition",
12
+ "name": "rc-combobox",
13
+ "declaration": {
14
+ "name": "RCCombobox",
15
+ "module": "/src/index.js"
16
+ }
17
+ },
18
+ {
19
+ "kind": "js",
20
+ "name": "*",
21
+ "declaration": {
22
+ "name": "*",
23
+ "module": "src/index.js"
24
+ }
25
+ }
26
+ ]
27
+ },
28
+ {
29
+ "kind": "javascript-module",
30
+ "path": "src/index.ts",
31
+ "declarations": [],
32
+ "exports": [
33
+ {
34
+ "kind": "js",
35
+ "name": "*",
36
+ "declaration": {
37
+ "name": "*",
38
+ "module": "src/rc-combobox.js"
39
+ }
40
+ }
41
+ ]
42
+ },
43
+ {
44
+ "kind": "javascript-module",
45
+ "path": "src/rc-combobox.ts",
46
+ "declarations": [
47
+ {
48
+ "kind": "class",
49
+ "description": "An editable combobox with autocomplete filtering and optional allow-create.\n\nExtends `rc-select` by replacing the trigger `<div>` with a text `<input>`\nand adding: live filtering of the listbox, keyboard navigation from input,\nand an optional \"Create '{text}'\" option for new entries.",
50
+ "name": "RCCombobox",
51
+ "cssProperties": [
52
+ {
53
+ "description": "Maximum popup height.",
54
+ "name": "--rc-combobox-max-height",
55
+ "default": "20em"
56
+ },
57
+ {
58
+ "description": "Anchor block size.",
59
+ "name": "--rc-combobox-control-block-size",
60
+ "default": "var(--rc-control-block-size)"
61
+ },
62
+ {
63
+ "description": "Anchor block-axis padding.",
64
+ "name": "--rc-combobox-padding-block",
65
+ "default": "calc(var(--rc-control-padding-block) / 2)"
66
+ },
67
+ {
68
+ "description": "Anchor inline-axis padding.",
69
+ "name": "--rc-combobox-padding-inline",
70
+ "default": "calc(var(--rc-control-padding-inline) / 2)"
71
+ },
72
+ {
73
+ "description": "Gap between chips, input, and toggle.",
74
+ "name": "--rc-combobox-gap",
75
+ "default": "var(--rc-control-gap)"
76
+ },
77
+ {
78
+ "description": "Anchor border radius.",
79
+ "name": "--rc-combobox-radius",
80
+ "default": "var(--rc-control-radius)"
81
+ },
82
+ {
83
+ "description": "Anchor border.",
84
+ "name": "--rc-combobox-border",
85
+ "default": "var(--rc-border)"
86
+ },
87
+ {
88
+ "description": "Popup listbox border radius.",
89
+ "name": "--rc-combobox-listbox-radius",
90
+ "default": "var(--rc-control-radius)"
91
+ },
92
+ {
93
+ "description": "Popup listbox block padding.",
94
+ "name": "--rc-combobox-listbox-padding-block",
95
+ "default": "var(--rc-control-padding-block)"
96
+ },
97
+ {
98
+ "description": "Multi-select chip border radius.",
99
+ "name": "--rc-combobox-chip-radius",
100
+ "default": "var(--rc-radius-md)"
101
+ },
102
+ {
103
+ "description": "Multi-select chip block-axis padding.",
104
+ "name": "--rc-combobox-chip-padding-block",
105
+ "default": "0.1em"
106
+ },
107
+ {
108
+ "description": "Multi-select chip inline-axis padding.",
109
+ "name": "--rc-combobox-chip-padding-inline",
110
+ "default": "0.3em"
111
+ }
112
+ ],
113
+ "cssParts": [
114
+ {
115
+ "description": "Outer container (includes chips + input + toggle).",
116
+ "name": "anchor"
117
+ },
118
+ {
119
+ "description": "Individual chip (multiple mode).",
120
+ "name": "chip"
121
+ },
122
+ {
123
+ "description": "Text label inside a chip.",
124
+ "name": "chip-label"
125
+ },
126
+ {
127
+ "description": "Remove button inside a chip.",
128
+ "name": "chip-remove"
129
+ },
130
+ {
131
+ "description": "The text input element.",
132
+ "name": "input"
133
+ },
134
+ {
135
+ "description": "The chevron toggle button.",
136
+ "name": "toggle"
137
+ }
138
+ ],
139
+ "slots": [
140
+ {
141
+ "description": "Required. A native `<select>` element for form submission.",
142
+ "name": "select"
143
+ },
144
+ {
145
+ "description": "Optional. Replaces the default chevron icon.",
146
+ "name": "toggle-icon"
147
+ }
148
+ ],
149
+ "members": [
150
+ {
151
+ "kind": "field",
152
+ "name": "allowCreate",
153
+ "type": {
154
+ "text": "boolean"
155
+ },
156
+ "default": "false",
157
+ "description": "When set, shows a \"Create '{text}'\" option for text that has no exact match.",
158
+ "attribute": "allowcreate"
159
+ },
160
+ {
161
+ "kind": "field",
162
+ "name": "filterStrategy",
163
+ "type": {
164
+ "text": "FilterStrategy"
165
+ },
166
+ "default": "'contains'",
167
+ "description": "How option labels are matched against typed input. Forwarded to the internal `rc-listbox`.\nDefaults to `'contains'` (substring). Set to `'prefix'` for starts-with matching,\nor pass a custom `(label, query) => boolean` predicate.\nFunction values are JS-only; string values may be set via the `filter-strategy` attribute.",
168
+ "attribute": "filter-strategy"
169
+ },
170
+ {
171
+ "kind": "field",
172
+ "name": "_$trigger",
173
+ "type": {
174
+ "text": "HTMLInputElement"
175
+ },
176
+ "privacy": "protected"
177
+ },
178
+ {
179
+ "kind": "field",
180
+ "name": "_filterText",
181
+ "type": {
182
+ "text": "string"
183
+ },
184
+ "privacy": "private",
185
+ "default": "''"
186
+ },
187
+ {
188
+ "kind": "field",
189
+ "name": "_closingPopup",
190
+ "type": {
191
+ "text": "boolean"
192
+ },
193
+ "privacy": "private",
194
+ "default": "false"
195
+ },
196
+ {
197
+ "kind": "method",
198
+ "name": "openPopup"
199
+ },
200
+ {
201
+ "kind": "method",
202
+ "name": "closePopup",
203
+ "parameters": [
204
+ {
205
+ "name": "_returnFocus",
206
+ "default": "true"
207
+ }
208
+ ]
209
+ },
210
+ {
211
+ "kind": "method",
212
+ "name": "_handleInput",
213
+ "privacy": "private",
214
+ "parameters": [
215
+ {
216
+ "name": "e",
217
+ "type": {
218
+ "text": "InputEvent"
219
+ }
220
+ }
221
+ ]
222
+ },
223
+ {
224
+ "kind": "method",
225
+ "name": "_handleInputFocus",
226
+ "privacy": "private"
227
+ },
228
+ {
229
+ "kind": "method",
230
+ "name": "_handleToggleClick",
231
+ "privacy": "private",
232
+ "parameters": [
233
+ {
234
+ "name": "e",
235
+ "type": {
236
+ "text": "MouseEvent"
237
+ }
238
+ }
239
+ ]
240
+ },
241
+ {
242
+ "kind": "method",
243
+ "name": "_handleAnchorClick",
244
+ "privacy": "private",
245
+ "parameters": [
246
+ {
247
+ "name": "e",
248
+ "type": {
249
+ "text": "MouseEvent"
250
+ }
251
+ }
252
+ ]
253
+ },
254
+ {
255
+ "kind": "method",
256
+ "name": "_updateCreateOption",
257
+ "privacy": "private"
258
+ },
259
+ {
260
+ "kind": "method",
261
+ "name": "_handleListboxChange",
262
+ "privacy": "protected",
263
+ "parameters": [
264
+ {
265
+ "name": "e",
266
+ "type": {
267
+ "text": "CustomEvent"
268
+ }
269
+ }
270
+ ]
271
+ },
272
+ {
273
+ "kind": "method",
274
+ "name": "_activateCreate",
275
+ "privacy": "private",
276
+ "parameters": [
277
+ {
278
+ "name": "text",
279
+ "type": {
280
+ "text": "string"
281
+ }
282
+ }
283
+ ]
284
+ },
285
+ {
286
+ "kind": "field",
287
+ "name": "value",
288
+ "type": {
289
+ "text": "string | string[]"
290
+ }
291
+ },
292
+ {
293
+ "kind": "method",
294
+ "name": "_syncInputToSelection",
295
+ "privacy": "private",
296
+ "return": {
297
+ "type": {
298
+ "text": "void"
299
+ }
300
+ }
301
+ },
302
+ {
303
+ "kind": "method",
304
+ "name": "_handleInputKeyDown",
305
+ "privacy": "private",
306
+ "parameters": [
307
+ {
308
+ "name": "e",
309
+ "type": {
310
+ "text": "KeyboardEvent"
311
+ }
312
+ }
313
+ ]
314
+ },
315
+ {
316
+ "kind": "method",
317
+ "name": "_focusLastChipRemove",
318
+ "privacy": "private"
319
+ },
320
+ {
321
+ "kind": "field",
322
+ "name": "_inputPlaceholder",
323
+ "type": {
324
+ "text": "string"
325
+ },
326
+ "privacy": "private",
327
+ "readonly": true
328
+ }
329
+ ],
330
+ "events": [
331
+ {
332
+ "description": "Inherited selection change event.",
333
+ "name": "rc-select-change"
334
+ },
335
+ {
336
+ "description": "When the \"Create\" option is activated. `detail: { text: string }`. Cancelable — call `preventDefault()` to stop the default insertion of the new option.",
337
+ "name": "rc-combobox-create"
338
+ }
339
+ ],
340
+ "attributes": [
341
+ {
342
+ "description": "When set, shows a \"Create '{text}'\" option for text that has no exact match.",
343
+ "name": "allowcreate",
344
+ "type": {
345
+ "text": "boolean"
346
+ },
347
+ "default": "false",
348
+ "fieldName": "allowCreate"
349
+ },
350
+ {
351
+ "name": "filter-strategy",
352
+ "type": {
353
+ "text": "FilterStrategy"
354
+ },
355
+ "default": "'contains'",
356
+ "description": "How option labels are matched against typed input. Forwarded to the internal `rc-listbox`.\nDefaults to `'contains'` (substring). Set to `'prefix'` for starts-with matching,\nor pass a custom `(label, query) => boolean` predicate.\nFunction values are JS-only; string values may be set via the `filter-strategy` attribute.",
357
+ "fieldName": "filterStrategy"
358
+ }
359
+ ],
360
+ "superclass": {
361
+ "name": "RCSelect",
362
+ "package": "@rcarls/rc-select"
363
+ },
364
+ "tagName": "rc-combobox",
365
+ "customElement": true
366
+ }
367
+ ],
368
+ "exports": [
369
+ {
370
+ "kind": "js",
371
+ "name": "RCCombobox",
372
+ "declaration": {
373
+ "name": "RCCombobox",
374
+ "module": "src/rc-combobox.ts"
375
+ }
376
+ },
377
+ {
378
+ "kind": "js",
379
+ "name": "default",
380
+ "declaration": {
381
+ "name": "RCCombobox",
382
+ "module": "src/rc-combobox.ts"
383
+ }
384
+ }
385
+ ]
386
+ }
387
+ ]
388
+ }
package/dist/demo.css ADDED
@@ -0,0 +1,85 @@
1
+ *, *::before, *::after {
2
+ box-sizing: border-box;
3
+ }
4
+
5
+ :root {
6
+ color-scheme: light dark;
7
+ --rc-accent: Highlight; /* Chrome doesn't support AccentColor on the open web */
8
+ }
9
+
10
+ @supports (color: AccentColor) {
11
+ :root {
12
+ --rc-accent: AccentColor;
13
+ }
14
+ }
15
+
16
+ [data-theme="light"] { color-scheme: light; }
17
+ [data-theme="dark"] { color-scheme: dark; }
18
+
19
+ [data-theme="solarized-light"] {
20
+ color-scheme: light;
21
+ --rc-surface: #fdf6e3;
22
+ --rc-text: #657b83;
23
+ --rc-border: 1px solid #93a1a1;
24
+ --rc-shadow: 0 2px 8px rgba(0, 0, 0, .12);
25
+ }
26
+
27
+ [data-theme="solarized-dark"] {
28
+ color-scheme: dark;
29
+ --rc-surface: #002b36;
30
+ --rc-text: #839496;
31
+ --rc-border: 1px solid #586e75;
32
+ --rc-shadow: 0 2px 8px rgba(0, 0, 0, .3);
33
+ }
34
+
35
+ body {
36
+ font-family: system-ui, sans-serif;
37
+ margin: 0;
38
+ }
39
+
40
+ .demo-page {
41
+ max-width: 60rem;
42
+ margin: 0 auto;
43
+ padding: 2rem 1.5rem;
44
+ }
45
+
46
+ .demo-controls {
47
+ display: flex;
48
+ align-items: center;
49
+ gap: 1rem;
50
+ margin-bottom: 2rem;
51
+ padding-bottom: 1rem;
52
+ border-bottom: 1px solid ButtonBorder;
53
+ }
54
+
55
+ .demo-controls .demo-title {
56
+ flex: 1;
57
+ }
58
+
59
+ .demo-controls h1 {
60
+ margin: 0 0 0.15rem;
61
+ font-size: 1.5rem;
62
+ }
63
+
64
+ .demo-controls a {
65
+ font-size: 0.8rem;
66
+ }
67
+
68
+ .demo-section {
69
+ margin-bottom: 2rem;
70
+ }
71
+
72
+ .demo-section h2 {
73
+ margin: 0 0 0.5rem;
74
+ }
75
+
76
+ .theme-picker {
77
+ padding: 0.35em 0.75em;
78
+ font-family: inherit;
79
+ font-size: 0.8rem;
80
+ cursor: pointer;
81
+ border: 1px solid ButtonBorder;
82
+ border-radius: 4px;
83
+ background: Canvas;
84
+ color: CanvasText;
85
+ }
package/dist/demo.js ADDED
@@ -0,0 +1,40 @@
1
+ const THEMES = ['', 'light', 'dark', 'solarized-light', 'solarized-dark'];
2
+ const LABELS = ['Auto', 'Light', 'Dark', 'Solarized ☀', 'Solarized ☾'];
3
+
4
+ const stored = localStorage.getItem('rc-demo-theme') ?? '';
5
+ applyTheme(stored);
6
+
7
+ function applyTheme(theme) {
8
+ if (theme) {
9
+ document.documentElement.dataset.theme = theme;
10
+ } else {
11
+ delete document.documentElement.dataset.theme;
12
+ }
13
+ }
14
+
15
+ function currentIndex() {
16
+ const current = document.documentElement.dataset.theme ?? '';
17
+ const idx = THEMES.indexOf(current);
18
+ return idx === -1 ? 0 : idx;
19
+ }
20
+
21
+ function updateButtons() {
22
+ const label = LABELS[currentIndex()];
23
+ document.querySelectorAll('.theme-picker').forEach((btn) => {
24
+ btn.textContent = label;
25
+ });
26
+ }
27
+
28
+ window.cycleTheme = function () {
29
+ const next = THEMES[(currentIndex() + 1) % THEMES.length];
30
+ applyTheme(next);
31
+ localStorage.setItem('rc-demo-theme', next);
32
+ updateButtons();
33
+ };
34
+
35
+ document.addEventListener('DOMContentLoaded', () => {
36
+ updateButtons();
37
+ document.querySelectorAll('.theme-picker').forEach((btn) => {
38
+ btn.addEventListener('click', window.cycleTheme);
39
+ });
40
+ });
@@ -0,0 +1,365 @@
1
+ import { css as h, nothing as d, html as u } from "lit";
2
+ import { property as p, query as b, state as g } from "lit/decorators.js";
3
+ import { RCSelect as v } from "@rcarls/rc-select";
4
+ const f = h`
5
+ :host {
6
+ display: inline-block;
7
+ }
8
+
9
+ #anchor {
10
+ display: flex;
11
+ align-items: center;
12
+ flex-wrap: wrap;
13
+ gap: var(--rc-combobox-gap, var(--rc-control-gap, 0.25em));
14
+ min-block-size: var(--rc-combobox-control-block-size, var(--rc-control-block-size, auto));
15
+ border: var(--rc-combobox-border, var(--rc-border, 1px solid var(--rc-border-color, ButtonBorder)));
16
+ border-radius: var(--rc-combobox-radius, var(--rc-control-radius, var(--rc-radius-sm, 0.125em)));
17
+ background: var(--rc-field, Field);
18
+ color: var(--rc-field-text, FieldText);
19
+ padding: var(--rc-combobox-padding-block, calc(var(--rc-control-padding-block, 0.25em) / 2))
20
+ var(--rc-combobox-padding-inline, calc(var(--rc-control-padding-inline, 0.5em) / 2));
21
+ cursor: text;
22
+ font-family: var(--rc-font-family, inherit);
23
+ font-size: var(--rc-font-size, inherit);
24
+ line-height: var(--rc-line-height, normal);
25
+ transition:
26
+ background-color var(--rc-motion-duration, 120ms),
27
+ border-color var(--rc-motion-duration, 120ms),
28
+ box-shadow var(--rc-motion-duration, 120ms);
29
+ }
30
+
31
+ /* Chips — the whole chip is the remove button for a larger touch target */
32
+ [part='chip'] {
33
+ display: inline-flex;
34
+ align-items: center;
35
+ gap: var(--rc-combobox-chip-gap, calc(var(--rc-control-gap, 0.25em) * 0.8));
36
+ padding: var(--rc-combobox-chip-padding-block, 0.1em)
37
+ var(--rc-combobox-chip-padding-inline, 0.3em);
38
+ border: var(--rc-combobox-chip-border, var(--rc-border, 1px solid var(--rc-border-color, ButtonBorder)));
39
+ border-radius: var(--rc-combobox-chip-radius, var(--rc-radius-md, 0.25em));
40
+ background: var(--rc-button-bg, ButtonFace);
41
+ color: var(--rc-button-text, ButtonText);
42
+ font: inherit;
43
+ font-size: 0.875em;
44
+ cursor: pointer;
45
+
46
+ &:hover {
47
+ background: var(--rc-highlight, Highlight);
48
+ color: var(--rc-highlight-text, HighlightText);
49
+ }
50
+
51
+ &:focus-visible {
52
+ outline: var(--rc-focus-ring, auto);
53
+ outline-offset: var(--rc-focus-ring-offset, 0);
54
+ }
55
+ }
56
+
57
+ /* Decorative × icon inside the chip — no interaction, aria-hidden */
58
+ [part='chip-remove'] {
59
+ display: inline-flex;
60
+ align-items: center;
61
+ justify-content: center;
62
+ width: 1em;
63
+ font-size: 0.85em;
64
+ pointer-events: none;
65
+ }
66
+
67
+ #trigger {
68
+ flex: 1;
69
+ min-width: 6em;
70
+ border: none;
71
+ background: transparent;
72
+ color: inherit;
73
+ font: inherit;
74
+ outline: none;
75
+ padding: var(--rc-combobox-input-padding-block, var(--rc-control-padding-block, 0.25em))
76
+ var(--rc-combobox-input-padding-inline, calc(var(--rc-control-padding-inline, 0.5em) / 2));
77
+ cursor: text;
78
+ }
79
+
80
+ #trigger::placeholder {
81
+ color: var(--rc-text-disabled, GrayText);
82
+ }
83
+
84
+ #toggle {
85
+ flex-shrink: 0;
86
+ display: inline-flex;
87
+ align-items: center;
88
+ justify-content: center;
89
+ padding: var(--rc-combobox-toggle-padding, var(--rc-control-padding-block, 0.25em));
90
+ border: none;
91
+ background: transparent;
92
+ color: inherit;
93
+ cursor: default;
94
+ font-size: 0.75em;
95
+ font: inherit;
96
+
97
+ &:focus-visible {
98
+ outline: var(--rc-focus-ring, auto);
99
+ outline-offset: var(--rc-focus-ring-offset, 0);
100
+ }
101
+ }
102
+
103
+ /* Hidden native select slot */
104
+ slot[name='select'] {
105
+ display: none;
106
+ }
107
+
108
+ /* Listbox popup — positioned by AnchorController via adoptedStyleSheets */
109
+ rc-listbox {
110
+ max-height: var(--rc-combobox-max-height, 20em);
111
+ overflow-y: auto;
112
+ background: var(--rc-surface, Canvas);
113
+ border: var(--rc-combobox-listbox-border, var(--rc-border, 1px solid var(--rc-border-color, ButtonBorder)));
114
+ border-radius: var(--rc-combobox-listbox-radius, var(--rc-control-radius, 0));
115
+ box-shadow: var(--rc-combobox-shadow, var(--rc-shadow, 0 2px 8px color-mix(in srgb, CanvasText 15%, transparent)));
116
+ color: var(--rc-field-text, FieldText);
117
+ padding-block: var(--rc-combobox-listbox-padding-block, var(--rc-control-padding-block, 0.25em));
118
+
119
+ &:not(:popover-open) {
120
+ display: none;
121
+ }
122
+ }
123
+
124
+ rc-listbox [part~='option'] {
125
+ display: flex;
126
+ align-items: center;
127
+ gap: var(--rc-item-gap, 0.4em);
128
+ padding: var(--rc-item-padding-block, 0.3em) var(--rc-item-padding-inline, 0.75em);
129
+ cursor: default;
130
+
131
+ /* display: flex overrides [hidden]'s browser-default display:none — restore it explicitly */
132
+ &[hidden] { display: none; }
133
+
134
+ &:not([hidden]):not([aria-disabled='true']):hover {
135
+ background: var(--rc-highlight, Highlight);
136
+ color: var(--rc-highlight-text, HighlightText);
137
+ }
138
+
139
+ &[data-active]:not([aria-disabled='true']) {
140
+ background: var(--rc-highlight, Highlight);
141
+ color: var(--rc-highlight-text, HighlightText);
142
+ outline: var(--rc-focus-ring, 2px solid var(--rc-accent, Highlight));
143
+ outline-offset: -2px;
144
+ }
145
+
146
+ &[aria-disabled='true'] {
147
+ opacity: var(--rc-disabled-opacity, 0.5);
148
+ cursor: not-allowed;
149
+ }
150
+ }
151
+
152
+ rc-listbox [part~='option-checkmark'] {
153
+ flex-shrink: 0;
154
+ width: 1em;
155
+ text-align: center;
156
+ font-size: 0.85em;
157
+ visibility: hidden;
158
+ }
159
+
160
+ rc-listbox [part~='option'][aria-selected='true'] [part~='option-checkmark'] {
161
+ visibility: visible;
162
+ }
163
+
164
+ rc-listbox [part~='create-option'] {
165
+ font-style: italic;
166
+ border-top: 1px solid var(--rc-border-color, ButtonBorder);
167
+ margin-top: 0.25em;
168
+ padding-top: 0.3em;
169
+ }
170
+ `;
171
+ var m = Object.defineProperty, a = (n, t, e, i) => {
172
+ for (var r = void 0, s = n.length - 1, c; s >= 0; s--)
173
+ (c = n[s]) && (r = c(t, e, r) || r);
174
+ return r && m(t, e, r), r;
175
+ };
176
+ const l = class l extends v {
177
+ constructor() {
178
+ super(...arguments), this.allowCreate = !1, this.filterStrategy = "contains", this._filterText = "", this._closingPopup = !1;
179
+ }
180
+ // ── Override popup lifecycle ──────────────────────────────────────────────────
181
+ openPopup() {
182
+ super.openPopup(), this._$listbox?.filterOptions(this._filterText);
183
+ }
184
+ closePopup(t = !0) {
185
+ this._filterText = "", this._$listbox?.clearFilter(), this._$listbox?.setCreateOption(null), this._closingPopup = !0, super.closePopup(!1), setTimeout(() => {
186
+ this._closingPopup = !1;
187
+ }, 0);
188
+ }
189
+ // ── Input events ──────────────────────────────────────────────────────────────
190
+ _handleInput(t) {
191
+ this._filterText = t.target.value, !this.open && this._filterText && this.openPopup(), this._$listbox?.filterOptions(this._filterText), this._updateCreateOption(), this._$listbox?.navigableItems.length ? this._adc.navigateToFirst() : this._adc.clear();
192
+ }
193
+ _handleInputFocus() {
194
+ !this.open && !this._closingPopup && this.openPopup();
195
+ }
196
+ _handleToggleClick(t) {
197
+ t.stopPropagation(), this.open ? this.closePopup() : (this.openPopup(), this._$trigger?.focus());
198
+ }
199
+ _handleAnchorClick(t) {
200
+ const e = t.target;
201
+ e.closest('[part~="chip"]') || e.id === "toggle" || this._$trigger?.focus();
202
+ }
203
+ // ── Create option ─────────────────────────────────────────────────────────────
204
+ _updateCreateOption() {
205
+ if (!this.allowCreate || !this._filterText.trim()) {
206
+ this._$listbox?.setCreateOption(null);
207
+ return;
208
+ }
209
+ const t = this._filterText.trim().toLowerCase(), e = this._$listbox?.allOptions.some(
210
+ (i) => i.label.toLowerCase() === t
211
+ ) ?? !1;
212
+ this._$listbox?.setCreateOption(e ? null : this._filterText.trim());
213
+ }
214
+ _handleListboxChange(t) {
215
+ const { optionValue: e, value: i } = t.detail;
216
+ if ((e ?? (Array.isArray(i) ? i.at(-1) : i)) === "__create__") {
217
+ t.stopPropagation(), this._activateCreate(this._filterText.trim());
218
+ return;
219
+ }
220
+ super._handleListboxChange(t), this.multiple ? (this._filterText = "", this._$trigger && (this._$trigger.value = ""), this._$listbox?.clearFilter(), this._updateCreateOption()) : this._syncInputToSelection();
221
+ }
222
+ async _activateCreate(t) {
223
+ if (!t) return;
224
+ const e = new CustomEvent("rc-combobox-create", {
225
+ bubbles: !0,
226
+ composed: !0,
227
+ cancelable: !0,
228
+ detail: { text: t }
229
+ });
230
+ if (this.dispatchEvent(e)) {
231
+ if (this._addOption({ value: t, label: t }), this.multiple) {
232
+ this._$listbox?.toggleOption(t), this._filterText = "", this._$trigger && (this._$trigger.value = ""), this._$listbox?.clearFilter(), this._$listbox?.setCreateOption(null);
233
+ return;
234
+ }
235
+ this._applySelection([t]), this._syncInputToSelection(), this.closePopup(!0), this._dispatchChange();
236
+ }
237
+ }
238
+ set value(t) {
239
+ super.value = t, this._syncInputToSelection();
240
+ }
241
+ get value() {
242
+ return super.value;
243
+ }
244
+ _syncInputToSelection() {
245
+ if (this.multiple) return;
246
+ const t = this.selectedValues[0], e = t ? this._labelFor(t) : "";
247
+ this._filterText = e, this._$trigger && (this._$trigger.value = e);
248
+ }
249
+ // ── Keyboard ──────────────────────────────────────────────────────────────────
250
+ _handleInputKeyDown(t) {
251
+ switch (t.key) {
252
+ case "ArrowDown":
253
+ t.preventDefault(), this.open ? this._adc.navigate(1) : (this.openPopup(), this._adc.navigateToFirst());
254
+ break;
255
+ case "ArrowUp":
256
+ t.preventDefault(), this.open && this._adc.navigate(-1);
257
+ break;
258
+ case "Home":
259
+ this.open && (t.preventDefault(), this._adc.navigateToFirst());
260
+ break;
261
+ case "End":
262
+ this.open && (t.preventDefault(), this._adc.navigateToLast());
263
+ break;
264
+ case "Enter": {
265
+ t.preventDefault();
266
+ const e = this._adc.activeItem;
267
+ e ? e.dispatchEvent(new PointerEvent("pointerdown", { bubbles: !0, cancelable: !0 })) : this.open && this._$listbox?.navigableItems.length && this._$listbox.navigableItems[0]?.dispatchEvent(new PointerEvent("pointerdown", { bubbles: !0, cancelable: !0 }));
268
+ break;
269
+ }
270
+ case "Tab":
271
+ if (this.open) {
272
+ const e = this._$listbox?.navigableItems[0];
273
+ e && e.dispatchEvent(new PointerEvent("pointerdown", { bubbles: !0, cancelable: !0 })), this.closePopup(!1);
274
+ }
275
+ break;
276
+ case "Escape":
277
+ t.preventDefault(), this._filterText = "", this._$trigger && (this._$trigger.value = ""), this.closePopup();
278
+ break;
279
+ case "Backspace":
280
+ this._filterText === "" && this.multiple && this._selectedValues.size > 0 && (t.preventDefault(), this._focusLastChipRemove());
281
+ break;
282
+ case "ArrowLeft":
283
+ t.target.selectionStart === 0 && this.multiple && this._selectedValues.size > 0 && (t.preventDefault(), this._focusLastChipRemove());
284
+ break;
285
+ }
286
+ }
287
+ _focusLastChipRemove() {
288
+ const t = Array.from(
289
+ this.renderRoot.querySelectorAll('button[part~="chip"]')
290
+ );
291
+ t[t.length - 1]?.focus();
292
+ }
293
+ get _inputPlaceholder() {
294
+ return this.multiple && this._selectedValues.size > 0 ? "" : this.placeholder;
295
+ }
296
+ // ── Render ────────────────────────────────────────────────────────────────────
297
+ render() {
298
+ const t = this.multiple && this._selectedValues.size > 0;
299
+ return u`
300
+ <div id="anchor" part="anchor" @click=${this._handleAnchorClick}>
301
+ ${t ? this._renderChips() : d}
302
+
303
+ <input
304
+ id="trigger"
305
+ part="input"
306
+ type="text"
307
+ role="combobox"
308
+ aria-haspopup="listbox"
309
+ aria-expanded=${this.open ? "true" : "false"}
310
+ aria-controls="listbox"
311
+ aria-autocomplete="list"
312
+ ?disabled=${this.disabled}
313
+ placeholder=${this._inputPlaceholder}
314
+ .value=${this._filterText}
315
+ autocomplete="off"
316
+ spellcheck="false"
317
+ @input=${this._handleInput}
318
+ @keydown=${this._handleInputKeyDown}
319
+ @focus=${this._handleInputFocus}
320
+ >
321
+
322
+ <button
323
+ id="toggle"
324
+ type="button"
325
+ part="toggle"
326
+ aria-hidden="true"
327
+ tabindex="-1"
328
+ @click=${this._handleToggleClick}
329
+ >
330
+ <slot name="toggle-icon">&#9660;</slot>
331
+ </button>
332
+ </div>
333
+
334
+ <rc-listbox
335
+ id="listbox"
336
+ part="listbox"
337
+ popover="manual"
338
+ ?multiple=${this.multiple}
339
+ checkmark
340
+ .filterStrategy=${this.filterStrategy}
341
+ @rc-listbox-change=${this._handleListboxChange}
342
+ ></rc-listbox>
343
+
344
+ <slot name="select" @slotchange=${this._handleSelectSlotChange}></slot>
345
+ `;
346
+ }
347
+ };
348
+ l.styles = f;
349
+ let o = l;
350
+ a([
351
+ p({ type: Boolean, attribute: "allowcreate" })
352
+ ], o.prototype, "allowCreate");
353
+ a([
354
+ p({ attribute: "filter-strategy", reflect: !1 })
355
+ ], o.prototype, "filterStrategy");
356
+ a([
357
+ b("#trigger")
358
+ ], o.prototype, "_$trigger");
359
+ a([
360
+ g()
361
+ ], o.prototype, "_filterText");
362
+ export {
363
+ o as R
364
+ };
365
+ //# sourceMappingURL=rc-combobox-D6aALkDz.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-combobox-D6aALkDz.js","sources":["../src/rc-combobox.styles.ts","../src/rc-combobox.ts"],"sourcesContent":["import { css } from 'lit';\n\nexport const comboboxStyles = css`\n :host {\n display: inline-block;\n }\n\n #anchor {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: var(--rc-combobox-gap, var(--rc-control-gap, 0.25em));\n min-block-size: var(--rc-combobox-control-block-size, var(--rc-control-block-size, auto));\n border: var(--rc-combobox-border, var(--rc-border, 1px solid var(--rc-border-color, ButtonBorder)));\n border-radius: var(--rc-combobox-radius, var(--rc-control-radius, var(--rc-radius-sm, 0.125em)));\n background: var(--rc-field, Field);\n color: var(--rc-field-text, FieldText);\n padding: var(--rc-combobox-padding-block, calc(var(--rc-control-padding-block, 0.25em) / 2))\n var(--rc-combobox-padding-inline, calc(var(--rc-control-padding-inline, 0.5em) / 2));\n cursor: text;\n font-family: var(--rc-font-family, inherit);\n font-size: var(--rc-font-size, inherit);\n line-height: var(--rc-line-height, normal);\n transition:\n background-color var(--rc-motion-duration, 120ms),\n border-color var(--rc-motion-duration, 120ms),\n box-shadow var(--rc-motion-duration, 120ms);\n }\n\n /* Chips — the whole chip is the remove button for a larger touch target */\n [part='chip'] {\n display: inline-flex;\n align-items: center;\n gap: var(--rc-combobox-chip-gap, calc(var(--rc-control-gap, 0.25em) * 0.8));\n padding: var(--rc-combobox-chip-padding-block, 0.1em)\n var(--rc-combobox-chip-padding-inline, 0.3em);\n border: var(--rc-combobox-chip-border, var(--rc-border, 1px solid var(--rc-border-color, ButtonBorder)));\n border-radius: var(--rc-combobox-chip-radius, var(--rc-radius-md, 0.25em));\n background: var(--rc-button-bg, ButtonFace);\n color: var(--rc-button-text, ButtonText);\n font: inherit;\n font-size: 0.875em;\n cursor: pointer;\n\n &:hover {\n background: var(--rc-highlight, Highlight);\n color: var(--rc-highlight-text, HighlightText);\n }\n\n &:focus-visible {\n outline: var(--rc-focus-ring, auto);\n outline-offset: var(--rc-focus-ring-offset, 0);\n }\n }\n\n /* Decorative × icon inside the chip — no interaction, aria-hidden */\n [part='chip-remove'] {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1em;\n font-size: 0.85em;\n pointer-events: none;\n }\n\n #trigger {\n flex: 1;\n min-width: 6em;\n border: none;\n background: transparent;\n color: inherit;\n font: inherit;\n outline: none;\n padding: var(--rc-combobox-input-padding-block, var(--rc-control-padding-block, 0.25em))\n var(--rc-combobox-input-padding-inline, calc(var(--rc-control-padding-inline, 0.5em) / 2));\n cursor: text;\n }\n\n #trigger::placeholder {\n color: var(--rc-text-disabled, GrayText);\n }\n\n #toggle {\n flex-shrink: 0;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: var(--rc-combobox-toggle-padding, var(--rc-control-padding-block, 0.25em));\n border: none;\n background: transparent;\n color: inherit;\n cursor: default;\n font-size: 0.75em;\n font: inherit;\n\n &:focus-visible {\n outline: var(--rc-focus-ring, auto);\n outline-offset: var(--rc-focus-ring-offset, 0);\n }\n }\n\n /* Hidden native select slot */\n slot[name='select'] {\n display: none;\n }\n\n /* Listbox popup — positioned by AnchorController via adoptedStyleSheets */\n rc-listbox {\n max-height: var(--rc-combobox-max-height, 20em);\n overflow-y: auto;\n background: var(--rc-surface, Canvas);\n border: var(--rc-combobox-listbox-border, var(--rc-border, 1px solid var(--rc-border-color, ButtonBorder)));\n border-radius: var(--rc-combobox-listbox-radius, var(--rc-control-radius, 0));\n box-shadow: var(--rc-combobox-shadow, var(--rc-shadow, 0 2px 8px color-mix(in srgb, CanvasText 15%, transparent)));\n color: var(--rc-field-text, FieldText);\n padding-block: var(--rc-combobox-listbox-padding-block, var(--rc-control-padding-block, 0.25em));\n\n &:not(:popover-open) {\n display: none;\n }\n }\n\n rc-listbox [part~='option'] {\n display: flex;\n align-items: center;\n gap: var(--rc-item-gap, 0.4em);\n padding: var(--rc-item-padding-block, 0.3em) var(--rc-item-padding-inline, 0.75em);\n cursor: default;\n\n /* display: flex overrides [hidden]'s browser-default display:none — restore it explicitly */\n &[hidden] { display: none; }\n\n &:not([hidden]):not([aria-disabled='true']):hover {\n background: var(--rc-highlight, Highlight);\n color: var(--rc-highlight-text, HighlightText);\n }\n\n &[data-active]:not([aria-disabled='true']) {\n background: var(--rc-highlight, Highlight);\n color: var(--rc-highlight-text, HighlightText);\n outline: var(--rc-focus-ring, 2px solid var(--rc-accent, Highlight));\n outline-offset: -2px;\n }\n\n &[aria-disabled='true'] {\n opacity: var(--rc-disabled-opacity, 0.5);\n cursor: not-allowed;\n }\n }\n\n rc-listbox [part~='option-checkmark'] {\n flex-shrink: 0;\n width: 1em;\n text-align: center;\n font-size: 0.85em;\n visibility: hidden;\n }\n\n rc-listbox [part~='option'][aria-selected='true'] [part~='option-checkmark'] {\n visibility: visible;\n }\n\n rc-listbox [part~='create-option'] {\n font-style: italic;\n border-top: 1px solid var(--rc-border-color, ButtonBorder);\n margin-top: 0.25em;\n padding-top: 0.3em;\n }\n`;\n\nexport default comboboxStyles;\n","import { html, nothing } from 'lit';\nimport { property, query, state } from 'lit/decorators.js';\nimport { RCSelect } from '@rcarls/rc-select';\nimport type { FilterStrategy } from '@rcarls/rc-listbox';\nimport { comboboxStyles } from './rc-combobox.styles.js';\n\nexport interface RCComboboxCreateEvent {\n /** The text typed by the user that didn't match any existing option. */\n text: string;\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'rc-combobox': RCCombobox;\n }\n}\n\n/**\n * An editable combobox with autocomplete filtering and optional allow-create.\n *\n * Extends `rc-select` by replacing the trigger `<div>` with a text `<input>`\n * and adding: live filtering of the listbox, keyboard navigation from input,\n * and an optional \"Create '{text}'\" option for new entries.\n *\n * @slot select - Required. A native `<select>` element for form submission.\n * @slot toggle-icon - Optional. Replaces the default chevron icon.\n *\n * @fires rc-select-change - Inherited selection change event.\n * @fires rc-combobox-create - When the \"Create\" option is activated.\n * `detail: { text: string }`. Cancelable — call `preventDefault()` to stop\n * the default insertion of the new option.\n *\n * @csspart anchor - Outer container (includes chips + input + toggle).\n * @csspart chip - Individual chip (multiple mode).\n * @csspart chip-label - Text label inside a chip.\n * @csspart chip-remove - Remove button inside a chip.\n * @csspart input - The text input element.\n * @csspart toggle - The chevron toggle button.\n *\n * @cssprop [--rc-combobox-max-height=20em] - Maximum popup height.\n * @cssprop [--rc-combobox-control-block-size=var(--rc-control-block-size)] - Anchor block size.\n * @cssprop [--rc-combobox-padding-block=calc(var(--rc-control-padding-block) / 2)] - Anchor block-axis padding.\n * @cssprop [--rc-combobox-padding-inline=calc(var(--rc-control-padding-inline) / 2)] - Anchor inline-axis padding.\n * @cssprop [--rc-combobox-gap=var(--rc-control-gap)] - Gap between chips, input, and toggle.\n * @cssprop [--rc-combobox-radius=var(--rc-control-radius)] - Anchor border radius.\n * @cssprop [--rc-combobox-border=var(--rc-border)] - Anchor border.\n * @cssprop [--rc-combobox-listbox-radius=var(--rc-control-radius)] - Popup listbox border radius.\n * @cssprop [--rc-combobox-listbox-padding-block=var(--rc-control-padding-block)] - Popup listbox block padding.\n * @cssprop [--rc-combobox-chip-radius=var(--rc-radius-md)] - Multi-select chip border radius.\n * @cssprop [--rc-combobox-chip-padding-block=0.1em] - Multi-select chip block-axis padding.\n * @cssprop [--rc-combobox-chip-padding-inline=0.3em] - Multi-select chip inline-axis padding.\n * @attr [allowcreate] - When present, shows a \"Create 'X'\" option for unmatched input.\n */\nexport class RCCombobox extends RCSelect {\n static override styles = comboboxStyles;\n\n /** When set, shows a \"Create '{text}'\" option for text that has no exact match. */\n @property({ type: Boolean, attribute: 'allowcreate' }) allowCreate = false;\n\n /**\n * How option labels are matched against typed input. Forwarded to the internal `rc-listbox`.\n * Defaults to `'contains'` (substring). Set to `'prefix'` for starts-with matching,\n * or pass a custom `(label, query) => boolean` predicate.\n * Function values are JS-only; string values may be set via the `filter-strategy` attribute.\n */\n @property({ attribute: 'filter-strategy', reflect: false }) filterStrategy: FilterStrategy = 'contains';\n\n @query('#trigger') protected override _$trigger!: HTMLInputElement;\n\n @state() private _filterText = '';\n\n // Guard against _handleInputFocus re-opening the popup immediately after close.\n // hidePopover() can trigger a browser-native focus-return to the input in Firefox,\n // so we use setTimeout(0) to defer the reset past any such focus events.\n private _closingPopup = false;\n\n // ── Override popup lifecycle ──────────────────────────────────────────────────\n\n override openPopup() {\n super.openPopup();\n this._$listbox?.filterOptions(this._filterText);\n }\n\n override closePopup(_returnFocus = true) {\n this._filterText = '';\n this._$listbox?.clearFilter();\n this._$listbox?.setCreateOption(null);\n this._closingPopup = true;\n super.closePopup(false);\n // Reset after any native focus-return from hidePopover() fires\n setTimeout(() => { this._closingPopup = false; }, 0);\n }\n\n // ── Input events ──────────────────────────────────────────────────────────────\n\n private _handleInput(e: InputEvent) {\n this._filterText = (e.target as HTMLInputElement).value;\n if (!this.open && this._filterText) this.openPopup();\n this._$listbox?.filterOptions(this._filterText);\n this._updateCreateOption();\n if (this._$listbox?.navigableItems.length) {\n this._adc.navigateToFirst();\n } else {\n this._adc.clear();\n }\n }\n\n private _handleInputFocus() {\n if (!this.open && !this._closingPopup) this.openPopup();\n }\n\n private _handleToggleClick(e: MouseEvent) {\n e.stopPropagation();\n if (this.open) this.closePopup();\n else {\n this.openPopup();\n this._$trigger?.focus();\n }\n }\n\n private _handleAnchorClick(e: MouseEvent) {\n const target = e.target as HTMLElement;\n if (target.closest('[part~=\"chip\"]') || (target as HTMLElement & { id?: string }).id === 'toggle') return;\n this._$trigger?.focus();\n }\n\n // ── Create option ─────────────────────────────────────────────────────────────\n\n private _updateCreateOption() {\n if (!this.allowCreate || !this._filterText.trim()) {\n this._$listbox?.setCreateOption(null);\n return;\n }\n const trimmed = this._filterText.trim().toLowerCase();\n const hasExact = this._$listbox?.allOptions.some(\n (o) => o.label.toLowerCase() === trimmed\n ) ?? false;\n this._$listbox?.setCreateOption(hasExact ? null : this._filterText.trim());\n }\n\n protected override _handleListboxChange(e: CustomEvent) {\n const { optionValue, value } = e.detail as {\n optionValue?: string;\n value: string | string[];\n selected: boolean;\n };\n const activatedValue = optionValue ?? (Array.isArray(value) ? value.at(-1) : value);\n if (activatedValue === '__create__') {\n e.stopPropagation();\n void this._activateCreate(this._filterText.trim());\n return;\n }\n super._handleListboxChange(e);\n if (!this.multiple) {\n this._syncInputToSelection();\n } else {\n this._filterText = '';\n if (this._$trigger) this._$trigger.value = '';\n this._$listbox?.clearFilter();\n this._updateCreateOption();\n }\n }\n\n private async _activateCreate(text: string) {\n if (!text) return;\n const createEvent = new CustomEvent<RCComboboxCreateEvent>('rc-combobox-create', {\n bubbles: true,\n composed: true,\n cancelable: true,\n detail: { text },\n });\n if (!this.dispatchEvent(createEvent)) return;\n\n this._addOption({ value: text, label: text });\n\n if (this.multiple) {\n this._$listbox?.toggleOption(text);\n this._filterText = '';\n if (this._$trigger) this._$trigger.value = '';\n this._$listbox?.clearFilter();\n this._$listbox?.setCreateOption(null);\n return;\n }\n\n this._applySelection([text]);\n this._syncInputToSelection();\n this.closePopup(true);\n this._dispatchChange();\n }\n\n override set value(value: string | string[] | undefined) {\n super.value = value;\n this._syncInputToSelection();\n }\n\n override get value(): string | string[] {\n return super.value;\n }\n\n private _syncInputToSelection(): void {\n if (this.multiple) return;\n\n const value = this.selectedValues[0];\n const label = value ? this._labelFor(value) : '';\n\n this._filterText = label;\n if (this._$trigger) this._$trigger.value = label;\n }\n\n // ── Keyboard ──────────────────────────────────────────────────────────────────\n\n private _handleInputKeyDown(e: KeyboardEvent) {\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n if (!this.open) { this.openPopup(); this._adc.navigateToFirst(); }\n else this._adc.navigate(1);\n break;\n case 'ArrowUp':\n e.preventDefault();\n if (this.open) this._adc.navigate(-1);\n break;\n case 'Home':\n if (this.open) { e.preventDefault(); this._adc.navigateToFirst(); }\n break;\n case 'End':\n if (this.open) { e.preventDefault(); this._adc.navigateToLast(); }\n break;\n case 'Enter': {\n e.preventDefault();\n const active = this._adc.activeItem;\n if (active) {\n active.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, cancelable: true }));\n } else if (this.open && this._$listbox?.navigableItems.length) {\n const first = this._$listbox.navigableItems[0];\n first?.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, cancelable: true }));\n }\n break;\n }\n case 'Tab':\n if (this.open) {\n const first = this._$listbox?.navigableItems[0];\n if (first) {\n first.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, cancelable: true }));\n }\n this.closePopup(false);\n }\n break;\n case 'Escape':\n e.preventDefault();\n this._filterText = '';\n if (this._$trigger) this._$trigger.value = '';\n this.closePopup();\n break;\n case 'Backspace':\n if (this._filterText === '' && this.multiple && this._selectedValues.size > 0) {\n e.preventDefault();\n this._focusLastChipRemove();\n }\n break;\n case 'ArrowLeft':\n if ((e.target as HTMLInputElement).selectionStart === 0\n && this.multiple && this._selectedValues.size > 0) {\n e.preventDefault();\n this._focusLastChipRemove();\n }\n break;\n }\n }\n\n private _focusLastChipRemove() {\n const buttons = Array.from(\n this.renderRoot.querySelectorAll<HTMLButtonElement>('button[part~=\"chip\"]')\n );\n buttons[buttons.length - 1]?.focus();\n }\n\n private get _inputPlaceholder(): string {\n if (this.multiple && this._selectedValues.size > 0) return '';\n return this.placeholder;\n }\n\n // ── Render ────────────────────────────────────────────────────────────────────\n\n protected override render() {\n const showChips = this.multiple && this._selectedValues.size > 0;\n\n return html`\n <div id=\"anchor\" part=\"anchor\" @click=${this._handleAnchorClick}>\n ${showChips ? this._renderChips() : nothing}\n\n <input\n id=\"trigger\"\n part=\"input\"\n type=\"text\"\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded=${this.open ? 'true' : 'false'}\n aria-controls=\"listbox\"\n aria-autocomplete=\"list\"\n ?disabled=${this.disabled}\n placeholder=${this._inputPlaceholder}\n .value=${this._filterText}\n autocomplete=\"off\"\n spellcheck=\"false\"\n @input=${this._handleInput}\n @keydown=${this._handleInputKeyDown}\n @focus=${this._handleInputFocus}\n >\n\n <button\n id=\"toggle\"\n type=\"button\"\n part=\"toggle\"\n aria-hidden=\"true\"\n tabindex=\"-1\"\n @click=${this._handleToggleClick}\n >\n <slot name=\"toggle-icon\">&#9660;</slot>\n </button>\n </div>\n\n <rc-listbox\n id=\"listbox\"\n part=\"listbox\"\n popover=\"manual\"\n ?multiple=${this.multiple}\n checkmark\n .filterStrategy=${this.filterStrategy}\n @rc-listbox-change=${this._handleListboxChange}\n ></rc-listbox>\n\n <slot name=\"select\" @slotchange=${this._handleSelectSlotChange}></slot>\n `;\n }\n}\n\nexport default RCCombobox;\n"],"names":["comboboxStyles","css","_RCCombobox","RCSelect","_returnFocus","e","target","trimmed","hasExact","o","optionValue","value","text","createEvent","label","active","first","buttons","showChips","html","nothing","RCCombobox","__decorateClass","property","query","state"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import "@rcarls/rc-listbox/define";
2
+ import { R as o } from "./rc-combobox-D6aALkDz.js";
3
+ customElements.get("rc-combobox") || customElements.define("rc-combobox", o);
4
+ export {
5
+ o as RCCombobox
6
+ };
7
+ //# sourceMappingURL=rc-combobox-define.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-combobox-define.js","sources":["../src/define.ts"],"sourcesContent":["import '@rcarls/rc-listbox/define';\nimport { RCCombobox } from './index.js';\n\ncustomElements.get('rc-combobox') || customElements.define('rc-combobox', RCCombobox);\n\nexport * from './index.js';\n"],"names":["RCCombobox"],"mappings":";;AAGA,eAAe,IAAI,aAAa,KAAK,eAAe,OAAO,eAAeA,CAAU;"}
@@ -0,0 +1,5 @@
1
+ import { R as m } from "./rc-combobox-D6aALkDz.js";
2
+ export {
3
+ m as RCCombobox
4
+ };
5
+ //# sourceMappingURL=rc-combobox.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rc-combobox.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -0,0 +1 @@
1
+ export * from './index.js';
@@ -0,0 +1 @@
1
+ export * from './rc-combobox.js';
@@ -0,0 +1,79 @@
1
+ import { RCSelect } from '@rcarls/rc-select';
2
+ import { FilterStrategy } from '@rcarls/rc-listbox';
3
+ export interface RCComboboxCreateEvent {
4
+ /** The text typed by the user that didn't match any existing option. */
5
+ text: string;
6
+ }
7
+ declare global {
8
+ interface HTMLElementTagNameMap {
9
+ 'rc-combobox': RCCombobox;
10
+ }
11
+ }
12
+ /**
13
+ * An editable combobox with autocomplete filtering and optional allow-create.
14
+ *
15
+ * Extends `rc-select` by replacing the trigger `<div>` with a text `<input>`
16
+ * and adding: live filtering of the listbox, keyboard navigation from input,
17
+ * and an optional "Create '{text}'" option for new entries.
18
+ *
19
+ * @slot select - Required. A native `<select>` element for form submission.
20
+ * @slot toggle-icon - Optional. Replaces the default chevron icon.
21
+ *
22
+ * @fires rc-select-change - Inherited selection change event.
23
+ * @fires rc-combobox-create - When the "Create" option is activated.
24
+ * `detail: { text: string }`. Cancelable — call `preventDefault()` to stop
25
+ * the default insertion of the new option.
26
+ *
27
+ * @csspart anchor - Outer container (includes chips + input + toggle).
28
+ * @csspart chip - Individual chip (multiple mode).
29
+ * @csspart chip-label - Text label inside a chip.
30
+ * @csspart chip-remove - Remove button inside a chip.
31
+ * @csspart input - The text input element.
32
+ * @csspart toggle - The chevron toggle button.
33
+ *
34
+ * @cssprop [--rc-combobox-max-height=20em] - Maximum popup height.
35
+ * @cssprop [--rc-combobox-control-block-size=var(--rc-control-block-size)] - Anchor block size.
36
+ * @cssprop [--rc-combobox-padding-block=calc(var(--rc-control-padding-block) / 2)] - Anchor block-axis padding.
37
+ * @cssprop [--rc-combobox-padding-inline=calc(var(--rc-control-padding-inline) / 2)] - Anchor inline-axis padding.
38
+ * @cssprop [--rc-combobox-gap=var(--rc-control-gap)] - Gap between chips, input, and toggle.
39
+ * @cssprop [--rc-combobox-radius=var(--rc-control-radius)] - Anchor border radius.
40
+ * @cssprop [--rc-combobox-border=var(--rc-border)] - Anchor border.
41
+ * @cssprop [--rc-combobox-listbox-radius=var(--rc-control-radius)] - Popup listbox border radius.
42
+ * @cssprop [--rc-combobox-listbox-padding-block=var(--rc-control-padding-block)] - Popup listbox block padding.
43
+ * @cssprop [--rc-combobox-chip-radius=var(--rc-radius-md)] - Multi-select chip border radius.
44
+ * @cssprop [--rc-combobox-chip-padding-block=0.1em] - Multi-select chip block-axis padding.
45
+ * @cssprop [--rc-combobox-chip-padding-inline=0.3em] - Multi-select chip inline-axis padding.
46
+ * @attr [allowcreate] - When present, shows a "Create 'X'" option for unmatched input.
47
+ */
48
+ export declare class RCCombobox extends RCSelect {
49
+ static styles: import('lit').CSSResult;
50
+ /** When set, shows a "Create '{text}'" option for text that has no exact match. */
51
+ allowCreate: boolean;
52
+ /**
53
+ * How option labels are matched against typed input. Forwarded to the internal `rc-listbox`.
54
+ * Defaults to `'contains'` (substring). Set to `'prefix'` for starts-with matching,
55
+ * or pass a custom `(label, query) => boolean` predicate.
56
+ * Function values are JS-only; string values may be set via the `filter-strategy` attribute.
57
+ */
58
+ filterStrategy: FilterStrategy;
59
+ protected _$trigger: HTMLInputElement;
60
+ private _filterText;
61
+ private _closingPopup;
62
+ openPopup(): void;
63
+ closePopup(_returnFocus?: boolean): void;
64
+ private _handleInput;
65
+ private _handleInputFocus;
66
+ private _handleToggleClick;
67
+ private _handleAnchorClick;
68
+ private _updateCreateOption;
69
+ protected _handleListboxChange(e: CustomEvent): void;
70
+ private _activateCreate;
71
+ set value(value: string | string[] | undefined);
72
+ get value(): string | string[];
73
+ private _syncInputToSelection;
74
+ private _handleInputKeyDown;
75
+ private _focusLastChipRemove;
76
+ private get _inputPlaceholder();
77
+ protected render(): import('lit').TemplateResult<1>;
78
+ }
79
+ export default RCCombobox;
@@ -0,0 +1,2 @@
1
+ export declare const comboboxStyles: import('lit').CSSResult;
2
+ export default comboboxStyles;
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@rcarls/rc-combobox",
3
+ "publishConfig": {
4
+ "access": "public"
5
+ },
6
+ "version": "0.1.0",
7
+ "description": "Headless editable combobox with filtering and allow-create, built with Lit",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/richardcarls/rc-webcomponents.git",
11
+ "directory": "packages/rc-combobox"
12
+ },
13
+ "homepage": "https://github.com/richardcarls/rc-webcomponents#readme",
14
+ "license": "MIT",
15
+ "type": "module",
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "module": "./dist/rc-combobox.js",
20
+ "exports": {
21
+ ".": {
22
+ "import": {
23
+ "types": "./dist/types/packages/rc-combobox/src/index.d.ts",
24
+ "default": "./dist/rc-combobox.js"
25
+ }
26
+ },
27
+ "./define": {
28
+ "import": {
29
+ "types": "./dist/types/packages/rc-combobox/src/define.d.ts",
30
+ "default": "./dist/rc-combobox-define.js"
31
+ }
32
+ }
33
+ },
34
+ "types": "./dist/types/packages/rc-combobox/src/index.d.ts",
35
+ "sideEffects": [
36
+ "./dist/rc-combobox-define.js"
37
+ ],
38
+ "customElements": "dist/custom-elements.json",
39
+ "scripts": {
40
+ "dev": "vite",
41
+ "build": "tsc && vite build && cem analyze",
42
+ "cem:analyze": "cem analyze",
43
+ "preview": "vite preview",
44
+ "test:browser": "vitest",
45
+ "test:browser:chrome": "vitest --project=chromium",
46
+ "test:browser:firefox": "vitest --project=firefox",
47
+ "yalc:publish": "yalc publish --push"
48
+ },
49
+ "dependencies": {
50
+ "@rcarls/rc-common": "workspace:^",
51
+ "@rcarls/rc-listbox": "workspace:^",
52
+ "@rcarls/rc-select": "workspace:^"
53
+ },
54
+ "devDependencies": {
55
+ "@custom-elements-manifest/analyzer": "0.11.0",
56
+ "@guanghechen/rollup-plugin-copy": "^6.0.9",
57
+ "@vitest/browser-playwright": "4.1.5",
58
+ "lit": "^3.0.0",
59
+ "playwright": "^1.56.0",
60
+ "rollup": "^4.60.2",
61
+ "typescript": "~5.9.3",
62
+ "vite": "^7.1.7",
63
+ "vite-plugin-dts": "^4.5.4",
64
+ "vite-plugin-static-copy": "^4.1.0",
65
+ "vitest": "^4.0.6",
66
+ "vitest-browser-lit": "^1.0.1"
67
+ },
68
+ "peerDependencies": {
69
+ "lit": "^3.0.0"
70
+ }
71
+ }