@l3mpire/ui 2.16.4 → 2.18.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
@@ -963,7 +963,7 @@ import {
963
963
 
964
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.
965
965
 
966
- **Dynamic options ("Me", "Unassigned", …):** enum/tags/relation properties accept a `dynamicOptions` array. Each entry is `{ value, label, description? }` and is rendered inline with regular options in the SingleSelect / MultiSelect dropdown. The `value` is a sentinel string stored on `FilterCondition.value` — the DS renders the `label` in the filter chip (not the raw sentinel) and 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.
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.
967
967
 
968
968
  ```tsx
969
969
  {
@@ -979,8 +979,9 @@ import {
979
979
  value: "__me__",
980
980
  label: "Me",
981
981
  description: "This value is dynamically applied to the current user",
982
+ icon: faBoltOutline,
982
983
  },
983
- { value: "__deactivated__", label: "All deactivated and removed owners" },
984
+ { value: "__deactivated__", label: "All deactivated and removed owners", icon: faBoltOutline },
984
985
  ],
985
986
  }
986
987
  ```
@@ -992,7 +993,7 @@ import {
992
993
  | `createFilterWithDefaults(propertyId, type)` | Creates FilterCondition with default operator |
993
994
  | `isNoValueOperator(op)` | True for is empty / is not empty / is true / is false |
994
995
  | `getValueInputType(type, op)` | Returns the input component name for a type+operator combo |
995
- | `formatFilterValue(value, dynamicOptions?)` | Stringifies a `FilterValue` for display resolves sentinel values via `dynamicOptions` labels |
996
+ | `formatFilterValue(value)` | Stringifies a `FilterValue` for display (handles Date, ranges, arrays, booleans) |
996
997
  | `getBadgeCount(value)` | Returns `+N` count for multi-value selections, `undefined` otherwise |
997
998
  | `createEmptyGroup()` / `isFilterGroup(node)` | Tree helpers for nested groups |
998
999
  | `wrapInGroup(condition)` / `unwrapGroup(group)` | Convert between condition and single-child group |
package/dist/index.d.mts CHANGED
@@ -899,6 +899,8 @@ declare const PropertySelector: React.FC<PropertySelectorProps>;
899
899
 
900
900
  interface KebabMenuProps {
901
901
  onConvertToAdvanced?: () => void;
902
+ /** When true, label reads "Add to advanced filters" instead of "Convert to advanced". */
903
+ hasAdvancedFilters?: boolean;
902
904
  onDelete?: () => void;
903
905
  open?: boolean;
904
906
  onOpenChange?: (open: boolean) => void;
@@ -931,11 +933,12 @@ interface InteractiveFilterChipProps {
931
933
  onPropertyChange?: (property: PropertyDefinition) => void;
932
934
  onDelete: () => void;
933
935
  onConvertToAdvanced?: () => void;
936
+ hasAdvancedFilters?: boolean;
934
937
  className?: string;
935
938
  }
936
939
  declare const InteractiveFilterChip: React.FC<InteractiveFilterChipProps>;
937
940
 
938
- type FilterBarMode = "default" | "minimal";
941
+ type FilterBarMode = "default" | "minimal" | "icon";
939
942
  /**
940
943
  * Observes the container width and returns the appropriate FilterBar mode.
941
944
  * - default: > breakpoint
@@ -952,6 +955,12 @@ interface FilterSystemProps {
952
955
  mode?: FilterBarMode;
953
956
  /** Width breakpoint (px) for switching to minimal mode. Default: 600. */
954
957
  breakpoint?: number;
958
+ /**
959
+ * Constrain popovers to stay within the container bounds. Use this when
960
+ * FilterSystem lives inside an iframe or a fixed-width panel where content
961
+ * must not overflow.
962
+ */
963
+ bounded?: boolean;
955
964
  children?: React.ReactNode;
956
965
  actions?: React.ReactNode;
957
966
  className?: string;
@@ -989,6 +998,7 @@ interface AdvancedPopoverProps {
989
998
  open?: boolean;
990
999
  onOpenChange?: (open: boolean) => void;
991
1000
  children?: React.ReactNode;
1001
+ collisionBoundary?: Element | null;
992
1002
  }
993
1003
  declare const AdvancedPopover: React.FC<AdvancedPopoverProps>;
994
1004
 
@@ -1002,6 +1012,7 @@ interface SummaryChipProps {
1002
1012
  className?: string;
1003
1013
  open?: boolean;
1004
1014
  onOpenChange?: (open: boolean) => void;
1015
+ collisionBoundary?: Element | null;
1005
1016
  }
1006
1017
  declare const SummaryChip: React.FC<SummaryChipProps>;
1007
1018
 
package/dist/index.d.ts CHANGED
@@ -899,6 +899,8 @@ declare const PropertySelector: React.FC<PropertySelectorProps>;
899
899
 
900
900
  interface KebabMenuProps {
901
901
  onConvertToAdvanced?: () => void;
902
+ /** When true, label reads "Add to advanced filters" instead of "Convert to advanced". */
903
+ hasAdvancedFilters?: boolean;
902
904
  onDelete?: () => void;
903
905
  open?: boolean;
904
906
  onOpenChange?: (open: boolean) => void;
@@ -931,11 +933,12 @@ interface InteractiveFilterChipProps {
931
933
  onPropertyChange?: (property: PropertyDefinition) => void;
932
934
  onDelete: () => void;
933
935
  onConvertToAdvanced?: () => void;
936
+ hasAdvancedFilters?: boolean;
934
937
  className?: string;
935
938
  }
936
939
  declare const InteractiveFilterChip: React.FC<InteractiveFilterChipProps>;
937
940
 
938
- type FilterBarMode = "default" | "minimal";
941
+ type FilterBarMode = "default" | "minimal" | "icon";
939
942
  /**
940
943
  * Observes the container width and returns the appropriate FilterBar mode.
941
944
  * - default: > breakpoint
@@ -952,6 +955,12 @@ interface FilterSystemProps {
952
955
  mode?: FilterBarMode;
953
956
  /** Width breakpoint (px) for switching to minimal mode. Default: 600. */
954
957
  breakpoint?: number;
958
+ /**
959
+ * Constrain popovers to stay within the container bounds. Use this when
960
+ * FilterSystem lives inside an iframe or a fixed-width panel where content
961
+ * must not overflow.
962
+ */
963
+ bounded?: boolean;
955
964
  children?: React.ReactNode;
956
965
  actions?: React.ReactNode;
957
966
  className?: string;
@@ -989,6 +998,7 @@ interface AdvancedPopoverProps {
989
998
  open?: boolean;
990
999
  onOpenChange?: (open: boolean) => void;
991
1000
  children?: React.ReactNode;
1001
+ collisionBoundary?: Element | null;
992
1002
  }
993
1003
  declare const AdvancedPopover: React.FC<AdvancedPopoverProps>;
994
1004
 
@@ -1002,6 +1012,7 @@ interface SummaryChipProps {
1002
1012
  className?: string;
1003
1013
  open?: boolean;
1004
1014
  onOpenChange?: (open: boolean) => void;
1015
+ collisionBoundary?: Element | null;
1005
1016
  }
1006
1017
  declare const SummaryChip: React.FC<SummaryChipProps>;
1007
1018
 
package/dist/index.js CHANGED
@@ -5440,6 +5440,23 @@ function updateNodeInTree(nodes, id, updater) {
5440
5440
  return node;
5441
5441
  });
5442
5442
  }
5443
+ function toggleGroupLogicInTree(nodes, id) {
5444
+ const idx = nodes.findIndex((n) => n.id === id);
5445
+ if (idx >= 0) {
5446
+ const current = nodes[idx].logicOperator ?? "and";
5447
+ const next = current === "and" ? "or" : "and";
5448
+ return nodes.map(
5449
+ (n, i) => i === 0 ? n : { ...n, logicOperator: next }
5450
+ );
5451
+ }
5452
+ return nodes.map((node) => {
5453
+ if (isFilterGroup(node)) {
5454
+ const updated = toggleGroupLogicInTree(node.children, id);
5455
+ if (updated !== node.children) return { ...node, children: updated };
5456
+ }
5457
+ return node;
5458
+ });
5459
+ }
5443
5460
  function removeNodeFromTree(nodes, id) {
5444
5461
  const result = [];
5445
5462
  for (const node of nodes) {
@@ -7166,6 +7183,7 @@ var import_icons30 = require("@l3mpire/icons");
7166
7183
  var import_jsx_runtime51 = require("react/jsx-runtime");
7167
7184
  var KebabMenu = ({
7168
7185
  onConvertToAdvanced,
7186
+ hasAdvancedFilters = false,
7169
7187
  onDelete,
7170
7188
  open,
7171
7189
  onOpenChange,
@@ -7204,7 +7222,7 @@ var KebabMenu = ({
7204
7222
  className: "shrink-0 text-[var(--color-dropdown-item-icon)]"
7205
7223
  }
7206
7224
  ),
7207
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "text-sm font-regular leading-sm text-[var(--color-dropdown-item-text)]", children: "Convert to advanced" })
7225
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)("span", { className: "text-sm font-regular leading-sm text-[var(--color-dropdown-item-text)]", children: hasAdvancedFilters ? "Add to advanced filters" : "Convert to advanced" })
7208
7226
  ]
7209
7227
  }
7210
7228
  ),
