elastic-input 0.3.5 → 0.3.6

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
@@ -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
 
@@ -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
  };
@@ -2821,6 +2823,9 @@ function AutocompleteDropdown({
2821
2823
  if (suggestion.type === "error") {
2822
2824
  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
2825
  }
2826
+ if (suggestion.type === "noResults") {
2827
+ 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);
2828
+ }
2824
2829
  if (suggestion.type === "loading") {
2825
2830
  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
2831
  }
@@ -3626,6 +3631,155 @@ function dedup(ranges) {
3626
3631
  }
3627
3632
  return result;
3628
3633
  }
3634
+ const DEFAULT_MAX_LINE_LENGTH = 60;
3635
+ const DEFAULT_INDENT = " ";
3636
+ function formatQuery(input, options) {
3637
+ const maxLineLength = (options == null ? void 0 : options.maxLineLength) ?? DEFAULT_MAX_LINE_LENGTH;
3638
+ const indent = (options == null ? void 0 : options.indent) ?? DEFAULT_INDENT;
3639
+ const whitespaceOperator = options == null ? void 0 : options.whitespaceOperator;
3640
+ let ast;
3641
+ if (typeof input === "string") {
3642
+ const tokens = new Lexer(input, { savedSearches: true, historySearch: true }).tokenize();
3643
+ const parser = new Parser(tokens);
3644
+ ast = parser.parse();
3645
+ } else {
3646
+ ast = input;
3647
+ }
3648
+ if (!ast) return typeof input === "string" ? input : "";
3649
+ return printNode(ast, 0, maxLineLength, indent, whitespaceOperator);
3650
+ }
3651
+ function resolveOperator(node, whitespaceOperator) {
3652
+ if (node.implicit) return whitespaceOperator ?? "";
3653
+ return node.operator;
3654
+ }
3655
+ function inline(node, whitespaceOperator) {
3656
+ switch (node.type) {
3657
+ case "FieldValue": {
3658
+ const val = node.quoted ? `"${node.value}"` : node.value;
3659
+ const op = node.operator === ":" ? ":" : `:${node.operator}`;
3660
+ let s = `${node.field}${op}${val}`;
3661
+ if (node.fuzzy != null) s += `~${node.fuzzy}`;
3662
+ if (node.proximity != null) s += `~${node.proximity}`;
3663
+ if (node.boost != null) s += `^${node.boost}`;
3664
+ return s;
3665
+ }
3666
+ case "BareTerm": {
3667
+ let s = node.quoted ? `"${node.value}"` : node.value;
3668
+ if (node.fuzzy != null) s += `~${node.fuzzy}`;
3669
+ if (node.proximity != null) s += `~${node.proximity}`;
3670
+ if (node.boost != null) s += `^${node.boost}`;
3671
+ return s;
3672
+ }
3673
+ case "Range": {
3674
+ const lb = node.lowerInclusive ? "[" : "{";
3675
+ const rb = node.upperInclusive ? "]" : "}";
3676
+ const lower = node.lowerQuoted ? `"${node.lower}"` : node.lower;
3677
+ const upper = node.upperQuoted ? `"${node.upper}"` : node.upper;
3678
+ const range = `${lb}${lower} TO ${upper}${rb}`;
3679
+ return node.field ? `${node.field}:${range}` : range;
3680
+ }
3681
+ case "Regex":
3682
+ return `/${node.pattern}/`;
3683
+ case "SavedSearch":
3684
+ return `#${node.name}`;
3685
+ case "HistoryRef":
3686
+ return `!${node.ref}`;
3687
+ case "Not":
3688
+ return `NOT ${inline(node.expression, whitespaceOperator)}`;
3689
+ case "Group": {
3690
+ let s = `(${inline(node.expression, whitespaceOperator)})`;
3691
+ if (node.boost != null) s += `^${node.boost}`;
3692
+ return s;
3693
+ }
3694
+ case "FieldGroup": {
3695
+ let s = `${node.field}:(${inline(node.expression, whitespaceOperator)})`;
3696
+ if (node.boost != null) s += `^${node.boost}`;
3697
+ return s;
3698
+ }
3699
+ case "BooleanExpr": {
3700
+ const op = resolveOperator(node, whitespaceOperator);
3701
+ const sep = op ? ` ${op} ` : " ";
3702
+ return `${inline(node.left, whitespaceOperator)}${sep}${inline(node.right, whitespaceOperator)}`;
3703
+ }
3704
+ case "Error":
3705
+ return node.value;
3706
+ }
3707
+ }
3708
+ function flattenChain(node) {
3709
+ const op = node.operator;
3710
+ const implicit = !!node.implicit;
3711
+ const operands = [];
3712
+ const collect = (n) => {
3713
+ if (n.type === "BooleanExpr" && n.operator === op && !!n.implicit === implicit) {
3714
+ collect(n.left);
3715
+ collect(n.right);
3716
+ } else {
3717
+ operands.push(n);
3718
+ }
3719
+ };
3720
+ collect(node);
3721
+ return { operator: op, implicit, operands };
3722
+ }
3723
+ function containsGroups(node) {
3724
+ if (node.type === "Group" || node.type === "FieldGroup") return true;
3725
+ if (node.type === "BooleanExpr") return containsGroups(node.left) || containsGroups(node.right);
3726
+ if (node.type === "Not") return containsGroups(node.expression);
3727
+ return false;
3728
+ }
3729
+ function shouldBreakGroup(expr, maxLineLength) {
3730
+ const inlined = inline(expr);
3731
+ if (inlined.length > maxLineLength) return true;
3732
+ if (containsGroups(expr)) return true;
3733
+ return false;
3734
+ }
3735
+ function printNode(node, depth, maxLineLength, indent, whitespaceOperator) {
3736
+ const pad = indent.repeat(depth);
3737
+ switch (node.type) {
3738
+ case "BooleanExpr": {
3739
+ const { operator, implicit, operands } = flattenChain(node);
3740
+ const displayOp = implicit ? whitespaceOperator ?? "" : operator;
3741
+ const sep = displayOp ? ` ${displayOp} ` : " ";
3742
+ const inlined = operands.map((o) => inline(o, whitespaceOperator)).join(sep);
3743
+ if (inlined.length <= maxLineLength) {
3744
+ return inlined;
3745
+ }
3746
+ const lines = operands.map((operand, i) => {
3747
+ const printed = printNode(operand, depth, maxLineLength, indent, whitespaceOperator);
3748
+ if (i === 0) return printed;
3749
+ return displayOp ? `${pad}${displayOp} ${printed}` : `${pad}${printed}`;
3750
+ });
3751
+ return lines.join("\n");
3752
+ }
3753
+ case "Group": {
3754
+ if (!shouldBreakGroup(node.expression, maxLineLength)) {
3755
+ let s2 = `(${inline(node.expression, whitespaceOperator)})`;
3756
+ if (node.boost != null) s2 += `^${node.boost}`;
3757
+ return s2;
3758
+ }
3759
+ const inner = printNode(node.expression, depth + 1, maxLineLength, indent, whitespaceOperator);
3760
+ let s = `(
3761
+ ${indentLines(inner, depth + 1, indent)}
3762
+ ${pad})`;
3763
+ if (node.boost != null) s += `^${node.boost}`;
3764
+ return s;
3765
+ }
3766
+ case "FieldGroup": {
3767
+ let s = `${node.field}:(${inline(node.expression, whitespaceOperator)})`;
3768
+ if (node.boost != null) s += `^${node.boost}`;
3769
+ return s;
3770
+ }
3771
+ case "Not":
3772
+ return `NOT ${printNode(node.expression, depth, maxLineLength, indent, whitespaceOperator)}`;
3773
+ default:
3774
+ return inline(node, whitespaceOperator);
3775
+ }
3776
+ }
3777
+ function indentLines(text, depth, indent) {
3778
+ const pad = indent.repeat(depth);
3779
+ return text.split("\n").map((line) => {
3780
+ return line.startsWith(pad) ? line : pad + line;
3781
+ }).join("\n");
3782
+ }
3629
3783
  class UndoStack {
3630
3784
  constructor(maxSize = 100) {
3631
3785
  __publicField(this, "stack", []);
@@ -3806,12 +3960,14 @@ function ElasticInput(props) {
3806
3960
  const renderHistoryItem = dropdownConfig == null ? void 0 : dropdownConfig.renderHistoryItem;
3807
3961
  const renderSavedSearchItem = dropdownConfig == null ? void 0 : dropdownConfig.renderSavedSearchItem;
3808
3962
  const renderDropdownHeader = dropdownConfig == null ? void 0 : dropdownConfig.renderHeader;
3963
+ const renderNoResults = dropdownConfig == null ? void 0 : dropdownConfig.renderNoResults;
3809
3964
  const autoSelect = (dropdownConfig == null ? void 0 : dropdownConfig.autoSelect) ?? false;
3810
3965
  const homeEndKeys = (dropdownConfig == null ? void 0 : dropdownConfig.homeEndKeys) ?? false;
3811
3966
  const multiline = (featuresConfig == null ? void 0 : featuresConfig.multiline) !== false;
3812
3967
  const smartSelectAll = (featuresConfig == null ? void 0 : featuresConfig.smartSelectAll) ?? false;
3813
3968
  const expandSelection = (featuresConfig == null ? void 0 : featuresConfig.expandSelection) ?? false;
3814
3969
  const wildcardWrap = (featuresConfig == null ? void 0 : featuresConfig.wildcardWrap) ?? false;
3970
+ const enableFormatQuery = (featuresConfig == null ? void 0 : featuresConfig.formatQuery) ?? false;
3815
3971
  const lexerOptions = React.useMemo(() => ({
3816
3972
  savedSearches: enableSavedSearches,
3817
3973
  historySearch: enableHistorySearch
@@ -4044,6 +4200,24 @@ function ElasticInput(props) {
4044
4200
  return { ...s, customContent: custom };
4045
4201
  });
4046
4202
  }, [renderFieldHint]);
4203
+ const tryShowNoResults = React.useCallback((context) => {
4204
+ if (!renderNoResults) return false;
4205
+ const content = renderNoResults({ cursorContext: context, partial: context.partial });
4206
+ if (content == null) return false;
4207
+ const noResultsSuggestion = {
4208
+ text: "",
4209
+ label: "",
4210
+ type: "noResults",
4211
+ customContent: content,
4212
+ replaceStart: 0,
4213
+ replaceEnd: 0
4214
+ };
4215
+ setSuggestions([noResultsSuggestion]);
4216
+ setSelectedSuggestionIndex(-1);
4217
+ setShowDatePicker(false);
4218
+ showDropdownAtPosition(32, 300);
4219
+ return true;
4220
+ }, [renderNoResults]);
4047
4221
  const updateSuggestionsFromTokens = React.useCallback((toks, offset) => {
4048
4222
  var _a, _b, _c;
4049
4223
  const result = engineRef.current.getSuggestions(toks, offset);
@@ -4138,10 +4312,12 @@ function ElasticInput(props) {
4138
4312
  setAutocompleteContext(contextType);
4139
4313
  showDropdownAtPosition(newSuggestions.length * 32, 300);
4140
4314
  } else {
4141
- setShowDropdown(false);
4142
- setShowDatePicker(false);
4143
- setSuggestions([]);
4144
4315
  setAutocompleteContext(contextType);
4316
+ if (!tryShowNoResults(result.context)) {
4317
+ setShowDropdown(false);
4318
+ setShowDatePicker(false);
4319
+ setSuggestions([]);
4320
+ }
4145
4321
  }
4146
4322
  } else {
4147
4323
  const token = result.context.token;
@@ -4237,17 +4413,20 @@ function ElasticInput(props) {
4237
4413
  showDropdownAtPosition(mapped.length * 32, 300);
4238
4414
  } else {
4239
4415
  const syncResult = engineRef.current.getSuggestions(stateRef.current.tokens, stateRef.current.cursorOffset);
4240
- const hintSuggestions = applyFieldHint(
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);
4416
+ if (partial && tryShowNoResults(syncResult.context)) {
4248
4417
  } else {
4249
- setShowDropdown(false);
4250
- setSuggestions([]);
4418
+ const hintSuggestions = applyFieldHint(
4419
+ syncResult.suggestions.filter((s) => s.type === "hint"),
4420
+ syncResult.context
4421
+ );
4422
+ if (hintSuggestions.length > 0) {
4423
+ setSuggestions(hintSuggestions);
4424
+ setSelectedSuggestionIndex(syncResult.context.partial || autoSelect ? 0 : -1);
4425
+ showDropdownAtPosition(hintSuggestions.length * 32, 300);
4426
+ } else if (!tryShowNoResults(syncResult.context)) {
4427
+ setShowDropdown(false);
4428
+ setSuggestions([]);
4429
+ }
4251
4430
  }
4252
4431
  }
4253
4432
  } catch (e) {
@@ -4272,7 +4451,7 @@ function ElasticInput(props) {
4272
4451
  }
4273
4452
  }, debounceMs);
4274
4453
  }
4275
- }, [fetchSuggestionsProp, savedSearches, searchHistory, suggestDebounceMs, applyFieldHint, computeDropdownPosition, showDropdownAtPosition, dropdownAlignToInput, dropdownOpen, dropdownOpenIsCallback, dropdownMode, showOperators, effectiveMaxSuggestions, loadingDelay, autoSelect]);
4454
+ }, [fetchSuggestionsProp, savedSearches, searchHistory, suggestDebounceMs, applyFieldHint, computeDropdownPosition, showDropdownAtPosition, dropdownAlignToInput, dropdownOpen, dropdownOpenIsCallback, dropdownMode, showOperators, effectiveMaxSuggestions, loadingDelay, autoSelect, tryShowNoResults]);
4276
4455
  updateSuggestionsRef.current = updateSuggestionsFromTokens;
