@l3mpire/ui 2.13.0 → 2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/USAGE.md CHANGED
@@ -941,7 +941,10 @@ import {
941
941
  | `InteractiveFilterChip` | FilterChip with per-segment popovers (property, operator, value, kebab) |
942
942
  | `PropertySelector` | 2-level popover: categories → properties with search |
943
943
  | `AdvancedChip` | Split button "Advanced filters (N)" + close (outlined neutral) |
944
- | `AdvancedPopover` | Popover with Where/And/Or rows, property/operator selects, inline value input |
944
+ | `AdvancedPopover` | Popover with Where/And/Or rows + nested groups, property/operator/value selects |
945
+ | `AdvancedRow` | Single condition row (property + operator + type-aware value popover + actions) |
946
+ | `AdvancedGroup` | Nested group container with shared connector and inline property selector |
947
+ | `FilterNodeActions` | Kebab menu shared by rows and groups (Duplicate / Turn into group↔filter / Delete) |
945
948
  | `SaveViewButton` | Split button (Save view + dropdown chevron) |
946
949
  | `SummaryChip` | Minimal mode: "Filters (N)" with interactive popover |
947
950
 
@@ -954,6 +957,10 @@ import {
954
957
 
955
958
  **And/Or toggle:** each row in the advanced popover has an independent And/Or toggle button. The choice is **persisted on each `FilterCondition` as `logicOperator: "and" | "or"`** (defaults to `"and"` when undefined), so it survives remounts and can be serialized.
956
959
 
960
+ **Nested groups:** the advanced popover supports arbitrarily-nested `FilterGroup` nodes via `FilterNode = FilterCondition | FilterGroup`. Each row's kebab menu (`FilterNodeActions`) lets users **Duplicate**, **Turn into group** / **Turn into filter**, or **Delete**. Groups share a single connector (`Where`/`And`/`Or`) for all their children and render an inline `PropertySelector` to add new conditions. Tree mutations go through the immutable `updateNodeInTree` / `removeNodeFromTree` / `insertAfterInTree` / `replaceNodeInTree` helpers in `utils`.
961
+
962
+ **Type-aware value inputs:** every `AdvancedRow` value slot renders the same `<ValueInput>` used by `InteractiveFilterChip` — text → text input, number → number / range, date → calendar, enum/tags → single/multi select with `dynamicOptions` support, etc. The trigger button shows the formatted current value (via `formatFilterValue`) and a `+N` badge for multi-selections (via `getBadgeCount`).
963
+
957
964
  **Advanced filter shortcut:** the PropertySelector footer shows an "Advanced filter · N rule(s)" item. Clicking it closes the property selector and opens the advanced popover directly in its initial empty state — where the user picks the first property inline via a "Where | [Select property ▾]" draft row. When no advanced filters exist yet, clicking this shortcut makes the AdvancedChip appear with the popover already open; closing without adding a filter removes the chip automatically.
958
965
 
959
966
  **Dynamic options ("Me", "Unassigned", …):** enum/tags/relation properties accept a `dynamicOptions` array. Each entry is `{ value, label, description?, icon? }` and is rendered at the top of the SingleSelect / MultiSelect dropdown with a divider separating it from the regular options. The `value` is a sentinel string stored on `FilterCondition.value` — the DS only renders, the consuming app resolves it at query time (e.g. `"__me__"` → `currentUser.id`). This keeps session/business logic out of the DS while still getting a consistent visual treatment.
@@ -986,6 +993,13 @@ import {
986
993
  | `createFilterWithDefaults(propertyId, type)` | Creates FilterCondition with default operator |
987
994
  | `isNoValueOperator(op)` | True for is empty / is not empty / is true / is false |
988
995
  | `getValueInputType(type, op)` | Returns the input component name for a type+operator combo |
996
+ | `formatFilterValue(value)` | Stringifies a `FilterValue` for display (handles Date, ranges, arrays, booleans) |
997
+ | `getBadgeCount(value)` | Returns `+N` count for multi-value selections, `undefined` otherwise |
998
+ | `createEmptyGroup()` / `isFilterGroup(node)` | Tree helpers for nested groups |
999
+ | `wrapInGroup(condition)` / `unwrapGroup(group)` | Convert between condition and single-child group |
1000
+ | `duplicateNode(node)` | Deep clone a node tree with fresh UUIDs |
1001
+ | `updateNodeInTree` / `removeNodeFromTree` / `insertAfterInTree` / `replaceNodeInTree` | Immutable tree mutations by id |
1002
+ | `countConditions(nodes)` | Count all leaf conditions in a tree |
989
1003
 
990
1004
  ---
991
1005
 
package/dist/index.d.mts CHANGED
@@ -723,15 +723,27 @@ type FilterValue = string | number | boolean | Date | [number, number] | [Date,
723
723
  type LogicOperator = "and" | "or";
724
724
  interface FilterCondition {
725
725
  id: string;
726
+ /** Optional discriminator. Defaults to "condition" when absent. */
727
+ type?: "condition";
726
728
  propertyId: string;
727
729
  operator: OperatorType | null;
728
730
  value: FilterValue;
729
- /** Logic connector to the previous filter. Defaults to "and". */
731
+ /** Logic connector to the previous sibling. Defaults to "and". */
730
732
  logicOperator?: LogicOperator;
731
733
  }
734
+ interface FilterGroup {
735
+ id: string;
736
+ type: "group";
737
+ /** Logic connector to the previous sibling. Defaults to "and". */
738
+ logicOperator?: LogicOperator;
739
+ children: FilterNode[];
740
+ }
741
+ /** A filter node is either a single condition or a group of nested nodes. */
742
+ type FilterNode = FilterCondition | FilterGroup;
732
743
  interface FilterState {
733
744
  basicFilters: FilterCondition[];
734
- advancedFilters: FilterCondition[];
745
+ /** Supports nested groups. Each node is either a condition or a group. */
746
+ advancedFilters: FilterNode[];
735
747
  sort: {
736
748
  field: string;
737
749
  direction: "asc" | "desc";
@@ -938,13 +950,15 @@ interface AdvancedRowProps {
938
950
  onUpdate: (condition: FilterCondition) => void;
939
951
  onPropertyChange: (property: PropertyDefinition) => void;
940
952
  onDelete: () => void;
953
+ onDuplicate?: () => void;
954
+ onTurnIntoGroup?: () => void;
941
955
  }
942
956
  declare const AdvancedRow: React.FC<AdvancedRowProps>;
943
957
 
944
958
  interface AdvancedPopoverProps {
945
- filters: FilterCondition[];
959
+ filters: FilterNode[];
946
960
  properties: PropertyDefinition[];
947
- onFiltersChange: (filters: FilterCondition[]) => void;
961
+ onFiltersChange: (filters: FilterNode[]) => void;
948
962
  open?: boolean;
949
963
  onOpenChange?: (open: boolean) => void;
950
964
  children?: React.ReactNode;
@@ -953,14 +967,12 @@ declare const AdvancedPopover: React.FC<AdvancedPopoverProps>;
953
967
 
954
968
  interface SummaryChipProps {
955
969
  count: number;
956
- filters: FilterCondition[];
970
+ filters: FilterNode[];
957
971
  properties: PropertyDefinition[];
958
- onFiltersChange: (filters: FilterCondition[]) => void;
972
+ onFiltersChange: (filters: FilterNode[]) => void;
959
973
  onClearAll: () => void;
960
- /** Custom trigger element. When omitted, renders the default "Filters (N)" button. */
961
974
  children?: React.ReactNode;
962
975
  className?: string;
963
- /** Controlled open state. */
964
976
  open?: boolean;
965
977
  onOpenChange?: (open: boolean) => void;
966
978
  }
package/dist/index.d.ts CHANGED
@@ -723,15 +723,27 @@ type FilterValue = string | number | boolean | Date | [number, number] | [Date,
723
723
  type LogicOperator = "and" | "or";
724
724
  interface FilterCondition {
725
725
  id: string;
726
+ /** Optional discriminator. Defaults to "condition" when absent. */
727
+ type?: "condition";
726
728
  propertyId: string;
727
729
  operator: OperatorType | null;
728
730
  value: FilterValue;
729
- /** Logic connector to the previous filter. Defaults to "and". */
731
+ /** Logic connector to the previous sibling. Defaults to "and". */
730
732
  logicOperator?: LogicOperator;
731
733
  }
734
+ interface FilterGroup {
735
+ id: string;
736
+ type: "group";
737
+ /** Logic connector to the previous sibling. Defaults to "and". */
738
+ logicOperator?: LogicOperator;
739
+ children: FilterNode[];
740
+ }
741
+ /** A filter node is either a single condition or a group of nested nodes. */
742
+ type FilterNode = FilterCondition | FilterGroup;
732
743
  interface FilterState {
733
744
  basicFilters: FilterCondition[];
734
- advancedFilters: FilterCondition[];
745
+ /** Supports nested groups. Each node is either a condition or a group. */
746
+ advancedFilters: FilterNode[];
735
747
  sort: {
736
748
  field: string;
737
749
  direction: "asc" | "desc";
@@ -938,13 +950,15 @@ interface AdvancedRowProps {
938
950
  onUpdate: (condition: FilterCondition) => void;
939
951
  onPropertyChange: (property: PropertyDefinition) => void;
940
952
  onDelete: () => void;
953
+ onDuplicate?: () => void;
954
+ onTurnIntoGroup?: () => void;
941
955
  }
942
956
  declare const AdvancedRow: React.FC<AdvancedRowProps>;
943
957
 
944
958
  interface AdvancedPopoverProps {
945
- filters: FilterCondition[];
959
+ filters: FilterNode[];
946
960
  properties: PropertyDefinition[];
947
- onFiltersChange: (filters: FilterCondition[]) => void;
961
+ onFiltersChange: (filters: FilterNode[]) => void;
948
962
  open?: boolean;
949
963
  onOpenChange?: (open: boolean) => void;
950
964
  children?: React.ReactNode;
@@ -953,14 +967,12 @@ declare const AdvancedPopover: React.FC<AdvancedPopoverProps>;
953
967
 
954
968
  interface SummaryChipProps {
955
969
  count: number;
956
- filters: FilterCondition[];
970
+ filters: FilterNode[];
957
971
  properties: PropertyDefinition[];
958
- onFiltersChange: (filters: FilterCondition[]) => void;
972
+ onFiltersChange: (filters: FilterNode[]) => void;
959
973
  onClearAll: () => void;
960
- /** Custom trigger element. When omitted, renders the default "Filters (N)" button. */
961
974
  children?: React.ReactNode;
962
975
  className?: string;
963
- /** Controlled open state. */
964
976
  open?: boolean;
965
977
  onOpenChange?: (open: boolean) => void;
966
978
  }