@wallarm-org/design-system 0.39.1 → 0.40.0-rc-feature-AS-982.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.
Files changed (38) hide show
  1. package/dist/components/FilterInput/FilterInputContext/types.d.ts +6 -0
  2. package/dist/components/FilterInput/FilterInputContext/useFilterInputContextValue.d.ts +2 -0
  3. package/dist/components/FilterInput/FilterInputContext/useFilterInputContextValue.js +4 -0
  4. package/dist/components/FilterInput/FilterInputField/FilterInputField.js +11 -40
  5. package/dist/components/FilterInput/FilterInputField/hooks/useSegmentEditKeyboard.d.ts +36 -0
  6. package/dist/components/FilterInput/FilterInputField/hooks/useSegmentEditKeyboard.js +77 -0
  7. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/getInitialSegmentText.d.ts +9 -0
  8. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/getInitialSegmentText.js +8 -0
  9. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useChipCascade.d.ts +45 -0
  10. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useChipCascade.js +103 -0
  11. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useChipEditing.d.ts +1 -0
  12. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useChipEditing.js +8 -6
  13. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useFilterInputAutocomplete.d.ts +2 -0
  14. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useFilterInputAutocomplete.js +27 -4
  15. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useInputHandlers.d.ts +7 -1
  16. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useInputHandlers.js +20 -12
  17. package/dist/components/FilterInput/hooks/useFilterInputAutocomplete/useMenuFlow.js +16 -11
  18. package/dist/components/FilterInput/lib/index.d.ts +1 -0
  19. package/dist/components/FilterInput/lib/index.js +2 -1
  20. package/dist/components/FilterInput/lib/segmentMenu.d.ts +9 -0
  21. package/dist/components/FilterInput/lib/segmentMenu.js +7 -0
  22. package/dist/components/SimpleCharts/LineChart/LineChartLine.js +6 -2
  23. package/dist/components/SimpleCharts/LineChart/LineChartTooltip.js +3 -3
  24. package/dist/components/SimpleCharts/LineChart/LineChartXAxis.js +3 -12
  25. package/dist/components/SimpleCharts/LineChart/LineChartYAxis.js +15 -6
  26. package/dist/components/SimpleCharts/LineChart/LineChartZoomBrush.js +8 -19
  27. package/dist/components/SimpleCharts/LineChart/classes.js +1 -1
  28. package/dist/components/SimpleCharts/LineChart/constants.d.ts +1 -1
  29. package/dist/components/SimpleCharts/LineChart/constants.js +2 -2
  30. package/dist/components/SimpleCharts/LineChart/lib/formatRange.d.ts +9 -5
  31. package/dist/components/SimpleCharts/LineChart/lib/formatRange.js +6 -1
  32. package/dist/components/SimpleCharts/LineChart/lib/sampleData.js +3 -3
  33. package/dist/components/SimpleCharts/hooks/useChartTimeFormatters.d.ts +5 -0
  34. package/dist/components/SimpleCharts/hooks/useChartTimeFormatters.js +2 -1
  35. package/dist/metadata/components.json +1 -1
  36. package/package.json +1 -1
  37. package/dist/components/SimpleCharts/LineChart/lib/renderAxisTick.d.ts +0 -13
  38. package/dist/components/SimpleCharts/LineChart/lib/renderAxisTick.js +0 -20
