@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,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,5 @@
1
+ import type { WordAtCursor } from "./types.js";
2
+ export declare function replaceWordAtCursor(text: string, wordRange: WordAtCursor, replacement: string): {
3
+ value: string;
4
+ cursorPosition: number;
5
+ };
@@ -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,4 @@
1
+ import type { Token } from "../../expressions/lexer/types.js";
2
+ export declare function DefaultTokenHighlighter({ token }: {
3
+ token: Token;
4
+ }): import("react/jsx-runtime").JSX.Element;
@@ -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
- const { item: dragged, id } = ds.pill.data;
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 newPill = { ...prev[thisRow] };
47
- newPill.pills = newPills;
48
- const newDef = [...prev];
49
- newDef[thisRow] = newPill;
50
- return newDef;
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
- const { item: dragged, id: dragRowId } = ds.pill.data;
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, isLast) => {
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 (!isLast && sort)
60
+ if (sort)
61
61
  return rows.sort(sort);
62
62
  return rows;
63
63
  };
@@ -0,0 +1,3 @@
1
+ import type { ASTNode } from "../parser/types.js";
2
+ import type { Plugin } from "../plugin.js";
3
+ export declare function compile(ast: ASTNode, plugins?: Plugin[]): ASTNode;
@@ -0,0 +1,4 @@
1
+ import { optimize } from "./optimize.js";
2
+ export function compile(ast, plugins) {
3
+ return optimize(ast, plugins);
4
+ }
@@ -0,0 +1,2 @@
1
+ import type { BinaryExpressionOperator } from "../lexer/types.js";
2
+ export declare function evalConstant(op: BinaryExpressionOperator, left: unknown, right: unknown): unknown | undefined;
@@ -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,2 @@
1
+ import type { ASTNode } from "../parser/types.js";
2
+ export declare function isConstant(node: ASTNode): boolean;
@@ -0,0 +1,6 @@
1
+ export function isConstant(node) {
2
+ return (node.type === "NumberLiteral" ||
3
+ node.type === "StringLiteral" ||
4
+ node.type === "BooleanLiteral" ||
5
+ node.type === "NullLiteral");
6
+ }
@@ -0,0 +1,2 @@
1
+ import type { ASTNode } from "../parser/types.js";
2
+ export declare function makeConstant(value: unknown, start: number, end: number): ASTNode;
@@ -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,3 @@
1
+ import type { ASTNode } from "../parser/types.js";
2
+ import type { Plugin } from "../plugin.js";
3
+ export declare function optimize(node: ASTNode, plugins?: Plugin[]): ASTNode;
@@ -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,2 @@
1
+ import type { ASTNode } from "../parser/types.js";
2
+ export declare function valueOf(node: ASTNode): unknown;
@@ -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,3 @@
1
+ export function clampOffset(source, offset) {
2
+ return Math.max(0, Math.min(offset, source.length));
3
+ }
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ import type { ErrorLocation } from "./types.js";
2
+ /** Format an error message with source context, caret display, and position info. */
3
+ export declare function formatError(message: string, location: ErrorLocation, suggestion?: string): string;