@lattice-php/lattice 0.3.0 → 0.5.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 (195) hide show
  1. package/dist/action/components/action-form.js +3 -3
  2. package/dist/action/components/action-group.js +1 -1
  3. package/dist/action/components/action.js +10 -16
  4. package/dist/action/components/action.js.map +1 -1
  5. package/dist/action/run-action.d.ts +9 -0
  6. package/dist/action/run-action.js +22 -0
  7. package/dist/action/run-action.js.map +1 -0
  8. package/dist/chat/components/chat-box.js +31 -14
  9. package/dist/chat/components/chat-box.js.map +1 -1
  10. package/dist/chat/components/message.js +2 -2
  11. package/dist/chat/components/message.js.map +1 -1
  12. package/dist/chat/components/prompt-input.js +1 -1
  13. package/dist/chat/index.js +2 -0
  14. package/dist/chat/index.js.map +1 -1
  15. package/dist/chat/transport.d.ts +2 -0
  16. package/dist/chat/transport.js +23 -3
  17. package/dist/chat/transport.js.map +1 -1
  18. package/dist/core/api.d.ts +14 -7
  19. package/dist/core/api.js +77 -1
  20. package/dist/core/api.js.map +1 -1
  21. package/dist/core/components/badge.js +4 -4
  22. package/dist/core/components/badge.js.map +1 -1
  23. package/dist/core/components/button.js +31 -19
  24. package/dist/core/components/button.js.map +1 -1
  25. package/dist/core/components/card.js +3 -2
  26. package/dist/core/components/card.js.map +1 -1
  27. package/dist/core/components/chart.js +3 -2
  28. package/dist/core/components/chart.js.map +1 -1
  29. package/dist/core/components/checkbox.js +1 -1
  30. package/dist/core/components/checkbox.js.map +1 -1
  31. package/dist/core/components/control.js +4 -4
  32. package/dist/core/components/control.js.map +1 -1
  33. package/dist/core/components/dialog.d.ts +1 -1
  34. package/dist/core/components/dialog.js +3 -2
  35. package/dist/core/components/dialog.js.map +1 -1
  36. package/dist/core/components/floating-panel.js +5 -4
  37. package/dist/core/components/floating-panel.js.map +1 -1
  38. package/dist/core/components/fragment.js +3 -6
  39. package/dist/core/components/fragment.js.map +1 -1
  40. package/dist/core/components/grid.js +2 -1
  41. package/dist/core/components/grid.js.map +1 -1
  42. package/dist/core/components/heading.js +1 -1
  43. package/dist/core/components/heading.js.map +1 -1
  44. package/dist/core/components/index.js +0 -2
  45. package/dist/core/components/index.js.map +1 -1
  46. package/dist/core/components/popover.d.ts +1 -1
  47. package/dist/core/components/popover.js +1 -1
  48. package/dist/core/components/popover.js.map +1 -1
  49. package/dist/core/components/section.js +7 -11
  50. package/dist/core/components/section.js.map +1 -1
  51. package/dist/core/components/segmented-pills.js +1 -1
  52. package/dist/core/components/segmented-pills.js.map +1 -1
  53. package/dist/core/components/stack.js +9 -2
  54. package/dist/core/components/stack.js.map +1 -1
  55. package/dist/core/components/tabs.js +2 -2
  56. package/dist/core/components/tabs.js.map +1 -1
  57. package/dist/core/nodes.d.ts +6 -0
  58. package/dist/core/nodes.js +13 -0
  59. package/dist/core/nodes.js.map +1 -0
  60. package/dist/core/renderer.js +1 -1
  61. package/dist/core/types.d.ts +3 -1
  62. package/dist/create-app.d.ts +21 -0
  63. package/dist/create-app.js +31 -0
  64. package/dist/create-app.js.map +1 -0
  65. package/dist/effects/registry.js +2 -1
  66. package/dist/effects/registry.js.map +1 -1
  67. package/dist/effects/use-effect-dispatcher.js +1 -1
  68. package/dist/effects/use-flash-effects.js +1 -1
  69. package/dist/events/event-names.d.ts +3 -2
  70. package/dist/events/event-names.js +3 -2
  71. package/dist/events/event-names.js.map +1 -1
  72. package/dist/form/components/base/affix-group.d.ts +17 -0
  73. package/dist/form/components/base/affix-group.js +49 -0
  74. package/dist/form/components/base/affix-group.js.map +1 -0
  75. package/dist/form/components/base/input-otp.d.ts +7 -0
  76. package/dist/form/components/base/input-otp.js +30 -0
  77. package/dist/form/components/base/input-otp.js.map +1 -0
  78. package/dist/form/components/base/label.js +1 -1
  79. package/dist/form/components/base/label.js.map +1 -1
  80. package/dist/form/components/base/submit-button.js +1 -1
  81. package/dist/form/components/base/submit-button.js.map +1 -1
  82. package/dist/form/components/base/textarea.js +1 -1
  83. package/dist/form/components/base/textarea.js.map +1 -1
  84. package/dist/form/components/conditions.js +1 -1
  85. package/dist/form/components/conditions.js.map +1 -1
  86. package/dist/form/components/fields/file-upload.js +35 -3
  87. package/dist/form/components/fields/file-upload.js.map +1 -1
  88. package/dist/form/components/fields/number-input.js +21 -15
  89. package/dist/form/components/fields/number-input.js.map +1 -1
  90. package/dist/form/components/fields/otp-input.d.ts +2 -0
  91. package/dist/form/components/fields/otp-input.js +25 -0
  92. package/dist/form/components/fields/otp-input.js.map +1 -0
  93. package/dist/form/components/fields/password-input.js +21 -15
  94. package/dist/form/components/fields/password-input.js.map +1 -1
  95. package/dist/form/components/fields/rich-editor.js +2 -2
  96. package/dist/form/components/fields/rich-editor.js.map +1 -1
  97. package/dist/form/components/fields/row-item.js +1 -1
  98. package/dist/form/components/fields/table-rows.js +1 -1
  99. package/dist/form/components/fields/text-input.js +19 -13
  100. package/dist/form/components/fields/text-input.js.map +1 -1
  101. package/dist/form/components/form.js +2 -2
  102. package/dist/form/components/form.js.map +1 -1
  103. package/dist/form/components/index.d.ts +1 -0
  104. package/dist/form/components/index.js +2 -1
  105. package/dist/form/components/prefill-targets.d.ts +0 -1
  106. package/dist/form/components/prefill-targets.js +1 -4
  107. package/dist/form/components/prefill-targets.js.map +1 -1
  108. package/dist/form/components/use-form-resolver.js +2 -2
  109. package/dist/form/components/use-form-resolver.js.map +1 -1
  110. package/dist/form/index.js +1 -0
  111. package/dist/form/index.js.map +1 -1
  112. package/dist/form/skeleton.js +1 -1
  113. package/dist/form/skeleton.js.map +1 -1
  114. package/dist/i18n/locale-reload.js +1 -1
  115. package/dist/i18n/translatable.d.ts +4 -0
  116. package/dist/i18n/translatable.js +24 -0
  117. package/dist/i18n/translatable.js.map +1 -0
  118. package/dist/index.d.ts +3 -0
  119. package/dist/index.js +15 -13
  120. package/dist/lattice.css +99 -6
  121. package/dist/layout/breadcrumbs.js +3 -2
  122. package/dist/layout/breadcrumbs.js.map +1 -1
  123. package/dist/layout/callouts.js +2 -2
  124. package/dist/layout/callouts.js.map +1 -1
  125. package/dist/layout/components.js +4 -2
  126. package/dist/layout/components.js.map +1 -1
  127. package/dist/layout/dropdown.js +2 -1
  128. package/dist/layout/dropdown.js.map +1 -1
  129. package/dist/layout/menu-item.js +33 -12
  130. package/dist/layout/menu-item.js.map +1 -1
  131. package/dist/layout/menu.js +2 -1
  132. package/dist/layout/menu.js.map +1 -1
  133. package/dist/layout/popover.js +1 -1
  134. package/dist/layout/sidebar.js +69 -29
  135. package/dist/layout/sidebar.js.map +1 -1
  136. package/dist/layout/topbar.d.ts +3 -0
  137. package/dist/layout/topbar.js +16 -0
  138. package/dist/layout/topbar.js.map +1 -0
  139. package/dist/page.js +15 -10
  140. package/dist/page.js.map +1 -1
  141. package/dist/realtime/build-effects.d.ts +4 -0
  142. package/dist/realtime/build-effects.js +23 -0
  143. package/dist/realtime/build-effects.js.map +1 -0
  144. package/dist/realtime/listeners.d.ts +8 -0
  145. package/dist/realtime/listeners.js +31 -0
  146. package/dist/realtime/listeners.js.map +1 -0
  147. package/dist/realtime/subscriptions.d.ts +4 -0
  148. package/dist/realtime/subscriptions.js +45 -0
  149. package/dist/realtime/subscriptions.js.map +1 -0
  150. package/dist/registry.js +3 -2
  151. package/dist/registry.js.map +1 -1
  152. package/dist/remote/components/data-list.d.ts +10 -0
  153. package/dist/remote/components/data-list.js +106 -0
  154. package/dist/remote/components/data-list.js.map +1 -0
  155. package/dist/remote/index.d.ts +1 -0
  156. package/dist/remote/index.js +11 -0
  157. package/dist/remote/index.js.map +1 -0
  158. package/dist/table/components/bulk-bar.js +10 -15
  159. package/dist/table/components/bulk-bar.js.map +1 -1
  160. package/dist/table/components/cells/text-cell.js +38 -3
  161. package/dist/table/components/cells/text-cell.js.map +1 -1
  162. package/dist/table/components/column-filter-control.d.ts +2 -1
  163. package/dist/table/components/column-filter-control.js +28 -19
  164. package/dist/table/components/column-filter-control.js.map +1 -1
  165. package/dist/table/components/column-header.js +2 -2
  166. package/dist/table/components/column-header.js.map +1 -1
  167. package/dist/table/components/filter-bar.d.ts +7 -6
  168. package/dist/table/components/filter-bar.js +51 -28
  169. package/dist/table/components/filter-bar.js.map +1 -1
  170. package/dist/table/components/filter-controls.js +2 -2
  171. package/dist/table/components/filter-controls.js.map +1 -1
  172. package/dist/table/components/filter-stack-bar.js +4 -3
  173. package/dist/table/components/filter-stack-bar.js.map +1 -1
  174. package/dist/table/components/filter-value-input.js +1 -1
  175. package/dist/table/components/filter-value-input.js.map +1 -1
  176. package/dist/table/components/sort-bar.js +1 -1
  177. package/dist/table/components/sort-bar.js.map +1 -1
  178. package/dist/table/components/table.js +26 -17
  179. package/dist/table/components/table.js.map +1 -1
  180. package/dist/table/filter-values.d.ts +5 -0
  181. package/dist/table/filter-values.js +10 -3
  182. package/dist/table/filter-values.js.map +1 -1
  183. package/dist/table/query.js +5 -4
  184. package/dist/table/query.js.map +1 -1
  185. package/dist/table/use-table.d.ts +1 -0
  186. package/dist/table/use-table.js +4 -0
  187. package/dist/table/use-table.js.map +1 -1
  188. package/dist/toast/toast.js +5 -2
  189. package/dist/toast/toast.js.map +1 -1
  190. package/dist/toast/toaster.js +6 -5
  191. package/dist/toast/toaster.js.map +1 -1
  192. package/dist/types/generated.d.ts +122 -14
  193. package/dist/vite.js +32 -1
  194. package/dist/vite.js.map +1 -1
  195. package/package.json +33 -3
