@lattice-php/lattice 0.9.0 → 0.11.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.
Files changed (91) hide show
  1. package/dist/action/components/action-form.js +1 -1
  2. package/dist/action/components/action-form.js.map +1 -1
  3. package/dist/chat/components/chat-box.js +1 -1
  4. package/dist/chat/components/chat-box.js.map +1 -1
  5. package/dist/chat/components/prompt-input.js +2 -2
  6. package/dist/chat/components/prompt-input.js.map +1 -1
  7. package/dist/core/components/combobox.js +1 -1
  8. package/dist/core/components/combobox.js.map +1 -1
  9. package/dist/core/components/fragment.js +4 -1
  10. package/dist/core/components/fragment.js.map +1 -1
  11. package/dist/core/components/icon.js +4 -1
  12. package/dist/core/components/icon.js.map +1 -1
  13. package/dist/core/components/info-tooltip.js +1 -1
  14. package/dist/core/components/info-tooltip.js.map +1 -1
  15. package/dist/core/components/spinner.js +1 -1
  16. package/dist/core/components/spinner.js.map +1 -1
  17. package/dist/core/components/tabs.js +1 -1
  18. package/dist/core/components/tabs.js.map +1 -1
  19. package/dist/core/components/text.js +4 -1
  20. package/dist/core/components/text.js.map +1 -1
  21. package/dist/form/components/fields/date-input.js +7 -6
  22. package/dist/form/components/fields/date-input.js.map +1 -1
  23. package/dist/form/components/fields/date-picker-control.d.ts +18 -0
  24. package/dist/form/components/fields/date-picker-control.js +199 -0
  25. package/dist/form/components/fields/date-picker-control.js.map +1 -0
  26. package/dist/form/components/fields/date-picker-value.d.ts +10 -0
  27. package/dist/form/components/fields/date-picker-value.js +125 -0
  28. package/dist/form/components/fields/date-picker-value.js.map +1 -0
  29. package/dist/form/components/fields/date-time-input.d.ts +2 -0
  30. package/dist/form/components/fields/date-time-input.js +34 -0
  31. package/dist/form/components/fields/date-time-input.js.map +1 -0
  32. package/dist/form/components/fields/file-upload.js +3 -3
  33. package/dist/form/components/fields/file-upload.js.map +1 -1
  34. package/dist/form/components/fields/lazy-date-picker-control.d.ts +2 -0
  35. package/dist/form/components/fields/lazy-date-picker-control.js +17 -0
  36. package/dist/form/components/fields/lazy-date-picker-control.js.map +1 -0
  37. package/dist/form/components/fields/number-input.js +1 -1
  38. package/dist/form/components/fields/rich-editor.js +3 -3
  39. package/dist/form/components/fields/rich-editor.js.map +1 -1
  40. package/dist/form/components/fields/row-action-menu.js +2 -2
  41. package/dist/form/components/fields/row-action-menu.js.map +1 -1
  42. package/dist/form/components/fields/select.js +1 -1
  43. package/dist/form/components/fields/select.js.map +1 -1
  44. package/dist/form/components/fields/table-rows.js +2 -2
  45. package/dist/form/components/fields/table-rows.js.map +1 -1
  46. package/dist/form/components/fields/text-input.js +1 -1
  47. package/dist/form/components/fields/time-input.d.ts +2 -0
  48. package/dist/form/components/fields/time-input.js +30 -0
  49. package/dist/form/components/fields/time-input.js.map +1 -0
  50. package/dist/form/components/fields/toggle.d.ts +2 -0
  51. package/dist/form/components/fields/toggle.js +65 -0
  52. package/dist/form/components/fields/toggle.js.map +1 -0
  53. package/dist/form/components/index.d.ts +3 -0
  54. package/dist/form/components/index.js +4 -1
  55. package/dist/form/components/types.d.ts +1 -1
  56. package/dist/form/components/use-controlled-field.d.ts +2 -0
  57. package/dist/form/components/use-controlled-field.js +6 -2
  58. package/dist/form/components/use-controlled-field.js.map +1 -1
  59. package/dist/form/index.js +4 -1
  60. package/dist/form/index.js.map +1 -1
  61. package/dist/lattice.css +6 -0
  62. package/dist/layout/callouts.js +1 -1
  63. package/dist/layout/callouts.js.map +1 -1
  64. package/dist/lib/utils.js +11 -1
  65. package/dist/lib/utils.js.map +1 -1
  66. package/dist/remote/components/data-list.js +3 -3
  67. package/dist/remote/components/data-list.js.map +1 -1
  68. package/dist/table/components/bulk-bar.js +2 -2
  69. package/dist/table/components/bulk-bar.js.map +1 -1
  70. package/dist/table/components/column-filter-control.js +5 -5
  71. package/dist/table/components/column-filter-control.js.map +1 -1
  72. package/dist/table/components/filter-bar.js +3 -3
  73. package/dist/table/components/filter-bar.js.map +1 -1
  74. package/dist/table/components/filter-controls.js +9 -9
  75. package/dist/table/components/filter-controls.js.map +1 -1
  76. package/dist/table/components/filter-stack-bar.js +1 -1
  77. package/dist/table/components/filter-stack-bar.js.map +1 -1
  78. package/dist/table/components/filter-value-input.js +5 -5
  79. package/dist/table/components/filter-value-input.js.map +1 -1
  80. package/dist/table/components/pagination.js +8 -8
  81. package/dist/table/components/pagination.js.map +1 -1
  82. package/dist/table/components/table.js +4 -4
  83. package/dist/table/components/table.js.map +1 -1
  84. package/dist/table/query.js +1 -1
  85. package/dist/table/query.js.map +1 -1
  86. package/dist/toast/toaster.js +1 -1
  87. package/dist/toast/toaster.js.map +1 -1
  88. package/dist/types/generated.d.ts +97 -2
  89. package/package.json +8 -2
  90. package/resources/icons/calendar.svg +18 -0
  91. package/resources/icons/chevron-left.svg +15 -0
@@ -1 +1 @@
1
- {"version":3,"file":"callouts.js","names":[],"sources":["../../resources/js/layout/callouts.tsx"],"sourcesContent":["import { Icon } from \"@lattice-php/lattice/icons\";\nimport { useEffect, useState } from \"react\";\nimport { RenderNode } from \"@lattice-php/lattice/core/renderer\";\nimport type { Callout } from \"@lattice-php/lattice/types/generated\";\nimport type { RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { onCallout } from \"@lattice-php/lattice/toast/callout\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\nimport { variantStyles } from \"@lattice-php/lattice/toast/variant-styles\";\n\ntype CalloutItem = Callout & { id: number };\n\nlet nextId = 0;\n\nconst Callouts: RendererComponent<\"callouts\"> = () => {\n const { t } = useT(\"lattice\");\n const [callouts, setCallouts] = useState<CalloutItem[]>([]);\n\n useEffect(\n () =>\n onCallout((callout) => {\n setCallouts((current) => [...current, { ...callout, id: nextId++ }]);\n }),\n [],\n );\n\n function dismiss(id: number): void {\n setCallouts((current) => current.filter((callout) => callout.id !== id));\n }\n\n if (callouts.length === 0) {\n return null;\n }\n\n return (\n <div className=\"flex w-full flex-col gap-2\">\n {callouts.map((callout) => (\n <div\n key={callout.id}\n role=\"status\"\n data-test={`callout-${callout.variant}`}\n className={cn(\n \"flex items-start gap-3 rounded-lt border border-l-4 border-lt-border bg-lt-popover p-4 text-lt-popover-fg\",\n variantStyles[callout.variant].accent,\n )}\n >\n {variantStyles[callout.variant].icon}\n <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n {callout.title ? (\n <p className=\"text-sm font-medium text-lt-fg\">{callout.title}</p>\n ) : null}\n <p className=\"text-sm text-lt-fg\">{callout.message}</p>\n {callout.action ? (\n <div className=\"flex flex-wrap gap-2\">\n <RenderNode node={callout.action} />\n </div>\n ) : null}\n </div>\n {callout.dismissible ? (\n <button\n type=\"button\"\n aria-label={t(\"a11y.dismiss\", \"Dismiss\")}\n data-test=\"callout-dismiss\"\n className=\"shrink-0 rounded-lt-sm p-1 text-lt-muted-fg transition-colors hover:bg-lt-muted hover:text-lt-fg\"\n onClick={() => dismiss(callout.id)}\n >\n <Icon name=\"x\" className=\"size-lt-icon-md\" />\n </button>\n ) : null}\n </div>\n ))}\n </div>\n );\n};\n\nexport default Callouts;\n"],"mappings":";;;;;;;;;AAYA,IAAI,SAAS;AAEb,IAAM,iBAAgD;CACpD,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,MAAM,CAAC,UAAU,eAAe,SAAwB,CAAC,CAAC;CAE1D,gBAEI,WAAW,YAAY;EACrB,aAAa,YAAY,CAAC,GAAG,SAAS;GAAE,GAAG;GAAS,IAAI;EAAS,CAAC,CAAC;CACrE,CAAC,GACH,CAAC,CACH;CAEA,SAAS,QAAQ,IAAkB;EACjC,aAAa,YAAY,QAAQ,QAAQ,YAAY,QAAQ,OAAO,EAAE,CAAC;CACzE;CAEA,IAAI,SAAS,WAAW,GACtB,OAAO;CAGT,OACE,oBAAC,OAAD;EAAK,WAAU;YACZ,SAAS,KAAK,YACb,qBAAC,OAAD;GAEE,MAAK;GACL,aAAW,WAAW,QAAQ;GAC9B,WAAW,GACT,6GACA,cAAc,QAAQ,SAAS,MACjC;aAPF;IASG,cAAc,QAAQ,SAAS;IAChC,qBAAC,OAAD;KAAK,WAAU;eAAf;MACG,QAAQ,QACP,oBAAC,KAAD;OAAG,WAAU;iBAAkC,QAAQ;MAAS,CAAA,IAC9D;MACJ,oBAAC,KAAD;OAAG,WAAU;iBAAsB,QAAQ;MAAW,CAAA;MACrD,QAAQ,SACP,oBAAC,OAAD;OAAK,WAAU;iBACb,oBAAC,YAAD,EAAY,MAAM,QAAQ,OAAS,CAAA;MAChC,CAAA,IACH;KACD;;IACJ,QAAQ,cACP,oBAAC,UAAD;KACE,MAAK;KACL,cAAY,EAAE,gBAAgB,SAAS;KACvC,aAAU;KACV,WAAU;KACV,eAAe,QAAQ,QAAQ,EAAE;eAEjC,oBAAC,MAAD;MAAM,MAAK;MAAI,WAAU;KAAmB,CAAA;IACtC,CAAA,IACN;GACD;KA/BE,QAAQ,EA+BV,CACN;CACE,CAAA;AAET"}
1
+ {"version":3,"file":"callouts.js","names":[],"sources":["../../resources/js/layout/callouts.tsx"],"sourcesContent":["import { Icon } from \"@lattice-php/lattice/icons\";\nimport { useEffect, useState } from \"react\";\nimport { RenderNode } from \"@lattice-php/lattice/core/renderer\";\nimport type { Callout } from \"@lattice-php/lattice/types/generated\";\nimport type { RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { onCallout } from \"@lattice-php/lattice/toast/callout\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\nimport { variantStyles } from \"@lattice-php/lattice/toast/variant-styles\";\n\ntype CalloutItem = Callout & { id: number };\n\nlet nextId = 0;\n\nconst Callouts: RendererComponent<\"callouts\"> = () => {\n const { t } = useT(\"lattice\");\n const [callouts, setCallouts] = useState<CalloutItem[]>([]);\n\n useEffect(\n () =>\n onCallout((callout) => {\n setCallouts((current) => [...current, { ...callout, id: nextId++ }]);\n }),\n [],\n );\n\n function dismiss(id: number): void {\n setCallouts((current) => current.filter((callout) => callout.id !== id));\n }\n\n if (callouts.length === 0) {\n return null;\n }\n\n return (\n <div className=\"flex w-full flex-col gap-2\">\n {callouts.map((callout) => (\n <div\n key={callout.id}\n role=\"status\"\n data-test={`callout-${callout.variant}`}\n className={cn(\n \"flex items-start gap-3 rounded-lt border border-l-4 border-lt-border bg-lt-popover p-4 text-lt-popover-fg\",\n variantStyles[callout.variant].accent,\n )}\n >\n {variantStyles[callout.variant].icon}\n <div className=\"flex min-w-0 flex-1 flex-col gap-2\">\n {callout.title ? (\n <p className=\"text-sm font-medium text-lt-fg\">{callout.title}</p>\n ) : null}\n <p className=\"text-sm text-lt-fg\">{callout.message}</p>\n {callout.action ? (\n <div className=\"flex flex-wrap gap-2\">\n <RenderNode node={callout.action} />\n </div>\n ) : null}\n </div>\n {callout.dismissible ? (\n <button\n type=\"button\"\n aria-label={t(\"common.dismiss\", \"Dismiss\")}\n data-test=\"callout-dismiss\"\n className=\"shrink-0 rounded-lt-sm p-1 text-lt-muted-fg transition-colors hover:bg-lt-muted hover:text-lt-fg\"\n onClick={() => dismiss(callout.id)}\n >\n <Icon name=\"x\" className=\"size-lt-icon-md\" />\n </button>\n ) : null}\n </div>\n ))}\n </div>\n );\n};\n\nexport default Callouts;\n"],"mappings":";;;;;;;;;AAYA,IAAI,SAAS;AAEb,IAAM,iBAAgD;CACpD,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,MAAM,CAAC,UAAU,eAAe,SAAwB,CAAC,CAAC;CAE1D,gBAEI,WAAW,YAAY;EACrB,aAAa,YAAY,CAAC,GAAG,SAAS;GAAE,GAAG;GAAS,IAAI;EAAS,CAAC,CAAC;CACrE,CAAC,GACH,CAAC,CACH;CAEA,SAAS,QAAQ,IAAkB;EACjC,aAAa,YAAY,QAAQ,QAAQ,YAAY,QAAQ,OAAO,EAAE,CAAC;CACzE;CAEA,IAAI,SAAS,WAAW,GACtB,OAAO;CAGT,OACE,oBAAC,OAAD;EAAK,WAAU;YACZ,SAAS,KAAK,YACb,qBAAC,OAAD;GAEE,MAAK;GACL,aAAW,WAAW,QAAQ;GAC9B,WAAW,GACT,6GACA,cAAc,QAAQ,SAAS,MACjC;aAPF;IASG,cAAc,QAAQ,SAAS;IAChC,qBAAC,OAAD;KAAK,WAAU;eAAf;MACG,QAAQ,QACP,oBAAC,KAAD;OAAG,WAAU;iBAAkC,QAAQ;MAAS,CAAA,IAC9D;MACJ,oBAAC,KAAD;OAAG,WAAU;iBAAsB,QAAQ;MAAW,CAAA;MACrD,QAAQ,SACP,oBAAC,OAAD;OAAK,WAAU;iBACb,oBAAC,YAAD,EAAY,MAAM,QAAQ,OAAS,CAAA;MAChC,CAAA,IACH;KACD;;IACJ,QAAQ,cACP,oBAAC,UAAD;KACE,MAAK;KACL,cAAY,EAAE,kBAAkB,SAAS;KACzC,aAAU;KACV,WAAU;KACV,eAAe,QAAQ,QAAQ,EAAE;eAEjC,oBAAC,MAAD;MAAM,MAAK;MAAI,WAAU;KAAmB,CAAA;IACtC,CAAA,IACN;GACD;KA/BE,QAAQ,EA+BV,CACN;CACE,CAAA;AAET"}
package/dist/lib/utils.js CHANGED
@@ -1,6 +1,16 @@
1
1
  import { clsx } from "clsx";
2
- import { twMerge } from "tailwind-merge";
2
+ import { extendTailwindMerge } from "tailwind-merge";
3
3
  //#region resources/js/lib/utils.ts
4
+ var twMerge = extendTailwindMerge({ extend: { classGroups: { "lt-icon-size": [{ size: [{ lt: [{ icon: [
5
+ "xs",
6
+ "sm",
7
+ "md",
8
+ "lg",
9
+ "xl",
10
+ "2xl",
11
+ "3xl",
12
+ "4xl"
13
+ ] }] }] }] } } });
4
14
  function cn(...inputs) {
5
15
  return twMerge(clsx(inputs));
6
16
  }
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","names":[],"sources":["../../resources/js/lib/utils.ts"],"sourcesContent":["import { clsx } from \"clsx\";\nimport type { ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n"],"mappings":";;;AAIA,SAAgB,GAAG,GAAG,QAAsB;CAC1C,OAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B"}
1
+ {"version":3,"file":"utils.js","names":[],"sources":["../../resources/js/lib/utils.ts"],"sourcesContent":["import { clsx } from \"clsx\";\nimport type { ClassValue } from \"clsx\";\nimport { extendTailwindMerge } from \"tailwind-merge\";\n\n// The sprite renderer always applies `size-lt-icon-md` as a baseline, and these\n// custom utilities are invisible to tailwind-merge's stock config, so an explicit\n// `size-lt-icon-*` would otherwise be left to fight the baseline on source order.\n// Registering them as one conflicting group lets the explicit size always win.\nconst twMerge = extendTailwindMerge<\"lt-icon-size\">({\n extend: {\n classGroups: {\n \"lt-icon-size\": [\n { size: [{ lt: [{ icon: [\"xs\", \"sm\", \"md\", \"lg\", \"xl\", \"2xl\", \"3xl\", \"4xl\"] }] }] },\n ],\n },\n },\n});\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs));\n}\n"],"mappings":";;;AAQA,IAAM,UAAU,oBAAoC,EAClD,QAAQ,EACN,aAAa,EACX,gBAAgB,CACd,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,EAAE,MAAM;CAAC;CAAM;CAAM;CAAM;CAAM;CAAM;CAAO;CAAO;AAAK,EAAE,CAAC,EAAE,CAAC,EAAE,CACpF,EACF,EACF,EACF,CAAC;AAED,SAAgB,GAAG,GAAG,QAAsB;CAC1C,OAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B"}
@@ -73,13 +73,13 @@ function RemoteDataList({ props }) {
73
73
  });