@@ -26,6 +26,12 @@ export interface FilterInputContextValue {
26
26
  /** Click on a segment of the *building* (in-progress) chip — re-opens the
27
27
  * corresponding menu and enters inline-edit without committing the chip. */
28
28
  onBuildingChipClick: (segment: ChipSegment, anchorRect: DOMRect) => void;
29
+ /** Switch the inline-edit to a different segment within the chip currently
30
+ * being edited — used by Backspace-on-empty to walk back through segments. */
31
+ onSwitchEditSegment: (targetSegment: ChipSegment) => boolean;
32
+ /** Remove the chip currently being edited inline — used by Backspace on an
33
+ * empty attribute segment when operator/value are absent. */
34
+ onRemoveEditingChip: () => void;
29
35
  onConnectorChange: (chipId: string, value: 'and' | 'or') => void;
30
36
  onChipRemove: (chipId: string) => void;
31
37
  onClear: () => void;
@@ -11,6 +11,8 @@ interface AutocompleteForContext {
11
11
  handleInputClick: () => void;
12
12
  handleChipClick: (chipId: string, segment: ChipSegment, anchorRect: DOMRect) => void;
13
13
  handleBuildingChipClick: (segment: ChipSegment, anchorRect: DOMRect) => void;
14
+ switchEditSegment: (targetSegment: ChipSegment) => boolean;
15
+ removeEditingChip: () => void;
14
16
  handleConnectorChange: (chipId: string, value: 'and' | 'or') => void;
15
17
  handleChipRemove: (chipId: string) => void;
16
18
  handleClear: () => void;
@@ -17,6 +17,8 @@ const useFilterInputContextValue = ({ chips, autocomplete, buildingChipRef, inpu
17
17
  onInputClick: autocomplete.handleInputClick,
18
18
  onChipClick: autocomplete.handleChipClick,
19
19
  onBuildingChipClick: autocomplete.handleBuildingChipClick,
20
+ onSwitchEditSegment: autocomplete.switchEditSegment,
21
+ onRemoveEditingChip: autocomplete.removeEditingChip,
20
22
  onConnectorChange: autocomplete.handleConnectorChange,
21
23
  onChipRemove: autocomplete.handleChipRemove,
22
24
  onClear: autocomplete.handleClear,
@@ -47,6 +49,8 @@ const useFilterInputContextValue = ({ chips, autocomplete, buildingChipRef, inpu
47
49
  autocomplete.handleInputClick,
48
50
  autocomplete.handleChipClick,
49
51
  autocomplete.handleBuildingChipClick,
52
+ autocomplete.switchEditSegment,
53
+ autocomplete.removeEditingChip,
50
54
  autocomplete.handleConnectorChange,
51
55
  autocomplete.handleChipRemove,
52
56
  autocomplete.handleClear,
@@ -1,21 +1,19 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { useCallback } from "react";
3
2
  import { cn } from "../../../utils/cn.js";
4
3
  import { inputVariants } from "../../Input/classes.js";
5
4
  import { ScrollArea, ScrollAreaScrollbar, ScrollAreaViewport } from "../../ScrollArea/index.js";
6
5
  import { useFilterInputContext } from "../FilterInputContext/index.js";
7
- import { isMenuRelated } from "../lib/index.js";
8
6
  import { ChipsWithGaps, TrailingGap } from "./ChipsWithGaps.js";
9
7
  import { ACTIONS_PADDING, COLLAPSED_MAX_HEIGHT, filterInputContainerVariants, filterInputInnerVariants } from "./classes.js";
10
8
  import { EditingProvider } from "./FilterInputChip/context/EditingContext.js";
11
9
  import { FilterInputChip } from "./FilterInputChip/FilterInputChip.js";
12
- import { SEGMENT_VARIANT } from "./FilterInputChip/segmentVariant.js";
13
10
  import { FilterInputFieldActions } from "./FilterInputFieldActions.js";
14
11
  import { FilterInputSearch } from "./FilterInputSearch.js";
15
12
  import { useChipsSplitting } from "./hooks/useChipsSplitting.js";
16
13
  import { useExpandCollapse } from "./hooks/useExpandCollapse.js";
14
+ import { useSegmentEditKeyboard } from "./hooks/useSegmentEditKeyboard.js";
17
15
  const FilterInputField = ({ className, ...props })=>{
18
- const { chips, buildingChipData, buildingChipRef, insertIndex, insertAfterConnector, error, onInputClick, onGapClick, onChipClick, onBuildingChipClick, onConnectorChange, onChipRemove, editingChipId, editingSegment, segmentFilterText, onSegmentFilterChange, onCancelSegmentEdit, onCustomValueCommit, onCustomAttributeCommit, onCustomOperatorCommit, menuRef } = useFilterInputContext();
16
+ const { chips, buildingChipData, buildingChipRef, insertIndex, insertAfterConnector, error, onInputClick, onGapClick, onChipClick, onBuildingChipClick, onConnectorChange, onChipRemove, editingChipId, editingSegment, segmentFilterText, onSegmentFilterChange, onCancelSegmentEdit, onCustomValueCommit, onCustomAttributeCommit, onCustomOperatorCommit, onSwitchEditSegment, onRemoveEditingChip, menuRef } = useFilterInputContext();
19
17
  const hasContent = chips.length > 0 || null != buildingChipData;
20
18
  const { isExpanded, isOverflowing, innerRef, toggleExpand, multiRow } = useExpandCollapse();
21
19
  const { chipsBefore, chipsAfter, hideTrailingGap, hideLeadingGap } = useChipsSplitting(chips, insertIndex, insertAfterConnector);
@@ -25,47 +23,20 @@ const FilterInputField = ({ className, ...props })=>{
25
23
  onChipRemove,
26
24
  onGapClick
27
25
  };
28
- const handleSegmentEditKeyDown = useCallback((e)=>{
29
- if ('Escape' === e.key) {
30
- e.preventDefault();
31
- onCancelSegmentEdit();
32
- return;
33
- }
34
- if ('Enter' === e.key && !e.defaultPrevented) {
35
- if (editingSegment === SEGMENT_VARIANT.value) {
36
- e.preventDefault();
37
- onCustomValueCommit(segmentFilterText);
38
- return;
39
- }
40
- if (editingSegment === SEGMENT_VARIANT.attribute) {
41
- e.preventDefault();
42
- onCustomAttributeCommit(segmentFilterText);
43
- return;
44
- }
45
- if (editingSegment === SEGMENT_VARIANT.operator) {
46
- e.preventDefault();
47
- onCustomOperatorCommit(segmentFilterText);
48
- return;
49
- }
50
- }
51
- if ('ArrowDown' === e.key) {
52
- e.preventDefault();
53
- menuRef.current?.focus();
54
- }
55
- }, [
56
- onCancelSegmentEdit,
26
+ const { handleSegmentEditKeyDown, handleSegmentEditBlur } = useSegmentEditKeyboard({
27
+ editingChipId,
57
28
  editingSegment,
58
29
  segmentFilterText,
30
+ chips,
31
+ buildingChipData,
32
+ menuRef,
33
+ onCancelSegmentEdit,
59
34
  onCustomValueCommit,
60
35
  onCustomAttributeCommit,
61
36
  onCustomOperatorCommit,
62
- menuRef
63
- ]);
64
- const handleSegmentEditBlur = useCallback((e)=>{
65
- if (!isMenuRelated(e.relatedTarget)) onCancelSegmentEdit();
66
- }, [
67
- onCancelSegmentEdit
68
- ]);
37
+ onSwitchEditSegment,
38
+ onRemoveEditingChip
39
+ });
69
40
  return /*#__PURE__*/ jsx(EditingProvider, {
70
41
  editingChipId: editingChipId,
71
42
  editingSegment: editingSegment,
@@ -0,0 +1,36 @@
1
+ import type { FocusEvent, KeyboardEvent, RefObject } from 'react';
2
+ import type { BuildingChipData } from '../../FilterInputContext/types';
3
+ import type { FilterInputChipData } from '../../types';
4
+ import { type ChipSegment } from '../FilterInputChip';
5
+ interface UseSegmentEditKeyboardOptions {
6
+ editingChipId: string | null;
7
+ editingSegment: ChipSegment | null;
8
+ segmentFilterText: string;
9
+ chips: FilterInputChipData[];
10
+ buildingChipData: BuildingChipData | null;
11
+ menuRef: RefObject<HTMLDivElement | null>;
12
+ onCancelSegmentEdit: () => void;
13
+ onCustomValueCommit: (text: string) => void;
14
+ onCustomAttributeCommit: (text: string) => void;
15
+ onCustomOperatorCommit: (text: string) => void;
16
+ onSwitchEditSegment: (target: ChipSegment) => boolean;
17
+ onRemoveEditingChip: () => void;
18
+ }
19
+ /**
20
+ * Keyboard handlers for a chip segment's inline-edit input:
21
+ *
22
+ * - Escape cancels.
23
+ * - Backspace on an empty input walks the cascade (value → operator →
24
+ * attribute) and, if the attribute is empty with no operator/value left,
25
+ * removes the chip.
26
+ * - Enter commits the typed text via the segment-specific commit callback.
27
+ * - ArrowDown moves focus into the open menu.
28
+ *
29
+ * Returns the keydown + blur handlers fed to `EditingProvider` so every
30
+ * segment input dispatches through the same logic.
31
+ */
32
+ export declare const useSegmentEditKeyboard: ({ editingChipId, editingSegment, segmentFilterText, chips, buildingChipData, menuRef, onCancelSegmentEdit, onCustomValueCommit, onCustomAttributeCommit, onCustomOperatorCommit, onSwitchEditSegment, onRemoveEditingChip, }: UseSegmentEditKeyboardOptions) => {
33
+ handleSegmentEditKeyDown: (e: KeyboardEvent<HTMLInputElement>) => void;
34
+ handleSegmentEditBlur: (e: FocusEvent<HTMLInputElement>) => void;
35
+ };
36
+ export {};
@@ -0,0 +1,77 @@
1
+ import { useCallback } from "react";
2
+ import { isMenuRelated } from "../../lib/index.js";
3
+ import { SEGMENT_VARIANT } from "../FilterInputChip/index.js";
4
+ const useSegmentEditKeyboard = ({ editingChipId, editingSegment, segmentFilterText, chips, buildingChipData, menuRef, onCancelSegmentEdit, onCustomValueCommit, onCustomAttributeCommit, onCustomOperatorCommit, onSwitchEditSegment, onRemoveEditingChip })=>{
5
+ const handleSegmentEditKeyDown = useCallback((e)=>{
6
+ if ('Escape' === e.key) {
7
+ e.preventDefault();
8
+ onCancelSegmentEdit();
9
+ return;
10
+ }
11
+ if ('Backspace' === e.key && '' === segmentFilterText) {
12
+ if (editingSegment === SEGMENT_VARIANT.attribute) {
13
+ const chipForEdit = editingChipId ? chips.find((c)=>c.id === editingChipId && 'chip' === c.variant) : null;
14
+ const operator = chipForEdit?.operator ?? buildingChipData?.operator ?? '';
15
+ if (!operator) {
16
+ e.preventDefault();
17
+ onRemoveEditingChip();
18
+ }
19
+ return;
20
+ }
21
+ if (editingSegment === SEGMENT_VARIANT.operator) {
22
+ e.preventDefault();
23
+ onSwitchEditSegment(SEGMENT_VARIANT.attribute);
24
+ return;
25
+ }
26
+ if (editingSegment === SEGMENT_VARIANT.value) {
27
+ e.preventDefault();
28
+ onSwitchEditSegment(SEGMENT_VARIANT.operator);
29
+ return;
30
+ }
31
+ }
32
+ if ('Enter' === e.key && !e.defaultPrevented) {
33
+ if (editingSegment === SEGMENT_VARIANT.value) {
34
+ e.preventDefault();
35
+ onCustomValueCommit(segmentFilterText);
36
+ return;
37
+ }
38
+ if (editingSegment === SEGMENT_VARIANT.attribute) {
39
+ e.preventDefault();
40
+ onCustomAttributeCommit(segmentFilterText);
41
+ return;
42
+ }
43
+ if (editingSegment === SEGMENT_VARIANT.operator) {
44
+ e.preventDefault();
45
+ onCustomOperatorCommit(segmentFilterText);
46
+ return;
47
+ }
48
+ }
49
+ if ('ArrowDown' === e.key) {
50
+ e.preventDefault();
51
+ menuRef.current?.focus();
52
+ }
53
+ }, [
54
+ onCancelSegmentEdit,
55
+ editingSegment,
56
+ segmentFilterText,
57
+ onCustomValueCommit,
58
+ onCustomAttributeCommit,
59
+ onCustomOperatorCommit,
60
+ onSwitchEditSegment,
61
+ onRemoveEditingChip,
62
+ editingChipId,
63
+ chips,
64
+ buildingChipData,
65
+ menuRef
66
+ ]);
67
+ const handleSegmentEditBlur = useCallback((e)=>{
68
+ if (!isMenuRelated(e.relatedTarget)) onCancelSegmentEdit();
69
+ }, [
70
+ onCancelSegmentEdit
71
+ ]);
72
+ return {
73
+ handleSegmentEditKeyDown,
74
+ handleSegmentEditBlur
75
+ };
76
+ };
77
+ export { useSegmentEditKeyboard };
@@ -0,0 +1,9 @@
1
+ import { type ChipSegment } from '../../FilterInputField/FilterInputChip';
2
+ import type { FieldMetadata, FilterOperator } from '../../types';
3
+ /**
4
+ * Initial text shown in a building chip's inline-edit input for the given
5
+ * segment. Mirrors what the chip is currently displaying so the user can
6
+ * backspace through it (segment-click reopen + Backspace cascade share this
7
+ * derivation, hence the standalone helper).
8
+ */
9
+ export declare const getInitialSegmentText: (segment: ChipSegment, selectedField: FieldMetadata, selectedOperator: FilterOperator | null, buildingMultiValue: string | undefined) => string;
@@ -0,0 +1,8 @@
1
+ import { SEGMENT_VARIANT } from "../../FilterInputField/FilterInputChip/index.js";
2
+ import { getOperatorLabel } from "../../lib/index.js";
3
+ const getInitialSegmentText = (segment, selectedField, selectedOperator, buildingMultiValue)=>{
4
+ if (segment === SEGMENT_VARIANT.attribute) return selectedField.label;
5
+ if (segment === SEGMENT_VARIANT.operator) return selectedOperator ? getOperatorLabel(selectedOperator, selectedField.type) : '';
6
+ return buildingMultiValue ?? '';
7
+ };
8
+ export { getInitialSegmentText };
@@ -0,0 +1,45 @@
1
+ import type { MutableRefObject, RefObject } from 'react';
2
+ import { type ChipSegment } from '../../FilterInputField/FilterInputChip';
3
+ import type { Condition, FieldMetadata, FilterInputChipData, FilterOperator, MenuState, UpsertCondition } from '../../types';
4
+ interface UseChipCascadeOptions {
5
+ editing: {
6
+ editingChipId: string | null;
7
+ editingSegment: ChipSegment | null;
8
+ startBuildingEdit: (segment: ChipSegment, currentText: string) => void;
9
+ switchEditSegment: (segment: ChipSegment, currentText: string) => void;
10
+ };
11
+ chips: FilterInputChipData[];
12
+ fields: FieldMetadata[];
13
+ conditionsRef: MutableRefObject<Condition[]>;
14
+ effectiveInsertIndexRef: RefObject<number>;
15
+ selectedField: FieldMetadata | null;
16
+ selectedOperator: FilterOperator | null;
17
+ buildingMultiValue: string | undefined;
18
+ upsertCondition: UpsertCondition;
19
+ removeCondition: (chipId: string) => void;
20
+ resetState: (continueBuilding?: boolean) => void;
21
+ setInputText: (text: string) => void;
22
+ setMenuState: (state: MenuState) => void;
23
+ setSelectedOperator: (op: FilterOperator | null) => void;
24
+ setBuildingMultiValue: (val: string | undefined) => void;
25
+ setInsertIndex: (fn: (prev: number | null) => number | null) => void;
26
+ }
27
+ /**
28
+ * Backspace-driven cascade for chip segments. Bundles three closely related
29
+ * actions so consumers don't have to wire them piecemeal:
30
+ *
31
+ * - `switchEditSegment` — walks inline-edit one segment to the left (value
32
+ * → operator → attribute). The source segment's chip data is cleared so
33
+ * the just-emptied text does not re-render after the switch.
34
+ * - `removeEditingChip` — drops the chip currently being edited (building
35
+ * reset or committed `removeCondition` + insertIndex bookkeeping).
36
+ * - `stepBackBuildingMenu` — from the main input during building, enters
37
+ * inline-edit on the previous segment with its text pre-selected, mirroring
38
+ * the segment-click + cascade UX.
39
+ */
40
+ export declare const useChipCascade: ({ editing, chips, fields, conditionsRef, effectiveInsertIndexRef, selectedField, selectedOperator, buildingMultiValue, upsertCondition, removeCondition, resetState, setInputText, setMenuState, setSelectedOperator, setBuildingMultiValue, setInsertIndex, }: UseChipCascadeOptions) => {
41
+ switchEditSegment: (targetSegment: ChipSegment) => boolean;
42
+ removeEditingChip: () => void;
43
+ stepBackBuildingMenu: (current: "field" | "operator" | "value") => void;
44
+ };
45
+ export {};
@@ -0,0 +1,103 @@
1
+ import { useCallback } from "react";
2
+ import { SEGMENT_VARIANT } from "../../FilterInputField/FilterInputChip/index.js";
3
+ import { SEGMENT_TO_MENU, chipIdToConditionIndex } from "../../lib/index.js";
4
+ import { getInitialSegmentText } from "./getInitialSegmentText.js";
5
+ const useChipCascade = ({ editing, chips, fields, conditionsRef, effectiveInsertIndexRef, selectedField, selectedOperator, buildingMultiValue, upsertCondition, removeCondition, resetState, setInputText, setMenuState, setSelectedOperator, setBuildingMultiValue, setInsertIndex })=>{
6
+ const switchEditSegment = useCallback((targetSegment)=>{
7
+ const sourceSegment = editing.editingSegment;
8
+ if (null === sourceSegment) return false;
9
+ const isBuildingEdit = !editing.editingChipId;
10
+ if (isBuildingEdit) {
11
+ if (!selectedField) return false;
12
+ if (sourceSegment === SEGMENT_VARIANT.value) setBuildingMultiValue(void 0);
13
+ else if (sourceSegment === SEGMENT_VARIANT.operator) {
14
+ setSelectedOperator(null);
15
+ setBuildingMultiValue(void 0);
16
+ }
17
+ const initialText = getInitialSegmentText(targetSegment, selectedField, selectedOperator, buildingMultiValue);
18
+ editing.startBuildingEdit(targetSegment, initialText);
19
+ setInputText('');
20
+ setMenuState(SEGMENT_TO_MENU[targetSegment]);
21
+ return true;
22
+ }
23
+ const editingId = editing.editingChipId;
24
+ if (!editingId) return false;
25
+ const chip = chips.find((c)=>c.id === editingId);
26
+ if (!chip || 'chip' !== chip.variant) return false;
27
+ const idx = chipIdToConditionIndex(editingId);
28
+ const condition = null !== idx ? conditionsRef.current[idx] : null;
29
+ const field = condition ? fields.find((f)=>f.name === condition.field) : null;
30
+ if (condition && field) {
31
+ if (sourceSegment === SEGMENT_VARIANT.value) upsertCondition(field, condition.operator, null, editingId, void 0, void 0, condition.dateOrigin);
32
+ else if (sourceSegment === SEGMENT_VARIANT.operator) upsertCondition(field, void 0, condition.value, editingId, void 0, void 0, condition.dateOrigin);
33
+ }
34
+ const targetText = targetSegment === SEGMENT_VARIANT.attribute ? chip.attribute ?? '' : targetSegment === SEGMENT_VARIANT.operator ? chip.operator ?? '' : '';
35
+ editing.switchEditSegment(targetSegment, targetText);
36
+ setMenuState(SEGMENT_TO_MENU[targetSegment]);
37
+ return true;
38
+ }, [
39
+ editing,
40
+ selectedField,
41
+ selectedOperator,
42
+ buildingMultiValue,
43
+ chips,
44
+ fields,
45
+ conditionsRef,
46
+ upsertCondition,
47
+ setBuildingMultiValue,
48
+ setSelectedOperator,
49
+ setInputText,
50
+ setMenuState
51
+ ]);
52
+ const removeEditingChip = useCallback(()=>{
53
+ if (null === editing.editingSegment) return;
54
+ const editingId = editing.editingChipId;
55
+ if (!editingId) return void resetState();
56
+ const chipCondIdx = chipIdToConditionIndex(editingId);
57
+ if (null !== chipCondIdx && chipCondIdx < effectiveInsertIndexRef.current) setInsertIndex((prev)=>null != prev ? prev - 1 : prev);
58
+ removeCondition(editingId);
59
+ resetState();
60
+ }, [
61
+ editing,
62
+ removeCondition,
63
+ resetState,
64
+ effectiveInsertIndexRef,
65
+ setInsertIndex
66
+ ]);
67
+ const stepBackBuildingMenu = useCallback((current)=>{
68
+ if (!selectedField) return void resetState();
69
+ if ('value' === current) {
70
+ setBuildingMultiValue(void 0);
71
+ setInputText('');
72
+ const operatorText = getInitialSegmentText(SEGMENT_VARIANT.operator, selectedField, selectedOperator, buildingMultiValue);
73
+ editing.startBuildingEdit(SEGMENT_VARIANT.operator, operatorText);
74
+ setMenuState('operator');
75
+ return;
76
+ }
77
+ if ('operator' === current) {
78
+ setSelectedOperator(null);
79
+ setBuildingMultiValue(void 0);
80
+ setInputText('');
81
+ editing.startBuildingEdit(SEGMENT_VARIANT.attribute, selectedField.label);
82
+ setMenuState('field');
83
+ return;
84
+ }
85
+ resetState();
86
+ }, [
87
+ selectedField,
88
+ selectedOperator,
89
+ buildingMultiValue,
90
+ editing,
91
+ resetState,
92
+ setBuildingMultiValue,
93
+ setInputText,
94
+ setMenuState,
95
+ setSelectedOperator
96
+ ]);
97
+ return {
98
+ switchEditSegment,
99
+ removeEditingChip,
100
+ stepBackBuildingMenu
101
+ };
102
+ };
103
+ export { useChipCascade };
@@ -27,6 +27,7 @@ export declare const useChipEditing: ({ conditions, chips, fields, containerRef,
27
27
  resetSegmentTyping: () => void;
28
28
  handleChipClick: (chipId: string, segment: ChipSegment, anchorRect: DOMRect) => void;
29
29
  startBuildingEdit: (segment: ChipSegment, currentText: string) => void;
30
+ switchEditSegment: (segment: ChipSegment, currentText: string) => void;
30
31
  clearEditing: () => void;
31
32
  };
32
33
  export {};
@@ -1,15 +1,10 @@
1
1
  import { useCallback, useMemo, useRef, useState } from "react";
2
2
  import { SEGMENT_VARIANT } from "../../FilterInputField/FilterInputChip/index.js";
3
- import { chipIdToConditionIndex, getOperatorFromLabel, isNoValueOperator } from "../../lib/index.js";
3
+ import { SEGMENT_TO_MENU, chipIdToConditionIndex, getOperatorFromLabel, isNoValueOperator } from "../../lib/index.js";
4
4
  const getConditionByChipId = (chipId, conditions)=>{
5
5
  const idx = chipIdToConditionIndex(chipId);
6
6
  return null !== idx ? conditions[idx] ?? null : null;
7
7
  };
8
- const SEGMENT_TO_MENU = {
9
- attribute: 'field',
10
- operator: 'operator',
11
- value: 'value'
12
- };
13
8
  const getFirstIncompleteSegment = (condition, fields)=>{
14
9
  const field = fields.find((f)=>f.name === condition.field);
15
10
  if (!field || condition.error === SEGMENT_VARIANT.attribute) return SEGMENT_VARIANT.attribute;
@@ -74,6 +69,11 @@ const useChipEditing = ({ conditions, chips, fields, containerRef, setMenuOffset
74
69
  setSegmentFilterText(currentText);
75
70
  setUserHasTyped(false);
76
71
  }, []);
72
+ const switchEditSegment = useCallback((segment, currentText)=>{
73
+ setEditingSegment(segment);
74
+ setSegmentFilterText(currentText);
75
+ setUserHasTyped(false);
76
+ }, []);
77
77
  const handleSegmentFilterChange = useCallback((text)=>{
78
78
  setSegmentFilterText(text);
79
79
  setUserHasTyped(true);
@@ -93,6 +93,7 @@ const useChipEditing = ({ conditions, chips, fields, containerRef, setMenuOffset
93
93
  resetSegmentTyping,
94
94
  handleChipClick,
95
95
  startBuildingEdit,
96
+ switchEditSegment,
96
97
  clearEditing
97
98
  }), [
98
99
  editingChipId,
@@ -103,6 +104,7 @@ const useChipEditing = ({ conditions, chips, fields, containerRef, setMenuOffset
103
104
  resetSegmentTyping,
104
105
  handleChipClick,
105
106
  startBuildingEdit,
107
+ switchEditSegment,
106
108
  clearEditing
107
109
  ]);
108
110
  };
@@ -54,6 +54,8 @@ export declare const useFilterInputAutocomplete: ({ fields, conditions, chips, u
54
54
  resetAutocompleteState: (continueBuilding?: boolean) => void;
55
55
  handleChipClick: (chipId: string, segment: ChipSegment, anchorRect: DOMRect) => void;
56
56
  handleBuildingChipClick: (segment: ChipSegment, anchorRect: DOMRect) => void;
57
+ switchEditSegment: (targetSegment: ChipSegment) => boolean;
58
+ removeEditingChip: () => void;
57
59
  handleConnectorChange: (connectorId: string, value: "and" | "or") => void;
58
60
  handleChipRemove: (chipId: string) => void;
59
61
  handleClear: () => void;
@@ -1,10 +1,12 @@
1
1
  import { useCallback, useRef, useState } from "react";
2
2
  import { SEGMENT_VARIANT } from "../../FilterInputField/FilterInputChip/index.js";
3
3
  import { useDateRange } from "../../FilterInputMenu/FilterInputDateValueMenu/hooks.js";
4
- import { applyAcceptChar, getOperatorLabel } from "../../lib/index.js";
4
+ import { SEGMENT_TO_MENU, applyAcceptChar } from "../../lib/index.js";
5
5
  import { deriveAutocompleteValues } from "./deriveAutocompleteValues.js";
6
+ import { getInitialSegmentText } from "./getInitialSegmentText.js";
6
7
  import { useBlurCommit } from "./useBlurCommit.js";
7
8
  import { useChipActions } from "./useChipActions.js";
9
+ import { useChipCascade } from "./useChipCascade.js";
8
10
  import { useChipEditing } from "./useChipEditing.js";
9
11
  import { useFocusManagement } from "./useFocusManagement.js";
10
12
  import { useInputHandlers } from "./useInputHandlers.js";
@@ -83,6 +85,24 @@ const useFilterInputAutocomplete = ({ fields, conditions, chips, upsertCondition
83
85
  setMenuState,
84
86
  setBuildingMultiValue
85
87
  });
88
+ const { switchEditSegment, removeEditingChip, stepBackBuildingMenu } = useChipCascade({
89
+ editing,
90
+ chips,
91
+ fields,
92
+ conditionsRef,
93
+ effectiveInsertIndexRef,
94
+ selectedField,
95
+ selectedOperator,
96
+ buildingMultiValue,
97
+ upsertCondition,
98
+ removeCondition,
99
+ resetState,
100
+ setInputText,
101
+ setMenuState,
102
+ setSelectedOperator,
103
+ setBuildingMultiValue,
104
+ setInsertIndex
105
+ });
86
106
  const { handleInputChange, handleInputClick, handleKeyDown, menuRef } = useInputHandlers({
87
107
  inputText,
88
108
  menuState,
@@ -101,7 +121,8 @@ const useFilterInputAutocomplete = ({ fields, conditions, chips, upsertCondition
101
121
  removeConditionAtIndex,
102
122
  handleFieldSelect,
103
123
  handleOperatorSelect,
104
- handleCustomValueCommit
124
+ handleCustomValueCommit,
125
+ stepBackBuildingMenu
105
126
  });
106
127
  const { commitBuildingOnBlur, hasIncompleteBuilding } = useBlurCommit({
107
128
  selectedField,
@@ -193,10 +214,10 @@ const useFilterInputAutocomplete = ({ fields, conditions, chips, upsertCondition
193
214
  if (!selectedField) return;
194
215
  const containerRect = containerRef.current?.getBoundingClientRect();
195
216
  setMenuOffset(containerRect ? anchorRect.left - containerRect.left : 0);
196
- const initialText = segment === SEGMENT_VARIANT.attribute ? selectedField.label : segment === SEGMENT_VARIANT.operator ? selectedOperator ? getOperatorLabel(selectedOperator, selectedField.type) : '' : buildingMultiValue ?? '';
217
+ const initialText = getInitialSegmentText(segment, selectedField, selectedOperator, buildingMultiValue);
197
218
  editing.startBuildingEdit(segment, initialText);
198
219
  setInputText('');
199
- setMenuState(segment === SEGMENT_VARIANT.attribute ? 'field' : segment === SEGMENT_VARIANT.operator ? 'operator' : 'value');
220
+ setMenuState(SEGMENT_TO_MENU[segment]);
200
221
  }, [
201
222
  selectedField,
202
223
  selectedOperator,
@@ -229,6 +250,8 @@ const useFilterInputAutocomplete = ({ fields, conditions, chips, upsertCondition
229
250
  resetAutocompleteState: resetState,
230
251
  handleChipClick: editing.handleChipClick,
231
252
  handleBuildingChipClick,
253
+ switchEditSegment,
254
+ removeEditingChip,
232
255
  handleConnectorChange: setConnectorValue,
233
256
  handleChipRemove,
234
257
  handleClear,
@@ -1,5 +1,6 @@
1
1
  import type { ChangeEvent, KeyboardEvent, MutableRefObject, RefObject } from 'react';
2
2
  import type { Condition, FieldMetadata, FilterOperator, MenuState } from '../../types';
3
+ type BuildingStep = 'field' | 'operator' | 'value';
3
4
  interface UseInputHandlersDeps {
4
5
  inputText: string;
5
6
  menuState: MenuState;
@@ -21,8 +22,13 @@ interface UseInputHandlersDeps {
21
22
  handleFieldSelect: (field: FieldMetadata) => void;
22
23
  handleOperatorSelect: (operator: FilterOperator) => void;
23
24
  handleCustomValueCommit: (text: string) => void;
25
+ /** Step the building chip one segment back (value → operator → field).
26
+ * Called on Backspace from an empty main input while a building chip is
27
+ * alive. At the field step (only attribute remains) it tears the chip
28
+ * down entirely. */
29
+ stepBackBuildingMenu: (current: BuildingStep) => void;
24
30
  }
25
- export declare const useInputHandlers: ({ inputText, menuState, selectedField, selectedOperator, isFocused, fields, inputRef, conditionsRef, conditionsLengthRef, effectiveInsertIndexRef, setInputText, setMenuState, setInsertIndex, resetMenuOffset, removeConditionAtIndex, handleFieldSelect, handleOperatorSelect, handleCustomValueCommit, }: UseInputHandlersDeps) => {
31
+ export declare const useInputHandlers: ({ inputText, menuState, selectedField, selectedOperator, isFocused, fields, inputRef, conditionsRef, conditionsLengthRef, effectiveInsertIndexRef, setInputText, setMenuState, setInsertIndex, resetMenuOffset, removeConditionAtIndex, handleFieldSelect, handleOperatorSelect, handleCustomValueCommit, stepBackBuildingMenu, }: UseInputHandlersDeps) => {
26
32
  handleInputChange: (e: ChangeEvent<HTMLInputElement>) => void;
27
33
  handleInputClick: () => void;
28
34
  handleKeyDown: (e: KeyboardEvent<HTMLInputElement>) => void;
@@ -1,6 +1,6 @@
1
1
  import { useCallback, useRef } from "react";
2
2
  import { OPERATOR_SYMBOLS, applyAcceptChar, getOperatorFromLabel, hasFieldValues, nextBuildingMenu } from "../../lib/index.js";
3
- const useInputHandlers = ({ inputText, menuState, selectedField, selectedOperator, isFocused, fields, inputRef, conditionsRef, conditionsLengthRef, effectiveInsertIndexRef, setInputText, setMenuState, setInsertIndex, resetMenuOffset, removeConditionAtIndex, handleFieldSelect, handleOperatorSelect, handleCustomValueCommit })=>{
3
+ const useInputHandlers = ({ inputText, menuState, selectedField, selectedOperator, isFocused, fields, inputRef, conditionsRef, conditionsLengthRef, effectiveInsertIndexRef, setInputText, setMenuState, setInsertIndex, resetMenuOffset, removeConditionAtIndex, handleFieldSelect, handleOperatorSelect, handleCustomValueCommit, stepBackBuildingMenu })=>{
4
4
  const menuRef = useRef(null);
5
5
  const handleInputChange = useCallback((e)=>{
6
6
  let text = e.target.value;
@@ -72,17 +72,24 @@ const useInputHandlers = ({ inputText, menuState, selectedField, selectedOperato
72
72
  handleCustomValueCommit(inputText);
73
73
  return;
74
74
  }
75
- if ('Backspace' === e.key && !e.repeat && '' === inputText && conditionsLengthRef.current > 0) {
76
- e.preventDefault();
77
- const removeIdx = effectiveInsertIndexRef.current - 1;
78
- if (removeIdx >= 0 && !conditionsRef.current[removeIdx]?.disabled) {
79
- removeConditionAtIndex(removeIdx);
80
- setInsertIndex((prev)=>{
81
- const eff = prev ?? conditionsLengthRef.current;
82
- return eff > 0 ? eff - 1 : 0;
83
- });
75
+ if ('Backspace' === e.key && !e.repeat && '' === inputText) {
76
+ if (selectedField && ('value' === menuState || 'operator' === menuState || 'field' === menuState)) {
77
+ e.preventDefault();
78
+ stepBackBuildingMenu(menuState);
79
+ return;
80
+ }
81
+ if (conditionsLengthRef.current > 0) {
82
+ e.preventDefault();
83
+ const removeIdx = effectiveInsertIndexRef.current - 1;
84
+ if (removeIdx >= 0 && !conditionsRef.current[removeIdx]?.disabled) {
85
+ removeConditionAtIndex(removeIdx);
86
+ setInsertIndex((prev)=>{
87
+ const eff = prev ?? conditionsLengthRef.current;
88
+ return eff > 0 ? eff - 1 : 0;
89
+ });
90
+ }
91
+ setMenuState('closed');
84
92
  }
85
- setMenuState('closed');
86
93
  }
87
94
  }, [
88
95
  inputText,
@@ -98,7 +105,8 @@ const useInputHandlers = ({ inputText, menuState, selectedField, selectedOperato
98
105
  setInsertIndex,
99
106
  conditionsRef,
100
107
  conditionsLengthRef,
101
- effectiveInsertIndexRef
108
+ effectiveInsertIndexRef,
109
+ stepBackBuildingMenu
102
110
  ]);
103
111
  return {
104
112
  handleInputChange,
@@ -25,6 +25,15 @@ const useMenuFlow = ({ editing, selectedField, selectedOperator, fields, inputRe
25
25
  const idx = chipIdToConditionIndex(editing.editingChipId);
26
26
  const condition = null !== idx ? conditionsRef.current[idx] : null;
27
27
  if (condition) {
28
+ if (!condition.operator) {
29
+ upsertCondition(field, void 0, condition.value, editing.editingChipId, void 0, void 0, condition.dateOrigin);
30
+ setSelectedField(field);
31
+ setSelectedOperator(null);
32
+ editing.setEditingSegment(SEGMENT_VARIANT.operator);
33
+ editing.setSegmentFilterText('');
34
+ setMenuState('operator');
35
+ return;
36
+ }
28
37
  const hasValueError = validateValueForField(field, condition.value);
29
38
  upsertCondition(field, condition.operator, condition.value, editing.editingChipId, void 0, hasValueError ? SEGMENT_VARIANT.value : void 0, condition.dateOrigin);
30
39
  }
@@ -205,17 +214,13 @@ const useMenuFlow = ({ editing, selectedField, selectedOperator, fields, inputRe
205
214
  const idx = chipIdToConditionIndex(editing.editingChipId);
206
215
  const condition = null !== idx ? conditionsRef.current[idx] : null;
207
216
  if (!condition) return;
208
- if (matchedField) {
209
- const hasValueError = validateValueForField(matchedField, condition.value);
210
- upsertCondition(matchedField, condition.operator, condition.value, editing.editingChipId, void 0, hasValueError ? SEGMENT_VARIANT.value : void 0, condition.dateOrigin);
211
- } else {
212
- const syntheticField = {
213
- name: trimmed,
214
- label: trimmed,
215
- type: 'string'
216
- };
217
- upsertCondition(syntheticField, condition.operator, condition.value, editing.editingChipId, void 0, SEGMENT_VARIANT.attribute, condition.dateOrigin);
218
- }
217
+ if (matchedField) return void handleFieldSelect(matchedField);
218
+ const syntheticField = {
219
+ name: trimmed,
220
+ label: trimmed,
221
+ type: 'string'
222
+ };
223
+ upsertCondition(syntheticField, condition.operator, condition.value, editing.editingChipId, void 0, SEGMENT_VARIANT.attribute, condition.dateOrigin);
219
224
  resetState();
220
225
  }, [
221
226
  editing,
@@ -11,5 +11,6 @@ export { filterAndSort } from './filterSort';
11
11
  export { getCurrentValueTokenText, getValueFilterText } from './menuFilterText';
12
12
  export { getFieldOperators, getOperatorFromLabel, getOperatorLabel, isBetweenOperator, isBuildingComplete, isMultiSelectOperator, isNoValueOperator, isOperatorAllowedForField, isValueShapeCompatible, NO_VALUE_PLACEHOLDER, nextBuildingMenu, } from './operators';
13
13
  export { type FilterParseError, isFilterParseError, parseExpression } from './parseExpression';
14
+ export { SEGMENT_TO_MENU } from './segmentMenu';
14
15
  export { serializeExpression } from './serializeExpression';
15
16
  export { createStatusCodeInputFilter, createStatusCodeNormalizer, createStatusCodeSerializer, createStatusCodeSuggestions, createStatusCodeValidator, } from './statusCode';
@@ -10,6 +10,7 @@ import { filterAndSort } from "./filterSort.js";
10
10
  import { getCurrentValueTokenText, getValueFilterText } from "./menuFilterText.js";
11
11
  import { NO_VALUE_PLACEHOLDER, getFieldOperators, getOperatorFromLabel, getOperatorLabel, isBetweenOperator, isBuildingComplete, isMultiSelectOperator, isNoValueOperator, isOperatorAllowedForField, isValueShapeCompatible, nextBuildingMenu } from "./operators.js";
12
12
  import { isFilterParseError, parseExpression } from "./parseExpression/index.js";
13
+ import { SEGMENT_TO_MENU } from "./segmentMenu.js";
13
14
  import { serializeExpression } from "./serializeExpression.js";
14
15
  import { createStatusCodeInputFilter, createStatusCodeNormalizer, createStatusCodeSerializer, createStatusCodeSuggestions, createStatusCodeValidator } from "./statusCode/index.js";
15
- export { CONNECTOR_ID_PATTERN, DATE_PRESETS, NO_VALUE_OPERATORS, NO_VALUE_PLACEHOLDER, OPERATORS_BY_TYPE, OPERATOR_LABELS, OPERATOR_LABELS_BY_TYPE, OPERATOR_SYMBOLS, QUERY_BAR_SELECTOR, VARIANT_LABELS, applyAcceptChar, applyFieldValueTransforms, applyKnownFieldHelpers, buildContainerAnchoredRect, chipIdToConditionIndex, createStatusCodeInputFilter, createStatusCodeNormalizer, createStatusCodeSerializer, createStatusCodeSuggestions, createStatusCodeValidator, filterAndSort, findChipSplitIndex, findOptionByValue, formatDateForChip, getCurrentValueTokenText, getDateDisplayLabel, getFieldOperators, getFieldValues, getKnownFieldSerializer, getOperatorFromLabel, getOperatorLabel, getValueFilterText, hasFieldValues, hasStaticAllowlist, isBetweenOperator, isBuildingComplete, isDatePreset, isFilterParseError, isMenuRelated, isMultiSelectOperator, isNoValueOperator, isOperatorAllowedForField, isValueShapeCompatible, nextBuildingMenu, parseExpression, serializeExpression };
16
+ export { CONNECTOR_ID_PATTERN, DATE_PRESETS, NO_VALUE_OPERATORS, NO_VALUE_PLACEHOLDER, OPERATORS_BY_TYPE, OPERATOR_LABELS, OPERATOR_LABELS_BY_TYPE, OPERATOR_SYMBOLS, QUERY_BAR_SELECTOR, SEGMENT_TO_MENU, VARIANT_LABELS, applyAcceptChar, applyFieldValueTransforms, applyKnownFieldHelpers, buildContainerAnchoredRect, chipIdToConditionIndex, createStatusCodeInputFilter, createStatusCodeNormalizer, createStatusCodeSerializer, createStatusCodeSuggestions, createStatusCodeValidator, filterAndSort, findChipSplitIndex, findOptionByValue, formatDateForChip, getCurrentValueTokenText, getDateDisplayLabel, getFieldOperators, getFieldValues, getKnownFieldSerializer, getOperatorFromLabel, getOperatorLabel, getValueFilterText, hasFieldValues, hasStaticAllowlist, isBetweenOperator, isBuildingComplete, isDatePreset, isFilterParseError, isMenuRelated, isMultiSelectOperator, isNoValueOperator, isOperatorAllowedForField, isValueShapeCompatible, nextBuildingMenu, parseExpression, serializeExpression };
@@ -0,0 +1,9 @@
1
+ import { type SegmentVariant } from '../FilterInputField/FilterInputChip/segmentVariant';
2
+ import type { MenuState } from '../types';
3
+ /**
4
+ * Open-menu state that corresponds to each chip segment. Used wherever an
5
+ * inline-edit transition needs to (re)open the matching menu — segment
6
+ * click, Backspace cascade, and the building-flow step-back from the main
7
+ * input. Kept here so the mapping is defined once.
8
+ */
9
+ export declare const SEGMENT_TO_MENU: Record<SegmentVariant, MenuState>;
@@ -0,0 +1,7 @@
1
+ import { SEGMENT_VARIANT } from "../FilterInputField/FilterInputChip/segmentVariant.js";
2
+ const SEGMENT_TO_MENU = {
3
+ [SEGMENT_VARIANT.attribute]: 'field',
4
+ [SEGMENT_VARIANT.operator]: 'operator',
5
+ [SEGMENT_VARIANT.value]: 'value'
6
+ };
7
+ export { SEGMENT_TO_MENU };
@@ -1,7 +1,7 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { useContext, useEffect } from "react";
3
3
  import { Line } from "recharts";
4
- import { LINE_ANIMATION_BEGIN, LINE_ANIMATION_DURATION, LINE_DASH_DASHARRAY, LINE_INACTIVE_OPACITY, LINE_STROKE_FILL, LINE_STROKE_WIDTH, resolveSeriesColor } from "./constants.js";
4
+ import { LINE_ACTIVE_DOT_RADIUS, LINE_ANIMATION_BEGIN, LINE_ANIMATION_DURATION, LINE_DASH_DASHARRAY, LINE_INACTIVE_OPACITY, LINE_STROKE_FILL, LINE_STROKE_WIDTH, resolveSeriesColor } from "./constants.js";
5
5
  import { LineChartActiveContext, LineChartDataContext } from "./LineChartContext.js";
6
6
  import { warnLineChartLine } from "./lib/warn.js";
7
7
  const wrap = (cb)=>cb ? (_props, event)=>cb(event) : void 0;
@@ -34,7 +34,11 @@ const LineChartLine = ({ seriesKey, curve = 'monotone', disableAnimation = false
34
34
  strokeLinecap: "round",
35
35
  strokeLinejoin: "round",
36
36
  dot: false,
37
- activeDot: false,
37
+ activeDot: {
38
+ r: LINE_ACTIVE_DOT_RADIUS,
39
+ stroke,
40
+ strokeWidth: 0
41
+ },
38
42
  opacity: opacity,
39
43
  connectNulls: connectNulls,
40
44
  isAnimationActive: disableAnimation ? false : 'auto',
@@ -2,15 +2,15 @@ import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useContext, useMemo } from "react";
3
3
  import { Tooltip, usePlotArea } from "recharts";
4
4
  import { lineChartTooltipCenterClasses } from "./classes.js";
5
- import { HOVER_POPOVER_TOP, LINE_CURSOR_DASHARRAY } from "./constants.js";
5
+ import { HOVER_POPOVER_TOP } from "./constants.js";
6
6
  import { LineChartDataContext, LineChartSelectionContext, LineChartZoomContext } from "./LineChartContext.js";
7
7
  import { LineChartHoverPopover } from "./LineChartHoverPopover.js";
8
8
  import { LineChartHoverPopoverRow } from "./LineChartHoverPopoverRow.js";
9
9
  import { LineChartHoverPopoverTimestamp } from "./LineChartHoverPopoverTimestamp.js";
10
10
  const TOOLTIP_CURSOR = {
11
- stroke: 'var(--color-border-strong-primary)',
11
+ stroke: 'var(--color-border-primary-light)',
12
12
  strokeWidth: 1,
13
- strokeDasharray: LINE_CURSOR_DASHARRAY
13
+ strokeDasharray: '4 4'
14
14
  };
15
15
  const TOOLTIP_ALLOW_ESCAPE = {
16
16
  x: false,
@@ -1,8 +1,8 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { useContext } from "react";
3
3
  import { XAxis } from "recharts";
4
+ import { LINE_AXIS_TICK_TEXT_PROPS } from "./constants.js";
4
5
  import { LineChartDataContext } from "./LineChartContext.js";
5
- import { renderAxisTick } from "./lib/renderAxisTick.js";
6
6
  const DENSITY_MIN_TICK_GAP = {
7
7
  sparse: 64,
8
8
  normal: 32,
@@ -11,16 +11,7 @@ const DENSITY_MIN_TICK_GAP = {
11
11
  const AXIS_LINE_PROPS = {
12
12
  stroke: 'var(--color-border-primary-light)'
13
13
  };
14
- const renderTick = renderAxisTick(({ index, visibleTicksCount })=>{
15
- if (0 === index) return {
16
- anchor: 'start'
17
- };
18
- if (index === visibleTicksCount - 1) return {
19
- anchor: 'end'
20
- };
21
- return {};
22
- });
23
- const LineChartXAxis = ({ tickFormatter, density, interval = 'preserveStartEnd', tickCount, ticks, minTickGap, domain, padding, type, hideTicks = false, axisLine = true })=>{
14
+ const LineChartXAxis = ({ tickFormatter, density, interval, tickCount, ticks, minTickGap, domain, padding, type, hideTicks = false, axisLine = true })=>{
24
15
  const dataCtx = useContext(LineChartDataContext);
25
16
  const xKey = dataCtx?.xKey ?? 'x';
26
17
  const resolvedMinTickGap = minTickGap ?? (density ? DENSITY_MIN_TICK_GAP[density] : void 0);
@@ -29,7 +20,7 @@ const LineChartXAxis = ({ tickFormatter, density, interval = 'preserveStartEnd',
29
20
  type: type,
30
21
  tickLine: false,
31
22
  axisLine: axisLine ? AXIS_LINE_PROPS : false,
32
- tick: hideTicks ? false : renderTick,
23
+ tick: hideTicks ? false : LINE_AXIS_TICK_TEXT_PROPS,
33
24
  tickFormatter: tickFormatter,
34
25
  interval: interval,
35
26
  tickCount: tickCount,
@@ -1,10 +1,19 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
- import { YAxis } from "recharts";
3
- import { LINE_Y_LABEL_WIDTH } from "./constants.js";
4
- import { renderAxisTick } from "./lib/renderAxisTick.js";
5
- const renderTick = renderAxisTick(({ index, visibleTicksCount })=>({
6
- skip: index === visibleTicksCount - 1
7
- }));
2
+ import { Text, YAxis } from "recharts";
3
+ import { LINE_AXIS_TICK_TEXT_PROPS, LINE_Y_LABEL_WIDTH } from "./constants.js";
4
+ const renderTick = (props)=>{
5
+ const { x, y, payload, visibleTicksCount, textAnchor, verticalAnchor, tickFormatter, index } = props;
6
+ if (index === visibleTicksCount - 1) return null;
7
+ const value = tickFormatter ? tickFormatter(payload.value, payload.index) : payload.value;
8
+ return /*#__PURE__*/ jsx(Text, {
9
+ x: x,
10
+ y: y,
11
+ textAnchor: textAnchor,
12
+ verticalAnchor: verticalAnchor,
13
+ ...LINE_AXIS_TICK_TEXT_PROPS,
14
+ children: value
15
+ });
16
+ };
8
17
  const LineChartYAxis = ({ tickFormatter, tickCount, ticks, domain, padding, width = LINE_Y_LABEL_WIDTH, hideTicks = false })=>/*#__PURE__*/ jsx(YAxis, {
9
18
  width: width,
10
19
  tickLine: false,
@@ -1,8 +1,7 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useContext, useEffect, useMemo, useRef } from "react";
3
3
  import { createPortal } from "react-dom";
4
- import { ReferenceArea, usePlotArea } from "recharts";
5
- import { formatChartDateTime } from "../lib/timeFormatters.js";
4
+ import { ReferenceArea } from "recharts";
6
5
  import { lineChartZoomCursorPopoverClasses } from "./classes.js";
7
6
  import { useZoomPendingListeners } from "./hooks/useZoomPendingListeners.js";
8
7
  import { LineChartDataContext, LineChartZoomContext } from "./LineChartContext.js";
@@ -10,24 +9,12 @@ import { LineChartZoomPopover } from "./LineChartZoomPopover.js";
10
9
  import { LineChartZoomPopoverConfirm } from "./LineChartZoomPopoverConfirm.js";
11
10
  import { LineChartZoomPopoverRange } from "./LineChartZoomPopoverRange.js";
12
11
  import { formatRange as formatRange_js_formatRange } from "./lib/formatRange.js";
13
- const defaultFormatRange = formatRange_js_formatRange((value)=>formatChartDateTime(value) || String(value));
14
12
  const POPOVER_OFFSET_X = 12;
15
- const LineChartZoomBrush = ({ disabled = false, formatRange = defaultFormatRange, confirmLabel = 'Zoom in', container })=>{
13
+ const POPOVER_OFFSET_Y = 12;
14
+ const LineChartZoomBrush = ({ disabled = false, formatRange = formatRange_js_formatRange, confirmLabel = 'Zoom in', container })=>{
16
15
  const dataCtx = useContext(LineChartDataContext);
17
16
  const zoomCtx = useContext(LineChartZoomContext);
18
17
  const popoverRef = useRef(null);
19
- const plotArea = usePlotArea();
20
- const rootRef = zoomCtx?.rootRef;
21
- const centerY = useMemo(()=>{
22
- if (!plotArea || !rootRef?.current) return null;
23
- const surface = rootRef.current.querySelector('.recharts-surface');
24
- if (!surface) return null;
25
- const rect = surface.getBoundingClientRect();
26
- return rect.top + plotArea.y + plotArea.height / 2;
27
- }, [
28
- plotArea,
29
- rootRef
30
- ]);
31
18
  const registerEnabled = zoomCtx?.registerEnabled;
32
19
  useEffect(()=>{
33
20
  if (disabled || !registerEnabled) return;
@@ -40,6 +27,7 @@ const LineChartZoomBrush = ({ disabled = false, formatRange = defaultFormatRange
40
27
  const pending = zoomCtx?.pending ?? null;
41
28
  const cancelPending = zoomCtx?.cancelPending;
42
29
  const confirmZoom = zoomCtx?.confirmZoom;
30
+ const rootRef = zoomCtx?.rootRef;
43
31
  useZoomPendingListeners({
44
32
  enabled: null !== pending,
45
33
  rootRef,
@@ -79,9 +67,9 @@ const LineChartZoomBrush = ({ disabled = false, formatRange = defaultFormatRange
79
67
  "data-zoom-state": isPending ? 'pending' : 'dragging',
80
68
  className: lineChartZoomCursorPopoverClasses,
81
69
  style: {
82
- top: centerY ?? popoverPosition.clientY,
70
+ top: popoverPosition.clientY - POPOVER_OFFSET_Y,
83
71
  left: popoverPosition.clientX + POPOVER_OFFSET_X,
84
- transform: 'translateY(-50%)',
72
+ transform: 'translateY(-100%)',
85
73
  pointerEvents: isPending ? 'auto' : 'none'
86
74
  },
87
75
  onMouseDown: (e)=>e.stopPropagation(),
@@ -104,7 +92,8 @@ const LineChartZoomBrush = ({ disabled = false, formatRange = defaultFormatRange
104
92
  x2: range.to,
105
93
  fill: "var(--color-states-primary-hover)",
106
94
  fillOpacity: 1,
107
- stroke: "none",
95
+ stroke: "var(--color-border-primary-light)",
96
+ strokeOpacity: 1,
108
97
  ifOverflow: "visible"
109
98
  }) : null,
110
99
  popoverContent ? /*#__PURE__*/ createPortal(popoverContent, container ?? document.body) : null
@@ -6,7 +6,7 @@ const lineChartZoomCursorPopoverClasses = "fixed z-30 pointer-events-none";
6
6
  const lineChartLegendVariants = cva('flex', {
7
7
  variants: {
8
8
  orientation: {
9
- horizontal: 'flex-row flex-wrap items-center gap-6 pl-12 py-2 [&:last-child]:pb-8',
9
+ horizontal: 'flex-row flex-wrap items-center gap-6 pl-12 py-2',
10
10
  vertical: 'flex-col gap-6 px-12 py-2 max-w-160'
11
11
  },
12
12
  align: {
@@ -1,8 +1,8 @@
1
1
  export { CHART_PALETTE_FILL as LINE_STROKE_FILL, resolveChartColor as resolveSeriesColor, } from '../lib/chartPalette';
2
2
  export declare const LINE_STROKE_WIDTH = 2;
3
3
  export declare const LINE_DASH_DASHARRAY = "6 4";
4
+ export declare const LINE_ACTIVE_DOT_RADIUS = 4;
4
5
  export declare const LINE_GRID_DASHARRAY = "4 4";
5
- export declare const LINE_CURSOR_DASHARRAY = "4 2";
6
6
  export declare const LINE_ANIMATION_DURATION = 400;
7
7
  export declare const LINE_ANIMATION_BEGIN = 0;
8
8
  export declare const LINE_INACTIVE_OPACITY = 0.3;
@@ -1,8 +1,8 @@
1
1
  import { CHART_PALETTE_FILL, resolveChartColor } from "../lib/chartPalette.js";
2
2
  const LINE_STROKE_WIDTH = 2;
3
3
  const LINE_DASH_DASHARRAY = '6 4';
4
+ const LINE_ACTIVE_DOT_RADIUS = 4;
4
5
  const LINE_GRID_DASHARRAY = '4 4';
5
- const LINE_CURSOR_DASHARRAY = '4 2';
6
6
  const LINE_ANIMATION_DURATION = 400;
7
7
  const LINE_ANIMATION_BEGIN = 0;
8
8
  const LINE_INACTIVE_OPACITY = 0.3;
@@ -23,4 +23,4 @@ const LINE_AXIS_TICK_TEXT_PROPS = {
23
23
  fontFamily: 'var(--font-sans)',
24
24
  fontWeight: 400
25
25
  };
26
- export { HOVER_POPOVER_TOP, LINE_ANIMATION_BEGIN, LINE_ANIMATION_DURATION, LINE_AXIS_TICK_TEXT_PROPS, LINE_CARD_HEIGHT, LINE_CURSOR_DASHARRAY, LINE_DASH_DASHARRAY, LINE_DEFAULT_BODY_MARGIN, LINE_GRID_DASHARRAY, LINE_HEADER_HEIGHT, LINE_INACTIVE_OPACITY, CHART_PALETTE_FILL as LINE_STROKE_FILL, LINE_STROKE_WIDTH, LINE_X_LABEL_HEIGHT, LINE_Y_LABEL_WIDTH, resolveChartColor as resolveSeriesColor };
26
+ export { HOVER_POPOVER_TOP, LINE_ACTIVE_DOT_RADIUS, LINE_ANIMATION_BEGIN, LINE_ANIMATION_DURATION, LINE_AXIS_TICK_TEXT_PROPS, LINE_CARD_HEIGHT, LINE_DASH_DASHARRAY, LINE_DEFAULT_BODY_MARGIN, LINE_GRID_DASHARRAY, LINE_HEADER_HEIGHT, LINE_INACTIVE_OPACITY, CHART_PALETTE_FILL as LINE_STROKE_FILL, LINE_STROKE_WIDTH, LINE_X_LABEL_HEIGHT, LINE_Y_LABEL_WIDTH, resolveChartColor as resolveSeriesColor };
@@ -1,5 +1,9 @@
1
- /** Higher-order range formatter joins `format(from)` and `format(to)` with `→`. */
2
- export declare const formatRange: (format: (value: unknown) => string) => (range: {
3
- from: unknown;
4
- to: unknown;
5
- }) => string;
1
+ import type { LineChartZoomRange } from '../LineChartContext';
2
+ /**
3
+ * Default range text for the zoom popover. Renders as `from → to` using the
4
+ * shared {@link formatChartDateTime} helper for numeric (timestamp) X values,
5
+ * which respects the app-level `order` / `hourCycle` defaults from the
6
+ * `DateFormatProvider`. Falls back to `String(value)` for non-numeric X values.
7
+ * Consumers override via the `formatRange` prop on `<LineChartZoomBrush>`.
8
+ */
9
+ export declare const formatRange: (range: LineChartZoomRange) => string;
@@ -1,2 +1,7 @@
1
- const formatRange = (format)=>(range)=>`${format(range.from)} ${format(range.to)}`;
1
+ import { formatChartDateTime } from "../../lib/timeFormatters.js";
2
+ const formatRange = (range)=>{
3
+ const from = formatChartDateTime(range.from) || String(range.from);
4
+ const to = formatChartDateTime(range.to) || String(range.to);
5
+ return `${from} → ${to}`;
6
+ };
2
7
  export { formatRange };
@@ -3,7 +3,7 @@ const jitter = (i, seed, salt)=>{
3
3
  return v - Math.floor(v);
4
4
  };
5
5
  const genHourly = (count, seed = 1)=>{
6
- const start = new Date(2025, 0, 1, 0, 0, 0).getTime();
6
+ const start = Date.UTC(2025, 0, 1, 0, 0, 0);
7
7
  const out = [];
8
8
  for(let i = 0; i < count; i++){
9
9
  const t = start + 60 * i * 60000;
@@ -20,7 +20,7 @@ const genHourly = (count, seed = 1)=>{
20
20
  return out;
21
21
  };
22
22
  const genDaily = (count)=>{
23
- const start = new Date(2024, 0, 1, 0, 0, 0).getTime();
23
+ const start = Date.UTC(2024, 0, 1, 0, 0, 0);
24
24
  const out = [];
25
25
  for(let i = 0; i < count; i++){
26
26
  const t = start + 24 * i * 3600000;
@@ -90,7 +90,7 @@ const dailyData60 = genDaily(60);
90
90
  const hourlyData1000 = genHourly(1000);
91
91
  const singlePointData = [
92
92
  {
93
- timestamp: new Date(2025, 0, 1, 12, 0, 0).getTime(),
93
+ timestamp: Date.UTC(2025, 0, 1, 12, 0, 0),
94
94
  requests: 142
95
95
  }
96
96
  ];
@@ -7,6 +7,11 @@ export interface ChartTimeFormatters {
7
7
  formatHourWithTimezone: (value: unknown) => ReactNode;
8
8
  formatDateWithTimezone: (value: unknown) => ReactNode;
9
9
  formatDateTimeWithTimezone: (value: unknown) => ReactNode;
10
+ /** `from → to` using `formatDate`. Suitable for `<LineChartZoomBrush formatRange>`. */
11
+ formatDateRange: (range: {
12
+ from: unknown;
13
+ to: unknown;
14
+ }) => string;
10
15
  }
11
16
  /**
12
17
  * Memoised bundle of chart time formatters bound to `DateFormatProvider`
@@ -22,7 +22,8 @@ const useChartTimeFormatters = ()=>{
22
22
  formatTimezone: formatChartTimezone,
23
23
  formatHourWithTimezone: withTimezoneChip(formatHour),
24
24
  formatDateWithTimezone: withTimezoneChip(formatDate),
25
- formatDateTimeWithTimezone: withTimezoneChip(formatDateTime)
25
+ formatDateTimeWithTimezone: withTimezoneChip(formatDateTime),
26
+ formatDateRange: ({ from, to })=>`${formatDate(from)} → ${formatDate(to)}`
26
27
  };
27
28
  }, [
28
29
  order,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "0.39.0",
3
- "generatedAt": "2026-05-19T14:54:13.372Z",
3
+ "generatedAt": "2026-05-19T13:23:43.047Z",
4
4
  "components": [
5
5
  {
6
6
  "name": "Accordion",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wallarm-org/design-system",
3
- "version": "0.39.1",
3
+ "version": "0.40.0-rc-feature-AS-982.1",
4
4
  "description": "Core design system library with React components and Storybook documentation",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -1,13 +0,0 @@
1
- import type { ReactNode } from 'react';
2
- import type { TextAnchor } from 'recharts/types/component/Text';
3
- import type { XAxisTickContentProps, YAxisTickContentProps } from 'recharts/types/util/types';
4
- export interface AxisTickDecision {
5
- /** When `true`, render nothing for this tick. */
6
- skip?: boolean;
7
- /** Override the axis-derived text anchor. */
8
- anchor?: TextAnchor;
9
- }
10
- type AxisTickProps = XAxisTickContentProps | YAxisTickContentProps;
11
- type Decide<T extends AxisTickProps> = (props: T) => AxisTickDecision;
12
- export declare const renderAxisTick: <T extends AxisTickProps>(decide?: Decide<T>) => (props: T) => ReactNode;
13
- export {};
@@ -1,20 +0,0 @@
1
- import { jsx } from "react/jsx-runtime";
2
- import { Text } from "recharts";
3
- import { LINE_AXIS_TICK_TEXT_PROPS } from "../constants.js";
4
- const renderAxisTick = (decide)=>(props)=>{
5
- const decision = decide?.(props) ?? {};
6
- if (decision.skip) return null;
7
- const { x, y, payload, textAnchor, verticalAnchor, tickFormatter, orientation } = props;
8
- const value = tickFormatter ? tickFormatter(payload.value, payload.index) : payload.value;
9
- const isHorizontal = 'top' === orientation || 'bottom' === orientation;
10
- const anchorToCoordinate = null != decision.anchor;
11
- return /*#__PURE__*/ jsx(Text, {
12
- x: anchorToCoordinate && isHorizontal ? payload.coordinate : x,
13
- y: anchorToCoordinate && !isHorizontal ? payload.coordinate : y,
14
- textAnchor: decision.anchor ?? textAnchor,
15
- verticalAnchor: verticalAnchor,
16
- ...LINE_AXIS_TICK_TEXT_PROPS,
17
- children: value
18
- });
19
- };
20
- export { renderAxisTick };