elastic-input 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +461 -0
  3. package/dist/autocomplete/AutocompleteEngine.d.ts +36 -0
  4. package/dist/autocomplete/suggestionTypes.d.ts +16 -0
  5. package/dist/components/AutocompleteDropdown.d.ts +24 -0
  6. package/dist/components/DatePicker.d.ts +9 -0
  7. package/dist/components/DateRangePicker.d.ts +16 -0
  8. package/dist/components/ElasticInput.d.ts +39 -0
  9. package/dist/components/HighlightedContent.d.ts +7 -0
  10. package/dist/components/ValidationSquiggles.d.ts +13 -0
  11. package/dist/constants.d.ts +18 -0
  12. package/dist/elastic-input.es.js +3670 -0
  13. package/dist/highlighting/parenMatch.d.ts +17 -0
  14. package/dist/highlighting/rangeHighlight.d.ts +10 -0
  15. package/dist/highlighting/regexHighlight.d.ts +10 -0
  16. package/dist/index.d.ts +18 -0
  17. package/dist/lexer/Lexer.d.ts +32 -0
  18. package/dist/lexer/tokens.d.ts +28 -0
  19. package/dist/parser/Parser.d.ts +37 -0
  20. package/dist/parser/ast.d.ts +97 -0
  21. package/dist/styles/inlineStyles.d.ts +19 -0
  22. package/dist/types.d.ts +383 -0
  23. package/dist/utils/cursorUtils.d.ts +16 -0
  24. package/dist/utils/dateUtils.d.ts +8 -0
  25. package/dist/utils/domUtils.d.ts +19 -0
  26. package/dist/utils/expandSelection.d.ts +20 -0
  27. package/dist/utils/extractValues.d.ts +26 -0
  28. package/dist/utils/smartSelect.d.ts +13 -0
  29. package/dist/utils/textUtils.d.ts +57 -0
  30. package/dist/utils/undoStack.d.ts +23 -0
  31. package/dist/validation/Validator.d.ts +39 -0
  32. package/dist/validation/dateValidator.d.ts +1 -0
  33. package/dist/validation/numberValidator.d.ts +1 -0
  34. package/package.json +74 -0