@@ -0,0 +1,45 @@
1
+ import { useT } from "../i18n/instance.js";
2
+ import { useEffectDispatcher } from "../effects/use-effect-dispatcher.js";
3
+ import { buildEffects } from "./build-effects.js";
4
+ import { useCallback } from "react";
5
+ import { Fragment as Fragment$1, jsx } from "react/jsx-runtime";
6
+ import { useEcho, useEchoPresence, useEchoPublic } from "@laravel/echo-react";
7
+ //#region resources/js/realtime/subscriptions.tsx
8
+ function useListenerHandler(listener) {
9
+ const { t } = useT("lattice");
10
+ const dispatch = useEffectDispatcher();
11
+ return useCallback((payload) => {
12
+ const data = typeof payload === "object" && payload !== null ? payload : {};
13
+ dispatch(buildEffects(listener.effects, data, t));
14
+ }, [
15
+ dispatch,
16
+ listener,
17
+ t
18
+ ]);
19
+ }
20
+ function PublicListener({ listener }) {
21
+ useEchoPublic(listener.channel, listener.events, useListenerHandler(listener));
22
+ return null;
23
+ }
24
+ function PrivateListener({ listener }) {
25
+ useEcho(listener.channel, listener.events, useListenerHandler(listener));
26
+ return null;
27
+ }
28
+ function PresenceListener({ listener }) {
29
+ useEchoPresence(listener.channel, listener.events, useListenerHandler(listener));
30
+ return null;
31
+ }
32
+ function Listener({ listener }) {
33
+ switch (listener.visibility) {
34
+ case "private": return /* @__PURE__ */ jsx(PrivateListener, { listener });
35
+ case "presence": return /* @__PURE__ */ jsx(PresenceListener, { listener });
36
+ default: return /* @__PURE__ */ jsx(PublicListener, { listener });
37
+ }
38
+ }
39
+ function Subscriptions({ listeners }) {
40
+ return /* @__PURE__ */ jsx(Fragment$1, { children: listeners.map((listener, index) => /* @__PURE__ */ jsx(Listener, { listener }, `${listener.visibility}:${listener.channel}:${index}`)) });
41
+ }
42
+ //#endregion
43
+ export { Subscriptions as default };
44
+
45
+ //# sourceMappingURL=subscriptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscriptions.js","names":[],"sources":["../../resources/js/realtime/subscriptions.tsx"],"sourcesContent":["import { useEcho, useEchoPresence, useEchoPublic } from \"@laravel/echo-react\";\nimport { useCallback } from \"react\";\nimport { useEffectDispatcher } from \"@lattice-php/lattice/effects/use-effect-dispatcher\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\nimport { buildEffects } from \"./build-effects\";\nimport type { RawEffect } from \"./build-effects\";\nimport type { ListenerPayload } from \"@lattice-php/lattice/types/generated\";\n\nfunction useListenerHandler(listener: ListenerPayload): (payload: unknown) => void {\n const { t } = useT(\"lattice\");\n const dispatch = useEffectDispatcher();\n\n return useCallback(\n (payload: unknown) => {\n const data = (typeof payload === \"object\" && payload !== null ? payload : {}) as Record<\n string,\n unknown\n >;\n dispatch(buildEffects(listener.effects as unknown as RawEffect[], data, t));\n },\n [dispatch, listener, t],\n );\n}\n\nfunction PublicListener({ listener }: { listener: ListenerPayload }) {\n useEchoPublic(listener.channel, listener.events, useListenerHandler(listener));\n return null;\n}\n\nfunction PrivateListener({ listener }: { listener: ListenerPayload }) {\n useEcho(listener.channel, listener.events, useListenerHandler(listener));\n return null;\n}\n\nfunction PresenceListener({ listener }: { listener: ListenerPayload }) {\n useEchoPresence(listener.channel, listener.events, useListenerHandler(listener));\n return null;\n}\n\nfunction Listener({ listener }: { listener: ListenerPayload }) {\n switch (listener.visibility) {\n case \"private\":\n return <PrivateListener listener={listener} />;\n case \"presence\":\n return <PresenceListener listener={listener} />;\n default:\n return <PublicListener listener={listener} />;\n }\n}\n\nexport default function Subscriptions({ listeners }: { listeners: ListenerPayload[] }) {\n return (\n <>\n {listeners.map((listener, index) => (\n <Listener key={`${listener.visibility}:${listener.channel}:${index}`} listener={listener} />\n ))}\n </>\n );\n}\n"],"mappings":";;;;;;;AAQA,SAAS,mBAAmB,UAAuD;CACjF,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,MAAM,WAAW,oBAAoB;CAErC,OAAO,aACJ,YAAqB;EACpB,MAAM,OAAQ,OAAO,YAAY,YAAY,YAAY,OAAO,UAAU,CAAC;EAI3E,SAAS,aAAa,SAAS,SAAmC,MAAM,CAAC,CAAC;CAC5E,GACA;EAAC;EAAU;EAAU;CAAC,CACxB;AACF;AAEA,SAAS,eAAe,EAAE,YAA2C;CACnE,cAAc,SAAS,SAAS,SAAS,QAAQ,mBAAmB,QAAQ,CAAC;CAC7E,OAAO;AACT;AAEA,SAAS,gBAAgB,EAAE,YAA2C;CACpE,QAAQ,SAAS,SAAS,SAAS,QAAQ,mBAAmB,QAAQ,CAAC;CACvE,OAAO;AACT;AAEA,SAAS,iBAAiB,EAAE,YAA2C;CACrE,gBAAgB,SAAS,SAAS,SAAS,QAAQ,mBAAmB,QAAQ,CAAC;CAC/E,OAAO;AACT;AAEA,SAAS,SAAS,EAAE,YAA2C;CAC7D,QAAQ,SAAS,YAAjB;EACE,KAAK,WACH,OAAO,oBAAC,iBAAD,EAA2B,SAAW,CAAA;EAC/C,KAAK,YACH,OAAO,oBAAC,kBAAD,EAA4B,SAAW,CAAA;EAChD,SACE,OAAO,oBAAC,gBAAD,EAA0B,SAAW,CAAA;CAChD;AACF;AAEA,SAAwB,cAAc,EAAE,aAA+C;CACrF,OACE,oBAAA,YAAA,EAAA,UACG,UAAU,KAAK,UAAU,UACxB,oBAAC,UAAD,EAAgF,SAAW,GAA5E,GAAG,SAAS,WAAW,GAAG,SAAS,QAAQ,GAAG,OAA8B,CAC5F,EACD,CAAA;AAEN"}
package/dist/registry.js CHANGED
@@ -1,12 +1,13 @@
1
1
  import { createRegistry } from "./core/registry.js";