4277
4456
  const closeDropdown = React.useCallback(() => {
4278
4457
  var _a;
@@ -4636,6 +4815,14 @@ function ElasticInput(props) {
4636
4815
  if (onKeyDownProp) onKeyDownProp(e);
4637
4816
  if (e.defaultPrevented) return;
4638
4817
  const s = stateRef.current;
4818
+ if (enableFormatQuery && e.key === "F" && e.altKey && e.shiftKey && !e.ctrlKey && !e.metaKey) {
4819
+ e.preventDefault();
4820
+ const formatted = formatQuery(currentValueRef.current);
4821
+ if (formatted !== currentValueRef.current) {
4822
+ applyNewValue(formatted, formatted.length);
4823
+ }
4824
+ return;
4825
+ }
4639
4826
  if (editorRef.current && editorRef.current.childNodes.length > 40) {
4640
4827
  const sel = window.getSelection();
4641
4828
  const hasSelection = sel != null && !sel.isCollapsed;
@@ -4932,7 +5119,7 @@ function ElasticInput(props) {
4932
5119
  if (onSearch) onSearch(currentValueRef.current, s.ast);
4933
5120
  return;
4934
5121
  }
4935
- }, [onSearch, closeDropdown, acceptSuggestion, applyNewValue, restoreUndoEntry, multiline, dropdownOpenIsCallback, dropdownMode, updateSuggestionsFromTokens, onKeyDownProp, onTabProp, smartSelectAll, expandSelection, homeEndKeys, getDropdownPageSize]);
5122
+ }, [onSearch, closeDropdown, acceptSuggestion, applyNewValue, restoreUndoEntry, multiline, dropdownOpenIsCallback, dropdownMode, updateSuggestionsFromTokens, onKeyDownProp, onTabProp, smartSelectAll, expandSelection, homeEndKeys, getDropdownPageSize, enableFormatQuery]);
4936
5123
  const handleKeyUp = React.useCallback((e) => {
4937
5124
  if (keyConsumedByDropdownRef.current) {
4938
5125
  keyConsumedByDropdownRef.current = false;
@@ -5175,143 +5362,6 @@ function walk(node, groupField, out) {
5175
5362
  break;
5176
5363
  }
5177
5364
  }
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
5365
  export {
5316
5366
  AutocompleteEngine,
5317
5367
  DARK_COLORS,
@@ -24,6 +24,8 @@ export interface BooleanExprNode {
24
24
  operator: 'AND' | 'OR';
25
25
  left: ASTNode;
26
26
  right: ASTNode;
27
+ /** True when the operator was inferred from whitespace (no explicit AND/OR token). */
28
+ implicit?: boolean;
27
29
  start: number;
28
30
  end: number;
29
31
  }
package/dist/types.d.ts CHANGED
@@ -273,6 +273,13 @@ export interface DropdownConfig {
273
273
  * dropdown is open and an item is already selected. When no item is selected
274
274
  * (index = -1), the keys pass through for normal text cursor movement. @default false */
275
275
  homeEndKeys?: boolean;
276
+ /** Called when the engine returns zero suggestions. Return a React element to display
277
+ * in the dropdown (e.g. "No results for …"), or null/undefined to hide the dropdown.
278
+ * Not called during async loading (the spinner handles that). */
279
+ renderNoResults?: (context: {
280
+ cursorContext: CursorContext;
281
+ partial: string;
282
+ }) => React.ReactNode | null | undefined;
276
283
  }
277
284
  /**
278
285
  * Feature toggles for optional editing behaviors. All default to false except `multiline`.
@@ -286,6 +293,8 @@ export interface FeaturesConfig {
286
293
  expandSelection?: boolean;
287
294
  /** Pressing `*` with a single value token selected wraps it in wildcards. @default false */
288
295
  wildcardWrap?: boolean;
296
+ /** Enable Alt+Shift+F to pretty-print the query in-place using `formatQuery`. @default false */
297
+ formatQuery?: boolean;
289
298
  /** Enable `#name` saved-search syntax and autocomplete. When false, `#` is a regular character. @default false */
290
299
  savedSearches?: boolean;
291
300
  /** 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "elastic-input",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "description": "Syntax-aware smart autocomplete input for Elastic query syntax",
5
5
  "license": "MIT",
6
6
  "repository": {