@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.
Files changed (175) hide show
  1. package/css/components/expression-editor.css +105 -0
  2. package/dist/components/column-manager/column-manager.d.ts +6 -1
  3. package/dist/components/column-manager/column-manager.js +23 -2
  4. package/dist/components/expressions-editor/(expressions)/expression.play.d.ts +4 -0
  5. package/dist/components/expressions-editor/(expressions)/expression.play.js +69 -0
  6. package/dist/components/expressions-editor/create-completion-provider.d.ts +3 -0
  7. package/dist/components/expressions-editor/create-completion-provider.js +161 -0
  8. package/dist/components/expressions-editor/create-key-down-handler.d.ts +18 -0
  9. package/dist/components/expressions-editor/create-key-down-handler.js +49 -0
  10. package/dist/components/expressions-editor/cursor-position/measure-cursor-at-pointer.d.ts +2 -0
  11. package/dist/components/expressions-editor/cursor-position/measure-cursor-at-pointer.js +56 -0
  12. package/dist/components/expressions-editor/cursor-position/use-cursor-position.d.ts +5 -0
  13. package/dist/components/expressions-editor/cursor-position/use-cursor-position.js +17 -0
  14. package/dist/components/expressions-editor/expression-editor.d.ts +3 -0
  15. package/dist/components/expressions-editor/expression-editor.js +138 -0
  16. package/dist/components/expressions-editor/get-indentation.d.ts +1 -0
  17. package/dist/components/expressions-editor/get-indentation.js +7 -0
  18. package/dist/components/expressions-editor/handle-completion-key-down.d.ts +2 -0
  19. package/dist/components/expressions-editor/handle-completion-key-down.js +22 -0
  20. package/dist/components/expressions-editor/handle-enter-key.d.ts +4 -0
  21. package/dist/components/expressions-editor/handle-enter-key.js +11 -0
  22. package/dist/components/expressions-editor/handle-manual-trigger.d.ts +2 -0
  23. package/dist/components/expressions-editor/handle-manual-trigger.js +3 -0
  24. package/dist/components/expressions-editor/index.d.ts +17 -0
  25. package/dist/components/expressions-editor/index.js +11 -0
  26. package/dist/components/expressions-editor/intellisence/completion-context.d.ts +16 -0
  27. package/dist/components/expressions-editor/intellisence/completion-context.js +7 -0
  28. package/dist/components/expressions-editor/intellisence/completion-item.d.ts +5 -0
  29. package/dist/components/expressions-editor/intellisence/completion-item.js +11 -0
  30. package/dist/components/expressions-editor/intellisence/completion-list.d.ts +8 -0
  31. package/dist/components/expressions-editor/intellisence/completion-list.js +18 -0
  32. package/dist/components/expressions-editor/intellisence/completion-popover.d.ts +1 -0
  33. package/dist/components/expressions-editor/intellisence/completion-popover.js +30 -0
  34. package/dist/components/expressions-editor/intellisence/filter-completions-by-prefix.d.ts +2 -0
  35. package/dist/components/expressions-editor/intellisence/filter-completions-by-prefix.js +6 -0
  36. package/dist/components/expressions-editor/intellisence/get-word-at-cursor.d.ts +2 -0
  37. package/dist/components/expressions-editor/intellisence/get-word-at-cursor.js +12 -0
  38. package/dist/components/expressions-editor/intellisence/use-complete-trigger.d.ts +13 -0
  39. package/dist/components/expressions-editor/intellisence/use-complete-trigger.js +41 -0
  40. package/dist/components/expressions-editor/intellisence/use-completions.d.ts +14 -0
  41. package/dist/components/expressions-editor/intellisence/use-completions.js +78 -0
  42. package/dist/components/expressions-editor/replace-word-at-cursor.d.ts +5 -0
  43. package/dist/components/expressions-editor/replace-word-at-cursor.js +7 -0
  44. package/dist/components/expressions-editor/token-highlighter-default.d.ts +4 -0
  45. package/dist/components/expressions-editor/token-highlighter-default.js +18 -0
  46. package/dist/components/expressions-editor/types.d.ts +47 -0
  47. package/dist/components/expressions-editor/types.js +1 -0
  48. package/dist/components/expressions-editor/use-keyboard-navigation.d.ts +10 -0
  49. package/dist/components/expressions-editor/use-keyboard-navigation.js +16 -0
  50. package/dist/components/pill-manager/container.js +55 -9
  51. package/dist/components/pill-manager/item.js +15 -1
  52. package/dist/components/select-all.js +1 -1
  53. package/dist/components/tree-view/root.d.ts +9 -0
  54. package/dist/components/tree-view/root.js +4 -2
  55. package/dist/data-source-client/hooks/use-flattened-groups.js +2 -2
  56. package/dist/expressions/compiler/compile.d.ts +3 -0
  57. package/dist/expressions/compiler/compile.js +4 -0
  58. package/dist/expressions/compiler/eval-constant.d.ts +2 -0
  59. package/dist/expressions/compiler/eval-constant.js +19 -0
  60. package/dist/expressions/compiler/is-constant.d.ts +2 -0
  61. package/dist/expressions/compiler/is-constant.js +6 -0
  62. package/dist/expressions/compiler/make-constant.d.ts +2 -0
  63. package/dist/expressions/compiler/make-constant.js +11 -0
  64. package/dist/expressions/compiler/optimize.d.ts +3 -0
  65. package/dist/expressions/compiler/optimize.js +47 -0
  66. package/dist/expressions/compiler/value-of.d.ts +2 -0
  67. package/dist/expressions/compiler/value-of.js +11 -0
  68. package/dist/expressions/errors/clamp-offset.d.ts +1 -0
  69. package/dist/expressions/errors/clamp-offset.js +3 -0
  70. package/dist/expressions/errors/expression-error.d.ts +10 -0
  71. package/dist/expressions/errors/expression-error.js +19 -0
  72. package/dist/expressions/errors/format-error.d.ts +3 -0
  73. package/dist/expressions/errors/format-error.js +27 -0
  74. package/dist/expressions/errors/get-line-end.d.ts +1 -0
  75. package/dist/expressions/errors/get-line-end.js +8 -0
  76. package/dist/expressions/errors/get-line-start.d.ts +1 -0
  77. package/dist/expressions/errors/get-line-start.js +8 -0
  78. package/dist/expressions/errors/index.d.ts +7 -0
  79. package/dist/expressions/errors/index.js +6 -0
  80. package/dist/expressions/errors/offset-to-position.d.ts +2 -0
  81. package/dist/expressions/errors/offset-to-position.js +17 -0
  82. package/dist/expressions/errors/types.d.ts +10 -0
  83. package/dist/expressions/errors/types.js +1 -0
  84. package/dist/expressions/evaluator/evaluate-binary.d.ts +4 -0
  85. package/dist/expressions/evaluator/evaluate-binary.js +21 -0
  86. package/dist/expressions/evaluator/evaluate-unary.d.ts +2 -0
  87. package/dist/expressions/evaluator/evaluate-unary.js +10 -0
  88. package/dist/expressions/evaluator/evaluate.d.ts +15 -0
  89. package/dist/expressions/evaluator/evaluate.js +62 -0
  90. package/dist/expressions/index.d.ts +5 -0
  91. package/dist/expressions/index.js +3 -0
  92. package/dist/expressions/lexer/predicates/is-alpha-numeric.d.ts +1 -0
  93. package/dist/expressions/lexer/predicates/is-alpha-numeric.js +5 -0
  94. package/dist/expressions/lexer/predicates/is-alpha.d.ts +1 -0
  95. package/dist/expressions/lexer/predicates/is-alpha.js +3 -0
  96. package/dist/expressions/lexer/predicates/is-binary-digit.d.ts +1 -0
  97. package/dist/expressions/lexer/predicates/is-binary-digit.js +3 -0
  98. package/dist/expressions/lexer/predicates/is-digit.d.ts +1 -0
  99. package/dist/expressions/lexer/predicates/is-digit.js +3 -0
  100. package/dist/expressions/lexer/predicates/is-hex-digit.d.ts +1 -0
  101. package/dist/expressions/lexer/predicates/is-hex-digit.js +4 -0
  102. package/dist/expressions/lexer/predicates/is-octal-digit.d.ts +1 -0
  103. package/dist/expressions/lexer/predicates/is-octal-digit.js +3 -0
  104. package/dist/expressions/lexer/tokenize/consume-digits.d.ts +1 -0
  105. package/dist/expressions/lexer/tokenize/consume-digits.js +5 -0
  106. package/dist/expressions/lexer/tokenize/scan-identifier.d.ts +6 -0
  107. package/dist/expressions/lexer/tokenize/scan-identifier.js +20 -0
  108. package/dist/expressions/lexer/tokenize/scan-number.d.ts +4 -0
  109. package/dist/expressions/lexer/tokenize/scan-number.js +32 -0
  110. package/dist/expressions/lexer/tokenize/scan-operator.d.ts +6 -0
  111. package/dist/expressions/lexer/tokenize/scan-operator.js +71 -0
  112. package/dist/expressions/lexer/tokenize/scan-string-literal.d.ts +4 -0
  113. package/dist/expressions/lexer/tokenize/scan-string-literal.js +22 -0
  114. package/dist/expressions/lexer/tokenize/scan-string.d.ts +4 -0
  115. package/dist/expressions/lexer/tokenize/scan-string.js +98 -0
  116. package/dist/expressions/lexer/tokenize/scan-template-literal.d.ts +4 -0
  117. package/dist/expressions/lexer/tokenize/scan-template-literal.js +51 -0
  118. package/dist/expressions/lexer/tokenize/tokenize.d.ts +4 -0
  119. package/dist/expressions/lexer/tokenize/tokenize.js +99 -0
  120. package/dist/expressions/lexer/types.d.ts +12 -0
  121. package/dist/expressions/lexer/types.js +1 -0
  122. package/dist/expressions/parser/parse-array-literal.d.ts +3 -0
  123. package/dist/expressions/parser/parse-array-literal.js +15 -0
  124. package/dist/expressions/parser/parse-expression.d.ts +3 -0
  125. package/dist/expressions/parser/parse-expression.js +74 -0
  126. package/dist/expressions/parser/parse-object-literal.d.ts +3 -0
  127. package/dist/expressions/parser/parse-object-literal.js +69 -0
  128. package/dist/expressions/parser/parse-pipe-transform.d.ts +3 -0
  129. package/dist/expressions/parser/parse-pipe-transform.js +26 -0
  130. package/dist/expressions/parser/parse-postfix.d.ts +3 -0
  131. package/dist/expressions/parser/parse-postfix.js +23 -0
  132. package/dist/expressions/parser/parse-primary.d.ts +3 -0
  133. package/dist/expressions/parser/parse-primary.js +35 -0
  134. package/dist/expressions/parser/parse-template-literal.d.ts +3 -0
  135. package/dist/expressions/parser/parse-template-literal.js +41 -0
  136. package/dist/expressions/parser/parse-unary.d.ts +3 -0
  137. package/dist/expressions/parser/parse-unary.js +28 -0
  138. package/dist/expressions/parser/parse.d.ts +3 -0
  139. package/dist/expressions/parser/parse.js +26 -0
  140. package/dist/expressions/parser/parser-context.d.ts +12 -0
  141. package/dist/expressions/parser/parser-context.js +14 -0
  142. package/dist/expressions/parser/precedence.d.ts +1 -0
  143. package/dist/expressions/parser/precedence.js +8 -0
  144. package/dist/expressions/parser/types.d.ts +97 -0
  145. package/dist/expressions/parser/types.js +1 -0
  146. package/dist/expressions/plugin.d.ts +20 -0
  147. package/dist/expressions/plugin.js +1 -0
  148. package/dist/expressions/plugins/access.d.ts +2 -0
  149. package/dist/expressions/plugins/access.js +138 -0
  150. package/dist/expressions/plugins/arrows.d.ts +2 -0
  151. package/dist/expressions/plugins/arrows.js +83 -0
  152. package/dist/expressions/plugins/booleans.d.ts +2 -0
  153. package/dist/expressions/plugins/booleans.js +34 -0
  154. package/dist/expressions/plugins/collections.d.ts +2 -0
  155. package/dist/expressions/plugins/collections.js +70 -0
  156. package/dist/expressions/plugins/comparison.d.ts +2 -0
  157. package/dist/expressions/plugins/comparison.js +51 -0
  158. package/dist/expressions/plugins/logical.d.ts +2 -0
  159. package/dist/expressions/plugins/logical.js +103 -0
  160. package/dist/expressions/plugins/membership.d.ts +2 -0
  161. package/dist/expressions/plugins/membership.js +71 -0
  162. package/dist/expressions/plugins/pipe.d.ts +2 -0
  163. package/dist/expressions/plugins/pipe.js +57 -0
  164. package/dist/expressions/plugins/resolved-identifier.d.ts +5 -0
  165. package/dist/expressions/plugins/resolved-identifier.js +19 -0
  166. package/dist/expressions/plugins/standard.d.ts +13 -0
  167. package/dist/expressions/plugins/standard.js +23 -0
  168. package/dist/expressions/plugins/strings.d.ts +2 -0
  169. package/dist/expressions/plugins/strings.js +44 -0
  170. package/dist/expressions/plugins/ternary.d.ts +2 -0
  171. package/dist/expressions/plugins/ternary.js +58 -0
  172. package/dist/expressions.d.ts +2 -0
  173. package/dist/expressions.js +2 -0
  174. package/dist/index.d.ts +1 -0
  175. 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
- return (_jsx(TreeView, { items: items, rowSelectionEnabled: false, draggable: true, rowHeight: 30, rowSelectAllShow: false, defaultExpansion: true, onItemsReordered: (x) => {
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,4 @@
1
+ import "../../../../css/light-dark.css";
2
+ import "../../../../css/components/expression-editor.css";
3
+ import "./expression.css";
4
+ export default function App(): import("react/jsx-runtime").JSX.Element;
@@ -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,3 @@
1
+ import type { Token } from "../../expressions/lexer/types.js";
2
+ import type { CompletionItem } from "./types.js";
3
+ export declare function createCompletionProvider(context: Record<string, unknown>): (tokens: Token[], cursorPosition: number) => CompletionItem[];
@@ -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,2 @@
1
+ import type { CursorCoordinates } from "../types.js";
2
+ export declare function measureCursorPosition(textarea: HTMLTextAreaElement, cursorIndex: number): CursorCoordinates;
@@ -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,5 @@
1
+ import type { CursorCoordinates } from "../types";
2
+ export declare function useCursorPosition(textareaRef: React.RefObject<HTMLTextAreaElement | null>): {
3
+ coordinates: CursorCoordinates;
4
+ update: () => void;
5
+ };
@@ -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;