LyteNyte Grid logo for light mode. Links back to the documentation home page.
Expressions

Expression Editor

Add syntax highlighting and context-aware completions to expression inputs.

Note

This guide assumes familiarity with LyteNyte Grid’s expression evaluator. For more details, refer to the Expressions Overview guide.

LyteNyte Grid exports ExpressionEditor, a compound component that wraps a <textarea> with syntax highlighting and optional context-aware completions. Use the ExpressionEditor component anywhere users need to write expressions.

The demo below demonstrates the ExpressionEditor component fully configured using the standard expression language provided by LyteNyte Grid.

Complete Expression Editor

Result
2number
Illustrative Examples
Context
user
object
firstName"John"
string
lastName"Smith"
string
age23
number
location"London"
string
fullName() => …
fn
Fork code on stack blitzFork code on code sandbox

Expression Editor Anatomy

ExpressionEditor is a compound component assembled from four parts. You can omit the completion sub-components entirely to use ExpressionEditor as a pure syntax-highlighted input.

<ExpressionEditor.Root value={value} onChange={setValue} tokenize={tokenize} completionProvider={provider}>
<ExpressionEditor.CompletionPopover>
<ExpressionEditor.CompletionList>
{({ items }) =>
items.map((item, index) => (
<ExpressionEditor.CompletionListItem key={item.id} item={item} index={index}>
{item.label}
</ExpressionEditor.CompletionListItem>
))
}
</ExpressionEditor.CompletionList>
</ExpressionEditor.CompletionPopover>
</ExpressionEditor.Root>

Minimal Editor Setup

The only required props are value, onChange, and tokenize. Rendering ExpressionEditor.Root with no children gives you a fully functional syntax-highlighted input with no completion UI:

import { Evaluator, ExpressionEditor, standardPlugins } from "@1771technologies/lytenyte-pro/expressions";
import { useCallback, useState } from "react";
const evaluator = new Evaluator(standardPlugins);
function MyEditor() {
const [value, setValue] = useState("");
const tokenize = useCallback((s: string) => evaluator.tokensSafe(s, true), []);
return (
<div data-ln-input="true" style={{ height: 40 }}>
<ExpressionEditor.Root value={value} onChange={setValue} tokenize={tokenize} />
</div>
);
}

Pass tokenize to ExpressionEditor.Root to enable syntax highlighting without completion UI. Use tokensSafe instead of tokens so the editor continues to render partial syntax highlighting when the expression has incomplete syntax.

Basic Expression Editor

Result
7number
Illustrative Examples
Fork code on stack blitzFork code on code sandbox

Expression Editor Parts

Editor Root

ExpressionEditor.Root is the editor. It manages the textarea, token rendering, and completion state. Wrap ExpressionEditor.Root in a data-ln-input container to apply the correct sizing styles.

<div data-ln-input="true" style={{ height: 40 }}>
<ExpressionEditor.Root value={value} onChange={setValue} tokenize={tokenize} />
</div>

Completion Popover

ExpressionEditor.CompletionPopover renders a floating panel at the cursor using the native Popover API. The component opens and closes automatically based on the editor’s completion state, so you do not need to manage its visibility.

<ExpressionEditor.Root>
<ExpressionEditor.CompletionPopover className="rounded-lg border py-1">
...
</ExpressionEditor.CompletionPopover>
</ExpressionEditor.Root>

Completion List

ExpressionEditor.CompletionList renders the suggestion list through a render prop that receives the current items and a loading flag. Use the loading flag to show a spinner while an async provider resolves.

<ExpressionEditor.CompletionPopover>
<ExpressionEditor.CompletionList>
{({ items, loading }) =>
loading ? (
<Spinner />
) : (
items.map((item, index) => (
<ExpressionEditor.CompletionListItem key={item.id} item={item} index={index}>
{item.label}
</ExpressionEditor.CompletionListItem>
))
)
}
</ExpressionEditor.CompletionList>
</ExpressionEditor.CompletionPopover>

Completion List Item

ExpressionEditor.CompletionListItem renders a single suggestion. It handles mousedown selection and manages the aria-selected attribute for the currently highlighted item. Target the selected state with [aria-selected="true"] or aria-selected in Tailwind.

<ExpressionEditor.CompletionListItem key={item.id} item={item} index={index} className="px-3 py-1.5">
{item.label}
</ExpressionEditor.CompletionListItem>

Completion System

Built-in Provider