2
+ import { layoutComponents } from "./layout/components.js";
2
3
  import { actionComponents } from "./action/index.js";
3
4
  import { chatPlugin } from "./chat/index.js";
4
5
  import { coreComponents } from "./core/components/index.js";
5
6
  import { formComponents } from "./form/index.js";
6
- import { layoutComponents } from "./layout/components.js";
7
+ import { remoteComponents } from "./remote/index.js";
7
8
  import { tableComponents } from "./table/index.js";
8
9
  //#region resources/js/registry.ts
9
- var registry = createRegistry(coreComponents, actionComponents, formComponents, layoutComponents, tableComponents, chatPlugin);
10
+ var registry = createRegistry(coreComponents, actionComponents, formComponents, layoutComponents, tableComponents, chatPlugin, remoteComponents);
10
11
  //#endregion
11
12
  export { registry };
12
13
 
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","names":[],"sources":["../resources/js/registry.ts"],"sourcesContent":["import { createRegistry } from \"@lattice-php/lattice/core/registry\";\nimport { actionComponents } from \"./action\";\nimport { chatPlugin } from \"./chat\";\nimport { coreComponents } from \"./core/components\";\nimport { formComponents } from \"./form\";\nimport { layoutComponents } from \"./layout/components\";\nimport { tableComponents } from \"./table\";\n\nexport const registry = createRegistry(\n coreComponents,\n actionComponents,\n formComponents,\n layoutComponents,\n tableComponents,\n chatPlugin,\n);\n"],"mappings":";;;;;;;;AAQA,IAAa,WAAW,eACtB,gBACA,kBACA,gBACA,kBACA,iBACA,UACF"}
1
+ {"version":3,"file":"registry.js","names":[],"sources":["../resources/js/registry.ts"],"sourcesContent":["import { createRegistry } from \"@lattice-php/lattice/core/registry\";\nimport { actionComponents } from \"./action\";\nimport { chatPlugin } from \"./chat\";\nimport { coreComponents } from \"./core/components\";\nimport { formComponents } from \"./form\";\nimport { remoteComponents } from \"./remote\";\nimport { layoutComponents } from \"./layout/components\";\nimport { tableComponents } from \"./table\";\n\nexport const registry = createRegistry(\n coreComponents,\n actionComponents,\n formComponents,\n layoutComponents,\n tableComponents,\n chatPlugin,\n remoteComponents,\n);\n"],"mappings":";;;;;;;;;AASA,IAAa,WAAW,eACtB,gBACA,kBACA,gBACA,kBACA,iBACA,YACA,gBACF"}
@@ -0,0 +1,10 @@
1
+ import { RendererComponent, Schema } from '../../core/types';
2
+ import { DataList as DataListProps } from '../../types/generated';
3
+ type RemoteDataListProps = DataListProps & {
4
+ schema?: Schema;
5
+ };
6
+ export declare function RemoteDataList({ props }: {
7
+ props: RemoteDataListProps;
8
+ }): import("react").JSX.Element;
9
+ export declare const DataList: RendererComponent<"remote.data-list">;
10
+ export default DataList;
@@ -0,0 +1,106 @@
1
+ import { Renderer } from "../../core/renderer.js";
2
+ import { useT } from "../../i18n/instance.js";
3
+ import { remoteJson } from "../../core/api.js";
4
+ import { useEffect, useState } from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ //#region resources/js/remote/components/data-list.tsx
7
+ function label(value) {
8
+ if (value === null || value === void 0) return "";
9
+ return String(value);
10
+ }
11
+ function isRecord(value) {
12
+ return typeof value === "object" && value !== null && !Array.isArray(value);
13
+ }
14
+ function dataBindings(value) {
15
+ if (!isRecord(value)) return {};
16
+ return Object.fromEntries(Object.entries(value).filter((entry) => {
17
+ return typeof entry[1] === "string";
18
+ }));
19
+ }
20
+ function rowValue(row, key) {
21
+ if (key in row) return row[key];
22
+ return key.split(".").reduce((value, segment) => {
23
+ return isRecord(value) ? value[segment] : void 0;
24
+ }, row);
25
+ }
26
+ function materializeProps(props, row) {
27
+ if (!isRecord(props)) return {};
28
+ const { dataBindings: bindings, ...materialized } = props;
29
+ for (const [prop, key] of Object.entries(dataBindings(bindings))) {
30
+ const value = rowValue(row, key);
31
+ if (value !== void 0) materialized[prop] = value;
32
+ }
33
+ return materialized;
34
+ }
35
+ function materializeNode(node, row) {
36
+ return {
37
+ ...node,
38
+ props: materializeProps(node.props, row),
39
+ schema: node.schema?.map((child) => materializeNode(child, row))
40
+ };
41
+ }
42
+ function materializeSchema(schema, row) {
43
+ return schema?.map((node) => materializeNode(node, row)) ?? [];
44
+ }
45
+ function RemoteDataList({ props }) {
46
+ const { t } = useT("lattice");
47
+ const [rows, setRows] = useState([]);
48
+ const [error, setError] = useState(null);
49
+ const [loading, setLoading] = useState(false);
50
+ useEffect(() => {
51
+ if (!props.remote || !props.dataEndpoint) {
52
+ setRows([]);
53
+ setLoading(false);
54
+ return;
55
+ }
56
+ let cancelled = false;
57
+ setError(null);
58
+ setLoading(true);
59
+ remoteJson(props.dataEndpoint, { remote: props.remote }).then((payload) => {
60
+ if (!cancelled) setRows(payload.data ?? []);
61
+ }).catch((caught) => {
62
+ if (!cancelled) setError(caught instanceof Error ? caught.message : String(caught));
63
+ }).finally(() => {
64
+ if (!cancelled) setLoading(false);
65
+ });
66
+ return () => {
67
+ cancelled = true;
68
+ };
69
+ }, [props.dataEndpoint, props.remote]);
70
+ if (error) return /* @__PURE__ */ jsx("div", {
71
+ className: "rounded-lt border border-lt-danger/40 p-3 text-sm",
72
+ children: error
73
+ });
74
+ if (loading || !props.remote) return /* @__PURE__ */ jsx("div", {
75
+ className: "rounded-lt border border-lt-border p-3 text-sm",
76
+ children: t("remote.data-list.loading", "Loading...")
77
+ });
78
+ return /* @__PURE__ */ jsxs("div", {
79
+ className: "rounded-lt border border-lt-border bg-lt-bg",
80
+ children: [/* @__PURE__ */ jsx("div", {
81
+ className: "border-b border-lt-border px-3 py-2 text-sm font-medium",
82
+ children: t("remote.data-list.title", "Remote data")
83
+ }), /* @__PURE__ */ jsxs("div", {
84
+ className: "divide-y divide-lt-border",
85
+ children: [rows.map((row, index) => {
86
+ return /* @__PURE__ */ jsx("div", {
87
+ className: "px-3 py-2",
88
+ children: /* @__PURE__ */ jsx(Renderer, { nodes: materializeSchema(props.schema, row) })
89
+ }, label(row.id) || index);
90
+ }), rows.length === 0 ? /* @__PURE__ */ jsx("div", {
91
+ className: "px-3 py-2 text-sm text-lt-muted-fg",
92
+ children: props.emptyLabel ?? t("remote.data-list.empty", "No data")
93
+ }) : null]
94
+ })]
95
+ });
96
+ }
97
+ var DataList = ({ node }) => {
98
+ return /* @__PURE__ */ jsx(RemoteDataList, { props: {
99
+ ...node.props,
100
+ schema: node.schema
101
+ } });
102
+ };
103
+ //#endregion
104
+ export { DataList, DataList as default, RemoteDataList };
105
+
106
+ //# sourceMappingURL=data-list.js.map
@@ -0,0 +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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"}
@@ -0,0 +1 @@
1
+ export declare const remoteComponents: import('../core/registry').Plugin;
@@ -0,0 +1,11 @@
1
+ import { createPlugin, eagerComponent } from "../core/registry.js";
2
+ import { DataList } from "./components/data-list.js";
3
+ //#region resources/js/remote/index.ts
4
+ var remoteComponents = createPlugin({
5
+ components: { "remote.data-list": eagerComponent(DataList) },
6
+ name: "lattice/remote"
7
+ });
8
+ //#endregion
9
+ export { remoteComponents };
10
+
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../resources/js/remote/index.ts"],"sourcesContent":["import { createPlugin, eagerComponent } from \"@lattice-php/lattice/core/registry\";\nimport DataListComponent from \"./components/data-list\";\n\nexport const remoteComponents = createPlugin({\n components: {\n \"remote.data-list\": eagerComponent(DataListComponent),\n },\n name: \"lattice/remote\",\n});\n"],"mappings":";;;AAGA,IAAa,mBAAmB,aAAa;CAC3C,YAAY,EACV,oBAAoB,eAAe,QAAiB,EACtD;CACA,MAAM;AACR,CAAC"}
@@ -1,14 +1,15 @@
1
1
  import { withHeaders } from "../../core/headers.js";
