elastic-input 0.1.0 → 0.3.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
@@ -4,7 +4,7 @@ A syntax-aware smart autocomplete input for building structured queries. Support
4
4
 
5
5
  Built with React functional components and hooks (compatible with React 16.8+), zero runtime dependencies beyond React/ReactDOM, and fully inline-styled (no CSS imports required).
6
6
 
7
- Vibe-coded as a proof of concept with Claude Code
7
+ **[Live Demo](https://krtools.github.io/elastic-input/)**
8
8
 
9
9
  ## Features
10
10
 
@@ -19,6 +19,12 @@ Vibe-coded as a proof of concept with Claude Code
19
19
  - **Fully configurable** — colors, structural styles, fonts, and layout are all customizable via props
20
20
  - **Dark mode ready** — ships with `DARK_COLORS` and `DARK_STYLES` presets
21
21
 
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm install elastic-input
26
+ ```
27
+
22
28
  ## Quick Start
23
29
 
24
30
  ```tsx
@@ -30,8 +36,8 @@ const fields: FieldConfig[] = [
30
36
  name: 'status',
31
37
  label: 'Status',
32
38
  type: 'string',
33
- suggestions: ['active', 'inactive', 'pending'],
34
39
  description: 'Account status',
40
+ placeholder: 'Search statuses...',
35
41
  },
36
42
  {
37
43
  name: 'created',
@@ -103,33 +109,36 @@ Implicit AND is supported — `status:active level:ERROR` is equivalent to `stat
103
109
  | `onValidationChange` | `(errors) => void` | — | Called when validation errors change |
104
110
  | `value` | `string` | — | Controlled input value |
105
111
  | `defaultValue` | `string` | — | Initial uncontrolled value |
106
- | `savedSearches` | `SavedSearch[] \| () => Promise<SavedSearch[]>` | — | Saved search definitions (sync or async) |
107
- | `searchHistory` | `HistoryEntry[] \| () => Promise<HistoryEntry[]>` | — | Search history entries (sync or async) |
108
- | `fetchSuggestions` | `(field, partial) => Promise<SuggestionItem[]>` | — | Async suggestion provider for field values |
112
+ | `savedSearches` | `SavedSearch[] \| (partial) => Promise<SavedSearch[]>` | — | Saved search definitions (sync array or async callback with partial) |
113
+ | `searchHistory` | `HistoryEntry[] \| (partial) => Promise<HistoryEntry[]>` | — | Search history entries (sync array or async callback with partial) |
114
+ | `fetchSuggestions` | `(field, partial) => Promise<SuggestionItem[]>` | — | Async suggestion provider for field values (called for all non-boolean fields) |
109
115
  | `colors` | `ColorConfig` | `DEFAULT_COLORS` | Syntax highlighting and UI colors |
110
116
  | `styles` | `StyleConfig` | `DEFAULT_STYLES` | Structural/layout style overrides |
111
117
  | `placeholder` | `string` | `"Search..."` | Placeholder text |
112
118
  | `className` | `string` | — | CSS class for the outer container |
119
+ | `classNames` | `ClassNamesConfig` | — | Custom CSS classes for sub-elements (editor, dropdown, tokens, etc.) |
113
120
  | `style` | `CSSProperties` | — | Inline styles for the outer container |
114
121
  | `inputRef` | `(api) => void` | — | Receive an imperative API handle |
115
- | `dropdown` | `DropdownConfig` | `{}` | Dropdown behavior and rendering (mode, triggers, renderers) |
122
+ | `dropdown` | `DropdownConfig` | `{}` | Dropdown behavior and rendering (open, triggers, renderers) |
116
123
  | `features` | `FeaturesConfig` | `{}` | Feature toggles (multiline, smartSelectAll, expandSelection, wildcardWrap, savedSearches, historySearch) |
117
124
  | `onKeyDown` | `(e) => void` | — | Called before internal keyboard handling |
118
125
  | `onFocus` | `() => void` | — | Called when the input gains focus |
119
126
  | `onBlur` | `() => void` | — | Called when the input loses focus |
120
127
  | `onTab` | `(context) => TabActionResult` | — | Override Tab key behavior (accept/blur/submit) |
121
128
  | `validateValue` | `(ctx) => ValidateReturn` | — | Custom validation for all value types |
129
+ | `parseDate` | `(value: string) => Date \| null` | — | Custom date parser for validation and date picker init |
122
130
 
123
131
  ## Field Configuration
124
132
 
125
133
  ```typescript
126
134
  interface FieldConfig {
127
- name: string; // Field identifier used in queries
128
- label?: string; // Display label (used in autocomplete)
129
- type: FieldType; // 'string' | 'number' | 'date' | 'boolean' | 'ip'
130
- suggestions?: string[];// Autocomplete values (any field type can have suggestions)
131
- operators?: string[]; // Allowed operators (future use)
132
- description?: string; // Shown in autocomplete dropdown
135
+ name: string; // Field identifier used in queries
136
+ label?: string; // Display label (used in autocomplete)
137
+ type: FieldType; // 'string' | 'number' | 'date' | 'boolean' | 'ip'
138
+ aliases?: string[]; // Alternative names that resolve to this field
139
+ operators?: string[]; // Allowed operators (future use)
140
+ description?: string; // Shown in autocomplete dropdown
141
+ placeholder?: string | false; // Hint shown while typing a value (false to suppress)
133
142
  }
134
143
  ```
135
144
 
@@ -233,12 +242,15 @@ const savedSearches = [
233
242
 
234
243
  Type `#` in the input to see saved search suggestions. Selecting one replaces the `#token` with the saved query text.
235
244
 
236
- Supports async loading:
245
+ Supports async loading with per-keystroke filtering:
237
246
 
238
247
  ```tsx
239
248
  <ElasticInput
240
249
  fields={fields}
241
- savedSearches={() => fetch('/api/saved-searches').then(r => r.json())}
250
+ savedSearches={async (partial) => {
251
+ const res = await fetch(`/api/saved-searches?q=${partial}`);
252
+ return res.json();
253
+ }}
242
254
  />
243
255
  ```
244
256
 
@@ -260,9 +272,21 @@ const history = [
260
272
 
261
273
  Type `!` to see history suggestions. Selecting one inserts the query (wrapped in parentheses if it contains boolean operators).
262
274
 
275
+ Supports async loading with per-keystroke filtering:
276
+
277
+ ```tsx
278
+ <ElasticInput
279
+ fields={fields}
280
+ searchHistory={async (partial) => {
281
+ const res = await fetch(`/api/history?q=${partial}`);
282
+ return res.json();
283
+ }}
284
+ />
285
+ ```
286
+
263
287
  ## Async Suggestions
264
288
 
265
- Provide dynamic suggestions for field values:
289
+ Provide dynamic suggestions for field values. Called for all non-boolean field value contexts when provided:
266
290
 
267
291
  ```tsx
268
292
  <ElasticInput
@@ -395,7 +419,7 @@ const ast = parser.parse();
395
419
 
396
420
  // Validate
397
421
  const fields: FieldConfig[] = [
398
- { name: 'status', type: 'string', suggestions: ['active', 'inactive'] },
422
+ { name: 'status', type: 'string' },
399
423
  { name: 'price', type: 'number' },
400
424
  ];
401
425
  const validator = new Validator(fields);
@@ -12,6 +12,10 @@ export interface AutocompleteResult {
12
12
  export interface AutocompleteOptions {
13
13
  showSavedSearchHint?: boolean;
14
14
  showHistoryHint?: boolean;
15
+ /** Set to true when a savedSearches callback is provided (for hints even when no sync data). */
16
+ hasSavedSearchProvider?: boolean;
17
+ /** Set to true when a searchHistory callback is provided (for hints even when no sync data). */
18
+ hasHistoryProvider?: boolean;
15
19
  }
16
20
  export declare class AutocompleteEngine {
17
21
  private fields;
@@ -33,4 +37,10 @@ export declare class AutocompleteEngine {
33
37
  private getSpecialHints;
34
38
  private getSavedSearchSuggestions;
35
39
  private getHistorySuggestions;
40
+ /**
41
+ * Wraps a history query in parens if the top-level AST node is a BooleanExpr
42
+ * (explicit AND/OR or implicit adjacency). Already-grouped expressions
43
+ * like -(a AND b) or (a OR b)^2 don't need extra wrapping.
44
+ */
45
+ static wrapHistoryQuery(query: string): string;
36
46
  }
@@ -19,6 +19,12 @@ interface AutocompleteDropdownProps {
19
19
  renderSavedSearchItem?: (search: SavedSearch, isSelected: boolean) => React.ReactNode | null | undefined;
20
20
  renderDropdownHeader?: (context: CursorContext) => React.ReactNode | null | undefined;
21
21
  cursorContext?: CursorContext | null;
22
+ /** Custom class names for dropdown elements. */
23
+ classNames?: {
24
+ dropdown?: string;
25
+ dropdownHeader?: string;
26
+ dropdownItem?: string;
27
+ };
22
28
  }
23
- export declare function AutocompleteDropdown({ suggestions, selectedIndex, onSelect, position, colors, styles, visible, fixedWidth, renderHistoryItem, renderSavedSearchItem, renderDropdownHeader, cursorContext, }: AutocompleteDropdownProps): React.ReactPortal | null;
29
+ export declare function AutocompleteDropdown({ suggestions, selectedIndex, onSelect, position, colors, styles, visible, fixedWidth, renderHistoryItem, renderSavedSearchItem, renderDropdownHeader, cursorContext, classNames, }: AutocompleteDropdownProps): React.ReactPortal | null;
24
30
  export {};
@@ -11,6 +11,8 @@ interface DateRangePickerProps {
11
11
  label: string;
12
12
  value: string;
13
13
  }[];
14
+ /** Custom class name for the date picker container. */
15
+ className?: string;
14
16
  }
15
- export declare function DateRangePicker({ onSelect, colors, styles: styleConfig, initialMode, initialStart, initialEnd, presets: presetsProp }: DateRangePickerProps): React.JSX.Element;
17
+ export declare function DateRangePicker({ onSelect, colors, styles: styleConfig, initialMode, initialStart, initialEnd, presets: presetsProp, className }: DateRangePickerProps): React.JSX.Element;
16
18
  export {};
@@ -16,7 +16,7 @@ export declare function computeDatePickerInit(context: {
16
16
  token?: {
17
17
  value: string;
18
18
  };
19
- }): DatePickerInit | null;
19
+ }, parseDateFn?: (value: string) => Date | null): DatePickerInit | null;
20
20
  /**
21
21
  * Determine whether the date picker needs to be unmounted and remounted.
22
22
  * This is necessary when initial state changes because DateRangePicker
@@ -3,5 +3,7 @@ import { ColorConfig } from '../types';
3
3
 
4
4
  export interface HighlightOptions {
5
5
  cursorOffset?: number;
6
+ /** Custom class name appended to every token span. */
7
+ tokenClassName?: string;
6
8
  }
7
9
  export declare function buildHighlightedHTML(tokens: Token[], colorConfig?: ColorConfig, options?: HighlightOptions): string;
@@ -8,6 +8,11 @@ interface ValidationSquigglesProps {
8
8
  colors?: ColorConfig;
9
9
  styles?: StyleConfig;
10
10
  containerRef?: HTMLDivElement | null;
11
+ /** Custom class names for squiggly and tooltip elements. */
12
+ classNames?: {
13
+ squiggly?: string;
14
+ tooltip?: string;
15
+ };
11
16
  }
12
- export declare function ValidationSquiggles({ errors, editorRef, cursorOffset, colors, styles, containerRef }: ValidationSquigglesProps): React.JSX.Element | null;
17
+ export declare function ValidationSquiggles({ errors, editorRef, cursorOffset, colors, styles, containerRef, classNames }: ValidationSquigglesProps): React.JSX.Element | null;
13
18
  export {};