@@ -0,0 +1,16 @@
1
+ export declare function getCaretCharOffset(element: HTMLElement): number;
2
+ export declare function getSelectionCharRange(element: HTMLElement): {
3
+ start: number;
4
+ end: number;
5
+ };
6
+ export declare function setCaretCharOffset(element: HTMLElement, offset: number): void;
7
+ /** Set a non-collapsed selection range by character offsets within a contentEditable element. */
8
+ export declare function setSelectionCharRange(element: HTMLElement, start: number, end: number): void;
9
+ /**
10
+ * Find the DOM node and offset corresponding to a character offset,
11
+ * treating <br> elements as single newline characters.
12
+ */
13
+ export declare function findNodeAtOffset(parent: Node, targetOffset: number): {
14
+ node: Node;
15
+ offset: number;
16
+ } | null;
@@ -0,0 +1,8 @@
1
+ export declare function isValidDateString(value: string): boolean;
2
+ export declare function formatDate(date: Date): string;
3
+ export declare function parseDate(value: string): Date | null;
4
+ export declare function getDaysInMonth(year: number, month: number): number;
5
+ export declare function getFirstDayOfMonth(year: number, month: number): number;
6
+ export declare function isSameDay(a: Date, b: Date): boolean;
7
+ export declare function isDateInRange(date: Date, start: Date | null, end: Date | null): boolean;
8
+ export declare function getMonthName(month: number): string;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Insert text at the current selection/cursor, replacing any selected content.
3
+ * Uses the modern Range API instead of the deprecated document.execCommand.
4
+ */
5
+ export declare function insertTextAtCursor(text: string): void;
6
+ /**
7
+ * Insert a line break (<br>) at the current selection/cursor.
8
+ * Uses the modern Range API instead of the deprecated document.execCommand.
9
+ */
10
+ export declare function insertLineBreakAtCursor(): void;
11
+ export declare function getCaretRect(): DOMRect | null;
12
+ export declare function getContainerOffset(container: HTMLElement): {
13
+ top: number;
14
+ left: number;
15
+ };
16
+ export declare function getDropdownPosition(caretRect: DOMRect, dropdownHeight: number, dropdownWidth: number): {
17
+ top: number;
18
+ left: number;
19
+ };
@@ -0,0 +1,20 @@
1
+ import { ASTNode } from '../parser/ast';
2
+ import { Token } from '../lexer/tokens';
3
+
4
+ export interface SelectionRange {
5
+ start: number;
6
+ end: number;
7
+ }
8
+ /**
9
+ * Build the expansion hierarchy for a given cursor offset.
10
+ *
11
+ * Returns an array of ranges sorted smallest-to-largest, each
12
+ * representing one "expand selection" level:
13
+ * [0] = token under cursor
14
+ * [1] = innermost AST node
15
+ * ...
16
+ * [N] = root (entire query)
17
+ *
18
+ * Duplicate ranges are collapsed so each step widens the selection.
19
+ */
20
+ export declare function getExpansionRanges(ast: ASTNode | null, tokens: Token[], offset: number): SelectionRange[];
@@ -0,0 +1,26 @@
1
+ import { ASTNode } from '../parser/ast';
2
+
3
+ /**
4
+ * Describes a content value extracted from the AST.
5
+ *
6
+ * Kinds: term, field_value, range_lower, range_upper, group_term, regex.
7
+ */
8
+ export type ExtractedValueKind = 'term' | 'field_value' | 'range_lower' | 'range_upper' | 'group_term' | 'regex';
9
+ export interface ExtractedValue {
10
+ kind: ExtractedValueKind;
11
+ value: string;
12
+ quoted: boolean;
13
+ fieldName?: string;
14
+ start: number;
15
+ end: number;
16
+ }
17
+ /**
18
+ * Walk an AST and extract all content values, returning them as an array
19
+ * of ExtractedValue descriptors in document order.
20
+ *
21
+ * Excludes structural elements (operators, parens, field names, colons,
22
+ * boost/fuzzy markers, saved searches, history refs, and error nodes).
23
+ *
24
+ * Pure function -- no DOM or React dependency. Tree-shakeable if unused.
25
+ */
26
+ export declare function extractValues(ast: ASTNode | null): ExtractedValue[];
@@ -0,0 +1,13 @@
1
+ import { Token } from '../lexer/tokens';
2
+
3
+ /**
4
+ * Determine the selection range for a smart Ctrl+A press.
5
+ *
6
+ * Returns the token range if the cursor is inside an eligible token
7
+ * and the selection doesn't already cover it. Returns null when the
8
+ * caller should fall through to normal select-all behavior.
9
+ */
10
+ export declare function getSmartSelectRange(tokens: Token[], selStart: number, selEnd: number): {
11
+ start: number;
12
+ end: number;
13
+ } | null;
@@ -0,0 +1,57 @@
1
+ import { Token } from '../lexer/tokens';
2
+
3
+ /**
4
+ * Normalize typographic/smart characters to their ASCII equivalents.
5
+ * Handles smart quotes (from Outlook, Word, macOS auto-correct, etc.),
6
+ * em/en dashes, ellipsis, non-breaking spaces, and other common substitutions.
7
+ */
8
+ export declare function normalizeTypographicChars(text: string): string;
9
+ /** Bracket/quote pairs for selection wrapping. */
10
+ export declare const WRAP_PAIRS: Record<string, string>;
11
+ /**
12
+ * Wrap a selection range with an open/close pair.
13
+ * Returns the new text and cursor position (placed after the closing bracket).
14
+ */
15
+ export declare function wrapSelection(text: string, selStart: number, selEnd: number, openChar: string, closeChar: string): {
16
+ newValue: string;
17
+ newCursorPos: number;
18
+ newSelStart: number;
19
+ newSelEnd: number;
20
+ };
21
+ /**
22
+ * Get the plaintext content from a contenteditable element,
23
+ * converting `<br>` elements to newline characters.
24
+ *
25
+ * When the element has no text content (only `<br>` elements), returns `''`.
26
+ * Browsers leave a `<br>` artifact in empty contentEditable divs; this
27
+ * check prevents that phantom newline from persisting through the
28
+ * lex → highlight → innerHTML cycle.
29
+ */
30
+ export declare function getPlainText(element: HTMLElement): string;
31
+ /**
32
+ * Find which token index the cursor (character offset) falls within.
33
+ */
34
+ export declare function findTokenAtOffset(tokens: Token[], offset: number): number;
35
+ /**
36
+ * Find the token indices that a character range [start, end] spans.
37
+ * Returns [startIdx, endIdx]. Both are -1 if no token is found.
38
+ *
39
+ * At token boundaries (where one token ends and the next begins),
40
+ * startIdx resolves to the **later** token and endIdx to the **earlier**
41
+ * one, so a selection exactly covering a single token always returns
42
+ * matching indices.
43
+ */
44
+ export declare function getTokenIndexRange(tokens: Token[], start: number, end: number): [number, number];
45
+ /**
46
+ * Find the previous non-whitespace token index.
47
+ */
48
+ export declare function findPrevNonWsToken(tokens: Token[], fromIndex: number): number;
49
+ /**
50
+ * Get the text range for a given token to be replaced by autocomplete.
51
+ * For FIELD_NAME tokens followed by a COLON, extends the range to include the colon
52
+ * so that inserting "field:" doesn't produce "field::".
53
+ */
54
+ export declare function getReplacementRange(token: Token | undefined, cursorOffset: number, tokens?: Token[]): {
55
+ start: number;
56
+ end: number;
57
+ };
@@ -0,0 +1,23 @@
1
+ export interface UndoEntry {
2
+ value: string;
3
+ cursorPos: number;
4
+ /** When present and !== cursorPos, represents a selection range [selStart, cursorPos]. */
5
+ selStart?: number;
6
+ }
7
+ export declare class UndoStack {
8
+ private stack;
9
+ private index;
10
+ private maxSize;
11
+ constructor(maxSize?: number);
12
+ /** Push a new state. Clears any redo entries ahead of current position. */
13
+ push(entry: UndoEntry): void;
14
+ /** Replace the current entry (used for grouping consecutive typing). */
15
+ replaceCurrent(entry: UndoEntry): void;
16
+ undo(): UndoEntry | null;
17
+ redo(): UndoEntry | null;
18
+ canUndo(): boolean;
19
+ canRedo(): boolean;
20
+ current(): UndoEntry | null;
21
+ clear(): void;
22
+ get length(): number;
23
+ }
@@ -0,0 +1,39 @@
1
+ import { ASTNode } from '../parser/ast';
2
+ import { FieldConfig, ValidateReturn, ValidateValueContext } from '../types';
3
+
4
+ /** Callback type for external value validation. */
5
+ export type ValidateValueFn = (context: ValidateValueContext) => ValidateReturn;
6
+ /** A validation or syntax error with character offsets for squiggly underline display. */
7
+ export interface ValidationError {
8
+ /** Human-readable error description. */
9
+ message: string;
10
+ /** Start character offset (inclusive) in the input string. */
11
+ start: number;
12
+ /** End character offset (exclusive) in the input string. */
13
+ end: number;
14
+ /** The field name this error relates to, if applicable. */
15
+ field?: string;
16
+ /** Severity level. `'warning'` renders amber squiggles; `'error'` (default) renders red. */
17
+ severity?: 'error' | 'warning';
18
+ }
19
+ export declare class Validator {
20
+ private fields;
21
+ constructor(fields: FieldConfig[]);
22
+ validate(ast: ASTNode | null, validateValueFn?: ValidateValueFn): ValidationError[];
23
+ private walkNode;
24
+ /** Walk inside a FieldGroup, validating bare terms / field values against the group's field config. */
25
+ private walkFieldGroup;
26
+ /** Validate a value against a field config (shared between FieldValue and FieldGroup terms). */
27
+ private validateFieldValue;
28
+ /** Flag empty range bounds (e.g. `[TO]`, `[ TO ]`). */
29
+ private validateEmptyRangeBounds;
30
+ /** Validate range bounds against a field config. */
31
+ private validateRangeBounds;
32
+ /** Post-validation pass: detect mixed AND/OR without parentheses. */
33
+ private checkAmbiguousPrecedence;
34
+ private findAndFlagAmbiguity;
35
+ /** Collect all BooleanExpr operators at the same group level (stop at Group boundaries). */
36
+ private collectBoolOps;
37
+ /** Collect non-BooleanExpr children from a BooleanExpr chain. */
38
+ private collectLeaves;
39
+ }
@@ -0,0 +1 @@
1
+ export declare function validateDate(value: string): string | null;
@@ -0,0 +1 @@
1
+ export declare function validateNumber(value: string): string | null;
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "elastic-input",
3
+ "version": "0.1.0",
4
+ "description": "Syntax-aware smart autocomplete input for Elastic query syntax",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/krtools/elastic-input.git"
9
+ },
10
+ "homepage": "https://github.com/krtools/elastic-input#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/krtools/elastic-input/issues"
13
+ },
14
+ "keywords": [
15
+ "react",
16
+ "elasticsearch",
17
+ "query-string",
18
+ "autocomplete",
19
+ "search-input",
20
+ "syntax-highlighting",
21
+ "contenteditable"
22
+ ],
23
+ "sideEffects": false,
24
+ "main": "dist/elastic-input.es.js",
25
+ "module": "dist/elastic-input.es.js",
26
+ "types": "dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/elastic-input.es.js"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist"
35
+ ],
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "scripts": {
40
+ "dev": "vite --config vite.demo.config.js",
41
+ "build": "vite build",
42
+ "build:demo": "vite build --config vite.demo.config.js",
43
+ "deploy:pages": "npm run build:demo && gh-pages -d dist-demo",
44
+ "test": "vitest run",
45
+ "test:watch": "vitest",
46
+ "prepack": "npx tsc --noEmit && npm test && vite build"
47
+ },
48
+ "browserslist": [
49
+ "Chrome >= 115",
50
+ "Firefox >= 140",
51
+ "Safari >= 18.0",
52
+ "Edge >= 122",
53
+ "Samsung >= 23"
54
+ ],
55
+ "engines": {
56
+ "node": ">=18.0.0"
57
+ },
58
+ "peerDependencies": {
59
+ "react": ">=16.8.0",
60
+ "react-dom": ">=16.8.0"
61
+ },
62
+ "devDependencies": {
63
+ "@changesets/cli": "^2.30.0",
64
+ "@types/react": "^16.14.0",
65
+ "@types/react-dom": "^16.9.0",
66
+ "gh-pages": "^6.3.0",
67
+ "react": "^16.14.0",
68
+ "react-dom": "^16.14.0",
69
+ "typescript": "^5.3.0",
70
+ "vite": "^5.4.0",
71
+ "vite-plugin-dts": "^3.9.0",
72
+ "vitest": "^4.1.0"
73
+ }
74
+ }