@wallarm-org/design-system 0.21.0 → 0.21.1
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/dist/components/DropdownMenu/DropdownMenuContent.js +1 -1
- package/dist/components/FilterInput/FilterInput.js +1 -0
- package/dist/components/FilterInput/FilterInputField/FilterInputChip/FilterInputChip.js +2 -2
- package/dist/components/FilterInput/FilterInputField/FilterInputSearch.js +1 -1
- package/dist/components/FilterInput/FilterInputField/InsertionGap/classes.d.ts +1 -1
- package/dist/components/FilterInput/FilterInputField/InsertionGap/classes.js +1 -1
- package/dist/components/FilterInput/FilterInputMenu/FilterInputValueMenu/ValueMenuItem.js +12 -6
- package/dist/components/FilterInput/hooks/useFilterInputSelection/lib/dom.d.ts +18 -4
- package/dist/components/FilterInput/hooks/useFilterInputSelection/lib/dom.js +74 -19
- package/dist/components/FilterInput/hooks/useFilterInputSelection/lib/index.d.ts +1 -1
- package/dist/components/FilterInput/hooks/useFilterInputSelection/lib/index.js +2 -2
- package/dist/components/FilterInput/hooks/useFilterInputSelection/useDragSelection.d.ts +2 -2
- package/dist/components/FilterInput/hooks/useFilterInputSelection/useDragSelection.js +17 -13
- package/dist/components/FilterInput/hooks/useFilterInputSelection/useFilterInputSelection.d.ts +2 -1
- package/dist/components/FilterInput/hooks/useFilterInputSelection/useFilterInputSelection.js +19 -4
- package/dist/components/FilterInput/lib/parseExpression/tokenizer.js +14 -0
- package/dist/components/FilterInput/lib/serializeExpression.js +3 -5
- package/dist/metadata/components.json +2 -2
- package/package.json +1 -1
|
@@ -31,7 +31,7 @@ const DropdownMenuContent = ({ className, children, ref, ...props })=>{
|
|
|
31
31
|
children: [
|
|
32
32
|
/*#__PURE__*/ jsx(ScrollAreaViewport, {
|
|
33
33
|
children: /*#__PURE__*/ jsx(ScrollAreaContent, {
|
|
34
|
-
className: cn('flex flex-col gap-1'),
|
|
34
|
+
className: cn('flex flex-col gap-1 !min-w-0'),
|
|
35
35
|
children: menuChildren
|
|
36
36
|
})
|
|
37
37
|
}),
|
|
@@ -44,7 +44,7 @@ const FilterInputChip = ({ ref, chipId, attribute, operator, value, error = fals
|
|
|
44
44
|
error: hasError,
|
|
45
45
|
interactive,
|
|
46
46
|
disabled
|
|
47
|
-
}), 'max-w-[
|
|
47
|
+
}), 'max-w-[320px]', className),
|
|
48
48
|
"data-slot": "filter-input-condition-chip",
|
|
49
49
|
...props,
|
|
50
50
|
children: [
|
|
@@ -65,7 +65,7 @@ const FilterInputChip = ({ ref, chipId, attribute, operator, value, error = fals
|
|
|
65
65
|
}),
|
|
66
66
|
(value || 'value' === activeSegment) && /*#__PURE__*/ jsx(Segment, {
|
|
67
67
|
variant: "value",
|
|
68
|
-
className: "
|
|
68
|
+
className: "min-w-0",
|
|
69
69
|
error: 'value' !== activeSegment && (true === error || 'value' === error),
|
|
70
70
|
valueParts: valueParts,
|
|
71
71
|
valueSeparator: valueSeparator,
|
|
@@ -2,7 +2,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { useFilterInputContext } from "../FilterInputContext/index.js";
|
|
3
3
|
import { filterInputInputVariants } from "./classes.js";
|
|
4
4
|
const CHAR_WIDTH_PX = 10;
|
|
5
|
-
const MIN_INPUT_WIDTH =
|
|
5
|
+
const MIN_INPUT_WIDTH = 4;
|
|
6
6
|
const FilterInputSearch = ({ hasContent })=>{
|
|
7
7
|
const { inputText, inputRef, placeholder, error, menuOpen, onInputChange, onInputKeyDown, onInputClick } = useFilterInputContext();
|
|
8
8
|
return /*#__PURE__*/ jsx("input", {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
/** Insertion gap button */
|
|
2
|
-
export declare const insertionGapButton = "group relative z-20 flex h-24 w-
|
|
2
|
+
export declare const insertionGapButton = "group relative z-20 flex h-24 w-4 shrink-0 cursor-text items-center justify-center";
|
|
3
3
|
/** Insertion gap divider indicator */
|
|
4
4
|
export declare const insertionGapDivider = "h-16 w-1 rounded-full bg-transparent transition-colors group-hover:bg-border-primary/50";
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
const insertionGapButton = 'group relative z-20 flex h-24 w-
|
|
1
|
+
const insertionGapButton = 'group relative z-20 flex h-24 w-4 shrink-0 cursor-text items-center justify-center';
|
|
2
2
|
const insertionGapDivider = 'h-16 w-1 rounded-full bg-transparent transition-colors group-hover:bg-border-primary/50';
|
|
3
3
|
export { insertionGapButton, insertionGapDivider };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { ChevronRight } from "../../../../icons/ChevronRight.js";
|
|
3
3
|
import { Checkmark } from "../../../Checkmark/index.js";
|
|
4
|
-
import { DropdownMenuItem
|
|
4
|
+
import { DropdownMenuItem } from "../../../DropdownMenu/index.js";
|
|
5
|
+
import { Text } from "../../../Text/index.js";
|
|
5
6
|
const ValueMenuItem = ({ option, isChecked, isPending, multiSelect, onSelect })=>/*#__PURE__*/ jsxs(DropdownMenuItem, {
|
|
6
7
|
value: String(option.value),
|
|
7
8
|
onSelect: onSelect,
|
|
@@ -17,20 +18,25 @@ const ValueMenuItem = ({ option, isChecked, isPending, multiSelect, onSelect })=
|
|
|
17
18
|
className: "size-6 rounded-full bg-current"
|
|
18
19
|
}),
|
|
19
20
|
/*#__PURE__*/ jsx("span", {
|
|
20
|
-
className: "leading-4
|
|
21
|
+
className: "min-w-0 truncate leading-4",
|
|
21
22
|
children: option.badge.text
|
|
22
23
|
})
|
|
23
24
|
]
|
|
24
|
-
}) : /*#__PURE__*/ jsx(
|
|
25
|
-
|
|
25
|
+
}) : /*#__PURE__*/ jsx("div", {
|
|
26
|
+
className: "min-w-0",
|
|
27
|
+
children: /*#__PURE__*/ jsx(Text, {
|
|
28
|
+
size: "sm",
|
|
29
|
+
truncate: true,
|
|
30
|
+
children: option.label
|
|
31
|
+
})
|
|
26
32
|
}),
|
|
27
33
|
multiSelect ? /*#__PURE__*/ jsx("div", {
|
|
28
|
-
className: "flex items-start justify-end py-2 ml-auto",
|
|
34
|
+
className: "flex shrink-0 items-start justify-end py-2 ml-auto",
|
|
29
35
|
children: /*#__PURE__*/ jsx(Checkmark, {
|
|
30
36
|
checkedState: isChecked
|
|
31
37
|
})
|
|
32
38
|
}) : isChecked ? /*#__PURE__*/ jsx("div", {
|
|
33
|
-
className: "flex items-start justify-end py-2 ml-auto",
|
|
39
|
+
className: "flex shrink-0 items-start justify-end py-2 ml-auto",
|
|
34
40
|
children: /*#__PURE__*/ jsx(Checkmark, {
|
|
35
41
|
checkedState: true
|
|
36
42
|
})
|
|
@@ -2,7 +2,21 @@ export declare const clearDragAttributes: (chips: Map<string, HTMLElement>) => v
|
|
|
2
2
|
export declare const hasDragSelection: (chips: Map<string, HTMLElement>) => boolean;
|
|
3
3
|
/** Get condition indices of drag-selected chips (chip-0, chip-2 → [0, 2]) */
|
|
4
4
|
export declare const getSelectedConditionIndices: (chips: Map<string, HTMLElement>) => number[];
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
interface VisualEntry {
|
|
6
|
+
id: string;
|
|
7
|
+
el: HTMLElement;
|
|
8
|
+
rect: DOMRect;
|
|
9
|
+
/** Index in the full sorted array (preserved through condition filtering) */
|
|
10
|
+
allIdx: number;
|
|
11
|
+
}
|
|
12
|
+
/** Snapshot registry entries with cached rects, sorted by visual position (top→bottom, left→right) */
|
|
13
|
+
export declare const getVisualEntries: (registry: Map<string, HTMLElement>) => VisualEntry[];
|
|
14
|
+
/** Resolve the condition chip ID at the mousedown point (called once per drag). */
|
|
15
|
+
export declare const resolveAnchorChipId: (registry: Map<string, HTMLElement>, target: HTMLElement, x: number, y: number) => string | null;
|
|
16
|
+
/**
|
|
17
|
+
* Mark chips as drag-selected from a fixed anchor chip to the current cursor position.
|
|
18
|
+
* Both anchor and end are resolved in the same visually-sorted array so indices always match.
|
|
19
|
+
* Connectors between selected condition chips are included automatically.
|
|
20
|
+
*/
|
|
21
|
+
export declare const updateDragSelection: (all: VisualEntry[], anchorChipId: string, currentX: number, currentY: number) => boolean;
|
|
22
|
+
export {};
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import { CHIP_ID_PREFIX } from "./constants.js";
|
|
2
2
|
const DRAG_ATTR = 'data-drag-selected';
|
|
3
|
-
const isChipInRange = (chip, x1, x2)=>{
|
|
4
|
-
const rect = chip.getBoundingClientRect();
|
|
5
|
-
const minX = Math.min(x1, x2);
|
|
6
|
-
const maxX = Math.max(x1, x2);
|
|
7
|
-
return rect.left <= maxX && rect.right >= minX;
|
|
8
|
-
};
|
|
9
3
|
const clearDragAttributes = (chips)=>{
|
|
10
4
|
[
|
|
11
5
|
...chips.values()
|
|
@@ -17,18 +11,79 @@ const hasDragSelection = (chips)=>[
|
|
|
17
11
|
const getSelectedConditionIndices = (chips)=>[
|
|
18
12
|
...chips.entries()
|
|
19
13
|
].filter(([id, el])=>id.startsWith(CHIP_ID_PREFIX) && el.hasAttribute(DRAG_ATTR)).map(([id])=>Number(id.slice(CHIP_ID_PREFIX.length))).filter((index)=>!Number.isNaN(index)).sort((a, b)=>a - b);
|
|
20
|
-
const
|
|
21
|
-
...registry.
|
|
22
|
-
].
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
14
|
+
const getVisualEntries = (registry)=>[
|
|
15
|
+
...registry.entries()
|
|
16
|
+
].map(([id, el])=>({
|
|
17
|
+
id,
|
|
18
|
+
el,
|
|
19
|
+
rect: el.getBoundingClientRect(),
|
|
20
|
+
allIdx: 0
|
|
21
|
+
})).sort((a, b)=>{
|
|
22
|
+
const rowDiff = a.rect.top - b.rect.top;
|
|
23
|
+
if (Math.abs(rowDiff) > Math.min(a.rect.height, b.rect.height) / 2) return rowDiff;
|
|
24
|
+
return a.rect.left - b.rect.left;
|
|
25
|
+
}).map((entry, i)=>({
|
|
26
|
+
...entry,
|
|
27
|
+
allIdx: i
|
|
28
|
+
}));
|
|
29
|
+
const findClosestByDistance = (entries, x, y)=>entries.reduce((best, entry, i)=>{
|
|
30
|
+
const r = entry.rect;
|
|
31
|
+
const dx = Math.max(r.left - x, 0, x - r.right);
|
|
32
|
+
const dy = Math.max(r.top - y, 0, y - r.bottom);
|
|
33
|
+
const dist = dx * dx + dy * dy;
|
|
34
|
+
return dist < best.dist ? {
|
|
35
|
+
idx: i,
|
|
36
|
+
dist
|
|
37
|
+
} : best;
|
|
38
|
+
}, {
|
|
39
|
+
idx: -1,
|
|
40
|
+
dist: 1 / 0
|
|
41
|
+
}).idx;
|
|
42
|
+
const findByReadingOrder = (entries, x, y)=>{
|
|
43
|
+
if (0 === entries.length) return -1;
|
|
44
|
+
const exactIdx = entries.findIndex((e)=>x >= e.rect.left && x <= e.rect.right && y >= e.rect.top && y <= e.rect.bottom);
|
|
45
|
+
if (-1 !== exactIdx) return exactIdx;
|
|
46
|
+
const chipHeight = entries[0].rect.height;
|
|
47
|
+
const lastBefore = entries.reduce((best, entry, i)=>{
|
|
48
|
+
const cy = (entry.rect.top + entry.rect.bottom) / 2;
|
|
49
|
+
const cx = (entry.rect.left + entry.rect.right) / 2;
|
|
50
|
+
const rowAbove = cy + chipHeight / 2 < y;
|
|
51
|
+
const sameRow = !rowAbove && Math.abs(cy - y) <= chipHeight;
|
|
52
|
+
if (rowAbove || sameRow && cx <= x) return i;
|
|
53
|
+
return best;
|
|
54
|
+
}, -1);
|
|
55
|
+
return lastBefore >= 0 ? lastBefore : findClosestByDistance(entries, x, y);
|
|
56
|
+
};
|
|
57
|
+
const resolveAnchorChipId = (registry, target, x, y)=>{
|
|
58
|
+
const hit = [
|
|
30
59
|
...registry.entries()
|
|
31
|
-
].
|
|
32
|
-
|
|
60
|
+
].find(([id, el])=>id.startsWith(CHIP_ID_PREFIX) && el.contains(target));
|
|
61
|
+
if (hit) return hit[0];
|
|
62
|
+
const conditions = getVisualEntries(registry).filter((e)=>e.id.startsWith(CHIP_ID_PREFIX));
|
|
63
|
+
if (0 === conditions.length) return null;
|
|
64
|
+
const idx = findClosestByDistance(conditions, x, y);
|
|
65
|
+
return idx >= 0 ? conditions[idx].id : null;
|
|
66
|
+
};
|
|
67
|
+
const updateDragSelection = (all, anchorChipId, currentX, currentY)=>{
|
|
68
|
+
if (0 === all.length) return false;
|
|
69
|
+
const conditions = all.filter((e)=>e.id.startsWith(CHIP_ID_PREFIX));
|
|
70
|
+
if (0 === conditions.length) return false;
|
|
71
|
+
const anchorIdx = conditions.findIndex((e)=>e.id === anchorChipId);
|
|
72
|
+
if (-1 === anchorIdx) return false;
|
|
73
|
+
const endIdx = findByReadingOrder(conditions, currentX, currentY);
|
|
74
|
+
if (-1 === endIdx) return false;
|
|
75
|
+
const minIdx = Math.min(anchorIdx, endIdx);
|
|
76
|
+
const maxIdx = Math.max(anchorIdx, endIdx);
|
|
77
|
+
const firstAll = conditions[minIdx].allIdx;
|
|
78
|
+
const lastAll = conditions[maxIdx].allIdx;
|
|
79
|
+
let found = false;
|
|
80
|
+
all.forEach((entry, i)=>{
|
|
81
|
+
const inRange = i >= firstAll && i <= lastAll;
|
|
82
|
+
if (inRange) {
|
|
83
|
+
entry.el.setAttribute(DRAG_ATTR, '');
|
|
84
|
+
found = true;
|
|
85
|
+
} else entry.el.removeAttribute(DRAG_ATTR);
|
|
86
|
+
});
|
|
87
|
+
return found;
|
|
33
88
|
};
|
|
34
|
-
export {
|
|
89
|
+
export { clearDragAttributes, getSelectedConditionIndices, getVisualEntries, hasDragSelection, resolveAnchorChipId, updateDragSelection };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { CHIP_ID_PREFIX, DRAG_THRESHOLD, PASTE_ERROR_TIMEOUT } from './constants';
|
|
2
|
-
export {
|
|
2
|
+
export { clearDragAttributes, getSelectedConditionIndices, getVisualEntries, hasDragSelection, resolveAnchorChipId, updateDragSelection, } from './dom';
|
|
3
3
|
export { serializeSelectedOrAll } from './serialize';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { CHIP_ID_PREFIX, DRAG_THRESHOLD, PASTE_ERROR_TIMEOUT } from "./constants.js";
|
|
2
|
-
import {
|
|
2
|
+
import { clearDragAttributes, getSelectedConditionIndices, getVisualEntries, hasDragSelection, resolveAnchorChipId, updateDragSelection } from "./dom.js";
|
|
3
3
|
import { serializeSelectedOrAll } from "./serialize.js";
|
|
4
|
-
export { CHIP_ID_PREFIX, DRAG_THRESHOLD, PASTE_ERROR_TIMEOUT,
|
|
4
|
+
export { CHIP_ID_PREFIX, DRAG_THRESHOLD, PASTE_ERROR_TIMEOUT, clearDragAttributes, getSelectedConditionIndices, getVisualEntries, hasDragSelection, resolveAnchorChipId, serializeSelectedOrAll, updateDragSelection };
|
|
@@ -2,9 +2,9 @@ import type { MouseEvent, RefObject } from 'react';
|
|
|
2
2
|
interface UseDragSelectionOptions {
|
|
3
3
|
chipRegistryRef: RefObject<Map<string, HTMLElement>>;
|
|
4
4
|
inputRef: RefObject<HTMLInputElement | null>;
|
|
5
|
-
|
|
5
|
+
closeMenu: () => void;
|
|
6
6
|
}
|
|
7
|
-
export declare const useDragSelection: ({ chipRegistryRef, inputRef,
|
|
7
|
+
export declare const useDragSelection: ({ chipRegistryRef, inputRef, closeMenu, }: UseDragSelectionOptions) => {
|
|
8
8
|
handleMouseDown: (e: MouseEvent<HTMLDivElement>) => void;
|
|
9
9
|
};
|
|
10
10
|
export {};
|
|
@@ -1,33 +1,37 @@
|
|
|
1
1
|
import { useCallback, useRef } from "react";
|
|
2
|
-
import { DRAG_THRESHOLD,
|
|
3
|
-
const
|
|
2
|
+
import { DRAG_THRESHOLD, getVisualEntries, resolveAnchorChipId, updateDragSelection } from "./lib/index.js";
|
|
3
|
+
const DRAG_IGNORE_SELECTOR = 'input, [role="combobox"]';
|
|
4
|
+
const useDragSelection = ({ chipRegistryRef, inputRef, closeMenu })=>{
|
|
4
5
|
const isDraggingRef = useRef(false);
|
|
5
6
|
const dragStartXRef = useRef(0);
|
|
7
|
+
const dragStartYRef = useRef(0);
|
|
8
|
+
const anchorChipIdRef = useRef(null);
|
|
9
|
+
const visualEntriesRef = useRef([]);
|
|
6
10
|
const handleMouseDown = useCallback((e)=>{
|
|
7
11
|
if (0 !== e.button) return;
|
|
8
12
|
const target = e.target;
|
|
9
|
-
if (target.closest(
|
|
13
|
+
if (target.closest(DRAG_IGNORE_SELECTOR)) return;
|
|
10
14
|
dragStartXRef.current = e.clientX;
|
|
15
|
+
dragStartYRef.current = e.clientY;
|
|
11
16
|
isDraggingRef.current = false;
|
|
17
|
+
anchorChipIdRef.current = resolveAnchorChipId(chipRegistryRef.current, target, e.clientX, e.clientY);
|
|
18
|
+
visualEntriesRef.current = getVisualEntries(chipRegistryRef.current);
|
|
12
19
|
const handleMouseMove = (moveEvent)=>{
|
|
13
|
-
|
|
20
|
+
const dx = moveEvent.clientX - dragStartXRef.current;
|
|
21
|
+
const dy = moveEvent.clientY - dragStartYRef.current;
|
|
22
|
+
if (Math.hypot(dx, dy) < DRAG_THRESHOLD) return;
|
|
14
23
|
if (!isDraggingRef.current) {
|
|
15
24
|
isDraggingRef.current = true;
|
|
16
25
|
document.body.style.userSelect = 'none';
|
|
26
|
+
closeMenu();
|
|
27
|
+
inputRef.current?.blur();
|
|
17
28
|
}
|
|
18
|
-
|
|
19
|
-
if (hasSelected) inputRef.current?.blur();
|
|
29
|
+
if (anchorChipIdRef.current) updateDragSelection(visualEntriesRef.current, anchorChipIdRef.current, moveEvent.clientX, moveEvent.clientY);
|
|
20
30
|
};
|
|
21
31
|
const handleMouseUp = ()=>{
|
|
22
32
|
document.removeEventListener('mousemove', handleMouseMove);
|
|
23
33
|
document.removeEventListener('mouseup', handleMouseUp);
|
|
24
34
|
document.body.style.userSelect = '';
|
|
25
|
-
if (!isDraggingRef.current) return;
|
|
26
|
-
const registry = chipRegistryRef.current;
|
|
27
|
-
if (areAllConditionsDragged(registry)) {
|
|
28
|
-
clearDragAttributes(registry);
|
|
29
|
-
onSelectAll();
|
|
30
|
-
}
|
|
31
35
|
isDraggingRef.current = false;
|
|
32
36
|
};
|
|
33
37
|
document.addEventListener('mousemove', handleMouseMove);
|
|
@@ -35,7 +39,7 @@ const useDragSelection = ({ chipRegistryRef, inputRef, onSelectAll })=>{
|
|
|
35
39
|
}, [
|
|
36
40
|
chipRegistryRef,
|
|
37
41
|
inputRef,
|
|
38
|
-
|
|
42
|
+
closeMenu
|
|
39
43
|
]);
|
|
40
44
|
return {
|
|
41
45
|
handleMouseDown
|
package/dist/components/FilterInput/hooks/useFilterInputSelection/useFilterInputSelection.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ interface UseFilterInputSelectionOptions {
|
|
|
4
4
|
conditions: Condition[];
|
|
5
5
|
connectors: Array<'and' | 'or'>;
|
|
6
6
|
fields: FieldMetadata[];
|
|
7
|
+
containerRef: RefObject<HTMLDivElement | null>;
|
|
7
8
|
chipRegistryRef: RefObject<Map<string, HTMLElement>>;
|
|
8
9
|
inputRef: RefObject<HTMLInputElement | null>;
|
|
9
10
|
clearAll: () => void;
|
|
@@ -11,7 +12,7 @@ interface UseFilterInputSelectionOptions {
|
|
|
11
12
|
closeMenu: () => void;
|
|
12
13
|
onChange?: (expression: ExprNode | null) => void;
|
|
13
14
|
}
|
|
14
|
-
export declare const useFilterInputSelection: ({ conditions, connectors, fields, chipRegistryRef, inputRef, clearAll, setInputText, closeMenu, onChange, }: UseFilterInputSelectionOptions) => {
|
|
15
|
+
export declare const useFilterInputSelection: ({ conditions, connectors, fields, containerRef, chipRegistryRef, inputRef, clearAll, setInputText, closeMenu, onChange, }: UseFilterInputSelectionOptions) => {
|
|
15
16
|
allSelected: boolean;
|
|
16
17
|
pasteError: string | null;
|
|
17
18
|
clearSelection: () => void;
|
package/dist/components/FilterInput/hooks/useFilterInputSelection/useFilterInputSelection.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { useCallback, useState } from "react";
|
|
2
|
-
import { clearDragAttributes } from "./lib/index.js";
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
|
+
import { clearDragAttributes, hasDragSelection } from "./lib/index.js";
|
|
3
3
|
import { useDragSelection } from "./useDragSelection.js";
|
|
4
4
|
import { useSelectionClipboard } from "./useSelectionClipboard.js";
|
|
5
5
|
import { useSelectionKeyboard } from "./useSelectionKeyboard.js";
|
|
6
|
-
const useFilterInputSelection = ({ conditions, connectors, fields, chipRegistryRef, inputRef, clearAll, setInputText, closeMenu, onChange })=>{
|
|
6
|
+
const useFilterInputSelection = ({ conditions, connectors, fields, containerRef, chipRegistryRef, inputRef, clearAll, setInputText, closeMenu, onChange })=>{
|
|
7
7
|
const [allSelected, setAllSelected] = useState(false);
|
|
8
8
|
const [pasteError, setPasteError] = useState(null);
|
|
9
9
|
const onSelectAll = useCallback(()=>setAllSelected(true), []);
|
|
@@ -14,10 +14,25 @@ const useFilterInputSelection = ({ conditions, connectors, fields, chipRegistryR
|
|
|
14
14
|
chipRegistryRef
|
|
15
15
|
]);
|
|
16
16
|
const dismissPasteError = useCallback(()=>setPasteError(null), []);
|
|
17
|
+
const allSelectedRef = useRef(false);
|
|
18
|
+
allSelectedRef.current = allSelected;
|
|
19
|
+
useEffect(()=>{
|
|
20
|
+
const handleClickOutside = (e)=>{
|
|
21
|
+
if (!containerRef.current?.contains(e.target)) {
|
|
22
|
+
if (allSelectedRef.current || hasDragSelection(chipRegistryRef.current)) clearSelection();
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
document.addEventListener('mousedown', handleClickOutside);
|
|
26
|
+
return ()=>document.removeEventListener('mousedown', handleClickOutside);
|
|
27
|
+
}, [
|
|
28
|
+
containerRef,
|
|
29
|
+
chipRegistryRef,
|
|
30
|
+
clearSelection
|
|
31
|
+
]);
|
|
17
32
|
const { handleMouseDown } = useDragSelection({
|
|
18
33
|
chipRegistryRef,
|
|
19
34
|
inputRef,
|
|
20
|
-
|
|
35
|
+
closeMenu
|
|
21
36
|
});
|
|
22
37
|
const { handleKeyDown } = useSelectionKeyboard({
|
|
23
38
|
allSelected,
|
|
@@ -67,6 +67,20 @@ const tokenize = (input)=>{
|
|
|
67
67
|
push('OPERATOR', ch, i);
|
|
68
68
|
continue;
|
|
69
69
|
}
|
|
70
|
+
if ('"' === ch || "'" === ch) {
|
|
71
|
+
const quote = ch;
|
|
72
|
+
const start = i;
|
|
73
|
+
i++;
|
|
74
|
+
while(i < input.length && input[i] !== quote)i++;
|
|
75
|
+
if (i >= input.length) throw FilterParseError(`Unterminated string at position ${start}`);
|
|
76
|
+
tokens.push({
|
|
77
|
+
type: 'IDENT',
|
|
78
|
+
value: input.slice(start + 1, i),
|
|
79
|
+
pos: start
|
|
80
|
+
});
|
|
81
|
+
i++;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
70
84
|
if (isIdentChar(ch)) {
|
|
71
85
|
const start = i;
|
|
72
86
|
while(i < input.length && isIdentChar(input[i]))i++;
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
+
const quoteValue = (v)=>`"${v}"`;
|
|
1
2
|
const serializeValue = (condition)=>{
|
|
2
3
|
if ('is_null' === condition.operator || 'is_not_null' === condition.operator) return '';
|
|
3
|
-
if (Array.isArray(condition.value)) {
|
|
4
|
-
|
|
5
|
-
return `[${parts.join(', ')}]`;
|
|
6
|
-
}
|
|
7
|
-
return String(condition.value ?? '');
|
|
4
|
+
if (Array.isArray(condition.value)) return `[${condition.value.map(quoteValue).join(', ')}]`;
|
|
5
|
+
return quoteValue(condition.value ?? '');
|
|
8
6
|
};
|
|
9
7
|
const serializeCondition = (condition)=>{
|
|
10
8
|
const field = condition.field;
|