The createCompletionProvider utility builds a completion provider from a context object, the same object you pass to evaluator.run. Pass the result to the completionProvider prop:

import { createCompletionProvider } from "@1771technologies/lytenyte-pro/expressions";
const context = {
user: { firstName: "John", lastName: "Smith" },
age: 23,
fullName: () => context.user.firstName + " " + context.user.lastName,
};
const provider = createCompletionProvider(context);
<ExpressionEditor.Root completionProvider={provider} ... />

The provider inspects the token stream before the cursor to determine which suggestions to return:

Position Completions Shown
Top level (empty, or after an operator) All keys of the context object
After a complete value Binary operators (+, -, ==, !=, &&, ||, …)
identifier. Properties of that identifier resolved through the context
identifier.nested. Properties at the fully resolved path
"string". Built-in String methods (length, includes, slice, …)
[array]. Built-in Array methods (map, filter, reduce, …)

Completion Triggers

Completions open automatically from specific triggers, or users can open them manually:

  • Dot: Typing . triggers property access completions.
  • Space: Typing a space after a complete value triggers completions, which is useful for discovering available binary operators.
  • Manual: Pressing Ctrl+Space opens completions at the current cursor position

Once the list opens, it filters results in real time using a prefix match on item.label. Completions close when a user accepts a suggestion, presses Escape, or moves focus away from the editor.

Custom Provider

Implement the CompletionProvider signature directly when you need suggestions that createCompletionProvider does not cover:

import type { CompletionItem } from "@1771technologies/lytenyte-pro/expressions";
import type { Token } from "@1771technologies/lytenyte-pro/types";
const FIELDS: CompletionItem[] = [
{ label: "Revenue", kind: "number", id: "Revenue" },
{ label: "Region", kind: "string", id: "Region" },
{ label: "InStock", kind: "boolean", id: "InStock" },
];
function myProvider(tokens: Token[], cursorPosition: number): CompletionItem[] {
const relevant = tokens.filter(
(t) => t.end <= cursorPosition && t.type !== "EOF" && t.type !== "Whitespace",
);
let i = relevant.length - 1;
if (relevant[i]?.type === "Identifier") i--;
const prev = relevant[i];
// After a dot, return nothing (our fields have no sub-properties)
if (prev?.type === "Punctuation" && prev.value === ".") return [];
return FIELDS;
}

The provider can also return a Promise<CompletionItem[]> for async lookups such as server-side field searches.

In the demo below, the custom provider surfaces domain fields and formula functions. Press Ctrl + Space to open completions.

Formula Editor

Result
75780number
Illustrative Examples
Context
Revenue84200
number
Quantity34
number
Region"West"
string
Product"Laptop Pro"
string
Discount0.1
number
InStocktrue
boolean
Fork code on stack blitzFork code on code sandbox

Syntax Highlighting

The default highlighter maps each token type to a CSS variable. Override the highlight prop to replace the highlighter entirely:

import type { Token } from "@1771technologies/lytenyte-pro/types";
function MyHighlighter({ token }: { token: Token }) {
if (token.type === "Number")
return <span style={{ color: "cornflowerblue" }}>{token.value}</span>;
if (token.type === "String")
return <span style={{ color: "mediumseagreen" }}>{token.value}</span>;
if (token.type === "ExpressionError")
return <span style={{ textDecoration: "wavy underline red" }}>{token.value}</span>;
return <span>{token.value}</span>;
}
<ExpressionEditor.Root tokenize={tokenize} highlight={MyHighlighter} ... />

To restyle without replacing the highlighter, override the CSS custom properties in your theme:

Variable Token Types
--ln-expr-number Number
--ln-expr-string String, TemplateLiteral
--ln-expr-operator Operator, Punctuation, Spread, Pipe, Arrow
--ln-expr-identifier Identifier
--ln-expr-error ExpressionError
--ln-expr-punctuation Unparsed

Styling

Import the stylesheet once before rendering ExpressionEditor:

import "@1771technologies/lytenyte-pro/expression-editor.css";

Keyboard Reference

Key Completion open Completion closed
/ Navigate the list -
Enter or Tab Accept selected suggestion -
Escape Dismiss completions -
Ctrl+Space / ⌘+Space - Open completions manually

Expression Editor Properties

ExpressionEditor.Root

Prop

ExpressionEditor.CompletionList

Prop

ExpressionEditor.CompletionListItem

Prop

Next Steps