74
74
  if (loading || !props.remote) return /* @__PURE__ */ jsx("div", {
75
75
  className: "rounded-lt border border-lt-border p-3 text-sm",
76
- children: t("remote.data-list.loading", "Loading...")
76
+ children: t("common.remote.data-list.loading", "Loading...")
77
77
  });
78
78
  return /* @__PURE__ */ jsxs("div", {
79
79
  className: "rounded-lt border border-lt-border bg-lt-bg",
80
80
  children: [/* @__PURE__ */ jsx("div", {
81
81
  className: "border-b border-lt-border px-3 py-2 text-sm font-medium",
82
- children: t("remote.data-list.title", "Remote data")
82
+ children: t("common.remote.data-list.title", "Remote data")
83
83
  }), /* @__PURE__ */ jsxs("div", {
84
84
  className: "divide-y divide-lt-border",
85
85
  children: [rows.map((row, index) => {
@@ -89,7 +89,7 @@ function RemoteDataList({ props }) {
89
89
  }, label(row.id) || index);
90
90
  }), rows.length === 0 ? /* @__PURE__ */ jsx("div", {
91
91
  className: "px-3 py-2 text-sm text-lt-muted-fg",
92
- children: props.emptyLabel ?? t("remote.data-list.empty", "No data")
92
+ children: props.emptyLabel ?? t("common.remote.data-list.empty", "No data")
93
93
  }) : null]
94
94
  })]
95
95
  });
