@1771technologies/lytenyte-pro 2.0.4-dev.3 → 2.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 (180) hide show
  1. package/README.md +54 -11
  2. package/css/components/expression-editor.css +106 -0
  3. package/dist/components/column-manager/column-manager.d.ts +6 -1
  4. package/dist/components/column-manager/column-manager.js +23 -2
  5. package/dist/components/expressions-editor/create-completion-provider.d.ts +10 -0
  6. package/dist/components/expressions-editor/create-completion-provider.js +391 -0
  7. package/dist/components/expressions-editor/create-key-down-handler.d.ts +18 -0
  8. package/dist/components/expressions-editor/create-key-down-handler.js +49 -0
  9. package/dist/components/expressions-editor/cursor-position/measure-cursor-at-pointer.d.ts +2 -0
  10. package/dist/components/expressions-editor/cursor-position/measure-cursor-at-pointer.js +56 -0
  11. package/dist/components/expressions-editor/cursor-position/use-cursor-position.d.ts +5 -0
  12. package/dist/components/expressions-editor/cursor-position/use-cursor-position.js +33 -0
  13. package/dist/components/expressions-editor/expression-editor.d.ts +3 -0
  14. package/dist/components/expressions-editor/expression-editor.js +158 -0
  15. package/dist/components/expressions-editor/get-indentation.d.ts +1 -0
  16. package/dist/components/expressions-editor/get-indentation.js +7 -0
  17. package/dist/components/expressions-editor/handle-completion-key-down.d.ts +2 -0
  18. package/dist/components/expressions-editor/handle-completion-key-down.js +22 -0
  19. package/dist/components/expressions-editor/handle-enter-key.d.ts +4 -0
  20. package/dist/components/expressions-editor/handle-enter-key.js +11 -0
  21. package/dist/components/expressions-editor/handle-manual-trigger.d.ts +2 -0
  22. package/dist/components/expressions-editor/handle-manual-trigger.js +3 -0
  23. package/dist/components/expressions-editor/index.d.ts +18 -0
  24. package/dist/components/expressions-editor/index.js +11 -0
  25. package/dist/components/expressions-editor/intellisence/completion-context.d.ts +17 -0
  26. package/dist/components/expressions-editor/intellisence/completion-context.js +7 -0
  27. package/dist/components/expressions-editor/intellisence/completion-item.d.ts +5 -0
  28. package/dist/components/expressions-editor/intellisence/completion-item.js +11 -0
  29. package/dist/components/expressions-editor/intellisence/completion-list.d.ts +8 -0
  30. package/dist/components/expressions-editor/intellisence/completion-list.js +18 -0
  31. package/dist/components/expressions-editor/intellisence/completion-popover.d.ts +1 -0
  32. package/dist/components/expressions-editor/intellisence/completion-popover.js +47 -0
  33. package/dist/components/expressions-editor/intellisence/filter-completions-by-prefix.d.ts +2 -0
  34. package/dist/components/expressions-editor/intellisence/filter-completions-by-prefix.js +6 -0
  35. package/dist/components/expressions-editor/intellisence/get-word-at-cursor.d.ts +2 -0
  36. package/dist/components/expressions-editor/intellisence/get-word-at-cursor.js +12 -0
  37. package/dist/components/expressions-editor/intellisence/use-complete-trigger.d.ts +13 -0
  38. package/dist/components/expressions-editor/intellisence/use-complete-trigger.js +41 -0
  39. package/dist/components/expressions-editor/intellisence/use-completions.d.ts +14 -0
  40. package/dist/components/expressions-editor/intellisence/use-completions.js +78 -0
  41. package/dist/components/expressions-editor/replace-word-at-cursor.d.ts +5 -0
  42. package/dist/components/expressions-editor/replace-word-at-cursor.js +7 -0
  43. package/dist/components/expressions-editor/token-highlighter-default.d.ts +4 -0
  44. package/dist/components/expressions-editor/token-highlighter-default.js +20 -0
  45. package/dist/components/expressions-editor/types.d.ts +48 -0
  46. package/dist/components/expressions-editor/types.js +1 -0
  47. package/dist/components/expressions-editor/use-keyboard-navigation.d.ts +10 -0
  48. package/dist/components/expressions-editor/use-keyboard-navigation.js +37 -0
  49. package/dist/components/pill-manager/container.js +55 -9
  50. package/dist/components/pill-manager/item.js +15 -1
  51. package/dist/components/select-all.js +1 -1
  52. package/dist/components/tree-view/root.d.ts +9 -0
  53. package/dist/components/tree-view/root.js +4 -2
  54. package/dist/data-source-client/hooks/use-flattened-groups.js +2 -2
  55. package/dist/expressions/compiler/compile.d.ts +3 -0
  56. package/dist/expressions/compiler/compile.js +4 -0
  57. package/dist/expressions/compiler/eval-constant.d.ts +2 -0
  58. package/dist/expressions/compiler/eval-constant.js +19 -0
  59. package/dist/expressions/compiler/is-constant.d.ts +2 -0
  60. package/dist/expressions/compiler/is-constant.js +6 -0
  61. package/dist/expressions/compiler/make-constant.d.ts +2 -0
  62. package/dist/expressions/compiler/make-constant.js +11 -0
  63. package/dist/expressions/compiler/optimize.d.ts +3 -0
  64. package/dist/expressions/compiler/optimize.js +47 -0
  65. package/dist/expressions/compiler/value-of.d.ts +2 -0
  66. package/dist/expressions/compiler/value-of.js +11 -0
  67. package/dist/expressions/errors/clamp-offset.d.ts +1 -0
  68. package/dist/expressions/errors/clamp-offset.js +3 -0
  69. package/dist/expressions/errors/expression-error.d.ts +10 -0
  70. package/dist/expressions/errors/expression-error.js +19 -0
  71. package/dist/expressions/errors/format-error.d.ts +3 -0
  72. package/dist/expressions/errors/format-error.js +27 -0
  73. package/dist/expressions/errors/get-line-end.d.ts +1 -0
  74. package/dist/expressions/errors/get-line-end.js +8 -0
  75. package/dist/expressions/errors/get-line-start.d.ts +1 -0
  76. package/dist/expressions/errors/get-line-start.js +8 -0
  77. package/dist/expressions/errors/index.d.ts +7 -0
  78. package/dist/expressions/errors/index.js +6 -0
  79. package/dist/expressions/errors/offset-to-position.d.ts +2 -0
  80. package/dist/expressions/errors/offset-to-position.js +17 -0
  81. package/dist/expressions/errors/types.d.ts +10 -0
  82. package/dist/expressions/errors/types.js +1 -0
  83. package/dist/expressions/evaluator/evaluate-binary.d.ts +4 -0
  84. package/dist/expressions/evaluator/evaluate-binary.js +21 -0
  85. package/dist/expressions/evaluator/evaluate-unary.d.ts +2 -0
  86. package/dist/expressions/evaluator/evaluate-unary.js +10 -0
  87. package/dist/expressions/evaluator/evaluate.d.ts +15 -0
  88. package/dist/expressions/evaluator/evaluate.js +62 -0
  89. package/dist/expressions/index.d.ts +7 -0
  90. package/dist/expressions/index.js +5 -0
  91. package/dist/expressions/lexer/predicates/is-alpha-numeric.d.ts +1 -0
  92. package/dist/expressions/lexer/predicates/is-alpha-numeric.js +5 -0
  93. package/dist/expressions/lexer/predicates/is-alpha.d.ts +1 -0
  94. package/dist/expressions/lexer/predicates/is-alpha.js +3 -0
  95. package/dist/expressions/lexer/predicates/is-binary-digit.d.ts +1 -0
  96. package/dist/expressions/lexer/predicates/is-binary-digit.js +3 -0
  97. package/dist/expressions/lexer/predicates/is-digit.d.ts +1 -0
  98. package/dist/expressions/lexer/predicates/is-digit.js +3 -0
  99. package/dist/expressions/lexer/predicates/is-hex-digit.d.ts +1 -0
  100. package/dist/expressions/lexer/predicates/is-hex-digit.js +4 -0
  101. package/dist/expressions/lexer/predicates/is-octal-digit.d.ts +1 -0
  102. package/dist/expressions/lexer/predicates/is-octal-digit.js +3 -0
  103. package/dist/expressions/lexer/tokenize/consume-digits.d.ts +1 -0
  104. package/dist/expressions/lexer/tokenize/consume-digits.js +5 -0
  105. package/dist/expressions/lexer/tokenize/scan-identifier.d.ts +6 -0
  106. package/dist/expressions/lexer/tokenize/scan-identifier.js +20 -0
  107. package/dist/expressions/lexer/tokenize/scan-number.d.ts +4 -0
  108. package/dist/expressions/lexer/tokenize/scan-number.js +32 -0
  109. package/dist/expressions/lexer/tokenize/scan-operator.d.ts +6 -0
  110. package/dist/expressions/lexer/tokenize/scan-operator.js +71 -0
  111. package/dist/expressions/lexer/tokenize/scan-string-literal.d.ts +4 -0
  112. package/dist/expressions/lexer/tokenize/scan-string-literal.js +22 -0
  113. package/dist/expressions/lexer/tokenize/scan-string.d.ts +4 -0
  114. package/dist/expressions/lexer/tokenize/scan-string.js +98 -0
  115. package/dist/expressions/lexer/tokenize/scan-template-literal.d.ts +4 -0
  116. package/dist/expressions/lexer/tokenize/scan-template-literal.js +51 -0
  117. package/dist/expressions/lexer/tokenize/tokenize.d.ts +4 -0
  118. package/dist/expressions/lexer/tokenize/tokenize.js +99 -0
  119. package/dist/expressions/lexer/types.d.ts +12 -0
  120. package/dist/expressions/lexer/types.js +1 -0
  121. package/dist/expressions/parser/parse-array-literal.d.ts +3 -0
  122. package/dist/expressions/parser/parse-array-literal.js +15 -0
  123. package/dist/expressions/parser/parse-expression.d.ts +3 -0
  124. package/dist/expressions/parser/parse-expression.js +74 -0
  125. package/dist/expressions/parser/parse-object-literal.d.ts +3 -0
  126. package/dist/expressions/parser/parse-object-literal.js +69 -0
  127. package/dist/expressions/parser/parse-pipe-transform.d.ts +3 -0
  128. package/dist/expressions/parser/parse-pipe-transform.js +26 -0
  129. package/dist/expressions/parser/parse-postfix.d.ts +3 -0
  130. package/dist/expressions/parser/parse-postfix.js +23 -0
  131. package/dist/expressions/parser/parse-primary.d.ts +3 -0
  132. package/dist/expressions/parser/parse-primary.js +35 -0
  133. package/dist/expressions/parser/parse-template-literal.d.ts +3 -0
  134. package/dist/expressions/parser/parse-template-literal.js +41 -0
  135. package/dist/expressions/parser/parse-unary.d.ts +3 -0
  136. package/dist/expressions/parser/parse-unary.js +28 -0
  137. package/dist/expressions/parser/parse.d.ts +3 -0
  138. package/dist/expressions/parser/parse.js +26 -0
  139. package/dist/expressions/parser/parser-context.d.ts +12 -0
  140. package/dist/expressions/parser/parser-context.js +14 -0
  141. package/dist/expressions/parser/precedence.d.ts +1 -0
  142. package/dist/expressions/parser/precedence.js +8 -0
  143. package/dist/expressions/parser/types.d.ts +101 -0
  144. package/dist/expressions/parser/types.js +1 -0
  145. package/dist/expressions/plugin.d.ts +20 -0
  146. package/dist/expressions/plugin.js +1 -0
  147. package/dist/expressions/plugins/access.d.ts +2 -0
  148. package/dist/expressions/plugins/access.js +138 -0
  149. package/dist/expressions/plugins/arrows.d.ts +2 -0
  150. package/dist/expressions/plugins/arrows.js +83 -0
  151. package/dist/expressions/plugins/booleans.d.ts +2 -0
  152. package/dist/expressions/plugins/booleans.js +34 -0
  153. package/dist/expressions/plugins/collections.d.ts +2 -0
  154. package/dist/expressions/plugins/collections.js +70 -0
  155. package/dist/expressions/plugins/comparison.d.ts +2 -0
  156. package/dist/expressions/plugins/comparison.js +51 -0
  157. package/dist/expressions/plugins/date-identifier.d.ts +4 -0
  158. package/dist/expressions/plugins/date-identifier.js +29 -0
  159. package/dist/expressions/plugins/date-literal.d.ts +2 -0
  160. package/dist/expressions/plugins/date-literal.js +71 -0
  161. package/dist/expressions/plugins/logical.d.ts +2 -0
  162. package/dist/expressions/plugins/logical.js +103 -0
  163. package/dist/expressions/plugins/membership.d.ts +2 -0
  164. package/dist/expressions/plugins/membership.js +71 -0
  165. package/dist/expressions/plugins/pipe.d.ts +2 -0
  166. package/dist/expressions/plugins/pipe.js +57 -0
  167. package/dist/expressions/plugins/quoted-identifier.d.ts +2 -0
  168. package/dist/expressions/plugins/quoted-identifier.js +35 -0
  169. package/dist/expressions/plugins/resolved-identifier.d.ts +5 -0
  170. package/dist/expressions/plugins/resolved-identifier.js +19 -0
  171. package/dist/expressions/plugins/standard.d.ts +15 -0
  172. package/dist/expressions/plugins/standard.js +27 -0
  173. package/dist/expressions/plugins/strings.d.ts +2 -0
  174. package/dist/expressions/plugins/strings.js +44 -0
  175. package/dist/expressions/plugins/ternary.d.ts +2 -0
  176. package/dist/expressions/plugins/ternary.js +58 -0
  177. package/dist/expressions.d.ts +2 -0
  178. package/dist/expressions.js +2 -0
  179. package/dist/index.d.ts +1 -0
  180. package/package.json +12 -5
