@1771technologies/lytenyte-pro 2.0.4-dev.2 → 2.0.4-dev.4
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/css/components/expression-editor.css +105 -0
- package/dist/components/column-manager/column-manager.d.ts +6 -1
- package/dist/components/column-manager/column-manager.js +23 -2
- package/dist/components/expressions-editor/(expressions)/expression.play.d.ts +4 -0
- package/dist/components/expressions-editor/(expressions)/expression.play.js +69 -0
- package/dist/components/expressions-editor/create-completion-provider.d.ts +3 -0
- package/dist/components/expressions-editor/create-completion-provider.js +161 -0
- package/dist/components/expressions-editor/create-key-down-handler.d.ts +18 -0
- package/dist/components/expressions-editor/create-key-down-handler.js +49 -0
- package/dist/components/expressions-editor/cursor-position/measure-cursor-at-pointer.d.ts +2 -0
- package/dist/components/expressions-editor/cursor-position/measure-cursor-at-pointer.js +56 -0
- package/dist/components/expressions-editor/cursor-position/use-cursor-position.d.ts +5 -0
- package/dist/components/expressions-editor/cursor-position/use-cursor-position.js +17 -0
- package/dist/components/expressions-editor/expression-editor.d.ts +3 -0
- package/dist/components/expressions-editor/expression-editor.js +138 -0
- package/dist/components/expressions-editor/get-indentation.d.ts +1 -0
- package/dist/components/expressions-editor/get-indentation.js +7 -0
- package/dist/components/expressions-editor/handle-completion-key-down.d.ts +2 -0
- package/dist/components/expressions-editor/handle-completion-key-down.js +22 -0
- package/dist/components/expressions-editor/handle-enter-key.d.ts +4 -0
- package/dist/components/expressions-editor/handle-enter-key.js +11 -0
- package/dist/components/expressions-editor/handle-manual-trigger.d.ts +2 -0
- package/dist/components/expressions-editor/handle-manual-trigger.js +3 -0
- package/dist/components/expressions-editor/index.d.ts +17 -0
- package/dist/components/expressions-editor/index.js +11 -0
- package/dist/components/expressions-editor/intellisence/completion-context.d.ts +16 -0
- package/dist/components/expressions-editor/intellisence/completion-context.js +7 -0
- package/dist/components/expressions-editor/intellisence/completion-item.d.ts +5 -0
- package/dist/components/expressions-editor/intellisence/completion-item.js +11 -0
- package/dist/components/expressions-editor/intellisence/completion-list.d.ts +8 -0
- package/dist/components/expressions-editor/intellisence/completion-list.js +18 -0
- package/dist/components/expressions-editor/intellisence/completion-popover.d.ts +1 -0
- package/dist/components/expressions-editor/intellisence/completion-popover.js +30 -0
- package/dist/components/expressions-editor/intellisence/filter-completions-by-prefix.d.ts +2 -0
- package/dist/components/expressions-editor/intellisence/filter-completions-by-prefix.js +6 -0
- package/dist/components/expressions-editor/intellisence/get-word-at-cursor.d.ts +2 -0
- package/dist/components/expressions-editor/intellisence/get-word-at-cursor.js +12 -0
- package/dist/components/expressions-editor/intellisence/use-complete-trigger.d.ts +13 -0
- package/dist/components/expressions-editor/intellisence/use-complete-trigger.js +41 -0
- package/dist/components/expressions-editor/intellisence/use-completions.d.ts +14 -0
- package/dist/components/expressions-editor/intellisence/use-completions.js +78 -0
- package/dist/components/expressions-editor/replace-word-at-cursor.d.ts +5 -0
- package/dist/components/expressions-editor/replace-word-at-cursor.js +7 -0
- package/dist/components/expressions-editor/token-highlighter-default.d.ts +4 -0
- package/dist/components/expressions-editor/token-highlighter-default.js +18 -0
- package/dist/components/expressions-editor/types.d.ts +47 -0
- package/dist/components/expressions-editor/types.js +1 -0
- package/dist/components/expressions-editor/use-keyboard-navigation.d.ts +10 -0
- package/dist/components/expressions-editor/use-keyboard-navigation.js +16 -0
- package/dist/components/pill-manager/container.js +55 -9
- package/dist/components/pill-manager/item.js +15 -1
- package/dist/components/select-all.js +1 -1
- package/dist/components/tree-view/root.d.ts +9 -0
- package/dist/components/tree-view/root.js +4 -2
- package/dist/data-source-client/hooks/use-flattened-groups.js +2 -2
- package/dist/expressions/compiler/compile.d.ts +3 -0
- package/dist/expressions/compiler/compile.js +4 -0
- package/dist/expressions/compiler/eval-constant.d.ts +2 -0
- package/dist/expressions/compiler/eval-constant.js +19 -0
- package/dist/expressions/compiler/is-constant.d.ts +2 -0
- package/dist/expressions/compiler/is-constant.js +6 -0
- package/dist/expressions/compiler/make-constant.d.ts +2 -0
- package/dist/expressions/compiler/make-constant.js +11 -0
- package/dist/expressions/compiler/optimize.d.ts +3 -0
- package/dist/expressions/compiler/optimize.js +47 -0
- package/dist/expressions/compiler/value-of.d.ts +2 -0
- package/dist/expressions/compiler/value-of.js +11 -0
- package/dist/expressions/errors/clamp-offset.d.ts +1 -0
- package/dist/expressions/errors/clamp-offset.js +3 -0
- package/dist/expressions/errors/expression-error.d.ts +10 -0
- package/dist/expressions/errors/expression-error.js +19 -0
- package/dist/expressions/errors/format-error.d.ts +3 -0
- package/dist/expressions/errors/format-error.js +27 -0
- package/dist/expressions/errors/get-line-end.d.ts +1 -0
- package/dist/expressions/errors/get-line-end.js +8 -0
- package/dist/expressions/errors/get-line-start.d.ts +1 -0
- package/dist/expressions/errors/get-line-start.js +8 -0
- package/dist/expressions/errors/index.d.ts +7 -0
- package/dist/expressions/errors/index.js +6 -0
- package/dist/expressions/errors/offset-to-position.d.ts +2 -0
- package/dist/expressions/errors/offset-to-position.js +17 -0
- package/dist/expressions/errors/types.d.ts +10 -0
- package/dist/expressions/errors/types.js +1 -0
- package/dist/expressions/evaluator/evaluate-binary.d.ts +4 -0
- package/dist/expressions/evaluator/evaluate-binary.js +21 -0
- package/dist/expressions/evaluator/evaluate-unary.d.ts +2 -0
- package/dist/expressions/evaluator/evaluate-unary.js +10 -0
- package/dist/expressions/evaluator/evaluate.d.ts +15 -0
- package/dist/expressions/evaluator/evaluate.js +62 -0
- package/dist/expressions/index.d.ts +5 -0
- package/dist/expressions/index.js +3 -0
- package/dist/expressions/lexer/predicates/is-alpha-numeric.d.ts +1 -0
- package/dist/expressions/lexer/predicates/is-alpha-numeric.js +5 -0
- package/dist/expressions/lexer/predicates/is-alpha.d.ts +1 -0
- package/dist/expressions/lexer/predicates/is-alpha.js +3 -0
- package/dist/expressions/lexer/predicates/is-binary-digit.d.ts +1 -0
- package/dist/expressions/lexer/predicates/is-binary-digit.js +3 -0
- package/dist/expressions/lexer/predicates/is-digit.d.ts +1 -0
- package/dist/expressions/lexer/predicates/is-digit.js +3 -0
- package/dist/expressions/lexer/predicates/is-hex-digit.d.ts +1 -0
- package/dist/expressions/lexer/predicates/is-hex-digit.js +4 -0
- package/dist/expressions/lexer/predicates/is-octal-digit.d.ts +1 -0
- package/dist/expressions/lexer/predicates/is-octal-digit.js +3 -0
- package/dist/expressions/lexer/tokenize/consume-digits.d.ts +1 -0
- package/dist/expressions/lexer/tokenize/consume-digits.js +5 -0
- package/dist/expressions/lexer/tokenize/scan-identifier.d.ts +6 -0
- package/dist/expressions/lexer/tokenize/scan-identifier.js +20 -0
- package/dist/expressions/lexer/tokenize/scan-number.d.ts +4 -0
- package/dist/expressions/lexer/tokenize/scan-number.js +32 -0
- package/dist/expressions/lexer/tokenize/scan-operator.d.ts +6 -0
- package/dist/expressions/lexer/tokenize/scan-operator.js +71 -0
- package/dist/expressions/lexer/tokenize/scan-string-literal.d.ts +4 -0
- package/dist/expressions/lexer/tokenize/scan-string-literal.js +22 -0
- package/dist/expressions/lexer/tokenize/scan-string.d.ts +4 -0
- package/dist/expressions/lexer/tokenize/scan-string.js +98 -0
- package/dist/expressions/lexer/tokenize/scan-template-literal.d.ts +4 -0
- package/dist/expressions/lexer/tokenize/scan-template-literal.js +51 -0
- package/dist/expressions/lexer/tokenize/tokenize.d.ts +4 -0
- package/dist/expressions/lexer/tokenize/tokenize.js +99 -0
- package/dist/expressions/lexer/types.d.ts +12 -0
- package/dist/expressions/lexer/types.js +1 -0
- package/dist/expressions/parser/parse-array-literal.d.ts +3 -0
- package/dist/expressions/parser/parse-array-literal.js +15 -0
- package/dist/expressions/parser/parse-expression.d.ts +3 -0
- package/dist/expressions/parser/parse-expression.js +74 -0
- package/dist/expressions/parser/parse-object-literal.d.ts +3 -0
- package/dist/expressions/parser/parse-object-literal.js +69 -0
- package/dist/expressions/parser/parse-pipe-transform.d.ts +3 -0
- package/dist/expressions/parser/parse-pipe-transform.js +26 -0
- package/dist/expressions/parser/parse-postfix.d.ts +3 -0
- package/dist/expressions/parser/parse-postfix.js +23 -0
- package/dist/expressions/parser/parse-primary.d.ts +3 -0
- package/dist/expressions/parser/parse-primary.js +35 -0
- package/dist/expressions/parser/parse-template-literal.d.ts +3 -0
- package/dist/expressions/parser/parse-template-literal.js +41 -0
- package/dist/expressions/parser/parse-unary.d.ts +3 -0
- package/dist/expressions/parser/parse-unary.js +28 -0
- package/dist/expressions/parser/parse.d.ts +3 -0
- package/dist/expressions/parser/parse.js +26 -0
- package/dist/expressions/parser/parser-context.d.ts +12 -0
- package/dist/expressions/parser/parser-context.js +14 -0
- package/dist/expressions/parser/precedence.d.ts +1 -0
- package/dist/expressions/parser/precedence.js +8 -0
- package/dist/expressions/parser/types.d.ts +97 -0
- package/dist/expressions/parser/types.js +1 -0
- package/dist/expressions/plugin.d.ts +20 -0
- package/dist/expressions/plugin.js +1 -0
- package/dist/expressions/plugins/access.d.ts +2 -0
- package/dist/expressions/plugins/access.js +138 -0
- package/dist/expressions/plugins/arrows.d.ts +2 -0
- package/dist/expressions/plugins/arrows.js +83 -0
- package/dist/expressions/plugins/booleans.d.ts +2 -0
- package/dist/expressions/plugins/booleans.js +34 -0
- package/dist/expressions/plugins/collections.d.ts +2 -0
- package/dist/expressions/plugins/collections.js +70 -0
- package/dist/expressions/plugins/comparison.d.ts +2 -0
- package/dist/expressions/plugins/comparison.js +51 -0
- package/dist/expressions/plugins/logical.d.ts +2 -0
- package/dist/expressions/plugins/logical.js +103 -0
- package/dist/expressions/plugins/membership.d.ts +2 -0
- package/dist/expressions/plugins/membership.js +71 -0
- package/dist/expressions/plugins/pipe.d.ts +2 -0
- package/dist/expressions/plugins/pipe.js +57 -0
- package/dist/expressions/plugins/resolved-identifier.d.ts +5 -0
- package/dist/expressions/plugins/resolved-identifier.js +19 -0
- package/dist/expressions/plugins/standard.d.ts +13 -0
- package/dist/expressions/plugins/standard.js +23 -0
- package/dist/expressions/plugins/strings.d.ts +2 -0
- package/dist/expressions/plugins/strings.js +44 -0
- package/dist/expressions/plugins/ternary.d.ts +2 -0
- package/dist/expressions/plugins/ternary.js +58 -0
- package/dist/expressions.d.ts +2 -0
- package/dist/expressions.js +2 -0
- package/dist/index.d.ts +1 -0
- package/package.json +11 -5
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
@layer ln-grid {
|
|
2
|
+
[data-ln-expression-editor] {
|
|
3
|
+
--ln-expr-number: light-dark(#b45309, #fab387);
|
|
4
|
+
--ln-expr-string: light-dark(#15803d, #a6e3a1);
|
|
5
|
+
--ln-expr-keyword: light-dark(#7c3aed, #cba6f7);
|
|
6
|
+
--ln-expr-identifier: light-dark(#0369a1, #89b4fa);
|
|
7
|
+
--ln-expr-operator: light-dark(#be185d, #f38ba8);
|
|
8
|
+
--ln-expr-punctuation: light-dark(#6b7280, #6c7086);
|
|
9
|
+
--ln-expr-error: light-dark(#dc2626, #f38ba8);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/* These must be identical otherwise the highlight will not match the input. */
|
|
13
|
+
[data-ln-expression-editor] {
|
|
14
|
+
font-family:
|
|
15
|
+
ui-monospace, "Cascadia Code", "Segoe UI Mono", "Liberation Mono", Menlo, Monaco, Consolas, monospace;
|
|
16
|
+
font-size: 16px;
|
|
17
|
+
white-space: nowrap;
|
|
18
|
+
word-break: normal;
|
|
19
|
+
overflow-wrap: normal;
|
|
20
|
+
tab-size: 2;
|
|
21
|
+
box-sizing: border-box;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
[data-ln-expression-input] {
|
|
25
|
+
caret-color: var(--ln-text);
|
|
26
|
+
box-sizing: border-box;
|
|
27
|
+
|
|
28
|
+
transition-property: border-color;
|
|
29
|
+
transition-duration: var(--ln-transition-duration);
|
|
30
|
+
transition-timing-function: var(--ln-transition-fn);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Completion popover */
|
|
34
|
+
@keyframes ln-expr-popover-in {
|
|
35
|
+
from {
|
|
36
|
+
opacity: 0;
|
|
37
|
+
translate: 0 -4px;
|
|
38
|
+
}
|
|
39
|
+
to {
|
|
40
|
+
opacity: 1;
|
|
41
|
+
translate: 0 0;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
[data-ln-expression-popover] {
|
|
46
|
+
background-color: var(--ln-bg-popover);
|
|
47
|
+
border: 1px solid var(--ln-border-popover);
|
|
48
|
+
border-radius: var(--ln-radius-md);
|
|
49
|
+
box-shadow: var(--ln-shadow-400);
|
|
50
|
+
padding: var(--ln-space-05);
|
|
51
|
+
min-width: 200px;
|
|
52
|
+
max-width: 360px;
|
|
53
|
+
box-sizing: border-box;
|
|
54
|
+
overflow: hidden;
|
|
55
|
+
|
|
56
|
+
&:popover-open {
|
|
57
|
+
animation: ln-expr-popover-in var(--ln-transition-duration) var(--ln-transition-fn) both;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
[data-ln-expression-completion-list] {
|
|
62
|
+
list-style: none;
|
|
63
|
+
margin: 0;
|
|
64
|
+
padding: 0;
|
|
65
|
+
max-height: 220px;
|
|
66
|
+
overflow-y: auto;
|
|
67
|
+
overscroll-behavior: contain;
|
|
68
|
+
|
|
69
|
+
/* Thin scrollbar */
|
|
70
|
+
scrollbar-width: thin;
|
|
71
|
+
scrollbar-color: var(--ln-border-strong) transparent;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
[data-ln-expression-completion-item] {
|
|
75
|
+
display: flex;
|
|
76
|
+
align-items: center;
|
|
77
|
+
gap: var(--ln-space-10);
|
|
78
|
+
padding-block: var(--ln-space-05);
|
|
79
|
+
padding-inline: var(--ln-space-10);
|
|
80
|
+
border-radius: var(--ln-radius);
|
|
81
|
+
font-family:
|
|
82
|
+
ui-monospace, "Cascadia Code", "Segoe UI Mono", "Liberation Mono", Menlo, Monaco, Consolas, monospace;
|
|
83
|
+
font-size: var(--ln-font-sm);
|
|
84
|
+
color: var(--ln-text);
|
|
85
|
+
cursor: pointer;
|
|
86
|
+
user-select: none;
|
|
87
|
+
|
|
88
|
+
transition-property: background-color, color;
|
|
89
|
+
transition-duration: var(--ln-transition-duration);
|
|
90
|
+
transition-timing-function: var(--ln-transition-fn);
|
|
91
|
+
|
|
92
|
+
&:hover {
|
|
93
|
+
background-color: var(--ln-bg-row-hover);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
&[data-ln-selected] {
|
|
97
|
+
background-color: var(--ln-primary-10);
|
|
98
|
+
color: var(--ln-text-dark);
|
|
99
|
+
|
|
100
|
+
&:hover {
|
|
101
|
+
background-color: var(--ln-primary-10);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -9,5 +9,10 @@ export interface ColumnManagerProps<Spec extends GridSpec = GridSpec> {
|
|
|
9
9
|
columns: Column<Spec>[];
|
|
10
10
|
row: RowNode<Spec["data"]>;
|
|
11
11
|
}) => ReactNode;
|
|
12
|
+
/**
|
|
13
|
+
* @alpha
|
|
14
|
+
* Use with caution.
|
|
15
|
+
*/
|
|
16
|
+
readonly getPillManagerTag?: (column: Column<Spec>) => Record<string, string>;
|
|
12
17
|
}
|
|
13
|
-
export declare function ColumnManager<Spec extends GridSpec = GridSpec>({ columns: provided, base, onColumnsChange, endElement, }: ColumnManagerProps<Spec>): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
export declare function ColumnManager<Spec extends GridSpec = GridSpec>({ columns: provided, base, onColumnsChange, endElement, getPillManagerTag, }: ColumnManagerProps<Spec>): import("react/jsx-runtime").JSX.Element;
|
|
@@ -3,7 +3,7 @@ import { useMemo } from "react";
|
|
|
3
3
|
import { TreeView } from "../tree-view/index.js";
|
|
4
4
|
import { Checkbox } from "../checkbox/checkbox.js";
|
|
5
5
|
import { computePathMatrix } from "@1771technologies/lytenyte-shared";
|
|
6
|
-
export function ColumnManager({ columns: provided, base, onColumnsChange, endElement, }) {
|
|
6
|
+
export function ColumnManager({ columns: provided, base, onColumnsChange, endElement, getPillManagerTag, }) {
|
|
7
7
|
const nonAdjacentSplit = useMemo(() => {
|
|
8
8
|
const paths = computePathMatrix(provided);
|
|
9
9
|
const adjusted = [];
|
|
@@ -24,7 +24,28 @@ export function ColumnManager({ columns: provided, base, onColumnsChange, endEle
|
|
|
24
24
|
const items = useMemo(() => {
|
|
25
25
|
return nonAdjacentSplit.map((x) => ({ id: x.id, path: x.groupPath ?? [], name: x.name, column: x }));
|
|
26
26
|
}, [nonAdjacentSplit]);
|
|
27
|
-
|
|
27
|
+
const getDragItems = useMemo(() => {
|
|
28
|
+
if (!getPillManagerTag)
|
|
29
|
+
return undefined;
|
|
30
|
+
return (row, leafs) => {
|
|
31
|
+
if (leafs.length || row.kind !== "leaf")
|
|
32
|
+
return {};
|
|
33
|
+
const column = row.data.column;
|
|
34
|
+
const mapTags = Object.entries(getPillManagerTag(column));
|
|
35
|
+
if (!mapTags.length)
|
|
36
|
+
return {};
|
|
37
|
+
return {
|
|
38
|
+
pill: {
|
|
39
|
+
kind: "site",
|
|
40
|
+
data: {
|
|
41
|
+
id: "__external__",
|
|
42
|
+
item: Object.fromEntries(mapTags),
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
};
|
|
47
|
+
}, [getPillManagerTag]);
|
|
48
|
+
return (_jsx(TreeView, { items: items, rowSelectionEnabled: false, draggable: true, rowHeight: 30, rowSelectAllShow: false, getDragTags: getDragItems, defaultExpansion: true, onItemsReordered: (x) => {
|
|
28
49
|
const columns = x.map((x) => provided.find((p) => p.id === x.column.id));
|
|
29
50
|
onColumnsChange(columns);
|
|
30
51
|
}, children: ({ row, leafs, toggle, dragProps, isOver, isBefore }) => {
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useCallback, useMemo } from "react";
|
|
3
|
+
import { createCompletionProvider, ExpressionEditor } from "../index.js";
|
|
4
|
+
import { Evaluator, standardPlugins } from "../../../expressions/index.js";
|
|
5
|
+
import "../../../../css/light-dark.css";
|
|
6
|
+
import "../../../../css/components/expression-editor.css";
|
|
7
|
+
import "./expression.css";
|
|
8
|
+
import { CompletionPopover } from "../intellisence/completion-popover.js";
|
|
9
|
+
import { CompletionList } from "../intellisence/completion-list.js";
|
|
10
|
+
import { CompletionListItem } from "../intellisence/completion-item.js";
|
|
11
|
+
const CONTEXT = {
|
|
12
|
+
user: {
|
|
13
|
+
name: "Alice",
|
|
14
|
+
age: 30,
|
|
15
|
+
email: "alice@example.com",
|
|
16
|
+
address: {
|
|
17
|
+
city: "Portland",
|
|
18
|
+
state: "OR",
|
|
19
|
+
zip: "97201",
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
order: {
|
|
23
|
+
total: 99.99,
|
|
24
|
+
status: "shipped",
|
|
25
|
+
items: 3,
|
|
26
|
+
},
|
|
27
|
+
Math: {
|
|
28
|
+
PI: 3.14159,
|
|
29
|
+
abs: "function",
|
|
30
|
+
max: "function",
|
|
31
|
+
min: "function",
|
|
32
|
+
round: "function",
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
const EXAMPLES = [
|
|
36
|
+
{
|
|
37
|
+
label: "Property access",
|
|
38
|
+
value: "user.address.city",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
label: "Math",
|
|
42
|
+
value: "Math.round(order.total * 1.08)",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
label: "Conditional",
|
|
46
|
+
value: "if order.total > 100 and user.age >= 18",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
label: "Object literal",
|
|
50
|
+
value: "{name: user.name, total: order.total, tax: order.total * 0.08}",
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
label: "Error token",
|
|
54
|
+
value: "user.name or undefined",
|
|
55
|
+
},
|
|
56
|
+
];
|
|
57
|
+
export default function App() {
|
|
58
|
+
const [value, setValue] = useState("user.name");
|
|
59
|
+
const evaluator = useMemo(() => new Evaluator(standardPlugins), []);
|
|
60
|
+
const tokenize = useCallback((input) => evaluator.tokensSafe(input, true), [evaluator]);
|
|
61
|
+
const provider = useMemo(() => {
|
|
62
|
+
return createCompletionProvider(CONTEXT);
|
|
63
|
+
}, []);
|
|
64
|
+
return (_jsxs("div", { className: "demo-container", children: [_jsx("label", { className: "demo-label", children: "Single-line" }), _jsx(ExpressionEditor.Root, { value: value, onChange: setValue, tokenize: tokenize, completionProvider: provider, placeholder: "Type an expression...", children: _jsx(CompletionPopover, { children: _jsx(CompletionList, { children: ({ items }) => {
|
|
65
|
+
return items.map((item, index) => {
|
|
66
|
+
return (_jsxs(CompletionListItem, { item: item, index: index, className: "completion-item", children: [_jsx("span", { className: "completion-kind", children: item.kind }), _jsx("span", { className: "completion-label", children: item.label })] }, item.id));
|
|
67
|
+
});
|
|
68
|
+
} }) }) }), _jsx("div", { className: "examples-row", children: EXAMPLES.map((ex) => (_jsx("button", { className: `example-btn${value === ex.value ? "active" : ""}`, onClick: () => setValue(ex.value), children: ex.label }, ex.label))) })] }));
|
|
69
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
const STRING_METHODS = [
|
|
2
|
+
{ label: "at", kind: "function" },
|
|
3
|
+
{ label: "charAt", kind: "function" },
|
|
4
|
+
{ label: "charCodeAt", kind: "function" },
|
|
5
|
+
{ label: "endsWith", kind: "function" },
|
|
6
|
+
{ label: "includes", kind: "function" },
|
|
7
|
+
{ label: "indexOf", kind: "function" },
|
|
8
|
+
{ label: "lastIndexOf", kind: "function" },
|
|
9
|
+
{ label: "length", kind: "number" },
|
|
10
|
+
{ label: "match", kind: "function" },
|
|
11
|
+
{ label: "padEnd", kind: "function" },
|
|
12
|
+
{ label: "padStart", kind: "function" },
|
|
13
|
+
{ label: "repeat", kind: "function" },
|
|
14
|
+
{ label: "replace", kind: "function" },
|
|
15
|
+
{ label: "replaceAll", kind: "function" },
|
|
16
|
+
{ label: "slice", kind: "function" },
|
|
17
|
+
{ label: "split", kind: "function" },
|
|
18
|
+
{ label: "startsWith", kind: "function" },
|
|
19
|
+
{ label: "substring", kind: "function" },
|
|
20
|
+
{ label: "toLowerCase", kind: "function" },
|
|
21
|
+
{ label: "toUpperCase", kind: "function" },
|
|
22
|
+
{ label: "trim", kind: "function" },
|
|
23
|
+
{ label: "trimEnd", kind: "function" },
|
|
24
|
+
{ label: "trimStart", kind: "function" },
|
|
25
|
+
];
|
|
26
|
+
const ARRAY_METHODS = [
|
|
27
|
+
{ label: "at", kind: "function" },
|
|
28
|
+
{ label: "concat", kind: "function" },
|
|
29
|
+
{ label: "entries", kind: "function" },
|
|
30
|
+
{ label: "every", kind: "function" },
|
|
31
|
+
{ label: "filter", kind: "function" },
|
|
32
|
+
{ label: "find", kind: "function" },
|
|
33
|
+
{ label: "findIndex", kind: "function" },
|
|
34
|
+
{ label: "flat", kind: "function" },
|
|
35
|
+
{ label: "flatMap", kind: "function" },
|
|
36
|
+
{ label: "forEach", kind: "function" },
|
|
37
|
+
{ label: "includes", kind: "function" },
|
|
38
|
+
{ label: "indexOf", kind: "function" },
|
|
39
|
+
{ label: "join", kind: "function" },
|
|
40
|
+
{ label: "keys", kind: "function" },
|
|
41
|
+
{ label: "length", kind: "number" },
|
|
42
|
+
{ label: "map", kind: "function" },
|
|
43
|
+
{ label: "reduce", kind: "function" },
|
|
44
|
+
{ label: "reduceRight", kind: "function" },
|
|
45
|
+
{ label: "reverse", kind: "function" },
|
|
46
|
+
{ label: "slice", kind: "function" },
|
|
47
|
+
{ label: "some", kind: "function" },
|
|
48
|
+
{ label: "sort", kind: "function" },
|
|
49
|
+
{ label: "values", kind: "function" },
|
|
50
|
+
];
|
|
51
|
+
function isDot(token) {
|
|
52
|
+
return (token.type === "Punctuation" && token.value === ".") || token.type === "OptionalChain";
|
|
53
|
+
}
|
|
54
|
+
function isClosingBracket(token) {
|
|
55
|
+
return token.type === "Punctuation" && token.value === "]";
|
|
56
|
+
}
|
|
57
|
+
function isStringLiteral(token) {
|
|
58
|
+
return token.type === "String" || token.type === "TemplateLiteral";
|
|
59
|
+
}
|
|
60
|
+
function analyzeTokens(tokens, cursorPosition) {
|
|
61
|
+
const relevant = tokens.filter((t) => t.end <= cursorPosition && t.type !== "EOF" && t.type !== "Whitespace");
|
|
62
|
+
if (relevant.length === 0)
|
|
63
|
+
return { kind: "top-level" };
|
|
64
|
+
let i = relevant.length - 1;
|
|
65
|
+
// Skip the partial word the user is currently typing
|
|
66
|
+
if (relevant[i].type === "Identifier")
|
|
67
|
+
i--;
|
|
68
|
+
if (i < 0)
|
|
69
|
+
return { kind: "top-level" };
|
|
70
|
+
// If the token before the word isn't a dot, we're at the top level
|
|
71
|
+
if (!isDot(relevant[i]))
|
|
72
|
+
return { kind: "top-level" };
|
|
73
|
+
// Step over the dot
|
|
74
|
+
i--;
|
|
75
|
+
if (i < 0)
|
|
76
|
+
return { kind: "none" };
|
|
77
|
+
const beforeDot = relevant[i];
|
|
78
|
+
// "hello". or `hello`. → string methods
|
|
79
|
+
if (isStringLiteral(beforeDot))
|
|
80
|
+
return { kind: "string-literal" };
|
|
81
|
+
// [1,2,3]. → array methods
|
|
82
|
+
if (isClosingBracket(beforeDot))
|
|
83
|
+
return { kind: "array-literal" };
|
|
84
|
+
// identifier chain: walk back through alternating Identifier / dot tokens
|
|
85
|
+
if (beforeDot.type === "Identifier") {
|
|
86
|
+
const path = [beforeDot.value];
|
|
87
|
+
i--;
|
|
88
|
+
while (i >= 1) {
|
|
89
|
+
if (!isDot(relevant[i]))
|
|
90
|
+
break;
|
|
91
|
+
i--;
|
|
92
|
+
if (relevant[i].type !== "Identifier")
|
|
93
|
+
break;
|
|
94
|
+
path.unshift(relevant[i].value);
|
|
95
|
+
i--;
|
|
96
|
+
}
|
|
97
|
+
return { kind: "context-path", path };
|
|
98
|
+
}
|
|
99
|
+
return { kind: "none" };
|
|
100
|
+
}
|
|
101
|
+
function resolveValue(context, path) {
|
|
102
|
+
let current = context;
|
|
103
|
+
for (const key of path) {
|
|
104
|
+
if (current == null || typeof current !== "object")
|
|
105
|
+
return undefined;
|
|
106
|
+
current = current[key];
|
|
107
|
+
}
|
|
108
|
+
return current;
|
|
109
|
+
}
|
|
110
|
+
function kindOf(value) {
|
|
111
|
+
if (typeof value === "function")
|
|
112
|
+
return "function";
|
|
113
|
+
if (Array.isArray(value))
|
|
114
|
+
return "array";
|
|
115
|
+
if (typeof value === "string")
|
|
116
|
+
return "string";
|
|
117
|
+
if (typeof value === "number")
|
|
118
|
+
return "number";
|
|
119
|
+
if (typeof value === "boolean")
|
|
120
|
+
return "boolean";
|
|
121
|
+
if (typeof value === "object" && value !== null)
|
|
122
|
+
return "object";
|
|
123
|
+
return "unknown";
|
|
124
|
+
}
|
|
125
|
+
function objectCompletions(obj) {
|
|
126
|
+
return Object.entries(obj).map(([key, val]) => ({
|
|
127
|
+
label: key,
|
|
128
|
+
kind: kindOf(val),
|
|
129
|
+
id: key,
|
|
130
|
+
}));
|
|
131
|
+
}
|
|
132
|
+
function builtinCompletions(methods) {
|
|
133
|
+
return methods.map((m) => ({ label: m.label, kind: m.kind, id: m.label }));
|
|
134
|
+
}
|
|
135
|
+
export function createCompletionProvider(context) {
|
|
136
|
+
return function completionProvider(tokens, cursorPosition) {
|
|
137
|
+
const analysis = analyzeTokens(tokens, cursorPosition);
|
|
138
|
+
switch (analysis.kind) {
|
|
139
|
+
case "top-level":
|
|
140
|
+
return objectCompletions(context);
|
|
141
|
+
case "string-literal":
|
|
142
|
+
return builtinCompletions(STRING_METHODS);
|
|
143
|
+
case "array-literal":
|
|
144
|
+
return builtinCompletions(ARRAY_METHODS);
|
|
145
|
+
case "context-path": {
|
|
146
|
+
const value = resolveValue(context, analysis.path);
|
|
147
|
+
if (value == null)
|
|
148
|
+
return [];
|
|
149
|
+
if (typeof value === "string")
|
|
150
|
+
return builtinCompletions(STRING_METHODS);
|
|
151
|
+
if (Array.isArray(value))
|
|
152
|
+
return builtinCompletions(ARRAY_METHODS);
|
|
153
|
+
if (typeof value === "object")
|
|
154
|
+
return objectCompletions(value);
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
case "none":
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { KeyboardEvent } from "react";
|
|
2
|
+
import type { CompletionItem, KeybindingConfig, WordAtCursor } from "./types.js";
|
|
3
|
+
interface KeyDownDeps {
|
|
4
|
+
readonly isPopoverOpen: boolean;
|
|
5
|
+
readonly multiline: boolean;
|
|
6
|
+
readonly keybindings?: KeybindingConfig;
|
|
7
|
+
readonly selectedItem: CompletionItem | null;
|
|
8
|
+
readonly wordAtCursor: WordAtCursor | null;
|
|
9
|
+
readonly onAcceptCompletion: (item: CompletionItem, word: WordAtCursor) => void;
|
|
10
|
+
readonly onNavigate: (direction: "up" | "down") => void;
|
|
11
|
+
readonly onDismiss: () => void;
|
|
12
|
+
readonly onCancelTrigger: () => void;
|
|
13
|
+
readonly onManualTrigger: () => void;
|
|
14
|
+
readonly onValueChange: (value: string) => void;
|
|
15
|
+
readonly onExternalKeyDown?: (e: KeyboardEvent<HTMLTextAreaElement>) => void;
|
|
16
|
+
}
|
|
17
|
+
export declare function createKeyDownHandler(deps: KeyDownDeps): (e: KeyboardEvent<HTMLTextAreaElement>) => void;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { handleCompletionKeyDown } from "./handle-completion-key-down.js";
|
|
2
|
+
import { handleEnterKey } from "./handle-enter-key.js";
|
|
3
|
+
import { isManualTrigger } from "./handle-manual-trigger.js";
|
|
4
|
+
export function createKeyDownHandler(deps) {
|
|
5
|
+
return (e) => {
|
|
6
|
+
if (deps.onExternalKeyDown) {
|
|
7
|
+
deps.onExternalKeyDown(e);
|
|
8
|
+
if (e.defaultPrevented)
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
if (deps.isPopoverOpen) {
|
|
12
|
+
const action = handleCompletionKeyDown(e.key, deps.keybindings);
|
|
13
|
+
if (action.type === "navigate") {
|
|
14
|
+
e.preventDefault();
|
|
15
|
+
deps.onNavigate(action.direction);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (action.type === "accept" && deps.selectedItem && deps.wordAtCursor) {
|
|
19
|
+
e.preventDefault();
|
|
20
|
+
deps.onAcceptCompletion(deps.selectedItem, deps.wordAtCursor);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
if (action.type === "dismiss") {
|
|
24
|
+
e.preventDefault();
|
|
25
|
+
deps.onDismiss();
|
|
26
|
+
deps.onCancelTrigger();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (isManualTrigger(e)) {
|
|
31
|
+
e.preventDefault();
|
|
32
|
+
deps.onManualTrigger();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (e.key === "Enter") {
|
|
36
|
+
const { value, selectionStart, selectionEnd } = e.currentTarget;
|
|
37
|
+
const result = handleEnterKey(value, selectionStart, selectionEnd, deps.multiline);
|
|
38
|
+
if (result === null) {
|
|
39
|
+
e.preventDefault();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
e.preventDefault();
|
|
43
|
+
e.currentTarget.value = result.value;
|
|
44
|
+
e.currentTarget.selectionStart = result.cursorPosition;
|
|
45
|
+
e.currentTarget.selectionEnd = result.cursorPosition;
|
|
46
|
+
deps.onValueChange(result.value);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export function measureCursorPosition(textarea, cursorIndex) {
|
|
2
|
+
const mirror = document.createElement("div");
|
|
3
|
+
const computed = window.getComputedStyle(textarea);
|
|
4
|
+
const propertiesToCopy = [
|
|
5
|
+
"fontFamily",
|
|
6
|
+
"fontSize",
|
|
7
|
+
"fontWeight",
|
|
8
|
+
"fontStyle",
|
|
9
|
+
"letterSpacing",
|
|
10
|
+
"lineHeight",
|
|
11
|
+
"textTransform",
|
|
12
|
+
"wordSpacing",
|
|
13
|
+
"textIndent",
|
|
14
|
+
"paddingTop",
|
|
15
|
+
"paddingRight",
|
|
16
|
+
"paddingBottom",
|
|
17
|
+
"paddingLeft",
|
|
18
|
+
"borderTopWidth",
|
|
19
|
+
"borderRightWidth",
|
|
20
|
+
"borderBottomWidth",
|
|
21
|
+
"borderLeftWidth",
|
|
22
|
+
"boxSizing",
|
|
23
|
+
"whiteSpace",
|
|
24
|
+
"wordBreak",
|
|
25
|
+
"overflowWrap",
|
|
26
|
+
"tabSize",
|
|
27
|
+
];
|
|
28
|
+
mirror.style.position = "absolute";
|
|
29
|
+
mirror.style.top = "-9999px";
|
|
30
|
+
mirror.style.left = "-9999px";
|
|
31
|
+
mirror.style.visibility = "hidden";
|
|
32
|
+
mirror.style.overflow = "hidden";
|
|
33
|
+
mirror.style.width = computed.width;
|
|
34
|
+
for (const prop of propertiesToCopy) {
|
|
35
|
+
mirror.style[prop] = computed[prop];
|
|
36
|
+
}
|
|
37
|
+
const textBeforeCursor = textarea.value.substring(0, cursorIndex);
|
|
38
|
+
const textNode = document.createTextNode(textBeforeCursor);
|
|
39
|
+
const marker = document.createElement("span");
|
|
40
|
+
marker.textContent = "\u200b";
|
|
41
|
+
mirror.appendChild(textNode);
|
|
42
|
+
mirror.appendChild(marker);
|
|
43
|
+
document.body.appendChild(mirror);
|
|
44
|
+
try {
|
|
45
|
+
const textareaRect = textarea.getBoundingClientRect();
|
|
46
|
+
const markerRect = marker.getBoundingClientRect();
|
|
47
|
+
const mirrorRect = mirror.getBoundingClientRect();
|
|
48
|
+
const top = textareaRect.top + (markerRect.top - mirrorRect.top) - textarea.scrollTop;
|
|
49
|
+
const left = textareaRect.left + (markerRect.left - mirrorRect.left) - textarea.scrollLeft;
|
|
50
|
+
const lineHeight = parseFloat(computed.lineHeight) || markerRect.height;
|
|
51
|
+
return { top, left, lineHeight };
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
document.body.removeChild(mirror);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { useState, useCallback } from "react";
|
|
2
|
+
import { measureCursorPosition } from "./measure-cursor-at-pointer.js";
|
|
3
|
+
export function useCursorPosition(textareaRef) {
|
|
4
|
+
const [coordinates, setCoordinates] = useState({
|
|
5
|
+
top: 0,
|
|
6
|
+
left: 0,
|
|
7
|
+
lineHeight: 22,
|
|
8
|
+
});
|
|
9
|
+
const update = useCallback(() => {
|
|
10
|
+
const textarea = textareaRef.current;
|
|
11
|
+
if (!textarea)
|
|
12
|
+
return;
|
|
13
|
+
const coords = measureCursorPosition(textarea, textarea.selectionStart);
|
|
14
|
+
setCoordinates(coords);
|
|
15
|
+
}, [textareaRef]);
|
|
16
|
+
return { coordinates, update };
|
|
17
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ExpressionEditorProps } from "./types";
|
|
2
|
+
import type { PropsWithChildren } from "react";
|
|
3
|
+
export declare function ExpressionEditor({ value, onChange: onValueChange, tokenize, highlight: Highlighter, completionProvider, placeholder, disabled, readOnly, className, style, children, }: PropsWithChildren<ExpressionEditorProps>): import("react/jsx-runtime").JSX.Element;
|