@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.
- package/README.md +54 -11
- package/css/components/expression-editor.css +106 -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/create-completion-provider.d.ts +10 -0
- package/dist/components/expressions-editor/create-completion-provider.js +391 -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 +33 -0
- package/dist/components/expressions-editor/expression-editor.d.ts +3 -0
- package/dist/components/expressions-editor/expression-editor.js +158 -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 +18 -0
- package/dist/components/expressions-editor/index.js +11 -0
- package/dist/components/expressions-editor/intellisence/completion-context.d.ts +17 -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 +47 -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 +20 -0
- package/dist/components/expressions-editor/types.d.ts +48 -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 +37 -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 +7 -0
- package/dist/expressions/index.js +5 -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 +101 -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/date-identifier.d.ts +4 -0
- package/dist/expressions/plugins/date-identifier.js +29 -0
- package/dist/expressions/plugins/date-literal.d.ts +2 -0
- package/dist/expressions/plugins/date-literal.js +71 -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/quoted-identifier.d.ts +2 -0
- package/dist/expressions/plugins/quoted-identifier.js +35 -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 +15 -0
- package/dist/expressions/plugins/standard.js +27 -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 +12 -5
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CompletionItem, WordAtCursor } from "../types.js";
|
|
2
|
+
import type { Token } from "../../../expressions/lexer/types.js";
|
|
3
|
+
export declare function useCompletions(completionProvider?: (tokens: Token[], cursorPosition: number) => CompletionItem[] | Promise<CompletionItem[]>): {
|
|
4
|
+
selectedItem: CompletionItem | null;
|
|
5
|
+
fetchCompletions: (tokens: Token[], cursorPosition: number, word: WordAtCursor) => Promise<void>;
|
|
6
|
+
updateFilter: (prefix: string) => void;
|
|
7
|
+
navigate: (direction: "up" | "down") => void;
|
|
8
|
+
dismiss: () => void;
|
|
9
|
+
items: CompletionItem[];
|
|
10
|
+
filteredItems: CompletionItem[];
|
|
11
|
+
selectedIndex: number;
|
|
12
|
+
isOpen: boolean;
|
|
13
|
+
isLoading: boolean;
|
|
14
|
+
};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { useState, useCallback, useRef } from "react";
|
|
2
|
+
import { filterCompletionsByPrefix } from "./filter-completions-by-prefix.js";
|
|
3
|
+
export function useCompletions(completionProvider) {
|
|
4
|
+
const [state, setState] = useState({
|
|
5
|
+
items: [],
|
|
6
|
+
filteredItems: [],
|
|
7
|
+
selectedIndex: 0,
|
|
8
|
+
isOpen: false,
|
|
9
|
+
isLoading: false,
|
|
10
|
+
});
|
|
11
|
+
const requestIdRef = useRef(0);
|
|
12
|
+
const fetchCompletions = useCallback(async (tokens, cursorPosition, word) => {
|
|
13
|
+
if (!completionProvider)
|
|
14
|
+
return;
|
|
15
|
+
const requestId = ++requestIdRef.current;
|
|
16
|
+
setState((prev) => ({ ...prev, isLoading: true, isOpen: true }));
|
|
17
|
+
try {
|
|
18
|
+
const result = await Promise.resolve(completionProvider(tokens, cursorPosition));
|
|
19
|
+
if (requestId !== requestIdRef.current)
|
|
20
|
+
return;
|
|
21
|
+
const filtered = filterCompletionsByPrefix(result, word.word);
|
|
22
|
+
setState({
|
|
23
|
+
items: result,
|
|
24
|
+
filteredItems: filtered,
|
|
25
|
+
selectedIndex: 0,
|
|
26
|
+
isOpen: filtered.length > 0,
|
|
27
|
+
isLoading: false,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
if (requestId !== requestIdRef.current)
|
|
32
|
+
return;
|
|
33
|
+
setState((prev) => ({
|
|
34
|
+
...prev,
|
|
35
|
+
isOpen: false,
|
|
36
|
+
isLoading: false,
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
}, [completionProvider]);
|
|
40
|
+
const updateFilter = useCallback((prefix) => {
|
|
41
|
+
setState((prev) => {
|
|
42
|
+
const filtered = filterCompletionsByPrefix(prev.items, prefix);
|
|
43
|
+
return {
|
|
44
|
+
...prev,
|
|
45
|
+
filteredItems: filtered,
|
|
46
|
+
selectedIndex: 0,
|
|
47
|
+
isOpen: filtered.length > 0,
|
|
48
|
+
};
|
|
49
|
+
});
|
|
50
|
+
}, []);
|
|
51
|
+
const navigate = useCallback((direction) => {
|
|
52
|
+
setState((prev) => {
|
|
53
|
+
if (prev.filteredItems.length === 0)
|
|
54
|
+
return prev;
|
|
55
|
+
const delta = direction === "down" ? 1 : -1;
|
|
56
|
+
const count = prev.filteredItems.length;
|
|
57
|
+
const nextIndex = (prev.selectedIndex + delta + count) % count;
|
|
58
|
+
return { ...prev, selectedIndex: nextIndex };
|
|
59
|
+
});
|
|
60
|
+
}, []);
|
|
61
|
+
const dismiss = useCallback(() => {
|
|
62
|
+
setState((prev) => ({
|
|
63
|
+
...prev,
|
|
64
|
+
isOpen: false,
|
|
65
|
+
isLoading: false,
|
|
66
|
+
selectedIndex: 0,
|
|
67
|
+
}));
|
|
68
|
+
}, []);
|
|
69
|
+
const selectedItem = state.isOpen && state.filteredItems.length > 0 ? state.filteredItems[state.selectedIndex] : null;
|
|
70
|
+
return {
|
|
71
|
+
...state,
|
|
72
|
+
selectedItem,
|
|
73
|
+
fetchCompletions,
|
|
74
|
+
updateFilter,
|
|
75
|
+
navigate,
|
|
76
|
+
dismiss,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function replaceWordAtCursor(text, wordRange, replacement) {
|
|
2
|
+
const before = text.substring(0, wordRange.start);
|
|
3
|
+
const after = text.substring(wordRange.end);
|
|
4
|
+
const value = before + replacement + after;
|
|
5
|
+
const cursorPosition = wordRange.start + replacement.length;
|
|
6
|
+
return { value, cursorPosition };
|
|
7
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
const tokenToSyntaxColor = {
|
|
3
|
+
Number: "number",
|
|
4
|
+
String: "string",
|
|
5
|
+
TemplateLiteral: "string",
|
|
6
|
+
DateLiteral: "date",
|
|
7
|
+
Spread: "operator",
|
|
8
|
+
Punctuation: "operator",
|
|
9
|
+
Pipe: "operator",
|
|
10
|
+
Operator: "operator",
|
|
11
|
+
Arrow: "operator",
|
|
12
|
+
Identifier: "identifier",
|
|
13
|
+
QuotedIdentifier: "identifier",
|
|
14
|
+
ExpressionError: "error",
|
|
15
|
+
Unparsed: "punctuation",
|
|
16
|
+
};
|
|
17
|
+
export function DefaultTokenHighlighter({ token }) {
|
|
18
|
+
const expr = tokenToSyntaxColor[token.type];
|
|
19
|
+
return _jsx("span", { style: { color: `var(--ln-expr-${expr})` }, children: token.value });
|
|
20
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { CSSProperties, ReactNode } from "react";
|
|
2
|
+
import type { Token } from "../../expressions/lexer/types.js";
|
|
3
|
+
export type CompletionItem = {
|
|
4
|
+
label: string;
|
|
5
|
+
kind: string;
|
|
6
|
+
id: string;
|
|
7
|
+
value?: string;
|
|
8
|
+
};
|
|
9
|
+
export type KeybindingConfig = {
|
|
10
|
+
accept?: string[];
|
|
11
|
+
dismiss?: string[];
|
|
12
|
+
navigateUp?: string[];
|
|
13
|
+
navigateDown?: string[];
|
|
14
|
+
};
|
|
15
|
+
export type CompletionAction = {
|
|
16
|
+
type: "navigate";
|
|
17
|
+
direction: "up" | "down";
|
|
18
|
+
} | {
|
|
19
|
+
type: "accept";
|
|
20
|
+
} | {
|
|
21
|
+
type: "dismiss";
|
|
22
|
+
} | {
|
|
23
|
+
type: "none";
|
|
24
|
+
};
|
|
25
|
+
export type WordAtCursor = {
|
|
26
|
+
word: string;
|
|
27
|
+
start: number;
|
|
28
|
+
end: number;
|
|
29
|
+
};
|
|
30
|
+
export type CursorCoordinates = {
|
|
31
|
+
top: number;
|
|
32
|
+
left: number;
|
|
33
|
+
lineHeight: number;
|
|
34
|
+
};
|
|
35
|
+
export type ExpressionEditorProps = {
|
|
36
|
+
value: string;
|
|
37
|
+
onChange: (value: string) => void;
|
|
38
|
+
tokenize: (value: string) => Token[];
|
|
39
|
+
highlight?: (props: {
|
|
40
|
+
token: Token;
|
|
41
|
+
}) => ReactNode;
|
|
42
|
+
completionProvider?: (tokens: Token[], index: number) => CompletionItem[] | Promise<CompletionItem[]>;
|
|
43
|
+
placeholder?: string;
|
|
44
|
+
disabled?: boolean;
|
|
45
|
+
readOnly?: boolean;
|
|
46
|
+
className?: string;
|
|
47
|
+
style?: CSSProperties;
|
|
48
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CompletionItem, WordAtCursor } from "./types";
|
|
2
|
+
interface NavigationDeps {
|
|
3
|
+
readonly onValueChange: (value: string) => void;
|
|
4
|
+
readonly onAccepted: (value: string, cursorPosition: number) => void;
|
|
5
|
+
readonly textareaRef: React.RefObject<HTMLTextAreaElement | null>;
|
|
6
|
+
}
|
|
7
|
+
export declare function useKeyboardNavigation({ onValueChange, onAccepted, textareaRef }: NavigationDeps): {
|
|
8
|
+
acceptCompletion: (item: CompletionItem, word: WordAtCursor) => void;
|
|
9
|
+
};
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { replaceWordAtCursor } from "./replace-word-at-cursor.js";
|
|
3
|
+
export function useKeyboardNavigation({ onValueChange, onAccepted, textareaRef }) {
|
|
4
|
+
const acceptCompletion = useCallback((item, word) => {
|
|
5
|
+
const textarea = textareaRef.current;
|
|
6
|
+
if (!textarea)
|
|
7
|
+
return;
|
|
8
|
+
// Extend the replacement range to consume any @"..." / @'...' prefix the user already typed.
|
|
9
|
+
// The partial word may contain spaces (e.g. @"Age Gr → word is "Gr"), so scan backwards
|
|
10
|
+
// through word chars and spaces to find the opening @" / @'.
|
|
11
|
+
let adjustedWord = word;
|
|
12
|
+
const text = textarea.value;
|
|
13
|
+
let scan = word.start - 1;
|
|
14
|
+
// skip back over any chars that could be part of a quoted identifier value (spaces included)
|
|
15
|
+
while (scan > 0 && text[scan] !== '"' && text[scan] !== "'")
|
|
16
|
+
scan--;
|
|
17
|
+
if (scan >= 1 && (text[scan] === '"' || text[scan] === "'") && text[scan - 1] === "@") {
|
|
18
|
+
adjustedWord = { ...word, start: scan - 1 };
|
|
19
|
+
}
|
|
20
|
+
const charBeforeWord = adjustedWord.start > 0 ? text[adjustedWord.start - 1] : "";
|
|
21
|
+
const noSpaceBefore = charBeforeWord === "" ||
|
|
22
|
+
charBeforeWord === " " ||
|
|
23
|
+
charBeforeWord === "." ||
|
|
24
|
+
charBeforeWord === "(" ||
|
|
25
|
+
charBeforeWord === "[" ||
|
|
26
|
+
charBeforeWord === "{";
|
|
27
|
+
const rawInsertText = item.value ?? item.label;
|
|
28
|
+
const insertText = noSpaceBefore ? rawInsertText : " " + rawInsertText;
|
|
29
|
+
const { value, cursorPosition } = replaceWordAtCursor(text, adjustedWord, insertText);
|
|
30
|
+
textarea.value = value;
|
|
31
|
+
textarea.selectionStart = cursorPosition;
|
|
32
|
+
textarea.selectionEnd = cursorPosition;
|
|
33
|
+
onValueChange(value);
|
|
34
|
+
onAccepted(value, cursorPosition);
|
|
35
|
+
}, [onValueChange, onAccepted, textareaRef]);
|
|
36
|
+
return { acceptCompletion };
|
|
37
|
+
}
|
|
@@ -3,14 +3,20 @@ import { forwardRef, memo, useEffect, useRef, useState } from "react";
|
|
|
3
3
|
import { usePillRoot } from "./root.context.js";
|
|
4
4
|
import { usePillRow } from "./pill-row.context.js";
|
|
5
5
|
import { dragX, dragY, getDragData, useCombinedRefs, useSelector, } from "@1771technologies/lytenyte-core/internal";
|
|
6
|
-
import { getFocusables } from "@1771technologies/lytenyte-shared";
|
|
6
|
+
import { equal, getFocusables } from "@1771technologies/lytenyte-shared";
|
|
7
7
|
function ContainerBase(props, forwarded) {
|
|
8
|
-
const { setCloned, orientation, rows, movedRef } = usePillRoot();
|
|
8
|
+
const { setCloned, cloned, orientation, rows, movedRef, onPillRowChange, onPillItemActiveChange } = usePillRoot();
|
|
9
9
|
const { row } = usePillRow();
|
|
10
10
|
const [over, setOver] = useState(false);
|
|
11
|
+
const [external, setExternal] = useState(false);
|
|
11
12
|
const x = useSelector(dragX);
|
|
12
13
|
const y = useSelector(dragY);
|
|
13
14
|
const [container, setContainer] = useState(null);
|
|
15
|
+
const ds = getDragData();
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (!ds)
|
|
18
|
+
setExternal(false);
|
|
19
|
+
}, [ds]);
|
|
14
20
|
const wasOver = useRef(false);
|
|
15
21
|
useEffect(() => {
|
|
16
22
|
if (!container)
|
|
@@ -20,12 +26,26 @@ function ContainerBase(props, forwarded) {
|
|
|
20
26
|
setOver(false);
|
|
21
27
|
return;
|
|
22
28
|
}
|
|
23
|
-
|
|
29
|
+
let { item: dragged, id } = ds.pill.data;
|
|
30
|
+
// We resolve the external item to a dragged item here.
|
|
31
|
+
const originalId = id;
|
|
32
|
+
if (id === "__external__") {
|
|
33
|
+
const d = dragged;
|
|
34
|
+
if (!d[row.id])
|
|
35
|
+
return;
|
|
36
|
+
id = row.id;
|
|
37
|
+
dragged = row.pills.find((x) => x.id === d[row.id]);
|
|
38
|
+
if (!dragged) {
|
|
39
|
+
console.error("Specified an external item, but that item is not in the pill group.");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
24
43
|
const bb = container.getBoundingClientRect();
|
|
25
44
|
const isOver = bb.left < x && bb.right > x && bb.top < y && bb.bottom > y;
|
|
26
45
|
// The current drag is from the current row.
|
|
27
46
|
if (id === row.id) {
|
|
28
47
|
movedRef.current = !isOver ? { id: row.id, pillId: dragged.id } : null;
|
|
48
|
+
setExternal(isOver && originalId === "__external__");
|
|
29
49
|
return;
|
|
30
50
|
}
|
|
31
51
|
const thisRow = rows.findIndex((x) => x.id === row.id);
|
|
@@ -43,11 +63,11 @@ function ContainerBase(props, forwarded) {
|
|
|
43
63
|
setCloned((prev) => {
|
|
44
64
|
if (!prev)
|
|
45
65
|
throw new Error("Can't call drag function without cloning nodes.");
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
return
|
|
66
|
+
const newPillSpec = { ...prev[thisRow] };
|
|
67
|
+
newPillSpec.pills = newPills;
|
|
68
|
+
const newPillRows = [...prev];
|
|
69
|
+
newPillRows[thisRow] = newPillSpec;
|
|
70
|
+
return newPillRows;
|
|
51
71
|
});
|
|
52
72
|
}
|
|
53
73
|
else {
|
|
@@ -80,7 +100,7 @@ function ContainerBase(props, forwarded) {
|
|
|
80
100
|
}, [container, movedRef, over, row, rows, setCloned, x, y]);
|
|
81
101
|
const combined = useCombinedRefs(forwarded, setContainer);
|
|
82
102
|
const rtl = useRef(null);
|
|
83
|
-
return (_jsx("div", { ...props, ref: combined, "data-ln-over": over, "data-ln-orientation": orientation, "data-ln-pill-container": true, "data-ln-pill-type": row.type, tabIndex: 0, onKeyDown: (ev) => {
|
|
103
|
+
return (_jsx("div", { ...props, ref: combined, "data-ln-over": over, "data-ln-external-over": external, "data-ln-orientation": orientation, "data-ln-pill-container": true, "data-ln-pill-type": row.type, tabIndex: 0, onKeyDown: (ev) => {
|
|
84
104
|
if (rtl.current == null) {
|
|
85
105
|
const dir = getComputedStyle(ev.currentTarget).direction;
|
|
86
106
|
rtl.current = dir === "rtl";
|
|
@@ -118,6 +138,32 @@ function ContainerBase(props, forwarded) {
|
|
|
118
138
|
}, onDrop: (e) => {
|
|
119
139
|
e.preventDefault();
|
|
120
140
|
e.stopPropagation();
|
|
141
|
+
const ds = getDragData();
|
|
142
|
+
if (!ds?.pill?.data || ds.pill.data.id != "__external__")
|
|
143
|
+
return;
|
|
144
|
+
let { item: dragged } = ds.pill.data;
|
|
145
|
+
// We resolve the external item to a dragged item here.
|
|
146
|
+
const d = dragged;
|
|
147
|
+
if (!d[row.id])
|
|
148
|
+
return;
|
|
149
|
+
dragged = row.pills.find((x) => x.id === d[row.id]);
|
|
150
|
+
const changed = cloned?.filter((x, i) => {
|
|
151
|
+
return !equal(x, rows[i]);
|
|
152
|
+
});
|
|
153
|
+
if (!dragged.active) {
|
|
154
|
+
const next = true;
|
|
155
|
+
const nextPill = { ...dragged, active: next };
|
|
156
|
+
const nextPills = [...row.pills];
|
|
157
|
+
nextPills.splice(nextPills.indexOf(dragged), 1, nextPill);
|
|
158
|
+
const nextRow = { ...row, pills: nextPills };
|
|
159
|
+
const index = rows.indexOf(row);
|
|
160
|
+
onPillItemActiveChange({ index, item: nextPill, row: nextRow });
|
|
161
|
+
}
|
|
162
|
+
dragged.active = true;
|
|
163
|
+
if (changed?.length) {
|
|
164
|
+
onPillRowChange({ changed, full: cloned });
|
|
165
|
+
}
|
|
166
|
+
setCloned(null);
|
|
121
167
|
}, onDragOver: (e) => {
|
|
122
168
|
e.dataTransfer.dropEffect = "move";
|
|
123
169
|
e.preventDefault();
|
|
@@ -53,7 +53,18 @@ function PillItemBase({ item, elementEnd, ...props }, ref) {
|
|
|
53
53
|
const ds = getDragData();
|
|
54
54
|
if (!ds?.pill?.data)
|
|
55
55
|
return;
|
|
56
|
-
|
|
56
|
+
let { item: dragged, id: dragRowId } = ds.pill.data;
|
|
57
|
+
const original = dragRowId;
|
|
58
|
+
// We resolve the external item to a dragged item here.
|
|
59
|
+
if (dragRowId === "__external__") {
|
|
60
|
+
const d = dragged;
|
|
61
|
+
if (!d[row.id])
|
|
62
|
+
return;
|
|
63
|
+
dragRowId = row.id;
|
|
64
|
+
dragged = row.pills.find((x) => x.id === d[row.id]);
|
|
65
|
+
if (!dragged)
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
57
68
|
const accepts = new Set([...(row.accepts ?? []), row.id]);
|
|
58
69
|
if (!accepts.has(dragRowId) && !item.tags?.some((x) => accepts.has(x)))
|
|
59
70
|
return;
|
|
@@ -93,6 +104,9 @@ function PillItemBase({ item, elementEnd, ...props }, ref) {
|
|
|
93
104
|
prevSwapId.current = currentSwapId;
|
|
94
105
|
prevRowId.current = currentRowId;
|
|
95
106
|
setCloned((prev) => {
|
|
107
|
+
if (original === "__external__" && !prev) {
|
|
108
|
+
prev = structuredClone(rows);
|
|
109
|
+
}
|
|
96
110
|
if (!prev)
|
|
97
111
|
throw new Error("Can't call drag function without cloning nodes.");
|
|
98
112
|
const thisRow = prev.findIndex((x) => x.id === row.id);
|
|
@@ -6,7 +6,7 @@ export function SelectAll({ api, slot, }) {
|
|
|
6
6
|
const s = api.useSelectionState();
|
|
7
7
|
const selected = useMemo(() => {
|
|
8
8
|
const selected = s.kind === "isolated" ? s.selected && s.exceptions.size === 0 : s.selected && s.children.size === 0;
|
|
9
|
-
return selected;
|
|
9
|
+
return !!selected;
|
|
10
10
|
}, [s]);
|
|
11
11
|
const indeterminate = useMemo(() => {
|
|
12
12
|
if (s.kind === "isolated")
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
2
|
import { type RowGroup, type RowLeaf, type RowSelectionLinked } from "@1771technologies/lytenyte-shared";
|
|
3
3
|
import type { TreeViewChildParams, TreeViewItem, TreeViewSelectAllParams } from "./types.js";
|
|
4
|
+
import type { DragItem, RowNode } from "../../types.js";
|
|
4
5
|
export interface TreeViewProps<T extends TreeViewItem> {
|
|
5
6
|
readonly items: T[];
|
|
6
7
|
readonly children?: (props: TreeViewChildParams<T>) => ReactNode;
|
|
@@ -22,6 +23,14 @@ export interface TreeViewProps<T extends TreeViewItem> {
|
|
|
22
23
|
* and drag is still being prototyped.
|
|
23
24
|
*/
|
|
24
25
|
readonly draggable?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* @alpha
|
|
28
|
+
* @internal
|
|
29
|
+
*
|
|
30
|
+
* Do not use this property unless you know what you are doing. Support for tree view drag
|
|
31
|
+
* and drag is still being prototyped.
|
|
32
|
+
*/
|
|
33
|
+
readonly getDragTags?: (row: RowNode<T>, leafs: RowLeaf<T>[]) => Record<string, DragItem>;
|
|
25
34
|
/**
|
|
26
35
|
* @alpha
|
|
27
36
|
* @internal
|
|
@@ -7,7 +7,7 @@ import { useClientDataSource } from "../../data-source-client/use-client-data-so
|
|
|
7
7
|
import { usePiece } from "@1771technologies/lytenyte-core/internal";
|
|
8
8
|
import { Root } from "../../root/root.js";
|
|
9
9
|
import { Grid } from "@1771technologies/lytenyte-core";
|
|
10
|
-
function TreeViewBase({ items, rowSelectAllShow: rowSelectAll = true, defaultExpansion, rowHeight = 28, children = TreeChildren, branchJoinSeparator = "/", rowSelectionEnabled = true, rowSelection, onRowSelectionChange, rowGroupExpansions, onRowGroupExpansionChange, selectAllSlot, draggable, onItemsReordered, }, forwarded) {
|
|
10
|
+
function TreeViewBase({ items, rowSelectAllShow: rowSelectAll = true, defaultExpansion, rowHeight = 28, children = TreeChildren, branchJoinSeparator = "/", rowSelectionEnabled = true, rowSelection, onRowSelectionChange, rowGroupExpansions, onRowGroupExpansionChange, selectAllSlot, draggable, getDragTags, onItemsReordered, }, forwarded) {
|
|
11
11
|
const groupFn = useMemo(() => {
|
|
12
12
|
return (x) => x.data.path;
|
|
13
13
|
}, []);
|
|
@@ -46,8 +46,10 @@ function TreeViewBase({ items, rowSelectAllShow: rowSelectAll = true, defaultExp
|
|
|
46
46
|
width: 20,
|
|
47
47
|
widthMin: 20,
|
|
48
48
|
cellRenderer: ({ row, rowIndex, api, indeterminate, selected }) => {
|
|
49
|
+
const tags = getDragTags?.(row, api.rowLeafs(row.id).map((x) => api.rowById(x)));
|
|
49
50
|
const { props, isDragActive } = api.useRowDrag({
|
|
50
51
|
rowIndex,
|
|
52
|
+
tags,
|
|
51
53
|
});
|
|
52
54
|
const over = over$.useValue();
|
|
53
55
|
const isBefore = isBefore$.useValue();
|
|
@@ -76,7 +78,7 @@ function TreeViewBase({ items, rowSelectAllShow: rowSelectAll = true, defaultExp
|
|
|
76
78
|
});
|
|
77
79
|
},
|
|
78
80
|
};
|
|
79
|
-
}, [children, draggable, isBefore$, itemLookup, over$, rowSelectionEnabled, selectAllSlot]);
|
|
81
|
+
}, [children, draggable, getDragTags, isBefore$, itemLookup, over$, rowSelectionEnabled, selectAllSlot]);
|
|
80
82
|
useImperativeHandle(forwarded, () => {
|
|
81
83
|
return {
|
|
82
84
|
rowsSelected: () => (api?.rowsSelected().rows ?? []).filter((x) => x.id !== "__ln_select_all"),
|
|
@@ -39,7 +39,7 @@ export function useFlattenedGroups(root, agg, leafs, workingSet, sort, expandedF
|
|
|
39
39
|
}, [agg, expandedFn, ignoreIsLast, leafs, root, sort, sortFallback, suppressLeafExpansion, workingSet]);
|
|
40
40
|
return flat;
|
|
41
41
|
}
|
|
42
|
-
const nodeChildrenToRows = (root, agg, leafs, workingSet, sort,
|
|
42
|
+
const nodeChildrenToRows = (root, agg, leafs, workingSet, sort, _) => {
|
|
43
43
|
const values = root.values();
|
|
44
44
|
const rows = [];
|
|
45
45
|
for (const v of values) {
|
|
@@ -57,7 +57,7 @@ const nodeChildrenToRows = (root, agg, leafs, workingSet, sort, isLast) => {
|
|
|
57
57
|
rows.push(row);
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
-
if (
|
|
60
|
+
if (sort)
|
|
61
61
|
return rows.sort(sort);
|
|
62
62
|
return rows;
|
|
63
63
|
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function evalConstant(op, left, right) {
|
|
2
|
+
if (typeof left === "number" && typeof right === "number") {
|
|
3
|
+
switch (op) {
|
|
4
|
+
case "+":
|
|
5
|
+
return left + right;
|
|
6
|
+
case "-":
|
|
7
|
+
return left - right;
|
|
8
|
+
case "*":
|
|
9
|
+
return left * right;
|
|
10
|
+
case "/":
|
|
11
|
+
return left / right;
|
|
12
|
+
case "%":
|
|
13
|
+
return left % right;
|
|
14
|
+
case "**":
|
|
15
|
+
return left ** right;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function makeConstant(value, start, end) {
|
|
2
|
+
if (typeof value === "number")
|
|
3
|
+
return { type: "NumberLiteral", value, start, end };
|
|
4
|
+
if (typeof value === "string")
|
|
5
|
+
return { type: "StringLiteral", value, start, end };
|
|
6
|
+
if (typeof value === "boolean")
|
|
7
|
+
return { type: "BooleanLiteral", value, start, end };
|
|
8
|
+
if (value === null)
|
|
9
|
+
return { type: "NullLiteral", value: null, start, end };
|
|
10
|
+
return { type: "UndefinedLiteral", value: undefined, start, end };
|
|
11
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { isConstant } from "./is-constant.js";
|
|
2
|
+
import { valueOf } from "./value-of.js";
|
|
3
|
+
import { evalConstant } from "./eval-constant.js";
|
|
4
|
+
import { makeConstant } from "./make-constant.js";
|
|
5
|
+
export function optimize(node, plugins) {
|
|
6
|
+
const opt = (n) => optimize(n, plugins);
|
|
7
|
+
// Try plugin optimization first
|
|
8
|
+
if (plugins) {
|
|
9
|
+
for (const plugin of plugins) {
|
|
10
|
+
if (plugin.optimize) {
|
|
11
|
+
const result = plugin.optimize(node, opt);
|
|
12
|
+
if (result)
|
|
13
|
+
return result;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
switch (node.type) {
|
|
18
|
+
case "BinaryExpression": {
|
|
19
|
+
const left = opt(node.left);
|
|
20
|
+
const right = opt(node.right);
|
|
21
|
+
if (isConstant(left) && isConstant(right)) {
|
|
22
|
+
const result = evalConstant(node.operator, valueOf(left), valueOf(right));
|
|
23
|
+
if (result !== undefined) {
|
|
24
|
+
return makeConstant(result, node.start, node.end);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return { ...node, left, right };
|
|
28
|
+
}
|
|
29
|
+
case "UnaryExpression": {
|
|
30
|
+
const operand = opt(node.operand);
|
|
31
|
+
if (isConstant(operand)) {
|
|
32
|
+
const val = valueOf(operand);
|
|
33
|
+
if (node.operator === "-" && typeof val === "number") {
|
|
34
|
+
return {
|
|
35
|
+
type: "NumberLiteral",
|
|
36
|
+
value: -val,
|
|
37
|
+
start: node.start,
|
|
38
|
+
end: node.end,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return { ...node, operand };
|
|
43
|
+
}
|
|
44
|
+
default:
|
|
45
|
+
return node;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function valueOf(node) {
|
|
2
|
+
if (node.type === "NumberLiteral")
|
|
3
|
+
return node.value;
|
|
4
|
+
if (node.type === "StringLiteral")
|
|
5
|
+
return node.value;
|
|
6
|
+
if (node.type === "BooleanLiteral")
|
|
7
|
+
return node.value;
|
|
8
|
+
if (node.type === "NullLiteral")
|
|
9
|
+
return null;
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function clampOffset(source: string, offset: number): number;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ErrorLocation } from "./types.js";
|
|
2
|
+
/** Syntax error thrown during parsing. Contains source location and formatted caret display. */
|
|
3
|
+
export declare class ExpressionError extends Error {
|
|
4
|
+
readonly rawMessage: string;
|
|
5
|
+
readonly start: number;
|
|
6
|
+
readonly end: number;
|
|
7
|
+
readonly source: string;
|
|
8
|
+
readonly suggestion?: string;
|
|
9
|
+
constructor(message: string, location: ErrorLocation, suggestion?: string);
|
|
10
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { formatError } from "./format-error.js";
|
|
2
|
+
/** Syntax error thrown during parsing. Contains source location and formatted caret display. */
|
|
3
|
+
export class ExpressionError extends Error {
|
|
4
|
+
rawMessage;
|
|
5
|
+
start;
|
|
6
|
+
end;
|
|
7
|
+
source;
|
|
8
|
+
suggestion;
|
|
9
|
+
constructor(message, location, suggestion) {
|
|
10
|
+
const formatted = formatError(message, location, suggestion);
|
|
11
|
+
super(formatted);
|
|
12
|
+
this.name = "ExpressionError";
|
|
13
|
+
this.rawMessage = message;
|
|
14
|
+
this.start = location.start;
|
|
15
|
+
this.end = location.end;
|
|
16
|
+
this.source = location.source;
|
|
17
|
+
this.suggestion = suggestion;
|
|
18
|
+
}
|
|
19
|
+
}
|