@@ -7391,6 +7409,7 @@ var InteractiveFilterChip = ({
7391
7409
  onPropertyChange,
7392
7410
  onDelete,
7393
7411
  onConvertToAdvanced,
7412
+ hasAdvancedFilters = false,
7394
7413
  className
7395
7414
  }) => {
7396
7415
  const [operatorOpen, setOperatorOpen] = React46.useState(false);
@@ -7546,6 +7565,7 @@ var InteractiveFilterChip = ({
7546
7565
  open: kebabOpen,
7547
7566
  onOpenChange: setKebabOpen,
7548
7567
  onConvertToAdvanced,
7568
+ hasAdvancedFilters,
7549
7569
  onDelete,
7550
7570
  children: /* @__PURE__ */ (0, import_jsx_runtime53.jsx)("div", { children: /* @__PURE__ */ (0, import_jsx_runtime53.jsx)(
7551
7571
  FilterChipSegment,
@@ -8044,7 +8064,8 @@ var AdvancedPopover = ({
8044
8064
  onFiltersChange,
8045
8065
  open,
8046
8066
  onOpenChange,
8047
- children
8067
+ children,
8068
+ collisionBoundary
8048
8069
  }) => {
8049
8070
  const [addSelectorOpen, setAddSelectorOpen] = React51.useState(false);
8050
8071
  const [draftPickerOpen, setDraftPickerOpen] = React51.useState(false);
@@ -8121,12 +8142,7 @@ var AdvancedPopover = ({
8121
8142
  }
8122
8143
  };
8123
8144
  const toggleLogicOp = (id) => {
8124
- onFiltersChange(
8125
- updateNodeInTree(filters, id, (n) => ({
8126
- ...n,
8127
- logicOperator: (n.logicOperator ?? "and") === "and" ? "or" : "and"
8128
- }))
8129
- );
8145
+ onFiltersChange(toggleGroupLogicInTree(filters, id));
8130
8146
  };
8131
8147
  const handleGroupChildrenChange = (groupId, children2) => {
8132
8148
  onFiltersChange(
@@ -8197,6 +8213,7 @@ var AdvancedPopover = ({
8197
8213
  sideOffset: 4,
8198
8214
  align: "start",
8199
8215
  collisionPadding: 16,
8216
+ collisionBoundary: collisionBoundary ?? void 0,
8200
8217
  onOpenAutoFocus: (e) => e.preventDefault(),
8201
8218
  className: cn(
8202
8219
  "z-50 flex flex-col",
@@ -8204,7 +8221,7 @@ var AdvancedPopover = ({
8204
8221
  "data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
8205
8222
  "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
8206
8223
  "data-[side=bottom]:slide-in-from-top-2",
8207
- "w-[min(640px,calc(100vw-32px))] min-w-[360px]"
8224
+ collisionBoundary ? "w-[min(640px,var(--radix-popover-content-available-width))] min-w-0" : "w-[min(640px,calc(100vw-32px))] min-w-[360px]"
8208
8225
  ),
8209
8226
  children: [
8210
8227
  /* @__PURE__ */ (0, import_jsx_runtime58.jsx)("div", { className: "flex flex-col gap-base p-base", children: filters.length > 0 ? filters.map((node, i) => renderNode(node, i)) : /* @__PURE__ */ (0, import_jsx_runtime58.jsx)(
@@ -8337,7 +8354,8 @@ var SummaryChip = ({
8337
8354
  children,
8338
8355
  className,
8339
8356
  open: openProp,
8340
- onOpenChange
8357
+ onOpenChange,
8358
+ collisionBoundary
8341
8359
  }) => {
8342
8360
  const [uncontrolledOpen, setUncontrolledOpen] = React52.useState(false);
8343
8361
  const isControlled = openProp !== void 0;
@@ -8415,12 +8433,7 @@ var SummaryChip = ({
8415
8433
  }
8416
8434
  };
8417
8435
  const toggleLogicOp = (id) => {
8418
- onFiltersChange(
8419
- updateNodeInTree(filters, id, (n) => ({
8420
- ...n,
8421
- logicOperator: (n.logicOperator ?? "and") === "and" ? "or" : "and"
8422
- }))
8423
- );
8436
+ onFiltersChange(toggleGroupLogicInTree(filters, id));
8424
8437
  };
8425
8438
  const handleGroupChildrenChange = (groupId, newChildren) => {
8426
8439
  onFiltersChange(
@@ -8510,6 +8523,7 @@ var SummaryChip = ({
8510
8523
  sideOffset: 4,
8511
8524
  align: "start",
8512
8525
  collisionPadding: 16,
8526
+ collisionBoundary: collisionBoundary ?? void 0,
8513
8527
  onOpenAutoFocus: (e) => e.preventDefault(),
8514
8528
  className: cn(
8515
8529
  "z-50 flex flex-col overflow-clip",
@@ -8517,7 +8531,7 @@ var SummaryChip = ({
8517
8531
  "data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
8518
8532
  "data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95",
8519
8533
  "data-[side=bottom]:slide-in-from-top-2",
8520
- "w-[min(640px,calc(100vw-32px))]"
8534
+ collisionBoundary ? "w-[min(640px,var(--radix-popover-content-available-width))]" : "w-[min(640px,calc(100vw-32px))]"
8521
8535
  ),
8522
8536
  children: [
8523
8537
  /* @__PURE__ */ (0, import_jsx_runtime59.jsx)("div", { className: "flex flex-col gap-base p-base", children: filters.length > 0 ? filters.map((node, i) => renderNode(node, i)) : /* @__PURE__ */ (0, import_jsx_runtime59.jsx)(
@@ -8631,12 +8645,14 @@ var FilterSystem = ({
8631
8645
  sortFields,
8632
8646
  mode: modeOverride,
8633
8647
  breakpoint,
8648
+ bounded = false,
8634
8649
  children,
8635
8650
  actions,
8636
8651
  className
8637
8652
  }) => {
8638
8653
  const containerRef = React54.useRef(null);
8639
8654
  const mode = useFilterBarMode(containerRef, modeOverride, breakpoint);
8655
+ const collisionBoundary = bounded ? containerRef.current : void 0;
8640
8656
  const [propertySelectorOpen, setPropertySelectorOpen] = React54.useState(false);
8641
8657
  const [advancedOpen, setAdvancedOpen] = React54.useState(false);
8642
8658
  const [summaryOpen, setSummaryOpen] = React54.useState(false);
@@ -8713,7 +8729,8 @@ var FilterSystem = ({
8713
8729
  };
8714
8730
  const getPropertyDef = (propertyId) => properties.find((p) => p.id === propertyId);
8715
8731
  const hasAdvanced = filterState.advancedFilters.length > 0;
8716
- const isMinimal = mode === "minimal";
8732
+ const isMinimal = mode === "minimal" || mode === "icon";
8733
+ const isIconOnly = mode === "icon";
8717
8734
  const handleOpenAdvanced = () => {
8718
8735
  setPropertySelectorOpen(false);
8719
8736
  requestAnimationFrame(() => {
@@ -8726,7 +8743,6 @@ var FilterSystem = ({
8726
8743
  };
8727
8744
  const advancedFilterCount = filterState.advancedFilters.length;
8728
8745
  const showAdvancedChip = hasAdvanced || advancedOpen;
8729
- const showSummaryChip = totalCount > 0 || summaryOpen;
8730
8746
  return /* @__PURE__ */ (0, import_jsx_runtime60.jsxs)(FilterBar, { ref: containerRef, className, children: [
8731
8747
  /* @__PURE__ */ (0, import_jsx_runtime60.jsxs)(FilterBarLeft, { className: "flex-nowrap flex-1 min-w-0 overflow-x-auto scrollbar-none outline-none [&>*]:shrink-0", children: [
8732
8748
  children,
@@ -8740,38 +8756,33 @@ var FilterSystem = ({
8740
8756
  iconOnly: isMinimal
8741
8757
  }
8742
8758
  ),
8743
- isMinimal ? /* @__PURE__ */ (0, import_jsx_runtime60.jsxs)(import_jsx_runtime60.Fragment, { children: [
8744
- showSummaryChip && /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
8745
- SummaryChip,
8746
- {
8747
- count: totalCount,
8748
- filters: [...filterState.basicFilters, ...filterState.advancedFilters],
8749
- properties,
8750
- onFiltersChange: (nodes) => {
8751
- onFilterStateChange({
8752
- ...filterState,
8753
- basicFilters: [],
8754
- advancedFilters: nodes
8755
- });
8756
- },
8757
- onClearAll: handleClearAll,
8758
- open: summaryOpen,
8759
- onOpenChange: setSummaryOpen
8760
- }
8761
- ),
8762
- !showSummaryChip && /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
8763
- PropertySelector,
8764
- {
8765
- properties,
8766
- onSelect: handleAddFilter,
8767
- open: propertySelectorOpen,
8768
- onOpenChange: setPropertySelectorOpen,
8769
- onAdvancedFilter: handleOpenAdvanced,
8770
- advancedFilterCount,
8771
- children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(FilterBarButton, { iconOnly: true })
8772
- }
8773
- )
8774
- ] }) : (
8759
+ isMinimal ? /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(import_jsx_runtime60.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
8760
+ SummaryChip,
8761
+ {
8762
+ count: totalCount,
8763
+ filters: [...filterState.basicFilters, ...filterState.advancedFilters],
8764
+ properties,
8765
+ onFiltersChange: (nodes) => {
8766
+ onFilterStateChange({
8767
+ ...filterState,
8768
+ basicFilters: [],
8769
+ advancedFilters: nodes
8770
+ });
8771
+ },
8772
+ onClearAll: handleClearAll,
8773
+ open: summaryOpen,
8774
+ onOpenChange: setSummaryOpen,
8775
+ collisionBoundary,
8776
+ children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
8777
+ FilterBarButton,
8778
+ {
8779
+ iconOnly: isIconOnly,
8780
+ count: totalCount > 0 ? totalCount : void 0,
8781
+ title: buildFilterSummary(filterState.basicFilters, filterState.advancedFilters.length, properties)
8782
+ }
8783
+ )
8784
+ }
8785
+ ) }) : (
8775
8786
  /* ── DEFAULT MODE ────────────────────────────────────── */
8776
8787
  /* @__PURE__ */ (0, import_jsx_runtime60.jsxs)(import_jsx_runtime60.Fragment, { children: [
8777
8788
  showAdvancedChip && /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
@@ -8782,6 +8793,7 @@ var FilterSystem = ({
8782
8793
  onFiltersChange: handleAdvancedFiltersChange,
8783
8794
  open: advancedOpen,
8784
8795
  onOpenChange: setAdvancedOpen,
8796
+ collisionBoundary,
8785
8797
  children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
8786
8798
  AdvancedChip,
8787
8799
  {
@@ -8806,7 +8818,8 @@ var FilterSystem = ({
8806
8818
  onUpdate: handleUpdateFilter,
8807
8819
  onPropertyChange: (newProp) => handlePropertyChange(filter.id, newProp),
8808
8820
  onDelete: () => handleDeleteFilter(filter.id),
8809
- onConvertToAdvanced: () => handleConvertToAdvanced(filter.id)
8821
+ onConvertToAdvanced: () => handleConvertToAdvanced(filter.id),
8822
+ hasAdvancedFilters: hasAdvanced
8810
8823
  },
8811
8824
  filter.id
8812
8825
  );
@@ -8832,13 +8845,13 @@ var FilterSystem = ({
8832
8845
  )
8833
8846
  ] })
8834
8847
  ),
8835
- totalCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
8848
+ totalCount > 0 && !isIconOnly && /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(
8836
8849
  "button",
8837
8850
  {
8838
8851
  type: "button",
8839
8852
  onClick: handleClearAll,
8840
- className: "shrink-0 flex items-center gap-sm px-base py-sm min-h-[32px] max-h-[32px] rounded-md cursor-pointer transition-colors hover:bg-[var(--color-accent)]",
8841
- children: isMinimal ? /* @__PURE__ */ (0, import_jsx_runtime60.jsx)(import_icons38.Icon, { icon: import_icons38.faXmarkOutline, size: "sm", className: "text-[var(--color-foreground)]" }) : /* @__PURE__ */ (0, import_jsx_runtime60.jsx)("span", { className: "text-sm font-semibold leading-sm text-[var(--color-foreground)]", children: "Clear" })
8853
+ className: "shrink-0 flex items-center gap-sm px-base py-sm min-h-[32px] max-h-[32px] rounded-md cursor-pointer transition-colors text-btn-ghost-brand-text-default hover:bg-btn-ghost-brand-bg-hover hover:text-btn-ghost-brand-text-hover active:bg-btn-ghost-brand-bg-pressed active:text-btn-ghost-brand-text-pressed",
8854
+ children: /* @__PURE__ */ (0, import_jsx_runtime60.jsx)("span", { className: "text-sm font-semibold leading-sm", children: "Clear" })
8842
8855
  }
8843
8856
  )
8844
8857
  ] }),
@@ -8846,6 +8859,20 @@ var FilterSystem = ({
8846
8859
  ] });
8847
8860
  };
8848
8861
  FilterSystem.displayName = "FilterSystem";
8862
+ function buildFilterSummary(filters, advancedCount, properties) {
8863
+ if (filters.length === 0 && advancedCount === 0) return void 0;
8864
+ const lines = [];
8865
+ for (const f of filters) {
8866
+ const prop = properties.find((p) => p.id === f.propertyId);
8867
+ if (!prop) continue;
8868
+ const val = formatFilterValue(f.value, prop.dynamicOptions);
8869
+ lines.push(val ? `${prop.label} ${f.operator} ${val}` : `${prop.label} ${f.operator ?? ""}`);
8870
+ }
8871
+ if (advancedCount > 0) {
8872
+ lines.push(`+ ${advancedCount} advanced filter${advancedCount > 1 ? "s" : ""}`);
8873
+ }
8874
+ return lines.join("\n");
8875
+ }
8849
8876
  // Annotate the CommonJS export names for ESM import in node:
8850
8877
  0 && (module.exports = {
8851
8878
  AdvancedChip,