elastic-input 0.3.5 → 0.3.7
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 +1 -0
- package/dist/components/AutocompleteDropdown.d.ts +4 -2
- package/dist/elastic-input.es.js +212 -155
- package/dist/parser/ast.d.ts +2 -0
- package/dist/types.d.ts +17 -0
- package/dist/utils/formatQuery.d.ts +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -487,6 +487,7 @@ formatQuery(query, { maxLineLength: 80, indent: '\t' });
|
|
|
487
487
|
|--------|------|---------|-------------|
|
|
488
488
|
| `maxLineLength` | `number` | `60` | Lines shorter than this stay inline |
|
|
489
489
|
| `indent` | `string` | `' '` (2 spaces) | Indent string per nesting level |
|
|
490
|
+
| `whitespaceOperator` | `string` | — | Replace implicit AND (whitespace) with this operator (e.g. `'AND'`, `'&&'`) |
|
|
490
491
|
|
|
491
492
|
## Requirements
|
|
492
493
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Suggestion } from '../autocomplete/suggestionTypes';
|
|
2
|
-
import { ColorConfig, StyleConfig, HistoryEntry, SavedSearch } from '../types';
|
|
2
|
+
import { ColorConfig, StyleConfig, HistoryEntry, SavedSearch, SuggestionItem } from '../types';
|
|
3
3
|
import { CursorContext } from '../parser/Parser';
|
|
4
4
|
import * as React from 'react';
|
|
5
5
|
interface AutocompleteDropdownProps {
|
|
@@ -21,6 +21,8 @@ interface AutocompleteDropdownProps {
|
|
|
21
21
|
cursorContext?: CursorContext | null;
|
|
22
22
|
/** Ref callback to expose the dropdown list element for page-size calculations. */
|
|
23
23
|
listRefCallback?: (el: HTMLDivElement | null) => void;
|
|
24
|
+
/** Controls the type badge in dropdown items. false=hide, true=default, callback=custom. */
|
|
25
|
+
renderType?: boolean | ((type: string, suggestion: SuggestionItem) => React.ReactNode | null | undefined);
|
|
24
26
|
/** Custom class names for dropdown elements. */
|
|
25
27
|
classNames?: {
|
|
26
28
|
dropdown?: string;
|
|
@@ -28,5 +30,5 @@ interface AutocompleteDropdownProps {
|
|
|
28
30
|
dropdownItem?: string;
|
|
29
31
|
};
|
|
30
32
|
}
|
|
31
|
-
export declare function AutocompleteDropdown({ suggestions, selectedIndex, onSelect, position, colors, styles, visible, fixedWidth, renderHistoryItem, renderSavedSearchItem, renderDropdownHeader, cursorContext, listRefCallback, classNames, }: AutocompleteDropdownProps): React.ReactPortal | null;
|
|
33
|
+
export declare function AutocompleteDropdown({ suggestions, selectedIndex, onSelect, position, colors, styles, visible, fixedWidth, renderHistoryItem, renderSavedSearchItem, renderDropdownHeader, cursorContext, listRefCallback, renderType, classNames, }: AutocompleteDropdownProps): React.ReactPortal | null;
|
|
32
34
|
export {};
|
package/dist/elastic-input.es.js
CHANGED
|
@@ -444,6 +444,7 @@ class Parser {
|
|
|
444
444
|
operator: "AND",
|
|
445
445
|
left: result,
|
|
446
446
|
right,
|
|
447
|
+
implicit: true,
|
|
447
448
|
start: result.start,
|
|
448
449
|
end: right.end
|
|
449
450
|
};
|
|
@@ -526,6 +527,7 @@ class Parser {
|
|
|
526
527
|
operator: "AND",
|
|
527
528
|
left,
|
|
528
529
|
right,
|
|
530
|
+
implicit: true,
|
|
529
531
|
start: left.start,
|
|
530
532
|
end: right.end
|
|
531
533
|
};
|
|
@@ -1215,6 +1217,7 @@ const DARK_COLORS = {
|
|
|
1215
1217
|
const DEFAULT_STYLES = {
|
|
1216
1218
|
fontFamily: "'SF Mono', 'Fira Code', 'Fira Mono', Menlo, Consolas, monospace",
|
|
1217
1219
|
fontSize: "14px",
|
|
1220
|
+
lineHeight: "1.5",
|
|
1218
1221
|
inputMinHeight: "40px",
|
|
1219
1222
|
inputPadding: "8px 12px",
|
|
1220
1223
|
inputBorderWidth: "2px",
|
|
@@ -2125,7 +2128,7 @@ function getEditableStyle(colors, styles) {
|
|
|
2125
2128
|
outline: "none",
|
|
2126
2129
|
fontSize: styles.fontSize,
|
|
2127
2130
|
fontFamily: styles.fontFamily,
|
|
2128
|
-
lineHeight:
|
|
2131
|
+
lineHeight: styles.lineHeight,
|
|
2129
2132
|
backgroundColor: colors.background,
|
|
2130
2133
|
color: colors.text,
|
|
2131
2134
|
caretColor: colors.cursor,
|
|
@@ -2153,7 +2156,7 @@ function getPlaceholderStyle(colors, styles) {
|
|
|
2153
2156
|
pointerEvents: "none",
|
|
2154
2157
|
fontSize: styles.fontSize,
|
|
2155
2158
|
fontFamily: styles.fontFamily,
|
|
2156
|
-
lineHeight:
|
|
2159
|
+
lineHeight: styles.lineHeight,
|
|
2157
2160
|
userSelect: "none"
|
|
2158
2161
|
};
|
|
2159
2162
|
}
|
|
@@ -2750,6 +2753,7 @@ function AutocompleteDropdown({
|
|
|
2750
2753
|
renderDropdownHeader,
|
|
2751
2754
|
cursorContext,
|
|
2752
2755
|
listRefCallback,
|
|
2756
|
+
renderType,
|
|
2753
2757
|
classNames
|
|
2754
2758
|
}) {
|
|
2755
2759
|
const portalRef = React.useRef(null);
|
|
@@ -2821,6 +2825,9 @@ function AutocompleteDropdown({
|
|
|
2821
2825
|
if (suggestion.type === "error") {
|
|
2822
2826
|
return /* @__PURE__ */ React.createElement("div", { key: i, className: cx("ei-dropdown-item", "ei-dropdown-item--error", classNames == null ? void 0 : classNames.dropdownItem), style: { ...itemStyle, cursor: "default", opacity: 0.8 } }, /* @__PURE__ */ React.createElement("span", { className: "ei-dropdown-item-label", style: { ...getDropdownItemLabelStyle(), color: mergedColors.error } }, suggestion.label || "Error loading suggestions"));
|
|
2823
2827
|
}
|
|
2828
|
+
if (suggestion.type === "noResults") {
|
|
2829
|
+
return /* @__PURE__ */ React.createElement("div", { key: i, className: cx("ei-dropdown-item", "ei-dropdown-item--no-results", classNames == null ? void 0 : classNames.dropdownItem), style: { ...itemStyle, cursor: "default", opacity: 0.7 } }, suggestion.customContent);
|
|
2830
|
+
}
|
|
2824
2831
|
if (suggestion.type === "loading") {
|
|
2825
2832
|
return /* @__PURE__ */ React.createElement("div", { key: i, className: cx("ei-dropdown-item", "ei-dropdown-item--loading", classNames == null ? void 0 : classNames.dropdownItem), style: { ...itemStyle, cursor: "default", opacity: 0.6, justifyContent: "center" } }, /* @__PURE__ */ React.createElement("span", { className: "ei-dropdown-item-label", style: { ...getDropdownItemLabelStyle(), fontStyle: "italic" } }, suggestion.label || "Searching..."), /* @__PURE__ */ React.createElement("span", { className: "ei-dropdown-spinner", style: { marginLeft: "6px", display: "inline-block", animation: "elastic-input-spin 1s linear infinite", width: "14px", height: "14px", border: "2px solid", borderColor: `${mergedColors.placeholder} transparent ${mergedColors.placeholder} transparent`, borderRadius: "50%" } }));
|
|
2826
2833
|
}
|
|
@@ -2897,7 +2904,10 @@ function AutocompleteDropdown({
|
|
|
2897
2904
|
},
|
|
2898
2905
|
/* @__PURE__ */ React.createElement("span", { className: "ei-dropdown-item-label", style: getDropdownItemLabelStyle() }, highlightMatch(suggestion.label, suggestion.matchPartial, isSelected)),
|
|
2899
2906
|
suggestion.description && /* @__PURE__ */ React.createElement("span", { className: "ei-dropdown-item-desc", style: getDropdownItemDescStyle() }, suggestion.description),
|
|
2900
|
-
suggestion.type && suggestion.type !== "hint" &&
|
|
2907
|
+
renderType !== false && suggestion.type && suggestion.type !== "hint" && (() => {
|
|
2908
|
+
const content2 = typeof renderType === "function" ? renderType(suggestion.type, { text: suggestion.text, label: suggestion.label, description: suggestion.description, type: suggestion.type }) : suggestion.type;
|
|
2909
|
+
return content2 != null ? /* @__PURE__ */ React.createElement("span", { className: "ei-dropdown-item-type", style: getDropdownItemTypeStyle(isSelected, mergedStyles) }, content2) : null;
|
|
2910
|
+
})()
|
|
2901
2911
|
);
|
|
2902
2912
|
}));
|
|
2903
2913
|
return ReactDOM.createPortal(content, portalRef.current);
|
|
@@ -3626,6 +3636,155 @@ function dedup(ranges) {
|
|
|
3626
3636
|
}
|
|
3627
3637
|
return result;
|
|
3628
3638
|
}
|
|
3639
|
+
const DEFAULT_MAX_LINE_LENGTH = 60;
|
|
3640
|
+
const DEFAULT_INDENT = " ";
|
|
3641
|
+
function formatQuery(input, options) {
|
|
3642
|
+
const maxLineLength = (options == null ? void 0 : options.maxLineLength) ?? DEFAULT_MAX_LINE_LENGTH;
|
|
3643
|
+
const indent = (options == null ? void 0 : options.indent) ?? DEFAULT_INDENT;
|
|
3644
|
+
const whitespaceOperator = options == null ? void 0 : options.whitespaceOperator;
|
|
3645
|
+
let ast;
|
|
3646
|
+
if (typeof input === "string") {
|
|
3647
|
+
const tokens = new Lexer(input, { savedSearches: true, historySearch: true }).tokenize();
|
|
3648
|
+
const parser = new Parser(tokens);
|
|
3649
|
+
ast = parser.parse();
|
|
3650
|
+
} else {
|
|
3651
|
+
ast = input;
|
|
3652
|
+
}
|
|
3653
|
+
if (!ast) return typeof input === "string" ? input : "";
|
|
3654
|
+
return printNode(ast, 0, maxLineLength, indent, whitespaceOperator);
|
|
3655
|
+
}
|
|
3656
|
+
function resolveOperator(node, whitespaceOperator) {
|
|
3657
|
+
if (node.implicit) return whitespaceOperator ?? "";
|
|
3658
|
+
return node.operator;
|
|
3659
|
+
}
|
|
3660
|
+
function inline(node, whitespaceOperator) {
|
|
3661
|
+
switch (node.type) {
|
|
3662
|
+
case "FieldValue": {
|
|
3663
|
+
const val = node.quoted ? `"${node.value}"` : node.value;
|
|
3664
|
+
const op = node.operator === ":" ? ":" : `:${node.operator}`;
|
|
3665
|
+
let s = `${node.field}${op}${val}`;
|
|
3666
|
+
if (node.fuzzy != null) s += `~${node.fuzzy}`;
|
|
3667
|
+
if (node.proximity != null) s += `~${node.proximity}`;
|
|
3668
|
+
if (node.boost != null) s += `^${node.boost}`;
|
|
3669
|
+
return s;
|
|
3670
|
+
}
|
|
3671
|
+
case "BareTerm": {
|
|
3672
|
+
let s = node.quoted ? `"${node.value}"` : node.value;
|
|
3673
|
+
if (node.fuzzy != null) s += `~${node.fuzzy}`;
|
|
3674
|
+
if (node.proximity != null) s += `~${node.proximity}`;
|
|
3675
|
+
if (node.boost != null) s += `^${node.boost}`;
|
|
3676
|
+
return s;
|
|
3677
|
+
}
|
|
3678
|
+
case "Range": {
|
|
3679
|
+
const lb = node.lowerInclusive ? "[" : "{";
|
|
3680
|
+
const rb = node.upperInclusive ? "]" : "}";
|
|
3681
|
+
const lower = node.lowerQuoted ? `"${node.lower}"` : node.lower;
|
|
3682
|
+
const upper = node.upperQuoted ? `"${node.upper}"` : node.upper;
|
|
3683
|
+
const range = `${lb}${lower} TO ${upper}${rb}`;
|
|
3684
|
+
return node.field ? `${node.field}:${range}` : range;
|
|
3685
|
+
}
|
|
3686
|
+
case "Regex":
|
|
3687
|
+
return `/${node.pattern}/`;
|
|
3688
|
+
case "SavedSearch":
|
|
3689
|
+
return `#${node.name}`;
|
|
3690
|
+
case "HistoryRef":
|
|
3691
|
+
return `!${node.ref}`;
|
|
3692
|
+
case "Not":
|
|
3693
|
+
return `NOT ${inline(node.expression, whitespaceOperator)}`;
|
|
3694
|
+
case "Group": {
|
|
3695
|
+
let s = `(${inline(node.expression, whitespaceOperator)})`;
|
|
3696
|
+
if (node.boost != null) s += `^${node.boost}`;
|
|
3697
|
+
return s;
|
|
3698
|
+
}
|
|
3699
|
+
case "FieldGroup": {
|
|
3700
|
+
let s = `${node.field}:(${inline(node.expression, whitespaceOperator)})`;
|
|
3701
|
+
if (node.boost != null) s += `^${node.boost}`;
|
|
3702
|
+
return s;
|
|
3703
|
+
}
|
|
3704
|
+
case "BooleanExpr": {
|
|
3705
|
+
const op = resolveOperator(node, whitespaceOperator);
|
|
3706
|
+
const sep = op ? ` ${op} ` : " ";
|
|
3707
|
+
return `${inline(node.left, whitespaceOperator)}${sep}${inline(node.right, whitespaceOperator)}`;
|
|
3708
|
+
}
|
|
3709
|
+
case "Error":
|
|
3710
|
+
return node.value;
|
|
3711
|
+
}
|
|
3712
|
+
}
|
|
3713
|
+
function flattenChain(node) {
|
|
3714
|
+
const op = node.operator;
|
|
3715
|
+
const implicit = !!node.implicit;
|
|
3716
|
+
const operands = [];
|
|
3717
|
+
const collect = (n) => {
|
|
3718
|
+
if (n.type === "BooleanExpr" && n.operator === op && !!n.implicit === implicit) {
|
|
3719
|
+
collect(n.left);
|
|
3720
|
+
collect(n.right);
|
|
3721
|
+
} else {
|
|
3722
|
+
operands.push(n);
|
|
3723
|
+
}
|
|
3724
|
+
};
|
|
3725
|
+
collect(node);
|
|
3726
|
+
return { operator: op, implicit, operands };
|
|
3727
|
+
}
|
|
3728
|
+
function containsGroups(node) {
|
|
3729
|
+
if (node.type === "Group" || node.type === "FieldGroup") return true;
|
|
3730
|
+
if (node.type === "BooleanExpr") return containsGroups(node.left) || containsGroups(node.right);
|
|
3731
|
+
if (node.type === "Not") return containsGroups(node.expression);
|
|
3732
|
+
return false;
|
|
3733
|
+
}
|
|
3734
|
+
function shouldBreakGroup(expr, maxLineLength) {
|
|
3735
|
+
const inlined = inline(expr);
|
|
3736
|
+
if (inlined.length > maxLineLength) return true;
|
|
3737
|
+
if (containsGroups(expr)) return true;
|
|
3738
|
+
return false;
|
|
3739
|
+
}
|
|
3740
|
+
function printNode(node, depth, maxLineLength, indent, whitespaceOperator) {
|
|
3741
|
+
const pad = indent.repeat(depth);
|
|
3742
|
+
switch (node.type) {
|
|
3743
|
+
case "BooleanExpr": {
|
|
3744
|
+
const { operator, implicit, operands } = flattenChain(node);
|
|
3745
|
+
const displayOp = implicit ? whitespaceOperator ?? "" : operator;
|
|
3746
|
+
const sep = displayOp ? ` ${displayOp} ` : " ";
|
|
3747
|
+
const inlined = operands.map((o) => inline(o, whitespaceOperator)).join(sep);
|
|
3748
|
+
if (inlined.length <= maxLineLength) {
|
|
3749
|
+
return inlined;
|
|
3750
|
+
}
|
|
3751
|
+
const lines = operands.map((operand, i) => {
|
|
3752
|
+
const printed = printNode(operand, depth, maxLineLength, indent, whitespaceOperator);
|
|
3753
|
+
if (i === 0) return printed;
|
|
3754
|
+
return displayOp ? `${pad}${displayOp} ${printed}` : `${pad}${printed}`;
|
|
3755
|
+
});
|
|
3756
|
+
return lines.join("\n");
|
|
3757
|
+
}
|
|
3758
|
+
case "Group": {
|
|
3759
|
+
if (!shouldBreakGroup(node.expression, maxLineLength)) {
|
|
3760
|
+
let s2 = `(${inline(node.expression, whitespaceOperator)})`;
|
|
3761
|
+
if (node.boost != null) s2 += `^${node.boost}`;
|
|
3762
|
+
return s2;
|
|
3763
|
+
}
|
|
3764
|
+
const inner = printNode(node.expression, depth + 1, maxLineLength, indent, whitespaceOperator);
|
|
3765
|
+
let s = `(
|
|
3766
|
+
${indentLines(inner, depth + 1, indent)}
|
|
3767
|
+
${pad})`;
|
|
3768
|
+
if (node.boost != null) s += `^${node.boost}`;
|
|
3769
|
+
return s;
|
|
3770
|
+
}
|
|
3771
|
+
case "FieldGroup": {
|
|
3772
|
+
let s = `${node.field}:(${inline(node.expression, whitespaceOperator)})`;
|
|
3773
|
+
if (node.boost != null) s += `^${node.boost}`;
|
|
3774
|
+
return s;
|
|
3775
|
+
}
|
|
3776
|
+
case "Not":
|
|
3777
|
+
return `NOT ${printNode(node.expression, depth, maxLineLength, indent, whitespaceOperator)}`;
|
|
3778
|
+
default:
|
|
3779
|
+
return inline(node, whitespaceOperator);
|
|
3780
|
+
}
|
|
3781
|
+
}
|
|
3782
|
+
function indentLines(text, depth, indent) {
|
|
3783
|
+
const pad = indent.repeat(depth);
|
|
3784
|
+
return text.split("\n").map((line) => {
|
|
3785
|
+
return line.startsWith(pad) ? line : pad + line;
|
|
3786
|
+
}).join("\n");
|
|
3787
|
+
}
|
|
3629
3788
|
class UndoStack {
|
|
3630
3789
|
constructor(maxSize = 100) {
|
|
3631
3790
|
__publicField(this, "stack", []);
|
|
@@ -3806,12 +3965,15 @@ function ElasticInput(props) {
|
|
|
3806
3965
|
const renderHistoryItem = dropdownConfig == null ? void 0 : dropdownConfig.renderHistoryItem;
|
|
3807
3966
|
const renderSavedSearchItem = dropdownConfig == null ? void 0 : dropdownConfig.renderSavedSearchItem;
|
|
3808
3967
|
const renderDropdownHeader = dropdownConfig == null ? void 0 : dropdownConfig.renderHeader;
|
|
3968
|
+
const renderNoResults = dropdownConfig == null ? void 0 : dropdownConfig.renderNoResults;
|
|
3969
|
+
const renderType = dropdownConfig == null ? void 0 : dropdownConfig.renderType;
|
|
3809
3970
|
const autoSelect = (dropdownConfig == null ? void 0 : dropdownConfig.autoSelect) ?? false;
|
|
3810
3971
|
const homeEndKeys = (dropdownConfig == null ? void 0 : dropdownConfig.homeEndKeys) ?? false;
|
|
3811
3972
|
const multiline = (featuresConfig == null ? void 0 : featuresConfig.multiline) !== false;
|
|
3812
3973
|
const smartSelectAll = (featuresConfig == null ? void 0 : featuresConfig.smartSelectAll) ?? false;
|
|
3813
3974
|
const expandSelection = (featuresConfig == null ? void 0 : featuresConfig.expandSelection) ?? false;
|
|
3814
3975
|
const wildcardWrap = (featuresConfig == null ? void 0 : featuresConfig.wildcardWrap) ?? false;
|
|
3976
|
+
const enableFormatQuery = (featuresConfig == null ? void 0 : featuresConfig.formatQuery) ?? false;
|
|
3815
3977
|
const lexerOptions = React.useMemo(() => ({
|
|
3816
3978
|
savedSearches: enableSavedSearches,
|
|
3817
3979
|
historySearch: enableHistorySearch
|
|
@@ -4044,6 +4206,24 @@ function ElasticInput(props) {
|
|
|
4044
4206
|
return { ...s, customContent: custom };
|
|
4045
4207
|
});
|
|
4046
4208
|
}, [renderFieldHint]);
|
|
4209
|
+
const tryShowNoResults = React.useCallback((context) => {
|
|
4210
|
+
if (!renderNoResults) return false;
|
|
4211
|
+
const content = renderNoResults({ cursorContext: context, partial: context.partial });
|
|
4212
|
+
if (content == null) return false;
|
|
4213
|
+
const noResultsSuggestion = {
|
|
4214
|
+
text: "",
|
|
4215
|
+
label: "",
|
|
4216
|
+
type: "noResults",
|
|
4217
|
+
customContent: content,
|
|
4218
|
+
replaceStart: 0,
|
|
4219
|
+
replaceEnd: 0
|
|
4220
|
+
};
|
|
4221
|
+
setSuggestions([noResultsSuggestion]);
|
|
4222
|
+
setSelectedSuggestionIndex(-1);
|
|
4223
|
+
setShowDatePicker(false);
|
|
4224
|
+
showDropdownAtPosition(32, 300);
|
|
4225
|
+
return true;
|
|
4226
|
+
}, [renderNoResults]);
|
|
4047
4227
|
const updateSuggestionsFromTokens = React.useCallback((toks, offset) => {
|
|
4048
4228
|
var _a, _b, _c;
|
|
4049
4229
|
const result = engineRef.current.getSuggestions(toks, offset);
|
|
@@ -4138,10 +4318,12 @@ function ElasticInput(props) {
|
|
|
4138
4318
|
setAutocompleteContext(contextType);
|
|
4139
4319
|
showDropdownAtPosition(newSuggestions.length * 32, 300);
|
|
4140
4320
|
} else {
|
|
4141
|
-
setShowDropdown(false);
|
|
4142
|
-
setShowDatePicker(false);
|
|
4143
|
-
setSuggestions([]);
|
|
4144
4321
|
setAutocompleteContext(contextType);
|
|
4322
|
+
if (!tryShowNoResults(result.context)) {
|
|
4323
|
+
setShowDropdown(false);
|
|
4324
|
+
setShowDatePicker(false);
|
|
4325
|
+
setSuggestions([]);
|
|
4326
|
+
}
|
|
4145
4327
|
}
|
|
4146
4328
|
} else {
|
|
4147
4329
|
const token = result.context.token;
|
|
@@ -4237,17 +4419,20 @@ function ElasticInput(props) {
|
|
|
4237
4419
|
showDropdownAtPosition(mapped.length * 32, 300);
|
|
4238
4420
|
} else {
|
|
4239
4421
|
const syncResult = engineRef.current.getSuggestions(stateRef.current.tokens, stateRef.current.cursorOffset);
|
|
4240
|
-
|
|
4241
|
-
syncResult.suggestions.filter((s) => s.type === "hint"),
|
|
4242
|
-
syncResult.context
|
|
4243
|
-
);
|
|
4244
|
-
if (hintSuggestions.length > 0) {
|
|
4245
|
-
setSuggestions(hintSuggestions);
|
|
4246
|
-
setSelectedSuggestionIndex(syncResult.context.partial || autoSelect ? 0 : -1);
|
|
4247
|
-
showDropdownAtPosition(hintSuggestions.length * 32, 300);
|
|
4422
|
+
if (partial && tryShowNoResults(syncResult.context)) {
|
|
4248
4423
|
} else {
|
|
4249
|
-
|
|
4250
|
-
|
|
4424
|
+
const hintSuggestions = applyFieldHint(
|
|
4425
|
+
syncResult.suggestions.filter((s) => s.type === "hint"),
|
|
4426
|
+
syncResult.context
|
|
4427
|
+
);
|
|
4428
|
+
if (hintSuggestions.length > 0) {
|
|
4429
|
+
setSuggestions(hintSuggestions);
|
|
4430
|
+
setSelectedSuggestionIndex(syncResult.context.partial || autoSelect ? 0 : -1);
|
|
4431
|
+
showDropdownAtPosition(hintSuggestions.length * 32, 300);
|
|
4432
|
+
} else if (!tryShowNoResults(syncResult.context)) {
|
|
4433
|
+
setShowDropdown(false);
|
|
4434
|
+
setSuggestions([]);
|
|
4435
|
+
}
|
|
4251
4436
|
}
|
|
4252
4437
|
}
|
|
4253
4438
|
} catch (e) {
|
|
@@ -4272,7 +4457,7 @@ function ElasticInput(props) {
|
|
|
4272
4457
|
}
|
|
4273
4458
|
}, debounceMs);
|
|
4274
4459
|
}
|
|
4275
|
-
}, [fetchSuggestionsProp, savedSearches, searchHistory, suggestDebounceMs, applyFieldHint, computeDropdownPosition, showDropdownAtPosition, dropdownAlignToInput, dropdownOpen, dropdownOpenIsCallback, dropdownMode, showOperators, effectiveMaxSuggestions, loadingDelay, autoSelect]);
|
|
4460
|
+
}, [fetchSuggestionsProp, savedSearches, searchHistory, suggestDebounceMs, applyFieldHint, computeDropdownPosition, showDropdownAtPosition, dropdownAlignToInput, dropdownOpen, dropdownOpenIsCallback, dropdownMode, showOperators, effectiveMaxSuggestions, loadingDelay, autoSelect, tryShowNoResults]);
|
|
4276
4461
|
updateSuggestionsRef.current = updateSuggestionsFromTokens;
|
|
4277
4462
|
const closeDropdown = React.useCallback(() => {
|
|
4278
4463
|
var _a;
|
|
@@ -4636,6 +4821,14 @@ function ElasticInput(props) {
|
|
|
4636
4821
|
if (onKeyDownProp) onKeyDownProp(e);
|
|
4637
4822
|
if (e.defaultPrevented) return;
|
|
4638
4823
|
const s = stateRef.current;
|
|
4824
|
+
if (enableFormatQuery && e.key === "F" && e.altKey && e.shiftKey && !e.ctrlKey && !e.metaKey) {
|
|
4825
|
+
e.preventDefault();
|
|
4826
|
+
const formatted = formatQuery(currentValueRef.current);
|
|
4827
|
+
if (formatted !== currentValueRef.current) {
|
|
4828
|
+
applyNewValue(formatted, formatted.length);
|
|
4829
|
+
}
|
|
4830
|
+
return;
|
|
4831
|
+
}
|
|
4639
4832
|
if (editorRef.current && editorRef.current.childNodes.length > 40) {
|
|
4640
4833
|
const sel = window.getSelection();
|
|
4641
4834
|
const hasSelection = sel != null && !sel.isCollapsed;
|
|
@@ -4932,7 +5125,7 @@ function ElasticInput(props) {
|
|
|
4932
5125
|
if (onSearch) onSearch(currentValueRef.current, s.ast);
|
|
4933
5126
|
return;
|
|
4934
5127
|
}
|
|
4935
|
-
}, [onSearch, closeDropdown, acceptSuggestion, applyNewValue, restoreUndoEntry, multiline, dropdownOpenIsCallback, dropdownMode, updateSuggestionsFromTokens, onKeyDownProp, onTabProp, smartSelectAll, expandSelection, homeEndKeys, getDropdownPageSize]);
|
|
5128
|
+
}, [onSearch, closeDropdown, acceptSuggestion, applyNewValue, restoreUndoEntry, multiline, dropdownOpenIsCallback, dropdownMode, updateSuggestionsFromTokens, onKeyDownProp, onTabProp, smartSelectAll, expandSelection, homeEndKeys, getDropdownPageSize, enableFormatQuery]);
|
|
4936
5129
|
const handleKeyUp = React.useCallback((e) => {
|
|
4937
5130
|
if (keyConsumedByDropdownRef.current) {
|
|
4938
5131
|
keyConsumedByDropdownRef.current = false;
|
|
@@ -5084,6 +5277,7 @@ function ElasticInput(props) {
|
|
|
5084
5277
|
listRefCallback: (el) => {
|
|
5085
5278
|
dropdownListRef.current = el;
|
|
5086
5279
|
},
|
|
5280
|
+
renderType,
|
|
5087
5281
|
classNames: classNames ? { dropdown: classNames.dropdown, dropdownHeader: classNames.dropdownHeader, dropdownItem: classNames.dropdownItem } : void 0
|
|
5088
5282
|
}
|
|
5089
5283
|
), showDatePicker && dropdownPosition ? /* @__PURE__ */ React.createElement(
|
|
@@ -5175,143 +5369,6 @@ function walk(node, groupField, out) {
|
|
|
5175
5369
|
break;
|
|
5176
5370
|
}
|
|
5177
5371
|
}
|
|
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
|
-
}
|
|
5315
5372
|
export {
|
|
5316
5373
|
AutocompleteEngine,
|
|
5317
5374
|
DARK_COLORS,
|
package/dist/parser/ast.d.ts
CHANGED
package/dist/types.d.ts
CHANGED
|
@@ -158,6 +158,8 @@ export interface StyleConfig {
|
|
|
158
158
|
fontFamily?: string;
|
|
159
159
|
/** Base font size for the input. */
|
|
160
160
|
fontSize?: string;
|
|
161
|
+
/** Line height for the input text and placeholder. @default '1.5' */
|
|
162
|
+
lineHeight?: string;
|
|
161
163
|
/** Minimum height of the input element. */
|
|
162
164
|
inputMinHeight?: string;
|
|
163
165
|
/** Padding inside the input element. */
|
|
@@ -273,6 +275,19 @@ export interface DropdownConfig {
|
|
|
273
275
|
* dropdown is open and an item is already selected. When no item is selected
|
|
274
276
|
* (index = -1), the keys pass through for normal text cursor movement. @default false */
|
|
275
277
|
homeEndKeys?: boolean;
|
|
278
|
+
/** Called when the engine returns zero suggestions. Return a React element to display
|
|
279
|
+
* in the dropdown (e.g. "No results for …"), or null/undefined to hide the dropdown.
|
|
280
|
+
* Not called during async loading (the spinner handles that). */
|
|
281
|
+
renderNoResults?: (context: {
|
|
282
|
+
cursorContext: CursorContext;
|
|
283
|
+
partial: string;
|
|
284
|
+
}) => React.ReactNode | null | undefined;
|
|
285
|
+
/** Controls the type badge shown in dropdown items.
|
|
286
|
+
* - `false` — hide the badge entirely
|
|
287
|
+
* - `true` (default) — show the raw `suggestion.type` string
|
|
288
|
+
* - callback `(type, suggestion) => ReactNode` — custom render per item; return null to hide
|
|
289
|
+
*/
|
|
290
|
+
renderType?: boolean | ((type: string, suggestion: SuggestionItem) => React.ReactNode | null | undefined);
|
|
276
291
|
}
|
|
277
292
|
/**
|
|
278
293
|
* Feature toggles for optional editing behaviors. All default to false except `multiline`.
|
|
@@ -286,6 +301,8 @@ export interface FeaturesConfig {
|
|
|
286
301
|
expandSelection?: boolean;
|
|
287
302
|
/** Pressing `*` with a single value token selected wraps it in wildcards. @default false */
|
|
288
303
|
wildcardWrap?: boolean;
|
|
304
|
+
/** Enable Alt+Shift+F to pretty-print the query in-place using `formatQuery`. @default false */
|
|
305
|
+
formatQuery?: boolean;
|
|
289
306
|
/** Enable `#name` saved-search syntax and autocomplete. When false, `#` is a regular character. @default false */
|
|
290
307
|
savedSearches?: boolean;
|
|
291
308
|
/** Enable `!query` history-search syntax and autocomplete. When false, `!` is a regular character. @default false */
|
|
@@ -6,6 +6,10 @@ export interface FormatQueryOptions {
|
|
|
6
6
|
maxLineLength?: number;
|
|
7
7
|
/** Indent string for each nesting level. @default ' ' (2 spaces) */
|
|
8
8
|
indent?: string;
|
|
9
|
+
/** When set, replaces implicit AND (whitespace between terms) with this operator
|
|
10
|
+
* string in the output. By default, implicit AND is preserved as whitespace.
|
|
11
|
+
* @example 'AND' — turns `status:active name:john` into `status:active AND name:john` */
|
|
12
|
+
whitespaceOperator?: string;
|
|
9
13
|
}
|
|
10
14
|
/**
|
|
11
15
|
* Pretty-print an Elasticsearch query string.
|