@@ -1 +1 @@
1
- {"version":3,"file":"data-list.js","names":[],"sources":["../../../resources/js/remote/components/data-list.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { Renderer } from \"@lattice-php/lattice/core/renderer\";\nimport type { Node, NodeProps, RendererComponent, Schema } from \"@lattice-php/lattice/core/types\";\nimport { remoteJson } from \"@lattice-php/lattice/core/api\";\nimport type { DataList as DataListProps } from \"@lattice-php/lattice/types/generated\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\n\ntype RemotePayload = {\n data?: Array<Record<string, unknown>>;\n};\n\ntype RemoteRow = Record<string, unknown>;\n\ntype DataBindings = Record<string, string>;\n\ntype RemoteDataListProps = DataListProps & {\n schema?: Schema;\n};\n\nfunction label(value: unknown): string {\n if (value === null || value === undefined) {\n return \"\";\n }\n\n return String(value);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction dataBindings(value: unknown): DataBindings {\n if (!isRecord(value)) {\n return {};\n }\n\n return Object.fromEntries(\n Object.entries(value).filter((entry): entry is [string, string] => {\n return typeof entry[1] === \"string\";\n }),\n );\n}\n\nfunction rowValue(row: RemoteRow, key: string): unknown {\n if (key in row) {\n return row[key];\n }\n\n return key.split(\".\").reduce<unknown>((value, segment) => {\n return isRecord(value) ? value[segment] : undefined;\n }, row);\n}\n\nfunction materializeProps(props: unknown, row: RemoteRow): NodeProps {\n if (!isRecord(props)) {\n return {};\n }\n\n const { dataBindings: bindings, ...materialized } = props;\n\n for (const [prop, key] of Object.entries(dataBindings(bindings))) {\n const value = rowValue(row, key);\n\n if (value !== undefined) {\n materialized[prop] = value;\n }\n }\n\n return materialized;\n}\n\nfunction materializeNode(node: Node, row: RemoteRow): Node {\n return {\n ...node,\n props: materializeProps(node.props, row),\n schema: node.schema?.map((child) => materializeNode(child, row)),\n } as Node;\n}\n\nfunction materializeSchema(schema: Schema | undefined, row: RemoteRow): Schema {\n return schema?.map((node) => materializeNode(node, row)) ?? [];\n}\n\nexport function RemoteDataList({ props }: { props: RemoteDataListProps }) {\n const { t } = useT(\"lattice\");\n const [rows, setRows] = useState<RemoteRow[]>([]);\n const [error, setError] = useState<string | null>(null);\n const [loading, setLoading] = useState(false);\n\n useEffect(() => {\n if (!props.remote || !props.dataEndpoint) {\n setRows([]);\n setLoading(false);\n\n return;\n }\n\n let cancelled = false;\n setError(null);\n setLoading(true);\n\n void remoteJson<RemotePayload>(props.dataEndpoint, { remote: props.remote })\n .then((payload) => {\n if (!cancelled) {\n setRows(payload.data ?? []);\n }\n })\n .catch((caught: unknown) => {\n if (!cancelled) {\n setError(caught instanceof Error ? caught.message : String(caught));\n }\n })\n .finally(() => {\n if (!cancelled) {\n setLoading(false);\n }\n });\n\n return () => {\n cancelled = true;\n };\n }, [props.dataEndpoint, props.remote]);\n\n if (error) {\n return <div className=\"rounded-lt border border-lt-danger/40 p-3 text-sm\">{error}</div>;\n }\n\n if (loading || !props.remote) {\n return (\n <div className=\"rounded-lt border border-lt-border p-3 text-sm\">\n {t(\"remote.data-list.loading\", \"Loading...\")}\n </div>\n );\n }\n\n return (\n <div className=\"rounded-lt border border-lt-border bg-lt-bg\">\n <div className=\"border-b border-lt-border px-3 py-2 text-sm font-medium\">\n {t(\"remote.data-list.title\", \"Remote data\")}\n </div>\n <div className=\"divide-y divide-lt-border\">\n {rows.map((row, index) => {\n return (\n <div className=\"px-3 py-2\" key={label(row.id) || index}>\n <Renderer nodes={materializeSchema(props.schema, row)} />\n </div>\n );\n })}\n {rows.length === 0 ? (\n <div className=\"px-3 py-2 text-sm text-lt-muted-fg\">\n {props.emptyLabel ?? t(\"remote.data-list.empty\", \"No data\")}\n </div>\n ) : null}\n </div>\n </div>\n );\n}\n\nexport const DataList: RendererComponent<\"remote.data-list\"> = ({ node }) => {\n return <RemoteDataList props={{ ...node.props, schema: node.schema }} />;\n};\n\nexport default DataList;\n"],"mappings":"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"}
1
+ {"version":3,"file":"data-list.js","names":[],"sources":["../../../resources/js/remote/components/data-list.tsx"],"sourcesContent":["import { useEffect, useState } from \"react\";\nimport { Renderer } from \"@lattice-php/lattice/core/renderer\";\nimport type { Node, NodeProps, RendererComponent, Schema } from \"@lattice-php/lattice/core/types\";\nimport { remoteJson } from \"@lattice-php/lattice/core/api\";\nimport type { DataList as DataListProps } from \"@lattice-php/lattice/types/generated\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\n\ntype RemotePayload = {\n data?: Array<Record<string, unknown>>;\n};\n\ntype RemoteRow = Record<string, unknown>;\n\ntype DataBindings = Record<string, string>;\n\ntype RemoteDataListProps = DataListProps & {\n schema?: Schema;\n};\n\nfunction label(value: unknown): string {\n if (value === null || value === undefined) {\n return \"\";\n }\n\n return String(value);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction dataBindings(value: unknown): DataBindings {\n if (!isRecord(value)) {\n return {};\n }\n\n return Object.fromEntries(\n Object.entries(value).filter((entry): entry is [string, string] => {\n return typeof entry[1] === \"string\";\n }),\n );\n}\n\nfunction rowValue(row: RemoteRow, key: string): unknown {\n if (key in row) {\n return row[key];\n }\n\n return key.split(\".\").reduce<unknown>((value, segment) => {\n return isRecord(value) ? value[segment] : undefined;\n }, row);\n}\n\nfunction materializeProps(props: unknown, row: RemoteRow): NodeProps {\n if (!isRecord(props)) {\n return {};\n }\n\n const { dataBindings: bindings, ...materialized } = props;\n\n for (const [prop, key] of Object.entries(dataBindings(bindings))) {\n const value = rowValue(row, key);\n\n if (value !== undefined) {\n materialized[prop] = value;\n }\n }\n\n return materialized;\n}\n\nfunction materializeNode(node: Node, row: RemoteRow): Node {\n return {\n ...node,\n props: materializeProps(node.props, row),\n schema: node.schema?.map((child) => materializeNode(child, row)),\n } as Node;\n}\n\nfunction materializeSchema(schema: Schema | undefined, row: RemoteRow): Schema {\n return schema?.map((node) => materializeNode(node, row)) ?? [];\n}\n\nexport function RemoteDataList({ props }: { props: RemoteDataListProps }) {\n const { t } = useT(\"lattice\");\n const [rows, setRows] = useState<RemoteRow[]>([]);\n const [error, setError] = useState<string | null>(null);\n const [loading, setLoading] = useState(false);\n\n useEffect(() => {\n if (!props.remote || !props.dataEndpoint) {\n setRows([]);\n setLoading(false);\n\n return;\n }\n\n let cancelled = false;\n setError(null);\n setLoading(true);\n\n void remoteJson<RemotePayload>(props.dataEndpoint, { remote: props.remote })\n .then((payload) => {\n if (!cancelled) {\n setRows(payload.data ?? []);\n }\n })\n .catch((caught: unknown) => {\n if (!cancelled) {\n setError(caught instanceof Error ? caught.message : String(caught));\n }\n })\n .finally(() => {\n if (!cancelled) {\n setLoading(false);\n }\n });\n\n return () => {\n cancelled = true;\n };\n }, [props.dataEndpoint, props.remote]);\n\n if (error) {\n return <div className=\"rounded-lt border border-lt-danger/40 p-3 text-sm\">{error}</div>;\n }\n\n if (loading || !props.remote) {\n return (\n <div className=\"rounded-lt border border-lt-border p-3 text-sm\">\n {t(\"common.remote.data-list.loading\", \"Loading...\")}\n </div>\n );\n }\n\n return (\n <div className=\"rounded-lt border border-lt-border bg-lt-bg\">\n <div className=\"border-b border-lt-border px-3 py-2 text-sm font-medium\">\n {t(\"common.remote.data-list.title\", \"Remote data\")}\n </div>\n <div className=\"divide-y divide-lt-border\">\n {rows.map((row, index) => {\n return (\n <div className=\"px-3 py-2\" key={label(row.id) || index}>\n <Renderer nodes={materializeSchema(props.schema, row)} />\n </div>\n );\n })}\n {rows.length === 0 ? (\n <div className=\"px-3 py-2 text-sm text-lt-muted-fg\">\n {props.emptyLabel ?? t(\"common.remote.data-list.empty\", \"No data\")}\n </div>\n ) : null}\n </div>\n </div>\n );\n}\n\nexport const DataList: RendererComponent<\"remote.data-list\"> = ({ node }) => {\n return <RemoteDataList props={{ ...node.props, schema: node.schema }} />;\n};\n\nexport default DataList;\n"],"mappings":"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"}
@@ -49,14 +49,14 @@ function BulkBar({ actions, selectedKeys, allMatching, total, query, canSelectAl
49
49
  children: [
50
50
  /* @__PURE__ */ jsx("span", {
51
51
  className: "font-medium",
52
- children: allMatching ? t("bulk.allSelected", "All {{count}} selected", { count }) : t("bulk.selected", "{{count}} selected", { count })
52
+ children: allMatching ? t("table.bulk.allSelected", "All {{count}} selected", { count }) : t("table.bulk.selected", "{{count}} selected", { count })
53
53
  }),
54
54
  canSelectAllMatching && /* @__PURE__ */ jsx("button", {
55
55
  type: "button",
56
56
  "data-test": "bulk-select-all-matching",
57
57
  className: "font-medium text-lt-primary underline underline-offset-2",
58
58
  onClick: onSelectAllMatching,
59
- children: t("bulk.selectAllMatching", "Select all {{total}} matching", { total })
59
+ children: t("table.bulk.selectAllMatching", "Select all {{total}} matching", { total })
60
60
  }),
61
61
  /* @__PURE__ */ jsx("div", {
62
62
  className: "flex flex-wrap items-center gap-2",
@@ -1 +1 @@
1
- {"version":3,"file":"bulk-bar.js","names":[],"sources":["../../../resources/js/table/components/bulk-bar.tsx"],"sourcesContent":["import { useHttp } from \"@inertiajs/react\";\nimport { useState } from \"react\";\nimport { ActionForm } from \"@lattice-php/lattice/action/components/action-form\";\nimport { runAction } from \"@lattice-php/lattice/action/run-action\";\nimport { getActionEffects } from \"@lattice-php/lattice/effects/dispatch\";\nimport type { ActionResponse } from \"@lattice-php/lattice/effects/dispatch\";\nimport { useEffectDispatcher } from \"@lattice-php/lattice/effects/use-effect-dispatcher\";\nimport { withHeaders } from \"@lattice-php/lattice/core/headers\";\nimport { Button } from \"@lattice-php/lattice/core/components/button\";\nimport { ConfirmDialog } from \"@lattice-php/lattice/core/components/confirm-dialog\";\nimport { Spinner } from \"@lattice-php/lattice/core/components/spinner\";\nimport { prefixedTestId } from \"@lattice-php/lattice/core/test-id\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\nimport type { BulkAction } from \"../bulk\";\n\ntype BulkData = {\n allMatching?: boolean;\n selected?: string[];\n};\n\nexport function BulkBar({\n actions,\n selectedKeys,\n allMatching,\n total,\n query,\n canSelectAllMatching,\n onSelectAllMatching,\n onCompleted,\n}: {\n actions: BulkAction[];\n selectedKeys: string[];\n allMatching: boolean;\n total?: number;\n query: Record<string, unknown>;\n canSelectAllMatching: boolean;\n onSelectAllMatching: () => void;\n onCompleted: () => void;\n}) {\n const { t } = useT(\"lattice\");\n const http = useHttp<BulkData, ActionResponse>({});\n const dispatch = useEffectDispatcher();\n const [confirming, setConfirming] = useState<BulkAction | null>(null);\n const [filling, setFilling] = useState<BulkAction | null>(null);\n\n const selectionPayload = (): Record<string, unknown> =>\n allMatching ? { allMatching: true, ...query } : { selected: selectedKeys };\n\n async function submit(action: BulkAction): Promise<void> {\n http.transform((data) => ({ ...data, ...selectionPayload() }));\n\n const ok = await runAction(\n () => http[action.method](action.endpoint, { headers: withHeaders(action.ref ?? \"\") }),\n dispatch,\n );\n\n if (ok) {\n setConfirming(null);\n onCompleted();\n }\n }\n\n function run(action: BulkAction): void {\n if (action.form) {\n setFilling(action);\n\n return;\n }\n\n if (action.confirmation) {\n setConfirming(action);\n\n return;\n }\n\n void submit(action);\n }\n\n const count = allMatching ? (total ?? selectedKeys.length) : selectedKeys.length;\n\n return (\n <div className=\"flex flex-wrap items-center gap-3 border-b border-lt-border bg-lt-muted/50 p-4 text-sm\">\n <span className=\"font-medium\">\n {allMatching\n ? t(\"bulk.allSelected\", \"All {{count}} selected\", { count })\n : t(\"bulk.selected\", \"{{count}} selected\", { count })}\n </span>\n {canSelectAllMatching && (\n <button\n type=\"button\"\n data-test=\"bulk-select-all-matching\"\n className=\"font-medium text-lt-primary underline underline-offset-2\"\n onClick={onSelectAllMatching}\n >\n {t(\"bulk.selectAllMatching\", \"Select all {{total}} matching\", { total })}\n </button>\n )}\n <div className=\"flex flex-wrap items-center gap-2\">\n {actions.map((action) => (\n <Button\n key={action.id}\n type=\"button\"\n data-test={prefixedTestId(\"bulk-action\", action.id)}\n variant={action.variant}\n disabled={http.processing}\n onClick={() => run(action)}\n >\n {http.processing && <Spinner />}\n {action.label}\n </Button>\n ))}\n </div>\n\n {confirming?.confirmation && (\n <ConfirmDialog\n title={confirming.confirmation.title ?? confirming.label}\n description={confirming.confirmation.description ?? undefined}\n confirmLabel={confirming.confirmation.confirmLabel ?? confirming.label}\n cancelLabel={confirming.confirmation.cancelLabel ?? \"Cancel\"}\n confirmVariant={confirming.variant}\n processing={http.processing}\n onConfirm={() => void submit(confirming)}\n onCancel={() => setConfirming(null)}\n />\n )}\n\n {filling?.form && (\n <ActionForm\n cancelLabel={filling.confirmation?.cancelLabel ?? \"Cancel\"}\n componentRef={filling.ref}\n description={filling.confirmation?.description ?? undefined}\n endpoint={filling.endpoint}\n extraData={selectionPayload()}\n formNode={filling.form}\n method={filling.method}\n onClose={() => setFilling(null)}\n onSuccess={(response) => {\n dispatch(getActionEffects(response.effects));\n setFilling(null);\n onCompleted();\n }}\n submitLabel={filling.confirmation?.confirmLabel ?? filling.label}\n title={filling.confirmation?.title ?? filling.label}\n />\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAoBA,SAAgB,QAAQ,EACtB,SACA,cACA,aACA,OACA,OACA,sBACA,qBACA,eAUC;CACD,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,MAAM,OAAO,QAAkC,CAAC,CAAC;CACjD,MAAM,WAAW,oBAAoB;CACrC,MAAM,CAAC,YAAY,iBAAiB,SAA4B,IAAI;CACpE,MAAM,CAAC,SAAS,cAAc,SAA4B,IAAI;CAE9D,MAAM,yBACJ,cAAc;EAAE,aAAa;EAAM,GAAG;CAAM,IAAI,EAAE,UAAU,aAAa;CAE3E,eAAe,OAAO,QAAmC;EACvD,KAAK,WAAW,UAAU;GAAE,GAAG;GAAM,GAAG,iBAAiB;EAAE,EAAE;EAO7D,IAAI,MALa,gBACT,KAAK,OAAO,QAAQ,OAAO,UAAU,EAAE,SAAS,YAAY,OAAO,OAAO,EAAE,EAAE,CAAC,GACrF,QACF,GAEQ;GACN,cAAc,IAAI;GAClB,YAAY;EACd;CACF;CAEA,SAAS,IAAI,QAA0B;EACrC,IAAI,OAAO,MAAM;GACf,WAAW,MAAM;GAEjB;EACF;EAEA,IAAI,OAAO,cAAc;GACvB,cAAc,MAAM;GAEpB;EACF;EAEA,OAAY,MAAM;CACpB;CAEA,MAAM,QAAQ,cAAe,SAAS,aAAa,SAAU,aAAa;CAE1E,OACE,qBAAC,OAAD;EAAK,WAAU;YAAf;GACE,oBAAC,QAAD;IAAM,WAAU;cACb,cACG,EAAE,oBAAoB,0BAA0B,EAAE,MAAM,CAAC,IACzD,EAAE,iBAAiB,sBAAsB,EAAE,MAAM,CAAC;GAClD,CAAA;GACL,wBACC,oBAAC,UAAD;IACE,MAAK;IACL,aAAU;IACV,WAAU;IACV,SAAS;cAER,EAAE,0BAA0B,iCAAiC,EAAE,MAAM,CAAC;GACjE,CAAA;GAEV,oBAAC,OAAD;IAAK,WAAU;cACZ,QAAQ,KAAK,WACZ,qBAAC,QAAD;KAEE,MAAK;KACL,aAAW,eAAe,eAAe,OAAO,EAAE;KAClD,SAAS,OAAO;KAChB,UAAU,KAAK;KACf,eAAe,IAAI,MAAM;eAN3B,CAQG,KAAK,cAAc,oBAAC,SAAD,CAAU,CAAA,GAC7B,OAAO,KACF;OATD,OAAO,EASN,CACT;GACE,CAAA;GAEJ,YAAY,gBACX,oBAAC,eAAD;IACE,OAAO,WAAW,aAAa,SAAS,WAAW;IACnD,aAAa,WAAW,aAAa,eAAe,KAAA;IACpD,cAAc,WAAW,aAAa,gBAAgB,WAAW;IACjE,aAAa,WAAW,aAAa,eAAe;IACpD,gBAAgB,WAAW;IAC3B,YAAY,KAAK;IACjB,iBAAiB,KAAK,OAAO,UAAU;IACvC,gBAAgB,cAAc,IAAI;GACnC,CAAA;GAGF,SAAS,QACR,oBAAC,YAAD;IACE,aAAa,QAAQ,cAAc,eAAe;IAClD,cAAc,QAAQ;IACtB,aAAa,QAAQ,cAAc,eAAe,KAAA;IAClD,UAAU,QAAQ;IAClB,WAAW,iBAAiB;IAC5B,UAAU,QAAQ;IAClB,QAAQ,QAAQ;IAChB,eAAe,WAAW,IAAI;IAC9B,YAAY,aAAa;KACvB,SAAS,iBAAiB,SAAS,OAAO,CAAC;KAC3C,WAAW,IAAI;KACf,YAAY;IACd;IACA,aAAa,QAAQ,cAAc,gBAAgB,QAAQ;IAC3D,OAAO,QAAQ,cAAc,SAAS,QAAQ;GAC/C,CAAA;EAEA;;AAET"}
1
+ {"version":3,"file":"bulk-bar.js","names":[],"sources":["../../../resources/js/table/components/bulk-bar.tsx"],"sourcesContent":["import { useHttp } from \"@inertiajs/react\";\nimport { useState } from \"react\";\nimport { ActionForm } from \"@lattice-php/lattice/action/components/action-form\";\nimport { runAction } from \"@lattice-php/lattice/action/run-action\";\nimport { getActionEffects } from \"@lattice-php/lattice/effects/dispatch\";\nimport type { ActionResponse } from \"@lattice-php/lattice/effects/dispatch\";\nimport { useEffectDispatcher } from \"@lattice-php/lattice/effects/use-effect-dispatcher\";\nimport { withHeaders } from \"@lattice-php/lattice/core/headers\";\nimport { Button } from \"@lattice-php/lattice/core/components/button\";\nimport { ConfirmDialog } from \"@lattice-php/lattice/core/components/confirm-dialog\";\nimport { Spinner } from \"@lattice-php/lattice/core/components/spinner\";\nimport { prefixedTestId } from \"@lattice-php/lattice/core/test-id\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\nimport type { BulkAction } from \"../bulk\";\n\ntype BulkData = {\n allMatching?: boolean;\n selected?: string[];\n};\n\nexport function BulkBar({\n actions,\n selectedKeys,\n allMatching,\n total,\n query,\n canSelectAllMatching,\n onSelectAllMatching,\n onCompleted,\n}: {\n actions: BulkAction[];\n selectedKeys: string[];\n allMatching: boolean;\n total?: number;\n query: Record<string, unknown>;\n canSelectAllMatching: boolean;\n onSelectAllMatching: () => void;\n onCompleted: () => void;\n}) {\n const { t } = useT(\"lattice\");\n const http = useHttp<BulkData, ActionResponse>({});\n const dispatch = useEffectDispatcher();\n const [confirming, setConfirming] = useState<BulkAction | null>(null);\n const [filling, setFilling] = useState<BulkAction | null>(null);\n\n const selectionPayload = (): Record<string, unknown> =>\n allMatching ? { allMatching: true, ...query } : { selected: selectedKeys };\n\n async function submit(action: BulkAction): Promise<void> {\n http.transform((data) => ({ ...data, ...selectionPayload() }));\n\n const ok = await runAction(\n () => http[action.method](action.endpoint, { headers: withHeaders(action.ref ?? \"\") }),\n dispatch,\n );\n\n if (ok) {\n setConfirming(null);\n onCompleted();\n }\n }\n\n function run(action: BulkAction): void {\n if (action.form) {\n setFilling(action);\n\n return;\n }\n\n if (action.confirmation) {\n setConfirming(action);\n\n return;\n }\n\n void submit(action);\n }\n\n const count = allMatching ? (total ?? selectedKeys.length) : selectedKeys.length;\n\n return (\n <div className=\"flex flex-wrap items-center gap-3 border-b border-lt-border bg-lt-muted/50 p-4 text-sm\">\n <span className=\"font-medium\">\n {allMatching\n ? t(\"table.bulk.allSelected\", \"All {{count}} selected\", { count })\n : t(\"table.bulk.selected\", \"{{count}} selected\", { count })}\n </span>\n {canSelectAllMatching && (\n <button\n type=\"button\"\n data-test=\"bulk-select-all-matching\"\n className=\"font-medium text-lt-primary underline underline-offset-2\"\n onClick={onSelectAllMatching}\n >\n {t(\"table.bulk.selectAllMatching\", \"Select all {{total}} matching\", { total })}\n </button>\n )}\n <div className=\"flex flex-wrap items-center gap-2\">\n {actions.map((action) => (\n <Button\n key={action.id}\n type=\"button\"\n data-test={prefixedTestId(\"bulk-action\", action.id)}\n variant={action.variant}\n disabled={http.processing}\n onClick={() => run(action)}\n >\n {http.processing && <Spinner />}\n {action.label}\n </Button>\n ))}\n </div>\n\n {confirming?.confirmation && (\n <ConfirmDialog\n title={confirming.confirmation.title ?? confirming.label}\n description={confirming.confirmation.description ?? undefined}\n confirmLabel={confirming.confirmation.confirmLabel ?? confirming.label}\n cancelLabel={confirming.confirmation.cancelLabel ?? \"Cancel\"}\n confirmVariant={confirming.variant}\n processing={http.processing}\n onConfirm={() => void submit(confirming)}\n onCancel={() => setConfirming(null)}\n />\n )}\n\n {filling?.form && (\n <ActionForm\n cancelLabel={filling.confirmation?.cancelLabel ?? \"Cancel\"}\n componentRef={filling.ref}\n description={filling.confirmation?.description ?? undefined}\n endpoint={filling.endpoint}\n extraData={selectionPayload()}\n formNode={filling.form}\n method={filling.method}\n onClose={() => setFilling(null)}\n onSuccess={(response) => {\n dispatch(getActionEffects(response.effects));\n setFilling(null);\n onCompleted();\n }}\n submitLabel={filling.confirmation?.confirmLabel ?? filling.label}\n title={filling.confirmation?.title ?? filling.label}\n />\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAoBA,SAAgB,QAAQ,EACtB,SACA,cACA,aACA,OACA,OACA,sBACA,qBACA,eAUC;CACD,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,MAAM,OAAO,QAAkC,CAAC,CAAC;CACjD,MAAM,WAAW,oBAAoB;CACrC,MAAM,CAAC,YAAY,iBAAiB,SAA4B,IAAI;CACpE,MAAM,CAAC,SAAS,cAAc,SAA4B,IAAI;CAE9D,MAAM,yBACJ,cAAc;EAAE,aAAa;EAAM,GAAG;CAAM,IAAI,EAAE,UAAU,aAAa;CAE3E,eAAe,OAAO,QAAmC;EACvD,KAAK,WAAW,UAAU;GAAE,GAAG;GAAM,GAAG,iBAAiB;EAAE,EAAE;EAO7D,IAAI,MALa,gBACT,KAAK,OAAO,QAAQ,OAAO,UAAU,EAAE,SAAS,YAAY,OAAO,OAAO,EAAE,EAAE,CAAC,GACrF,QACF,GAEQ;GACN,cAAc,IAAI;GAClB,YAAY;EACd;CACF;CAEA,SAAS,IAAI,QAA0B;EACrC,IAAI,OAAO,MAAM;GACf,WAAW,MAAM;GAEjB;EACF;EAEA,IAAI,OAAO,cAAc;GACvB,cAAc,MAAM;GAEpB;EACF;EAEA,OAAY,MAAM;CACpB;CAEA,MAAM,QAAQ,cAAe,SAAS,aAAa,SAAU,aAAa;CAE1E,OACE,qBAAC,OAAD;EAAK,WAAU;YAAf;GACE,oBAAC,QAAD;IAAM,WAAU;cACb,cACG,EAAE,0BAA0B,0BAA0B,EAAE,MAAM,CAAC,IAC/D,EAAE,uBAAuB,sBAAsB,EAAE,MAAM,CAAC;GACxD,CAAA;GACL,wBACC,oBAAC,UAAD;IACE,MAAK;IACL,aAAU;IACV,WAAU;IACV,SAAS;cAER,EAAE,gCAAgC,iCAAiC,EAAE,MAAM,CAAC;GACvE,CAAA;GAEV,oBAAC,OAAD;IAAK,WAAU;cACZ,QAAQ,KAAK,WACZ,qBAAC,QAAD;KAEE,MAAK;KACL,aAAW,eAAe,eAAe,OAAO,EAAE;KAClD,SAAS,OAAO;KAChB,UAAU,KAAK;KACf,eAAe,IAAI,MAAM;eAN3B,CAQG,KAAK,cAAc,oBAAC,SAAD,CAAU,CAAA,GAC7B,OAAO,KACF;OATD,OAAO,EASN,CACT;GACE,CAAA;GAEJ,YAAY,gBACX,oBAAC,eAAD;IACE,OAAO,WAAW,aAAa,SAAS,WAAW;IACnD,aAAa,WAAW,aAAa,eAAe,KAAA;IACpD,cAAc,WAAW,aAAa,gBAAgB,WAAW;IACjE,aAAa,WAAW,aAAa,eAAe;IACpD,gBAAgB,WAAW;IAC3B,YAAY,KAAK;IACjB,iBAAiB,KAAK,OAAO,UAAU;IACvC,gBAAgB,cAAc,IAAI;GACnC,CAAA;GAGF,SAAS,QACR,oBAAC,YAAD;IACE,aAAa,QAAQ,cAAc,eAAe;IAClD,cAAc,QAAQ;IACtB,aAAa,QAAQ,cAAc,eAAe,KAAA;IAClD,UAAU,QAAQ;IAClB,WAAW,iBAAiB;IAC5B,UAAU,QAAQ;IAClB,QAAQ,QAAQ;IAChB,eAAe,WAAW,IAAI;IAC9B,YAAY,aAAa;KACvB,SAAS,iBAAiB,SAAS,OAAO,CAAC;KAC3C,WAAW,IAAI;KACf,YAAY;IACd;IACA,aAAa,QAAQ,cAAc,gBAAgB,QAAQ;IAC3D,OAAO,QAAQ,cAAc,SAAS,QAAQ;GAC/C,CAAA;EAEA;;AAET"}
@@ -57,7 +57,7 @@ function ColumnFilterControl({ column, clauses, processing, onAdd, onUpdate, onR
57
57
  asChild: true,
58
58
  children: /* @__PURE__ */ jsxs("button", {
59
59
  type: "button",
60
- "aria-label": t("filter.columnFilters", "{{label}} filters", { label: column.label }),
60
+ "aria-label": t("table.filter.columnFilters", "{{label}} filters", { label: column.label }),
61
61
  "data-test": `filter-${column.key}`,
62
62
  className: "relative -ml-px inline-flex size-9 shrink-0 items-center justify-center rounded-r-lt-sm border border-lt-input disabled:opacity-50 data-[state=open]:z-10 data-[state=open]:border-lt-primary",
63
63
  disabled: processing,
@@ -221,7 +221,7 @@ function FilterClauseList({ column, clauses, operators, defaultOperator, process
221
221
  name: "plus",
222
222
  "aria-hidden": "true",
223
223
  className: "size-lt-icon-md"
224
- }), t("filter.add", "Add filter")]
224
+ }), t("table.filter.add", "Add filter")]
225
225
  })
226
226
  })
227
227
  ]
@@ -235,7 +235,7 @@ function FilterClauseRow({ column, type, operators, clause, processing, onOperat
235
235
  children: [/* @__PURE__ */ jsxs("div", {
236
236
  className: "flex items-center gap-2",
237
237
  children: [operators.length > 1 ? /* @__PURE__ */ jsx("select", {
238
- "aria-label": t("filter.operator", "{{label}} operator", { label: column.label }),
238
+ "aria-label": t("table.filter.operator", "{{label}} operator", { label: column.label }),
239
239
  "data-test": `filter-${column.key}-operator`,
240
240
  className: cn(fieldClass, "flex-1"),
241
241
  disabled: processing,
@@ -250,7 +250,7 @@ function FilterClauseRow({ column, type, operators, clause, processing, onOperat
250
250
  children: operatorLabel(clause.operator)
251
251
  }), /* @__PURE__ */ jsx("button", {
252
252
  type: "button",
253
- "aria-label": t("filter.remove", "Remove {{label}} filter", { label: column.label }),
253
+ "aria-label": t("table.filter.remove", "Remove {{label}} filter", { label: column.label }),
254
254
  "data-test": `filter-${column.key}-remove`,
255
255
  className: "inline-flex size-9 items-center justify-center rounded-lt-sm border border-lt-border hover:bg-lt-muted disabled:opacity-50",
256
256
  disabled: processing,
@@ -264,7 +264,7 @@ function FilterClauseRow({ column, type, operators, clause, processing, onOperat
264
264
  }), !valueless && /* @__PURE__ */ jsx(FilterValueInput, {
265
265
  type,
266
266
  label: column.label,
267
- ariaLabel: t("filter.value", "{{label}} filter value", { label: column.label }),
267
+ ariaLabel: t("table.filter.value", "{{label}} filter value", { label: column.label }),
268
268
  value: clause.value,
269
269
  processing,
270
270
  onCommit: onValue,
@@ -1 +1 @@
1
- {"version":3,"file":"column-filter-control.js","names":[],"sources":["../../../resources/js/table/components/column-filter-control.tsx"],"sourcesContent":["import { Icon } from \"@lattice-php/lattice/icons\";\nimport { useState } from \"react\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@lattice-php/lattice/core/components/popover\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport type {\n ColumnFilterOption,\n FilterData,\n FilterType,\n Op,\n} from \"@lattice-php/lattice/types/generated\";\nimport { operatorLabel, VALUELESS_FILTER_OPERATORS } from \"../query\";\nimport type { FilterClause, TableColumn } from \"../types\";\nimport { type FilterOptionSearch, TableFilterControl } from \"./filter-controls\";\nimport { fieldClass, FilterValueInput } from \"./filter-value-input\";\n\ntype ColumnClause = { clause: FilterClause; index: number };\n\nexport function ColumnFilterControl({\n column,\n clauses,\n processing,\n onAdd,\n onUpdate,\n onRemove,\n onReplace,\n onSearch,\n}: {\n column: TableColumn;\n clauses: ColumnClause[];\n processing: boolean;\n onAdd: (clause: FilterClause) => void;\n onUpdate: (index: number, clause: FilterClause) => void;\n onRemove: (index: number) => void;\n onReplace: (field: string, clauses: FilterClause[]) => void;\n onSearch?: FilterOptionSearch;\n}) {\n const { t } = useT(\"lattice\");\n const filter = column.filter;\n\n if (!filter) {\n return null;\n }\n\n if (filter.control === \"select\") {\n return (\n <ColumnSelectFilter\n column={column}\n clauses={clauses}\n processing={processing}\n onReplace={onReplace}\n onSearch={onSearch}\n />\n );\n }\n\n const type = filter.type ?? \"text\";\n const operators = filter.operators ?? [];\n const defaultOperator = filter.defaultOperator ?? operators[0] ?? \"eq\";\n const primary = clauses.find((entry) => entry.clause.operator === defaultOperator) ?? clauses[0];\n\n function commitPrimary(value: string): void {\n if (value === \"\") {\n if (primary) {\n onRemove(primary.index);\n }\n\n return;\n }\n\n if (primary) {\n onUpdate(primary.index, { ...primary.clause, value });\n } else {\n onAdd({ field: column.key, operator: defaultOperator, value });\n }\n }\n\n return (\n <div className=\"flex min-w-0 max-w-80 items-stretch\">\n <div className=\"min-w-0 flex-1\">\n <FilterValueInput\n type={type}\n label={column.label}\n value={primary?.clause.value ?? \"\"}\n processing={processing}\n withSearchIcon={type === \"text\" || type === \"number\"}\n grouped\n testId={`filter-${column.key}-value`}\n onCommit={commitPrimary}\n onClear={primary ? () => onRemove(primary.index) : undefined}\n />\n </div>\n <Popover>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n aria-label={t(\"filter.columnFilters\", \"{{label}} filters\", { label: column.label })}\n data-test={`filter-${column.key}`}\n className=\"relative -ml-px inline-flex size-9 shrink-0 items-center justify-center rounded-r-lt-sm border border-lt-input disabled:opacity-50 data-[state=open]:z-10 data-[state=open]:border-lt-primary\"\n disabled={processing}\n >\n <Icon name=\"filter\" aria-hidden=\"true\" className=\"size-lt-icon-md\" />\n {clauses.length > 0 && (\n <span className=\"absolute -right-1.5 -top-1.5 inline-flex size-4 items-center justify-center rounded-full bg-lt-primary text-xs font-medium text-lt-primary-fg\">\n {clauses.length}\n </span>\n )}\n </button>\n </PopoverTrigger>\n\n <PopoverContent align=\"start\" className=\"w-80 p-4\">\n <FilterClauseList\n column={column}\n clauses={clauses}\n operators={operators}\n defaultOperator={defaultOperator}\n processing={processing}\n onAdd={onAdd}\n onUpdate={onUpdate}\n onRemove={onRemove}\n />\n </PopoverContent>\n </Popover>\n </div>\n );\n}\n\nfunction serializeColumnValue(value: unknown): string {\n if (Array.isArray(value)) {\n return value.join(\",\");\n }\n\n return typeof value === \"string\" ? value : \"\";\n}\n\nfunction ColumnSelectFilter({\n column,\n clauses,\n processing,\n onReplace,\n onSearch,\n}: {\n column: TableColumn;\n clauses: ColumnClause[];\n processing: boolean;\n onReplace: (field: string, clauses: FilterClause[]) => void;\n onSearch?: FilterOptionSearch;\n}) {\n const filter = column.filter;\n\n if (!filter) {\n return null;\n }\n\n const multiple = filter.multiple;\n const operator = filter.defaultOperator;\n const clauseOptions = filter.clauseOptions ?? [];\n const activeClauseOption = findActiveClauseOption(\n clauses.map((entry) => entry.clause),\n clauseOptions,\n );\n const active = clauses.find((entry) => entry.clause.operator === operator) ?? clauses[0];\n const value: unknown = activeClauseOption\n ? activeClauseOption.value\n : multiple\n ? active?.clause.value\n ? active.clause.value.split(\",\")\n : []\n : (active?.clause.value ?? \"\");\n\n const data: FilterData = {\n key: column.key,\n label: column.label,\n type: \"select\",\n props: { options: filter.options, multiple, searchable: filter.searchable, placeholder: null },\n };\n\n function change(next: unknown): void {\n const serialized = serializeColumnValue(next);\n\n if (serialized === \"\") {\n onReplace(column.key, []);\n\n return;\n }\n\n const clauseOption = clauseOptions.find((option) => option.value === serialized);\n\n if (clauseOption) {\n onReplace(column.key, clausesForOption(column.key, clauseOption));\n\n return;\n }\n\n onReplace(column.key, [{ field: column.key, operator, value: serialized }]);\n }\n\n return (\n <TableFilterControl\n filter={data}\n value={value}\n processing={processing}\n onChange={change}\n onSearch={onSearch}\n />\n );\n}\n\nfunction clausesForOption(field: string, option: ColumnFilterOption): FilterClause[] {\n return option.clauses.map((clause) => ({\n field,\n operator: clause.operator,\n value: clause.value,\n }));\n}\n\nfunction findActiveClauseOption(\n clauses: FilterClause[],\n options: ColumnFilterOption[],\n): ColumnFilterOption | undefined {\n return options.find((option) => clausesMatch(clauses, clausesForOption(\"\", option)));\n}\n\nfunction clausesMatch(active: FilterClause[], expected: FilterClause[]): boolean {\n if (active.length !== expected.length) {\n return false;\n }\n\n return expected.every((clause) =>\n active.some(\n (current) => current.operator === clause.operator && current.value === clause.value,\n ),\n );\n}\n\nfunction FilterClauseList({\n column,\n clauses,\n operators,\n defaultOperator,\n processing,\n onAdd,\n onUpdate,\n onRemove,\n}: {\n column: TableColumn;\n clauses: ColumnClause[];\n operators: Op[];\n defaultOperator: Op;\n processing: boolean;\n onAdd: (clause: FilterClause) => void;\n onUpdate: (index: number, clause: FilterClause) => void;\n onRemove: (index: number) => void;\n}) {\n const { t } = useT(\"lattice\");\n const type = column.filter?.type ?? \"text\";\n const [draftOperator, setDraftOperator] = useState(defaultOperator);\n const [adding, setAdding] = useState(clauses.length === 0);\n\n return (\n <div className=\"grid gap-3\">\n {clauses.map((entry) => (\n <FilterClauseRow\n key={entry.index}\n column={column}\n type={type}\n operators={operators}\n clause={entry.clause}\n processing={processing}\n onOperator={(operator) => onUpdate(entry.index, { ...entry.clause, operator })}\n onValue={(value) =>\n value === \"\" ? onRemove(entry.index) : onUpdate(entry.index, { ...entry.clause, value })\n }\n onRemove={() => onRemove(entry.index)}\n />\n ))}\n\n {adding && (\n <FilterClauseRow\n column={column}\n type={type}\n operators={operators}\n clause={{ field: column.key, operator: draftOperator, value: \"\" }}\n processing={processing}\n onOperator={(operator) => {\n if (VALUELESS_FILTER_OPERATORS.has(operator)) {\n onAdd({ field: column.key, operator, value: \"\" });\n setDraftOperator(defaultOperator);\n setAdding(false);\n return;\n }\n setDraftOperator(operator);\n }}\n onValue={(value) => {\n if (value !== \"\") {\n onAdd({ field: column.key, operator: draftOperator, value });\n setDraftOperator(defaultOperator);\n setAdding(false);\n }\n }}\n onRemove={() => setAdding(false)}\n />\n )}\n\n <div className=\"border-t border-lt-border pt-3\">\n <button\n type=\"button\"\n data-test={`filter-${column.key}-add`}\n className=\"inline-flex w-full items-center justify-center gap-2 rounded-lt-sm bg-lt-muted px-3 py-2 text-sm font-medium hover:bg-lt-muted/70 disabled:opacity-50\"\n disabled={processing}\n onClick={() => setAdding(true)}\n >\n <Icon name=\"plus\" aria-hidden=\"true\" className=\"size-lt-icon-md\" />\n {t(\"filter.add\", \"Add filter\")}\n </button>\n </div>\n </div>\n );\n}\n\nfunction FilterClauseRow({\n column,\n type,\n operators,\n clause,\n processing,\n onOperator,\n onValue,\n onRemove,\n}: {\n column: TableColumn;\n type: FilterType;\n operators: Op[];\n clause: FilterClause;\n processing: boolean;\n onOperator: (operator: Op) => void;\n onValue: (value: string) => void;\n onRemove: () => void;\n}) {\n const { t } = useT(\"lattice\");\n const valueless = VALUELESS_FILTER_OPERATORS.has(clause.operator);\n\n return (\n <div className=\"grid gap-2\">\n <div className=\"flex items-center gap-2\">\n {operators.length > 1 ? (\n <select\n aria-label={t(\"filter.operator\", \"{{label}} operator\", { label: column.label })}\n data-test={`filter-${column.key}-operator`}\n className={cn(fieldClass, \"flex-1\")}\n disabled={processing}\n value={clause.operator}\n onChange={(event) => onOperator(event.target.value as Op)}\n >\n {operators.map((operator) => (\n <option key={operator} value={operator}>\n {operatorLabel(operator)}\n </option>\n ))}\n </select>\n ) : (\n <span className=\"flex-1 text-sm font-medium\">{operatorLabel(clause.operator)}</span>\n )}\n <button\n type=\"button\"\n aria-label={t(\"filter.remove\", \"Remove {{label}} filter\", { label: column.label })}\n data-test={`filter-${column.key}-remove`}\n className=\"inline-flex size-9 items-center justify-center rounded-lt-sm border border-lt-border hover:bg-lt-muted disabled:opacity-50\"\n disabled={processing}\n onClick={onRemove}\n >\n <Icon name=\"trash-2\" aria-hidden=\"true\" className=\"size-lt-icon-md\" />\n </button>\n </div>\n {!valueless && (\n <FilterValueInput\n type={type}\n label={column.label}\n ariaLabel={t(\"filter.value\", \"{{label}} filter value\", { label: column.label })}\n value={clause.value}\n processing={processing}\n onCommit={onValue}\n onClear={() => onValue(\"\")}\n />\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;AAsBA,SAAgB,oBAAoB,EAClC,QACA,SACA,YACA,OACA,UACA,UACA,WACA,YAUC;CACD,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,MAAM,SAAS,OAAO;CAEtB,IAAI,CAAC,QACH,OAAO;CAGT,IAAI,OAAO,YAAY,UACrB,OACE,oBAAC,oBAAD;EACU;EACC;EACG;EACD;EACD;CACX,CAAA;CAIL,MAAM,OAAO,OAAO,QAAQ;CAC5B,MAAM,YAAY,OAAO,aAAa,CAAC;CACvC,MAAM,kBAAkB,OAAO,mBAAmB,UAAU,MAAM;CAClE,MAAM,UAAU,QAAQ,MAAM,UAAU,MAAM,OAAO,aAAa,eAAe,KAAK,QAAQ;CAE9F,SAAS,cAAc,OAAqB;EAC1C,IAAI,UAAU,IAAI;GAChB,IAAI,SACF,SAAS,QAAQ,KAAK;GAGxB;EACF;EAEA,IAAI,SACF,SAAS,QAAQ,OAAO;GAAE,GAAG,QAAQ;GAAQ;EAAM,CAAC;OAEpD,MAAM;GAAE,OAAO,OAAO;GAAK,UAAU;GAAiB;EAAM,CAAC;CAEjE;CAEA,OACE,qBAAC,OAAD;EAAK,WAAU;YAAf,CACE,oBAAC,OAAD;GAAK,WAAU;aACb,oBAAC,kBAAD;IACQ;IACN,OAAO,OAAO;IACd,OAAO,SAAS,OAAO,SAAS;IACpB;IACZ,gBAAgB,SAAS,UAAU,SAAS;IAC5C,SAAA;IACA,QAAQ,UAAU,OAAO,IAAI;IAC7B,UAAU;IACV,SAAS,gBAAgB,SAAS,QAAQ,KAAK,IAAI,KAAA;GACpD,CAAA;EACE,CAAA,GACL,qBAAC,SAAD,EAAA,UAAA,CACE,oBAAC,gBAAD;GAAgB,SAAA;aACd,qBAAC,UAAD;IACE,MAAK;IACL,cAAY,EAAE,wBAAwB,qBAAqB,EAAE,OAAO,OAAO,MAAM,CAAC;IAClF,aAAW,UAAU,OAAO;IAC5B,WAAU;IACV,UAAU;cALZ,CAOE,oBAAC,MAAD;KAAM,MAAK;KAAS,eAAY;KAAO,WAAU;IAAmB,CAAA,GACnE,QAAQ,SAAS,KAChB,oBAAC,QAAD;KAAM,WAAU;eACb,QAAQ;IACL,CAAA,CAEF;;EACM,CAAA,GAEhB,oBAAC,gBAAD;GAAgB,OAAM;GAAQ,WAAU;aACtC,oBAAC,kBAAD;IACU;IACC;IACE;IACM;IACL;IACL;IACG;IACA;GACX,CAAA;EACa,CAAA,CACT,EAAA,CAAA,CACN;;AAET;AAEA,SAAS,qBAAqB,OAAwB;CACpD,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,KAAK,GAAG;CAGvB,OAAO,OAAO,UAAU,WAAW,QAAQ;AAC7C;AAEA,SAAS,mBAAmB,EAC1B,QACA,SACA,YACA,WACA,YAOC;CACD,MAAM,SAAS,OAAO;CAEtB,IAAI,CAAC,QACH,OAAO;CAGT,MAAM,WAAW,OAAO;CACxB,MAAM,WAAW,OAAO;CACxB,MAAM,gBAAgB,OAAO,iBAAiB,CAAC;CAC/C,MAAM,qBAAqB,uBACzB,QAAQ,KAAK,UAAU,MAAM,MAAM,GACnC,aACF;CACA,MAAM,SAAS,QAAQ,MAAM,UAAU,MAAM,OAAO,aAAa,QAAQ,KAAK,QAAQ;CACtF,MAAM,QAAiB,qBACnB,mBAAmB,QACnB,WACE,QAAQ,OAAO,QACb,OAAO,OAAO,MAAM,MAAM,GAAG,IAC7B,CAAC,IACF,QAAQ,OAAO,SAAS;CAE/B,MAAM,OAAmB;EACvB,KAAK,OAAO;EACZ,OAAO,OAAO;EACd,MAAM;EACN,OAAO;GAAE,SAAS,OAAO;GAAS;GAAU,YAAY,OAAO;GAAY,aAAa;EAAK;CAC/F;CAEA,SAAS,OAAO,MAAqB;EACnC,MAAM,aAAa,qBAAqB,IAAI;EAE5C,IAAI,eAAe,IAAI;GACrB,UAAU,OAAO,KAAK,CAAC,CAAC;GAExB;EACF;EAEA,MAAM,eAAe,cAAc,MAAM,WAAW,OAAO,UAAU,UAAU;EAE/E,IAAI,cAAc;GAChB,UAAU,OAAO,KAAK,iBAAiB,OAAO,KAAK,YAAY,CAAC;GAEhE;EACF;EAEA,UAAU,OAAO,KAAK,CAAC;GAAE,OAAO,OAAO;GAAK;GAAU,OAAO;EAAW,CAAC,CAAC;CAC5E;CAEA,OACE,oBAAC,oBAAD;EACE,QAAQ;EACD;EACK;EACZ,UAAU;EACA;CACX,CAAA;AAEL;AAEA,SAAS,iBAAiB,OAAe,QAA4C;CACnF,OAAO,OAAO,QAAQ,KAAK,YAAY;EACrC;EACA,UAAU,OAAO;EACjB,OAAO,OAAO;CAChB,EAAE;AACJ;AAEA,SAAS,uBACP,SACA,SACgC;CAChC,OAAO,QAAQ,MAAM,WAAW,aAAa,SAAS,iBAAiB,IAAI,MAAM,CAAC,CAAC;AACrF;AAEA,SAAS,aAAa,QAAwB,UAAmC;CAC/E,IAAI,OAAO,WAAW,SAAS,QAC7B,OAAO;CAGT,OAAO,SAAS,OAAO,WACrB,OAAO,MACJ,YAAY,QAAQ,aAAa,OAAO,YAAY,QAAQ,UAAU,OAAO,KAChF,CACF;AACF;AAEA,SAAS,iBAAiB,EACxB,QACA,SACA,WACA,iBACA,YACA,OACA,UACA,YAUC;CACD,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,MAAM,OAAO,OAAO,QAAQ,QAAQ;CACpC,MAAM,CAAC,eAAe,oBAAoB,SAAS,eAAe;CAClE,MAAM,CAAC,QAAQ,aAAa,SAAS,QAAQ,WAAW,CAAC;CAEzD,OACE,qBAAC,OAAD;EAAK,WAAU;YAAf;GACG,QAAQ,KAAK,UACZ,oBAAC,iBAAD;IAEU;IACF;IACK;IACX,QAAQ,MAAM;IACF;IACZ,aAAa,aAAa,SAAS,MAAM,OAAO;KAAE,GAAG,MAAM;KAAQ;IAAS,CAAC;IAC7E,UAAU,UACR,UAAU,KAAK,SAAS,MAAM,KAAK,IAAI,SAAS,MAAM,OAAO;KAAE,GAAG,MAAM;KAAQ;IAAM,CAAC;IAEzF,gBAAgB,SAAS,MAAM,KAAK;GACrC,GAXM,MAAM,KAWZ,CACF;GAEA,UACC,oBAAC,iBAAD;IACU;IACF;IACK;IACX,QAAQ;KAAE,OAAO,OAAO;KAAK,UAAU;KAAe,OAAO;IAAG;IACpD;IACZ,aAAa,aAAa;KACxB,IAAI,2BAA2B,IAAI,QAAQ,GAAG;MAC5C,MAAM;OAAE,OAAO,OAAO;OAAK;OAAU,OAAO;MAAG,CAAC;MAChD,iBAAiB,eAAe;MAChC,UAAU,KAAK;MACf;KACF;KACA,iBAAiB,QAAQ;IAC3B;IACA,UAAU,UAAU;KAClB,IAAI,UAAU,IAAI;MAChB,MAAM;OAAE,OAAO,OAAO;OAAK,UAAU;OAAe;MAAM,CAAC;MAC3D,iBAAiB,eAAe;MAChC,UAAU,KAAK;KACjB;IACF;IACA,gBAAgB,UAAU,KAAK;GAChC,CAAA;GAGH,oBAAC,OAAD;IAAK,WAAU;cACb,qBAAC,UAAD;KACE,MAAK;KACL,aAAW,UAAU,OAAO,IAAI;KAChC,WAAU;KACV,UAAU;KACV,eAAe,UAAU,IAAI;eAL/B,CAOE,oBAAC,MAAD;MAAM,MAAK;MAAO,eAAY;MAAO,WAAU;KAAmB,CAAA,GACjE,EAAE,cAAc,YAAY,CACvB;;GACL,CAAA;EACF;;AAET;AAEA,SAAS,gBAAgB,EACvB,QACA,MACA,WACA,QACA,YACA,YACA,SACA,YAUC;CACD,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,MAAM,YAAY,2BAA2B,IAAI,OAAO,QAAQ;CAEhE,OACE,qBAAC,OAAD;EAAK,WAAU;YAAf,CACE,qBAAC,OAAD;GAAK,WAAU;aAAf,CACG,UAAU,SAAS,IAClB,oBAAC,UAAD;IACE,cAAY,EAAE,mBAAmB,sBAAsB,EAAE,OAAO,OAAO,MAAM,CAAC;IAC9E,aAAW,UAAU,OAAO,IAAI;IAChC,WAAW,GAAG,YAAY,QAAQ;IAClC,UAAU;IACV,OAAO,OAAO;IACd,WAAW,UAAU,WAAW,MAAM,OAAO,KAAW;cAEvD,UAAU,KAAK,aACd,oBAAC,UAAD;KAAuB,OAAO;eAC3B,cAAc,QAAQ;IACjB,GAFK,QAEL,CACT;GACK,CAAA,IAER,oBAAC,QAAD;IAAM,WAAU;cAA8B,cAAc,OAAO,QAAQ;GAAQ,CAAA,GAErF,oBAAC,UAAD;IACE,MAAK;IACL,cAAY,EAAE,iBAAiB,2BAA2B,EAAE,OAAO,OAAO,MAAM,CAAC;IACjF,aAAW,UAAU,OAAO,IAAI;IAChC,WAAU;IACV,UAAU;IACV,SAAS;cAET,oBAAC,MAAD;KAAM,MAAK;KAAU,eAAY;KAAO,WAAU;IAAmB,CAAA;GAC/D,CAAA,CACL;MACJ,CAAC,aACA,oBAAC,kBAAD;GACQ;GACN,OAAO,OAAO;GACd,WAAW,EAAE,gBAAgB,0BAA0B,EAAE,OAAO,OAAO,MAAM,CAAC;GAC9E,OAAO,OAAO;GACF;GACZ,UAAU;GACV,eAAe,QAAQ,EAAE;EAC1B,CAAA,CAEA;;AAET"}
1
+ {"version":3,"file":"column-filter-control.js","names":[],"sources":["../../../resources/js/table/components/column-filter-control.tsx"],"sourcesContent":["import { Icon } from \"@lattice-php/lattice/icons\";\nimport { useState } from \"react\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@lattice-php/lattice/core/components/popover\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport type {\n ColumnFilterOption,\n FilterData,\n FilterType,\n Op,\n} from \"@lattice-php/lattice/types/generated\";\nimport { operatorLabel, VALUELESS_FILTER_OPERATORS } from \"../query\";\nimport type { FilterClause, TableColumn } from \"../types\";\nimport { type FilterOptionSearch, TableFilterControl } from \"./filter-controls\";\nimport { fieldClass, FilterValueInput } from \"./filter-value-input\";\n\ntype ColumnClause = { clause: FilterClause; index: number };\n\nexport function ColumnFilterControl({\n column,\n clauses,\n processing,\n onAdd,\n onUpdate,\n onRemove,\n onReplace,\n onSearch,\n}: {\n column: TableColumn;\n clauses: ColumnClause[];\n processing: boolean;\n onAdd: (clause: FilterClause) => void;\n onUpdate: (index: number, clause: FilterClause) => void;\n onRemove: (index: number) => void;\n onReplace: (field: string, clauses: FilterClause[]) => void;\n onSearch?: FilterOptionSearch;\n}) {\n const { t } = useT(\"lattice\");\n const filter = column.filter;\n\n if (!filter) {\n return null;\n }\n\n if (filter.control === \"select\") {\n return (\n <ColumnSelectFilter\n column={column}\n clauses={clauses}\n processing={processing}\n onReplace={onReplace}\n onSearch={onSearch}\n />\n );\n }\n\n const type = filter.type ?? \"text\";\n const operators = filter.operators ?? [];\n const defaultOperator = filter.defaultOperator ?? operators[0] ?? \"eq\";\n const primary = clauses.find((entry) => entry.clause.operator === defaultOperator) ?? clauses[0];\n\n function commitPrimary(value: string): void {\n if (value === \"\") {\n if (primary) {\n onRemove(primary.index);\n }\n\n return;\n }\n\n if (primary) {\n onUpdate(primary.index, { ...primary.clause, value });\n } else {\n onAdd({ field: column.key, operator: defaultOperator, value });\n }\n }\n\n return (\n <div className=\"flex min-w-0 max-w-80 items-stretch\">\n <div className=\"min-w-0 flex-1\">\n <FilterValueInput\n type={type}\n label={column.label}\n value={primary?.clause.value ?? \"\"}\n processing={processing}\n withSearchIcon={type === \"text\" || type === \"number\"}\n grouped\n testId={`filter-${column.key}-value`}\n onCommit={commitPrimary}\n onClear={primary ? () => onRemove(primary.index) : undefined}\n />\n </div>\n <Popover>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n aria-label={t(\"table.filter.columnFilters\", \"{{label}} filters\", {\n label: column.label,\n })}\n data-test={`filter-${column.key}`}\n className=\"relative -ml-px inline-flex size-9 shrink-0 items-center justify-center rounded-r-lt-sm border border-lt-input disabled:opacity-50 data-[state=open]:z-10 data-[state=open]:border-lt-primary\"\n disabled={processing}\n >\n <Icon name=\"filter\" aria-hidden=\"true\" className=\"size-lt-icon-md\" />\n {clauses.length > 0 && (\n <span className=\"absolute -right-1.5 -top-1.5 inline-flex size-4 items-center justify-center rounded-full bg-lt-primary text-xs font-medium text-lt-primary-fg\">\n {clauses.length}\n </span>\n )}\n </button>\n </PopoverTrigger>\n\n <PopoverContent align=\"start\" className=\"w-80 p-4\">\n <FilterClauseList\n column={column}\n clauses={clauses}\n operators={operators}\n defaultOperator={defaultOperator}\n processing={processing}\n onAdd={onAdd}\n onUpdate={onUpdate}\n onRemove={onRemove}\n />\n </PopoverContent>\n </Popover>\n </div>\n );\n}\n\nfunction serializeColumnValue(value: unknown): string {\n if (Array.isArray(value)) {\n return value.join(\",\");\n }\n\n return typeof value === \"string\" ? value : \"\";\n}\n\nfunction ColumnSelectFilter({\n column,\n clauses,\n processing,\n onReplace,\n onSearch,\n}: {\n column: TableColumn;\n clauses: ColumnClause[];\n processing: boolean;\n onReplace: (field: string, clauses: FilterClause[]) => void;\n onSearch?: FilterOptionSearch;\n}) {\n const filter = column.filter;\n\n if (!filter) {\n return null;\n }\n\n const multiple = filter.multiple;\n const operator = filter.defaultOperator;\n const clauseOptions = filter.clauseOptions ?? [];\n const activeClauseOption = findActiveClauseOption(\n clauses.map((entry) => entry.clause),\n clauseOptions,\n );\n const active = clauses.find((entry) => entry.clause.operator === operator) ?? clauses[0];\n const value: unknown = activeClauseOption\n ? activeClauseOption.value\n : multiple\n ? active?.clause.value\n ? active.clause.value.split(\",\")\n : []\n : (active?.clause.value ?? \"\");\n\n const data: FilterData = {\n key: column.key,\n label: column.label,\n type: \"select\",\n props: { options: filter.options, multiple, searchable: filter.searchable, placeholder: null },\n };\n\n function change(next: unknown): void {\n const serialized = serializeColumnValue(next);\n\n if (serialized === \"\") {\n onReplace(column.key, []);\n\n return;\n }\n\n const clauseOption = clauseOptions.find((option) => option.value === serialized);\n\n if (clauseOption) {\n onReplace(column.key, clausesForOption(column.key, clauseOption));\n\n return;\n }\n\n onReplace(column.key, [{ field: column.key, operator, value: serialized }]);\n }\n\n return (\n <TableFilterControl\n filter={data}\n value={value}\n processing={processing}\n onChange={change}\n onSearch={onSearch}\n />\n );\n}\n\nfunction clausesForOption(field: string, option: ColumnFilterOption): FilterClause[] {\n return option.clauses.map((clause) => ({\n field,\n operator: clause.operator,\n value: clause.value,\n }));\n}\n\nfunction findActiveClauseOption(\n clauses: FilterClause[],\n options: ColumnFilterOption[],\n): ColumnFilterOption | undefined {\n return options.find((option) => clausesMatch(clauses, clausesForOption(\"\", option)));\n}\n\nfunction clausesMatch(active: FilterClause[], expected: FilterClause[]): boolean {\n if (active.length !== expected.length) {\n return false;\n }\n\n return expected.every((clause) =>\n active.some(\n (current) => current.operator === clause.operator && current.value === clause.value,\n ),\n );\n}\n\nfunction FilterClauseList({\n column,\n clauses,\n operators,\n defaultOperator,\n processing,\n onAdd,\n onUpdate,\n onRemove,\n}: {\n column: TableColumn;\n clauses: ColumnClause[];\n operators: Op[];\n defaultOperator: Op;\n processing: boolean;\n onAdd: (clause: FilterClause) => void;\n onUpdate: (index: number, clause: FilterClause) => void;\n onRemove: (index: number) => void;\n}) {\n const { t } = useT(\"lattice\");\n const type = column.filter?.type ?? \"text\";\n const [draftOperator, setDraftOperator] = useState(defaultOperator);\n const [adding, setAdding] = useState(clauses.length === 0);\n\n return (\n <div className=\"grid gap-3\">\n {clauses.map((entry) => (\n <FilterClauseRow\n key={entry.index}\n column={column}\n type={type}\n operators={operators}\n clause={entry.clause}\n processing={processing}\n onOperator={(operator) => onUpdate(entry.index, { ...entry.clause, operator })}\n onValue={(value) =>\n value === \"\" ? onRemove(entry.index) : onUpdate(entry.index, { ...entry.clause, value })\n }\n onRemove={() => onRemove(entry.index)}\n />\n ))}\n\n {adding && (\n <FilterClauseRow\n column={column}\n type={type}\n operators={operators}\n clause={{ field: column.key, operator: draftOperator, value: \"\" }}\n processing={processing}\n onOperator={(operator) => {\n if (VALUELESS_FILTER_OPERATORS.has(operator)) {\n onAdd({ field: column.key, operator, value: \"\" });\n setDraftOperator(defaultOperator);\n setAdding(false);\n return;\n }\n setDraftOperator(operator);\n }}\n onValue={(value) => {\n if (value !== \"\") {\n onAdd({ field: column.key, operator: draftOperator, value });\n setDraftOperator(defaultOperator);\n setAdding(false);\n }\n }}\n onRemove={() => setAdding(false)}\n />\n )}\n\n <div className=\"border-t border-lt-border pt-3\">\n <button\n type=\"button\"\n data-test={`filter-${column.key}-add`}\n className=\"inline-flex w-full items-center justify-center gap-2 rounded-lt-sm bg-lt-muted px-3 py-2 text-sm font-medium hover:bg-lt-muted/70 disabled:opacity-50\"\n disabled={processing}\n onClick={() => setAdding(true)}\n >\n <Icon name=\"plus\" aria-hidden=\"true\" className=\"size-lt-icon-md\" />\n {t(\"table.filter.add\", \"Add filter\")}\n </button>\n </div>\n </div>\n );\n}\n\nfunction FilterClauseRow({\n column,\n type,\n operators,\n clause,\n processing,\n onOperator,\n onValue,\n onRemove,\n}: {\n column: TableColumn;\n type: FilterType;\n operators: Op[];\n clause: FilterClause;\n processing: boolean;\n onOperator: (operator: Op) => void;\n onValue: (value: string) => void;\n onRemove: () => void;\n}) {\n const { t } = useT(\"lattice\");\n const valueless = VALUELESS_FILTER_OPERATORS.has(clause.operator);\n\n return (\n <div className=\"grid gap-2\">\n <div className=\"flex items-center gap-2\">\n {operators.length > 1 ? (\n <select\n aria-label={t(\"table.filter.operator\", \"{{label}} operator\", { label: column.label })}\n data-test={`filter-${column.key}-operator`}\n className={cn(fieldClass, \"flex-1\")}\n disabled={processing}\n value={clause.operator}\n onChange={(event) => onOperator(event.target.value as Op)}\n >\n {operators.map((operator) => (\n <option key={operator} value={operator}>\n {operatorLabel(operator)}\n </option>\n ))}\n </select>\n ) : (\n <span className=\"flex-1 text-sm font-medium\">{operatorLabel(clause.operator)}</span>\n )}\n <button\n type=\"button\"\n aria-label={t(\"table.filter.remove\", \"Remove {{label}} filter\", { label: column.label })}\n data-test={`filter-${column.key}-remove`}\n className=\"inline-flex size-9 items-center justify-center rounded-lt-sm border border-lt-border hover:bg-lt-muted disabled:opacity-50\"\n disabled={processing}\n onClick={onRemove}\n >\n <Icon name=\"trash-2\" aria-hidden=\"true\" className=\"size-lt-icon-md\" />\n </button>\n </div>\n {!valueless && (\n <FilterValueInput\n type={type}\n label={column.label}\n ariaLabel={t(\"table.filter.value\", \"{{label}} filter value\", { label: column.label })}\n value={clause.value}\n processing={processing}\n onCommit={onValue}\n onClear={() => onValue(\"\")}\n />\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -23,7 +23,7 @@ function FilterBar({ filters, values, processing, hasActiveFilters, onChange, on
23
23
  "data-test": `table-filter-chip-${filter.key}-remove`,
24
24
  className: "inline-flex size-5 items-center justify-center rounded-lt-sm text-lt-muted-fg hover:bg-lt-border disabled:opacity-50",
25
25
  disabled: processing,
26
- "aria-label": t("filter.remove", "Remove {{label}} filter", { label: filter.label }),
26
+ "aria-label": t("table.filter.remove", "Remove {{label}} filter", { label: filter.label }),
27
27
  onClick: () => onChange(filter.key, void 0),
28
28
  children: /* @__PURE__ */ jsx(Icon, {
29
29
  name: "x",
@@ -37,7 +37,7 @@ function FilterBar({ filters, values, processing, hasActiveFilters, onChange, on
37
37
  className: "text-lt-muted-fg underline-offset-2 hover:underline disabled:opacity-50",
38
38
  disabled: processing,
39
39
  onClick: onReset,
40
- children: t("filter.resetAll", "Reset all")
40
+ children: t("table.filter.resetAll", "Reset all")
41
41
  })]
42
42
  })
43
43
  });
@@ -49,7 +49,7 @@ function FilterMenu({ filters, values, processing, onChange, onSearch }) {
49
49
  asChild: true,
50
50
  children: /* @__PURE__ */ jsxs("button", {
51
51
  type: "button",
52
- "aria-label": t("filter.filters", "Filters"),
52
+ "aria-label": t("table.filter.filters", "Filters"),
53
53
  "data-test": "table-filters-menu",
54
54
  className: "relative inline-flex size-7 shrink-0 items-center justify-center rounded-lt-sm text-lt-muted-fg hover:bg-lt-muted hover:text-lt-fg disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-lt-ring/50 data-[state=open]:bg-lt-muted data-[state=open]:text-lt-fg",
55
55
  disabled: processing,
@@ -1 +1 @@
1
- {"version":3,"file":"filter-bar.js","names":[],"sources":["../../../resources/js/table/components/filter-bar.tsx"],"sourcesContent":["import { Icon } from \"@lattice-php/lattice/icons\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@lattice-php/lattice/core/components/popover\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\nimport type { FilterData, Option } from \"@lattice-php/lattice/types/generated\";\nimport { filterOptions, isActiveFilterValue, stringProp } from \"../filter-values\";\nimport { TableFilterControl } from \"./filter-controls\";\n\nexport function FilterBar({\n filters,\n values,\n processing,\n hasActiveFilters,\n onChange,\n onReset,\n}: {\n filters: FilterData[];\n values: Record<string, unknown>;\n processing: boolean;\n hasActiveFilters: boolean;\n onChange: (key: string, value: unknown) => void;\n onReset: () => void;\n}) {\n const { t } = useT(\"lattice\");\n const active = filters.filter((filter) => isActiveFilterValue(values[filter.key]));\n\n if (!hasActiveFilters) {\n return null;\n }\n\n return (\n <div className=\"border-b border-lt-border px-4 py-3\">\n <div className=\"flex flex-wrap items-center gap-2 text-sm\">\n {active.map((filter) => (\n <span\n key={filter.key}\n className=\"inline-flex items-center gap-1.5 rounded-lt-sm bg-lt-muted px-2 py-1\"\n >\n <span>\n {`${filter.label}: `}\n <span className=\"font-semibold\">{displayValue(filter, values[filter.key])}</span>\n </span>\n <button\n type=\"button\"\n data-test={`table-filter-chip-${filter.key}-remove`}\n className=\"inline-flex size-5 items-center justify-center rounded-lt-sm text-lt-muted-fg hover:bg-lt-border disabled:opacity-50\"\n disabled={processing}\n aria-label={t(\"filter.remove\", \"Remove {{label}} filter\", {\n label: filter.label,\n })}\n onClick={() => onChange(filter.key, undefined)}\n >\n <Icon name=\"x\" aria-hidden=\"true\" className=\"size-lt-icon-sm\" />\n </button>\n </span>\n ))}\n <button\n type=\"button\"\n data-test=\"table-filters-reset\"\n className=\"text-lt-muted-fg underline-offset-2 hover:underline disabled:opacity-50\"\n disabled={processing}\n onClick={onReset}\n >\n {t(\"filter.resetAll\", \"Reset all\")}\n </button>\n </div>\n </div>\n );\n}\n\nexport function FilterMenu({\n filters,\n values,\n processing,\n onChange,\n onSearch,\n}: {\n filters: FilterData[];\n values: Record<string, unknown>;\n processing: boolean;\n onChange: (key: string, value: unknown) => void;\n onSearch?: (filterKey: string, query: string) => Promise<Option[]>;\n}) {\n const { t } = useT(\"lattice\");\n const active = filters.filter((filter) => isActiveFilterValue(values[filter.key]));\n const filtersLabel = t(\"filter.filters\", \"Filters\");\n\n return (\n <Popover>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n aria-label={filtersLabel}\n data-test=\"table-filters-menu\"\n className=\"relative inline-flex size-7 shrink-0 items-center justify-center rounded-lt-sm text-lt-muted-fg hover:bg-lt-muted hover:text-lt-fg disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-lt-ring/50 data-[state=open]:bg-lt-muted data-[state=open]:text-lt-fg\"\n disabled={processing}\n >\n <Icon name=\"filter\" aria-hidden=\"true\" className=\"size-lt-icon-md\" />\n {active.length > 0 && (\n <span className=\"absolute -right-1 -top-1 inline-flex size-3.5 items-center justify-center rounded-full bg-lt-primary text-[10px] font-medium leading-none text-lt-primary-fg\">\n {active.length}\n </span>\n )}\n </button>\n </PopoverTrigger>\n <PopoverContent align=\"end\" className=\"w-80 p-4\">\n <div className=\"grid gap-3\">\n {filters.map((filter) => (\n <div key={filter.key} className=\"grid gap-1\">\n {filter.type !== \"toggle\" && (\n <span className=\"text-xs font-medium text-lt-muted-fg\">{filter.label}</span>\n )}\n <TableFilterControl\n filter={filter}\n value={values[filter.key]}\n processing={processing}\n onChange={(value) => onChange(filter.key, value)}\n onSearch={onSearch ? (query) => onSearch(filter.key, query) : undefined}\n />\n </div>\n ))}\n </div>\n </PopoverContent>\n </Popover>\n );\n}\n\nfunction optionLabel(filter: FilterData, value: string): string {\n return filterOptions(filter).find((option) => option.value === value)?.label ?? value;\n}\n\nfunction displayValue(filter: FilterData, value: unknown): string {\n if (filter.type === \"select\") {\n if (Array.isArray(value)) {\n return value.map((item) => optionLabel(filter, String(item))).join(\", \");\n }\n\n return optionLabel(filter, String(value));\n }\n\n if (filter.type === \"ternary\") {\n return value === \"true\"\n ? stringProp(filter, \"trueLabel\", \"True\")\n : stringProp(filter, \"falseLabel\", \"False\");\n }\n\n if (filter.type === \"date-range\" && value && typeof value === \"object\") {\n const range = value as { from?: string; until?: string };\n\n return [range.from, range.until].filter((part) => part).join(\" – \");\n }\n\n return String(value);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"filter-bar.js","names":[],"sources":["../../../resources/js/table/components/filter-bar.tsx"],"sourcesContent":["import { Icon } from \"@lattice-php/lattice/icons\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@lattice-php/lattice/core/components/popover\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\nimport type { FilterData, Option } from \"@lattice-php/lattice/types/generated\";\nimport { filterOptions, isActiveFilterValue, stringProp } from \"../filter-values\";\nimport { TableFilterControl } from \"./filter-controls\";\n\nexport function FilterBar({\n filters,\n values,\n processing,\n hasActiveFilters,\n onChange,\n onReset,\n}: {\n filters: FilterData[];\n values: Record<string, unknown>;\n processing: boolean;\n hasActiveFilters: boolean;\n onChange: (key: string, value: unknown) => void;\n onReset: () => void;\n}) {\n const { t } = useT(\"lattice\");\n const active = filters.filter((filter) => isActiveFilterValue(values[filter.key]));\n\n if (!hasActiveFilters) {\n return null;\n }\n\n return (\n <div className=\"border-b border-lt-border px-4 py-3\">\n <div className=\"flex flex-wrap items-center gap-2 text-sm\">\n {active.map((filter) => (\n <span\n key={filter.key}\n className=\"inline-flex items-center gap-1.5 rounded-lt-sm bg-lt-muted px-2 py-1\"\n >\n <span>\n {`${filter.label}: `}\n <span className=\"font-semibold\">{displayValue(filter, values[filter.key])}</span>\n </span>\n <button\n type=\"button\"\n data-test={`table-filter-chip-${filter.key}-remove`}\n className=\"inline-flex size-5 items-center justify-center rounded-lt-sm text-lt-muted-fg hover:bg-lt-border disabled:opacity-50\"\n disabled={processing}\n aria-label={t(\"table.filter.remove\", \"Remove {{label}} filter\", {\n label: filter.label,\n })}\n onClick={() => onChange(filter.key, undefined)}\n >\n <Icon name=\"x\" aria-hidden=\"true\" className=\"size-lt-icon-sm\" />\n </button>\n </span>\n ))}\n <button\n type=\"button\"\n data-test=\"table-filters-reset\"\n className=\"text-lt-muted-fg underline-offset-2 hover:underline disabled:opacity-50\"\n disabled={processing}\n onClick={onReset}\n >\n {t(\"table.filter.resetAll\", \"Reset all\")}\n </button>\n </div>\n </div>\n );\n}\n\nexport function FilterMenu({\n filters,\n values,\n processing,\n onChange,\n onSearch,\n}: {\n filters: FilterData[];\n values: Record<string, unknown>;\n processing: boolean;\n onChange: (key: string, value: unknown) => void;\n onSearch?: (filterKey: string, query: string) => Promise<Option[]>;\n}) {\n const { t } = useT(\"lattice\");\n const active = filters.filter((filter) => isActiveFilterValue(values[filter.key]));\n const filtersLabel = t(\"table.filter.filters\", \"Filters\");\n\n return (\n <Popover>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n aria-label={filtersLabel}\n data-test=\"table-filters-menu\"\n className=\"relative inline-flex size-7 shrink-0 items-center justify-center rounded-lt-sm text-lt-muted-fg hover:bg-lt-muted hover:text-lt-fg disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-lt-ring/50 data-[state=open]:bg-lt-muted data-[state=open]:text-lt-fg\"\n disabled={processing}\n >\n <Icon name=\"filter\" aria-hidden=\"true\" className=\"size-lt-icon-md\" />\n {active.length > 0 && (\n <span className=\"absolute -right-1 -top-1 inline-flex size-3.5 items-center justify-center rounded-full bg-lt-primary text-[10px] font-medium leading-none text-lt-primary-fg\">\n {active.length}\n </span>\n )}\n </button>\n </PopoverTrigger>\n <PopoverContent align=\"end\" className=\"w-80 p-4\">\n <div className=\"grid gap-3\">\n {filters.map((filter) => (\n <div key={filter.key} className=\"grid gap-1\">\n {filter.type !== \"toggle\" && (\n <span className=\"text-xs font-medium text-lt-muted-fg\">{filter.label}</span>\n )}\n <TableFilterControl\n filter={filter}\n value={values[filter.key]}\n processing={processing}\n onChange={(value) => onChange(filter.key, value)}\n onSearch={onSearch ? (query) => onSearch(filter.key, query) : undefined}\n />\n </div>\n ))}\n </div>\n </PopoverContent>\n </Popover>\n );\n}\n\nfunction optionLabel(filter: FilterData, value: string): string {\n return filterOptions(filter).find((option) => option.value === value)?.label ?? value;\n}\n\nfunction displayValue(filter: FilterData, value: unknown): string {\n if (filter.type === \"select\") {\n if (Array.isArray(value)) {\n return value.map((item) => optionLabel(filter, String(item))).join(\", \");\n }\n\n return optionLabel(filter, String(value));\n }\n\n if (filter.type === \"ternary\") {\n return value === \"true\"\n ? stringProp(filter, \"trueLabel\", \"True\")\n : stringProp(filter, \"falseLabel\", \"False\");\n }\n\n if (filter.type === \"date-range\" && value && typeof value === \"object\") {\n const range = value as { from?: string; until?: string };\n\n return [range.from, range.until].filter((part) => part).join(\" – \");\n }\n\n return String(value);\n}\n"],"mappings":"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"}
@@ -57,7 +57,7 @@ function TableFilterControl({ filter, value, processing, onChange, onSearch }) {
57
57
  }
58
58
  function SelectControl({ filter, value, processing, onChange }) {
59
59
  const { t } = useT("lattice");
60
- const placeholder = stringProp(filter, "placeholder", t("filter.all", "All"));
60
+ const placeholder = stringProp(filter, "placeholder", t("table.filter.all", "All"));
61
61
  return /* @__PURE__ */ jsxs("select", {
62
62
  "aria-label": filter.label,
63
63
  "data-test": `table-filter-${filter.key}`,
@@ -81,7 +81,7 @@ function MultiSelectControl({ filter, value, processing, onChange }) {
81
81
  function toggle(optionValue) {
82
82
  onChange(selected.includes(optionValue) ? selected.filter((item) => item !== optionValue) : [...selected, optionValue]);
83
83
  }
84
- const summary = selected.length === 0 ? stringProp(filter, "placeholder", t("filter.all", "All")) : t("filter.selectedCount", "{{amount}} selected", { amount: selected.length });
84
+ const summary = selected.length === 0 ? stringProp(filter, "placeholder", t("table.filter.all", "All")) : t("table.filter.selectedCount", "{{amount}} selected", { amount: selected.length });
85
85
  return /* @__PURE__ */ jsxs("div", {
86
86
  className: "relative",
87
87
  children: [/* @__PURE__ */ jsxs("button", {
@@ -126,7 +126,7 @@ function SearchableSelectControl({ filter, value, processing, onChange, onSearch
126
126
  const [loading, setLoading] = useState(false);
127
127
  const selected = multiple ? Array.isArray(value) ? value : [] : typeof value === "string" && value !== "" ? [value] : [];
128
128
  const labels = new Map([...filterOptions(filter), ...results].map((option) => [option.value, option.label]));
129
- const summary = selected.length === 0 ? stringProp(filter, "placeholder", t("filter.all", "All")) : multiple ? t("filter.selectedCount", "{{amount}} selected", { amount: selected.length }) : labels.get(selected[0]) ?? selected[0];
129
+ const summary = selected.length === 0 ? stringProp(filter, "placeholder", t("table.filter.all", "All")) : multiple ? t("table.filter.selectedCount", "{{amount}} selected", { amount: selected.length }) : labels.get(selected[0]) ?? selected[0];
130
130
  function search(query) {
131
131
  setLoading(true);
132
132
  onSearch(query).then((options) => {
@@ -146,7 +146,7 @@ function SearchableSelectControl({ filter, value, processing, onChange, onSearch
146
146
  open,
147
147
  onOpenChange: setOpen,
148
148
  options: results,
149
- searchLabel: t("filter.search", "Search"),
149
+ searchLabel: t("table.filter.search", "Search"),
150
150
  selected,
151
151
  testId: `table-filter-${filter.key}`,
152
152
  trigger: /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
@@ -167,9 +167,9 @@ function SearchableSelectControl({ filter, value, processing, onChange, onSearch
167
167
  }
168
168
  function TernaryControl({ filter, value, processing, onChange }) {
169
169
  const { t } = useT("lattice");
170
- const placeholder = stringProp(filter, "placeholder", t("filter.all", "All"));
171
- const trueLabel = stringProp(filter, "trueLabel", t("filter.true", "True"));
172
- const falseLabel = stringProp(filter, "falseLabel", t("filter.false", "False"));
170
+ const placeholder = stringProp(filter, "placeholder", t("table.filter.all", "All"));
171
+ const trueLabel = stringProp(filter, "trueLabel", t("table.filter.true", "True"));
172
+ const falseLabel = stringProp(filter, "falseLabel", t("table.filter.false", "False"));
173
173
  return /* @__PURE__ */ jsxs("select", {
174
174
  "aria-label": filter.label,
175
175
  "data-test": `table-filter-${filter.key}`,
@@ -207,7 +207,7 @@ function DateRangeControl({ filter, value, processing, onChange }) {
207
207
  children: [
208
208
  /* @__PURE__ */ jsx("input", {
209
209
  type: "date",
210
- "aria-label": t("filter.from", "{{label}} from", { label: filter.label }),
210
+ "aria-label": t("table.filter.from", "{{label}} from", { label: filter.label }),
211
211
  "data-test": `table-filter-${filter.key}-from`,
212
212
  className: fieldClass,
213
213
  disabled: processing,
@@ -224,7 +224,7 @@ function DateRangeControl({ filter, value, processing, onChange }) {
224
224
  }),
225
225
  /* @__PURE__ */ jsx("input", {
226
226
  type: "date",
227
- "aria-label": t("filter.until", "{{label}} until", { label: filter.label }),
227
+ "aria-label": t("table.filter.until", "{{label}} until", { label: filter.label }),
228
228
  "data-test": `table-filter-${filter.key}-until`,
229
229
  className: fieldClass,
230
230
  disabled: processing,
@@ -1 +1 @@
1
- {"version":3,"file":"filter-controls.js","names":[],"sources":["../../../resources/js/table/components/filter-controls.tsx"],"sourcesContent":["import { Icon } from \"@lattice-php/lattice/icons\";\nimport { useState } from \"react\";\nimport { Checkbox } from \"@lattice-php/lattice/core/components/checkbox\";\nimport { Combobox } from \"@lattice-php/lattice/core/components/combobox\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport type { FilterData, Option } from \"@lattice-php/lattice/types/generated\";\nimport { filterOptions, stringProp } from \"../filter-values\";\nimport { fieldClass } from \"./filter-value-input\";\n\nexport type DateRangeValue = { from?: string; until?: string };\n\nexport type FilterOptionSearch = (query: string) => Promise<Option[]>;\n\n/**\n * Renders the control for a single dedicated table filter, dispatching on the\n * filter's `type`. The current value comes from the table state; `onChange`\n * pushes a new value (an empty value clears the filter).\n */\nexport function TableFilterControl({\n filter,\n value,\n processing,\n onChange,\n onSearch,\n}: {\n filter: FilterData;\n value: unknown;\n processing: boolean;\n onChange: (value: unknown) => void;\n onSearch?: FilterOptionSearch;\n}) {\n switch (filter.type) {\n case \"select\":\n if (filter.props.searchable === true && onSearch) {\n return (\n <SearchableSelectControl\n filter={filter}\n value={value}\n processing={processing}\n onChange={onChange}\n onSearch={onSearch}\n />\n );\n }\n\n return filter.props.multiple === true ? (\n <MultiSelectControl\n filter={filter}\n value={value}\n processing={processing}\n onChange={onChange}\n />\n ) : (\n <SelectControl filter={filter} value={value} processing={processing} onChange={onChange} />\n );\n case \"ternary\":\n return (\n <TernaryControl filter={filter} value={value} processing={processing} onChange={onChange} />\n );\n case \"date-range\":\n return (\n <DateRangeControl\n filter={filter}\n value={value}\n processing={processing}\n onChange={onChange}\n />\n );\n case \"toggle\":\n return (\n <ToggleControl filter={filter} value={value} processing={processing} onChange={onChange} />\n );\n default:\n return null;\n }\n}\n\nfunction SelectControl({\n filter,\n value,\n processing,\n onChange,\n}: {\n filter: FilterData;\n value: unknown;\n processing: boolean;\n onChange: (value: unknown) => void;\n}) {\n const { t } = useT(\"lattice\");\n const placeholder = stringProp(filter, \"placeholder\", t(\"filter.all\", \"All\"));\n\n return (\n <select\n aria-label={filter.label}\n data-test={`table-filter-${filter.key}`}\n className={fieldClass}\n disabled={processing}\n value={typeof value === \"string\" ? value : \"\"}\n onChange={(event) => onChange(event.target.value)}\n >\n <option value=\"\">{placeholder}</option>\n {filterOptions(filter).map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n );\n}\n\nfunction MultiSelectControl({\n filter,\n value,\n processing,\n onChange,\n}: {\n filter: FilterData;\n value: unknown;\n processing: boolean;\n onChange: (value: unknown) => void;\n}) {\n const { t } = useT(\"lattice\");\n const [open, setOpen] = useState(false);\n const selected = Array.isArray(value) ? (value as string[]) : [];\n\n function toggle(optionValue: string): void {\n const next = selected.includes(optionValue)\n ? selected.filter((item) => item !== optionValue)\n : [...selected, optionValue];\n\n onChange(next);\n }\n\n const summary =\n selected.length === 0\n ? stringProp(filter, \"placeholder\", t(\"filter.all\", \"All\"))\n : t(\"filter.selectedCount\", \"{{amount}} selected\", { amount: selected.length });\n\n return (\n <div className=\"relative\">\n <button\n type=\"button\"\n aria-label={filter.label}\n aria-expanded={open}\n data-test={`table-filter-${filter.key}`}\n className={`${fieldClass} flex items-center justify-between gap-2`}\n disabled={processing}\n onClick={() => setOpen((current) => !current)}\n >\n <span className=\"truncate\">{summary}</span>\n <Icon name=\"chevron-down\" aria-hidden=\"true\" className=\"size-lt-icon-sm shrink-0\" />\n </button>\n {open && (\n <div className=\"absolute z-10 mt-1 min-w-full rounded-lt-sm border border-lt-border bg-lt-bg p-1 shadow-lt-md\">\n {filterOptions(filter).map((option) => (\n <label\n key={option.value}\n className=\"flex cursor-pointer items-center gap-2 rounded-lt-sm px-2 py-1.5 text-sm hover:bg-lt-muted\"\n >\n <Checkbox\n aria-label={option.label}\n data-test={`table-filter-${filter.key}-${option.value}`}\n checked={selected.includes(option.value)}\n disabled={processing}\n onCheckedChange={() => toggle(option.value)}\n />\n <span className=\"truncate\">{option.label}</span>\n </label>\n ))}\n </div>\n )}\n </div>\n );\n}\n\nfunction SearchableSelectControl({\n filter,\n value,\n processing,\n onChange,\n onSearch,\n}: {\n filter: FilterData;\n value: unknown;\n processing: boolean;\n onChange: (value: unknown) => void;\n onSearch: FilterOptionSearch;\n}) {\n const { t } = useT(\"lattice\");\n const multiple = filter.props.multiple === true;\n const [open, setOpen] = useState(false);\n const [results, setResults] = useState<Option[]>(() => filterOptions(filter));\n const [loading, setLoading] = useState(false);\n\n const selected = multiple\n ? Array.isArray(value)\n ? (value as string[])\n : []\n : typeof value === \"string\" && value !== \"\"\n ? [value]\n : [];\n\n const labels = new Map(\n [...filterOptions(filter), ...results].map((option) => [option.value, option.label]),\n );\n const summary =\n selected.length === 0\n ? stringProp(filter, \"placeholder\", t(\"filter.all\", \"All\"))\n : multiple\n ? t(\"filter.selectedCount\", \"{{amount}} selected\", { amount: selected.length })\n : (labels.get(selected[0]) ?? selected[0]);\n\n function search(query: string): void {\n setLoading(true);\n void onSearch(query).then((options) => {\n setResults(options);\n setLoading(false);\n });\n }\n\n function choose(optionValue: string): void {\n onChange(\n multiple\n ? selected.includes(optionValue)\n ? selected.filter((item) => item !== optionValue)\n : [...selected, optionValue]\n : optionValue,\n );\n }\n\n return (\n <Combobox\n contentClassName=\"w-60\"\n loading={loading}\n multiple={multiple}\n onSearch={search}\n onSelect={choose}\n open={open}\n onOpenChange={setOpen}\n options={results}\n searchLabel={t(\"filter.search\", \"Search\")}\n selected={selected}\n testId={`table-filter-${filter.key}`}\n trigger={\n <>\n <span className=\"truncate\">{summary}</span>\n <Icon name=\"chevron-down\" aria-hidden=\"true\" className=\"size-lt-icon-sm shrink-0\" />\n </>\n }\n triggerClassName={cn(fieldClass, \"flex items-center justify-between gap-2\")}\n triggerProps={{\n \"aria-label\": filter.label,\n \"data-test\": `table-filter-${filter.key}`,\n disabled: processing,\n }}\n />\n );\n}\n\nfunction TernaryControl({\n filter,\n value,\n processing,\n onChange,\n}: {\n filter: FilterData;\n value: unknown;\n processing: boolean;\n onChange: (value: unknown) => void;\n}) {\n const { t } = useT(\"lattice\");\n const placeholder = stringProp(filter, \"placeholder\", t(\"filter.all\", \"All\"));\n const trueLabel = stringProp(filter, \"trueLabel\", t(\"filter.true\", \"True\"));\n const falseLabel = stringProp(filter, \"falseLabel\", t(\"filter.false\", \"False\"));\n\n return (\n <select\n aria-label={filter.label}\n data-test={`table-filter-${filter.key}`}\n className={fieldClass}\n disabled={processing}\n value={typeof value === \"string\" ? value : \"\"}\n onChange={(event) => onChange(event.target.value)}\n >\n <option value=\"\">{placeholder}</option>\n <option value=\"true\">{trueLabel}</option>\n <option value=\"false\">{falseLabel}</option>\n </select>\n );\n}\n\nfunction DateRangeControl({\n filter,\n value,\n processing,\n onChange,\n}: {\n filter: FilterData;\n value: unknown;\n processing: boolean;\n onChange: (value: unknown) => void;\n}) {\n const { t } = useT(\"lattice\");\n const range: DateRangeValue = value && typeof value === \"object\" ? (value as DateRangeValue) : {};\n\n function commit(next: DateRangeValue): void {\n onChange({ from: next.from ?? \"\", until: next.until ?? \"\" });\n }\n\n return (\n <div className=\"flex items-center gap-1\">\n <input\n type=\"date\"\n aria-label={t(\"filter.from\", \"{{label}} from\", { label: filter.label })}\n data-test={`table-filter-${filter.key}-from`}\n className={fieldClass}\n disabled={processing}\n value={range.from ?? \"\"}\n onChange={(event) => commit({ ...range, from: event.target.value })}\n />\n <span className=\"text-lt-muted-fg\" aria-hidden=\"true\">\n –\n </span>\n <input\n type=\"date\"\n aria-label={t(\"filter.until\", \"{{label}} until\", { label: filter.label })}\n data-test={`table-filter-${filter.key}-until`}\n className={fieldClass}\n disabled={processing}\n value={range.until ?? \"\"}\n onChange={(event) => commit({ ...range, until: event.target.value })}\n />\n </div>\n );\n}\n\nfunction ToggleControl({\n filter,\n value,\n processing,\n onChange,\n}: {\n filter: FilterData;\n value: unknown;\n processing: boolean;\n onChange: (value: unknown) => void;\n}) {\n const checked = value === \"1\" || value === true || value === \"true\";\n\n return (\n <label className=\"flex h-9 cursor-pointer items-center gap-2 text-sm\">\n <Checkbox\n aria-label={filter.label}\n data-test={`table-filter-${filter.key}`}\n checked={checked}\n disabled={processing}\n onCheckedChange={(next) => onChange(next === true ? \"1\" : \"\")}\n />\n <span>{filter.label}</span>\n </label>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"filter-controls.js","names":[],"sources":["../../../resources/js/table/components/filter-controls.tsx"],"sourcesContent":["import { Icon } from \"@lattice-php/lattice/icons\";\nimport { useState } from \"react\";\nimport { Checkbox } from \"@lattice-php/lattice/core/components/checkbox\";\nimport { Combobox } from \"@lattice-php/lattice/core/components/combobox\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport type { FilterData, Option } from \"@lattice-php/lattice/types/generated\";\nimport { filterOptions, stringProp } from \"../filter-values\";\nimport { fieldClass } from \"./filter-value-input\";\n\nexport type DateRangeValue = { from?: string; until?: string };\n\nexport type FilterOptionSearch = (query: string) => Promise<Option[]>;\n\n/**\n * Renders the control for a single dedicated table filter, dispatching on the\n * filter's `type`. The current value comes from the table state; `onChange`\n * pushes a new value (an empty value clears the filter).\n */\nexport function TableFilterControl({\n filter,\n value,\n processing,\n onChange,\n onSearch,\n}: {\n filter: FilterData;\n value: unknown;\n processing: boolean;\n onChange: (value: unknown) => void;\n onSearch?: FilterOptionSearch;\n}) {\n switch (filter.type) {\n case \"select\":\n if (filter.props.searchable === true && onSearch) {\n return (\n <SearchableSelectControl\n filter={filter}\n value={value}\n processing={processing}\n onChange={onChange}\n onSearch={onSearch}\n />\n );\n }\n\n return filter.props.multiple === true ? (\n <MultiSelectControl\n filter={filter}\n value={value}\n processing={processing}\n onChange={onChange}\n />\n ) : (\n <SelectControl filter={filter} value={value} processing={processing} onChange={onChange} />\n );\n case \"ternary\":\n return (\n <TernaryControl filter={filter} value={value} processing={processing} onChange={onChange} />\n );\n case \"date-range\":\n return (\n <DateRangeControl\n filter={filter}\n value={value}\n processing={processing}\n onChange={onChange}\n />\n );\n case \"toggle\":\n return (\n <ToggleControl filter={filter} value={value} processing={processing} onChange={onChange} />\n );\n default:\n return null;\n }\n}\n\nfunction SelectControl({\n filter,\n value,\n processing,\n onChange,\n}: {\n filter: FilterData;\n value: unknown;\n processing: boolean;\n onChange: (value: unknown) => void;\n}) {\n const { t } = useT(\"lattice\");\n const placeholder = stringProp(filter, \"placeholder\", t(\"table.filter.all\", \"All\"));\n\n return (\n <select\n aria-label={filter.label}\n data-test={`table-filter-${filter.key}`}\n className={fieldClass}\n disabled={processing}\n value={typeof value === \"string\" ? value : \"\"}\n onChange={(event) => onChange(event.target.value)}\n >\n <option value=\"\">{placeholder}</option>\n {filterOptions(filter).map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n );\n}\n\nfunction MultiSelectControl({\n filter,\n value,\n processing,\n onChange,\n}: {\n filter: FilterData;\n value: unknown;\n processing: boolean;\n onChange: (value: unknown) => void;\n}) {\n const { t } = useT(\"lattice\");\n const [open, setOpen] = useState(false);\n const selected = Array.isArray(value) ? (value as string[]) : [];\n\n function toggle(optionValue: string): void {\n const next = selected.includes(optionValue)\n ? selected.filter((item) => item !== optionValue)\n : [...selected, optionValue];\n\n onChange(next);\n }\n\n const summary =\n selected.length === 0\n ? stringProp(filter, \"placeholder\", t(\"table.filter.all\", \"All\"))\n : t(\"table.filter.selectedCount\", \"{{amount}} selected\", { amount: selected.length });\n\n return (\n <div className=\"relative\">\n <button\n type=\"button\"\n aria-label={filter.label}\n aria-expanded={open}\n data-test={`table-filter-${filter.key}`}\n className={`${fieldClass} flex items-center justify-between gap-2`}\n disabled={processing}\n onClick={() => setOpen((current) => !current)}\n >\n <span className=\"truncate\">{summary}</span>\n <Icon name=\"chevron-down\" aria-hidden=\"true\" className=\"size-lt-icon-sm shrink-0\" />\n </button>\n {open && (\n <div className=\"absolute z-10 mt-1 min-w-full rounded-lt-sm border border-lt-border bg-lt-bg p-1 shadow-lt-md\">\n {filterOptions(filter).map((option) => (\n <label\n key={option.value}\n className=\"flex cursor-pointer items-center gap-2 rounded-lt-sm px-2 py-1.5 text-sm hover:bg-lt-muted\"\n >\n <Checkbox\n aria-label={option.label}\n data-test={`table-filter-${filter.key}-${option.value}`}\n checked={selected.includes(option.value)}\n disabled={processing}\n onCheckedChange={() => toggle(option.value)}\n />\n <span className=\"truncate\">{option.label}</span>\n </label>\n ))}\n </div>\n )}\n </div>\n );\n}\n\nfunction SearchableSelectControl({\n filter,\n value,\n processing,\n onChange,\n onSearch,\n}: {\n filter: FilterData;\n value: unknown;\n processing: boolean;\n onChange: (value: unknown) => void;\n onSearch: FilterOptionSearch;\n}) {\n const { t } = useT(\"lattice\");\n const multiple = filter.props.multiple === true;\n const [open, setOpen] = useState(false);\n const [results, setResults] = useState<Option[]>(() => filterOptions(filter));\n const [loading, setLoading] = useState(false);\n\n const selected = multiple\n ? Array.isArray(value)\n ? (value as string[])\n : []\n : typeof value === \"string\" && value !== \"\"\n ? [value]\n : [];\n\n const labels = new Map(\n [...filterOptions(filter), ...results].map((option) => [option.value, option.label]),\n );\n const summary =\n selected.length === 0\n ? stringProp(filter, \"placeholder\", t(\"table.filter.all\", \"All\"))\n : multiple\n ? t(\"table.filter.selectedCount\", \"{{amount}} selected\", { amount: selected.length })\n : (labels.get(selected[0]) ?? selected[0]);\n\n function search(query: string): void {\n setLoading(true);\n void onSearch(query).then((options) => {\n setResults(options);\n setLoading(false);\n });\n }\n\n function choose(optionValue: string): void {\n onChange(\n multiple\n ? selected.includes(optionValue)\n ? selected.filter((item) => item !== optionValue)\n : [...selected, optionValue]\n : optionValue,\n );\n }\n\n return (\n <Combobox\n contentClassName=\"w-60\"\n loading={loading}\n multiple={multiple}\n onSearch={search}\n onSelect={choose}\n open={open}\n onOpenChange={setOpen}\n options={results}\n searchLabel={t(\"table.filter.search\", \"Search\")}\n selected={selected}\n testId={`table-filter-${filter.key}`}\n trigger={\n <>\n <span className=\"truncate\">{summary}</span>\n <Icon name=\"chevron-down\" aria-hidden=\"true\" className=\"size-lt-icon-sm shrink-0\" />\n </>\n }\n triggerClassName={cn(fieldClass, \"flex items-center justify-between gap-2\")}\n triggerProps={{\n \"aria-label\": filter.label,\n \"data-test\": `table-filter-${filter.key}`,\n disabled: processing,\n }}\n />\n );\n}\n\nfunction TernaryControl({\n filter,\n value,\n processing,\n onChange,\n}: {\n filter: FilterData;\n value: unknown;\n processing: boolean;\n onChange: (value: unknown) => void;\n}) {\n const { t } = useT(\"lattice\");\n const placeholder = stringProp(filter, \"placeholder\", t(\"table.filter.all\", \"All\"));\n const trueLabel = stringProp(filter, \"trueLabel\", t(\"table.filter.true\", \"True\"));\n const falseLabel = stringProp(filter, \"falseLabel\", t(\"table.filter.false\", \"False\"));\n\n return (\n <select\n aria-label={filter.label}\n data-test={`table-filter-${filter.key}`}\n className={fieldClass}\n disabled={processing}\n value={typeof value === \"string\" ? value : \"\"}\n onChange={(event) => onChange(event.target.value)}\n >\n <option value=\"\">{placeholder}</option>\n <option value=\"true\">{trueLabel}</option>\n <option value=\"false\">{falseLabel}</option>\n </select>\n );\n}\n\nfunction DateRangeControl({\n filter,\n value,\n processing,\n onChange,\n}: {\n filter: FilterData;\n value: unknown;\n processing: boolean;\n onChange: (value: unknown) => void;\n}) {\n const { t } = useT(\"lattice\");\n const range: DateRangeValue = value && typeof value === \"object\" ? (value as DateRangeValue) : {};\n\n function commit(next: DateRangeValue): void {\n onChange({ from: next.from ?? \"\", until: next.until ?? \"\" });\n }\n\n return (\n <div className=\"flex items-center gap-1\">\n <input\n type=\"date\"\n aria-label={t(\"table.filter.from\", \"{{label}} from\", { label: filter.label })}\n data-test={`table-filter-${filter.key}-from`}\n className={fieldClass}\n disabled={processing}\n value={range.from ?? \"\"}\n onChange={(event) => commit({ ...range, from: event.target.value })}\n />\n <span className=\"text-lt-muted-fg\" aria-hidden=\"true\">\n –\n </span>\n <input\n type=\"date\"\n aria-label={t(\"table.filter.until\", \"{{label}} until\", { label: filter.label })}\n data-test={`table-filter-${filter.key}-until`}\n className={fieldClass}\n disabled={processing}\n value={range.until ?? \"\"}\n onChange={(event) => commit({ ...range, until: event.target.value })}\n />\n </div>\n );\n}\n\nfunction ToggleControl({\n filter,\n value,\n processing,\n onChange,\n}: {\n filter: FilterData;\n value: unknown;\n processing: boolean;\n onChange: (value: unknown) => void;\n}) {\n const checked = value === \"1\" || value === true || value === \"true\";\n\n return (\n <label className=\"flex h-9 cursor-pointer items-center gap-2 text-sm\">\n <Checkbox\n aria-label={filter.label}\n data-test={`table-filter-${filter.key}`}\n checked={checked}\n disabled={processing}\n onCheckedChange={(next) => onChange(next === true ? \"1\" : \"\")}\n />\n <span>{filter.label}</span>\n </label>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;AAmBA,SAAgB,mBAAmB,EACjC,QACA,OACA,YACA,UACA,YAOC;CACD,QAAQ,OAAO,MAAf;EACE,KAAK;GACH,IAAI,OAAO,MAAM,eAAe,QAAQ,UACtC,OACE,oBAAC,yBAAD;IACU;IACD;IACK;IACF;IACA;GACX,CAAA;GAIL,OAAO,OAAO,MAAM,aAAa,OAC/B,oBAAC,oBAAD;IACU;IACD;IACK;IACF;GACX,CAAA,IAED,oBAAC,eAAD;IAAuB;IAAe;IAAmB;IAAsB;GAAW,CAAA;EAE9F,KAAK,WACH,OACE,oBAAC,gBAAD;GAAwB;GAAe;GAAmB;GAAsB;EAAW,CAAA;EAE/F,KAAK,cACH,OACE,oBAAC,kBAAD;GACU;GACD;GACK;GACF;EACX,CAAA;EAEL,KAAK,UACH,OACE,oBAAC,eAAD;GAAuB;GAAe;GAAmB;GAAsB;EAAW,CAAA;EAE9F,SACE,OAAO;CACX;AACF;AAEA,SAAS,cAAc,EACrB,QACA,OACA,YACA,YAMC;CACD,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,MAAM,cAAc,WAAW,QAAQ,eAAe,EAAE,oBAAoB,KAAK,CAAC;CAElF,OACE,qBAAC,UAAD;EACE,cAAY,OAAO;EACnB,aAAW,gBAAgB,OAAO;EAClC,WAAW;EACX,UAAU;EACV,OAAO,OAAO,UAAU,WAAW,QAAQ;EAC3C,WAAW,UAAU,SAAS,MAAM,OAAO,KAAK;YANlD,CAQE,oBAAC,UAAD;GAAQ,OAAM;aAAI;EAAoB,CAAA,GACrC,cAAc,MAAM,EAAE,KAAK,WAC1B,oBAAC,UAAD;GAA2B,OAAO,OAAO;aACtC,OAAO;EACF,GAFK,OAAO,KAEZ,CACT,CACK;;AAEZ;AAEA,SAAS,mBAAmB,EAC1B,QACA,OACA,YACA,YAMC;CACD,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,MAAM,CAAC,MAAM,WAAW,SAAS,KAAK;CACtC,MAAM,WAAW,MAAM,QAAQ,KAAK,IAAK,QAAqB,CAAC;CAE/D,SAAS,OAAO,aAA2B;EAKzC,SAJa,SAAS,SAAS,WAAW,IACtC,SAAS,QAAQ,SAAS,SAAS,WAAW,IAC9C,CAAC,GAAG,UAAU,WAAW,CAEhB;CACf;CAEA,MAAM,UACJ,SAAS,WAAW,IAChB,WAAW,QAAQ,eAAe,EAAE,oBAAoB,KAAK,CAAC,IAC9D,EAAE,8BAA8B,uBAAuB,EAAE,QAAQ,SAAS,OAAO,CAAC;CAExF,OACE,qBAAC,OAAD;EAAK,WAAU;YAAf,CACE,qBAAC,UAAD;GACE,MAAK;GACL,cAAY,OAAO;GACnB,iBAAe;GACf,aAAW,gBAAgB,OAAO;GAClC,WAAW,GAAG,WAAW;GACzB,UAAU;GACV,eAAe,SAAS,YAAY,CAAC,OAAO;aAP9C,CASE,oBAAC,QAAD;IAAM,WAAU;cAAY;GAAc,CAAA,GAC1C,oBAAC,MAAD;IAAM,MAAK;IAAe,eAAY;IAAO,WAAU;GAA4B,CAAA,CAC7E;MACP,QACC,oBAAC,OAAD;GAAK,WAAU;aACZ,cAAc,MAAM,EAAE,KAAK,WAC1B,qBAAC,SAAD;IAEE,WAAU;cAFZ,CAIE,oBAAC,UAAD;KACE,cAAY,OAAO;KACnB,aAAW,gBAAgB,OAAO,IAAI,GAAG,OAAO;KAChD,SAAS,SAAS,SAAS,OAAO,KAAK;KACvC,UAAU;KACV,uBAAuB,OAAO,OAAO,KAAK;IAC3C,CAAA,GACD,oBAAC,QAAD;KAAM,WAAU;eAAY,OAAO;IAAY,CAAA,CAC1C;MAXA,OAAO,KAWP,CACR;EACE,CAAA,CAEJ;;AAET;AAEA,SAAS,wBAAwB,EAC/B,QACA,OACA,YACA,UACA,YAOC;CACD,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,MAAM,WAAW,OAAO,MAAM,aAAa;CAC3C,MAAM,CAAC,MAAM,WAAW,SAAS,KAAK;CACtC,MAAM,CAAC,SAAS,cAAc,eAAyB,cAAc,MAAM,CAAC;CAC5E,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAE5C,MAAM,WAAW,WACb,MAAM,QAAQ,KAAK,IAChB,QACD,CAAC,IACH,OAAO,UAAU,YAAY,UAAU,KACrC,CAAC,KAAK,IACN,CAAC;CAEP,MAAM,SAAS,IAAI,IACjB,CAAC,GAAG,cAAc,MAAM,GAAG,GAAG,OAAO,EAAE,KAAK,WAAW,CAAC,OAAO,OAAO,OAAO,KAAK,CAAC,CACrF;CACA,MAAM,UACJ,SAAS,WAAW,IAChB,WAAW,QAAQ,eAAe,EAAE,oBAAoB,KAAK,CAAC,IAC9D,WACE,EAAE,8BAA8B,uBAAuB,EAAE,QAAQ,SAAS,OAAO,CAAC,IACjF,OAAO,IAAI,SAAS,EAAE,KAAK,SAAS;CAE7C,SAAS,OAAO,OAAqB;EACnC,WAAW,IAAI;EACf,SAAc,KAAK,EAAE,MAAM,YAAY;GACrC,WAAW,OAAO;GAClB,WAAW,KAAK;EAClB,CAAC;CACH;CAEA,SAAS,OAAO,aAA2B;EACzC,SACE,WACI,SAAS,SAAS,WAAW,IAC3B,SAAS,QAAQ,SAAS,SAAS,WAAW,IAC9C,CAAC,GAAG,UAAU,WAAW,IAC3B,WACN;CACF;CAEA,OACE,oBAAC,UAAD;EACE,kBAAiB;EACR;EACC;EACV,UAAU;EACV,UAAU;EACJ;EACN,cAAc;EACd,SAAS;EACT,aAAa,EAAE,uBAAuB,QAAQ;EACpC;EACV,QAAQ,gBAAgB,OAAO;EAC/B,SACE,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,QAAD;GAAM,WAAU;aAAY;EAAc,CAAA,GAC1C,oBAAC,MAAD;GAAM,MAAK;GAAe,eAAY;GAAO,WAAU;EAA4B,CAAA,CACnF,EAAA,CAAA;EAEJ,kBAAkB,GAAG,YAAY,yCAAyC;EAC1E,cAAc;GACZ,cAAc,OAAO;GACrB,aAAa,gBAAgB,OAAO;GACpC,UAAU;EACZ;CACD,CAAA;AAEL;AAEA,SAAS,eAAe,EACtB,QACA,OACA,YACA,YAMC;CACD,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,MAAM,cAAc,WAAW,QAAQ,eAAe,EAAE,oBAAoB,KAAK,CAAC;CAClF,MAAM,YAAY,WAAW,QAAQ,aAAa,EAAE,qBAAqB,MAAM,CAAC;CAChF,MAAM,aAAa,WAAW,QAAQ,cAAc,EAAE,sBAAsB,OAAO,CAAC;CAEpF,OACE,qBAAC,UAAD;EACE,cAAY,OAAO;EACnB,aAAW,gBAAgB,OAAO;EAClC,WAAW;EACX,UAAU;EACV,OAAO,OAAO,UAAU,WAAW,QAAQ;EAC3C,WAAW,UAAU,SAAS,MAAM,OAAO,KAAK;YANlD;GAQE,oBAAC,UAAD;IAAQ,OAAM;cAAI;GAAoB,CAAA;GACtC,oBAAC,UAAD;IAAQ,OAAM;cAAQ;GAAkB,CAAA;GACxC,oBAAC,UAAD;IAAQ,OAAM;cAAS;GAAmB,CAAA;EACpC;;AAEZ;AAEA,SAAS,iBAAiB,EACxB,QACA,OACA,YACA,YAMC;CACD,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,MAAM,QAAwB,SAAS,OAAO,UAAU,WAAY,QAA2B,CAAC;CAEhG,SAAS,OAAO,MAA4B;EAC1C,SAAS;GAAE,MAAM,KAAK,QAAQ;GAAI,OAAO,KAAK,SAAS;EAAG,CAAC;CAC7D;CAEA,OACE,qBAAC,OAAD;EAAK,WAAU;YAAf;GACE,oBAAC,SAAD;IACE,MAAK;IACL,cAAY,EAAE,qBAAqB,kBAAkB,EAAE,OAAO,OAAO,MAAM,CAAC;IAC5E,aAAW,gBAAgB,OAAO,IAAI;IACtC,WAAW;IACX,UAAU;IACV,OAAO,MAAM,QAAQ;IACrB,WAAW,UAAU,OAAO;KAAE,GAAG;KAAO,MAAM,MAAM,OAAO;IAAM,CAAC;GACnE,CAAA;GACD,oBAAC,QAAD;IAAM,WAAU;IAAmB,eAAY;cAAO;GAEhD,CAAA;GACN,oBAAC,SAAD;IACE,MAAK;IACL,cAAY,EAAE,sBAAsB,mBAAmB,EAAE,OAAO,OAAO,MAAM,CAAC;IAC9E,aAAW,gBAAgB,OAAO,IAAI;IACtC,WAAW;IACX,UAAU;IACV,OAAO,MAAM,SAAS;IACtB,WAAW,UAAU,OAAO;KAAE,GAAG;KAAO,OAAO,MAAM,OAAO;IAAM,CAAC;GACpE,CAAA;EACE;;AAET;AAEA,SAAS,cAAc,EACrB,QACA,OACA,YACA,YAMC;CACD,MAAM,UAAU,UAAU,OAAO,UAAU,QAAQ,UAAU;CAE7D,OACE,qBAAC,SAAD;EAAO,WAAU;YAAjB,CACE,oBAAC,UAAD;GACE,cAAY,OAAO;GACnB,aAAW,gBAAgB,OAAO;GACzB;GACT,UAAU;GACV,kBAAkB,SAAS,SAAS,SAAS,OAAO,MAAM,EAAE;EAC7D,CAAA,GACD,oBAAC,QAAD,EAAA,UAAO,OAAO,MAAY,CAAA,CACrB;;AAEX"}
@@ -20,7 +20,7 @@ function FilterStackBar({ filters, columnsByKey, processing, onRemove }) {
20
20
  "data-test": `filter-chip-${clause.field}-remove`,
21
21
  className: "inline-flex size-5 items-center justify-center rounded-lt-sm text-lt-muted-fg hover:bg-lt-muted disabled:opacity-50",
22
22
  disabled: processing,
23
- "aria-label": t("filter.remove", "Remove {{label}} filter", { label }),
23
+ "aria-label": t("table.filter.remove", "Remove {{label}} filter", { label }),
24
24
  onClick: () => onRemove(index),
25
25
  children: /* @__PURE__ */ jsx(Icon, {
26
26
  name: "x",
@@ -1 +1 @@
1
- {"version":3,"file":"filter-stack-bar.js","names":[],"sources":["../../../resources/js/table/components/filter-stack-bar.tsx"],"sourcesContent":["import { Icon } from \"@lattice-php/lattice/icons\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\nimport { operatorLabel, VALUELESS_FILTER_OPERATORS } from \"../query\";\nimport type { FilterClause, TableColumn } from \"../types\";\n\nexport function FilterStackBar({\n filters,\n columnsByKey,\n processing,\n onRemove,\n}: {\n filters: FilterClause[];\n columnsByKey: Map<string, TableColumn>;\n processing: boolean;\n onRemove: (index: number) => void;\n}) {\n const { t } = useT(\"lattice\");\n\n return (\n <div className=\"flex flex-wrap items-center gap-4 border-b border-lt-border px-4 py-2.5 text-sm\">\n {filters.map((clause, index) => {\n const label = columnsByKey.get(clause.field)?.label ?? clause.field;\n const valueless = VALUELESS_FILTER_OPERATORS.has(clause.operator);\n\n return (\n <span\n key={`${clause.field}-${clause.operator}-${index}`}\n className=\"inline-flex items-center gap-1.5\"\n >\n <span>\n {valueless\n ? `${label} ${operatorLabel(clause.operator)}`\n : `${label} ${operatorLabel(clause.operator)}: `}\n {!valueless && <span className=\"font-semibold\">{clause.value}</span>}\n </span>\n <button\n type=\"button\"\n data-test={`filter-chip-${clause.field}-remove`}\n className=\"inline-flex size-5 items-center justify-center rounded-lt-sm text-lt-muted-fg hover:bg-lt-muted disabled:opacity-50\"\n disabled={processing}\n aria-label={t(\"filter.remove\", \"Remove {{label}} filter\", { label })}\n onClick={() => onRemove(index)}\n >\n <Icon name=\"x\" aria-hidden=\"true\" className=\"size-lt-icon-sm\" />\n </button>\n </span>\n );\n })}\n </div>\n );\n}\n"],"mappings":";;;;;AAKA,SAAgB,eAAe,EAC7B,SACA,cACA,YACA,YAMC;CACD,MAAM,EAAE,MAAM,KAAK,SAAS;CAE5B,OACE,oBAAC,OAAD;EAAK,WAAU;YACZ,QAAQ,KAAK,QAAQ,UAAU;GAC9B,MAAM,QAAQ,aAAa,IAAI,OAAO,KAAK,GAAG,SAAS,OAAO;GAC9D,MAAM,YAAY,2BAA2B,IAAI,OAAO,QAAQ;GAEhE,OACE,qBAAC,QAAD;IAEE,WAAU;cAFZ,CAIE,qBAAC,QAAD,EAAA,UAAA,CACG,YACG,GAAG,MAAM,GAAG,cAAc,OAAO,QAAQ,MACzC,GAAG,MAAM,GAAG,cAAc,OAAO,QAAQ,EAAE,KAC9C,CAAC,aAAa,oBAAC,QAAD;KAAM,WAAU;eAAiB,OAAO;IAAY,CAAA,CAC/D,EAAA,CAAA,GACN,oBAAC,UAAD;KACE,MAAK;KACL,aAAW,eAAe,OAAO,MAAM;KACvC,WAAU;KACV,UAAU;KACV,cAAY,EAAE,iBAAiB,2BAA2B,EAAE,MAAM,CAAC;KACnE,eAAe,SAAS,KAAK;eAE7B,oBAAC,MAAD;MAAM,MAAK;MAAI,eAAY;MAAO,WAAU;KAAmB,CAAA;IACzD,CAAA,CACJ;MAnBC,GAAG,OAAO,MAAM,GAAG,OAAO,SAAS,GAAG,OAmBvC;EAEV,CAAC;CACE,CAAA;AAET"}
1
+ {"version":3,"file":"filter-stack-bar.js","names":[],"sources":["../../../resources/js/table/components/filter-stack-bar.tsx"],"sourcesContent":["import { Icon } from \"@lattice-php/lattice/icons\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\nimport { operatorLabel, VALUELESS_FILTER_OPERATORS } from \"../query\";\nimport type { FilterClause, TableColumn } from \"../types\";\n\nexport function FilterStackBar({\n filters,\n columnsByKey,\n processing,\n onRemove,\n}: {\n filters: FilterClause[];\n columnsByKey: Map<string, TableColumn>;\n processing: boolean;\n onRemove: (index: number) => void;\n}) {\n const { t } = useT(\"lattice\");\n\n return (\n <div className=\"flex flex-wrap items-center gap-4 border-b border-lt-border px-4 py-2.5 text-sm\">\n {filters.map((clause, index) => {\n const label = columnsByKey.get(clause.field)?.label ?? clause.field;\n const valueless = VALUELESS_FILTER_OPERATORS.has(clause.operator);\n\n return (\n <span\n key={`${clause.field}-${clause.operator}-${index}`}\n className=\"inline-flex items-center gap-1.5\"\n >\n <span>\n {valueless\n ? `${label} ${operatorLabel(clause.operator)}`\n : `${label} ${operatorLabel(clause.operator)}: `}\n {!valueless && <span className=\"font-semibold\">{clause.value}</span>}\n </span>\n <button\n type=\"button\"\n data-test={`filter-chip-${clause.field}-remove`}\n className=\"inline-flex size-5 items-center justify-center rounded-lt-sm text-lt-muted-fg hover:bg-lt-muted disabled:opacity-50\"\n disabled={processing}\n aria-label={t(\"table.filter.remove\", \"Remove {{label}} filter\", { label })}\n onClick={() => onRemove(index)}\n >\n <Icon name=\"x\" aria-hidden=\"true\" className=\"size-lt-icon-sm\" />\n </button>\n </span>\n );\n })}\n </div>\n );\n}\n"],"mappings":";;;;;AAKA,SAAgB,eAAe,EAC7B,SACA,cACA,YACA,YAMC;CACD,MAAM,EAAE,MAAM,KAAK,SAAS;CAE5B,OACE,oBAAC,OAAD;EAAK,WAAU;YACZ,QAAQ,KAAK,QAAQ,UAAU;GAC9B,MAAM,QAAQ,aAAa,IAAI,OAAO,KAAK,GAAG,SAAS,OAAO;GAC9D,MAAM,YAAY,2BAA2B,IAAI,OAAO,QAAQ;GAEhE,OACE,qBAAC,QAAD;IAEE,WAAU;cAFZ,CAIE,qBAAC,QAAD,EAAA,UAAA,CACG,YACG,GAAG,MAAM,GAAG,cAAc,OAAO,QAAQ,MACzC,GAAG,MAAM,GAAG,cAAc,OAAO,QAAQ,EAAE,KAC9C,CAAC,aAAa,oBAAC,QAAD;KAAM,WAAU;eAAiB,OAAO;IAAY,CAAA,CAC/D,EAAA,CAAA,GACN,oBAAC,UAAD;KACE,MAAK;KACL,aAAW,eAAe,OAAO,MAAM;KACvC,WAAU;KACV,UAAU;KACV,cAAY,EAAE,uBAAuB,2BAA2B,EAAE,MAAM,CAAC;KACzE,eAAe,SAAS,KAAK;eAE7B,oBAAC,MAAD;MAAM,MAAK;MAAI,eAAY;MAAO,WAAU;KAAmB,CAAA;IACzD,CAAA,CACJ;MAnBC,GAAG,OAAO,MAAM,GAAG,OAAO,SAAS,GAAG,OAmBvC;EAEV,CAAC;CACE,CAAA;AAET"}
@@ -8,7 +8,7 @@ var fieldClass = controlSurface({ density: "compact" });
8
8
  function FilterValueInput({ type, label, value, processing, withSearchIcon = false, grouped = false, ariaLabel, testId, onCommit, onClear }) {
9
9
  const { t } = useT("lattice");
10
10
  const [draft, setDraft] = useState(value);
11
- const inputLabel = ariaLabel ?? t("filter.filterBy", "Filter {{label}}", { label });
11
+ const inputLabel = ariaLabel ?? t("table.filter.filterBy", "Filter {{label}}", { label });
12
12
  const groupedClass = grouped ? "rounded-r-none" : "";
13
13
  useEffect(() => {
14
14
  setDraft(value);
@@ -23,15 +23,15 @@ function FilterValueInput({ type, label, value, processing, withSearchIcon = fal
23
23
  children: [
24
24
  /* @__PURE__ */ jsx("option", {
25
25
  value: "",
26
- children: t("filter.all", "All")
26
+ children: t("table.filter.all", "All")
27
27
  }),
28
28
  /* @__PURE__ */ jsx("option", {
29
29
  value: "true",
30
- children: t("filter.true", "True")
30
+ children: t("table.filter.true", "True")
31
31
  }),
32
32
  /* @__PURE__ */ jsx("option", {
33
33
  value: "false",
34
- children: t("filter.false", "False")
34
+ children: t("table.filter.false", "False")
35
35
  })
36
36
  ]
37
37
  });
@@ -72,7 +72,7 @@ function FilterValueInput({ type, label, value, processing, withSearchIcon = fal
72
72
  }),
73
73
  onClear && draft !== "" && /* @__PURE__ */ jsx("button", {
74
74
  type: "button",
75
- "aria-label": t("filter.clear", "Clear {{label}} filter", { label }),
75
+ "aria-label": t("table.filter.clear", "Clear {{label}} filter", { label }),
76
76
  "data-test": testId ? `${testId}-clear` : void 0,
77
77
  className: "absolute right-1 inline-flex size-6 items-center justify-center rounded-lt-sm hover:bg-lt-muted disabled:opacity-50",
78
78
  disabled: processing,