@handled-ai/design-system 0.16.1 → 0.16.2

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.
@@ -3,7 +3,7 @@ import * as React from 'react';
3
3
  import { VariantProps } from 'class-variance-authority';
4
4
 
5
5
  declare const badgeVariants: (props?: ({
6
- variant?: "link" | "default" | "secondary" | "destructive" | "outline" | "ghost" | null | undefined;
6
+ variant?: "default" | "secondary" | "destructive" | "outline" | "ghost" | "link" | null | undefined;
7
7
  } & class_variance_authority_types.ClassProp) | undefined) => string;
8
8
  declare function Badge({ className, variant, asChild, ...props }: React.ComponentProps<"span"> & VariantProps<typeof badgeVariants> & {
9
9
  asChild?: boolean;
@@ -3,7 +3,7 @@ import * as React from 'react';
3
3
  import { VariantProps } from 'class-variance-authority';
4
4
 
5
5
  declare const buttonVariants: (props?: ({
6
- variant?: "link" | "default" | "secondary" | "destructive" | "outline" | "ghost" | null | undefined;
6
+ variant?: "default" | "secondary" | "destructive" | "outline" | "ghost" | "link" | null | undefined;
7
7
  size?: "default" | "sm" | "lg" | "icon" | null | undefined;
8
8
  } & class_variance_authority_types.ClassProp) | undefined) => string;
9
9
  declare function Button({ className, variant, size, asChild, ...props }: React.ComponentProps<"button"> & VariantProps<typeof buttonVariants> & {
@@ -121,6 +121,18 @@ function isCompleteCondition(condition, fields) {
121
121
  if (isUnaryOperator(condition.operator)) return true;
122
122
  return condition.value !== null && condition.value !== "";
123
123
  }
124
+ function getConditionsSignature(conditions) {
125
+ return conditions.map((condition) => `${condition.id}:${condition.field}:${condition.operator}:${String(condition.value)}`).join(";");
126
+ }
127
+ function getFieldsSignature(fields) {
128
+ return fields.map((field) => {
129
+ var _a;
130
+ return `${field.id}:${field.type}:${field.label}:${((_a = field.operators) != null ? _a : []).join("|")}`;
131
+ }).join(";");
132
+ }
133
+ function getCommittedConditions(drafts, fields) {
134
+ return drafts.map((condition) => normalizeCondition(condition, fields)).filter((condition) => isCompleteCondition(condition, fields));
135
+ }
124
136
  function getFieldIcon(type) {
125
137
  if (type === "currency") return DollarSign;
126
138
  if (type === "number") return Hash;
@@ -263,26 +275,31 @@ function DataTableConditionFilter({
263
275
  const [drafts, setDrafts] = React.useState(
264
276
  () => conditions.map((condition) => normalizeCondition(condition, fields))
265
277
  );
278
+ const fieldsSignature = React.useMemo(() => getFieldsSignature(fields), [fields]);
279
+ const conditionsSignature = React.useMemo(() => getConditionsSignature(conditions), [conditions]);
280
+ const fieldsRef = React.useRef(fields);
266
281
  React.useEffect(() => {
267
- setDrafts(conditions.map((condition) => normalizeCondition(condition, fields)));
268
- }, [conditions, fields]);
282
+ setDrafts(conditions.map((condition) => normalizeCondition(condition, fieldsRef.current)));
283
+ }, [conditionsSignature]);
284
+ React.useEffect(() => {
285
+ if (fieldsRef.current !== fields) {
286
+ fieldsRef.current = fields;
287
+ setDrafts((current) => current.map((condition) => normalizeCondition(condition, fields)));
288
+ }
289
+ }, [fieldsSignature, fields]);
269
290
  const commitDrafts = React.useCallback(
270
291
  (nextDrafts = drafts) => {
271
- onConditionsChange(
272
- nextDrafts.map((condition) => normalizeCondition(condition, fields)).filter((condition) => isCompleteCondition(condition, fields))
273
- );
292
+ onConditionsChange(getCommittedConditions(nextDrafts, fields));
274
293
  },
275
294
  [drafts, fields, onConditionsChange]
276
295
  );
277
296
  const handleAdd = () => {
278
297
  const firstField = fields[0];
279
298
  if (!firstField) return;
280
- const committedDrafts = drafts.filter(
281
- (condition) => isCompleteCondition(condition, fields)
282
- );
299
+ const committedDrafts = getCommittedConditions(drafts, fields);
283
300
  const nextDrafts = [...committedDrafts, createDraftCondition(firstField)];
284
301
  setDrafts(nextDrafts);
285
- commitDrafts(committedDrafts);
302
+ onConditionsChange(committedDrafts);
286
303
  };
287
304
  const handleUpdate = (index, updated) => {
288
305
  setDrafts((current) => {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/data-table-condition-filter.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport {\n CalendarDays,\n DollarSign,\n Eye,\n Hash,\n MoreHorizontal,\n Plus,\n Trash2,\n Type,\n} from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\nimport { Button } from \"./button\"\nimport { Input } from \"./input\"\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"./select\"\n\n// ── Types ──────────────────────────────────────────────────────\n\nexport type ConditionOperator =\n | \"eq\"\n | \"neq\"\n | \"gt\"\n | \"gte\"\n | \"lt\"\n | \"lte\"\n | \"in\"\n | \"is_null\"\n | \"is_not_null\"\n\nexport interface ConditionFieldDef {\n /** Unique field key (e.g., \"Account_Balance__c\") */\n id: string\n /** Display label (e.g., \"Account Balance\") */\n label: string\n /** Field data type — determines which operators are available and how the value input renders */\n type: \"text\" | \"number\" | \"currency\" | \"date\"\n /** Allowed operators for this field. Defaults based on type if not provided. */\n operators?: ConditionOperator[]\n}\n\nexport interface ConditionFilterValue {\n /** Stable identity — used as React key to avoid stale-state bugs on removal */\n id: string\n field: string\n operator: ConditionOperator\n value: string | number | null\n}\n\ninterface DataTableConditionFilterProps {\n /** Available fields the user can filter on */\n fields: ConditionFieldDef[]\n /** Current active conditions */\n conditions: ConditionFilterValue[]\n /** Called when conditions change (add, update, remove) */\n onConditionsChange: (conditions: ConditionFilterValue[]) => void\n className?: string\n}\n\n// ── Constants ──────────────────────────────────────────────────\n\nconst OPERATOR_LABELS: Record<ConditionOperator, string> = {\n eq: \"=\",\n neq: \"≠\",\n gt: \">\",\n gte: \"≥\",\n lt: \"<\",\n lte: \"≤\",\n in: \"contains\",\n is_null: \"is empty\",\n is_not_null: \"is not empty\",\n}\n\nconst NUMERIC_OPERATORS: ConditionOperator[] = [\n \"eq\",\n \"neq\",\n \"gt\",\n \"gte\",\n \"lt\",\n \"lte\",\n \"is_null\",\n \"is_not_null\",\n]\n\nconst DEFAULT_OPERATORS: Record<ConditionFieldDef[\"type\"], ConditionOperator[]> = {\n text: [\"eq\", \"neq\", \"is_null\", \"is_not_null\"],\n number: NUMERIC_OPERATORS,\n currency: NUMERIC_OPERATORS,\n date: [\"gt\", \"gte\", \"lt\", \"lte\", \"eq\", \"neq\", \"is_null\", \"is_not_null\"],\n}\n\n/** Generate a stable unique ID for a new condition row. */\nfunction generateConditionId(): string {\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n return crypto.randomUUID()\n }\n return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n}\n\n// ── Helpers ────────────────────────────────────────────────────\n\nfunction getOperators(field: ConditionFieldDef): ConditionOperator[] {\n return (field.operators ?? DEFAULT_OPERATORS[field.type]).filter((op) => op !== \"in\")\n}\n\nfunction isUnaryOperator(op: ConditionOperator): boolean {\n return op === \"is_null\" || op === \"is_not_null\"\n}\n\nfunction getDefaultOperator(field: ConditionFieldDef): ConditionOperator {\n return getOperators(field)[0] ?? \"eq\"\n}\n\nfunction createDraftCondition(field: ConditionFieldDef): ConditionFilterValue {\n return {\n id: generateConditionId(),\n field: field.id,\n operator: getDefaultOperator(field),\n value: null,\n }\n}\n\nfunction normalizeCondition(\n condition: ConditionFilterValue,\n fields: ConditionFieldDef[],\n): ConditionFilterValue {\n const field = fields.find((f) => f.id === condition.field) ?? fields[0]\n if (!field) return condition\n\n const operators = getOperators(field)\n const operator = operators.includes(condition.operator)\n ? condition.operator\n : getDefaultOperator(field)\n\n return {\n ...condition,\n field: field.id,\n operator,\n value: isUnaryOperator(operator) ? null : condition.value,\n }\n}\n\nfunction parseConditionValue(\n raw: string,\n fieldType: ConditionFieldDef[\"type\"],\n): string | number | null {\n if (raw === \"\") return null\n if (fieldType === \"number\" || fieldType === \"currency\") {\n const parsed = Number(raw)\n return Number.isNaN(parsed) ? null : parsed\n }\n return raw\n}\n\nfunction isCompleteCondition(\n condition: ConditionFilterValue,\n fields: ConditionFieldDef[],\n): boolean {\n const field = fields.find((f) => f.id === condition.field)\n if (!field) return false\n if (!getOperators(field).includes(condition.operator)) return false\n if (isUnaryOperator(condition.operator)) return true\n return condition.value !== null && condition.value !== \"\"\n}\n\nfunction getFieldIcon(type: ConditionFieldDef[\"type\"]) {\n if (type === \"currency\") return DollarSign\n if (type === \"number\") return Hash\n if (type === \"date\") return CalendarDays\n return Type\n}\n\n// ── Condition Row ──────────────────────────────────────────────\n\ninterface ConditionRowProps {\n condition: ConditionFilterValue\n fields: ConditionFieldDef[]\n index: number\n onChange: (updated: ConditionFilterValue) => void\n onRemove: () => void\n onCommit: () => void\n}\n\nfunction ConditionRow({\n condition,\n fields,\n index,\n onChange,\n onRemove,\n onCommit,\n}: ConditionRowProps) {\n const fieldDef = fields.find((f) => f.id === condition.field) ?? fields[0]\n const operators = getOperators(fieldDef)\n const isUnary = isUnaryOperator(condition.operator)\n const FieldIcon = getFieldIcon(fieldDef.type)\n\n const handleFieldChange = (newFieldId: string) => {\n const newFieldDef = fields.find((f) => f.id === newFieldId) ?? fields[0]\n if (!newFieldDef) return\n onChange({\n ...condition,\n field: newFieldDef.id,\n operator: getDefaultOperator(newFieldDef),\n value: null,\n })\n }\n\n const handleOperatorChange = (newOp: ConditionOperator) => {\n onChange({\n ...condition,\n operator: newOp,\n value: isUnaryOperator(newOp) ? null : condition.value,\n })\n }\n\n const handleValueChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n onChange({\n ...condition,\n value: parseConditionValue(event.target.value, fieldDef.type),\n })\n }\n\n return (\n <div\n className=\"grid grid-cols-[52px_minmax(150px,1fr)_120px_minmax(140px,1fr)_auto] items-center gap-2 rounded-lg border border-border/70 bg-background p-2 shadow-sm\"\n data-slot=\"condition-row\"\n >\n <div className=\"text-xs font-medium text-muted-foreground\">\n {index === 0 ? \"Where\" : \"And\"}\n </div>\n\n <Select value={condition.field} onValueChange={handleFieldChange}>\n <SelectTrigger className=\"h-8 w-full justify-start gap-2\" size=\"sm\">\n <FieldIcon className=\"h-3.5 w-3.5 text-muted-foreground\" />\n <SelectValue placeholder={fieldDef.label} />\n </SelectTrigger>\n <SelectContent>\n {fields.map((field) => {\n const Icon = getFieldIcon(field.type)\n return (\n <SelectItem key={field.id} value={field.id}>\n <span className=\"inline-flex items-center gap-2\">\n <Icon className=\"h-3.5 w-3.5 text-muted-foreground\" />\n {field.label}\n </span>\n </SelectItem>\n )\n })}\n </SelectContent>\n </Select>\n\n <Select\n value={condition.operator}\n onValueChange={(value) => handleOperatorChange(value as ConditionOperator)}\n >\n <SelectTrigger className=\"h-8 w-full\" size=\"sm\">\n <SelectValue placeholder=\"Operator\" />\n </SelectTrigger>\n <SelectContent>\n {operators.map((op) => (\n <SelectItem key={op} value={op}>\n {OPERATOR_LABELS[op]}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n {isUnary ? (\n <div className=\"h-8 rounded-md border border-dashed border-border/70 bg-muted/30 px-3 py-1.5 text-xs text-muted-foreground\">\n No value needed\n </div>\n ) : (\n <div className=\"relative flex items-center\">\n {fieldDef.type === \"currency\" ? (\n <span className=\"pointer-events-none absolute left-2 text-sm text-muted-foreground\">\n $\n </span>\n ) : null}\n <Input\n type={\n fieldDef.type === \"number\" || fieldDef.type === \"currency\"\n ? \"number\"\n : fieldDef.type === \"date\"\n ? \"date\"\n : \"text\"\n }\n value={condition.value != null ? String(condition.value) : \"\"}\n onChange={handleValueChange}\n onClick={(event) => event.stopPropagation()}\n onKeyDown={(event) => {\n event.stopPropagation()\n if (event.key === \"Enter\") {\n onCommit()\n }\n }}\n placeholder={\n fieldDef.type === \"currency\"\n ? \"Amount\"\n : fieldDef.type === \"number\"\n ? \"Enter number...\"\n : fieldDef.type === \"date\"\n ? \"\"\n : \"Enter value...\"\n }\n className={cn(\"h-8\", fieldDef.type === \"currency\" && \"pl-6\")}\n />\n </div>\n )}\n\n <div className=\"flex items-center gap-1\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8 text-muted-foreground\"\n aria-label=\"Toggle condition visibility\"\n onClick={(event) => event.preventDefault()}\n >\n <Eye className=\"size-4\" />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8 text-muted-foreground\"\n aria-label=\"More condition actions\"\n onClick={(event) => event.preventDefault()}\n >\n <MoreHorizontal className=\"size-4\" />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8 text-muted-foreground hover:text-destructive\"\n onClick={onRemove}\n aria-label=\"Remove condition\"\n >\n <Trash2 className=\"size-4\" />\n </Button>\n </div>\n </div>\n )\n}\n\n// ── Main Component ─────────────────────────────────────────────\n\nfunction DataTableConditionFilter({\n fields,\n conditions,\n onConditionsChange,\n className,\n}: DataTableConditionFilterProps) {\n const [drafts, setDrafts] = React.useState<ConditionFilterValue[]>(() =>\n conditions.map((condition) => normalizeCondition(condition, fields)),\n )\n\n React.useEffect(() => {\n setDrafts(conditions.map((condition) => normalizeCondition(condition, fields)))\n }, [conditions, fields])\n\n const commitDrafts = React.useCallback(\n (nextDrafts: ConditionFilterValue[] = drafts) => {\n onConditionsChange(\n nextDrafts\n .map((condition) => normalizeCondition(condition, fields))\n .filter((condition) => isCompleteCondition(condition, fields)),\n )\n },\n [drafts, fields, onConditionsChange],\n )\n\n const handleAdd = () => {\n const firstField = fields[0]\n if (!firstField) return\n const committedDrafts = drafts.filter((condition) =>\n isCompleteCondition(condition, fields),\n )\n const nextDrafts = [...committedDrafts, createDraftCondition(firstField)]\n setDrafts(nextDrafts)\n commitDrafts(committedDrafts)\n }\n\n const handleUpdate = (index: number, updated: ConditionFilterValue) => {\n setDrafts((current) => {\n const next = [...current]\n next[index] = normalizeCondition(updated, fields)\n return next\n })\n }\n\n const handleRemove = (index: number) => {\n setDrafts((current) => {\n const next = current.filter((_, currentIndex) => currentIndex !== index)\n commitDrafts(next)\n return next\n })\n }\n\n const handleClear = () => {\n setDrafts([])\n onConditionsChange([])\n }\n\n const hasAppliedConditions = conditions.length > 0\n const hasDrafts = drafts.length > 0\n\n return (\n <div\n className={cn(\n \"w-[min(760px,calc(100vw-2rem))] rounded-xl border border-border bg-background p-3 text-foreground shadow-xl\",\n className,\n )}\n data-slot=\"condition-filter\"\n onClick={(event) => event.stopPropagation()}\n onKeyDown={(event) => {\n if (event.key !== \"Escape\") {\n event.stopPropagation()\n }\n }}\n >\n <div className=\"mb-3 flex items-center justify-between gap-3 border-b border-border/70 pb-3\">\n <div>\n <div className=\"text-sm font-semibold\">Filter builder</div>\n <div className=\"text-xs text-muted-foreground\">\n Build field, operator, and value conditions.\n </div>\n </div>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 text-xs text-destructive hover:text-destructive\"\n onClick={handleClear}\n disabled={!hasAppliedConditions && !hasDrafts}\n >\n Clear filters\n </Button>\n </div>\n\n <div className=\"flex flex-col gap-2\">\n {drafts.map((condition, index) => (\n <ConditionRow\n key={condition.id}\n condition={condition}\n fields={fields}\n index={index}\n onChange={(updated) => handleUpdate(index, updated)}\n onRemove={() => handleRemove(index)}\n onCommit={() => commitDrafts()}\n />\n ))}\n </div>\n\n {!hasDrafts ? (\n <div className=\"rounded-lg border border-dashed border-border/80 bg-muted/20 px-3 py-5 text-center text-xs text-muted-foreground\">\n No builder filters yet. Add a filter to start a condition row.\n </div>\n ) : null}\n\n <div className=\"mt-3 flex flex-wrap items-center justify-between gap-2 border-t border-border/70 pt-3\">\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 text-xs text-muted-foreground\"\n onClick={handleAdd}\n >\n <Plus className=\"mr-1 size-3.5\" />\n Add filter\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 text-xs text-muted-foreground opacity-60\"\n disabled\n aria-disabled=\"true\"\n >\n <Plus className=\"mr-1 size-3.5\" />\n Add filter group\n </Button>\n </div>\n <Button\n type=\"button\"\n size=\"sm\"\n className=\"h-8 text-xs\"\n onClick={() => commitDrafts()}\n >\n Apply\n </Button>\n </div>\n </div>\n )\n}\n\nexport {\n DataTableConditionFilter,\n OPERATOR_LABELS,\n DEFAULT_OPERATORS,\n generateConditionId,\n getOperators,\n}\nexport type { DataTableConditionFilterProps }\n"],"mappings":"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","names":["_a"]}
1
+ {"version":3,"sources":["../../src/components/data-table-condition-filter.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport {\n CalendarDays,\n DollarSign,\n Eye,\n Hash,\n MoreHorizontal,\n Plus,\n Trash2,\n Type,\n} from \"lucide-react\"\n\nimport { cn } from \"../lib/utils\"\nimport { Button } from \"./button\"\nimport { Input } from \"./input\"\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"./select\"\n\n// ── Types ──────────────────────────────────────────────────────\n\nexport type ConditionOperator =\n | \"eq\"\n | \"neq\"\n | \"gt\"\n | \"gte\"\n | \"lt\"\n | \"lte\"\n | \"in\"\n | \"is_null\"\n | \"is_not_null\"\n\nexport interface ConditionFieldDef {\n /** Unique field key (e.g., \"Account_Balance__c\") */\n id: string\n /** Display label (e.g., \"Account Balance\") */\n label: string\n /** Field data type — determines which operators are available and how the value input renders */\n type: \"text\" | \"number\" | \"currency\" | \"date\"\n /** Allowed operators for this field. Defaults based on type if not provided. */\n operators?: ConditionOperator[]\n}\n\nexport interface ConditionFilterValue {\n /** Stable identity — used as React key to avoid stale-state bugs on removal */\n id: string\n field: string\n operator: ConditionOperator\n value: string | number | null\n}\n\ninterface DataTableConditionFilterProps {\n /** Available fields the user can filter on */\n fields: ConditionFieldDef[]\n /** Current active conditions */\n conditions: ConditionFilterValue[]\n /** Called when conditions change (add, update, remove) */\n onConditionsChange: (conditions: ConditionFilterValue[]) => void\n className?: string\n}\n\n// ── Constants ──────────────────────────────────────────────────\n\nconst OPERATOR_LABELS: Record<ConditionOperator, string> = {\n eq: \"=\",\n neq: \"≠\",\n gt: \">\",\n gte: \"≥\",\n lt: \"<\",\n lte: \"≤\",\n in: \"contains\",\n is_null: \"is empty\",\n is_not_null: \"is not empty\",\n}\n\nconst NUMERIC_OPERATORS: ConditionOperator[] = [\n \"eq\",\n \"neq\",\n \"gt\",\n \"gte\",\n \"lt\",\n \"lte\",\n \"is_null\",\n \"is_not_null\",\n]\n\nconst DEFAULT_OPERATORS: Record<ConditionFieldDef[\"type\"], ConditionOperator[]> = {\n text: [\"eq\", \"neq\", \"is_null\", \"is_not_null\"],\n number: NUMERIC_OPERATORS,\n currency: NUMERIC_OPERATORS,\n date: [\"gt\", \"gte\", \"lt\", \"lte\", \"eq\", \"neq\", \"is_null\", \"is_not_null\"],\n}\n\n/** Generate a stable unique ID for a new condition row. */\nfunction generateConditionId(): string {\n if (typeof crypto !== \"undefined\" && typeof crypto.randomUUID === \"function\") {\n return crypto.randomUUID()\n }\n return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`\n}\n\n// ── Helpers ────────────────────────────────────────────────────\n\nfunction getOperators(field: ConditionFieldDef): ConditionOperator[] {\n return (field.operators ?? DEFAULT_OPERATORS[field.type]).filter((op) => op !== \"in\")\n}\n\nfunction isUnaryOperator(op: ConditionOperator): boolean {\n return op === \"is_null\" || op === \"is_not_null\"\n}\n\nfunction getDefaultOperator(field: ConditionFieldDef): ConditionOperator {\n return getOperators(field)[0] ?? \"eq\"\n}\n\nfunction createDraftCondition(field: ConditionFieldDef): ConditionFilterValue {\n return {\n id: generateConditionId(),\n field: field.id,\n operator: getDefaultOperator(field),\n value: null,\n }\n}\n\nfunction normalizeCondition(\n condition: ConditionFilterValue,\n fields: ConditionFieldDef[],\n): ConditionFilterValue {\n const field = fields.find((f) => f.id === condition.field) ?? fields[0]\n if (!field) return condition\n\n const operators = getOperators(field)\n const operator = operators.includes(condition.operator)\n ? condition.operator\n : getDefaultOperator(field)\n\n return {\n ...condition,\n field: field.id,\n operator,\n value: isUnaryOperator(operator) ? null : condition.value,\n }\n}\n\nfunction parseConditionValue(\n raw: string,\n fieldType: ConditionFieldDef[\"type\"],\n): string | number | null {\n if (raw === \"\") return null\n if (fieldType === \"number\" || fieldType === \"currency\") {\n const parsed = Number(raw)\n return Number.isNaN(parsed) ? null : parsed\n }\n return raw\n}\n\nfunction isCompleteCondition(\n condition: ConditionFilterValue,\n fields: ConditionFieldDef[],\n): boolean {\n const field = fields.find((f) => f.id === condition.field)\n if (!field) return false\n if (!getOperators(field).includes(condition.operator)) return false\n if (isUnaryOperator(condition.operator)) return true\n return condition.value !== null && condition.value !== \"\"\n}\n\nfunction getConditionsSignature(conditions: ConditionFilterValue[]): string {\n return conditions\n .map((condition) => `${condition.id}:${condition.field}:${condition.operator}:${String(condition.value)}`)\n .join(\";\")\n}\n\nfunction getFieldsSignature(fields: ConditionFieldDef[]): string {\n return fields\n .map((field) => `${field.id}:${field.type}:${field.label}:${(field.operators ?? []).join(\"|\")}`)\n .join(\";\")\n}\n\nfunction getCommittedConditions(\n drafts: ConditionFilterValue[],\n fields: ConditionFieldDef[],\n): ConditionFilterValue[] {\n return drafts\n .map((condition) => normalizeCondition(condition, fields))\n .filter((condition) => isCompleteCondition(condition, fields))\n}\n\nfunction getFieldIcon(type: ConditionFieldDef[\"type\"]) {\n if (type === \"currency\") return DollarSign\n if (type === \"number\") return Hash\n if (type === \"date\") return CalendarDays\n return Type\n}\n\n// ── Condition Row ──────────────────────────────────────────────\n\ninterface ConditionRowProps {\n condition: ConditionFilterValue\n fields: ConditionFieldDef[]\n index: number\n onChange: (updated: ConditionFilterValue) => void\n onRemove: () => void\n onCommit: () => void\n}\n\nfunction ConditionRow({\n condition,\n fields,\n index,\n onChange,\n onRemove,\n onCommit,\n}: ConditionRowProps) {\n const fieldDef = fields.find((f) => f.id === condition.field) ?? fields[0]\n const operators = getOperators(fieldDef)\n const isUnary = isUnaryOperator(condition.operator)\n const FieldIcon = getFieldIcon(fieldDef.type)\n\n const handleFieldChange = (newFieldId: string) => {\n const newFieldDef = fields.find((f) => f.id === newFieldId) ?? fields[0]\n if (!newFieldDef) return\n onChange({\n ...condition,\n field: newFieldDef.id,\n operator: getDefaultOperator(newFieldDef),\n value: null,\n })\n }\n\n const handleOperatorChange = (newOp: ConditionOperator) => {\n onChange({\n ...condition,\n operator: newOp,\n value: isUnaryOperator(newOp) ? null : condition.value,\n })\n }\n\n const handleValueChange = (event: React.ChangeEvent<HTMLInputElement>) => {\n onChange({\n ...condition,\n value: parseConditionValue(event.target.value, fieldDef.type),\n })\n }\n\n return (\n <div\n className=\"grid grid-cols-[52px_minmax(150px,1fr)_120px_minmax(140px,1fr)_auto] items-center gap-2 rounded-lg border border-border/70 bg-background p-2 shadow-sm\"\n data-slot=\"condition-row\"\n >\n <div className=\"text-xs font-medium text-muted-foreground\">\n {index === 0 ? \"Where\" : \"And\"}\n </div>\n\n <Select value={condition.field} onValueChange={handleFieldChange}>\n <SelectTrigger className=\"h-8 w-full justify-start gap-2\" size=\"sm\">\n <FieldIcon className=\"h-3.5 w-3.5 text-muted-foreground\" />\n <SelectValue placeholder={fieldDef.label} />\n </SelectTrigger>\n <SelectContent>\n {fields.map((field) => {\n const Icon = getFieldIcon(field.type)\n return (\n <SelectItem key={field.id} value={field.id}>\n <span className=\"inline-flex items-center gap-2\">\n <Icon className=\"h-3.5 w-3.5 text-muted-foreground\" />\n {field.label}\n </span>\n </SelectItem>\n )\n })}\n </SelectContent>\n </Select>\n\n <Select\n value={condition.operator}\n onValueChange={(value) => handleOperatorChange(value as ConditionOperator)}\n >\n <SelectTrigger className=\"h-8 w-full\" size=\"sm\">\n <SelectValue placeholder=\"Operator\" />\n </SelectTrigger>\n <SelectContent>\n {operators.map((op) => (\n <SelectItem key={op} value={op}>\n {OPERATOR_LABELS[op]}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n {isUnary ? (\n <div className=\"h-8 rounded-md border border-dashed border-border/70 bg-muted/30 px-3 py-1.5 text-xs text-muted-foreground\">\n No value needed\n </div>\n ) : (\n <div className=\"relative flex items-center\">\n {fieldDef.type === \"currency\" ? (\n <span className=\"pointer-events-none absolute left-2 text-sm text-muted-foreground\">\n $\n </span>\n ) : null}\n <Input\n type={\n fieldDef.type === \"number\" || fieldDef.type === \"currency\"\n ? \"number\"\n : fieldDef.type === \"date\"\n ? \"date\"\n : \"text\"\n }\n value={condition.value != null ? String(condition.value) : \"\"}\n onChange={handleValueChange}\n onClick={(event) => event.stopPropagation()}\n onKeyDown={(event) => {\n event.stopPropagation()\n if (event.key === \"Enter\") {\n onCommit()\n }\n }}\n placeholder={\n fieldDef.type === \"currency\"\n ? \"Amount\"\n : fieldDef.type === \"number\"\n ? \"Enter number...\"\n : fieldDef.type === \"date\"\n ? \"\"\n : \"Enter value...\"\n }\n className={cn(\"h-8\", fieldDef.type === \"currency\" && \"pl-6\")}\n />\n </div>\n )}\n\n <div className=\"flex items-center gap-1\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8 text-muted-foreground\"\n aria-label=\"Toggle condition visibility\"\n onClick={(event) => event.preventDefault()}\n >\n <Eye className=\"size-4\" />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8 text-muted-foreground\"\n aria-label=\"More condition actions\"\n onClick={(event) => event.preventDefault()}\n >\n <MoreHorizontal className=\"size-4\" />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8 text-muted-foreground hover:text-destructive\"\n onClick={onRemove}\n aria-label=\"Remove condition\"\n >\n <Trash2 className=\"size-4\" />\n </Button>\n </div>\n </div>\n )\n}\n\n// ── Main Component ─────────────────────────────────────────────\n\nfunction DataTableConditionFilter({\n fields,\n conditions,\n onConditionsChange,\n className,\n}: DataTableConditionFilterProps) {\n const [drafts, setDrafts] = React.useState<ConditionFilterValue[]>(() =>\n conditions.map((condition) => normalizeCondition(condition, fields)),\n )\n\n const fieldsSignature = React.useMemo(() => getFieldsSignature(fields), [fields])\n const conditionsSignature = React.useMemo(() => getConditionsSignature(conditions), [conditions])\n const fieldsRef = React.useRef(fields)\n\n React.useEffect(() => {\n setDrafts(conditions.map((condition) => normalizeCondition(condition, fieldsRef.current)))\n }, [conditionsSignature])\n\n React.useEffect(() => {\n if (fieldsRef.current !== fields) {\n fieldsRef.current = fields\n setDrafts((current) => current.map((condition) => normalizeCondition(condition, fields)))\n }\n // Depend on a structural signature so inline-but-equivalent field arrays do\n // not wipe in-progress drafts before Apply.\n }, [fieldsSignature, fields])\n\n const commitDrafts = React.useCallback(\n (nextDrafts: ConditionFilterValue[] = drafts) => {\n onConditionsChange(getCommittedConditions(nextDrafts, fields))\n },\n [drafts, fields, onConditionsChange],\n )\n\n const handleAdd = () => {\n const firstField = fields[0]\n if (!firstField) return\n const committedDrafts = getCommittedConditions(drafts, fields)\n const nextDrafts = [...committedDrafts, createDraftCondition(firstField)]\n setDrafts(nextDrafts)\n onConditionsChange(committedDrafts)\n }\n\n const handleUpdate = (index: number, updated: ConditionFilterValue) => {\n setDrafts((current) => {\n const next = [...current]\n next[index] = normalizeCondition(updated, fields)\n return next\n })\n }\n\n const handleRemove = (index: number) => {\n setDrafts((current) => {\n const next = current.filter((_, currentIndex) => currentIndex !== index)\n commitDrafts(next)\n return next\n })\n }\n\n const handleClear = () => {\n setDrafts([])\n onConditionsChange([])\n }\n\n const hasAppliedConditions = conditions.length > 0\n const hasDrafts = drafts.length > 0\n\n return (\n <div\n className={cn(\n \"w-[min(760px,calc(100vw-2rem))] rounded-xl border border-border bg-background p-3 text-foreground shadow-xl\",\n className,\n )}\n data-slot=\"condition-filter\"\n onClick={(event) => event.stopPropagation()}\n onKeyDown={(event) => {\n if (event.key !== \"Escape\") {\n event.stopPropagation()\n }\n }}\n >\n <div className=\"mb-3 flex items-center justify-between gap-3 border-b border-border/70 pb-3\">\n <div>\n <div className=\"text-sm font-semibold\">Filter builder</div>\n <div className=\"text-xs text-muted-foreground\">\n Build field, operator, and value conditions.\n </div>\n </div>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 text-xs text-destructive hover:text-destructive\"\n onClick={handleClear}\n disabled={!hasAppliedConditions && !hasDrafts}\n >\n Clear filters\n </Button>\n </div>\n\n <div className=\"flex flex-col gap-2\">\n {drafts.map((condition, index) => (\n <ConditionRow\n key={condition.id}\n condition={condition}\n fields={fields}\n index={index}\n onChange={(updated) => handleUpdate(index, updated)}\n onRemove={() => handleRemove(index)}\n onCommit={() => commitDrafts()}\n />\n ))}\n </div>\n\n {!hasDrafts ? (\n <div className=\"rounded-lg border border-dashed border-border/80 bg-muted/20 px-3 py-5 text-center text-xs text-muted-foreground\">\n No builder filters yet. Add a filter to start a condition row.\n </div>\n ) : null}\n\n <div className=\"mt-3 flex flex-wrap items-center justify-between gap-2 border-t border-border/70 pt-3\">\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 text-xs text-muted-foreground\"\n onClick={handleAdd}\n >\n <Plus className=\"mr-1 size-3.5\" />\n Add filter\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-8 text-xs text-muted-foreground opacity-60\"\n disabled\n aria-disabled=\"true\"\n >\n <Plus className=\"mr-1 size-3.5\" />\n Add filter group\n </Button>\n </div>\n <Button\n type=\"button\"\n size=\"sm\"\n className=\"h-8 text-xs\"\n onClick={() => commitDrafts()}\n >\n Apply\n </Button>\n </div>\n </div>\n )\n}\n\nexport {\n DataTableConditionFilter,\n OPERATOR_LABELS,\n DEFAULT_OPERATORS,\n generateConditionId,\n getOperators,\n}\nexport type { DataTableConditionFilterProps }\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAgQM,cAKE,YALF;AA9PN,YAAY,WAAW;AACvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,SAAS,UAAU;AACnB,SAAS,cAAc;AACvB,SAAS,aAAa;AACtB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AA8CP,MAAM,kBAAqD;AAAA,EACzD,IAAI;AAAA,EACJ,KAAK;AAAA,EACL,IAAI;AAAA,EACJ,KAAK;AAAA,EACL,IAAI;AAAA,EACJ,KAAK;AAAA,EACL,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,aAAa;AACf;AAEA,MAAM,oBAAyC;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,MAAM,oBAA4E;AAAA,EAChF,MAAM,CAAC,MAAM,OAAO,WAAW,aAAa;AAAA,EAC5C,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,MAAM,CAAC,MAAM,OAAO,MAAM,OAAO,MAAM,OAAO,WAAW,aAAa;AACxE;AAGA,SAAS,sBAA8B;AACrC,MAAI,OAAO,WAAW,eAAe,OAAO,OAAO,eAAe,YAAY;AAC5E,WAAO,OAAO,WAAW;AAAA,EAC3B;AACA,SAAO,GAAG,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAChE;AAIA,SAAS,aAAa,OAA+C;AA7GrE;AA8GE,WAAQ,WAAM,cAAN,YAAmB,kBAAkB,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,OAAO,IAAI;AACtF;AAEA,SAAS,gBAAgB,IAAgC;AACvD,SAAO,OAAO,aAAa,OAAO;AACpC;AAEA,SAAS,mBAAmB,OAA6C;AArHzE;AAsHE,UAAO,kBAAa,KAAK,EAAE,CAAC,MAArB,YAA0B;AACnC;AAEA,SAAS,qBAAqB,OAAgD;AAC5E,SAAO;AAAA,IACL,IAAI,oBAAoB;AAAA,IACxB,OAAO,MAAM;AAAA,IACb,UAAU,mBAAmB,KAAK;AAAA,IAClC,OAAO;AAAA,EACT;AACF;AAEA,SAAS,mBACP,WACA,QACsB;AArIxB;AAsIE,QAAM,SAAQ,YAAO,KAAK,CAAC,MAAM,EAAE,OAAO,UAAU,KAAK,MAA3C,YAAgD,OAAO,CAAC;AACtE,MAAI,CAAC,MAAO,QAAO;AAEnB,QAAM,YAAY,aAAa,KAAK;AACpC,QAAM,WAAW,UAAU,SAAS,UAAU,QAAQ,IAClD,UAAU,WACV,mBAAmB,KAAK;AAE5B,SAAO,iCACF,YADE;AAAA,IAEL,OAAO,MAAM;AAAA,IACb;AAAA,IACA,OAAO,gBAAgB,QAAQ,IAAI,OAAO,UAAU;AAAA,EACtD;AACF;AAEA,SAAS,oBACP,KACA,WACwB;AACxB,MAAI,QAAQ,GAAI,QAAO;AACvB,MAAI,cAAc,YAAY,cAAc,YAAY;AACtD,UAAM,SAAS,OAAO,GAAG;AACzB,WAAO,OAAO,MAAM,MAAM,IAAI,OAAO;AAAA,EACvC;AACA,SAAO;AACT;AAEA,SAAS,oBACP,WACA,QACS;AACT,QAAM,QAAQ,OAAO,KAAK,CAAC,MAAM,EAAE,OAAO,UAAU,KAAK;AACzD,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI,CAAC,aAAa,KAAK,EAAE,SAAS,UAAU,QAAQ,EAAG,QAAO;AAC9D,MAAI,gBAAgB,UAAU,QAAQ,EAAG,QAAO;AAChD,SAAO,UAAU,UAAU,QAAQ,UAAU,UAAU;AACzD;AAEA,SAAS,uBAAuB,YAA4C;AAC1E,SAAO,WACJ,IAAI,CAAC,cAAc,GAAG,UAAU,EAAE,IAAI,UAAU,KAAK,IAAI,UAAU,QAAQ,IAAI,OAAO,UAAU,KAAK,CAAC,EAAE,EACxG,KAAK,GAAG;AACb;AAEA,SAAS,mBAAmB,QAAqC;AAC/D,SAAO,OACJ,IAAI,CAAC,UAAO;AArLjB;AAqLoB,cAAG,MAAM,EAAE,IAAI,MAAM,IAAI,IAAI,MAAM,KAAK,MAAK,WAAM,cAAN,YAAmB,CAAC,GAAG,KAAK,GAAG,CAAC;AAAA,GAAE,EAC9F,KAAK,GAAG;AACb;AAEA,SAAS,uBACP,QACA,QACwB;AACxB,SAAO,OACJ,IAAI,CAAC,cAAc,mBAAmB,WAAW,MAAM,CAAC,EACxD,OAAO,CAAC,cAAc,oBAAoB,WAAW,MAAM,CAAC;AACjE;AAEA,SAAS,aAAa,MAAiC;AACrD,MAAI,SAAS,WAAY,QAAO;AAChC,MAAI,SAAS,SAAU,QAAO;AAC9B,MAAI,SAAS,OAAQ,QAAO;AAC5B,SAAO;AACT;AAaA,SAAS,aAAa;AAAA,EACpB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAsB;AA3NtB;AA4NE,QAAM,YAAW,YAAO,KAAK,CAAC,MAAM,EAAE,OAAO,UAAU,KAAK,MAA3C,YAAgD,OAAO,CAAC;AACzE,QAAM,YAAY,aAAa,QAAQ;AACvC,QAAM,UAAU,gBAAgB,UAAU,QAAQ;AAClD,QAAM,YAAY,aAAa,SAAS,IAAI;AAE5C,QAAM,oBAAoB,CAAC,eAAuB;AAjOpD,QAAAA;AAkOI,UAAM,eAAcA,MAAA,OAAO,KAAK,CAAC,MAAM,EAAE,OAAO,UAAU,MAAtC,OAAAA,MAA2C,OAAO,CAAC;AACvE,QAAI,CAAC,YAAa;AAClB,aAAS,iCACJ,YADI;AAAA,MAEP,OAAO,YAAY;AAAA,MACnB,UAAU,mBAAmB,WAAW;AAAA,MACxC,OAAO;AAAA,IACT,EAAC;AAAA,EACH;AAEA,QAAM,uBAAuB,CAAC,UAA6B;AACzD,aAAS,iCACJ,YADI;AAAA,MAEP,UAAU;AAAA,MACV,OAAO,gBAAgB,KAAK,IAAI,OAAO,UAAU;AAAA,IACnD,EAAC;AAAA,EACH;AAEA,QAAM,oBAAoB,CAAC,UAA+C;AACxE,aAAS,iCACJ,YADI;AAAA,MAEP,OAAO,oBAAoB,MAAM,OAAO,OAAO,SAAS,IAAI;AAAA,IAC9D,EAAC;AAAA,EACH;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,aAAU;AAAA,MAEV;AAAA,4BAAC,SAAI,WAAU,6CACZ,oBAAU,IAAI,UAAU,OAC3B;AAAA,QAEA,qBAAC,UAAO,OAAO,UAAU,OAAO,eAAe,mBAC7C;AAAA,+BAAC,iBAAc,WAAU,kCAAiC,MAAK,MAC7D;AAAA,gCAAC,aAAU,WAAU,qCAAoC;AAAA,YACzD,oBAAC,eAAY,aAAa,SAAS,OAAO;AAAA,aAC5C;AAAA,UACA,oBAAC,iBACE,iBAAO,IAAI,CAAC,UAAU;AACrB,kBAAM,OAAO,aAAa,MAAM,IAAI;AACpC,mBACE,oBAAC,cAA0B,OAAO,MAAM,IACtC,+BAAC,UAAK,WAAU,kCACd;AAAA,kCAAC,QAAK,WAAU,qCAAoC;AAAA,cACnD,MAAM;AAAA,eACT,KAJe,MAAM,EAKvB;AAAA,UAEJ,CAAC,GACH;AAAA,WACF;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO,UAAU;AAAA,YACjB,eAAe,CAAC,UAAU,qBAAqB,KAA0B;AAAA,YAEzE;AAAA,kCAAC,iBAAc,WAAU,cAAa,MAAK,MACzC,8BAAC,eAAY,aAAY,YAAW,GACtC;AAAA,cACA,oBAAC,iBACE,oBAAU,IAAI,CAAC,OACd,oBAAC,cAAoB,OAAO,IACzB,0BAAgB,EAAE,KADJ,EAEjB,CACD,GACH;AAAA;AAAA;AAAA,QACF;AAAA,QAEC,UACC,oBAAC,SAAI,WAAU,8GAA6G,6BAE5H,IAEA,qBAAC,SAAI,WAAU,8BACZ;AAAA,mBAAS,SAAS,aACjB,oBAAC,UAAK,WAAU,qEAAoE,eAEpF,IACE;AAAA,UACJ;AAAA,YAAC;AAAA;AAAA,cACC,MACE,SAAS,SAAS,YAAY,SAAS,SAAS,aAC5C,WACA,SAAS,SAAS,SAChB,SACA;AAAA,cAER,OAAO,UAAU,SAAS,OAAO,OAAO,UAAU,KAAK,IAAI;AAAA,cAC3D,UAAU;AAAA,cACV,SAAS,CAAC,UAAU,MAAM,gBAAgB;AAAA,cAC1C,WAAW,CAAC,UAAU;AACpB,sBAAM,gBAAgB;AACtB,oBAAI,MAAM,QAAQ,SAAS;AACzB,2BAAS;AAAA,gBACX;AAAA,cACF;AAAA,cACA,aACE,SAAS,SAAS,aACd,WACA,SAAS,SAAS,WAChB,oBACA,SAAS,SAAS,SAChB,KACA;AAAA,cAEV,WAAW,GAAG,OAAO,SAAS,SAAS,cAAc,MAAM;AAAA;AAAA,UAC7D;AAAA,WACF;AAAA,QAGF,qBAAC,SAAI,WAAU,2BACb;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,WAAU;AAAA,cACV,cAAW;AAAA,cACX,SAAS,CAAC,UAAU,MAAM,eAAe;AAAA,cAEzC,8BAAC,OAAI,WAAU,UAAS;AAAA;AAAA,UAC1B;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,WAAU;AAAA,cACV,cAAW;AAAA,cACX,SAAS,CAAC,UAAU,MAAM,eAAe;AAAA,cAEzC,8BAAC,kBAAe,WAAU,UAAS;AAAA;AAAA,UACrC;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,WAAU;AAAA,cACV,SAAS;AAAA,cACT,cAAW;AAAA,cAEX,8BAAC,UAAO,WAAU,UAAS;AAAA;AAAA,UAC7B;AAAA,WACF;AAAA;AAAA;AAAA,EACF;AAEJ;AAIA,SAAS,yBAAyB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAkC;AAChC,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM;AAAA,IAAiC,MACjE,WAAW,IAAI,CAAC,cAAc,mBAAmB,WAAW,MAAM,CAAC;AAAA,EACrE;AAEA,QAAM,kBAAkB,MAAM,QAAQ,MAAM,mBAAmB,MAAM,GAAG,CAAC,MAAM,CAAC;AAChF,QAAM,sBAAsB,MAAM,QAAQ,MAAM,uBAAuB,UAAU,GAAG,CAAC,UAAU,CAAC;AAChG,QAAM,YAAY,MAAM,OAAO,MAAM;AAErC,QAAM,UAAU,MAAM;AACpB,cAAU,WAAW,IAAI,CAAC,cAAc,mBAAmB,WAAW,UAAU,OAAO,CAAC,CAAC;AAAA,EAC3F,GAAG,CAAC,mBAAmB,CAAC;AAExB,QAAM,UAAU,MAAM;AACpB,QAAI,UAAU,YAAY,QAAQ;AAChC,gBAAU,UAAU;AACpB,gBAAU,CAAC,YAAY,QAAQ,IAAI,CAAC,cAAc,mBAAmB,WAAW,MAAM,CAAC,CAAC;AAAA,IAC1F;AAAA,EAGF,GAAG,CAAC,iBAAiB,MAAM,CAAC;AAE5B,QAAM,eAAe,MAAM;AAAA,IACzB,CAAC,aAAqC,WAAW;AAC/C,yBAAmB,uBAAuB,YAAY,MAAM,CAAC;AAAA,IAC/D;AAAA,IACA,CAAC,QAAQ,QAAQ,kBAAkB;AAAA,EACrC;AAEA,QAAM,YAAY,MAAM;AACtB,UAAM,aAAa,OAAO,CAAC;AAC3B,QAAI,CAAC,WAAY;AACjB,UAAM,kBAAkB,uBAAuB,QAAQ,MAAM;AAC7D,UAAM,aAAa,CAAC,GAAG,iBAAiB,qBAAqB,UAAU,CAAC;AACxE,cAAU,UAAU;AACpB,uBAAmB,eAAe;AAAA,EACpC;AAEA,QAAM,eAAe,CAAC,OAAe,YAAkC;AACrE,cAAU,CAAC,YAAY;AACrB,YAAM,OAAO,CAAC,GAAG,OAAO;AACxB,WAAK,KAAK,IAAI,mBAAmB,SAAS,MAAM;AAChD,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAEA,QAAM,eAAe,CAAC,UAAkB;AACtC,cAAU,CAAC,YAAY;AACrB,YAAM,OAAO,QAAQ,OAAO,CAAC,GAAG,iBAAiB,iBAAiB,KAAK;AACvE,mBAAa,IAAI;AACjB,aAAO;AAAA,IACT,CAAC;AAAA,EACH;AAEA,QAAM,cAAc,MAAM;AACxB,cAAU,CAAC,CAAC;AACZ,uBAAmB,CAAC,CAAC;AAAA,EACvB;AAEA,QAAM,uBAAuB,WAAW,SAAS;AACjD,QAAM,YAAY,OAAO,SAAS;AAElC,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACA,aAAU;AAAA,MACV,SAAS,CAAC,UAAU,MAAM,gBAAgB;AAAA,MAC1C,WAAW,CAAC,UAAU;AACpB,YAAI,MAAM,QAAQ,UAAU;AAC1B,gBAAM,gBAAgB;AAAA,QACxB;AAAA,MACF;AAAA,MAEA;AAAA,6BAAC,SAAI,WAAU,+EACb;AAAA,+BAAC,SACC;AAAA,gCAAC,SAAI,WAAU,yBAAwB,4BAAc;AAAA,YACrD,oBAAC,SAAI,WAAU,iCAAgC,0DAE/C;AAAA,aACF;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,WAAU;AAAA,cACV,SAAS;AAAA,cACT,UAAU,CAAC,wBAAwB,CAAC;AAAA,cACrC;AAAA;AAAA,UAED;AAAA,WACF;AAAA,QAEA,oBAAC,SAAI,WAAU,uBACZ,iBAAO,IAAI,CAAC,WAAW,UACtB;AAAA,UAAC;AAAA;AAAA,YAEC;AAAA,YACA;AAAA,YACA;AAAA,YACA,UAAU,CAAC,YAAY,aAAa,OAAO,OAAO;AAAA,YAClD,UAAU,MAAM,aAAa,KAAK;AAAA,YAClC,UAAU,MAAM,aAAa;AAAA;AAAA,UANxB,UAAU;AAAA,QAOjB,CACD,GACH;AAAA,QAEC,CAAC,YACA,oBAAC,SAAI,WAAU,oHAAmH,4EAElI,IACE;AAAA,QAEJ,qBAAC,SAAI,WAAU,yFACb;AAAA,+BAAC,SAAI,WAAU,2BACb;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,SAAQ;AAAA,gBACR,MAAK;AAAA,gBACL,WAAU;AAAA,gBACV,SAAS;AAAA,gBAET;AAAA,sCAAC,QAAK,WAAU,iBAAgB;AAAA,kBAAE;AAAA;AAAA;AAAA,YAEpC;AAAA,YACA;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,SAAQ;AAAA,gBACR,MAAK;AAAA,gBACL,WAAU;AAAA,gBACV,UAAQ;AAAA,gBACR,iBAAc;AAAA,gBAEd;AAAA,sCAAC,QAAK,WAAU,iBAAgB;AAAA,kBAAE;AAAA;AAAA;AAAA,YAEpC;AAAA,aACF;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,MAAK;AAAA,cACL,WAAU;AAAA,cACV,SAAS,MAAM,aAAa;AAAA,cAC7B;AAAA;AAAA,UAED;AAAA,WACF;AAAA;AAAA;AAAA,EACF;AAEJ;","names":["_a"]}
@@ -84,23 +84,12 @@ function DataTableFilter({
84
84
  [presetFilters]
85
85
  );
86
86
  const activeCount = React.useMemo(() => {
87
- var _a;
88
87
  const userCount = Object.values(selectedFilters).reduce(
89
88
  (count, selected) => count + selected.length,
90
89
  0
91
90
  );
92
- const presetCount = 0;
93
- if (presetFilters) {
94
- for (const [categoryId, presetValues] of Object.entries(presetFilters)) {
95
- for (const value of presetValues) {
96
- if ((_a = selectedFilters[categoryId]) == null ? void 0 : _a.includes(value)) {
97
- } else {
98
- }
99
- }
100
- }
101
- }
102
- return userCount + presetCount + conditionFilters.length;
103
- }, [selectedFilters, presetFilters, conditionFilters.length]);
91
+ return userCount + conditionFilters.length;
92
+ }, [selectedFilters, conditionFilters.length]);
104
93
  const presetChips = React.useMemo(() => {
105
94
  var _a, _b;
106
95
  if (!presetFilters) return [];
@@ -275,7 +264,7 @@ function DataTableFilter({
275
264
  align: "start",
276
265
  side: "right",
277
266
  sideOffset: 8,
278
- className: "z-60 outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
267
+ className: "z-50 outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
279
268
  onEscapeKeyDown: () => setConditionBuilderOpen(false),
280
269
  onInteractOutside: (event) => {
281
270
  const target = event.target;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/data-table-filter.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { Check, ListFilter, Plus, Search } from \"lucide-react\"\n\nimport { Popover as PopoverPrimitive } from \"radix-ui\"\n\nimport { cn } from \"../lib/utils\"\nimport { Button } from \"./button\"\nimport {\n DataTableConditionFilter,\n type ConditionFieldDef,\n type ConditionFilterValue,\n} from \"./data-table-condition-filter\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuTrigger,\n} from \"./dropdown-menu\"\n\nexport interface FilterOption {\n label: string\n value: string\n}\n\nexport interface DataTableFilterCategory {\n id: string\n label: string\n icon: React.ComponentType<{ className?: string }>\n options: (string | FilterOption)[]\n /** Filter behavior. Defaults to \"multi\" (checkbox multi-select). */\n type?: \"multi\" | \"single\" | \"boolean\"\n}\n\nfunction getOptionValue(option: string | FilterOption): string {\n return typeof option === \"string\" ? option : option.value\n}\nfunction getOptionLabel(option: string | FilterOption): string {\n return typeof option === \"string\" ? option : option.label\n}\n\nexport interface DataTableFilterProps {\n categories: DataTableFilterCategory[]\n selectedFilters: Record<string, string[]>\n onToggleFilter: (categoryId: string, option: string) => void\n className?: string\n /** Minimum number of options before showing the sub-menu search input. Defaults to 8. */\n optionSearchThreshold?: number\n /** Filters applied by default. Shown as distinct chips that can be toggled off but not dismissed. */\n presetFilters?: Record<string, string[]>\n /** Callback when a preset filter is toggled on/off. */\n onTogglePreset?: (categoryId: string, option: string) => void\n /** Label shown on preset chips to distinguish from user-applied filters. Default: \"Default\". */\n presetLabel?: string\n /** Fields exposed in the unified condition-builder panel. */\n conditionFields?: ConditionFieldDef[]\n /** Active builder-managed field/operator/value conditions. */\n conditionFilters?: ConditionFilterValue[]\n /** Callback when builder-managed conditions are applied, removed, or cleared. */\n onConditionFiltersChange?: (conditions: ConditionFilterValue[]) => void\n /** Dropdown entry label for the condition-builder panel. Default: \"Add filter\". */\n conditionBuilderLabel?: string\n}\n\nexport function DataTableFilter({\n categories,\n selectedFilters,\n onToggleFilter,\n className,\n optionSearchThreshold = 8,\n presetFilters,\n onTogglePreset,\n presetLabel = \"Default\",\n conditionFields = [],\n conditionFilters = [],\n onConditionFiltersChange,\n conditionBuilderLabel = \"Add filter\",\n}: DataTableFilterProps) {\n const [query, setQuery] = React.useState(\"\")\n const [subQueries, setSubQueries] = React.useState<Record<string, string>>({})\n const [conditionBuilderOpen, setConditionBuilderOpen] = React.useState(false)\n const hasConditionBuilder = conditionFields.length > 0\n\n const visibleCategories = React.useMemo(() => {\n const normalized = query.trim().toLowerCase()\n if (!normalized) {\n return categories\n }\n\n return categories.filter((category) => {\n if (category.label.toLowerCase().includes(normalized)) {\n return true\n }\n\n return category.options.some((option) =>\n getOptionLabel(option).toLowerCase().includes(normalized)\n )\n })\n }, [categories, query])\n\n /** Check if a specific option is a preset filter */\n const isPresetOption = React.useCallback(\n (categoryId: string, value: string): boolean => {\n return presetFilters?.[categoryId]?.includes(value) ?? false\n },\n [presetFilters]\n )\n\n const activeCount = React.useMemo(() => {\n // Count user-selected filters\n const userCount = Object.values(selectedFilters).reduce(\n (count, selected) => count + selected.length,\n 0\n )\n\n // Count active preset filters (those that are in presetFilters AND currently active in selectedFilters)\n const presetCount = 0\n if (presetFilters) {\n for (const [categoryId, presetValues] of Object.entries(presetFilters)) {\n for (const value of presetValues) {\n // Only count if the preset is active (in selectedFilters) but NOT already counted as a user filter\n if (selectedFilters[categoryId]?.includes(value)) {\n // Already counted in userCount, skip\n } else {\n // Not in selectedFilters — it's an inactive preset, don't count\n }\n }\n }\n }\n\n return userCount + presetCount + conditionFilters.length\n }, [selectedFilters, presetFilters, conditionFilters.length])\n\n /** Collect all preset chips to render */\n const presetChips = React.useMemo(() => {\n if (!presetFilters) return []\n\n const chips: { categoryId: string; value: string; label: string; active: boolean }[] = []\n\n for (const [categoryId, values] of Object.entries(presetFilters)) {\n const category = categories.find((c) => c.id === categoryId)\n for (const value of values) {\n const option = category?.options.find(\n (opt) => getOptionValue(opt) === value\n )\n const label = option ? getOptionLabel(option) : value\n const active = selectedFilters[categoryId]?.includes(value) ?? false\n chips.push({ categoryId, value, label, active })\n }\n }\n\n return chips\n }, [presetFilters, categories, selectedFilters])\n\n const triggerButton = (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"outline\"\n size=\"sm\"\n className={cn(\n \"h-8 gap-2 rounded-md border-border/60 bg-background text-xs font-normal shadow-none hover:bg-muted/50\",\n className\n )}\n >\n <ListFilter className=\"h-3.5 w-3.5\" />\n Filter\n {activeCount > 0 ? (\n <span className=\"rounded bg-muted px-1.5 py-0 text-[10px] font-semibold\">\n {activeCount}\n </span>\n ) : null}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-[240px] p-0\">\n <div className=\"sticky top-0 z-10 border-b border-border bg-popover p-2\">\n <div className=\"relative\">\n <Search className=\"absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground\" />\n <input\n className=\"h-8 w-full rounded-md bg-muted/50 py-1 pr-2 pl-7 text-xs outline-none transition-colors placeholder:text-muted-foreground/70 focus:bg-muted\"\n placeholder=\"Search filters...\"\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n onClick={(event) => event.stopPropagation()}\n onKeyDown={(event) => event.stopPropagation()}\n />\n </div>\n </div>\n\n <div className=\"max-h-[320px] overflow-y-auto p-1\">\n {visibleCategories.map((category) => {\n const filterType = category.type ?? \"multi\"\n\n /* ── Boolean toggle ─────────────────────────────────── */\n if (filterType === \"boolean\") {\n const active = selectedFilters[category.id]?.includes(\"true\") ?? false\n return (\n <DropdownMenuItem\n key={category.id}\n className={cn(\n \"cursor-pointer py-1.5 text-xs\",\n active && \"text-brand-purple\"\n )}\n onSelect={(event) => {\n event.preventDefault()\n onToggleFilter(category.id, \"true\")\n }}\n >\n <category.icon className=\"mr-2 h-3.5 w-3.5\" />\n {category.label}\n {active ? <Check className=\"ml-auto h-4 w-4\" /> : null}\n </DropdownMenuItem>\n )\n }\n\n /* ── Sub-menu (single / multi) ──────────────────────── */\n const subQuery = (subQueries[category.id] ?? \"\").trim().toLowerCase()\n const filteredOptions = subQuery\n ? category.options.filter((opt) =>\n getOptionLabel(opt).toLowerCase().includes(subQuery)\n )\n : category.options\n\n return (\n <DropdownMenuSub\n key={category.id}\n onOpenChange={(open) => {\n if (!open) {\n setSubQueries((prev) => {\n const next = { ...prev }\n delete next[category.id]\n return next\n })\n }\n }}\n >\n <DropdownMenuSubTrigger className=\"cursor-pointer py-1.5 text-xs\">\n <category.icon className=\"mr-2 h-3.5 w-3.5 text-muted-foreground\" />\n {category.label}\n </DropdownMenuSubTrigger>\n <DropdownMenuSubContent className=\"max-h-[320px] w-52 overflow-y-auto p-1\">\n {/* Submenu search — only for categories with many options */}\n {category.options.length > optionSearchThreshold && (\n <div className=\"sticky top-0 z-10 border-b border-border bg-popover p-1.5\">\n <div className=\"relative\">\n <Search className=\"absolute left-2 top-1/2 h-3 w-3 -translate-y-1/2 text-muted-foreground\" />\n <input\n className=\"h-7 w-full rounded-md bg-muted/50 py-1 pr-2 pl-7 text-xs outline-none transition-colors placeholder:text-muted-foreground/70 focus:bg-muted\"\n placeholder=\"Search...\"\n value={subQueries[category.id] ?? \"\"}\n onChange={(e) =>\n setSubQueries((prev) => ({ ...prev, [category.id]: e.target.value }))\n }\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => {\n // Allow navigation keys to propagate to Radix menu handling\n // so keyboard users can move to and select filtered options.\n const navKeys = [\"ArrowDown\", \"ArrowUp\", \"Enter\", \"Escape\", \"Tab\"]\n if (!navKeys.includes(e.key)) {\n e.stopPropagation()\n }\n }}\n />\n </div>\n </div>\n )}\n {/* Filtered options */}\n {filteredOptions.map((option) => {\n const value = getOptionValue(option)\n const label = getOptionLabel(option)\n const selected = selectedFilters[category.id]?.includes(value) ?? false\n const isPreset = isPresetOption(category.id, value)\n return (\n <DropdownMenuItem\n key={value}\n className=\"cursor-pointer justify-between text-xs\"\n onSelect={(event) => {\n event.preventDefault()\n onToggleFilter(category.id, value)\n }}\n >\n {label}\n {selected ? (\n isPreset ? (\n <span className=\"text-brand-purple text-[10px] font-semibold\">\n {presetLabel}\n </span>\n ) : filterType === \"single\" ? (\n <span className=\"h-1.5 w-1.5 rounded-full bg-current\" />\n ) : (\n <span className=\"text-[10px] font-semibold text-brand-purple\">\n Applied\n </span>\n )\n ) : null}\n </DropdownMenuItem>\n )\n })}\n {filteredOptions.length === 0 && category.options.length > 0 && (\n <div className=\"p-2 text-center text-xs text-muted-foreground\">\n No matches\n </div>\n )}\n </DropdownMenuSubContent>\n </DropdownMenuSub>\n )\n })}\n\n {visibleCategories.length === 0 ? (\n <div className=\"p-2 text-center text-xs text-muted-foreground\">\n No filters found\n </div>\n ) : null}\n </div>\n\n {hasConditionBuilder ? (\n <div className=\"border-t border-border p-1\">\n <PopoverPrimitive.Root\n open={conditionBuilderOpen}\n onOpenChange={setConditionBuilderOpen}\n >\n <PopoverPrimitive.Trigger asChild>\n <DropdownMenuItem\n className=\"cursor-pointer py-1.5 text-xs\"\n onSelect={(event) => {\n event.preventDefault()\n setConditionBuilderOpen(true)\n }}\n >\n <Plus className=\"mr-2 h-3.5 w-3.5 text-muted-foreground\" />\n {conditionBuilderLabel}\n {conditionFilters.length > 0 ? (\n <span className=\"ml-auto rounded bg-muted px-1.5 py-0 text-[10px] font-semibold\">\n {conditionFilters.length}\n </span>\n ) : null}\n </DropdownMenuItem>\n </PopoverPrimitive.Trigger>\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n align=\"start\"\n side=\"right\"\n sideOffset={8}\n className=\"z-60 outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95\"\n onEscapeKeyDown={() => setConditionBuilderOpen(false)}\n onInteractOutside={(event) => {\n const target = event.target as HTMLElement | null\n if (target?.closest('[data-slot=\"dropdown-menu-content\"]')) {\n event.preventDefault()\n }\n }}\n >\n <DataTableConditionFilter\n fields={conditionFields}\n conditions={conditionFilters}\n onConditionsChange={onConditionFiltersChange ?? (() => {})}\n />\n </PopoverPrimitive.Content>\n </PopoverPrimitive.Portal>\n </PopoverPrimitive.Root>\n </div>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n\n // If there are preset chips, wrap trigger + chips together\n if (presetChips.length > 0) {\n return (\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {triggerButton}\n {presetChips.map((chip) => (\n <button\n key={`${chip.categoryId}-${chip.value}`}\n type=\"button\"\n onClick={() => onTogglePreset?.(chip.categoryId, chip.value)}\n className={cn(\n \"inline-flex items-center gap-1 rounded-md border px-2 py-0.5 text-[11px] font-medium transition-colors\",\n chip.active\n ? \"border-dashed border-brand-purple/30 bg-brand-purple/5 text-brand-purple/80\"\n : \"border-border/40 bg-transparent text-muted-foreground/60 line-through\"\n )}\n >\n <span className=\"text-brand-purple/50 text-[10px]\">\n {presetLabel}:{\" \"}\n </span>\n {chip.label}\n </button>\n ))}\n </div>\n )\n }\n\n return triggerButton\n}\n"],"mappings":"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","names":["_a","_b"]}
1
+ {"version":3,"sources":["../../src/components/data-table-filter.tsx"],"sourcesContent":["\"use client\"\n\nimport * as React from \"react\"\nimport { Check, ListFilter, Plus, Search } from \"lucide-react\"\n\nimport { Popover as PopoverPrimitive } from \"radix-ui\"\n\nimport { cn } from \"../lib/utils\"\nimport { Button } from \"./button\"\nimport {\n DataTableConditionFilter,\n type ConditionFieldDef,\n type ConditionFilterValue,\n} from \"./data-table-condition-filter\"\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSub,\n DropdownMenuSubContent,\n DropdownMenuSubTrigger,\n DropdownMenuTrigger,\n} from \"./dropdown-menu\"\n\nexport interface FilterOption {\n label: string\n value: string\n}\n\nexport interface DataTableFilterCategory {\n id: string\n label: string\n icon: React.ComponentType<{ className?: string }>\n options: (string | FilterOption)[]\n /** Filter behavior. Defaults to \"multi\" (checkbox multi-select). */\n type?: \"multi\" | \"single\" | \"boolean\"\n}\n\nfunction getOptionValue(option: string | FilterOption): string {\n return typeof option === \"string\" ? option : option.value\n}\nfunction getOptionLabel(option: string | FilterOption): string {\n return typeof option === \"string\" ? option : option.label\n}\n\nexport interface DataTableFilterProps {\n categories: DataTableFilterCategory[]\n selectedFilters: Record<string, string[]>\n onToggleFilter: (categoryId: string, option: string) => void\n className?: string\n /** Minimum number of options before showing the sub-menu search input. Defaults to 8. */\n optionSearchThreshold?: number\n /** Filters applied by default. Shown as distinct chips that can be toggled off but not dismissed. */\n presetFilters?: Record<string, string[]>\n /** Callback when a preset filter is toggled on/off. */\n onTogglePreset?: (categoryId: string, option: string) => void\n /** Label shown on preset chips to distinguish from user-applied filters. Default: \"Default\". */\n presetLabel?: string\n /** Fields exposed in the unified condition-builder panel. */\n conditionFields?: ConditionFieldDef[]\n /** Active builder-managed field/operator/value conditions. */\n conditionFilters?: ConditionFilterValue[]\n /** Callback when builder-managed conditions are applied, removed, or cleared. */\n onConditionFiltersChange?: (conditions: ConditionFilterValue[]) => void\n /** Dropdown entry label for the condition-builder panel. Default: \"Add filter\". */\n conditionBuilderLabel?: string\n}\n\nexport function DataTableFilter({\n categories,\n selectedFilters,\n onToggleFilter,\n className,\n optionSearchThreshold = 8,\n presetFilters,\n onTogglePreset,\n presetLabel = \"Default\",\n conditionFields = [],\n conditionFilters = [],\n onConditionFiltersChange,\n conditionBuilderLabel = \"Add filter\",\n}: DataTableFilterProps) {\n const [query, setQuery] = React.useState(\"\")\n const [subQueries, setSubQueries] = React.useState<Record<string, string>>({})\n const [conditionBuilderOpen, setConditionBuilderOpen] = React.useState(false)\n const hasConditionBuilder = conditionFields.length > 0\n\n const visibleCategories = React.useMemo(() => {\n const normalized = query.trim().toLowerCase()\n if (!normalized) {\n return categories\n }\n\n return categories.filter((category) => {\n if (category.label.toLowerCase().includes(normalized)) {\n return true\n }\n\n return category.options.some((option) =>\n getOptionLabel(option).toLowerCase().includes(normalized)\n )\n })\n }, [categories, query])\n\n /** Check if a specific option is a preset filter */\n const isPresetOption = React.useCallback(\n (categoryId: string, value: string): boolean => {\n return presetFilters?.[categoryId]?.includes(value) ?? false\n },\n [presetFilters]\n )\n\n const activeCount = React.useMemo(() => {\n const userCount = Object.values(selectedFilters).reduce(\n (count, selected) => count + selected.length,\n 0\n )\n\n return userCount + conditionFilters.length\n }, [selectedFilters, conditionFilters.length])\n\n /** Collect all preset chips to render */\n const presetChips = React.useMemo(() => {\n if (!presetFilters) return []\n\n const chips: { categoryId: string; value: string; label: string; active: boolean }[] = []\n\n for (const [categoryId, values] of Object.entries(presetFilters)) {\n const category = categories.find((c) => c.id === categoryId)\n for (const value of values) {\n const option = category?.options.find(\n (opt) => getOptionValue(opt) === value\n )\n const label = option ? getOptionLabel(option) : value\n const active = selectedFilters[categoryId]?.includes(value) ?? false\n chips.push({ categoryId, value, label, active })\n }\n }\n\n return chips\n }, [presetFilters, categories, selectedFilters])\n\n const triggerButton = (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"outline\"\n size=\"sm\"\n className={cn(\n \"h-8 gap-2 rounded-md border-border/60 bg-background text-xs font-normal shadow-none hover:bg-muted/50\",\n className\n )}\n >\n <ListFilter className=\"h-3.5 w-3.5\" />\n Filter\n {activeCount > 0 ? (\n <span className=\"rounded bg-muted px-1.5 py-0 text-[10px] font-semibold\">\n {activeCount}\n </span>\n ) : null}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"start\" className=\"w-[240px] p-0\">\n <div className=\"sticky top-0 z-10 border-b border-border bg-popover p-2\">\n <div className=\"relative\">\n <Search className=\"absolute left-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground\" />\n <input\n className=\"h-8 w-full rounded-md bg-muted/50 py-1 pr-2 pl-7 text-xs outline-none transition-colors placeholder:text-muted-foreground/70 focus:bg-muted\"\n placeholder=\"Search filters...\"\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n onClick={(event) => event.stopPropagation()}\n onKeyDown={(event) => event.stopPropagation()}\n />\n </div>\n </div>\n\n <div className=\"max-h-[320px] overflow-y-auto p-1\">\n {visibleCategories.map((category) => {\n const filterType = category.type ?? \"multi\"\n\n /* ── Boolean toggle ─────────────────────────────────── */\n if (filterType === \"boolean\") {\n const active = selectedFilters[category.id]?.includes(\"true\") ?? false\n return (\n <DropdownMenuItem\n key={category.id}\n className={cn(\n \"cursor-pointer py-1.5 text-xs\",\n active && \"text-brand-purple\"\n )}\n onSelect={(event) => {\n event.preventDefault()\n onToggleFilter(category.id, \"true\")\n }}\n >\n <category.icon className=\"mr-2 h-3.5 w-3.5\" />\n {category.label}\n {active ? <Check className=\"ml-auto h-4 w-4\" /> : null}\n </DropdownMenuItem>\n )\n }\n\n /* ── Sub-menu (single / multi) ──────────────────────── */\n const subQuery = (subQueries[category.id] ?? \"\").trim().toLowerCase()\n const filteredOptions = subQuery\n ? category.options.filter((opt) =>\n getOptionLabel(opt).toLowerCase().includes(subQuery)\n )\n : category.options\n\n return (\n <DropdownMenuSub\n key={category.id}\n onOpenChange={(open) => {\n if (!open) {\n setSubQueries((prev) => {\n const next = { ...prev }\n delete next[category.id]\n return next\n })\n }\n }}\n >\n <DropdownMenuSubTrigger className=\"cursor-pointer py-1.5 text-xs\">\n <category.icon className=\"mr-2 h-3.5 w-3.5 text-muted-foreground\" />\n {category.label}\n </DropdownMenuSubTrigger>\n <DropdownMenuSubContent className=\"max-h-[320px] w-52 overflow-y-auto p-1\">\n {/* Submenu search — only for categories with many options */}\n {category.options.length > optionSearchThreshold && (\n <div className=\"sticky top-0 z-10 border-b border-border bg-popover p-1.5\">\n <div className=\"relative\">\n <Search className=\"absolute left-2 top-1/2 h-3 w-3 -translate-y-1/2 text-muted-foreground\" />\n <input\n className=\"h-7 w-full rounded-md bg-muted/50 py-1 pr-2 pl-7 text-xs outline-none transition-colors placeholder:text-muted-foreground/70 focus:bg-muted\"\n placeholder=\"Search...\"\n value={subQueries[category.id] ?? \"\"}\n onChange={(e) =>\n setSubQueries((prev) => ({ ...prev, [category.id]: e.target.value }))\n }\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => {\n // Allow navigation keys to propagate to Radix menu handling\n // so keyboard users can move to and select filtered options.\n const navKeys = [\"ArrowDown\", \"ArrowUp\", \"Enter\", \"Escape\", \"Tab\"]\n if (!navKeys.includes(e.key)) {\n e.stopPropagation()\n }\n }}\n />\n </div>\n </div>\n )}\n {/* Filtered options */}\n {filteredOptions.map((option) => {\n const value = getOptionValue(option)\n const label = getOptionLabel(option)\n const selected = selectedFilters[category.id]?.includes(value) ?? false\n const isPreset = isPresetOption(category.id, value)\n return (\n <DropdownMenuItem\n key={value}\n className=\"cursor-pointer justify-between text-xs\"\n onSelect={(event) => {\n event.preventDefault()\n onToggleFilter(category.id, value)\n }}\n >\n {label}\n {selected ? (\n isPreset ? (\n <span className=\"text-brand-purple text-[10px] font-semibold\">\n {presetLabel}\n </span>\n ) : filterType === \"single\" ? (\n <span className=\"h-1.5 w-1.5 rounded-full bg-current\" />\n ) : (\n <span className=\"text-[10px] font-semibold text-brand-purple\">\n Applied\n </span>\n )\n ) : null}\n </DropdownMenuItem>\n )\n })}\n {filteredOptions.length === 0 && category.options.length > 0 && (\n <div className=\"p-2 text-center text-xs text-muted-foreground\">\n No matches\n </div>\n )}\n </DropdownMenuSubContent>\n </DropdownMenuSub>\n )\n })}\n\n {visibleCategories.length === 0 ? (\n <div className=\"p-2 text-center text-xs text-muted-foreground\">\n No filters found\n </div>\n ) : null}\n </div>\n\n {hasConditionBuilder ? (\n <div className=\"border-t border-border p-1\">\n <PopoverPrimitive.Root\n open={conditionBuilderOpen}\n onOpenChange={setConditionBuilderOpen}\n >\n <PopoverPrimitive.Trigger asChild>\n <DropdownMenuItem\n className=\"cursor-pointer py-1.5 text-xs\"\n onSelect={(event) => {\n event.preventDefault()\n setConditionBuilderOpen(true)\n }}\n >\n <Plus className=\"mr-2 h-3.5 w-3.5 text-muted-foreground\" />\n {conditionBuilderLabel}\n {conditionFilters.length > 0 ? (\n <span className=\"ml-auto rounded bg-muted px-1.5 py-0 text-[10px] font-semibold\">\n {conditionFilters.length}\n </span>\n ) : null}\n </DropdownMenuItem>\n </PopoverPrimitive.Trigger>\n <PopoverPrimitive.Portal>\n <PopoverPrimitive.Content\n align=\"start\"\n side=\"right\"\n sideOffset={8}\n className=\"z-50 outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95\"\n onEscapeKeyDown={() => setConditionBuilderOpen(false)}\n onInteractOutside={(event) => {\n const target = event.target as HTMLElement | null\n if (target?.closest('[data-slot=\"dropdown-menu-content\"]')) {\n event.preventDefault()\n }\n }}\n >\n <DataTableConditionFilter\n fields={conditionFields}\n conditions={conditionFilters}\n onConditionsChange={onConditionFiltersChange ?? (() => {})}\n />\n </PopoverPrimitive.Content>\n </PopoverPrimitive.Portal>\n </PopoverPrimitive.Root>\n </div>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n\n // If there are preset chips, wrap trigger + chips together\n if (presetChips.length > 0) {\n return (\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {triggerButton}\n {presetChips.map((chip) => (\n <button\n key={`${chip.categoryId}-${chip.value}`}\n type=\"button\"\n onClick={() => onTogglePreset?.(chip.categoryId, chip.value)}\n className={cn(\n \"inline-flex items-center gap-1 rounded-md border px-2 py-0.5 text-[11px] font-medium transition-colors\",\n chip.active\n ? \"border-dashed border-brand-purple/30 bg-brand-purple/5 text-brand-purple/80\"\n : \"border-border/40 bg-transparent text-muted-foreground/60 line-through\"\n )}\n >\n <span className=\"text-brand-purple/50 text-[10px]\">\n {presetLabel}:{\" \"}\n </span>\n {chip.label}\n </button>\n ))}\n </div>\n )\n }\n\n return triggerButton\n}\n"],"mappings":"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","names":["_a","_b"]}
@@ -5,7 +5,7 @@ import { Tabs as Tabs$1 } from 'radix-ui';
5
5
 
6
6
  declare function Tabs({ className, orientation, ...props }: React.ComponentProps<typeof Tabs$1.Root>): React.JSX.Element;
7
7
  declare const tabsListVariants: (props?: ({
8
- variant?: "line" | "default" | null | undefined;
8
+ variant?: "default" | "line" | null | undefined;
9
9
  } & class_variance_authority_types.ClassProp) | undefined) => string;
10
10
  declare function TabsList({ className, variant, ...props }: React.ComponentProps<typeof Tabs$1.List> & VariantProps<typeof tabsListVariants>): React.JSX.Element;
11
11
  declare function TabsTrigger({ className, ...props }: React.ComponentProps<typeof Tabs$1.Trigger>): React.JSX.Element;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@handled-ai/design-system",
3
- "version": "0.16.1",
3
+ "version": "0.16.2",
4
4
  "description": "Handled UI component library (shadcn-style, New York)",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@9.12.0",
@@ -151,6 +151,32 @@ describe("DataTableConditionFilter", () => {
151
151
  expect(onConditionsChange).toHaveBeenCalledWith([]);
152
152
  });
153
153
 
154
+
155
+ it("preserves an in-progress draft when fields are recreated with the same definition", () => {
156
+ const { rerender } = render(
157
+ <DataTableConditionFilter
158
+ fields={[...allFields]}
159
+ conditions={[]}
160
+ onConditionsChange={onConditionsChange}
161
+ />,
162
+ );
163
+
164
+ fireEvent.click(screen.getByText("Add filter"));
165
+ fireEvent.change(screen.getByPlaceholderText("Enter value..."), {
166
+ target: { value: "Acme" },
167
+ });
168
+
169
+ rerender(
170
+ <DataTableConditionFilter
171
+ fields={[...allFields]}
172
+ conditions={[]}
173
+ onConditionsChange={onConditionsChange}
174
+ />,
175
+ );
176
+
177
+ expect((screen.getByPlaceholderText("Enter value...") as HTMLInputElement).value).toBe("Acme");
178
+ });
179
+
154
180
  it("commits a text draft with Apply after a value is entered", () => {
155
181
  render(
156
182
  <DataTableConditionFilter
@@ -294,6 +294,27 @@ describe("DataTableFilter", () => {
294
294
  expect(screen.getByText("Filter builder")).toBeDefined();
295
295
  });
296
296
 
297
+
298
+ it("opens the condition builder without closing the menu selection", () => {
299
+ render(
300
+ <DataTableFilter
301
+ {...defaultProps}
302
+ conditionFields={[{ id: "name", label: "Name", type: "text" }]}
303
+ />
304
+ );
305
+
306
+ const builderEntry = document.querySelector('[data-slot="popover-trigger"]');
307
+ expect(builderEntry).not.toBeNull();
308
+
309
+ const preventDefaultSpy = vi.fn();
310
+ const mockEvent = new MouseEvent("click", { bubbles: true });
311
+ Object.defineProperty(mockEvent, "preventDefault", { value: preventDefaultSpy });
312
+ builderEntry!.dispatchEvent(mockEvent);
313
+
314
+ expect(preventDefaultSpy).toHaveBeenCalled();
315
+ expect(screen.getByText("Filter builder")).toBeDefined();
316
+ });
317
+
297
318
  it("uses the custom condition builder label when provided", () => {
298
319
  render(
299
320
  <DataTableFilter
@@ -171,6 +171,27 @@ function isCompleteCondition(
171
171
  return condition.value !== null && condition.value !== ""
172
172
  }
173
173
 
174
+ function getConditionsSignature(conditions: ConditionFilterValue[]): string {
175
+ return conditions
176
+ .map((condition) => `${condition.id}:${condition.field}:${condition.operator}:${String(condition.value)}`)
177
+ .join(";")
178
+ }
179
+
180
+ function getFieldsSignature(fields: ConditionFieldDef[]): string {
181
+ return fields
182
+ .map((field) => `${field.id}:${field.type}:${field.label}:${(field.operators ?? []).join("|")}`)
183
+ .join(";")
184
+ }
185
+
186
+ function getCommittedConditions(
187
+ drafts: ConditionFilterValue[],
188
+ fields: ConditionFieldDef[],
189
+ ): ConditionFilterValue[] {
190
+ return drafts
191
+ .map((condition) => normalizeCondition(condition, fields))
192
+ .filter((condition) => isCompleteCondition(condition, fields))
193
+ }
194
+
174
195
  function getFieldIcon(type: ConditionFieldDef["type"]) {
175
196
  if (type === "currency") return DollarSign
176
197
  if (type === "number") return Hash
@@ -363,17 +384,26 @@ function DataTableConditionFilter({
363
384
  conditions.map((condition) => normalizeCondition(condition, fields)),
364
385
  )
365
386
 
387
+ const fieldsSignature = React.useMemo(() => getFieldsSignature(fields), [fields])
388
+ const conditionsSignature = React.useMemo(() => getConditionsSignature(conditions), [conditions])
389
+ const fieldsRef = React.useRef(fields)
390
+
391
+ React.useEffect(() => {
392
+ setDrafts(conditions.map((condition) => normalizeCondition(condition, fieldsRef.current)))
393
+ }, [conditionsSignature])
394
+
366
395
  React.useEffect(() => {
367
- setDrafts(conditions.map((condition) => normalizeCondition(condition, fields)))
368
- }, [conditions, fields])
396
+ if (fieldsRef.current !== fields) {
397
+ fieldsRef.current = fields
398
+ setDrafts((current) => current.map((condition) => normalizeCondition(condition, fields)))
399
+ }
400
+ // Depend on a structural signature so inline-but-equivalent field arrays do
401
+ // not wipe in-progress drafts before Apply.
402
+ }, [fieldsSignature, fields])
369
403
 
370
404
  const commitDrafts = React.useCallback(
371
405
  (nextDrafts: ConditionFilterValue[] = drafts) => {
372
- onConditionsChange(
373
- nextDrafts
374
- .map((condition) => normalizeCondition(condition, fields))
375
- .filter((condition) => isCompleteCondition(condition, fields)),
376
- )
406
+ onConditionsChange(getCommittedConditions(nextDrafts, fields))
377
407
  },
378
408
  [drafts, fields, onConditionsChange],
379
409
  )
@@ -381,12 +411,10 @@ function DataTableConditionFilter({
381
411
  const handleAdd = () => {
382
412
  const firstField = fields[0]
383
413
  if (!firstField) return
384
- const committedDrafts = drafts.filter((condition) =>
385
- isCompleteCondition(condition, fields),
386
- )
414
+ const committedDrafts = getCommittedConditions(drafts, fields)
387
415
  const nextDrafts = [...committedDrafts, createDraftCondition(firstField)]
388
416
  setDrafts(nextDrafts)
389
- commitDrafts(committedDrafts)
417
+ onConditionsChange(committedDrafts)
390
418
  }
391
419
 
392
420
  const handleUpdate = (index: number, updated: ConditionFilterValue) => {
@@ -111,29 +111,13 @@ export function DataTableFilter({
111
111
  )
112
112
 
113
113
  const activeCount = React.useMemo(() => {
114
- // Count user-selected filters
115
114
  const userCount = Object.values(selectedFilters).reduce(
116
115
  (count, selected) => count + selected.length,
117
116
  0
118
117
  )
119
118
 
120
- // Count active preset filters (those that are in presetFilters AND currently active in selectedFilters)
121
- const presetCount = 0
122
- if (presetFilters) {
123
- for (const [categoryId, presetValues] of Object.entries(presetFilters)) {
124
- for (const value of presetValues) {
125
- // Only count if the preset is active (in selectedFilters) but NOT already counted as a user filter
126
- if (selectedFilters[categoryId]?.includes(value)) {
127
- // Already counted in userCount, skip
128
- } else {
129
- // Not in selectedFilters — it's an inactive preset, don't count
130
- }
131
- }
132
- }
133
- }
134
-
135
- return userCount + presetCount + conditionFilters.length
136
- }, [selectedFilters, presetFilters, conditionFilters.length])
119
+ return userCount + conditionFilters.length
120
+ }, [selectedFilters, conditionFilters.length])
137
121
 
138
122
  /** Collect all preset chips to render */
139
123
  const presetChips = React.useMemo(() => {
@@ -345,7 +329,7 @@ export function DataTableFilter({
345
329
  align="start"
346
330
  side="right"
347
331
  sideOffset={8}
348
- className="z-60 outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95"
332
+ className="z-50 outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95"
349
333
  onEscapeKeyDown={() => setConditionBuilderOpen(false)}
350
334
  onInteractOutside={(event) => {
351
335
  const target = event.target as HTMLElement | null