2
+ import { useT } from "../../i18n/instance.js";
2
3
  import { prefixedTestId } from "../../core/test-id.js";
4
+ import { getActionEffects } from "../../effects/dispatch.js";
5
+ import { useEffectDispatcher } from "../../effects/use-effect-dispatcher.js";
3
6
  import { Button } from "../../core/components/button.js";
4
- import { useT } from "../../i18n/instance.js";
5
7
  import { Spinner } from "../../core/components/spinner.js";
6
8
  import { ConfirmDialog } from "../../core/components/confirm-dialog.js";
7
- import { dispatchActionError, getActionEffects } from "../../effects/dispatch.js";
8
- import { useEffectDispatcher } from "../../effects/use-effect-dispatcher.js";
9
+ import { runAction } from "../../action/run-action.js";
9
10
  import { ActionForm } from "../../action/components/action-form.js";
10
- import { useState } from "react";
11
11
  import { useHttp } from "@inertiajs/react";
12
+ import { useState } from "react";
12
13
  import { jsx, jsxs } from "react/jsx-runtime";
13
14
  //#region resources/js/table/components/bulk-bar.tsx
14
15
  function BulkBar({ actions, selectedKeys, allMatching, total, query, canSelectAllMatching, onSelectAllMatching, onCompleted }) {
@@ -22,19 +23,13 @@ function BulkBar({ actions, selectedKeys, allMatching, total, query, canSelectAl
22
23
  ...query
23
24
  } : { selected: selectedKeys };
24
25
  async function submit(action) {
25
- try {
26
- http.transform((data) => ({
27
- ...data,
28
- ...allMatching ? {
29
- allMatching: true,
30
- ...query
31
- } : { selected: selectedKeys }
32
- }));
33
- dispatch(getActionEffects((await http[action.method](action.endpoint, { headers: withHeaders(action.ref ?? "") })).effects));
26
+ http.transform((data) => ({
27
+ ...data,
28
+ ...selectionPayload()
29
+ }));
30
+ if (await runAction(() => http[action.method](action.endpoint, { headers: withHeaders(action.ref ?? "") }), dispatch)) {
34
31
  setConfirming(null);
35
32
  onCompleted();
36
- } catch (error) {
37
- dispatchActionError(error);
38
33
  }
39
34
  }
40
35
  function run(action) {
@@ -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 { dispatchActionError, 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 try {\n http.transform((data) => ({\n ...data,\n ...(allMatching ? { allMatching: true, ...query } : { selected: selectedKeys }),\n }));\n\n const response = await http[action.method](action.endpoint, {\n headers: withHeaders(action.ref ?? \"\"),\n });\n\n dispatch(getActionEffects(response.effects));\n setConfirming(null);\n onCompleted();\n } catch (error) {\n dispatchActionError(error);\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":";;;;;;;;;;;;;AAmBA,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,IAAI;GACF,KAAK,WAAW,UAAU;IACxB,GAAG;IACH,GAAI,cAAc;KAAE,aAAa;KAAM,GAAG;IAAM,IAAI,EAAE,UAAU,aAAa;GAC/E,EAAE;GAMF,SAAS,kBAAiB,MAJH,KAAK,OAAO,QAAQ,OAAO,UAAU,EAC1D,SAAS,YAAY,OAAO,OAAO,EAAE,EACvC,CAAC,GAEkC,OAAO,CAAC;GAC3C,cAAc,IAAI;GAClB,YAAY;EACd,SAAS,OAAO;GACd,oBAAoB,KAAK;EAC3B;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(\"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,10 +1,45 @@
1
+ import { cn } from "../../../lib/utils.js";
1
2
  import { Icon } from "../../../icons/sprite.js";
2
3
  import { copyToClipboard } from "../../../clipboard/index.js";
3
4
  import { formatCell, resolveLink } from "../../format.js";
4
5
  import { useEffect, useState } from "react";
5
6
  import { jsx, jsxs } from "react/jsx-runtime";
6
7
  //#region resources/js/table/components/cells/text-cell.tsx
7
- var TextCell = ({ column, props, row, value }) => {
8
+ var TextCell = (args) => {
9
+ if (args.props.multiple) return /* @__PURE__ */ jsx(MultipleCell, { ...args });
10
+ if (args.props.badge) return /* @__PURE__ */ jsx(SingleBadgeCell, { ...args });
11
+ return /* @__PURE__ */ jsx(PlainTextCell, { ...args });
12
+ };
13
+ function Badge({ label, color }) {
14
+ if (label === "") return null;
15
+ return /* @__PURE__ */ jsx("span", {
16
+ className: cn("lt-cell-badge", `lt-cell-tone-${color || "gray"}`),
17
+ children: label
18
+ });
19
+ }
20
+ function MultipleCell({ column, props, value }) {
21
+ const items = Array.isArray(value) ? value : [];
22
+ if (items.length === 0) return null;
23
+ if (!props.badge) return /* @__PURE__ */ jsx("span", { children: items.map((item) => formatCell(item, column)).join(", ") });
24
+ return /* @__PURE__ */ jsx("div", {
25
+ className: "flex flex-wrap gap-1",
26
+ children: items.map((item, index) => {
27
+ const chip = item;
28
+ return /* @__PURE__ */ jsx(Badge, {
29
+ label: formatCell(chip.value, column),
30
+ color: chip.color
31
+ }, index);
32
+ })
33
+ });
34
+ }
35
+ function SingleBadgeCell({ column, props, row, value }) {
36
+ const colorKey = props.badge.colorKey;
37
+ return /* @__PURE__ */ jsx(Badge, {
38
+ label: formatCell(value, column),
39
+ color: String(row[colorKey] ?? "")
40
+ });
41
+ }
42
+ function PlainTextCell({ column, props, row, value }) {
8
43
  const text = formatCell(value, column);
9
44
  const [copied, setCopied] = useState(false);
10
45
  const href = resolveLink(column, row, value);
@@ -30,7 +65,7 @@ var TextCell = ({ column, props, row, value }) => {
30
65
  children: [/* @__PURE__ */ jsx("span", { children: content }), /* @__PURE__ */ jsxs("button", {
31
66
  type: "button",
32
67
  "data-test": `copy-${column.key}`,
33
- className: "inline-flex items-center gap-1 rounded border border-lt-border px-2 py-1 text-xs",
68
+ className: "inline-flex items-center gap-1 rounded-lt-sm border border-lt-border px-2 py-1 text-xs",
34
69
  "aria-label": `${copied ? "Copied" : "Copy"} ${column.label}`,
35
70
  onClick: handleCopy,
36
71
  children: [copied ? /* @__PURE__ */ jsx(Icon, {
@@ -42,7 +77,7 @@ var TextCell = ({ column, props, row, value }) => {
42
77
  }), copied ? "Copied" : "Copy"]
43
78
  })]
44
79
  });
45
- };
80
+ }
46
81
  //#endregion
47
82
  export { TextCell };
48
83
 
@@ -1 +1 @@
1
- {"version":3,"file":"text-cell.js","names":[],"sources":["../../../../resources/js/table/components/cells/text-cell.tsx"],"sourcesContent":["import { copyToClipboard } from \"@lattice-php/lattice/clipboard\";\nimport { Icon } from \"@lattice-php/lattice/icons\";\nimport { useEffect, useState } from \"react\";\nimport { formatCell, resolveLink } from \"../../format\";\nimport type { ColumnCellComponent } from \"../../registry\";\n\nexport const TextCell: ColumnCellComponent<\"column.text\"> = ({ column, props, row, value }) => {\n const text = formatCell(value, column);\n const [copied, setCopied] = useState(false);\n const href = resolveLink(column, row, value);\n const content = href ? (\n <a\n className=\"underline underline-offset-2\"\n href={href}\n rel={props.link?.external ? \"noreferrer\" : undefined}\n target={props.link?.external ? \"_blank\" : undefined}\n >\n {text}\n </a>\n ) : (\n text\n );\n\n useEffect(() => {\n if (!copied) {\n return;\n }\n\n const timeout = window.setTimeout(() => setCopied(false), 1500);\n\n return () => window.clearTimeout(timeout);\n }, [copied]);\n\n function handleCopy(): void {\n void copyToClipboard(text);\n setCopied(true);\n }\n\n if (!props.copyable) {\n return content;\n }\n\n return (\n <span className=\"inline-flex items-center gap-2\">\n <span>{content}</span>\n <button\n type=\"button\"\n data-test={`copy-${column.key}`}\n className=\"inline-flex items-center gap-1 rounded border border-lt-border px-2 py-1 text-xs\"\n aria-label={`${copied ? \"Copied\" : \"Copy\"} ${column.label}`}\n onClick={handleCopy}\n >\n {copied ? (\n <Icon name=\"check\" className=\"size-lt-icon-xs\" />\n ) : (\n <Icon name=\"copy\" className=\"size-lt-icon-xs\" />\n )}\n {copied ? \"Copied\" : \"Copy\"}\n </button>\n </span>\n );\n};\n"],"mappings":";;;;;;AAMA,IAAa,YAAgD,EAAE,QAAQ,OAAO,KAAK,YAAY;CAC7F,MAAM,OAAO,WAAW,OAAO,MAAM;CACrC,MAAM,CAAC,QAAQ,aAAa,SAAS,KAAK;CAC1C,MAAM,OAAO,YAAY,QAAQ,KAAK,KAAK;CAC3C,MAAM,UAAU,OACd,oBAAC,KAAD;EACE,WAAU;EACJ;EACN,KAAK,MAAM,MAAM,WAAW,eAAe,KAAA;EAC3C,QAAQ,MAAM,MAAM,WAAW,WAAW,KAAA;YAEzC;CACA,CAAA,IAEH;CAGF,gBAAgB;EACd,IAAI,CAAC,QACH;EAGF,MAAM,UAAU,OAAO,iBAAiB,UAAU,KAAK,GAAG,IAAI;EAE9D,aAAa,OAAO,aAAa,OAAO;CAC1C,GAAG,CAAC,MAAM,CAAC;CAEX,SAAS,aAAmB;EAC1B,gBAAqB,IAAI;EACzB,UAAU,IAAI;CAChB;CAEA,IAAI,CAAC,MAAM,UACT,OAAO;CAGT,OACE,qBAAC,QAAD;EAAM,WAAU;YAAhB,CACE,oBAAC,QAAD,EAAA,UAAO,QAAc,CAAA,GACrB,qBAAC,UAAD;GACE,MAAK;GACL,aAAW,QAAQ,OAAO;GAC1B,WAAU;GACV,cAAY,GAAG,SAAS,WAAW,OAAO,GAAG,OAAO;GACpD,SAAS;aALX,CAOG,SACC,oBAAC,MAAD;IAAM,MAAK;IAAQ,WAAU;GAAmB,CAAA,IAEhD,oBAAC,MAAD;IAAM,MAAK;IAAO,WAAU;GAAmB,CAAA,GAEhD,SAAS,WAAW,MACf;IACJ;;AAEV"}
1
+ {"version":3,"file":"text-cell.js","names":[],"sources":["../../../../resources/js/table/components/cells/text-cell.tsx"],"sourcesContent":["import { copyToClipboard } from \"@lattice-php/lattice/clipboard\";\nimport { Icon } from \"@lattice-php/lattice/icons\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { type ReactNode, useEffect, useState } from \"react\";\nimport { formatCell, resolveLink } from \"../../format\";\nimport type { ColumnCellArgs, ColumnCellComponent } from \"../../registry\";\n\ntype TextProps = ColumnCellArgs<\"column.text\">[\"props\"];\n\nexport const TextCell: ColumnCellComponent<\"column.text\"> = (args) => {\n if (args.props.multiple) {\n return <MultipleCell {...args} />;\n }\n\n if (args.props.badge) {\n return <SingleBadgeCell {...args} />;\n }\n\n return <PlainTextCell {...args} />;\n};\n\nfunction Badge({ label, color }: { label: string; color?: string | null }): ReactNode {\n if (label === \"\") {\n return null;\n }\n\n return <span className={cn(\"lt-cell-badge\", `lt-cell-tone-${color || \"gray\"}`)}>{label}</span>;\n}\n\nfunction MultipleCell({ column, props, value }: ColumnCellArgs<\"column.text\">): ReactNode {\n const items = Array.isArray(value) ? value : [];\n\n if (items.length === 0) {\n return null;\n }\n\n if (!props.badge) {\n return <span>{items.map((item) => formatCell(item, column)).join(\", \")}</span>;\n }\n\n return (\n <div className=\"flex flex-wrap gap-1\">\n {items.map((item, index) => {\n const chip = item as { value: unknown; color?: string };\n\n return <Badge key={index} label={formatCell(chip.value, column)} color={chip.color} />;\n })}\n </div>\n );\n}\n\nfunction SingleBadgeCell({ column, props, row, value }: ColumnCellArgs<\"column.text\">): ReactNode {\n const colorKey = (props.badge as NonNullable<TextProps[\"badge\"]>).colorKey;\n\n return <Badge label={formatCell(value, column)} color={String(row[colorKey] ?? \"\")} />;\n}\n\nfunction PlainTextCell({ column, props, row, value }: ColumnCellArgs<\"column.text\">): ReactNode {\n const text = formatCell(value, column);\n const [copied, setCopied] = useState(false);\n const href = resolveLink(column, row, value);\n const content = href ? (\n <a\n className=\"underline underline-offset-2\"\n href={href}\n rel={props.link?.external ? \"noreferrer\" : undefined}\n target={props.link?.external ? \"_blank\" : undefined}\n >\n {text}\n </a>\n ) : (\n text\n );\n\n useEffect(() => {\n if (!copied) {\n return;\n }\n\n const timeout = window.setTimeout(() => setCopied(false), 1500);\n\n return () => window.clearTimeout(timeout);\n }, [copied]);\n\n function handleCopy(): void {\n void copyToClipboard(text);\n setCopied(true);\n }\n\n if (!props.copyable) {\n return content;\n }\n\n return (\n <span className=\"inline-flex items-center gap-2\">\n <span>{content}</span>\n <button\n type=\"button\"\n data-test={`copy-${column.key}`}\n className=\"inline-flex items-center gap-1 rounded-lt-sm border border-lt-border px-2 py-1 text-xs\"\n aria-label={`${copied ? \"Copied\" : \"Copy\"} ${column.label}`}\n onClick={handleCopy}\n >\n {copied ? (\n <Icon name=\"check\" className=\"size-lt-icon-xs\" />\n ) : (\n <Icon name=\"copy\" className=\"size-lt-icon-xs\" />\n )}\n {copied ? \"Copied\" : \"Copy\"}\n </button>\n </span>\n );\n}\n"],"mappings":";;;;;;;AASA,IAAa,YAAgD,SAAS;CACpE,IAAI,KAAK,MAAM,UACb,OAAO,oBAAC,cAAD,EAAc,GAAI,KAAO,CAAA;CAGlC,IAAI,KAAK,MAAM,OACb,OAAO,oBAAC,iBAAD,EAAiB,GAAI,KAAO,CAAA;CAGrC,OAAO,oBAAC,eAAD,EAAe,GAAI,KAAO,CAAA;AACnC;AAEA,SAAS,MAAM,EAAE,OAAO,SAA8D;CACpF,IAAI,UAAU,IACZ,OAAO;CAGT,OAAO,oBAAC,QAAD;EAAM,WAAW,GAAG,iBAAiB,gBAAgB,SAAS,QAAQ;YAAI;CAAY,CAAA;AAC/F;AAEA,SAAS,aAAa,EAAE,QAAQ,OAAO,SAAmD;CACxF,MAAM,QAAQ,MAAM,QAAQ,KAAK,IAAI,QAAQ,CAAC;CAE9C,IAAI,MAAM,WAAW,GACnB,OAAO;CAGT,IAAI,CAAC,MAAM,OACT,OAAO,oBAAC,QAAD,EAAA,UAAO,MAAM,KAAK,SAAS,WAAW,MAAM,MAAM,CAAC,EAAE,KAAK,IAAI,EAAQ,CAAA;CAG/E,OACE,oBAAC,OAAD;EAAK,WAAU;YACZ,MAAM,KAAK,MAAM,UAAU;GAC1B,MAAM,OAAO;GAEb,OAAO,oBAAC,OAAD;IAAmB,OAAO,WAAW,KAAK,OAAO,MAAM;IAAG,OAAO,KAAK;GAAQ,GAAlE,KAAkE;EACvF,CAAC;CACE,CAAA;AAET;AAEA,SAAS,gBAAgB,EAAE,QAAQ,OAAO,KAAK,SAAmD;CAChG,MAAM,WAAY,MAAM,MAA0C;CAElE,OAAO,oBAAC,OAAD;EAAO,OAAO,WAAW,OAAO,MAAM;EAAG,OAAO,OAAO,IAAI,aAAa,EAAE;CAAI,CAAA;AACvF;AAEA,SAAS,cAAc,EAAE,QAAQ,OAAO,KAAK,SAAmD;CAC9F,MAAM,OAAO,WAAW,OAAO,MAAM;CACrC,MAAM,CAAC,QAAQ,aAAa,SAAS,KAAK;CAC1C,MAAM,OAAO,YAAY,QAAQ,KAAK,KAAK;CAC3C,MAAM,UAAU,OACd,oBAAC,KAAD;EACE,WAAU;EACJ;EACN,KAAK,MAAM,MAAM,WAAW,eAAe,KAAA;EAC3C,QAAQ,MAAM,MAAM,WAAW,WAAW,KAAA;YAEzC;CACA,CAAA,IAEH;CAGF,gBAAgB;EACd,IAAI,CAAC,QACH;EAGF,MAAM,UAAU,OAAO,iBAAiB,UAAU,KAAK,GAAG,IAAI;EAE9D,aAAa,OAAO,aAAa,OAAO;CAC1C,GAAG,CAAC,MAAM,CAAC;CAEX,SAAS,aAAmB;EAC1B,gBAAqB,IAAI;EACzB,UAAU,IAAI;CAChB;CAEA,IAAI,CAAC,MAAM,UACT,OAAO;CAGT,OACE,qBAAC,QAAD;EAAM,WAAU;YAAhB,CACE,oBAAC,QAAD,EAAA,UAAO,QAAc,CAAA,GACrB,qBAAC,UAAD;GACE,MAAK;GACL,aAAW,QAAQ,OAAO;GAC1B,WAAU;GACV,cAAY,GAAG,SAAS,WAAW,OAAO,GAAG,OAAO;GACpD,SAAS;aALX,CAOG,SACC,oBAAC,MAAD;IAAM,MAAK;IAAQ,WAAU;GAAmB,CAAA,IAEhD,oBAAC,MAAD;IAAM,MAAK;IAAO,WAAU;GAAmB,CAAA,GAEhD,SAAS,WAAW,MACf;IACJ;;AAEV"}
@@ -4,13 +4,14 @@ type ColumnClause = {
4
4
  clause: FilterClause;
5
5
  index: number;
6
6
  };
7
- export declare function ColumnFilterControl({ column, clauses, processing, onAdd, onUpdate, onRemove, onSearch, }: {
7
+ export declare function ColumnFilterControl({ column, clauses, processing, onAdd, onUpdate, onRemove, onReplace, onSearch, }: {
8
8
  column: TableColumn;
9
9
  clauses: ColumnClause[];
10
10
  processing: boolean;
11
11
  onAdd: (clause: FilterClause) => void;
12
12
  onUpdate: (index: number, clause: FilterClause) => void;
13
13
  onRemove: (index: number) => void;
14
+ onReplace: (field: string, clauses: FilterClause[]) => void;
14
15
  onSearch?: FilterOptionSearch;
15
16
  }): import("react").JSX.Element | null;
16
17
  export {};
@@ -8,7 +8,7 @@ import { TableFilterControl } from "./filter-controls.js";
8
8
  import { useState } from "react";
9
9
  import { jsx, jsxs } from "react/jsx-runtime";
10
10
  //#region resources/js/table/components/column-filter-control.tsx
11
- function ColumnFilterControl({ column, clauses, processing, onAdd, onUpdate, onRemove, onSearch }) {
11
+ function ColumnFilterControl({ column, clauses, processing, onAdd, onUpdate, onRemove, onReplace, onSearch }) {
12
12
  const { t } = useT("lattice");
13
13
  const filter = column.filter;
14
14
  if (!filter) return null;
@@ -16,9 +16,7 @@ function ColumnFilterControl({ column, clauses, processing, onAdd, onUpdate, onR
16
16
  column,
17
17
  clauses,
18
18
  processing,
19
- onAdd,
20
- onUpdate,
21
- onRemove,
19
+ onReplace,
22
20
  onSearch
23
21
  });
24
22
  const type = filter.type ?? "text";
@@ -92,18 +90,15 @@ function serializeColumnValue(value) {
92
90
  if (Array.isArray(value)) return value.join(",");
93
91
  return typeof value === "string" ? value : "";
94
92
  }
95
- /**
96
- * Renders a column's option dropdown by reusing the shared table-filter select
97
- * control, mapping its value to/from a single `eq` clause (or an `in` clause
98
- * carrying a comma-joined value when the column is `multiple`).
99
- */
100
- function ColumnSelectFilter({ column, clauses, processing, onAdd, onUpdate, onRemove, onSearch }) {
93
+ function ColumnSelectFilter({ column, clauses, processing, onReplace, onSearch }) {
101
94
  const filter = column.filter;
102
95
  if (!filter) return null;
103
96
  const multiple = filter.multiple;
104
97
  const operator = filter.defaultOperator;
98
+ const clauseOptions = filter.clauseOptions ?? [];
99
+ const activeClauseOption = findActiveClauseOption(clauses.map((entry) => entry.clause), clauseOptions);
105
100
  const active = clauses.find((entry) => entry.clause.operator === operator) ?? clauses[0];
106
- const value = multiple ? active?.clause.value ? active.clause.value.split(",") : [] : active?.clause.value ?? "";
101
+ const value = activeClauseOption ? activeClauseOption.value : multiple ? active?.clause.value ? active.clause.value.split(",") : [] : active?.clause.value ?? "";
107
102
  const data = {
108
103
  key: column.key,
109
104
  label: column.label,
@@ -118,19 +113,19 @@ function ColumnSelectFilter({ column, clauses, processing, onAdd, onUpdate, onRe
118
113
  function change(next) {
119
114
  const serialized = serializeColumnValue(next);
120
115
  if (serialized === "") {
121
- if (active) onRemove(active.index);
116
+ onReplace(column.key, []);
122
117
  return;
123
118
  }
124
- if (active) onUpdate(active.index, {
125
- ...active.clause,
126
- operator,
127
- value: serialized
128
- });
129
- else onAdd({
119
+ const clauseOption = clauseOptions.find((option) => option.value === serialized);
120
+ if (clauseOption) {
121
+ onReplace(column.key, clausesForOption(column.key, clauseOption));
122
+ return;
123
+ }
124
+ onReplace(column.key, [{
130
125
  field: column.key,
131
126
  operator,
132
127
  value: serialized
133
- });
128
+ }]);
134
129
  }
135
130
  return /* @__PURE__ */ jsx(TableFilterControl, {
136
131
  filter: data,
@@ -140,6 +135,20 @@ function ColumnSelectFilter({ column, clauses, processing, onAdd, onUpdate, onRe
140
135
  onSearch
141
136
  });
142
137
  }
138
+ function clausesForOption(field, option) {
139
+ return option.clauses.map((clause) => ({
140
+ field,
141
+ operator: clause.operator,
142
+ value: clause.value
143
+ }));
144
+ }
145
+ function findActiveClauseOption(clauses, options) {
146
+ return options.find((option) => clausesMatch(clauses, clausesForOption("", option)));
147
+ }
148
+ function clausesMatch(active, expected) {
149
+ if (active.length !== expected.length) return false;
150
+ return expected.every((clause) => active.some((current) => current.operator === clause.operator && current.value === clause.value));
151
+ }
143
152
  function FilterClauseList({ column, clauses, operators, defaultOperator, processing, onAdd, onUpdate, onRemove }) {
144
153
  const { t } = useT("lattice");
145
154
  const type = column.filter?.type ?? "text";
@@ -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 { FilterData, FilterType, Op } 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 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 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 onAdd={onAdd}\n onUpdate={onUpdate}\n onRemove={onRemove}\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\n/**\n * Renders a column's option dropdown by reusing the shared table-filter select\n * control, mapping its value to/from a single `eq` clause (or an `in` clause\n * carrying a comma-joined value when the column is `multiple`).\n */\nfunction ColumnSelectFilter({\n column,\n clauses,\n processing,\n onAdd,\n onUpdate,\n onRemove,\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 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 active = clauses.find((entry) => entry.clause.operator === operator) ?? clauses[0];\n const value: unknown = 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 if (active) {\n onRemove(active.index);\n }\n\n return;\n }\n\n if (active) {\n onUpdate(active.index, { ...active.clause, operator, value: serialized });\n } else {\n onAdd({ field: column.key, operator, value: serialized });\n }\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 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":"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"}
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":"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"}
@@ -26,12 +26,12 @@ function ColumnHeader({ column, processing, resizeHandleProps, sort, state }) {
26
26
  const align = column.align;
27
27
  return /* @__PURE__ */ jsxs("div", {
28
28
  "aria-sort": getColumnAriaSort(columnSort),
29
- className: cn("relative min-w-0 px-4 py-3 pr-5 align-middle font-medium text-lt-muted-fg", alignText(align)),
29
+ className: cn("relative min-w-0 px-4 py-3 pr-5 align-middle font-semibold text-lt-fg", alignText(align)),
30
30
  role: "columnheader",
31
31
  children: [column.sortable ? /* @__PURE__ */ jsxs("button", {
32
32
  type: "button",
33
33
  "aria-label": `Sort ${column.label}`,
34
- className: cn("flex w-full items-center gap-1.5 font-medium", alignJustify(align)),
34
+ className: cn("flex w-full items-center gap-1.5 font-semibold", alignJustify(align)),
35
35
  "data-test": `sort-${column.key}`,
36
36
  disabled: processing,
37
37
  onClick: () => sort(column),