rich-input 1.3.0 → 1.4.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 CHANGED
@@ -5,18 +5,20 @@
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
6
6
  [![Custom Elements](https://img.shields.io/badge/Web_Components-Custom_Elements_v1-orange.svg)](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_custom_elements)
7
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"`).
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 queries with `keyword:value` filters, prefix `operators` (e.g. `-`), boolean `combinators` (e.g. `AND`, `OR`), and grouping `delimiters` (e.g. `()`), such as `(label:"We Play House Recordings" OR year:2026) AND -style:"Acid House"`.
9
9
 
10
10
  ---
11
11
 
12
12
  ## Features
13
13
 
14
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:`.
15
+ - **Contextual Autocomplete**:
16
+ - **Keywords**: Typing at the start of a token or after an operator/delimiter (e.g. typing `a` or `-s`) suggests configured keywords like `artist:` and `-style:`.
17
+ - **Combinators**: Typing a configured boolean combinator (e.g. `A` or `O` when `combinators="AND OR"` is set) suggests `AND` and `OR`.
17
18
  - **Values**: Typing within a keyword value (e.g. `label:"K` or `label:K`) suggests matching options like `"Keinemusik"` and `"Kranky"`.
19
+ - **Configurable Operators, Combinators & Delimiters**: Configure prefix operators (`operators="- ~ +"`), boolean combinators (`combinators="AND OR NOT"`), and grouping delimiter pairs (`delimiters="() [] {}"`) per instance via HTML attributes / JS properties, or globally via static properties on `RichInput`.
18
20
  - **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 (with a hidden mirror-div fallback in unsupported browsers).
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.
21
+ - **Native In-Input Highlighting via CSS Custom Highlight API**: Highlights keyword values, operators, combinators, delimiters, and invalid tokens inside the `<input>` control using standard CSS rules like `::highlight(label)`, `::highlight(rich-input-operator)`, `::highlight(rich-input-combinator)`, and `::highlight(rich-input-delimiter)` without brittle mirror-div overlays.
20
22
  - **Declarative Configuration via `<datalist>`**: Configure keywords and options purely in HTML by nesting standard `<datalist>` elements with `<option>` tags inside `<rich-input>`.
21
23
  - **Rich Option Markup**: Embed custom HTML markup (such as logos, images, icons, and avatars) directly inside `<option>` elements for rich, visual suggestion popovers.
22
24
  - **Form Associated**: Implements `static formAssociated = true` and `ElementInternals` to participate seamlessly in `<form>` submission, `FormData`, and form reset lifecycles.
@@ -36,8 +38,11 @@ The visual below illustrates the internal Shadow DOM elements, exposed CSS Shado
36
38
 
37
39
  - `<rich-input>`: The host custom element wrapping the control, datalists, and suggestions popover.
38
40
  - `::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"`).
41
+ - `::part(icon)`: The leading icon element (defaults to a search magnifying glass, customizable via CSS `content` or `background`).
40
42
  - `::highlight(<keyword>)`: Target pseudo-element for styling keyword values via the CSS Custom Highlight API (e.g. `::highlight(label)`, `::highlight(year)`).
43
+ - `::highlight(rich-input-operator)`: Target pseudo-element for styling keyword operators (e.g. `-` in `-style:"Acid House"`).
44
+ - `::highlight(rich-input-combinator)`: Target pseudo-element for styling query combinators (e.g. `OR` in `artist:"Aphex Twin" OR label:"Defected"`).
45
+ - `::highlight(rich-input-delimiter)`: Target pseudo-element for styling grouping delimiters (e.g. `(` and `)` in `(artist:"Aphex Twin" OR label:"Defected")`).
41
46
  - `::highlight(rich-input-keyword)`: Target pseudo-element for styling keyword prefixes (e.g. `label:`, `year:`).
42
47
  - `::highlight(rich-input-invalid)`: Target pseudo-element for marking unrecognized keywords or invalid keyword values (not in datalist) with a squiggly underline.
43
48
  - `::part(clear-button)`: The clear button (visible when text is present).
@@ -49,6 +54,7 @@ The visual below illustrates the internal Shadow DOM elements, exposed CSS Shado
49
54
  - `::part(suggestion-item-selected)`: The suggestion row matching the value currently echoed in the input.
50
55
  - `::part(suggestion-image)`: The circular logo, icon, or avatar image prepended to rich suggestions.
51
56
  - `::part(suggestion-keyword)`: The keyword label text inside a keyword suggestion.
57
+ - `::part(suggestion-combinator)`: The combinator label text inside a combinator suggestion.
52
58
  - `::part(suggestion-value)`: The value label text inside a value suggestion.
53
59
 
54
60
  ---
@@ -77,10 +83,15 @@ Or via CDN:
77
83
 
78
84
  ### 2. Basic Usage
79
85
 
80
- Nest `<datalist>` elements inside `<rich-input>` to configure keywords and autocomplete suggestions:
86
+ Nest `<datalist>` elements inside `<rich-input>` to configure keywords and autocomplete suggestions, and optionally configure `operators`, `combinators`, and `delimiters`:
81
87
 
82
88
  ```html
83
- <rich-input placeholder="Search music catalog...">
89
+ <rich-input
90
+ operators="-"
91
+ combinators="AND OR"
92
+ delimiters="()"
93
+ placeholder="Search music catalog..."
94
+ >
84
95
  <datalist id="label" label="Record Label">
85
96
  <option value="Defected"></option>
86
97
  <option value="House"></option>
@@ -99,21 +110,25 @@ Nest `<datalist>` elements inside `<rich-input>` to configure keywords and autoc
99
110
  <option value="2024"></option>
100
111
  </datalist>
101
112
 
102
- <datalist id="playlist" label="Playlist">
103
- <option value="WPH Classics"></option>
104
- <option value="Late Night Grooves"></option>
113
+ <datalist id="style" label="Style">
114
+ <option value="Acid House"></option>
115
+ <option value="Deep House"></option>
116
+ <option value="Dub Techno"></option>
105
117
  </datalist>
106
118
  </rich-input>
107
119
  ```
108
120
 
109
121
  ---
110
122
 
111
- ## Datalist Configuration
123
+ ## Datalist & Syntax Configuration
112
124
 
113
- Configuration is defined by standard HTML `<datalist>` elements placed inside the `<rich-input>` element:
125
+ Configuration is defined by attributes on `<rich-input>` and standard HTML `<datalist>` elements placed inside the `<rich-input>` element:
114
126
 
115
127
  | Element / Attribute | Type | Description |
116
128
  |---|---|---|
129
+ | `<rich-input operators="...">` | `string` | Optional space-separated list of single-character prefix operators (e.g. `operators="- ~ +"`). Defaults to `"-"` (negative filter). Set `operators=""` to disable operators. |
130
+ | `<rich-input combinators="...">` | `string` | Optional space-separated list of boolean query combinators (e.g. `combinators="AND OR NOT"`). Defaults to `""` (empty array). |
131
+ | `<rich-input delimiters="...">` | `string` | Optional space-separated list of two-character opening/closing delimiter pairs (e.g. `delimiters="() [] {}"`). Defaults to `"()"` (parentheses). Set `delimiters=""` to disable delimiters. |
117
132
  | `<datalist id="...">` | `string` | **Required.** The keyword identifier used in queries (e.g. `id="artist"` produces `artist:`). Case-insensitive. |
118
133
  | `<datalist label="...">` | `string` | Human-readable label displayed in suggestion headers. Defaults to capitalized `id`. |
119
134
  | `<datalist data-type="...">` | `string` | Optional data type (`"string"` or `"number"`). |
@@ -121,63 +136,55 @@ Configuration is defined by standard HTML `<datalist>` elements placed inside th
121
136
  | `<option label="...">` | `string` | Optional descriptive label shown alongside the value. |
122
137
  | `<option>` children | `Node` | Optional image (`<img>`) prepended to the suggested value. |
123
138
 
124
- Datalists can be added, updated, or removed dynamically at runtime; `<rich-input>` observes changes via `slotchange` and `MutationObserver`.
139
+ Datalists and syntax attributes can be added, updated, or removed dynamically at runtime; `<rich-input>` observes changes via `attributeChangedCallback` and `MutationObserver`.
140
+
141
+ ### Configuring Operators, Combinators & Delimiters via JavaScript
142
+
143
+ In addition to HTML attributes, you can configure `operators`, `combinators`, and `delimiters` per instance or globally on the `RichInput` class (which sets the default for newly created instances that do not specify a local override):
144
+
145
+ ```javascript
146
+ import { RichInput } from '@bramus/rich-input';
147
+
148
+ // Set global defaults for newly created <rich-input> elements
149
+ RichInput.operators = ['-', '+'];
150
+ RichInput.combinators = ['AND', 'OR', 'NOT'];
151
+ RichInput.delimiters = ['()', '[]'];
152
+
153
+ // Or configure an individual instance via properties or methods
154
+ const input = document.querySelector('rich-input');
155
+ input.operators = ['-'];
156
+ input.combinators = ['AND', 'OR'];
157
+ input.delimiters = ['()'];
158
+
159
+ // Reset an instance or global setting back to defaults by assigning null
160
+ input.operators = null;
161
+ ```
125
162
 
126
163
  ---
127
164
 
128
- ## Rich Option Markup
165
+ ## Rich Option Markup (`<img>` Support)
129
166
 
130
- `<rich-input>` supports rich HTML markup inside `<option>` elements. For example, for record labels or artists, you can prepend a logo image:
167
+ Options inside a `<datalist>` can include an `<img>` element to display thumbnails, avatars, or record label logos in the autocomplete suggestions popover:
131
168
 
132
169
  ```html
133
- <rich-input placeholder="Search...">
134
- <datalist id="label" label="Record Label">
135
- <option value="Defected">
136
- <img src="assets/defected.jpg" height="50" width="50" alt="Defected Logo">
137
- Defected
138
- </option>
139
- <option value="House">
140
- <img src="assets/house.jpg" height="50" width="50" alt="House Logo">
141
- House
142
- </option>
143
- <option value="Keinemusik">
144
- <img src="assets/keinemusik.jpg" height="50" width="50" alt="Keinemusik Logo">
145
- Keinemusik
146
- </option>
147
- <option value="Kranky">
148
- <img src="assets/kranky.jpg" height="50" width="50" alt="Kranky Logo">
149
- Kranky
150
- </option>
151
- <option value="Madhouse Records">
152
- <img src="assets/madhouse-records.jpg" height="50" width="50" alt="Madhouse Records Logo">
153
- Madhouse Records
154
- </option>
155
- <option value="Ninja Tune">
156
- <img src="assets/ninja-tune.jpg" height="50" width="50" alt="Ninja Tune Logo">
157
- Ninja Tune
158
- </option>
159
- <option value="Warp Records">
160
- <img src="assets/warp-records.png" height="50" width="50" alt="Warp Records Logo">
161
- Warp Records
162
- </option>
163
- <option value="We Play House Recordings">
164
- <img src="assets/we-play-house-recordings.jpg" height="50" width="50" alt="We Play House Recordings Logo">
165
- We Play House Recordings
166
- </option>
167
- <option value="XL Recordings">
168
- <img src="assets/xl-recordings.jpg" height="50" width="50" alt="XL Recordings Logo">
169
- XL Recordings
170
- </option>
171
- </datalist>
172
- </rich-input>
170
+ <datalist id="label" label="Record Label">
171
+ <option value="We Play House Recordings">
172
+ <img src="assets/we-play-house-recordings.jpg" height="50" width="50" alt="WPH">
173
+ We Play House Recordings
174
+ </option>
175
+ <option value="Defected">
176
+ <img src="assets/defected.jpg" height="50" width="50" alt="Defected">
177
+ Defected
178
+ </option>
179
+ </datalist>
173
180
  ```
174
181
 
175
- 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):
182
+ When `<rich-input>` parses a `<datalist>`, it clones any `<img>` found inside `<option>` and renders it inside the corresponding suggestion row with `part="suggestion-image"`. You can style these images from your stylesheet using `::part(suggestion-image)`:
176
183
 
