elastic-input 0.3.0 → 0.3.2
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/autocomplete/suggestionTypes.d.ts +1 -1
- package/dist/elastic-input.es.js +52 -23
- package/dist/types.d.ts +10 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -139,6 +139,7 @@ interface FieldConfig {
|
|
|
139
139
|
operators?: string[]; // Allowed operators (future use)
|
|
140
140
|
description?: string; // Shown in autocomplete dropdown
|
|
141
141
|
placeholder?: string | false; // Hint shown while typing a value (false to suppress)
|
|
142
|
+
suggestions?: boolean; // Whether fetchSuggestions is called (default true)
|
|
142
143
|
}
|
|
143
144
|
```
|
|
144
145
|
|
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
|
+
historyDescription: "#656d76"
|
|
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
|
+
historyDescription: "#484f58"
|
|
1212
1214
|
};
|
|
1213
1215
|
const DEFAULT_STYLES = {
|
|
1214
1216
|
fontFamily: "'SF Mono', 'Fira Code', 'Fira Mono', Menlo, Consolas, monospace",
|
|
@@ -1530,7 +1532,7 @@ class AutocompleteEngine {
|
|
|
1530
1532
|
return this.searchHistory.slice(0, this.maxSuggestions).map((h) => ({
|
|
1531
1533
|
text: AutocompleteEngine.wrapHistoryQuery(h.query),
|
|
1532
1534
|
label: h.label || h.query,
|
|
1533
|
-
description: h.
|
|
1535
|
+
description: h.description,
|
|
1534
1536
|
type: "history",
|
|
1535
1537
|
replaceStart: start,
|
|
1536
1538
|
replaceEnd: end,
|
|
@@ -2799,7 +2801,7 @@ function AutocompleteDropdown({
|
|
|
2799
2801
|
whiteSpace: "normal",
|
|
2800
2802
|
wordBreak: "break-all",
|
|
2801
2803
|
width: "100%"
|
|
2802
|
-
} }, highlightMatch(suggestion.label, suggestion.matchPartial, isSelected)), /* @__PURE__ */ React.createElement("span", { style: { display: "flex", alignItems: "center", gap: "8px", width: "100%" } }, suggestion.description && /* @__PURE__ */ React.createElement("span", { className: "ei-dropdown-item-desc", style: { ...getDropdownItemDescStyle(), flex: 1 } }, suggestion.description), /* @__PURE__ */ React.createElement("span", { className: "ei-dropdown-item-type", style: { ...getDropdownItemTypeStyle(isSelected, mergedStyles), marginLeft: "auto" } }, "history")))
|
|
2804
|
+
} }, highlightMatch(suggestion.label, suggestion.matchPartial, isSelected)), /* @__PURE__ */ React.createElement("span", { style: { display: "flex", alignItems: "center", gap: "8px", width: "100%" } }, suggestion.description != null && /* @__PURE__ */ React.createElement("span", { className: "ei-dropdown-item-desc", style: { ...getDropdownItemDescStyle(), flex: 1, color: mergedColors.historyDescription, opacity: 1 } }, suggestion.description), /* @__PURE__ */ React.createElement("span", { className: "ei-dropdown-item-type", style: { ...getDropdownItemTypeStyle(isSelected, mergedStyles), marginLeft: "auto" } }, "history")))
|
|
2803
2805
|
);
|
|
2804
2806
|
}
|
|
2805
2807
|
if (suggestion.type === "savedSearch" && renderSavedSearchItem && suggestion.sourceData) {
|
|
@@ -3735,13 +3737,14 @@ function ElasticInput(props) {
|
|
|
3735
3737
|
const effectiveMaxSuggestions = maxSuggestions || DEFAULT_MAX_SUGGESTIONS;
|
|
3736
3738
|
const suggestDebounceMs = dropdownConfig == null ? void 0 : dropdownConfig.suggestDebounceMs;
|
|
3737
3739
|
const dropdownMaxHeightPx = parseInt((stylesProp == null ? void 0 : stylesProp.dropdownMaxHeight) || "300", 10) || 300;
|
|
3738
|
-
const enableSavedSearches = (featuresConfig == null ? void 0 : featuresConfig.savedSearches) ??
|
|
3739
|
-
const enableHistorySearch = (featuresConfig == null ? void 0 : featuresConfig.historySearch) ??
|
|
3740
|
+
const enableSavedSearches = (featuresConfig == null ? void 0 : featuresConfig.savedSearches) ?? !!savedSearches;
|
|
3741
|
+
const enableHistorySearch = (featuresConfig == null ? void 0 : featuresConfig.historySearch) ?? !!searchHistory;
|
|
3740
3742
|
const showSavedSearchHint = (dropdownConfig == null ? void 0 : dropdownConfig.showSavedSearchHint) ?? enableSavedSearches;
|
|
3741
3743
|
const showHistoryHint = (dropdownConfig == null ? void 0 : dropdownConfig.showHistoryHint) ?? enableHistorySearch;
|
|
3742
3744
|
const showOperators = (dropdownConfig == null ? void 0 : dropdownConfig.showOperators) !== false;
|
|
3743
3745
|
const triggerOnNavigation = (dropdownOpenIsCallback || dropdownMode !== "input") && (dropdownConfig == null ? void 0 : dropdownConfig.onNavigation) !== false;
|
|
3744
3746
|
const navigationDelay = (dropdownConfig == null ? void 0 : dropdownConfig.navigationDelay) ?? 0;
|
|
3747
|
+
const loadingDelay = (dropdownConfig == null ? void 0 : dropdownConfig.loadingDelay) ?? 0;
|
|
3745
3748
|
const renderFieldHint = dropdownConfig == null ? void 0 : dropdownConfig.renderFieldHint;
|
|
3746
3749
|
const renderHistoryItem = dropdownConfig == null ? void 0 : dropdownConfig.renderHistoryItem;
|
|
3747
3750
|
const renderSavedSearchItem = dropdownConfig == null ? void 0 : dropdownConfig.renderSavedSearchItem;
|
|
@@ -3786,6 +3789,7 @@ function ElasticInput(props) {
|
|
|
3786
3789
|
const abortControllerRef = React.useRef(null);
|
|
3787
3790
|
const highlightTimerRef = React.useRef(null);
|
|
3788
3791
|
const navDelayTimerRef = React.useRef(null);
|
|
3792
|
+
const loadingDelayTimerRef = React.useRef(null);
|
|
3789
3793
|
const asyncActiveRef = React.useRef(false);
|
|
3790
3794
|
const datePickerInitRef = React.useRef(null);
|
|
3791
3795
|
const datePickerReplaceRef = React.useRef(null);
|
|
@@ -3988,11 +3992,15 @@ function ElasticInput(props) {
|
|
|
3988
3992
|
}
|
|
3989
3993
|
}
|
|
3990
3994
|
const resolvedField = result.context.fieldName ? engineRef.current.resolveField(result.context.fieldName) : void 0;
|
|
3991
|
-
const willFetchAsync = !!(fetchSuggestionsProp && result.context.type === "FIELD_VALUE" && result.context.fieldName && (resolvedField == null ? void 0 : resolvedField.type) !== "boolean") || !!(typeof savedSearches === "function" && result.context.type === "SAVED_SEARCH") || !!(typeof searchHistory === "function" && result.context.type === "HISTORY_REF");
|
|
3995
|
+
const willFetchAsync = !!(fetchSuggestionsProp && result.context.type === "FIELD_VALUE" && result.context.fieldName && (resolvedField == null ? void 0 : resolvedField.type) !== "boolean" && (resolvedField == null ? void 0 : resolvedField.suggestions) !== false) || !!(typeof savedSearches === "function" && result.context.type === "SAVED_SEARCH") || !!(typeof searchHistory === "function" && result.context.type === "HISTORY_REF");
|
|
3992
3996
|
if (result.showDatePicker) {
|
|
3993
3997
|
asyncActiveRef.current = false;
|
|
3994
3998
|
(_a = abortControllerRef.current) == null ? void 0 : _a.abort();
|
|
3995
3999
|
if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
|
|
4000
|
+
if (loadingDelayTimerRef.current) {
|
|
4001
|
+
clearTimeout(loadingDelayTimerRef.current);
|
|
4002
|
+
loadingDelayTimerRef.current = null;
|
|
4003
|
+
}
|
|
3996
4004
|
const init = computeDatePickerInit(result.context, parseDateProp);
|
|
3997
4005
|
const prevInit = datePickerInitRef.current;
|
|
3998
4006
|
datePickerInitRef.current = init;
|
|
@@ -4020,6 +4028,10 @@ function ElasticInput(props) {
|
|
|
4020
4028
|
asyncActiveRef.current = false;
|
|
4021
4029
|
(_b = abortControllerRef.current) == null ? void 0 : _b.abort();
|
|
4022
4030
|
if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
|
|
4031
|
+
if (loadingDelayTimerRef.current) {
|
|
4032
|
+
clearTimeout(loadingDelayTimerRef.current);
|
|
4033
|
+
loadingDelayTimerRef.current = null;
|
|
4034
|
+
}
|
|
4023
4035
|
const newSuggestions = applyFieldHint(result.suggestions, result.context);
|
|
4024
4036
|
if (newSuggestions.length > 0) {
|
|
4025
4037
|
setSuggestions(newSuggestions);
|
|
@@ -4038,20 +4050,28 @@ function ElasticInput(props) {
|
|
|
4038
4050
|
const token = result.context.token;
|
|
4039
4051
|
const start = token ? token.start : offset;
|
|
4040
4052
|
const end = token ? token.end : offset;
|
|
4041
|
-
const
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4053
|
+
const showSpinner = () => {
|
|
4054
|
+
loadingDelayTimerRef.current = null;
|
|
4055
|
+
const loadingSuggestion = {
|
|
4056
|
+
text: "",
|
|
4057
|
+
label: "Searching...",
|
|
4058
|
+
type: "loading",
|
|
4059
|
+
replaceStart: start,
|
|
4060
|
+
replaceEnd: end
|
|
4061
|
+
};
|
|
4062
|
+
setSuggestions([loadingSuggestion]);
|
|
4063
|
+
if (!dropdownAlignToInput) setShowDropdown(false);
|
|
4064
|
+
setShowDatePicker(false);
|
|
4065
|
+
setSelectedSuggestionIndex(-1);
|
|
4066
|
+
showDropdownAtPosition(32, 300);
|
|
4048
4067
|
};
|
|
4049
|
-
|
|
4050
|
-
if (
|
|
4051
|
-
|
|
4052
|
-
|
|
4068
|
+
if (loadingDelayTimerRef.current) clearTimeout(loadingDelayTimerRef.current);
|
|
4069
|
+
if (loadingDelay > 0) {
|
|
4070
|
+
loadingDelayTimerRef.current = setTimeout(showSpinner, loadingDelay);
|
|
4071
|
+
} else {
|
|
4072
|
+
showSpinner();
|
|
4073
|
+
}
|
|
4053
4074
|
setAutocompleteContext(contextType);
|
|
4054
|
-
showDropdownAtPosition(32, 300);
|
|
4055
4075
|
}
|
|
4056
4076
|
if (willFetchAsync) {
|
|
4057
4077
|
const partial = result.context.partial;
|
|
@@ -4086,7 +4106,7 @@ function ElasticInput(props) {
|
|
|
4086
4106
|
mapped = fetched.map((h) => ({
|
|
4087
4107
|
text: AutocompleteEngine.wrapHistoryQuery(h.query),
|
|
4088
4108
|
label: h.label || h.query,
|
|
4089
|
-
description: h.
|
|
4109
|
+
description: h.description,
|
|
4090
4110
|
type: "history",
|
|
4091
4111
|
replaceStart: start,
|
|
4092
4112
|
replaceEnd: end,
|
|
@@ -4109,6 +4129,10 @@ function ElasticInput(props) {
|
|
|
4109
4129
|
matchPartial: partial
|
|
4110
4130
|
}));
|
|
4111
4131
|
}
|
|
4132
|
+
if (loadingDelayTimerRef.current) {
|
|
4133
|
+
clearTimeout(loadingDelayTimerRef.current);
|
|
4134
|
+
loadingDelayTimerRef.current = null;
|
|
4135
|
+
}
|
|
4112
4136
|
mapped = mapped.slice(0, effectiveMaxSuggestions);
|
|
4113
4137
|
if (mapped.length > 0) {
|
|
4114
4138
|
setSuggestions(mapped);
|
|
@@ -4130,6 +4154,10 @@ function ElasticInput(props) {
|
|
|
4130
4154
|
}
|
|
4131
4155
|
}
|
|
4132
4156
|
} catch (e) {
|
|
4157
|
+
if (loadingDelayTimerRef.current) {
|
|
4158
|
+
clearTimeout(loadingDelayTimerRef.current);
|
|
4159
|
+
loadingDelayTimerRef.current = null;
|
|
4160
|
+
}
|
|
4133
4161
|
if (!controller.signal.aborted) {
|
|
4134
4162
|
const errorMsg = e instanceof Error ? e.message : "Error loading suggestions";
|
|
4135
4163
|
const errorSuggestion = {
|
|
@@ -4147,7 +4175,7 @@ function ElasticInput(props) {
|
|
|
4147
4175
|
}
|
|
4148
4176
|
}, debounceMs);
|
|
4149
4177
|
}
|
|
4150
|
-
}, [fetchSuggestionsProp, savedSearches, searchHistory, suggestDebounceMs, applyFieldHint, computeDropdownPosition, showDropdownAtPosition, dropdownAlignToInput, dropdownOpen, dropdownOpenIsCallback, dropdownMode, showOperators, effectiveMaxSuggestions]);
|
|
4178
|
+
}, [fetchSuggestionsProp, savedSearches, searchHistory, suggestDebounceMs, applyFieldHint, computeDropdownPosition, showDropdownAtPosition, dropdownAlignToInput, dropdownOpen, dropdownOpenIsCallback, dropdownMode, showOperators, effectiveMaxSuggestions, loadingDelay]);
|
|
4151
4179
|
updateSuggestionsRef.current = updateSuggestionsFromTokens;
|
|
4152
4180
|
const closeDropdown = React.useCallback(() => {
|
|
4153
4181
|
var _a;
|
|
@@ -4348,6 +4376,7 @@ function ElasticInput(props) {
|
|
|
4348
4376
|
if (debounceTimerRef.current) clearTimeout(debounceTimerRef.current);
|
|
4349
4377
|
if (highlightTimerRef.current) clearTimeout(highlightTimerRef.current);
|
|
4350
4378
|
if (navDelayTimerRef.current) clearTimeout(navDelayTimerRef.current);
|
|
4379
|
+
if (loadingDelayTimerRef.current) clearTimeout(loadingDelayTimerRef.current);
|
|
4351
4380
|
(_a = abortControllerRef.current) == null ? void 0 : _a.abort();
|
|
4352
4381
|
};
|
|
4353
4382
|
}, []);
|
|
@@ -4387,7 +4416,7 @@ function ElasticInput(props) {
|
|
|
4387
4416
|
} else {
|
|
4388
4417
|
const rect = getCaretRect();
|
|
4389
4418
|
if (rect) {
|
|
4390
|
-
const height = s.showDatePicker ? 350 : s.suggestions.length * 32;
|
|
4419
|
+
const height = s.showDatePicker ? 350 : capDropdownHeight(s.suggestions.length * 32, dropdownMaxHeightPx);
|
|
4391
4420
|
setDropdownPosition(getDropdownPosition(rect, height, 300));
|
|
4392
4421
|
}
|
|
4393
4422
|
}
|
|
@@ -4398,7 +4427,7 @@ function ElasticInput(props) {
|
|
|
4398
4427
|
window.removeEventListener("resize", reposition);
|
|
4399
4428
|
window.removeEventListener("scroll", reposition, true);
|
|
4400
4429
|
};
|
|
4401
|
-
}, [dropdownAlignToInput]);
|
|
4430
|
+
}, [dropdownAlignToInput, dropdownMaxHeightPx]);
|
|
4402
4431
|
const prevParenMatchRef = React.useRef(null);
|
|
4403
4432
|
const prevColorsRef = React.useRef(colors);
|
|
4404
4433
|
React.useEffect(() => {
|
package/dist/types.d.ts
CHANGED
|
@@ -49,6 +49,8 @@ export interface FieldConfig {
|
|
|
49
49
|
description?: string;
|
|
50
50
|
/** Custom placeholder hint shown in the dropdown while typing a value for this field (e.g. "Search by company name..."). Overrides the default type-based hint. Set to `false` to suppress the hint entirely. */
|
|
51
51
|
placeholder?: string | false;
|
|
52
|
+
/** Whether `fetchSuggestions` should be called for this field. Defaults to `true`. Set to `false` to skip the async fetch entirely (no "Searching..." spinner, no dropdown). */
|
|
53
|
+
suggestions?: boolean;
|
|
52
54
|
}
|
|
53
55
|
/** A saved/named search that users can reference with `#name` syntax. */
|
|
54
56
|
export interface SavedSearch {
|
|
@@ -65,10 +67,10 @@ export interface SavedSearch {
|
|
|
65
67
|
export interface HistoryEntry {
|
|
66
68
|
/** The query string from the history entry. */
|
|
67
69
|
query: string;
|
|
68
|
-
/** Unix timestamp (ms) of when the query was executed. Used for ordering. */
|
|
69
|
-
timestamp?: number;
|
|
70
70
|
/** Optional label for display in the autocomplete dropdown. Falls back to `query`. */
|
|
71
71
|
label?: string;
|
|
72
|
+
/** Optional description shown below the label (e.g. date, category). Rendered as-is. */
|
|
73
|
+
description?: React.ReactNode;
|
|
72
74
|
}
|
|
73
75
|
/** An item returned by the async `fetchSuggestions` callback for field value autocomplete. */
|
|
74
76
|
export interface SuggestionItem {
|
|
@@ -138,6 +140,8 @@ export interface ColorConfig {
|
|
|
138
140
|
matchedParenBg?: string;
|
|
139
141
|
/** Warning-severity squiggly underlines (e.g. ambiguous precedence). */
|
|
140
142
|
warning?: string;
|
|
143
|
+
/** Color for history item descriptions. Set to `'transparent'` to hide. */
|
|
144
|
+
historyDescription?: string;
|
|
141
145
|
}
|
|
142
146
|
/**
|
|
143
147
|
* Structural and layout style overrides for the input and dropdown.
|
|
@@ -239,6 +243,10 @@ export interface DropdownConfig {
|
|
|
239
243
|
* immediate. If the user types before the delay elapses, the timer is cancelled.
|
|
240
244
|
* Ignored when `onNavigation` is false. @default 0 */
|
|
241
245
|
navigationDelay?: number;
|
|
246
|
+
/** Delay in ms before showing the "Searching..." spinner on first entry into an
|
|
247
|
+
* async field. If the fetch resolves before the delay, the spinner never appears.
|
|
248
|
+
* Subsequent keystrokes preserve previous results regardless of this setting. @default 0 */
|
|
249
|
+
loadingDelay?: number;
|
|
242
250
|
/** Custom renderer for field value hints. Return a React element for rich content,
|
|
243
251
|
* or null/undefined for the default hint. */
|
|
244
252
|
renderFieldHint?: (field: FieldConfig, partial: string) => React.ReactNode | null | undefined;
|