elastic-input 0.3.3 → 0.3.5
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 +43 -0
- package/dist/components/AutocompleteDropdown.d.ts +3 -1
- package/dist/components/HighlightedContent.d.ts +3 -1
- package/dist/elastic-input.es.js +331 -30
- package/dist/index.d.ts +2 -0
- package/dist/types.d.ts +54 -15
- package/dist/utils/formatQuery.d.ts +14 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -127,6 +127,8 @@ Implicit AND is supported — `status:active level:ERROR` is equivalent to `stat
|
|
|
127
127
|
| `onTab` | `(context) => TabActionResult` | — | Override Tab key behavior (accept/blur/submit) |
|
|
128
128
|
| `validateValue` | `(ctx) => ValidateReturn` | — | Custom validation for all value types |
|
|
129
129
|
| `parseDate` | `(value: string) => Date \| null` | — | Custom date parser for validation and date picker init |
|
|
130
|
+
| `plainModeLength` | `number` | — | Character count at which highlighting, autocomplete, and validation are disabled for performance |
|
|
131
|
+
| `interceptPaste` | `(text, event) => string \| null \| Promise<…>` | — | Transform or cancel pasted text before insertion; supports async |
|
|
130
132
|
|
|
131
133
|
## Field Configuration
|
|
132
134
|
|
|
@@ -348,6 +350,14 @@ const myColors: ColorConfig = {
|
|
|
348
350
|
cursor: '#1f2328',
|
|
349
351
|
dropdownSelected: '#0969da',
|
|
350
352
|
dropdownHover: '#f6f8fa',
|
|
353
|
+
// Per-field-type value colors (overrides fieldValue for typed fields)
|
|
354
|
+
valueTypes: {
|
|
355
|
+
string: '#0550ae',
|
|
356
|
+
number: '#0a3069',
|
|
357
|
+
date: '#8250df',
|
|
358
|
+
boolean: '#cf222e',
|
|
359
|
+
ip: '#116329',
|
|
360
|
+
},
|
|
351
361
|
};
|
|
352
362
|
```
|
|
353
363
|
|
|
@@ -445,6 +455,39 @@ Pass `HighlightOptions` for matched-paren highlighting:
|
|
|
445
455
|
buildHighlightedHTML(tokens, DEFAULT_COLORS, { cursorOffset: 5 });
|
|
446
456
|
```
|
|
447
457
|
|
|
458
|
+
## Query Formatting
|
|
459
|
+
|
|
460
|
+
Pretty-print messy or minified queries with `formatQuery` — a pure function (no React or DOM required):
|
|
461
|
+
|
|
462
|
+
```typescript
|
|
463
|
+
import { formatQuery } from 'elastic-input';
|
|
464
|
+
|
|
465
|
+
formatQuery('(status:active OR status:lead) AND deal_value:>5000 AND NOT tags:churned');
|
|
466
|
+
// (status:active OR status:lead)
|
|
467
|
+
// AND deal_value:>5000
|
|
468
|
+
// AND NOT tags:churned
|
|
469
|
+
|
|
470
|
+
formatQuery('( (status:active AND deal_value:>10000) OR (status:lead AND tags:enterprise) ) AND created:[2024-01-01 TO 2024-12-31]');
|
|
471
|
+
// (
|
|
472
|
+
// status:active AND deal_value:>10000
|
|
473
|
+
// OR status:lead AND tags:enterprise
|
|
474
|
+
// )
|
|
475
|
+
// AND created:[2024-01-01 TO 2024-12-31]
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
Accepts a raw query string or a pre-parsed `ASTNode`. Options control line-break threshold and indentation:
|
|
479
|
+
|
|
480
|
+
```typescript
|
|
481
|
+
import type { FormatQueryOptions } from 'elastic-input';
|
|
482
|
+
|
|
483
|
+
formatQuery(query, { maxLineLength: 80, indent: '\t' });
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
| Option | Type | Default | Description |
|
|
487
|
+
|--------|------|---------|-------------|
|
|
488
|
+
| `maxLineLength` | `number` | `60` | Lines shorter than this stay inline |
|
|
489
|
+
| `indent` | `string` | `' '` (2 spaces) | Indent string per nesting level |
|
|
490
|
+
|
|
448
491
|
## Requirements
|
|
449
492
|
|
|
450
493
|
### Runtime (Browser)
|
|
@@ -19,6 +19,8 @@ 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
|
+
/** Ref callback to expose the dropdown list element for page-size calculations. */
|
|
23
|
+
listRefCallback?: (el: HTMLDivElement | null) => void;
|
|
22
24
|
/** Custom class names for dropdown elements. */
|
|
23
25
|
classNames?: {
|
|
24
26
|
dropdown?: string;
|
|
@@ -26,5 +28,5 @@ interface AutocompleteDropdownProps {
|
|
|
26
28
|
dropdownItem?: string;
|
|
27
29
|
};
|
|
28
30
|
}
|
|
29
|
-
export declare function AutocompleteDropdown({ suggestions, selectedIndex, onSelect, position, colors, styles, visible, fixedWidth, renderHistoryItem, renderSavedSearchItem, renderDropdownHeader, cursorContext, classNames, }: AutocompleteDropdownProps): React.ReactPortal | null;
|
|
31
|
+
export declare function AutocompleteDropdown({ suggestions, selectedIndex, onSelect, position, colors, styles, visible, fixedWidth, renderHistoryItem, renderSavedSearchItem, renderDropdownHeader, cursorContext, listRefCallback, classNames, }: AutocompleteDropdownProps): React.ReactPortal | null;
|
|
30
32
|
export {};
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { Token } from '../lexer/tokens';
|
|
2
|
-
import { ColorConfig } from '../types';
|
|
2
|
+
import { ColorConfig, FieldType } from '../types';
|
|
3
3
|
|
|
4
4
|
export interface HighlightOptions {
|
|
5
5
|
cursorOffset?: number;
|
|
6
6
|
/** Custom class name appended to every token span. */
|
|
7
7
|
tokenClassName?: string;
|
|
8
|
+
/** Field lookup map for per-type value coloring (keyed by field name and aliases). */
|
|
9
|
+
fieldTypeMap?: Map<string, FieldType>;
|
|
8
10
|
}
|
|
9
11
|
export declare function buildHighlightedHTML(tokens: Token[], colorConfig?: ColorConfig, options?: HighlightOptions): string;
|
package/dist/elastic-input.es.js
CHANGED
|
@@ -1180,7 +1180,8 @@ const DEFAULT_COLORS = {
|
|
|
1180
1180
|
regexAlternation: "#8250df",
|
|
1181
1181
|
regexText: "#0a3069",
|
|
1182
1182
|
matchedParenBg: "#fff3cd",
|
|
1183
|
-
warning: "#d4a72c"
|
|
1183
|
+
warning: "#d4a72c",
|
|
1184
|
+
valueTypes: {}
|
|
1184
1185
|
};
|
|
1185
1186
|
const DARK_COLORS = {
|
|
1186
1187
|
fieldName: "#79c0ff",
|
|
@@ -1208,7 +1209,8 @@ const DARK_COLORS = {
|
|
|
1208
1209
|
regexAlternation: "#d2a8ff",
|
|
1209
1210
|
regexText: "#a5d6ff",
|
|
1210
1211
|
matchedParenBg: "#3d3222",
|
|
1211
|
-
warning: "#e3b341"
|
|
1212
|
+
warning: "#e3b341",
|
|
1213
|
+
valueTypes: {}
|
|
1212
1214
|
};
|
|
1213
1215
|
const DEFAULT_STYLES = {
|
|
1214
1216
|
fontFamily: "'SF Mono', 'Fira Code', 'Fira Mono', Menlo, Consolas, monospace",
|
|
@@ -2623,9 +2625,60 @@ function buildHighlightedHTML(tokens, colorConfig, options) {
|
|
|
2623
2625
|
const colors = mergeColors(colorConfig);
|
|
2624
2626
|
if (tokens.length === 0) return "";
|
|
2625
2627
|
const parenMatch = (options == null ? void 0 : options.cursorOffset) !== void 0 ? findMatchingParen(tokens, options.cursorOffset) : null;
|
|
2626
|
-
|
|
2628
|
+
const valueTypes = colorConfig == null ? void 0 : colorConfig.valueTypes;
|
|
2629
|
+
const fieldTypeMap = options == null ? void 0 : options.fieldTypeMap;
|
|
2630
|
+
let tokenFieldTypes;
|
|
2631
|
+
if (valueTypes && fieldTypeMap) {
|
|
2632
|
+
tokenFieldTypes = new Array(tokens.length);
|
|
2633
|
+
let pendingFieldName;
|
|
2634
|
+
let sawColon = false;
|
|
2635
|
+
const groupStack = [];
|
|
2636
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
2637
|
+
const t = tokens[i];
|
|
2638
|
+
if (t.type === TokenType.FIELD_NAME) {
|
|
2639
|
+
pendingFieldName = t.value;
|
|
2640
|
+
sawColon = false;
|
|
2641
|
+
} else if (t.type === TokenType.COLON && pendingFieldName) {
|
|
2642
|
+
sawColon = true;
|
|
2643
|
+
} else if (t.type === TokenType.WHITESPACE) ;
|
|
2644
|
+
else if (t.type === TokenType.LPAREN) {
|
|
2645
|
+
if (sawColon && pendingFieldName) {
|
|
2646
|
+
groupStack.push(fieldTypeMap.get(pendingFieldName.toLowerCase()));
|
|
2647
|
+
} else {
|
|
2648
|
+
groupStack.push(void 0);
|
|
2649
|
+
}
|
|
2650
|
+
pendingFieldName = void 0;
|
|
2651
|
+
sawColon = false;
|
|
2652
|
+
} else if (t.type === TokenType.RPAREN) {
|
|
2653
|
+
groupStack.pop();
|
|
2654
|
+
pendingFieldName = void 0;
|
|
2655
|
+
sawColon = false;
|
|
2656
|
+
} else if (t.type === TokenType.VALUE || t.type === TokenType.QUOTED_VALUE || t.type === TokenType.RANGE || t.type === TokenType.REGEX || t.type === TokenType.WILDCARD) {
|
|
2657
|
+
if (sawColon && pendingFieldName) {
|
|
2658
|
+
tokenFieldTypes[i] = fieldTypeMap.get(pendingFieldName.toLowerCase());
|
|
2659
|
+
} else if (groupStack.length > 0) {
|
|
2660
|
+
tokenFieldTypes[i] = groupStack[groupStack.length - 1];
|
|
2661
|
+
}
|
|
2662
|
+
pendingFieldName = void 0;
|
|
2663
|
+
sawColon = false;
|
|
2664
|
+
} else if (t.type === TokenType.AND || t.type === TokenType.OR || t.type === TokenType.NOT) {
|
|
2665
|
+
pendingFieldName = void 0;
|
|
2666
|
+
sawColon = false;
|
|
2667
|
+
} else {
|
|
2668
|
+
pendingFieldName = void 0;
|
|
2669
|
+
sawColon = false;
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
return tokens.map((token, tokenIndex) => {
|
|
2627
2674
|
const colorKey = TOKEN_COLOR_MAP[token.type] || "text";
|
|
2628
|
-
|
|
2675
|
+
let color = colors[colorKey] || colors.text;
|
|
2676
|
+
if (valueTypes && tokenFieldTypes) {
|
|
2677
|
+
const ft = tokenFieldTypes[tokenIndex];
|
|
2678
|
+
if (ft && valueTypes[ft]) {
|
|
2679
|
+
color = valueTypes[ft];
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2629
2682
|
const escapedValue = escapeHTML(token.value);
|
|
2630
2683
|
if (token.type === TokenType.WHITESPACE) {
|
|
2631
2684
|
return escapedValue.replace(/\n/g, "<br>");
|
|
@@ -2696,6 +2749,7 @@ function AutocompleteDropdown({
|
|
|
2696
2749
|
renderSavedSearchItem,
|
|
2697
2750
|
renderDropdownHeader,
|
|
2698
2751
|
cursorContext,
|
|
2752
|
+
listRefCallback,
|
|
2699
2753
|
classNames
|
|
2700
2754
|
}) {
|
|
2701
2755
|
const portalRef = React.useRef(null);
|
|
@@ -2731,7 +2785,10 @@ function AutocompleteDropdown({
|
|
|
2731
2785
|
left: `${position.left}px`,
|
|
2732
2786
|
...fixedWidth != null ? { width: `${fixedWidth}px`, minWidth: "unset", maxWidth: "unset" } : {}
|
|
2733
2787
|
};
|
|
2734
|
-
const content = /* @__PURE__ */ React.createElement("div", { className: cx("ei-dropdown", classNames == null ? void 0 : classNames.dropdown), style: dropdownStyle, ref:
|
|
2788
|
+
const content = /* @__PURE__ */ React.createElement("div", { className: cx("ei-dropdown", classNames == null ? void 0 : classNames.dropdown), style: dropdownStyle, ref: (el) => {
|
|
2789
|
+
listRef.current = el;
|
|
2790
|
+
listRefCallback == null ? void 0 : listRefCallback(el);
|
|
2791
|
+
}, onMouseDown: (e) => e.preventDefault() }, hasHeader && /* @__PURE__ */ React.createElement("div", { className: cx("ei-dropdown-header", classNames == null ? void 0 : classNames.dropdownHeader), style: {
|
|
2735
2792
|
padding: mergedStyles.dropdownItemPadding || "4px 10px",
|
|
2736
2793
|
fontSize: "11px",
|
|
2737
2794
|
color: mergedColors.placeholder,
|
|
@@ -3725,7 +3782,9 @@ function ElasticInput(props) {
|
|
|
3725
3782
|
onBlur: onBlurProp,
|
|
3726
3783
|
onTab: onTabProp,
|
|
3727
3784
|
validateValue,
|
|
3728
|
-
parseDate: parseDateProp
|
|
3785
|
+
parseDate: parseDateProp,
|
|
3786
|
+
plainModeLength,
|
|
3787
|
+
interceptPaste
|
|
3729
3788
|
} = props;
|
|
3730
3789
|
const dropdownOpen = (dropdownConfig == null ? void 0 : dropdownConfig.open) ?? (dropdownConfig == null ? void 0 : dropdownConfig.mode) ?? "always";
|
|
3731
3790
|
const dropdownOpenIsCallback = typeof dropdownOpen === "function";
|
|
@@ -3747,6 +3806,8 @@ function ElasticInput(props) {
|
|
|
3747
3806
|
const renderHistoryItem = dropdownConfig == null ? void 0 : dropdownConfig.renderHistoryItem;
|
|
3748
3807
|
const renderSavedSearchItem = dropdownConfig == null ? void 0 : dropdownConfig.renderSavedSearchItem;
|
|
3749
3808
|
const renderDropdownHeader = dropdownConfig == null ? void 0 : dropdownConfig.renderHeader;
|
|
3809
|
+
const autoSelect = (dropdownConfig == null ? void 0 : dropdownConfig.autoSelect) ?? false;
|
|
3810
|
+
const homeEndKeys = (dropdownConfig == null ? void 0 : dropdownConfig.homeEndKeys) ?? false;
|
|
3750
3811
|
const multiline = (featuresConfig == null ? void 0 : featuresConfig.multiline) !== false;
|
|
3751
3812
|
const smartSelectAll = (featuresConfig == null ? void 0 : featuresConfig.smartSelectAll) ?? false;
|
|
3752
3813
|
const expandSelection = (featuresConfig == null ? void 0 : featuresConfig.expandSelection) ?? false;
|
|
@@ -3772,6 +3833,16 @@ function ElasticInput(props) {
|
|
|
3772
3833
|
cancelled = true;
|
|
3773
3834
|
};
|
|
3774
3835
|
}, [fieldsProp]);
|
|
3836
|
+
const fieldTypeMap = React.useMemo(() => {
|
|
3837
|
+
const map = /* @__PURE__ */ new Map();
|
|
3838
|
+
for (const f of resolvedFields) {
|
|
3839
|
+
map.set(f.name.toLowerCase(), f.type);
|
|
3840
|
+
if (f.aliases) {
|
|
3841
|
+
for (const a of f.aliases) map.set(a.toLowerCase(), f.type);
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3844
|
+
return map;
|
|
3845
|
+
}, [resolvedFields]);
|
|
3775
3846
|
const editorRef = React.useRef(null);
|
|
3776
3847
|
const [editorEl, setEditorEl] = React.useState(null);
|
|
3777
3848
|
const editorRefCallback = React.useCallback((el) => {
|
|
@@ -3779,9 +3850,11 @@ function ElasticInput(props) {
|
|
|
3779
3850
|
setEditorEl(el);
|
|
3780
3851
|
}, []);
|
|
3781
3852
|
const containerRef = React.useRef(null);
|
|
3853
|
+
const dropdownListRef = React.useRef(null);
|
|
3782
3854
|
const currentValueRef = React.useRef(value || defaultValue || "");
|
|
3783
3855
|
const debounceTimerRef = React.useRef(null);
|
|
3784
3856
|
const isComposingRef = React.useRef(false);
|
|
3857
|
+
const keyConsumedByDropdownRef = React.useRef(false);
|
|
3785
3858
|
const undoStackRef = React.useRef(new UndoStack());
|
|
3786
3859
|
const typingGroupTimerRef = React.useRef(null);
|
|
3787
3860
|
const abortControllerRef = React.useRef(null);
|
|
@@ -3818,6 +3891,9 @@ function ElasticInput(props) {
|
|
|
3818
3891
|
const [validationErrors, setValidationErrors] = React.useState([]);
|
|
3819
3892
|
const [isFocused, setIsFocused] = React.useState(false);
|
|
3820
3893
|
const [isEmpty, setIsEmpty] = React.useState(!currentValueRef.current);
|
|
3894
|
+
const [isPlainMode, setIsPlainMode] = React.useState(
|
|
3895
|
+
plainModeLength != null && currentValueRef.current.length >= plainModeLength
|
|
3896
|
+
);
|
|
3821
3897
|
const [cursorOffset, setCursorOffset] = React.useState(0);
|
|
3822
3898
|
const [selectionEnd, setSelectionEnd] = React.useState(0);
|
|
3823
3899
|
const [autocompleteContext, setAutocompleteContext] = React.useState("");
|
|
@@ -3887,11 +3963,34 @@ function ElasticInput(props) {
|
|
|
3887
3963
|
const HIGHLIGHT_DEBOUNCE_MS = 60;
|
|
3888
3964
|
const applyHighlight = React.useCallback((tokens2, offset) => {
|
|
3889
3965
|
if (!editorRef.current) return;
|
|
3890
|
-
const html = buildHighlightedHTML(tokens2, colors, { cursorOffset: offset, tokenClassName: classNames == null ? void 0 : classNames.token });
|
|
3966
|
+
const html = buildHighlightedHTML(tokens2, colors, { cursorOffset: offset, tokenClassName: classNames == null ? void 0 : classNames.token, fieldTypeMap });
|
|
3891
3967
|
editorRef.current.innerHTML = html;
|
|
3892
3968
|
setCaretCharOffset(editorRef.current, offset);
|
|
3893
3969
|
}, [colors]);
|
|
3894
3970
|
const processInput = React.useCallback((text, updateDropdown) => {
|
|
3971
|
+
const plain = plainModeLength != null && text.length >= plainModeLength;
|
|
3972
|
+
setIsPlainMode(plain);
|
|
3973
|
+
if (plain) {
|
|
3974
|
+
setTokens([]);
|
|
3975
|
+
setAst(null);
|
|
3976
|
+
setValidationErrors([]);
|
|
3977
|
+
setIsEmpty(text.length === 0);
|
|
3978
|
+
setSuggestions([]);
|
|
3979
|
+
setShowDropdown(false);
|
|
3980
|
+
setShowDatePicker(false);
|
|
3981
|
+
if (editorRef.current) {
|
|
3982
|
+
const offset = getCaretCharOffset(editorRef.current);
|
|
3983
|
+
if (editorRef.current.querySelector("span")) {
|
|
3984
|
+
editorRef.current.textContent = text;
|
|
3985
|
+
setCaretCharOffset(editorRef.current, offset);
|
|
3986
|
+
}
|
|
3987
|
+
setCursorOffset(offset);
|
|
3988
|
+
setSelectionEnd(offset);
|
|
3989
|
+
}
|
|
3990
|
+
if (onChange) onChange(text, null);
|
|
3991
|
+
if (onValidationChange) onValidationChange([]);
|
|
3992
|
+
return;
|
|
3993
|
+
}
|
|
3895
3994
|
const lexer = new Lexer(text, lexerOptions);
|
|
3896
3995
|
const newTokens = lexer.tokenize();
|
|
3897
3996
|
const parser = new Parser(newTokens);
|
|
@@ -3933,7 +4032,7 @@ function ElasticInput(props) {
|
|
|
3933
4032
|
}
|
|
3934
4033
|
if (onChange) onChange(text, newAst);
|
|
3935
4034
|
if (onValidationChange) onValidationChange(newErrors);
|
|
3936
|
-
}, [colors, onChange, onValidationChange, applyHighlight]);
|
|
4035
|
+
}, [colors, onChange, onValidationChange, applyHighlight, plainModeLength]);
|
|
3937
4036
|
const applyFieldHint = React.useCallback((suggestions2, context) => {
|
|
3938
4037
|
if (!renderFieldHint || context.type !== "FIELD_VALUE" || !context.fieldName) return suggestions2;
|
|
3939
4038
|
const resolved = engineRef.current.resolveField(context.fieldName);
|
|
@@ -4035,7 +4134,7 @@ function ElasticInput(props) {
|
|
|
4035
4134
|
setSuggestions(newSuggestions);
|
|
4036
4135
|
if (!dropdownAlignToInput) setShowDropdown(false);
|
|
4037
4136
|
setShowDatePicker(false);
|
|
4038
|
-
setSelectedSuggestionIndex(result.context.partial ? 0 : -1);
|
|
4137
|
+
setSelectedSuggestionIndex(result.context.partial || autoSelect ? 0 : -1);
|
|
4039
4138
|
setAutocompleteContext(contextType);
|
|
4040
4139
|
showDropdownAtPosition(newSuggestions.length * 32, 300);
|
|
4041
4140
|
} else {
|
|
@@ -4134,7 +4233,7 @@ function ElasticInput(props) {
|
|
|
4134
4233
|
mapped = mapped.slice(0, effectiveMaxSuggestions);
|
|
4135
4234
|
if (mapped.length > 0) {
|
|
4136
4235
|
setSuggestions(mapped);
|
|
4137
|
-
setSelectedSuggestionIndex(partial ? 0 : -1);
|
|
4236
|
+
setSelectedSuggestionIndex(partial || autoSelect ? 0 : -1);
|
|
4138
4237
|
showDropdownAtPosition(mapped.length * 32, 300);
|
|
4139
4238
|
} else {
|
|
4140
4239
|
const syncResult = engineRef.current.getSuggestions(stateRef.current.tokens, stateRef.current.cursorOffset);
|
|
@@ -4144,7 +4243,7 @@ function ElasticInput(props) {
|
|
|
4144
4243
|
);
|
|
4145
4244
|
if (hintSuggestions.length > 0) {
|
|
4146
4245
|
setSuggestions(hintSuggestions);
|
|
4147
|
-
setSelectedSuggestionIndex(syncResult.context.partial ? 0 : -1);
|
|
4246
|
+
setSelectedSuggestionIndex(syncResult.context.partial || autoSelect ? 0 : -1);
|
|
4148
4247
|
showDropdownAtPosition(hintSuggestions.length * 32, 300);
|
|
4149
4248
|
} else {
|
|
4150
4249
|
setShowDropdown(false);
|
|
@@ -4173,7 +4272,7 @@ function ElasticInput(props) {
|
|
|
4173
4272
|
}
|
|
4174
4273
|
}, debounceMs);
|
|
4175
4274
|
}
|
|
4176
|
-
}, [fetchSuggestionsProp, savedSearches, searchHistory, suggestDebounceMs, applyFieldHint, computeDropdownPosition, showDropdownAtPosition, dropdownAlignToInput, dropdownOpen, dropdownOpenIsCallback, dropdownMode, showOperators, effectiveMaxSuggestions, loadingDelay]);
|
|
4275
|
+
}, [fetchSuggestionsProp, savedSearches, searchHistory, suggestDebounceMs, applyFieldHint, computeDropdownPosition, showDropdownAtPosition, dropdownAlignToInput, dropdownOpen, dropdownOpenIsCallback, dropdownMode, showOperators, effectiveMaxSuggestions, loadingDelay, autoSelect]);
|
|
4177
4276
|
updateSuggestionsRef.current = updateSuggestionsFromTokens;
|
|
4178
4277
|
const closeDropdown = React.useCallback(() => {
|
|
4179
4278
|
var _a;
|
|
@@ -4231,7 +4330,7 @@ function ElasticInput(props) {
|
|
|
4231
4330
|
const syntaxErrors = parser.getErrors().map((e) => ({ message: e.message, start: e.start, end: e.end }));
|
|
4232
4331
|
const newErrors = [...syntaxErrors, ...validatorRef.current.validate(newAst, validateValueRef.current, parseDateProp)];
|
|
4233
4332
|
if (editorRef.current) {
|
|
4234
|
-
const html = buildHighlightedHTML(newTokens, colors, { cursorOffset: newCursorPos, tokenClassName: classNames == null ? void 0 : classNames.token });
|
|
4333
|
+
const html = buildHighlightedHTML(newTokens, colors, { cursorOffset: newCursorPos, tokenClassName: classNames == null ? void 0 : classNames.token, fieldTypeMap });
|
|
4235
4334
|
editorRef.current.innerHTML = html;
|
|
4236
4335
|
setCaretCharOffset(editorRef.current, newCursorPos);
|
|
4237
4336
|
}
|
|
@@ -4441,7 +4540,7 @@ function ElasticInput(props) {
|
|
|
4441
4540
|
if (matchKey === prevParenMatchRef.current && !colorsChanged) return;
|
|
4442
4541
|
prevParenMatchRef.current = matchKey;
|
|
4443
4542
|
const savedOffset = getCaretCharOffset(editorRef.current);
|
|
4444
|
-
const html = buildHighlightedHTML(currentTokens, colors, { cursorOffset: effectiveCursor, tokenClassName: classNames == null ? void 0 : classNames.token });
|
|
4543
|
+
const html = buildHighlightedHTML(currentTokens, colors, { cursorOffset: effectiveCursor, tokenClassName: classNames == null ? void 0 : classNames.token, fieldTypeMap });
|
|
4445
4544
|
editorRef.current.innerHTML = html;
|
|
4446
4545
|
setCaretCharOffset(editorRef.current, savedOffset);
|
|
4447
4546
|
}, [cursorOffset, selectionEnd, isFocused, colors]);
|
|
@@ -4503,7 +4602,7 @@ function ElasticInput(props) {
|
|
|
4503
4602
|
const newErrors = [...syntaxErrors, ...validatorRef.current.validate(newAst, validateValueRef.current, parseDateProp)];
|
|
4504
4603
|
const hasSelection = entry.selStart != null && entry.selStart !== entry.cursorPos;
|
|
4505
4604
|
if (editorRef.current) {
|
|
4506
|
-
const html = buildHighlightedHTML(newTokens, colors, { cursorOffset: entry.cursorPos, tokenClassName: classNames == null ? void 0 : classNames.token });
|
|
4605
|
+
const html = buildHighlightedHTML(newTokens, colors, { cursorOffset: entry.cursorPos, tokenClassName: classNames == null ? void 0 : classNames.token, fieldTypeMap });
|
|
4507
4606
|
editorRef.current.innerHTML = html;
|
|
4508
4607
|
if (hasSelection) {
|
|
4509
4608
|
setSelectionCharRange(editorRef.current, entry.selStart, entry.cursorPos);
|
|
@@ -4521,6 +4620,17 @@ function ElasticInput(props) {
|
|
|
4521
4620
|
if (onChange) onChange(entry.value, newAst);
|
|
4522
4621
|
if (onValidationChange) onValidationChange(newErrors);
|
|
4523
4622
|
}, [colors, onChange, onValidationChange, closeDropdown]);
|
|
4623
|
+
const getDropdownPageSize = React.useCallback(() => {
|
|
4624
|
+
const list = dropdownListRef.current;
|
|
4625
|
+
if (!list) return 10;
|
|
4626
|
+
const visibleHeight = list.clientHeight;
|
|
4627
|
+
const items = list.querySelectorAll(".ei-dropdown-item");
|
|
4628
|
+
const firstItem = items[0];
|
|
4629
|
+
if (!firstItem) return 10;
|
|
4630
|
+
const itemHeight = firstItem.offsetHeight;
|
|
4631
|
+
if (itemHeight <= 0) return 10;
|
|
4632
|
+
return Math.max(1, Math.floor(visibleHeight / itemHeight));
|
|
4633
|
+
}, []);
|
|
4524
4634
|
const handleKeyDown = React.useCallback((e) => {
|
|
4525
4635
|
var _a, _b;
|
|
4526
4636
|
if (onKeyDownProp) onKeyDownProp(e);
|
|
@@ -4590,7 +4700,7 @@ function ElasticInput(props) {
|
|
|
4590
4700
|
const newAst = parser.parse();
|
|
4591
4701
|
const syntaxErrors = parser.getErrors().map((err) => ({ message: err.message, start: err.start, end: err.end }));
|
|
4592
4702
|
const newErrors = [...syntaxErrors, ...validatorRef.current.validate(newAst, validateValueRef.current, parseDateProp)];
|
|
4593
|
-
const html = buildHighlightedHTML(newTokens, colors, { cursorOffset: newSelEnd, tokenClassName: classNames == null ? void 0 : classNames.token });
|
|
4703
|
+
const html = buildHighlightedHTML(newTokens, colors, { cursorOffset: newSelEnd, tokenClassName: classNames == null ? void 0 : classNames.token, fieldTypeMap });
|
|
4594
4704
|
editorRef.current.innerHTML = html;
|
|
4595
4705
|
setSelectionCharRange(editorRef.current, newSelStart, newSelEnd);
|
|
4596
4706
|
setTokens(newTokens);
|
|
@@ -4676,16 +4786,49 @@ function ElasticInput(props) {
|
|
|
4676
4786
|
handleInput();
|
|
4677
4787
|
return;
|
|
4678
4788
|
}
|
|
4789
|
+
keyConsumedByDropdownRef.current = false;
|
|
4679
4790
|
if (s.showDropdown && s.suggestions.length > 0) {
|
|
4680
4791
|
switch (e.key) {
|
|
4681
4792
|
case "ArrowDown":
|
|
4682
4793
|
e.preventDefault();
|
|
4683
|
-
|
|
4794
|
+
keyConsumedByDropdownRef.current = true;
|
|
4795
|
+
setSelectedSuggestionIndex((i) => i >= s.suggestions.length - 1 ? 0 : i + 1);
|
|
4684
4796
|
return;
|
|
4685
4797
|
case "ArrowUp":
|
|
4686
4798
|
e.preventDefault();
|
|
4687
|
-
|
|
4799
|
+
keyConsumedByDropdownRef.current = true;
|
|
4800
|
+
setSelectedSuggestionIndex((i) => i <= 0 ? s.suggestions.length - 1 : i - 1);
|
|
4801
|
+
return;
|
|
4802
|
+
case "PageDown": {
|
|
4803
|
+
e.preventDefault();
|
|
4804
|
+
keyConsumedByDropdownRef.current = true;
|
|
4805
|
+
const pageSize = getDropdownPageSize();
|
|
4806
|
+
setSelectedSuggestionIndex((i) => Math.min(i + pageSize, s.suggestions.length - 1));
|
|
4807
|
+
return;
|
|
4808
|
+
}
|
|
4809
|
+
case "PageUp": {
|
|
4810
|
+
e.preventDefault();
|
|
4811
|
+
keyConsumedByDropdownRef.current = true;
|
|
4812
|
+
const pageSize = getDropdownPageSize();
|
|
4813
|
+
setSelectedSuggestionIndex((i) => Math.max(i - pageSize, 0));
|
|
4688
4814
|
return;
|
|
4815
|
+
}
|
|
4816
|
+
case "Home":
|
|
4817
|
+
if (homeEndKeys && s.selectedSuggestionIndex >= 0) {
|
|
4818
|
+
e.preventDefault();
|
|
4819
|
+
keyConsumedByDropdownRef.current = true;
|
|
4820
|
+
setSelectedSuggestionIndex(0);
|
|
4821
|
+
return;
|
|
4822
|
+
}
|
|
4823
|
+
break;
|
|
4824
|
+
case "End":
|
|
4825
|
+
if (homeEndKeys && s.selectedSuggestionIndex >= 0) {
|
|
4826
|
+
e.preventDefault();
|
|
4827
|
+
keyConsumedByDropdownRef.current = true;
|
|
4828
|
+
setSelectedSuggestionIndex(s.suggestions.length - 1);
|
|
4829
|
+
return;
|
|
4830
|
+
}
|
|
4831
|
+
break;
|
|
4689
4832
|
case "Enter":
|
|
4690
4833
|
if (s.selectedSuggestionIndex >= 0) {
|
|
4691
4834
|
const selected = s.suggestions[s.selectedSuggestionIndex];
|
|
@@ -4789,8 +4932,12 @@ function ElasticInput(props) {
|
|
|
4789
4932
|
if (onSearch) onSearch(currentValueRef.current, s.ast);
|
|
4790
4933
|
return;
|
|
4791
4934
|
}
|
|
4792
|
-
}, [onSearch, closeDropdown, acceptSuggestion, applyNewValue, restoreUndoEntry, multiline, dropdownOpenIsCallback, dropdownMode, updateSuggestionsFromTokens, onKeyDownProp, onTabProp, smartSelectAll, expandSelection]);
|
|
4935
|
+
}, [onSearch, closeDropdown, acceptSuggestion, applyNewValue, restoreUndoEntry, multiline, dropdownOpenIsCallback, dropdownMode, updateSuggestionsFromTokens, onKeyDownProp, onTabProp, smartSelectAll, expandSelection, homeEndKeys, getDropdownPageSize]);
|
|
4793
4936
|
const handleKeyUp = React.useCallback((e) => {
|
|
4937
|
+
if (keyConsumedByDropdownRef.current) {
|
|
4938
|
+
keyConsumedByDropdownRef.current = false;
|
|
4939
|
+
return;
|
|
4940
|
+
}
|
|
4794
4941
|
const navKeys = ["ArrowLeft", "ArrowRight", "Home", "End", "PageUp", "PageDown"];
|
|
4795
4942
|
if (navKeys.includes(e.key)) {
|
|
4796
4943
|
if (!editorRef.current) return;
|
|
@@ -4806,12 +4953,10 @@ function ElasticInput(props) {
|
|
|
4806
4953
|
requestAnimationFrame(() => {
|
|
4807
4954
|
if (editorRef.current) {
|
|
4808
4955
|
const toks = stateRef.current.tokens;
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
4812
|
-
|
|
4813
|
-
triggerSuggestionsFromNavigation([], 0);
|
|
4814
|
-
}
|
|
4956
|
+
const offset = toks.length > 0 ? getCaretCharOffset(editorRef.current) : 0;
|
|
4957
|
+
setCursorOffset(offset);
|
|
4958
|
+
setSelectionEnd(offset);
|
|
4959
|
+
triggerSuggestionsFromNavigation(toks, offset);
|
|
4815
4960
|
}
|
|
4816
4961
|
});
|
|
4817
4962
|
}, [handleInput, triggerSuggestionsFromNavigation, onFocusProp]);
|
|
@@ -4836,16 +4981,31 @@ function ElasticInput(props) {
|
|
|
4836
4981
|
}
|
|
4837
4982
|
triggerSuggestionsFromNavigation(stateRef.current.tokens, selRange.start);
|
|
4838
4983
|
}, [triggerSuggestionsFromNavigation, closeDropdown]);
|
|
4839
|
-
const
|
|
4840
|
-
e.preventDefault();
|
|
4841
|
-
const pastedText = normalizeTypographicChars(e.clipboardData.getData("text/plain"));
|
|
4984
|
+
const doPaste = React.useCallback((text) => {
|
|
4842
4985
|
if (typingGroupTimerRef.current) {
|
|
4843
4986
|
clearTimeout(typingGroupTimerRef.current);
|
|
4844
4987
|
typingGroupTimerRef.current = null;
|
|
4845
4988
|
}
|
|
4846
|
-
insertTextAtCursor(
|
|
4989
|
+
insertTextAtCursor(text);
|
|
4847
4990
|
handleInput();
|
|
4848
4991
|
}, [handleInput]);
|
|
4992
|
+
const handlePaste = React.useCallback((e) => {
|
|
4993
|
+
e.preventDefault();
|
|
4994
|
+
const pastedText = normalizeTypographicChars(e.clipboardData.getData("text/plain"));
|
|
4995
|
+
if (interceptPaste) {
|
|
4996
|
+
const result = interceptPaste(pastedText, e);
|
|
4997
|
+
if (result != null && typeof (result == null ? void 0 : result.then) === "function") {
|
|
4998
|
+
result.then((transformed) => {
|
|
4999
|
+
if (transformed != null) doPaste(transformed);
|
|
5000
|
+
}).catch(() => {
|
|
5001
|
+
});
|
|
5002
|
+
} else if (result != null) {
|
|
5003
|
+
doPaste(result);
|
|
5004
|
+
}
|
|
5005
|
+
} else {
|
|
5006
|
+
doPaste(pastedText);
|
|
5007
|
+
}
|
|
5008
|
+
}, [interceptPaste, doPaste]);
|
|
4849
5009
|
const handleDateSelect = React.useCallback((dateStr) => {
|
|
4850
5010
|
const s = stateRef.current;
|
|
4851
5011
|
const saved = datePickerReplaceRef.current;
|
|
@@ -4921,6 +5081,9 @@ function ElasticInput(props) {
|
|
|
4921
5081
|
renderSavedSearchItem,
|
|
4922
5082
|
renderDropdownHeader,
|
|
4923
5083
|
cursorContext,
|
|
5084
|
+
listRefCallback: (el) => {
|
|
5085
|
+
dropdownListRef.current = el;
|
|
5086
|
+
},
|
|
4924
5087
|
classNames: classNames ? { dropdown: classNames.dropdown, dropdownHeader: classNames.dropdownHeader, dropdownItem: classNames.dropdownItem } : void 0
|
|
4925
5088
|
}
|
|
4926
5089
|
), showDatePicker && dropdownPosition ? /* @__PURE__ */ React.createElement(
|
|
@@ -5012,6 +5175,143 @@ function walk(node, groupField, out) {
|
|
|
5012
5175
|
break;
|
|
5013
5176
|
}
|
|
5014
5177
|
}
|
|
5178
|
+
const DEFAULT_MAX_LINE_LENGTH = 60;
|
|
5179
|
+
const DEFAULT_INDENT = " ";
|
|
5180
|
+
function formatQuery(input, options) {
|
|
5181
|
+
const maxLineLength = (options == null ? void 0 : options.maxLineLength) ?? DEFAULT_MAX_LINE_LENGTH;
|
|
5182
|
+
const indent = (options == null ? void 0 : options.indent) ?? DEFAULT_INDENT;
|
|
5183
|
+
let ast;
|
|
5184
|
+
if (typeof input === "string") {
|
|
5185
|
+
const tokens = new Lexer(input, { savedSearches: true, historySearch: true }).tokenize();
|
|
5186
|
+
const parser = new Parser(tokens);
|
|
5187
|
+
ast = parser.parse();
|
|
5188
|
+
} else {
|
|
5189
|
+
ast = input;
|
|
5190
|
+
}
|
|
5191
|
+
if (!ast) return typeof input === "string" ? input : "";
|
|
5192
|
+
return printNode(ast, 0, maxLineLength, indent);
|
|
5193
|
+
}
|
|
5194
|
+
function inline(node) {
|
|
5195
|
+
switch (node.type) {
|
|
5196
|
+
case "FieldValue": {
|
|
5197
|
+
const val = node.quoted ? `"${node.value}"` : node.value;
|
|
5198
|
+
const op = node.operator === ":" ? ":" : `:${node.operator}`;
|
|
5199
|
+
let s = `${node.field}${op}${val}`;
|
|
5200
|
+
if (node.fuzzy != null) s += `~${node.fuzzy}`;
|
|
5201
|
+
if (node.proximity != null) s += `~${node.proximity}`;
|
|
5202
|
+
if (node.boost != null) s += `^${node.boost}`;
|
|
5203
|
+
return s;
|
|
5204
|
+
}
|
|
5205
|
+
case "BareTerm": {
|
|
5206
|
+
let s = node.quoted ? `"${node.value}"` : node.value;
|
|
5207
|
+
if (node.fuzzy != null) s += `~${node.fuzzy}`;
|
|
5208
|
+
if (node.proximity != null) s += `~${node.proximity}`;
|
|
5209
|
+
if (node.boost != null) s += `^${node.boost}`;
|
|
5210
|
+
return s;
|
|
5211
|
+
}
|
|
5212
|
+
case "Range": {
|
|
5213
|
+
const lb = node.lowerInclusive ? "[" : "{";
|
|
5214
|
+
const rb = node.upperInclusive ? "]" : "}";
|
|
5215
|
+
const lower = node.lowerQuoted ? `"${node.lower}"` : node.lower;
|
|
5216
|
+
const upper = node.upperQuoted ? `"${node.upper}"` : node.upper;
|
|
5217
|
+
const range = `${lb}${lower} TO ${upper}${rb}`;
|
|
5218
|
+
return node.field ? `${node.field}:${range}` : range;
|
|
5219
|
+
}
|
|
5220
|
+
case "Regex":
|
|
5221
|
+
return `/${node.pattern}/`;
|
|
5222
|
+
case "SavedSearch":
|
|
5223
|
+
return `#${node.name}`;
|
|
5224
|
+
case "HistoryRef":
|
|
5225
|
+
return `!${node.ref}`;
|
|
5226
|
+
case "Not":
|
|
5227
|
+
return `NOT ${inline(node.expression)}`;
|
|
5228
|
+
case "Group": {
|
|
5229
|
+
let s = `(${inline(node.expression)})`;
|
|
5230
|
+
if (node.boost != null) s += `^${node.boost}`;
|
|
5231
|
+
return s;
|
|
5232
|
+
}
|
|
5233
|
+
case "FieldGroup": {
|
|
5234
|
+
let s = `${node.field}:(${inline(node.expression)})`;
|
|
5235
|
+
if (node.boost != null) s += `^${node.boost}`;
|
|
5236
|
+
return s;
|
|
5237
|
+
}
|
|
5238
|
+
case "BooleanExpr":
|
|
5239
|
+
return `${inline(node.left)} ${node.operator} ${inline(node.right)}`;
|
|
5240
|
+
case "Error":
|
|
5241
|
+
return node.value;
|
|
5242
|
+
}
|
|
5243
|
+
}
|
|
5244
|
+
function flattenChain(node) {
|
|
5245
|
+
const op = node.operator;
|
|
5246
|
+
const operands = [];
|
|
5247
|
+
const collect = (n) => {
|
|
5248
|
+
if (n.type === "BooleanExpr" && n.operator === op) {
|
|
5249
|
+
collect(n.left);
|
|
5250
|
+
collect(n.right);
|
|
5251
|
+
} else {
|
|
5252
|
+
operands.push(n);
|
|
5253
|
+
}
|
|
5254
|
+
};
|
|
5255
|
+
collect(node);
|
|
5256
|
+
return { operator: op, operands };
|
|
5257
|
+
}
|
|
5258
|
+
function containsGroups(node) {
|
|
5259
|
+
if (node.type === "Group" || node.type === "FieldGroup") return true;
|
|
5260
|
+
if (node.type === "BooleanExpr") return containsGroups(node.left) || containsGroups(node.right);
|
|
5261
|
+
if (node.type === "Not") return containsGroups(node.expression);
|
|
5262
|
+
return false;
|
|
5263
|
+
}
|
|
5264
|
+
function shouldBreakGroup(expr, maxLineLength) {
|
|
5265
|
+
const inlined = inline(expr);
|
|
5266
|
+
if (inlined.length > maxLineLength) return true;
|
|
5267
|
+
if (containsGroups(expr)) return true;
|
|
5268
|
+
return false;
|
|
5269
|
+
}
|
|
5270
|
+
function printNode(node, depth, maxLineLength, indent) {
|
|
5271
|
+
const pad = indent.repeat(depth);
|
|
5272
|
+
switch (node.type) {
|
|
5273
|
+
case "BooleanExpr": {
|
|
5274
|
+
const { operator, operands } = flattenChain(node);
|
|
5275
|
+
const inlined = operands.map((o) => inline(o)).join(` ${operator} `);
|
|
5276
|
+
if (inlined.length <= maxLineLength) {
|
|
5277
|
+
return inlined;
|
|
5278
|
+
}
|
|
5279
|
+
const lines = operands.map((operand, i) => {
|
|
5280
|
+
const printed = printNode(operand, depth, maxLineLength, indent);
|
|
5281
|
+
return i === 0 ? printed : `${pad}${operator} ${printed}`;
|
|
5282
|
+
});
|
|
5283
|
+
return lines.join("\n");
|
|
5284
|
+
}
|
|
5285
|
+
case "Group": {
|
|
5286
|
+
if (!shouldBreakGroup(node.expression, maxLineLength)) {
|
|
5287
|
+
let s2 = `(${inline(node.expression)})`;
|
|
5288
|
+
if (node.boost != null) s2 += `^${node.boost}`;
|
|
5289
|
+
return s2;
|
|
5290
|
+
}
|
|
5291
|
+
const inner = printNode(node.expression, depth + 1, maxLineLength, indent);
|
|
5292
|
+
let s = `(
|
|
5293
|
+
${indentLines(inner, depth + 1, indent)}
|
|
5294
|
+
${pad})`;
|
|
5295
|
+
if (node.boost != null) s += `^${node.boost}`;
|
|
5296
|
+
return s;
|
|
5297
|
+
}
|
|
5298
|
+
case "FieldGroup": {
|
|
5299
|
+
let s = `${node.field}:(${inline(node.expression)})`;
|
|
5300
|
+
if (node.boost != null) s += `^${node.boost}`;
|
|
5301
|
+
return s;
|
|
5302
|
+
}
|
|
5303
|
+
case "Not":
|
|
5304
|
+
return `NOT ${printNode(node.expression, depth, maxLineLength, indent)}`;
|
|
5305
|
+
default:
|
|
5306
|
+
return inline(node);
|
|
5307
|
+
}
|
|
5308
|
+
}
|
|
5309
|
+
function indentLines(text, depth, indent) {
|
|
5310
|
+
const pad = indent.repeat(depth);
|
|
5311
|
+
return text.split("\n").map((line) => {
|
|
5312
|
+
return line.startsWith(pad) ? line : pad + line;
|
|
5313
|
+
}).join("\n");
|
|
5314
|
+
}
|
|
5015
5315
|
export {
|
|
5016
5316
|
AutocompleteEngine,
|
|
5017
5317
|
DARK_COLORS,
|
|
@@ -5023,5 +5323,6 @@ export {
|
|
|
5023
5323
|
Parser,
|
|
5024
5324
|
Validator,
|
|
5025
5325
|
buildHighlightedHTML,
|
|
5026
|
-
extractValues
|
|
5326
|
+
extractValues,
|
|
5327
|
+
formatQuery
|
|
5027
5328
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export { AutocompleteEngine } from './autocomplete/AutocompleteEngine';
|
|
|
9
9
|
export type { AutocompleteOptions } from './autocomplete/AutocompleteEngine';
|
|
10
10
|
export { extractValues } from './utils/extractValues';
|
|
11
11
|
export type { ExtractedValue, ExtractedValueKind } from './utils/extractValues';
|
|
12
|
+
export { formatQuery } from './utils/formatQuery';
|
|
13
|
+
export type { FormatQueryOptions } from './utils/formatQuery';
|
|
12
14
|
export { DEFAULT_COLORS, DARK_COLORS, DEFAULT_STYLES, DARK_STYLES } from './constants';
|
|
13
15
|
export type { ElasticInputProps, ElasticInputAPI, FieldConfig, FieldsSource, FieldType, SavedSearch, HistoryEntry, SuggestionItem, ColorConfig, StyleConfig, ValidateValueContext, ValidationResult, ValidateReturn, TabContext, TabActionResult, DropdownConfig, DropdownOpenContext, DropdownOpenProp, FeaturesConfig, ClassNamesConfig, } from './types';
|
|
14
16
|
export type { Token, TokenType } from './lexer/tokens';
|
package/dist/types.d.ts
CHANGED
|
@@ -140,6 +140,14 @@ export interface ColorConfig {
|
|
|
140
140
|
matchedParenBg?: string;
|
|
141
141
|
/** Warning-severity squiggly underlines (e.g. ambiguous precedence). */
|
|
142
142
|
warning?: string;
|
|
143
|
+
/** Per-field-type value colors. Overrides `fieldValue` for values belonging to a typed field. */
|
|
144
|
+
valueTypes?: {
|
|
145
|
+
string?: string;
|
|
146
|
+
number?: string;
|
|
147
|
+
date?: string;
|
|
148
|
+
boolean?: string;
|
|
149
|
+
ip?: string;
|
|
150
|
+
};
|
|
143
151
|
}
|
|
144
152
|
/**
|
|
145
153
|
* Structural and layout style overrides for the input and dropdown.
|
|
@@ -257,6 +265,14 @@ export interface DropdownConfig {
|
|
|
257
265
|
/** Custom renderer for a header above the suggestion list. Return a React element,
|
|
258
266
|
* or null/undefined for no header. */
|
|
259
267
|
renderHeader?: (context: CursorContext) => React.ReactNode | null | undefined;
|
|
268
|
+
/** Automatically select the first suggestion when the dropdown opens, even with an
|
|
269
|
+
* empty partial. When false, the first item is only pre-selected after the user
|
|
270
|
+
* starts typing a partial match. @default false */
|
|
271
|
+
autoSelect?: boolean;
|
|
272
|
+
/** When true, Home/End keys navigate to the first/last dropdown item while the
|
|
273
|
+
* dropdown is open and an item is already selected. When no item is selected
|
|
274
|
+
* (index = -1), the keys pass through for normal text cursor movement. @default false */
|
|
275
|
+
homeEndKeys?: boolean;
|
|
260
276
|
}
|
|
261
277
|
/**
|
|
262
278
|
* Feature toggles for optional editing behaviors. All default to false except `multiline`.
|
|
@@ -295,21 +311,6 @@ export interface ElasticInputAPI {
|
|
|
295
311
|
/** Selects a character range in the input. Focuses the input if not already focused. */
|
|
296
312
|
setSelection: (start: number, end: number) => void;
|
|
297
313
|
}
|
|
298
|
-
/**
|
|
299
|
-
* Props for the ElasticInput component.
|
|
300
|
-
*
|
|
301
|
-
* @example
|
|
302
|
-
* ```tsx
|
|
303
|
-
* <ElasticInput
|
|
304
|
-
* fields={[
|
|
305
|
-
* { name: 'status', type: 'string', suggestions: ['active', 'inactive'] },
|
|
306
|
-
* { name: 'price', type: 'number' },
|
|
307
|
-
* ]}
|
|
308
|
-
* onSearch={(query, ast) => console.log('Search:', query)}
|
|
309
|
-
* placeholder="Search..."
|
|
310
|
-
* />
|
|
311
|
-
* ```
|
|
312
|
-
*/
|
|
313
314
|
/** Context passed to the `onTab` callback. */
|
|
314
315
|
export interface TabContext {
|
|
315
316
|
/** The currently selected suggestion, or `null` if nothing is highlighted. */
|
|
@@ -356,6 +357,21 @@ export interface ClassNamesConfig {
|
|
|
356
357
|
}
|
|
357
358
|
/** Field definitions — either a static array or an async loader function. */
|
|
358
359
|
export type FieldsSource = FieldConfig[] | (() => Promise<FieldConfig[]>);
|
|
360
|
+
/**
|
|
361
|
+
* Props for the ElasticInput component.
|
|
362
|
+
*
|
|
363
|
+
* @example
|
|
364
|
+
* ```tsx
|
|
365
|
+
* <ElasticInput
|
|
366
|
+
* fields={[
|
|
367
|
+
* { name: 'status', type: 'string', suggestions: ['active', 'inactive'] },
|
|
368
|
+
* { name: 'price', type: 'number' },
|
|
369
|
+
* ]}
|
|
370
|
+
* onSearch={(query, ast) => console.log('Search:', query)}
|
|
371
|
+
* placeholder="Search..."
|
|
372
|
+
* />
|
|
373
|
+
* ```
|
|
374
|
+
*/
|
|
359
375
|
export interface ElasticInputProps {
|
|
360
376
|
/** Field definitions that determine autocomplete, validation, and syntax highlighting. Accepts a static array or an async loader function. */
|
|
361
377
|
fields: FieldsSource;
|
|
@@ -438,4 +454,27 @@ export interface ElasticInputProps {
|
|
|
438
454
|
* The built-in parser handles YYYY-MM-DD, ISO 8601, and `now±Xd` syntax.
|
|
439
455
|
*/
|
|
440
456
|
parseDate?: (value: string) => Date | null;
|
|
457
|
+
/** When the input text reaches this character count, syntax highlighting, autocomplete,
|
|
458
|
+
* and validation are disabled and the input becomes a plain text box. `0` = always plain. */
|
|
459
|
+
plainModeLength?: number;
|
|
460
|
+
/**
|
|
461
|
+
* Intercept paste events before text is inserted. Receives the plain-text clipboard
|
|
462
|
+
* content and the original ClipboardEvent. Return:
|
|
463
|
+
* - A `string` to insert that text instead of the original
|
|
464
|
+
* - `null` to cancel the paste entirely
|
|
465
|
+
* - A `Promise` resolving to either — the component remains fully interactive while
|
|
466
|
+
* the promise is pending (no text is inserted until it resolves)
|
|
467
|
+
*
|
|
468
|
+
* @example
|
|
469
|
+
* ```tsx
|
|
470
|
+
* interceptPaste={async (text) => {
|
|
471
|
+
* if (text.includes('\n')) {
|
|
472
|
+
* const choice = await showDialog('Join lines with AND?');
|
|
473
|
+
* return choice ? text.split('\n').join(' AND ') : text;
|
|
474
|
+
* }
|
|
475
|
+
* return text; // pass through unchanged
|
|
476
|
+
* }}
|
|
477
|
+
* ```
|
|
478
|
+
*/
|
|
479
|
+
interceptPaste?: (text: string, event: React.ClipboardEvent) => string | null | Promise<string | null>;
|
|
441
480
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ASTNode } from '../parser/ast';
|
|
2
|
+
|
|
3
|
+
/** Options for `formatQuery` pretty-printing. */
|
|
4
|
+
export interface FormatQueryOptions {
|
|
5
|
+
/** Max length before a line is broken into multiple lines. @default 60 */
|
|
6
|
+
maxLineLength?: number;
|
|
7
|
+
/** Indent string for each nesting level. @default ' ' (2 spaces) */
|
|
8
|
+
indent?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Pretty-print an Elasticsearch query string.
|
|
12
|
+
* Accepts a raw query string or a pre-parsed AST node.
|
|
13
|
+
*/
|
|
14
|
+
export declare function formatQuery(input: string | ASTNode, options?: FormatQueryOptions): string;
|