rich-input 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/LICENSE +21 -0
- package/README.md +419 -0
- package/assets/rich-input-parts.svg +480 -0
- package/components/rich-input.js +1003 -0
- package/index.js +32 -0
- package/package.json +35 -0
- package/utils/highlights.js +101 -0
- package/utils/positioning.js +111 -0
- package/utils/query-parser.js +344 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bramus Van Damme <bramus@bram.us> (https://www.bram.us/)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
# rich-input
|
|
2
|
+
|
|
3
|
+
> A rich input field `<rich-input>` with keyword-based autocomplete and in-input highlighting powered by `<datalist>`, the **OpaqueRange API**, and the **CSS Custom Highlight API**.
|
|
4
|
+
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements)
|
|
7
|
+
|
|
8
|
+
The `<rich-input>` component is a rich input field that acts like a standard `<input type="text">` so users can type ordinary text or search terms, but enhances it with contextual autocomplete and in-input highlighting for structured `keyword:value` entries (such as `label:"We Play House Recordings" year:2026 playlist:"WPH Classics"`).
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- **Standard Input Ergonomics**: Acts and feels like a regular `<input type="text">` with standard value access, selection ranges, and events.
|
|
15
|
+
- **Dual Contextual Autocomplete**:
|
|
16
|
+
- **Keywords**: Typing at the start of a token (e.g. typing `a`) suggests configured keywords like `artist:` and `style:`.
|
|
17
|
+
- **Values**: Typing within a keyword value (e.g. `label:"K` or `label:K`) suggests matching options like `"Keinemusik"` and `"Kranky"`.
|
|
18
|
+
- **Range-Based Positioning via OpaqueRange**: Positions autocomplete dropdown popovers anchored to the start of the active `OpaqueRange` (e.g. at the opening quotation mark of a value) using `range.getBoundingClientRect()`, rather than shifting with the cursor.
|
|
19
|
+
- **Native In-Input Highlighting via CSS Custom Highlight API**: Highlights keyword values inside the `<input>` control using standard CSS rules like `::highlight(label)` or `::highlight(year)` without brittle mirror-div overlays.
|
|
20
|
+
- **Declarative Configuration via `<datalist>`**: Configure keywords and options purely in HTML by nesting standard `<datalist>` elements with `<option>` tags inside `<rich-input>`.
|
|
21
|
+
- **Rich Option Markup**: Embed custom HTML markup (such as logos, images, icons, and avatars) directly inside `<option>` elements for rich, visual suggestion popovers.
|
|
22
|
+
- **Form Associated**: Implements `static formAssociated = true` and `ElementInternals` to participate seamlessly in `<form>` submission, `FormData`, and form reset lifecycles.
|
|
23
|
+
- **Shadow Parts Theming (`::part`)**: Full CSS customizability using `::part(input)`, `::part(control)`, `::part(popover)`, `::part(suggestion-item)`, etc.
|
|
24
|
+
- **Accessible (W3C Combobox Pattern)**: ARIA 1.2 compliant combobox with keyboard navigation (`ArrowUp`, `ArrowDown`, `Enter`, `Tab`, `Escape`), `aria-expanded`, and `aria-activedescendant`.
|
|
25
|
+
- **Graceful Fallback**: Automatically feature-detects `createValueRange` and gracefully falls back to control-aligned popovers in environments without OpaqueRange.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Component Anatomy & Shadow Parts
|
|
30
|
+
|
|
31
|
+
The visual below illustrates the internal Shadow DOM elements, exposed CSS Shadow Parts (`::part`), and CSS Custom Highlight pseudo-elements (`::highlight`), showing how they relate to one another:
|
|
32
|
+
|
|
33
|
+
<p align="center">
|
|
34
|
+
<img src="assets/rich-input-parts.svg" alt="<rich-input> Component Anatomy, Shadow Parts, and Highlight Pseudos" width="100%">
|
|
35
|
+
</p>
|
|
36
|
+
|
|
37
|
+
- `<rich-input>`: The host custom element wrapping the control, datalists, and suggestions popover.
|
|
38
|
+
- `::part(control)`: The outer input container enclosing the icon, input, and clear button.
|
|
39
|
+
- `::part(icon)`: The default leading search magnifying glass SVG icon (fallback in `slot="leading"`).
|
|
40
|
+
- `::part(input)`: The native `<input type="text">` where users type.
|
|
41
|
+
- `::highlight(<keyword>)`: Target pseudo-element for styling keyword values via the CSS Custom Highlight API (e.g. `::highlight(label)`, `::highlight(year)`).
|
|
42
|
+
- `::part(clear-button)`: The clear button (visible when text is present).
|
|
43
|
+
- `::part(popover)`: The autocomplete dropdown popover container anchored to the start of the active range via `OpaqueRange`.
|
|
44
|
+
- `::part(suggestions-header)`: The header bar at the top of the suggestions popover.
|
|
45
|
+
- `::part(suggestions-list)`: The `<ul>` container holding autocomplete suggestion items.
|
|
46
|
+
- `::part(suggestion-item)`: Each suggestion `<li>` row.
|
|
47
|
+
- `::part(suggestion-item-active)`: The currently selected / keyboard-focused suggestion row.
|
|
48
|
+
- `::part(suggestion-image)`: The circular logo, icon, or avatar image prepended to rich suggestions.
|
|
49
|
+
- `::part(suggestion-keyword)`: The keyword label text inside a keyword suggestion.
|
|
50
|
+
- `::part(suggestion-value)`: The value label text inside a value suggestion.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Quick Start
|
|
55
|
+
|
|
56
|
+
### 1. Installation
|
|
57
|
+
|
|
58
|
+
Install via npm:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
npm install rich-input
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Or import directly in your HTML/JavaScript bundle:
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
import 'rich-input';
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Or via CDN:
|
|
71
|
+
|
|
72
|
+
```html
|
|
73
|
+
<script type="module" src="https://esm.sh/rich-input"></script>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 2. Basic Usage
|
|
77
|
+
|
|
78
|
+
Nest `<datalist>` elements inside `<rich-input>` to configure keywords and autocomplete suggestions:
|
|
79
|
+
|
|
80
|
+
```html
|
|
81
|
+
<rich-input placeholder="Search music catalog...">
|
|
82
|
+
<datalist id="label" label="Record Label">
|
|
83
|
+
<option value="Defected"></option>
|
|
84
|
+
<option value="Keinemusik"></option>
|
|
85
|
+
<option value="Kranky"></option>
|
|
86
|
+
<option value="Madhouse Records"></option>
|
|
87
|
+
<option value="Ninja Tune"></option>
|
|
88
|
+
<option value="We Play House Recordings"></option>
|
|
89
|
+
<option value="XL Recordings"></option>
|
|
90
|
+
</datalist>
|
|
91
|
+
|
|
92
|
+
<datalist id="year" label="Release Year" data-type="number">
|
|
93
|
+
<option value="2026"></option>
|
|
94
|
+
<option value="2025"></option>
|
|
95
|
+
<option value="2024"></option>
|
|
96
|
+
</datalist>
|
|
97
|
+
|
|
98
|
+
<datalist id="playlist" label="Playlist">
|
|
99
|
+
<option value="WPH Classics"></option>
|
|
100
|
+
<option value="Late Night Grooves"></option>
|
|
101
|
+
</datalist>
|
|
102
|
+
</rich-input>
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Datalist Configuration
|
|
108
|
+
|
|
109
|
+
Configuration is defined by standard HTML `<datalist>` elements placed inside the `<rich-input>` element:
|
|
110
|
+
|
|
111
|
+
| Element / Attribute | Type | Description |
|
|
112
|
+
|---|---|---|
|
|
113
|
+
| `<datalist id="...">` | `string` | **Required.** The keyword identifier used in queries (e.g. `id="artist"` produces `artist:`). Case-insensitive. |
|
|
114
|
+
| `<datalist label="...">` | `string` | Human-readable label displayed in suggestion headers. Defaults to capitalized `id`. |
|
|
115
|
+
| `<datalist data-type="...">` | `string` | Optional data type (`"string"` or `"number"`). |
|
|
116
|
+
| `<option value="...">` | `string` | The suggested value. If the value contains spaces, quotes are automatically added when inserted (e.g. `"We Play House Recordings"`). |
|
|
117
|
+
| `<option label="...">` | `string` | Optional descriptive label shown alongside the value. |
|
|
118
|
+
| `<option>` children | `Node` | Optional image (`<img>`) prepended to the suggested value. |
|
|
119
|
+
|
|
120
|
+
Datalists can be added, updated, or removed dynamically at runtime; `<rich-input>` observes changes via `slotchange` and `MutationObserver`.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Rich Option Markup
|
|
125
|
+
|
|
126
|
+
`<rich-input>` supports rich HTML markup inside `<option>` elements. For example, for record labels or artists, you can prepend a logo image:
|
|
127
|
+
|
|
128
|
+
```html
|
|
129
|
+
<rich-input placeholder="Search...">
|
|
130
|
+
<datalist id="label" label="Record Label">
|
|
131
|
+
<option value="Defected">
|
|
132
|
+
<img src="assets/defected.jpg" height="50" width="50" alt="Defected Logo">
|
|
133
|
+
Defected
|
|
134
|
+
</option>
|
|
135
|
+
<option value="Keinemusik">
|
|
136
|
+
<img src="assets/keinemusik.jpg" height="50" width="50" alt="Keinemusik Logo">
|
|
137
|
+
Keinemusik
|
|
138
|
+
</option>
|
|
139
|
+
<option value="Kranky">
|
|
140
|
+
<img src="assets/kranky.jpg" height="50" width="50" alt="Kranky Logo">
|
|
141
|
+
Kranky
|
|
142
|
+
</option>
|
|
143
|
+
<option value="Madhouse Records">
|
|
144
|
+
<img src="assets/madhouse-records.jpg" height="50" width="50" alt="Madhouse Records Logo">
|
|
145
|
+
Madhouse Records
|
|
146
|
+
</option>
|
|
147
|
+
<option value="Ninja Tune">
|
|
148
|
+
<img src="assets/ninja-tune.jpg" height="50" width="50" alt="Ninja Tune Logo">
|
|
149
|
+
Ninja Tune
|
|
150
|
+
</option>
|
|
151
|
+
<option value="We Play House Recordings">
|
|
152
|
+
<img src="assets/we-play-house-recordings.jpg" height="50" width="50" alt="We Play House Recordings Logo">
|
|
153
|
+
We Play House Recordings
|
|
154
|
+
</option>
|
|
155
|
+
<option value="XL Recordings">
|
|
156
|
+
<img src="assets/xl-recordings.jpg" height="50" width="50" alt="XL Recordings Logo">
|
|
157
|
+
XL Recordings
|
|
158
|
+
</option>
|
|
159
|
+
</datalist>
|
|
160
|
+
</rich-input>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
When suggesting values for `label:`, `<rich-input>` sniffs the image inside the `<option>`, renders it alongside the option's text content, and exposes `::part(suggestion-image)` for external CSS styling (e.g. as a `1em` circular icon):
|
|
164
|
+
|
|
165
|
+
```css
|
|
166
|
+
rich-input::part(suggestion-image) {
|
|
167
|
+
width: 1em;
|
|
168
|
+
height: 1em;
|
|
169
|
+
border-radius: 50%;
|
|
170
|
+
object-fit: cover;
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Custom Leading Icon & Slots
|
|
177
|
+
|
|
178
|
+
`<rich-input>` provides named slots to customize elements inside the control:
|
|
179
|
+
|
|
180
|
+
- **`slot="leading"`**: Replace the leading icon. The default magnifying glass SVG is provided as fallback content inside the slot, so passing a custom element into `slot="leading"` automatically replaces it without needing CSS overrides.
|
|
181
|
+
- **`slot="trailing"`**: Add controls or elements after the clear button (e.g. submit button, voice input, keyboard shortcut badge).
|
|
182
|
+
|
|
183
|
+
### Passing a Custom Leading Icon
|
|
184
|
+
|
|
185
|
+
Provide your own SVG or image with `slot="leading"`:
|
|
186
|
+
|
|
187
|
+
```html
|
|
188
|
+
<rich-input placeholder="Search music catalog...">
|
|
189
|
+
<!-- Custom leading music icon -->
|
|
190
|
+
<svg slot="leading" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
191
|
+
<path d="M9 18V5l12-2v13"></path>
|
|
192
|
+
<circle cx="6" cy="18" r="3"></circle>
|
|
193
|
+
<circle cx="18" cy="16" r="3"></circle>
|
|
194
|
+
</svg>
|
|
195
|
+
|
|
196
|
+
<datalist id="genre" label="Genre">
|
|
197
|
+
<option value="House"></option>
|
|
198
|
+
<option value="Techno"></option>
|
|
199
|
+
</datalist>
|
|
200
|
+
</rich-input>
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
When an element with `slot="leading"` is supplied, the default search magnifying glass icon is automatically suppressed. If no slotted element is provided, the default magnifying glass icon renders as fallback.
|
|
204
|
+
|
|
205
|
+
### Available Slots
|
|
206
|
+
|
|
207
|
+
| Slot Name | Description |
|
|
208
|
+
|---|---|
|
|
209
|
+
| `leading` | Custom leading icon or content. Defaults to the search magnifying glass icon (`::part(icon)`). |
|
|
210
|
+
| `trailing` | Custom content rendered after the clear button. |
|
|
211
|
+
| *(default)* | Unnamed slot where `<datalist>` configuration elements are placed (visually hidden). |
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## The OpaqueRange API
|
|
216
|
+
|
|
217
|
+
The [OpaqueRange API](https://chromestatus.com/feature/6297362687066112) is a web platform standard introduced in Chromium 152 (Google Chrome, Microsoft Edge) that enables range-based operations over the text content of form controls (`<input>` and `<textarea>`).
|
|
218
|
+
|
|
219
|
+
Before `OpaqueRange`, web authors had to clone form controls into hidden `<div>`s to measure caret coordinates or apply highlights. `OpaqueRange` provides native access:
|
|
220
|
+
|
|
221
|
+
```javascript
|
|
222
|
+
// Measure exact caret coordinates inside <input>
|
|
223
|
+
const range = input.createValueRange(caretPos, caretPos);
|
|
224
|
+
const rect = range.getBoundingClientRect();
|
|
225
|
+
|
|
226
|
+
// Anchor autocomplete popover at caret
|
|
227
|
+
popover.style.left = `${rect.left}px`;
|
|
228
|
+
popover.style.top = `${rect.bottom + 4}px`;
|
|
229
|
+
|
|
230
|
+
// Highlight syntax directly inside <input>
|
|
231
|
+
const valueRange = input.createValueRange(valStart, valEnd);
|
|
232
|
+
const highlight = new Highlight(valueRange);
|
|
233
|
+
CSS.highlights.set('label', highlight);
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
`<rich-input>` automatically checks `typeof HTMLInputElement.prototype.createValueRange === 'function'`. On supported browsers, caret tracking and `::highlight()` are applied natively. On browsers without `createValueRange`, `<rich-input>` falls back to input-relative popover positioning.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Styling Highlights with the CSS Custom Highlight API
|
|
241
|
+
|
|
242
|
+
Values corresponding to configured keywords are registered into the global `CSS.highlights` registry. Style them directly using `::highlight(keyword)`:
|
|
243
|
+
|
|
244
|
+
```css
|
|
245
|
+
/* Style the value set in label:"We Play House Recordings" */
|
|
246
|
+
::highlight(label) {
|
|
247
|
+
background-color: oklch(0.92 0.08 240);
|
|
248
|
+
color: oklch(0.28 0.14 240);
|
|
249
|
+
text-decoration: 2px underline solid oklch(0.5 0.15 240 / 0.5);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* Style numeric year values like year:2026 */
|
|
253
|
+
::highlight(year) {
|
|
254
|
+
background-color: oklch(0.93 0.1 85);
|
|
255
|
+
color: oklch(0.35 0.14 85);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/* Style artist names */
|
|
259
|
+
::highlight(artist) {
|
|
260
|
+
background-color: oklch(0.92 0.1 320);
|
|
261
|
+
color: oklch(0.32 0.14 320);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/* Style musical style filters */
|
|
265
|
+
::highlight(style) {
|
|
266
|
+
background-color: oklch(0.93 0.08 190);
|
|
267
|
+
color: oklch(0.3 0.12 190);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/* Style genre filters */
|
|
271
|
+
::highlight(genre) {
|
|
272
|
+
background-color: oklch(0.93 0.1 145);
|
|
273
|
+
color: oklch(0.3 0.14 145);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* Generic prefix highlight for keyword labels (e.g. "label:", "year:") */
|
|
277
|
+
::highlight(rich-input-keyword) {
|
|
278
|
+
color: #64748b;
|
|
279
|
+
text-shadow: 0 0 1px rgba(0, 0, 0, 0.15);
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
> **Note:** Supported CSS properties on `::highlight()` include `color`, `background-color`, `text-decoration`, `text-shadow`, `-webkit-text-stroke-color`, `-webkit-text-stroke-width`, and `-webkit-text-fill-color`.
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Styling the Input with Shadow Parts (`::part`)
|
|
288
|
+
|
|
289
|
+
Every internal element of `<rich-input>` is exposed via `::part()`:
|
|
290
|
+
|
|
291
|
+
```css
|
|
292
|
+
/* Style the outer control container */
|
|
293
|
+
rich-input::part(control) {
|
|
294
|
+
border-radius: 9999px;
|
|
295
|
+
border: 2px solid #2563eb;
|
|
296
|
+
padding: 0 1.25rem;
|
|
297
|
+
background: #ffffff;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/* Style the native text input */
|
|
301
|
+
rich-input::part(input) {
|
|
302
|
+
font-family: 'JetBrains Mono', monospace;
|
|
303
|
+
font-size: 1rem;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/* Style the suggestions popover */
|
|
307
|
+
rich-input::part(popover) {
|
|
308
|
+
border-radius: 12px;
|
|
309
|
+
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.15);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/* Style active suggestion item */
|
|
313
|
+
rich-input::part(suggestion-item-active) {
|
|
314
|
+
background-color: #dbeafe;
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Available Shadow Parts
|
|
319
|
+
|
|
320
|
+
| Part Name | Description |
|
|
321
|
+
|---|---|
|
|
322
|
+
| `::part(control)` | The wrapper container enclosing the search icon, input, and clear button |
|
|
323
|
+
| `::part(input)` | The internal native `<input type="text">` |
|
|
324
|
+
| `::part(icon)` | The default leading search icon SVG (fallback in `slot="leading"`) |
|
|
325
|
+
| `::part(clear-button)` | The clear button (visible when text is present) |
|
|
326
|
+
| `::part(popover)` | The autocomplete popover container |
|
|
327
|
+
| `::part(suggestions-header)` | The header bar at the top of the popover |
|
|
328
|
+
| `::part(suggestions-list)` | The `<ul>` list element |
|
|
329
|
+
| `::part(suggestion-item)` | Each suggestion `<li>` item |
|
|
330
|
+
| `::part(suggestion-item-active)` | The currently selected / hovered suggestion item |
|
|
331
|
+
| `::part(suggestion-keyword)` | Keyword name element in suggestion items |
|
|
332
|
+
| `::part(suggestion-value)` | Value element in suggestion items |
|
|
333
|
+
| `::part(suggestion-content)` | The content container inside each suggestion item |
|
|
334
|
+
| `::part(suggestion-image)` | Image or icon element rendered inside rich suggestion items |
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
## JavaScript API
|
|
339
|
+
|
|
340
|
+
### Properties
|
|
341
|
+
|
|
342
|
+
- `value` (`string`): Gets or sets the search input value. Updates highlights and form value automatically.
|
|
343
|
+
- `placeholder` (`string`): Gets or sets the input placeholder text.
|
|
344
|
+
- `disabled` (`boolean`): Disables or enables the input control.
|
|
345
|
+
- `name` (`string`): Form field name when submitted inside a `<form>`.
|
|
346
|
+
- `selectionStart` / `selectionEnd` (`number`): Text selection / cursor indices.
|
|
347
|
+
|
|
348
|
+
### Methods
|
|
349
|
+
|
|
350
|
+
- `getParsedQuery()`: Returns a parsed object representing the search query:
|
|
351
|
+
```json
|
|
352
|
+
{
|
|
353
|
+
"raw": "label:\"We Play House Recordings\" year:2026 chicago house",
|
|
354
|
+
"text": "chicago house",
|
|
355
|
+
"keywords": {
|
|
356
|
+
"label": ["We Play House Recordings"],
|
|
357
|
+
"year": ["2026"]
|
|
358
|
+
},
|
|
359
|
+
"tokens": [...]
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
- `getKeywords()`: Returns an array of configured keyword definitions from the datalists.
|
|
363
|
+
- `focus(options)`: Focuses the internal input.
|
|
364
|
+
- `blur()`: Removes focus from the internal input.
|
|
365
|
+
- `select()`: Selects all text inside the input.
|
|
366
|
+
- `setSelectionRange(start, end, direction)`: Sets caret or selection range.
|
|
367
|
+
|
|
368
|
+
### Events
|
|
369
|
+
|
|
370
|
+
- `input`: Dispatched when the search value changes (bubbles, composed).
|
|
371
|
+
- `change`: Dispatched on blur or when a search change is committed.
|
|
372
|
+
- `search`: Dispatched when the user presses `Enter` with suggestions closed.
|
|
373
|
+
- `rich-input-select`: Dispatched when an autocomplete suggestion is selected.
|
|
374
|
+
- `event.detail`: `{ type, keyword, value, label, query }`
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## Form Integration
|
|
379
|
+
|
|
380
|
+
`<rich-input>` supports native `<form>` submission through standard `ElementInternals`:
|
|
381
|
+
|
|
382
|
+
```html
|
|
383
|
+
<form id="search-form" action="/search" method="GET">
|
|
384
|
+
<rich-input name="q" placeholder="Search tracks...">
|
|
385
|
+
<datalist id="genre" label="Genre">
|
|
386
|
+
<option value="House"></option>
|
|
387
|
+
<option value="Techno"></option>
|
|
388
|
+
</datalist>
|
|
389
|
+
</rich-input>
|
|
390
|
+
<button type="submit">Search</button>
|
|
391
|
+
</form>
|
|
392
|
+
|
|
393
|
+
<script>
|
|
394
|
+
const form = document.getElementById('search-form');
|
|
395
|
+
form.addEventListener('submit', (e) => {
|
|
396
|
+
e.preventDefault();
|
|
397
|
+
const data = new FormData(form);
|
|
398
|
+
console.log('Submitted query:', data.get('q'));
|
|
399
|
+
});
|
|
400
|
+
</script>
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
## Building from Source
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
# Build ./dist package
|
|
409
|
+
npm run build
|
|
410
|
+
|
|
411
|
+
# Start local demo server
|
|
412
|
+
npm start
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
## License
|
|
418
|
+
|
|
419
|
+
[MIT](LICENSE) © [Bramus Van Damme](https://www.bram.us/)
|