@@ -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 { VirtualElement } from "../../external/floating-ui.js";
2
+ export declare function useCursorPosition(textareaRef: React.RefObject<HTMLTextAreaElement | null>): {
3
+ virtualElement: VirtualElement;
4
+ update: () => void;
5
+ };
@@ -0,0 +1,33 @@
1
+ import { useRef, useCallback } from "react";
2
+ import { measureCursorPosition } from "./measure-cursor-at-pointer.js";
3
+ export function useCursorPosition(textareaRef) {
4
+ const cursorIndexRef = useRef(0);
5
+ const virtualElement = useRef({
6
+ getBoundingClientRect() {
7
+ const textarea = textareaRef.current;
8
+ if (!textarea)
9
+ return { top: 0, left: 0, right: 0, bottom: 0, x: 0, y: 0, width: 0, height: 0 };
10
+ const { top, left, lineHeight } = measureCursorPosition(textarea, cursorIndexRef.current);
11
+ return {
12
+ top,
13
+ left,
14
+ right: left,
15
+ bottom: top + lineHeight,
16
+ x: left,
17
+ y: top,
18
+ width: 0,
19
+ height: lineHeight,
20
+ };
21
+ },
22
+ get contextElement() {
23
+ return textareaRef.current ?? undefined;
24
+ },
25
+ });
26
+ const update = useCallback(() => {
27
+ const textarea = textareaRef.current;
28
+ if (!textarea)
29
+ return;
30
+ cursorIndexRef.current = textarea.selectionStart;
31
+ }, [textareaRef]);
32
+ return { virtualElement: virtualElement.current, update };
33
+ }
@@ -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;
@@ -0,0 +1,158 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef, useCallback, useMemo, Fragment } from "react";
3
+ import { useCompletions } from "./intellisence/use-completions.js";
4
+ import { useCompletionTrigger } from "./intellisence/use-complete-trigger.js";
5
+ import { useKeyboardNavigation } from "./use-keyboard-navigation.js";
6
+ import { useCursorPosition } from "./cursor-position/use-cursor-position.js";
7
+ import { createKeyDownHandler } from "./create-key-down-handler.js";
8
+ import { getWordAtCursor } from "./intellisence/get-word-at-cursor.js";
9
+ import { DefaultTokenHighlighter } from "./token-highlighter-default.js";
10
+ import { CompletionListProvider, CompletionPopoverProvider } from "./intellisence/completion-context.js";
11
+ const sharedFontStyle = {
12
+ fontFamily: "inherit",
13
+ fontSize: "inherit",
14
+ fontWeight: "inherit",
15
+ fontStyle: "inherit",
16
+ letterSpacing: "inherit",
17
+ lineHeight: "inherit",
18
+ padding: "0",
19
+ };
20
+ const triggerCharacters = [".", " "];
21
+ export function ExpressionEditor({ value, onChange: onValueChange, tokenize, highlight: Highlighter = DefaultTokenHighlighter, completionProvider, placeholder, disabled, readOnly, className, style, children, }) {
22
+ // TODO @Lee: we should eventually allow multiline expressions but for now there isn't really a valid
23
+ // use case that requires multiline expressions without also allow variable support.
24
+ const multiline = false;
25
+ const textareaRef = useRef(null);
26
+ const depsRef = useRef(null);
27
+ const tokens = useMemo(() => tokenize(value), [value, tokenize]);
28
+ const completions = useCompletions(completionProvider);
29
+ const cursorPosition = useCursorPosition(textareaRef);
30
+ const trigger = useCompletionTrigger({
31
+ triggerCharacters,
32
+ onTrigger: (toks, pos, word) => {
33
+ cursorPosition.update();
34
+ completions.fetchCompletions(toks, pos, word);
35
+ },
36
+ tokenize,
37
+ });
38
+ const navigation = useKeyboardNavigation({
39
+ onValueChange,
40
+ onAccepted: (value, cursorPosition) => {
41
+ if (completionProvider) {
42
+ trigger.triggerManually(value, cursorPosition + 1);
43
+ }
44
+ else {
45
+ completions.dismiss();
46
+ }
47
+ },
48
+ textareaRef,
49
+ });
50
+ depsRef.current = {
51
+ isOpen: completions.isOpen,
52
+ selectedItem: completions.selectedItem,
53
+ navigate: completions.navigate,
54
+ dismiss: completions.dismiss,
55
+ acceptCompletion: navigation.acceptCompletion,
56
+ triggerManually: trigger.triggerManually,
57
+ onValueChange,
58
+ multiline,
59
+ updateFilter: completions.updateFilter,
60
+ handleInputChange: trigger.handleInputChange,
61
+ cancel: trigger.cancel,
62
+ };
63
+ const handleKeyDown = useCallback((e) => {
64
+ const deps = depsRef.current;
65
+ const textarea = e.currentTarget;
66
+ const wordAtCursor = getWordAtCursor(textarea.value, textarea.selectionStart);
67
+ const handler = createKeyDownHandler({
68
+ isPopoverOpen: deps.isOpen,
69
+ multiline: deps.multiline,
70
+ selectedItem: deps.selectedItem,
71
+ wordAtCursor,
72
+ onAcceptCompletion: deps.acceptCompletion,
73
+ onNavigate: deps.navigate,
74
+ onDismiss: deps.dismiss,
75
+ onCancelTrigger: deps.cancel,
76
+ onManualTrigger: () => {
77
+ deps.triggerManually(textarea.value, textarea.selectionStart);
78
+ },
79
+ onValueChange: deps.onValueChange,
80
+ onExternalKeyDown: deps.onExternalKeyDown,
81
+ });
82
+ handler(e);
83
+ }, []);
84
+ const handleChange = useCallback((e) => {
85
+ const { value: newValue, selectionStart } = e.currentTarget;
86
+ const deps = depsRef.current;
87
+ deps.onValueChange(newValue);
88
+ const word = getWordAtCursor(newValue, selectionStart);
89
+ if (deps.isOpen) {
90
+ const charBefore = newValue[selectionStart - 1] || "";
91
+ if (word.word.length > 0) {
92
+ deps.updateFilter(word.word);
93
+ }
94
+ else if (charBefore === '"' || charBefore === "'") {
95
+ // String delimiter typed — close the popover rather than re-triggering.
96
+ deps.dismiss();
97
+ deps.cancel();
98
+ }
99
+ else if (!triggerCharacters.includes(charBefore)) {
100
+ // Non-word, non-trigger char (e.g. an operator) - re-fetch so the list
101
+ // reflects the new syntactic context rather than keeping stale results.
102
+ deps.triggerManually(newValue, selectionStart);
103
+ }
104
+ }
105
+ deps.handleInputChange(newValue, selectionStart);
106
+ }, []);
107
+ const handleBlur = useCallback(() => {
108
+ const deps = depsRef.current;
109
+ deps.dismiss();
110
+ deps.cancel();
111
+ }, []);
112
+ const handleItemSelect = useCallback((item) => {
113
+ const textarea = textareaRef.current;
114
+ if (!textarea)
115
+ return;
116
+ const wordAtCursor = getWordAtCursor(textarea.value, textarea.selectionStart);
117
+ depsRef.current.acceptCompletion(item, wordAtCursor);
118
+ }, []);
119
+ const whiteSpace = multiline ? "pre-wrap" : "pre";
120
+ const wordBreak = multiline ? "break-word" : undefined;
121
+ const gridCell = {
122
+ gridArea: "1 / 1",
123
+ ...sharedFontStyle,
124
+ whiteSpace,
125
+ wordBreak,
126
+ overflowWrap: multiline ? "break-word" : undefined,
127
+ boxSizing: "border-box",
128
+ };
129
+ const popoverValue = useMemo(() => {
130
+ return { isOpen: completions.isOpen, referenceElement: cursorPosition.virtualElement };
131
+ }, [completions.isOpen, cursorPosition.virtualElement]);
132
+ const listValue = useMemo(() => {
133
+ return {
134
+ items: completions.filteredItems,
135
+ loading: completions.isLoading,
136
+ selectedIndex: completions.selectedIndex,
137
+ onSelect: handleItemSelect,
138
+ };
139
+ }, [completions.filteredItems, completions.isLoading, completions.selectedIndex, handleItemSelect]);
140
+ return (_jsxs("div", { className: className, style: {
141
+ display: "grid",
142
+ width: "100%",
143
+ ...style,
144
+ }, "data-ln-expression-editor": true, children: [_jsx("textarea", { ref: textareaRef, value: value, onChange: handleChange, onKeyDown: handleKeyDown, onBlur: handleBlur, disabled: disabled, readOnly: readOnly, placeholder: placeholder, rows: multiline ? undefined : 1, "data-ln-expression-input": true, style: {
145
+ ...gridCell,
146
+ display: "block",
147
+ width: "100%",
148
+ resize: multiline ? "vertical" : "none",
149
+ background: "transparent",
150
+ color: "transparent",
151
+ WebkitTextFillColor: "transparent",
152
+ outline: "none",
153
+ overflow: multiline ? "auto" : "hidden",
154
+ margin: 0,
155
+ border: 0,
156
+ zIndex: 1,
157
+ }, autoCapitalize: "off", autoComplete: "off", autoCorrect: "off", spellCheck: false }), _jsx("div", { "aria-hidden": "true", style: { ...gridCell, pointerEvents: "none", overflow: "hidden", zIndex: 0 }, "data-ln-expression--tokens": true, children: tokens.map((token) => (_jsx(Fragment, { children: _jsx(Highlighter, { token: token }) }, token.start))) }), _jsx(CompletionPopoverProvider, { value: popoverValue, children: _jsx(CompletionListProvider, { value: listValue, children: children }) })] }));
158
+ }
@@ -0,0 +1 @@
1
+ export declare function getIndentation(text: string, cursorPosition: number): string;
@@ -0,0 +1,7 @@
1
+ export function getIndentation(text, cursorPosition) {
2
+ const textBeforeCursor = text.substring(0, cursorPosition);
3
+ const lines = textBeforeCursor.split("\n");
4
+ const currentLine = lines[lines.length - 1];
5
+ const match = currentLine.match(/^(\s*)/);
6
+ return match ? match[1] : "";
7
+ }
@@ -0,0 +1,2 @@
1
+ import type { CompletionAction, KeybindingConfig } from "./types.js";
2
+ export declare function handleCompletionKeyDown(key: string, keybindings?: KeybindingConfig): CompletionAction;
@@ -0,0 +1,22 @@
1
+ const DEFAULT_KEYBINDINGS = {
2
+ accept: ["Enter", "Tab"],
3
+ dismiss: ["Escape"],
4
+ navigateUp: ["ArrowUp"],
5
+ navigateDown: ["ArrowDown"],
6
+ };
7
+ export function handleCompletionKeyDown(key, keybindings) {
8
+ const bindings = { ...DEFAULT_KEYBINDINGS, ...keybindings };
9
+ if (bindings.navigateUp.includes(key)) {
10
+ return { type: "navigate", direction: "up" };
11
+ }
12
+ if (bindings.navigateDown.includes(key)) {
13
+ return { type: "navigate", direction: "down" };
14
+ }
15
+ if (bindings.accept.includes(key)) {
16
+ return { type: "accept" };
17
+ }
18
+ if (bindings.dismiss.includes(key)) {
19
+ return { type: "dismiss" };
20
+ }
21
+ return { type: "none" };
22
+ }
@@ -0,0 +1,4 @@
1
+ export declare function handleEnterKey(value: string, selectionStart: number, selectionEnd: number, multiline: boolean): {
2
+ value: string;
3
+ cursorPosition: number;
4
+ } | null;
@@ -0,0 +1,11 @@
1
+ import { getIndentation } from "./get-indentation.js";
2
+ export function handleEnterKey(value, selectionStart, selectionEnd, multiline) {
3
+ if (!multiline) {
4
+ return null;
5
+ }
6
+ const indent = getIndentation(value, selectionStart);
7
+ const insertion = "\n" + indent;
8
+ const newValue = value.substring(0, selectionStart) + insertion + value.substring(selectionEnd);
9
+ const cursorPosition = selectionStart + insertion.length;
10
+ return { value: newValue, cursorPosition };
11
+ }
@@ -0,0 +1,2 @@
1
+ import type { KeyboardEvent } from "react";
2
+ export declare function isManualTrigger(e: KeyboardEvent<HTMLTextAreaElement>): boolean;
@@ -0,0 +1,3 @@
1
+ export function isManualTrigger(e) {
2
+ return (e.ctrlKey || e.metaKey) && e.key === " ";
3
+ }
@@ -0,0 +1,18 @@
1
+ import { ExpressionEditor as Expr } from "./expression-editor.js";
2
+ export declare const ExpressionEditor: {
3
+ Root: typeof Expr;
4
+ CompletionPopover: import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
5
+ CompletionList: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLUListElement>, HTMLUListElement>, "children"> & {
6
+ children?: (props: {
7
+ readonly items: import("./types.js").CompletionItem[];
8
+ readonly loading: boolean;
9
+ }) => import("react").ReactNode;
10
+ }, "ref"> & import("react").RefAttributes<HTMLUListElement>>;
11
+ CompletionListItem: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLLIElement> & import("react").LiHTMLAttributes<HTMLLIElement> & {
12
+ readonly item: import("./types.js").CompletionItem;
13
+ readonly index: number;
14
+ }, "ref"> & import("react").RefAttributes<HTMLLIElement>>;
15
+ };
16
+ export { createCompletionProvider } from "./create-completion-provider.js";
17
+ export type { CompletionItem } from "./types.js";
18
+ export type { ContextEntry } from "./create-completion-provider.js";
@@ -0,0 +1,11 @@
1
+ import { ExpressionEditor as Expr } from "./expression-editor.js";
2
+ import { CompletionListItem } from "./intellisence/completion-item.js";
3
+ import { CompletionList } from "./intellisence/completion-list.js";
4
+ import { CompletionPopover } from "./intellisence/completion-popover.js";
5
+ export const ExpressionEditor = {
6
+ Root: Expr,
7
+ CompletionPopover,
8
+ CompletionList,
9
+ CompletionListItem,
10
+ };
11
+ export { createCompletionProvider } from "./create-completion-provider.js";
@@ -0,0 +1,17 @@
1
+ import type { VirtualElement } from "../../external/floating-ui.js";
2
+ import type { CompletionItem } from "../types";
3
+ interface ContextPopover {
4
+ readonly isOpen: boolean;
5
+ readonly referenceElement: VirtualElement | null;
6
+ }
7
+ export declare const CompletionPopoverProvider: import("react").Provider<ContextPopover>;
8
+ export declare const useCompletionPopover: () => ContextPopover;
9
+ interface ContextList {
10
+ readonly items: CompletionItem[];
11
+ readonly loading: boolean;
12
+ readonly selectedIndex: number;
13
+ readonly onSelect: (item: CompletionItem) => void;
14
+ }
15
+ export declare const CompletionListProvider: import("react").Provider<ContextList>;
16
+ export declare const useCompletionListContext: () => ContextList;
17
+ export {};
@@ -0,0 +1,7 @@
1
+ import { createContext, useContext } from "react";
2
+ const context = createContext({});
3
+ export const CompletionPopoverProvider = context.Provider;
4
+ export const useCompletionPopover = () => useContext(context);
5
+ const contextList = createContext({});
6
+ export const CompletionListProvider = contextList.Provider;
7
+ export const useCompletionListContext = () => useContext(contextList);
@@ -0,0 +1,5 @@
1
+ import type { CompletionItem } from "../types.js";
2
+ export declare const CompletionListItem: import("react").ForwardRefExoticComponent<Omit<import("react").ClassAttributes<HTMLLIElement> & import("react").LiHTMLAttributes<HTMLLIElement> & {
3
+ readonly item: CompletionItem;
4
+ readonly index: number;
5
+ }, "ref"> & import("react").RefAttributes<HTMLLIElement>>;
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { forwardRef } from "react";
3
+ import { useCompletionListContext } from "./completion-context.js";
4
+ function CompleteListItemBase({ item, index, ...props }, ref) {
5
+ const { selectedIndex, onSelect } = useCompletionListContext();
6
+ return (_jsx("li", { ...props, ref: ref, role: "option", "aria-selected": index === selectedIndex, "data-ln-selected": index === selectedIndex ? "" : undefined, onMouseDown: (e) => {
7
+ e.preventDefault();
8
+ onSelect(item);
9
+ }, "data-ln-expression-completion-item": true }));
10
+ }
11
+ export const CompletionListItem = forwardRef(CompleteListItemBase);
@@ -0,0 +1,8 @@
1
+ import { type ReactNode } from "react";
2
+ import type { CompletionItem } from "../types.js";
3
+ export declare const CompletionList: import("react").ForwardRefExoticComponent<Omit<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLUListElement>, HTMLUListElement>, "children"> & {
4
+ children?: (props: {
5
+ readonly items: CompletionItem[];
6
+ readonly loading: boolean;
7
+ }) => ReactNode;
8
+ }, "ref"> & import("react").RefAttributes<HTMLUListElement>>;
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useRef, useEffect, forwardRef } from "react";
3
+ import { useCompletionListContext } from "./completion-context.js";
4
+ import { useCombinedRefs } from "@1771technologies/lytenyte-core/internal";
5
+ function CompletionListBase({ children, ...props }, ref) {
6
+ const { selectedIndex, items, loading } = useCompletionListContext();
7
+ const listRef = useRef(null);
8
+ useEffect(() => {
9
+ const list = listRef.current;
10
+ if (!list)
11
+ return;
12
+ const selected = list.querySelector("[data-ln-selected]");
13
+ selected?.scrollIntoView({ block: "nearest" });
14
+ }, [selectedIndex]);
15
+ const combined = useCombinedRefs(listRef, ref);
16
+ return (_jsx("ul", { ...props, ref: combined, role: "listbox", "data-ln-expression-completion-list": true, children: children?.({ items, loading }) }));
17
+ }
18
+ export const CompletionList = forwardRef(CompletionListBase);
@@ -0,0 +1 @@
1
+ export declare const CompletionPopover: import("react").ForwardRefExoticComponent<Omit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "ref"> & import("react").RefAttributes<HTMLDivElement>>;
@@ -0,0 +1,47 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useRef, useEffect, forwardRef } from "react";
3
+ import { useCompletionPopover } from "./completion-context.js";
4
+ import { useCombinedRefs } from "@1771technologies/lytenyte-core/internal";
5
+ import { autoUpdate, computePosition, offset, shift } from "../../external/floating-ui.js";
6
+ function CompletionPopoverImpl({ children, ...props }, ref) {
7
+ const { isOpen, referenceElement } = useCompletionPopover();
8
+ const popoverRef = useRef(null);
9
+ useEffect(() => {
10
+ const el = popoverRef.current;
11
+ if (!el)
12
+ return;
13
+ if (isOpen) {
14
+ if (!el.matches(":popover-open"))
15
+ el.showPopover();
16
+ }
17
+ else {
18
+ if (el.matches(":popover-open"))
19
+ el.hidePopover();
20
+ }
21
+ }, [isOpen]);
22
+ useEffect(() => {
23
+ const el = popoverRef.current;
24
+ if (!isOpen || !el || !referenceElement)
25
+ return;
26
+ function update() {
27
+ computePosition(referenceElement, el, {
28
+ strategy: "fixed",
29
+ placement: "bottom-start",
30
+ middleware: [offset(4), shift({ padding: 8 })],
31
+ }).then(({ x, y }) => {
32
+ el.style.left = `${x}px`;
33
+ el.style.top = `${y}px`;
34
+ });
35
+ }
36
+ return autoUpdate(referenceElement, el, update);
37
+ }, [isOpen, referenceElement]);
38
+ const combined = useCombinedRefs(ref, popoverRef);
39
+ return (_jsx("div", { ...props, ref: combined, popover: "manual", "data-ln-expression-popover": true, style: {
40
+ position: "fixed",
41
+ top: 0,
42
+ left: 0,
43
+ margin: 0,
44
+ inset: "unset",
45
+ }, children: isOpen ? children : null }));
46
+ }
47
+ export const CompletionPopover = forwardRef(CompletionPopoverImpl);
@@ -0,0 +1,2 @@
1
+ import type { CompletionItem } from "../types.js";
2
+ export declare function filterCompletionsByPrefix(items: CompletionItem[], prefix: string): CompletionItem[];
@@ -0,0 +1,6 @@
1
+ export function filterCompletionsByPrefix(items, prefix) {
2
+ if (!prefix)
3
+ return items;
4
+ const lower = prefix.toLowerCase();
5
+ return items.filter((item) => item.label.toLowerCase().startsWith(lower));
6
+ }
@@ -0,0 +1,2 @@
1
+ import type { WordAtCursor } from "../types.js";
2
+ export declare function getWordAtCursor(text: string, cursorPosition: number): WordAtCursor;
@@ -0,0 +1,12 @@
1
+ export function getWordAtCursor(text, cursorPosition) {
2
+ const wordPattern = /[a-zA-Z0-9_]/;
3
+ let start = cursorPosition;
4
+ while (start > 0 && wordPattern.test(text[start - 1])) {
5
+ start--;
6
+ }
7
+ return {
8
+ word: text.substring(start, cursorPosition),
9
+ start,
10
+ end: cursorPosition,
11
+ };
12
+ }
@@ -0,0 +1,13 @@
1
+ import type { WordAtCursor } from "../types";
2
+ import type { Token } from "../../../expressions/lexer/types";
3
+ type TriggerConfig = {
4
+ triggerCharacters: string[];
5
+ onTrigger: (tokens: Token[], cursorPosition: number, word: WordAtCursor) => void;
6
+ tokenize: (value: string) => Token[];
7
+ };
8
+ export declare function useCompletionTrigger(config: TriggerConfig): {
9
+ handleInputChange: (value: string, cursorPosition: number) => void;
10
+ triggerManually: (value: string, cursorPosition: number) => void;
11
+ cancel: () => void;
12
+ };
13
+ export {};
@@ -0,0 +1,41 @@
1
+ import { useCallback, useRef, useEffect } from "react";
2
+ import { getWordAtCursor } from "./get-word-at-cursor.js";
3
+ const DEBOUNCE_DELAY = 150;
4
+ export function useCompletionTrigger(config) {
5
+ const timerRef = useRef(null);
6
+ const configRef = useRef(config);
7
+ configRef.current = config;
8
+ const cancel = useCallback(() => {
9
+ if (timerRef.current !== null) {
10
+ clearTimeout(timerRef.current);
11
+ timerRef.current = null;
12
+ }
13
+ }, []);
14
+ useEffect(() => cancel, [cancel]);
15
+ const triggerManually = useCallback((value, cursorPosition) => {
16
+ cancel();
17
+ const { tokenize, onTrigger } = configRef.current;
18
+ const tokens = tokenize(value);
19
+ const word = getWordAtCursor(value, cursorPosition);
20
+ onTrigger(tokens, cursorPosition, word);
21
+ }, [cancel]);
22
+ const handleInputChange = useCallback((value, cursorPosition) => {
23
+ cancel();
24
+ const { triggerCharacters, tokenize, onTrigger } = configRef.current;
25
+ const charBeforeCursor = value[cursorPosition - 1] || "";
26
+ if (triggerCharacters.includes(charBeforeCursor)) {
27
+ const tokens = tokenize(value);
28
+ const word = getWordAtCursor(value, cursorPosition);
29
+ onTrigger(tokens, cursorPosition, word);
30
+ return;
31
+ }
32
+ const word = getWordAtCursor(value, cursorPosition);
33
+ if (word.word.length > 0) {
34
+ timerRef.current = setTimeout(() => {
35
+ const tokens = configRef.current.tokenize(value);
36
+ configRef.current.onTrigger(tokens, cursorPosition, word);
37
+ }, DEBOUNCE_DELAY);
38
+ }
39
+ }, [cancel]);
40
+ return { handleInputChange, triggerManually, cancel };
41
+ }