177
184
  ```css
178
185
  rich-input::part(suggestion-image) {
179
- width: 1em;
180
- height: 1em;
186
+ width: 2.5em;
187
+ height: 2.5em;
181
188
  border-radius: 50%;
182
189
  object-fit: cover;
183
190
  }
@@ -185,43 +192,22 @@ rich-input::part(suggestion-image) {
185
192
 
186
193
  ---
187
194
 
188
- ## Custom Leading Icon & Slots
189
-
190
- `<rich-input>` provides named slots to customize elements inside the control:
195
+ ## Customizing the Leading Icon via `::part(icon)`
191
196
 
192
- - **`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.
193
- - **`slot="trailing"`**: Add controls or elements after the clear button (e.g. submit button, voice input, keyboard shortcut badge).
197
+ By default, `<rich-input>` renders a leading search magnifying glass icon exposed as `::part(icon)`. You can customize or replace this icon using either the CSS `content` property (for example, to display an emoji) or the CSS `background` property (to display a custom SVG or image):
194
198
 
195
- ### Passing a Custom Leading Icon
196
-
197
- Provide your own SVG or image with `slot="leading"`:
199
+ ```css
200
+ /* Option 1: Show an emoji or text via the CSS content property */
201
+ rich-input::part(icon) {
202
+ content: "🎵";
203
+ }
198
204
 
199
- ```html
200
- <rich-input placeholder="Search music catalog...">
201
- <!-- Custom leading music icon -->
202
- <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">
203
- <path d="M9 18V5l12-2v13"></path>
204
- <circle cx="6" cy="18" r="3"></circle>
205
- <circle cx="18" cy="16" r="3"></circle>
206
- </svg>
207
-
208
- <datalist id="genre" label="Genre">
209
- <option value="House"></option>
210
- <option value="Techno"></option>
211
- </datalist>
212
- </rich-input>
205
+ /* Option 2: Show a custom SVG/image via the CSS background property */
206
+ rich-input::part(icon) {
207
+ background: url("music-note.svg") no-repeat center / contain;
208
+ }
213
209
  ```
214
210
 
215
- 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.
216
-
217
- ### Available Slots
218
-
219
- | Slot Name | Description |
220
- |---|---|
221
- | `leading` | Custom leading icon or content. Defaults to the search magnifying glass icon (`::part(icon)`). |
222
- | `trailing` | Custom content rendered after the clear button. |
223
- | *(default)* | Unnamed slot where `<datalist>` configuration elements are placed (visually hidden). |
224
-
225
211
  ---
226
212
 
227
213
  ## The OpaqueRange API
@@ -275,6 +261,24 @@ Values corresponding to configured keywords are registered into the global `CSS.
275
261
  color: oklch(0.32 0.14 320);
276
262
  }
277
263
 
264
+ /* Generic prefix highlight for operators (e.g. "-" in "-style:Acid") */
265
+ ::highlight(rich-input-operator) {
266
+ color: #e11d48;
267
+ text-shadow: 0 0 1px rgba(225, 29, 72, 0.2);
268
+ }
269
+
270
+ /* Generic highlight for combinators (e.g. "OR" in "artist:Aphex OR label:Defected") */
271
+ ::highlight(rich-input-combinator) {
272
+ color: #7c3aed;
273
+ text-shadow: 0 0 1px rgba(124, 58, 237, 0.2);
274
+ }
275
+
276
+ /* Generic highlight for delimiters (e.g. "(" and ")") */
277
+ ::highlight(rich-input-delimiter) {
278
+ color: #0284c7;
279
+ text-shadow: 0 0 1px rgba(2, 132, 199, 0.25);
280
+ }
281
+
278
282
  /* Generic prefix highlight for keyword labels (e.g. "label:", "year:") */
279
283
  ::highlight(rich-input-keyword) {
280
284
  color: #64748b;
@@ -345,7 +349,7 @@ rich-input::part(suggestion-item-active) {
345
349
  |---|---|
346
350
  | `::part(control)` | The wrapper container enclosing the search icon, input, and clear button |
347
351
  | `::part(input)` | The internal native `<input type="text">` |
348
- | `::part(icon)` | The default leading search icon SVG (fallback in `slot="leading"`) |
352
+ | `::part(icon)` | The leading icon element (defaults to a search magnifying glass, customizable via CSS `content` or `background`) |
349
353
  | `::part(clear-button)` | The clear button (visible when text is present) |
350
354
  | `::part(popover)` | The autocomplete popover container |
351
355
  | `::part(suggestions-header)` | The header bar at the top of the popover |
@@ -354,6 +358,7 @@ rich-input::part(suggestion-item-active) {
354
358
  | `::part(suggestion-item-active)` | The currently focused / hovered suggestion item |
355
359
  | `::part(suggestion-item-selected)` | The suggestion item matching the value currently echoed in the input |
356
360
  | `::part(suggestion-keyword)` | Keyword name element in suggestion items |
361
+ | `::part(suggestion-combinator)` | Combinator name element in suggestion items |
357
362
  | `::part(suggestion-value)` | Value element in suggestion items |
358
363
  | `::part(suggestion-content)` | The content container inside each suggestion item |
359
364
  | `::part(suggestion-image)` | Image or icon element rendered inside rich suggestion items |
@@ -365,6 +370,12 @@ rich-input::part(suggestion-item-active) {
365
370
  ### Properties
366
371
 
367
372
  - `value` (`string`): Gets or sets the search input value. Updates highlights and form value automatically.
373
+ - `operators` (`string[] | string`): Gets or sets the prefix operators (e.g. `['-', '~', '+']` or `'- ~ +'`) for this instance. Defaults to `RichInput.operators` (`['-']`). Setting to `null` clears the instance override and falls back to the global configuration.
374
+ - `RichInput.operators` (`string[] | string`): Static getter and setter to configure global default operators for all instances without a local override.
375
+ - `combinators` (`string[] | string`): Gets or sets the query combinators (e.g. `['AND', 'OR', 'NOT']` or `'AND OR NOT'`) for this instance. Defaults to `RichInput.combinators` (`[]`). Setting to `null` clears the instance override and falls back to the global configuration.
376
+ - `RichInput.combinators` (`string[] | string`): Static getter and setter to configure global default combinators for all instances without a local override.
377
+ - `delimiters` (`string[] | string`): Gets or sets the delimiter pairs (e.g. `['()', '{}', '[]']` or `'{} () []'`) for this instance. Defaults to `RichInput.delimiters` (`['()']`). Setting to `null` clears the instance override and falls back to the global configuration.
378
+ - `RichInput.delimiters` (`string[] | string`): Static getter and setter to configure global default delimiter pairs for all instances without a local override.
368
379
  - `placeholder` (`string`): Gets or sets the input placeholder text.
369
380
  - `disabled` (`boolean`): Disables or enables the input control.
370
381
  - `name` (`string`): Form field name when submitted inside a `<form>`.
@@ -372,19 +383,30 @@ rich-input::part(suggestion-item-active) {
372
383
 
373
384
  ### Methods
374
385
 
375
- - `getParsedQuery()`: Returns a parsed object representing the search query:
386
+ - `getParsedQuery()`: Returns a parsed object containing the `raw` query string and an ordered `tokens` array (`keyword`, `combinator`, `delimiter`, `text`, and `whitespace` tokens):
376
387
  ```json
377
388
  {
378
- "raw": "label:\"We Play House Recordings\" year:2026 chicago house",
379
- "text": "chicago house",
380
- "keywords": {
381
- "label": ["We Play House Recordings"],
382
- "year": ["2026"]
383
- },
384
- "tokens": [...]
389
+ "raw": "(artist:\"Aphex Twin\" OR year:2026) AND -style:\"Acid House\"",
390
+ "tokens": [
391
+ { "type": "delimiter", "raw": "(", "delimiter": "(", "pair": "()", "role": "open", "start": 0, "end": 1 },
392
+ { "type": "keyword", "operator": null, "keyword": "artist", "innerValue": "Aphex Twin", "start": 1, "end": 20 },
393
+ { "type": "whitespace", "raw": " ", "start": 20, "end": 21 },
394
+ { "type": "combinator", "raw": "OR", "combinator": "OR", "start": 21, "end": 23 },
395
+ { "type": "whitespace", "raw": " ", "start": 23, "end": 24 },
396
+ { "type": "keyword", "operator": null, "keyword": "year", "innerValue": "2026", "start": 24, "end": 33 },
397
+ { "type": "delimiter", "raw": ")", "delimiter": ")", "pair": "()", "role": "close", "start": 33, "end": 34 },
398
+ { "type": "whitespace", "raw": " ", "start": 34, "end": 35 },
399
+ { "type": "combinator", "raw": "AND", "combinator": "AND", "start": 35, "end": 38 },
400
+ { "type": "whitespace", "raw": " ", "start": 38, "end": 39 },
401
+ { "type": "keyword", "operator": "-", "keyword": "style", "innerValue": "Acid House", "start": 39, "end": 58 }
402
+ ]
385
403
  }
386
404
  ```
387
405
  - `getKeywords()`: Returns an array of configured keyword definitions from the datalists.
406
+ - `getOperators()` / `setOperators(operators)`: Gets or sets the operators for this instance (or globally via `RichInput.getOperators()` / `RichInput.setOperators(operators)`).
407
+ - `getCombinators()` / `setCombinators(combinators)`: Gets or sets the combinators for this instance (or globally via `RichInput.getCombinators()` / `RichInput.setCombinators(combinators)`).
408
+ - `getDelimiters()` / `setDelimiters(delimiters)`: Gets or sets the delimiters for this instance (or globally via `RichInput.getDelimiters()` / `RichInput.setDelimiters(delimiters)`).
409
+ - `getActiveValueRanges()` / `getActiveKeywordRanges()` / `getActiveOperatorRanges()` / `getActiveCombinatorRanges()` / `getActiveDelimiterRanges()` / `getActiveInvalidRanges()`: Returns the active highlight range descriptors for each token category.
388
410
  - `focus(options)`: Focuses the internal input.
389
411
  - `blur()`: Removes focus from the internal input.
390
412
  - `select()`: Selects all text inside the input.
@@ -396,7 +418,7 @@ rich-input::part(suggestion-item-active) {
396
418
  - `change`: Dispatched on blur or when a search change is committed.
397
419
  - `search`: Dispatched when the user presses `Enter` with suggestions closed.
398
420
  - `rich-input-select`: Dispatched when an autocomplete suggestion is selected.
399
- - `event.detail`: `{ type, keyword, value, label, query }`
421
+ - `event.detail`: `{ type, operator, keyword, value, label, query }`
400
422
 
401
423
  ---
402
424