@lattice-php/lattice 0.20.0 → 0.22.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 (212) hide show
  1. package/README.md +10 -0
  2. package/dist/action/components/action-form.d.ts +6 -1
  3. package/dist/action/components/action-form.js +48 -58
  4. package/dist/action/components/action-form.js.map +1 -1
  5. package/dist/action/components/action-trigger-provider.d.ts +1 -1
  6. package/dist/action/components/action-trigger-provider.js.map +1 -1
  7. package/dist/action/components/action.js +2 -1
  8. package/dist/action/components/action.js.map +1 -1
  9. package/dist/action/hooks/use-action.d.ts +1 -1
  10. package/dist/action/hooks/use-action.js +42 -33
  11. package/dist/action/hooks/use-action.js.map +1 -1
  12. package/dist/action/index.d.ts +0 -2
  13. package/dist/action/index.js +1 -2
  14. package/dist/action/lib/action-label.d.ts +2 -0
  15. package/dist/action/lib/action-label.js +9 -0
  16. package/dist/action/lib/action-label.js.map +1 -0
  17. package/dist/action/lib/run-action.d.ts +7 -7
  18. package/dist/action/lib/run-action.js +8 -7
  19. package/dist/action/lib/run-action.js.map +1 -1
  20. package/dist/chat/plugin.d.ts +0 -1
  21. package/dist/chat/plugin.js.map +1 -1
  22. package/dist/clipboard/index.d.ts +0 -4
  23. package/dist/clipboard/index.js +1 -15
  24. package/dist/{events → core}/event-names.js +1 -1
  25. package/dist/core/event-names.js.map +1 -0
  26. package/dist/core/hooks/use-window-event.d.ts +8 -0
  27. package/dist/core/hooks/use-window-event.js +22 -0
  28. package/dist/core/hooks/use-window-event.js.map +1 -0
  29. package/dist/core/index.js +3 -3
  30. package/dist/core/types.d.ts +8 -73
  31. package/dist/create-app.js +1 -1
  32. package/dist/effects/dispatch.js +1 -1
  33. package/dist/effects/dispatch.js.map +1 -1
  34. package/dist/effects/registry.d.ts +2 -18
  35. package/dist/effects/registry.js +4 -8
  36. package/dist/effects/registry.js.map +1 -1
  37. package/dist/event-bridge.js +10 -23
  38. package/dist/event-bridge.js.map +1 -1
  39. package/dist/form/components/fields/date-picker-field.js +1 -1
  40. package/dist/form/components/fields/file-upload.js +4 -6
  41. package/dist/form/components/fields/file-upload.js.map +1 -1
  42. package/dist/form/components/fields/select.js +2 -2
  43. package/dist/form/components/fields/select.js.map +1 -1
  44. package/dist/form/components/fields/table-rows.js +3 -15
  45. package/dist/form/components/fields/table-rows.js.map +1 -1
  46. package/dist/form/components/form.js +11 -24
  47. package/dist/form/components/form.js.map +1 -1
  48. package/dist/form/embed.d.ts +4 -0
  49. package/dist/form/embed.js +3 -1
  50. package/dist/form/lib/collect-fields.d.ts +7 -0
  51. package/dist/form/lib/collect-fields.js +19 -0
  52. package/dist/form/lib/collect-fields.js.map +1 -0
  53. package/dist/form/lib/field-errors.d.ts +3 -0
  54. package/dist/form/lib/field-errors.js +11 -0
  55. package/dist/form/lib/field-errors.js.map +1 -0
  56. package/dist/format/format-context.d.ts +6 -0
  57. package/dist/format/format-context.js +17 -0
  58. package/dist/format/format-context.js.map +1 -0
  59. package/dist/format/number.d.ts +1 -1
  60. package/dist/format/number.js.map +1 -1
  61. package/dist/format/value.d.ts +1 -1
  62. package/dist/format/value.js.map +1 -1
  63. package/dist/i18n/backend.js +1 -1
  64. package/dist/i18n/index.js +2 -2
  65. package/dist/i18n/instance.js +1 -1
  66. package/dist/i18n/locale-reload.d.ts +1 -1
  67. package/dist/i18n/locale-reload.js +7 -6
  68. package/dist/i18n/locale-reload.js.map +1 -1
  69. package/dist/i18n/locale.js +1 -1
  70. package/dist/i18n/locale.js.map +1 -1
  71. package/dist/i18n/page-props.js +1 -1
  72. package/dist/i18n/shared-props.js +1 -3
  73. package/dist/i18n/shared-props.js.map +1 -1
  74. package/dist/i18n/timezone.js +1 -1
  75. package/dist/i18n/timezone.js.map +1 -1
  76. package/dist/index.d.ts +6 -7
  77. package/dist/index.js +7 -8
  78. package/dist/lattice.css +105 -0
  79. package/dist/layout/components/callouts.js +1 -1
  80. package/dist/layout/components/sidebar.js +15 -48
  81. package/dist/layout/components/sidebar.js.map +1 -1
  82. package/dist/layout/plugin.d.ts +0 -1
  83. package/dist/layout/plugin.js.map +1 -1
  84. package/dist/lib/is-truthy.d.ts +2 -0
  85. package/dist/lib/is-truthy.js +9 -0
  86. package/dist/lib/is-truthy.js.map +1 -0
  87. package/dist/lib/use-debounced-callback.d.ts +10 -0
  88. package/dist/lib/use-debounced-callback.js +34 -0
  89. package/dist/lib/use-debounced-callback.js.map +1 -0
  90. package/dist/lib/use-media-query.d.ts +5 -0
  91. package/dist/lib/use-media-query.js +26 -0
  92. package/dist/lib/use-media-query.js.map +1 -0
  93. package/dist/lib/use-persistent-state.d.ts +13 -0
  94. package/dist/lib/use-persistent-state.js +62 -0
  95. package/dist/lib/use-persistent-state.js.map +1 -0
  96. package/dist/notifications/components/notifications.js +4 -1
  97. package/dist/notifications/components/notifications.js.map +1 -1
  98. package/dist/notifications/plugin.d.ts +0 -1
  99. package/dist/notifications/plugin.js.map +1 -1
  100. package/dist/provider-base.js +1 -1
  101. package/dist/realtime/listeners.d.ts +1 -1
  102. package/dist/realtime/listeners.js +1 -1
  103. package/dist/realtime/listeners.js.map +1 -1
  104. package/dist/registry.d.ts +5 -0
  105. package/dist/registry.js.map +1 -1
  106. package/dist/remote/components/data-list.js +1 -1
  107. package/dist/remote/plugin.d.ts +0 -1
  108. package/dist/remote/plugin.js.map +1 -1
  109. package/dist/table/components/bulk-bar.js +18 -15
  110. package/dist/table/components/bulk-bar.js.map +1 -1
  111. package/dist/table/components/cells/boolean-cell.js +1 -3
  112. package/dist/table/components/cells/boolean-cell.js.map +1 -1
  113. package/dist/table/components/cells/copyable-cell.d.ts +9 -0
  114. package/dist/table/components/cells/copyable-cell.js +17 -0
  115. package/dist/table/components/cells/copyable-cell.js.map +1 -0
  116. package/dist/table/components/cells/money-cell.js +7 -18
  117. package/dist/table/components/cells/money-cell.js.map +1 -1
  118. package/dist/table/components/cells/number-cell.js +14 -27
  119. package/dist/table/components/cells/number-cell.js.map +1 -1
  120. package/dist/table/components/cells/numeric-cell.d.ts +10 -0
  121. package/dist/table/components/cells/numeric-cell.js +25 -0
  122. package/dist/table/components/cells/numeric-cell.js.map +1 -0
  123. package/dist/table/components/cells/stack-cell.js +1 -1
  124. package/dist/table/components/cells/text-cell.js +4 -5
  125. package/dist/table/components/cells/text-cell.js.map +1 -1
  126. package/dist/table/components/filter-controls.js +1 -3
  127. package/dist/table/components/filter-controls.js.map +1 -1
  128. package/dist/table/components/table-cell.js.map +1 -1
  129. package/dist/table/components/table-search.js +4 -7
  130. package/dist/table/components/table-search.js.map +1 -1
  131. package/dist/table/components/table.js +2 -3
  132. package/dist/table/components/table.js.map +1 -1
  133. package/dist/table/hooks/use-column-visibility.js +29 -58
  134. package/dist/table/hooks/use-column-visibility.js.map +1 -1
  135. package/dist/table/hooks/use-table.js +5 -13
  136. package/dist/table/hooks/use-table.js.map +1 -1
  137. package/dist/table/lib/bulk.d.ts +2 -0
  138. package/dist/table/lib/bulk.js +5 -2
  139. package/dist/table/lib/bulk.js.map +1 -1
  140. package/dist/table/lib/query.js +1 -1
  141. package/dist/table/lib/query.js.map +1 -1
  142. package/dist/table/plugin.d.ts +0 -1
  143. package/dist/table/plugin.js.map +1 -1
  144. package/dist/table/types.d.ts +4 -57
  145. package/dist/toast/callout.js +1 -1
  146. package/dist/toast/callout.js.map +1 -1
  147. package/dist/toast/index.d.ts +1 -1
  148. package/dist/toast/index.js +2 -2
  149. package/dist/toast/toast.d.ts +0 -2
  150. package/dist/toast/toast.js +3 -10
  151. package/dist/toast/toast.js.map +1 -1
  152. package/dist/toast/toaster.js +2 -2
  153. package/dist/types/generated.d.ts +224 -80
  154. package/dist/types/index.d.ts +1 -1
  155. package/dist/ui/button.d.ts +2 -2
  156. package/dist/ui/chart-view.js +7 -30
  157. package/dist/ui/chart-view.js.map +1 -1
  158. package/dist/ui/click-behavior.d.ts +4 -4
  159. package/dist/ui/click-behavior.js.map +1 -1
  160. package/dist/ui/collapsible.js +3 -15
  161. package/dist/ui/collapsible.js.map +1 -1
  162. package/dist/ui/combobox.js +6 -3
  163. package/dist/ui/combobox.js.map +1 -1
  164. package/dist/ui/confirm-dialog.js +1 -1
  165. package/dist/ui/confirm-dialog.js.map +1 -1
  166. package/dist/ui/control.d.ts +1 -1
  167. package/dist/ui/dialog.d.ts +6 -2
  168. package/dist/ui/dialog.js +29 -5
  169. package/dist/ui/dialog.js.map +1 -1
  170. package/dist/ui/fragment.js +1 -1
  171. package/dist/ui/fragment.js.map +1 -1
  172. package/dist/ui/icon-button.d.ts +1 -1
  173. package/dist/ui/image-preview.js +1 -1
  174. package/dist/ui/image-preview.js.map +1 -1
  175. package/dist/ui/modal.js +6 -3
  176. package/dist/ui/modal.js.map +1 -1
  177. package/dist/ui/pill.d.ts +2 -0
  178. package/dist/ui/pill.js +10 -0
  179. package/dist/ui/pill.js.map +1 -0
  180. package/dist/ui/plugin.js.map +1 -1
  181. package/dist/ui/progress.js +1 -1
  182. package/dist/ui/section.js +2 -15
  183. package/dist/ui/section.js.map +1 -1
  184. package/dist/ui/segmented-pills.js +2 -1
  185. package/dist/ui/segmented-pills.js.map +1 -1
  186. package/dist/ui/tabs.js +12 -10
  187. package/dist/ui/tabs.js.map +1 -1
  188. package/dist/ui/tree-context.js +11 -13
  189. package/dist/ui/tree-context.js.map +1 -1
  190. package/dist/ui/tree.js +1 -1
  191. package/dist/ui/tree.js.map +1 -1
  192. package/dist/ui/use-collapsible-state.d.ts +7 -0
  193. package/dist/ui/use-collapsible-state.js +21 -0
  194. package/dist/ui/use-collapsible-state.js.map +1 -0
  195. package/dist-standalone/chunks/chart-view-C6R5iPra.js +36 -0
  196. package/dist-standalone/chunks/{date-picker-field-VbH9xrQO.js → date-picker-field-DhhEB-Mh.js} +1 -1
  197. package/dist-standalone/chunks/{page-props-AIa5UR6n.js → page-props-DOfg30l5.js} +1 -1
  198. package/dist-standalone/chunks/{rich-editor-field-DLQTyt36.js → rich-editor-field-DORXTHPw.js} +2 -2
  199. package/dist-standalone/chunks/{subscriptions-DY2tj9Im.js → subscriptions-DK_s_F2g.js} +1 -1
  200. package/dist-standalone/chunks/use-effect-dispatcher-EVZxmfoo.js +97 -0
  201. package/dist-standalone/lattice.css +1 -1
  202. package/dist-standalone/lattice.js +15 -15
  203. package/dist-standalone/manifest.json +8 -8
  204. package/package.json +3 -3
  205. package/dist/clipboard/index.js.map +0 -1
  206. package/dist/events/event-names.js.map +0 -1
  207. package/dist/table/components/table-action-node.d.ts +0 -4
  208. package/dist/table/components/table-action-node.js +0 -24
  209. package/dist/table/components/table-action-node.js.map +0 -1
  210. package/dist-standalone/chunks/chart-view-wJmnVu7L.js +0 -36
  211. package/dist-standalone/chunks/use-effect-dispatcher-feEs0GCP.js +0 -97
  212. /package/dist/{events → core}/event-names.d.ts +0 -0
package/README.md CHANGED
@@ -36,6 +36,16 @@ See [Installation](https://latticephp.com/introduction/installation/) for the fu
36
36
 
37
37
  Full documentation, guides, and examples live at **[latticephp.com](https://latticephp.com)**.
38
38
 
39
+ ## Generating types for custom wire classes
40
+
41
+ If you define your own Lattice components, fields, columns, filters, or effects in PHP, regenerate the matching TypeScript so the React side stays in lockstep:
42
+
43
+ ```bash
44
+ php artisan lattice:typescript
45
+ ```
46
+
47
+ Projects that use only Lattice's bundled components never need this — the command detects it and no-ops. Generating custom types requires the dev dependency `spatie/laravel-typescript-transformer`.
48
+
39
49
  ## License
40
50
 
41
51
  The MIT License (MIT). See [LICENSE.md](LICENSE.md) for details.
@@ -1,4 +1,6 @@
1
+ import { DialogPlacement } from '../../ui/dialog.js';
1
2
  import { Node } from '../../core/types.js';
3
+ import { ModalWidth } from '../../types/generated.js';
2
4
  import { ActionResponse } from '../../effects/dispatch.js';
3
5
  type ActionFormProps = {
4
6
  cancelLabel: string;
@@ -12,13 +14,16 @@ type ActionFormProps = {
12
14
  method: string;
13
15
  onClose: () => void;
14
16
  onSuccess: (response: ActionResponse) => void;
17
+ /** Dialog placement for the form modal; sheets dock to a viewport edge. */
18
+ placement?: DialogPlacement;
15
19
  submitLabel: string;
16
20
  title: string;
21
+ width?: ModalWidth;
17
22
  };
18
23
  /**
19
24
  * Fetch a lazily-served form schema from the action endpoint while `enabled`,
20
25
  * so it can be prefilled per record. Returns null until it arrives.
21
26
  */
22
27
  export declare function useLazyActionForm(endpoint: string, componentRef: string, enabled: boolean): Node | null;
23
- export declare function ActionForm({ description, formNode, onClose, title, ...rest }: ActionFormProps): import("react").JSX.Element;
28
+ export declare function ActionForm({ description, formNode, onClose, placement, title, width, ...rest }: ActionFormProps): import("react").JSX.Element;
24
29
  export {};
@@ -1,6 +1,7 @@
1
1
  import { Renderer } from "../../core/renderer.js";
2
2
  import { useT } from "../../i18n/instance.js";
3
- import { dispatchActionError } from "../../effects/dispatch.js";
3
+ import { dispatchActionError, getActionEffects } from "../../effects/dispatch.js";
4
+ import { useEffectDispatcher } from "../../effects/use-effect-dispatcher.js";
4
5
  import { Button } from "../../ui/button.js";
5
6
  import { Dialog, DialogContent, DialogHeader } from "../../ui/dialog.js";
6
7
  import { Spinner } from "../../ui/spinner.js";
@@ -8,11 +9,13 @@ import { apiFetch } from "../../core/api.js";
8
9
  import { Skeleton } from "../../ui/skeleton.js";
9
10
  import { FormProvider } from "../../form/hooks/context.js";
10
11
  import { PrefillProvider } from "../../form/hooks/prefill-context.js";
11
- import { walkFields } from "../../form/lib/field-props.js";
12
12
  import { ResolvedNodesProvider } from "../../form/hooks/resolved-nodes.js";
13
13
  import { FormValuesProvider, useFormValues } from "../../form/hooks/values.js";
14
14
  import "../../form/lib/form-transport.js";
15
15
  import { useFormResolver } from "../../form/hooks/use-form-resolver.js";
16
+ import { collectFields } from "../../form/lib/collect-fields.js";
17
+ import { firstErrors } from "../../form/lib/field-errors.js";
18
+ import { useDebouncedCallback } from "../../lib/use-debounced-callback.js";
16
19
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
17
20
  import { jsx, jsxs } from "react/jsx-runtime";
18
21
  //#region resources/js/action/components/action-form.tsx
@@ -43,11 +46,6 @@ function useLazyActionForm(endpoint, componentRef, enabled) {
43
46
  ]);
44
47
  return node;
45
48
  }
46
- function firstErrors(errors) {
47
- const result = {};
48
- for (const [key, value] of Object.entries(errors ?? {})) result[key] = Array.isArray(value) ? value[0] : value;
49
- return result;
50
- }
51
49
  function ActionFormSkeleton() {
52
50
  return /* @__PURE__ */ jsxs("div", {
53
51
  className: "space-y-4",
@@ -59,22 +57,6 @@ function ActionFormSkeleton() {
59
57
  ]
60
58
  });
61
59
  }
62
- function collectFields(formNode) {
63
- const labels = {};
64
- const values = {};
65
- walkFields(formNode.schema, (props) => {
66
- if (!props.name) return;
67
- if (props.label) labels[props.name] = props.label;
68
- if (props.value !== void 0) values[props.name] = props.value;
69
- });
70
- return {
71
- labels,
72
- values: {
73
- ...values,
74
- ...formNode.props?.state
75
- }
76
- };
77
- }
78
60
  function ActionFormBody({ cancelLabel, componentRef, endpoint, extraData, fieldLabels, formNode, method, onClose, onSuccess, precognitive, submitLabel }) {
79
61
  const values = useFormValues();
80
62
  const valuesRef = useRef(values);
@@ -82,9 +64,9 @@ function ActionFormBody({ cancelLabel, componentRef, endpoint, extraData, fieldL
82
64
  const extraDataRef = useRef(extraData);
83
65
  extraDataRef.current = extraData;
84
66
  const { nodes: resolvedNodes, markUserEdit } = useFormResolver(endpoint, componentRef, formNode.schema);
67
+ const dispatch = useEffectDispatcher();
85
68
  const [errors, setErrors] = useState({});
86
69
  const [processing, setProcessing] = useState(false);
87
- const timer = useRef(void 0);
88
70
  const request = useCallback((extraHeaders) => apiFetch(endpoint, {
89
71
  body: JSON.stringify({
90
72
  ...valuesRef.current,
@@ -105,44 +87,42 @@ function ActionFormBody({ cancelLabel, componentRef, endpoint, extraData, fieldL
105
87
  [field]: void 0
106
88
  });
107
89
  }, []);
90
+ const runValidation = useDebouncedCallback((field) => {
91
+ request({
92
+ Precognition: "true",
93
+ "Precognition-Validate-Only": field
94
+ }).then(async (response) => {
95
+ if (response.status === 422) {
96
+ const body = await response.json();
97
+ setErrors((current) => ({
98
+ ...current,
99
+ ...firstErrors(body.errors)
100
+ }));
101
+ return;
102
+ }
103
+ clearErrors(field);
104
+ }).catch(() => {});
105
+ }, 250);
108
106
  const validate = useCallback((field) => {
109
- if (!precognitive) return;
110
- window.clearTimeout(timer.current);
111
- timer.current = window.setTimeout(() => {
112
- request({
113
- Precognition: "true",
114
- "Precognition-Validate-Only": field
115
- }).then(async (response) => {
116
- if (response.status === 422) {
117
- const body = await response.json();
118
- setErrors((current) => ({
119
- ...current,
120
- ...firstErrors(body.errors)
121
- }));
122
- return;
123
- }
124
- clearErrors(field);
125
- }).catch(() => {});
126
- }, 250);
127
- }, [
128
- clearErrors,
129
- precognitive,
130
- request
131
- ]);
107
+ if (precognitive) runValidation(field);
108
+ }, [precognitive, runValidation]);
132
109
  const submit = useCallback(() => {
133
110
  setProcessing(true);
134
111
  request().then(async (response) => {
135
- if (response.status === 422) {
136
- setErrors(firstErrors((await response.json()).errors));
137
- return;
138
- }
139
- if (!response.ok) {
140
- dispatchActionError(/* @__PURE__ */ new Error(`Action request failed with status ${response.status}`));
112
+ const body = await response.json().catch(() => ({}));
113
+ dispatch(getActionEffects(body.effects));
114
+ if (response.status === 422 && body.errors) {
115
+ setErrors(firstErrors(body.errors));
141
116
  return;
142
117
  }
143
- onSuccess(await response.json());
118
+ if (!response.ok) return;
119
+ onSuccess(body);
144
120
  }).catch((error) => dispatchActionError(error)).finally(() => setProcessing(false));
145
- }, [onSuccess, request]);
121
+ }, [
122
+ dispatch,
123
+ onSuccess,
124
+ request
125
+ ]);
146
126
  return /* @__PURE__ */ jsx(FormProvider, {
147
127
  value: useMemo(() => ({
148
128
  action: endpoint,
@@ -196,7 +176,16 @@ function ActionFormBody({ cancelLabel, componentRef, endpoint, extraData, fieldL
196
176
  }
197
177
  function ActionFormContent({ formNode, ...rest }) {
198
178
  const precognitive = Boolean(formNode.props?.precognitive);
199
- const { labels: fieldLabels, values: initialValues } = useMemo(() => collectFields(formNode), [formNode]);
179
+ const { labels: fieldLabels, values: initialValues } = useMemo(() => {
180
+ const { labels, values } = collectFields(formNode.schema);
181
+ return {
182
+ labels,
183
+ values: {
184
+ ...values,
185
+ ...formNode.props?.state
186
+ }
187
+ };
188
+ }, [formNode]);
200
189
  return /* @__PURE__ */ jsx(FormValuesProvider, {
201
190
  initial: initialValues,
202
191
  children: /* @__PURE__ */ jsx(ActionFormBody, {
@@ -207,7 +196,7 @@ function ActionFormContent({ formNode, ...rest }) {
207
196
  })
208
197
  });
209
198
  }
210
- function ActionForm({ description, formNode, onClose, title, ...rest }) {
199
+ function ActionForm({ description, formNode, onClose, placement, title, width, ...rest }) {
211
200
  const { t } = useT("lattice");
212
201
  return /* @__PURE__ */ jsx(Dialog, {
213
202
  open: true,
@@ -216,7 +205,8 @@ function ActionForm({ description, formNode, onClose, title, ...rest }) {
216
205
  },
217
206
  children: /* @__PURE__ */ jsxs(DialogContent, {
218
207
  ...description ? {} : { "aria-describedby": void 0 },
219
- className: "max-h-[min(680px,calc(100vh-2rem))] w-full max-w-lg overflow-y-auto",
208
+ placement,
209
+ width,
220
210
  children: [/* @__PURE__ */ jsx(DialogHeader, {
221
211
  closeLabel: t("common.close", "Close"),
222
212
  description,
@@ -1 +1 @@
1
- {"version":3,"file":"action-form.js","names":[],"sources":["../../../resources/js/action/components/action-form.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { apiFetch } from \"@lattice-php/lattice/core/api\";\nimport { Button } from \"@lattice-php/lattice/ui/button\";\nimport { Dialog, DialogContent, DialogHeader } from \"@lattice-php/lattice/ui/dialog\";\nimport { Skeleton } from \"@lattice-php/lattice/ui/skeleton\";\nimport { Spinner } from \"@lattice-php/lattice/ui/spinner\";\nimport { Renderer } from \"@lattice-php/lattice/core/renderer\";\nimport type { Node } from \"@lattice-php/lattice/core/types\";\nimport {\n FORM_DEBOUNCE_MS,\n FormProvider,\n FormValuesProvider,\n PrefillProvider,\n ResolvedNodesProvider,\n useFormResolver,\n useFormValues,\n walkFields,\n} from \"@lattice-php/lattice/form/embed\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\nimport { dispatchActionError } from \"@lattice-php/lattice/effects/dispatch\";\nimport type { ActionResponse } from \"@lattice-php/lattice/effects/dispatch\";\n\ntype FieldErrors = Record<string, string | undefined>;\n\ntype ActionFormProps = {\n cancelLabel: string;\n componentRef: string;\n description?: string;\n endpoint: string;\n /** Extra payload merged into every request, e.g. a bulk action's selection. */\n extraData?: Record<string, unknown>;\n /** The form to render; null while a lazy schema is still being fetched. */\n formNode: Node | null;\n method: string;\n onClose: () => void;\n onSuccess: (response: ActionResponse) => void;\n submitLabel: string;\n title: string;\n};\n\n/**\n * Fetch a lazily-served form schema from the action endpoint while `enabled`,\n * so it can be prefilled per record. Returns null until it arrives.\n */\nexport function useLazyActionForm(\n endpoint: string,\n componentRef: string,\n enabled: boolean,\n): Node | null {\n const [node, setNode] = useState<Node | null>(null);\n\n useEffect(() => {\n if (!enabled) {\n setNode(null);\n\n return;\n }\n\n const controller = new AbortController();\n\n void apiFetch(endpoint, {\n body: JSON.stringify({ _form: true }),\n ref: componentRef,\n method: \"POST\",\n signal: controller.signal,\n throwOnError: false,\n })\n .then((response) => (response.ok ? (response.json() as Promise<Node>) : null))\n .then((fetched) => setNode(fetched))\n .catch(() => {});\n\n return () => controller.abort();\n }, [enabled, endpoint, componentRef]);\n\n return node;\n}\n\nfunction firstErrors(errors: Record<string, string[] | string> | undefined): FieldErrors {\n const result: FieldErrors = {};\n\n for (const [key, value] of Object.entries(errors ?? {})) {\n result[key] = Array.isArray(value) ? value[0] : value;\n }\n\n return result;\n}\n\nfunction ActionFormSkeleton() {\n return (\n <div className=\"space-y-4\" data-lattice-action-form-loading>\n <Skeleton className=\"h-4 w-24\" />\n <Skeleton className=\"h-10 w-full\" />\n <Skeleton className=\"h-10 w-full\" />\n </div>\n );\n}\n\ntype CollectedFields = {\n labels: Record<string, string>;\n values: Record<string, unknown>;\n};\n\nfunction collectFields(formNode: Node): CollectedFields {\n const labels: Record<string, string> = {};\n const values: Record<string, unknown> = {};\n\n walkFields(formNode.schema, (props) => {\n if (!props.name) {\n return;\n }\n if (props.label) {\n labels[props.name] = props.label;\n }\n if (props.value !== undefined) {\n values[props.name] = props.value;\n }\n });\n\n return {\n labels,\n values: { ...values, ...(formNode.props?.state as Record<string, unknown> | undefined) },\n };\n}\n\nfunction ActionFormBody({\n cancelLabel,\n componentRef,\n endpoint,\n extraData,\n fieldLabels,\n formNode,\n method,\n onClose,\n onSuccess,\n precognitive,\n submitLabel,\n}: Omit<ActionFormProps, \"description\" | \"title\"> & {\n fieldLabels: Record<string, string>;\n formNode: Node;\n precognitive: boolean;\n}) {\n const values = useFormValues();\n const valuesRef = useRef(values);\n valuesRef.current = values;\n const extraDataRef = useRef(extraData);\n extraDataRef.current = extraData;\n const { nodes: resolvedNodes, markUserEdit } = useFormResolver(\n endpoint,\n componentRef,\n formNode.schema,\n );\n\n const [errors, setErrors] = useState<FieldErrors>({});\n const [processing, setProcessing] = useState(false);\n const timer = useRef<number | undefined>(undefined);\n\n const request = useCallback(\n (extraHeaders?: Record<string, string>): Promise<Response> =>\n apiFetch(endpoint, {\n body: JSON.stringify({ ...valuesRef.current, ...extraDataRef.current }),\n method,\n ref: componentRef,\n headers: extraHeaders,\n throwOnError: false,\n }),\n [componentRef, endpoint, method],\n );\n\n const clearErrors = useCallback((field: string) => {\n setErrors((current) =>\n current[field] === undefined ? current : { ...current, [field]: undefined },\n );\n }, []);\n\n const validate = useCallback(\n (field: string) => {\n if (!precognitive) {\n return;\n }\n\n window.clearTimeout(timer.current);\n timer.current = window.setTimeout(() => {\n void request({ Precognition: \"true\", \"Precognition-Validate-Only\": field })\n .then(async (response) => {\n if (response.status === 422) {\n const body = (await response.json()) as { errors?: Record<string, string[]> };\n setErrors((current) => ({ ...current, ...firstErrors(body.errors) }));\n\n return;\n }\n\n clearErrors(field);\n })\n .catch(() => {});\n }, FORM_DEBOUNCE_MS);\n },\n [clearErrors, precognitive, request],\n );\n\n const submit = useCallback(() => {\n setProcessing(true);\n\n void request()\n .then(async (response) => {\n if (response.status === 422) {\n const body = (await response.json()) as { errors?: Record<string, string[]> };\n setErrors(firstErrors(body.errors));\n\n return;\n }\n\n if (!response.ok) {\n dispatchActionError(new Error(`Action request failed with status ${response.status}`));\n\n return;\n }\n\n onSuccess((await response.json()) as ActionResponse);\n })\n .catch((error: unknown) => dispatchActionError(error))\n .finally(() => setProcessing(false));\n }, [onSuccess, request]);\n\n const context = useMemo(\n () => ({\n action: endpoint,\n clearErrors,\n componentRef,\n errors,\n fieldLabels,\n precognitive,\n processing,\n validate,\n }),\n [clearErrors, componentRef, endpoint, errors, fieldLabels, precognitive, processing, validate],\n );\n\n return (\n <FormProvider value={context}>\n <form\n className=\"flex flex-col gap-6\"\n onSubmit={(event) => {\n event.preventDefault();\n submit();\n }}\n >\n <PrefillProvider value={{ markUserEdit }}>\n <ResolvedNodesProvider nodes={resolvedNodes}>\n <Renderer nodes={formNode.schema ?? []} />\n </ResolvedNodesProvider>\n </PrefillProvider>\n\n <div className=\"flex justify-end gap-3\">\n <Button\n data-test=\"action-form-cancel\"\n disabled={processing}\n onClick={onClose}\n type=\"button\"\n variant=\"ghost\"\n >\n {cancelLabel}\n </Button>\n\n <Button data-test=\"action-form-submit\" disabled={processing} type=\"submit\">\n {processing && <Spinner />}\n {submitLabel}\n </Button>\n </div>\n </form>\n </FormProvider>\n );\n}\n\nfunction ActionFormContent({\n formNode,\n ...rest\n}: Omit<ActionFormProps, \"description\" | \"title\"> & { formNode: Node }) {\n const precognitive = Boolean(formNode.props?.precognitive);\n const { labels: fieldLabels, values: initialValues } = useMemo(\n () => collectFields(formNode),\n [formNode],\n );\n\n return (\n <FormValuesProvider initial={initialValues}>\n <ActionFormBody\n fieldLabels={fieldLabels}\n formNode={formNode}\n precognitive={precognitive}\n {...rest}\n />\n </FormValuesProvider>\n );\n}\n\nexport function ActionForm({ description, formNode, onClose, title, ...rest }: ActionFormProps) {\n const { t } = useT(\"lattice\");\n\n return (\n <Dialog\n open\n onOpenChange={(open) => {\n if (!open) {\n onClose();\n }\n }}\n >\n <DialogContent\n {...(description ? {} : { \"aria-describedby\": undefined })}\n className=\"max-h-[min(680px,calc(100vh-2rem))] w-full max-w-lg overflow-y-auto\"\n >\n <DialogHeader\n closeLabel={t(\"common.close\", \"Close\")}\n description={description}\n title={title}\n />\n\n <div className=\"mt-6\">\n {formNode ? (\n <ActionFormContent formNode={formNode} onClose={onClose} {...rest} />\n ) : (\n <ActionFormSkeleton />\n )}\n </div>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"action-form.js","names":[],"sources":["../../../resources/js/action/components/action-form.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { apiFetch } from \"@lattice-php/lattice/core/api\";\nimport { Button } from \"@lattice-php/lattice/ui/button\";\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n type DialogPlacement,\n} from \"@lattice-php/lattice/ui/dialog\";\nimport { Skeleton } from \"@lattice-php/lattice/ui/skeleton\";\nimport { Spinner } from \"@lattice-php/lattice/ui/spinner\";\nimport { Renderer } from \"@lattice-php/lattice/core/renderer\";\nimport type { Node } from \"@lattice-php/lattice/core/types\";\nimport type { ModalWidth } from \"@lattice-php/lattice/types/generated\";\nimport {\n collectFields,\n FORM_DEBOUNCE_MS,\n FormProvider,\n FormValuesProvider,\n firstErrors,\n PrefillProvider,\n ResolvedNodesProvider,\n useFormResolver,\n useFormValues,\n} from \"@lattice-php/lattice/form/embed\";\nimport type { FieldErrors } from \"@lattice-php/lattice/form/embed\";\nimport { useDebouncedCallback } from \"@lattice-php/lattice/lib/use-debounced-callback\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\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\";\n\ntype ActionFormProps = {\n cancelLabel: string;\n componentRef: string;\n description?: string;\n endpoint: string;\n /** Extra payload merged into every request, e.g. a bulk action's selection. */\n extraData?: Record<string, unknown>;\n /** The form to render; null while a lazy schema is still being fetched. */\n formNode: Node | null;\n method: string;\n onClose: () => void;\n onSuccess: (response: ActionResponse) => void;\n /** Dialog placement for the form modal; sheets dock to a viewport edge. */\n placement?: DialogPlacement;\n submitLabel: string;\n title: string;\n width?: ModalWidth;\n};\n\n/**\n * Fetch a lazily-served form schema from the action endpoint while `enabled`,\n * so it can be prefilled per record. Returns null until it arrives.\n */\nexport function useLazyActionForm(\n endpoint: string,\n componentRef: string,\n enabled: boolean,\n): Node | null {\n const [node, setNode] = useState<Node | null>(null);\n\n useEffect(() => {\n if (!enabled) {\n setNode(null);\n\n return;\n }\n\n const controller = new AbortController();\n\n void apiFetch(endpoint, {\n body: JSON.stringify({ _form: true }),\n ref: componentRef,\n method: \"POST\",\n signal: controller.signal,\n throwOnError: false,\n })\n .then((response) => (response.ok ? (response.json() as Promise<Node>) : null))\n .then((fetched) => setNode(fetched))\n .catch(() => {});\n\n return () => controller.abort();\n }, [enabled, endpoint, componentRef]);\n\n return node;\n}\n\nfunction ActionFormSkeleton() {\n return (\n <div className=\"space-y-4\" data-lattice-action-form-loading>\n <Skeleton className=\"h-4 w-24\" />\n <Skeleton className=\"h-10 w-full\" />\n <Skeleton className=\"h-10 w-full\" />\n </div>\n );\n}\n\nfunction ActionFormBody({\n cancelLabel,\n componentRef,\n endpoint,\n extraData,\n fieldLabels,\n formNode,\n method,\n onClose,\n onSuccess,\n precognitive,\n submitLabel,\n}: Omit<ActionFormProps, \"description\" | \"title\"> & {\n fieldLabels: Record<string, string>;\n formNode: Node;\n precognitive: boolean;\n}) {\n const values = useFormValues();\n const valuesRef = useRef(values);\n valuesRef.current = values;\n const extraDataRef = useRef(extraData);\n extraDataRef.current = extraData;\n const { nodes: resolvedNodes, markUserEdit } = useFormResolver(\n endpoint,\n componentRef,\n formNode.schema,\n );\n\n const dispatch = useEffectDispatcher();\n const [errors, setErrors] = useState<FieldErrors>({});\n const [processing, setProcessing] = useState(false);\n\n const request = useCallback(\n (extraHeaders?: Record<string, string>): Promise<Response> =>\n apiFetch(endpoint, {\n body: JSON.stringify({ ...valuesRef.current, ...extraDataRef.current }),\n method,\n ref: componentRef,\n headers: extraHeaders,\n throwOnError: false,\n }),\n [componentRef, endpoint, method],\n );\n\n const clearErrors = useCallback((field: string) => {\n setErrors((current) =>\n current[field] === undefined ? current : { ...current, [field]: undefined },\n );\n }, []);\n\n const runValidation = useDebouncedCallback((field: string) => {\n void request({ Precognition: \"true\", \"Precognition-Validate-Only\": field })\n .then(async (response) => {\n if (response.status === 422) {\n const body = (await response.json()) as { errors?: Record<string, string[]> };\n setErrors((current) => ({ ...current, ...firstErrors(body.errors) }));\n\n return;\n }\n\n clearErrors(field);\n })\n .catch(() => {});\n }, FORM_DEBOUNCE_MS);\n\n const validate = useCallback(\n (field: string) => {\n if (precognitive) {\n runValidation(field);\n }\n },\n [precognitive, runValidation],\n );\n\n const submit = useCallback(() => {\n setProcessing(true);\n\n void request()\n .then(async (response) => {\n const body = (await response.json().catch(() => ({}))) as ActionResponse & {\n errors?: Record<string, string[]>;\n };\n\n dispatch(getActionEffects(body.effects));\n\n if (response.status === 422 && body.errors) {\n setErrors(firstErrors(body.errors));\n\n return;\n }\n\n if (!response.ok) {\n return;\n }\n\n onSuccess(body);\n })\n .catch((error: unknown) => dispatchActionError(error))\n .finally(() => setProcessing(false));\n }, [dispatch, onSuccess, request]);\n\n const context = useMemo(\n () => ({\n action: endpoint,\n clearErrors,\n componentRef,\n errors,\n fieldLabels,\n precognitive,\n processing,\n validate,\n }),\n [clearErrors, componentRef, endpoint, errors, fieldLabels, precognitive, processing, validate],\n );\n\n return (\n <FormProvider value={context}>\n <form\n className=\"flex flex-col gap-6\"\n onSubmit={(event) => {\n event.preventDefault();\n submit();\n }}\n >\n <PrefillProvider value={{ markUserEdit }}>\n <ResolvedNodesProvider nodes={resolvedNodes}>\n <Renderer nodes={formNode.schema ?? []} />\n </ResolvedNodesProvider>\n </PrefillProvider>\n\n <div className=\"flex justify-end gap-3\">\n <Button\n data-test=\"action-form-cancel\"\n disabled={processing}\n onClick={onClose}\n type=\"button\"\n variant=\"ghost\"\n >\n {cancelLabel}\n </Button>\n\n <Button data-test=\"action-form-submit\" disabled={processing} type=\"submit\">\n {processing && <Spinner />}\n {submitLabel}\n </Button>\n </div>\n </form>\n </FormProvider>\n );\n}\n\nfunction ActionFormContent({\n formNode,\n ...rest\n}: Omit<ActionFormProps, \"description\" | \"title\"> & { formNode: Node }) {\n const precognitive = Boolean(formNode.props?.precognitive);\n const { labels: fieldLabels, values: initialValues } = useMemo(() => {\n const { labels, values } = collectFields(formNode.schema);\n\n return {\n labels,\n values: { ...values, ...(formNode.props?.state as Record<string, unknown> | undefined) },\n };\n }, [formNode]);\n\n return (\n <FormValuesProvider initial={initialValues}>\n <ActionFormBody\n fieldLabels={fieldLabels}\n formNode={formNode}\n precognitive={precognitive}\n {...rest}\n />\n </FormValuesProvider>\n );\n}\n\nexport function ActionForm({\n description,\n formNode,\n onClose,\n placement,\n title,\n width,\n ...rest\n}: ActionFormProps) {\n const { t } = useT(\"lattice\");\n\n return (\n <Dialog\n open\n onOpenChange={(open) => {\n if (!open) {\n onClose();\n }\n }}\n >\n <DialogContent\n {...(description ? {} : { \"aria-describedby\": undefined })}\n placement={placement}\n width={width}\n >\n <DialogHeader\n closeLabel={t(\"common.close\", \"Close\")}\n description={description}\n title={title}\n />\n\n <div className=\"mt-6\">\n {formNode ? (\n <ActionFormContent formNode={formNode} onClose={onClose} {...rest} />\n ) : (\n <ActionFormSkeleton />\n )}\n </div>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":"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"}
@@ -2,7 +2,7 @@ import { ReactNode } from 'react';
2
2
  import { Node } from '../../core/types.js';
3
3
  import { TriggerState } from '../../ui/click-behavior.js';
4
4
  export declare function ActionTrigger({ action, children, }: {
5
- action: Node<"action">;
5
+ action: Node<"action" | "action.bulk">;
6
6
  children: (trigger: TriggerState) => ReactNode;
7
7
  }): import("react").JSX.Element;
8
8
  export declare function ActionInteractionProvider({ children }: {
@@ -1 +1 @@
1
- {"version":3,"file":"action-trigger-provider.js","names":[],"sources":["../../../resources/js/action/components/action-trigger-provider.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { Node } from \"@lattice-php/lattice/core/types\";\nimport { ActionTriggerProvider, type TriggerState } from \"@lattice-php/lattice/ui/click-behavior\";\nimport { useAction } from \"@lattice-php/lattice/action/hooks/use-action\";\n\nexport function ActionTrigger({\n action,\n children,\n}: {\n action: Node<\"action\">;\n children: (trigger: TriggerState) => ReactNode;\n}) {\n const { processing, requestSubmit, overlays } = useAction(action);\n\n return (\n <>\n {children({ onClick: requestSubmit, processing })}\n {overlays}\n </>\n );\n}\n\nexport function ActionInteractionProvider({ children }: { children: ReactNode }) {\n return (\n <ActionTriggerProvider\n render={(props) => <ActionTrigger action={props.action}>{props.children}</ActionTrigger>}\n >\n {children}\n </ActionTriggerProvider>\n );\n}\n"],"mappings":";;;;AAKA,SAAgB,cAAc,EAC5B,QACA,YAIC;CACD,MAAM,EAAE,YAAY,eAAe,aAAa,UAAU,MAAM;CAEhE,OACE,qBAAA,UAAA,EAAA,UAAA,CACG,SAAS;EAAE,SAAS;EAAe;CAAW,CAAC,GAC/C,QACD,EAAA,CAAA;AAEN;AAEA,SAAgB,0BAA0B,EAAE,YAAqC;CAC/E,OACE,oBAAC,uBAAD;EACE,SAAS,UAAU,oBAAC,eAAD;GAAe,QAAQ,MAAM;aAAS,MAAM;EAAwB,CAAA;EAEtF;CACoB,CAAA;AAE3B"}
1
+ {"version":3,"file":"action-trigger-provider.js","names":[],"sources":["../../../resources/js/action/components/action-trigger-provider.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { Node } from \"@lattice-php/lattice/core/types\";\nimport { ActionTriggerProvider, type TriggerState } from \"@lattice-php/lattice/ui/click-behavior\";\nimport { useAction } from \"@lattice-php/lattice/action/hooks/use-action\";\n\nexport function ActionTrigger({\n action,\n children,\n}: {\n action: Node<\"action\" | \"action.bulk\">;\n children: (trigger: TriggerState) => ReactNode;\n}) {\n const { processing, requestSubmit, overlays } = useAction(action);\n\n return (\n <>\n {children({ onClick: requestSubmit, processing })}\n {overlays}\n </>\n );\n}\n\nexport function ActionInteractionProvider({ children }: { children: ReactNode }) {\n return (\n <ActionTriggerProvider\n render={(props) => <ActionTrigger action={props.action}>{props.children}</ActionTrigger>}\n >\n {children}\n </ActionTriggerProvider>\n );\n}\n"],"mappings":";;;;AAKA,SAAgB,cAAc,EAC5B,QACA,YAIC;CACD,MAAM,EAAE,YAAY,eAAe,aAAa,UAAU,MAAM;CAEhE,OACE,qBAAA,UAAA,EAAA,UAAA,CACG,SAAS;EAAE,SAAS;EAAe;CAAW,CAAC,GAC/C,QACD,EAAA,CAAA;AAEN;AAEA,SAAgB,0BAA0B,EAAE,YAAqC;CAC/E,OACE,oBAAC,uBAAD;EACE,SAAS,UAAU,oBAAC,eAAD;GAAe,QAAQ,MAAM;aAAS,MAAM;EAAwB,CAAA;EAEtF;CACoB,CAAA;AAE3B"}
@@ -2,6 +2,7 @@ import { IconRenderer } from "../../icons/icon-renderer.js";
2
2
  import { prefixedTestId } from "../../core/test-id.js";
3
3
  import { Button } from "../../ui/button.js";
4
4
  import { Spinner } from "../../ui/spinner.js";
5
+ import { actionLabel } from "../lib/action-label.js";
5
6
  import { useAction } from "../hooks/use-action.js";
6
7
  import { actionMenuItemClassName, useActionMenu } from "../../ui/action-menu-context.js";
7
8
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -9,7 +10,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
10
  var ActionComponent = ({ node }) => {
10
11
  const endpoint = node.props.endpoint ?? "";
11
12
  const icon = node.props.icon;
12
- const label = node.props.label ?? "Run action";
13
+ const label = actionLabel(node);
13
14
  const isMenuItem = useActionMenu();
14
15
  const variant = node.props.variant ?? "default";
15
16
  const { processing, requestSubmit, overlays } = useAction(node);
@@ -1 +1 @@
1
- {"version":3,"file":"action.js","names":[],"sources":["../../../resources/js/action/components/action.tsx"],"sourcesContent":["import { Button } from \"@lattice-php/lattice/ui/button\";\nimport { Spinner } from \"@lattice-php/lattice/ui/spinner\";\nimport { prefixedTestId } from \"@lattice-php/lattice/core/test-id\";\nimport type { RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { IconRenderer } from \"@lattice-php/lattice/icons\";\nimport {\n actionMenuItemClassName,\n useActionMenu,\n} from \"@lattice-php/lattice/ui/action-menu-context\";\nimport { useAction } from \"@lattice-php/lattice/action/hooks/use-action\";\n\nconst ActionComponent: RendererComponent<\"action\"> = ({ node }) => {\n const endpoint = node.props.endpoint ?? \"\";\n const icon = node.props.icon;\n const label = node.props.label ?? \"Run action\";\n const isMenuItem = useActionMenu();\n const variant = node.props.variant ?? \"default\";\n const { processing, requestSubmit, overlays } = useAction(node);\n const testId = node.key ?? prefixedTestId(\"action\", node.id);\n\n return (\n <>\n <Button\n className={isMenuItem ? actionMenuItemClassName : undefined}\n data-lattice-component={node.id}\n data-test={testId}\n disabled={processing || !endpoint}\n onClick={requestSubmit}\n type=\"button\"\n variant={isMenuItem ? \"ghost\" : variant}\n >\n {processing ? (\n <Spinner className={isMenuItem ? \"size-lt-icon-sm\" : undefined} />\n ) : (\n icon && (\n <IconRenderer\n className={isMenuItem ? \"size-lt-icon-sm\" : \"size-lt-icon-md\"}\n icon={icon}\n />\n )\n )}\n {label}\n </Button>\n\n {overlays}\n </>\n );\n};\n\nexport default ActionComponent;\n"],"mappings":";;;;;;;;AAWA,IAAM,mBAAgD,EAAE,WAAW;CACjE,MAAM,WAAW,KAAK,MAAM,YAAY;CACxC,MAAM,OAAO,KAAK,MAAM;CACxB,MAAM,QAAQ,KAAK,MAAM,SAAS;CAClC,MAAM,aAAa,cAAc;CACjC,MAAM,UAAU,KAAK,MAAM,WAAW;CACtC,MAAM,EAAE,YAAY,eAAe,aAAa,UAAU,IAAI;CAC9D,MAAM,SAAS,KAAK,OAAO,eAAe,UAAU,KAAK,EAAE;CAE3D,OACE,qBAAA,UAAA,EAAA,UAAA,CACE,qBAAC,QAAD;EACE,WAAW,aAAa,0BAA0B,KAAA;EAClD,0BAAwB,KAAK;EAC7B,aAAW;EACX,UAAU,cAAc,CAAC;EACzB,SAAS;EACT,MAAK;EACL,SAAS,aAAa,UAAU;YAPlC,CASG,aACC,oBAAC,SAAD,EAAS,WAAW,aAAa,oBAAoB,KAAA,EAAY,CAAA,IAEjE,QACE,oBAAC,cAAD;GACE,WAAW,aAAa,oBAAoB;GACtC;EACP,CAAA,GAGJ,KACK;KAEP,QACD,EAAA,CAAA;AAEN"}
1
+ {"version":3,"file":"action.js","names":[],"sources":["../../../resources/js/action/components/action.tsx"],"sourcesContent":["import { Button } from \"@lattice-php/lattice/ui/button\";\nimport { Spinner } from \"@lattice-php/lattice/ui/spinner\";\nimport { prefixedTestId } from \"@lattice-php/lattice/core/test-id\";\nimport type { RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { IconRenderer } from \"@lattice-php/lattice/icons\";\nimport {\n actionMenuItemClassName,\n useActionMenu,\n} from \"@lattice-php/lattice/ui/action-menu-context\";\nimport { useAction } from \"@lattice-php/lattice/action/hooks/use-action\";\nimport { actionLabel } from \"@lattice-php/lattice/action/lib/action-label\";\n\nconst ActionComponent: RendererComponent<\"action\"> = ({ node }) => {\n const endpoint = node.props.endpoint ?? \"\";\n const icon = node.props.icon;\n const label = actionLabel(node);\n const isMenuItem = useActionMenu();\n const variant = node.props.variant ?? \"default\";\n const { processing, requestSubmit, overlays } = useAction(node);\n const testId = node.key ?? prefixedTestId(\"action\", node.id);\n\n return (\n <>\n <Button\n className={isMenuItem ? actionMenuItemClassName : undefined}\n data-lattice-component={node.id}\n data-test={testId}\n disabled={processing || !endpoint}\n onClick={requestSubmit}\n type=\"button\"\n variant={isMenuItem ? \"ghost\" : variant}\n >\n {processing ? (\n <Spinner className={isMenuItem ? \"size-lt-icon-sm\" : undefined} />\n ) : (\n icon && (\n <IconRenderer\n className={isMenuItem ? \"size-lt-icon-sm\" : \"size-lt-icon-md\"}\n icon={icon}\n />\n )\n )}\n {label}\n </Button>\n\n {overlays}\n </>\n );\n};\n\nexport default ActionComponent;\n"],"mappings":";;;;;;;;;AAYA,IAAM,mBAAgD,EAAE,WAAW;CACjE,MAAM,WAAW,KAAK,MAAM,YAAY;CACxC,MAAM,OAAO,KAAK,MAAM;CACxB,MAAM,QAAQ,YAAY,IAAI;CAC9B,MAAM,aAAa,cAAc;CACjC,MAAM,UAAU,KAAK,MAAM,WAAW;CACtC,MAAM,EAAE,YAAY,eAAe,aAAa,UAAU,IAAI;CAC9D,MAAM,SAAS,KAAK,OAAO,eAAe,UAAU,KAAK,EAAE;CAE3D,OACE,qBAAA,UAAA,EAAA,UAAA,CACE,qBAAC,QAAD;EACE,WAAW,aAAa,0BAA0B,KAAA;EAClD,0BAAwB,KAAK;EAC7B,aAAW;EACX,UAAU,cAAc,CAAC;EACzB,SAAS;EACT,MAAK;EACL,SAAS,aAAa,UAAU;YAPlC,CASG,aACC,oBAAC,SAAD,EAAS,WAAW,aAAa,oBAAoB,KAAA,EAAY,CAAA,IAEjE,QACE,oBAAC,cAAD;GACE,WAAW,aAAa,oBAAoB;GACtC;EACP,CAAA,GAGJ,KACK;KAEP,QACD,EAAA,CAAA;AAEN"}
@@ -14,5 +14,5 @@ type UseAction = {
14
14
  * otherwise dispatch) and renders the matching overlays. The host owns the
15
15
  * trigger element so each surface keeps its own styling.
16
16
  */
17
- export declare function useAction(node: Node<"action">): UseAction;
17
+ export declare function useAction(node: Node<"action" | "action.bulk">): UseAction;
18
18
  export {};
@@ -1,10 +1,12 @@
1
1
  import { withHeaders } from "../../core/headers.js";
2
- import { getActionEffects } from "../../effects/dispatch.js";
2
+ import { translate } from "../../i18n/instance.js";
3
3
  import { useEffectDispatcher } from "../../effects/use-effect-dispatcher.js";
4
4
  import { ConfirmDialog } from "../../ui/confirm-dialog.js";
5
+ import { apiFetch } from "../../core/api.js";
5
6
  import { runAction } from "../lib/run-action.js";
6
7
  import { ActionForm, useLazyActionForm } from "../components/action-form.js";
7
- import { router, useHttp } from "@inertiajs/react";
8
+ import { actionLabel } from "../lib/action-label.js";
9
+ import { router } from "@inertiajs/react";
8
10
  import { useState } from "react";
9
11
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
10
12
  //#region resources/js/action/hooks/use-action.tsx
@@ -18,13 +20,13 @@ function useAction(node) {
18
20
  const endpoint = node.props.endpoint ?? "";
19
21
  const componentRef = node.props.ref ?? "";
20
22
  const method = node.props.method ?? "post";
21
- const label = node.props.label ?? "Run action";
23
+ const label = actionLabel(node);
22
24
  const variant = node.props.variant ?? "default";
23
25
  const confirmation = node.props.confirmation;
24
26
  const inlineForm = node.props.form;
25
27
  const lazyForm = node.props.lazyForm === true;
26
28
  const hasForm = Boolean(inlineForm) || lazyForm;
27
- const http = useHttp({});
29
+ const [processing, setProcessing] = useState(false);
28
30
  const dispatch = useEffectDispatcher();
29
31
  const [isConfirming, setIsConfirming] = useState(false);
30
32
  const [isFilling, setIsFilling] = useState(false);
@@ -37,7 +39,14 @@ function useAction(node) {
37
39
  setIsConfirming(false);
38
40
  return;
39
41
  }
40
- if (await runAction(() => http[method](endpoint, { headers: withHeaders(componentRef) }), dispatch)) setIsConfirming(false);
42
+ setProcessing(true);
43
+ const ok = await runAction(() => apiFetch(endpoint, {
44
+ method,
45
+ ref: componentRef,
46
+ throwOnError: false
47
+ }), dispatch);
48
+ setProcessing(false);
49
+ if (ok) setIsConfirming(false);
41
50
  };
42
51
  const requestSubmit = () => {
43
52
  if (hasForm) {
@@ -52,36 +61,36 @@ function useAction(node) {
52
61
  };
53
62
  const confirmationTitle = confirmation?.title ?? label;
54
63
  const confirmationConfirmLabel = confirmation?.confirmLabel ?? label;
55
- const confirmationCancelLabel = confirmation?.cancelLabel ?? "Cancel";
56
- const overlays = /* @__PURE__ */ jsxs(Fragment$1, { children: [isConfirming && confirmation && /* @__PURE__ */ jsx(ConfirmDialog, {
57
- title: confirmationTitle,
58
- description: confirmation.description ?? void 0,
59
- confirmLabel: confirmationConfirmLabel,
60
- cancelLabel: confirmationCancelLabel,
61
- confirmVariant: variant,
62
- processing: http.processing,
63
- confirmDisabled: !endpoint,
64
- onConfirm: () => void submit(),
65
- onCancel: () => setIsConfirming(false)
66
- }), isFilling && hasForm && /* @__PURE__ */ jsx(ActionForm, {
67
- cancelLabel: confirmationCancelLabel,
68
- componentRef,
69
- description: confirmation?.description ?? void 0,
70
- endpoint,
71
- formNode,
72
- method,
73
- onClose: () => setIsFilling(false),
74
- onSuccess: (response) => {
75
- dispatch(getActionEffects(response.effects));
76
- setIsFilling(false);
77
- },
78
- submitLabel: confirmationConfirmLabel,
79
- title: confirmationTitle
80
- })] });
64
+ const confirmationCancelLabel = confirmation?.cancelLabel ?? translate("lattice", "common.cancel", "Cancel");
81
65
  return {
82
- processing: http.processing,
66
+ processing,
83
67
  requestSubmit,
84
- overlays
68
+ overlays: /* @__PURE__ */ jsxs(Fragment$1, { children: [isConfirming && confirmation && /* @__PURE__ */ jsx(ConfirmDialog, {
69
+ title: confirmationTitle,
70
+ description: confirmation.description ?? void 0,
71
+ confirmLabel: confirmationConfirmLabel,
72
+ cancelLabel: confirmationCancelLabel,
73
+ confirmVariant: variant,
74
+ processing,
75
+ confirmDisabled: !endpoint,
76
+ onConfirm: () => void submit(),
77
+ onCancel: () => setIsConfirming(false)
78
+ }), isFilling && hasForm && /* @__PURE__ */ jsx(ActionForm, {
79
+ cancelLabel: confirmationCancelLabel,
80
+ componentRef,
81
+ description: confirmation?.description ?? void 0,
82
+ endpoint,
83
+ formNode,
84
+ method,
85
+ onClose: () => setIsFilling(false),
86
+ onSuccess: () => {
87
+ setIsFilling(false);
88
+ },
89
+ placement: node.props.modalSide ?? "center",
90
+ submitLabel: confirmationConfirmLabel,
91
+ title: confirmationTitle,
92
+ width: node.props.modalWidth ?? void 0
93
+ })] })
85
94
  };
86
95
  }
87
96
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"use-action.js","names":[],"sources":["../../../resources/js/action/hooks/use-action.tsx"],"sourcesContent":["import { router, useHttp } from \"@inertiajs/react\";\nimport type { Method } from \"@inertiajs/core\";\nimport { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { ConfirmDialog } from \"@lattice-php/lattice/ui/confirm-dialog\";\nimport { withHeaders } from \"@lattice-php/lattice/core/headers\";\nimport type { Node } from \"@lattice-php/lattice/core/types\";\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 { runAction } from \"@lattice-php/lattice/action/lib/run-action\";\nimport { ActionForm, useLazyActionForm } from \"@lattice-php/lattice/action/components/action-form\";\n\ntype UseAction = {\n /** Whether the action request is in flight. */\n processing: boolean;\n /** Gate then run the action: open the form, confirm, or dispatch directly. */\n requestSubmit: () => void;\n /** The confirm dialog and action form rendered next to the trigger. */\n overlays: ReactNode;\n};\n\n/**\n * The shared action machinery behind the Action button, action menu items, and\n * action links: it gates submission (form → modal, confirmation → confirm,\n * otherwise dispatch) and renders the matching overlays. The host owns the\n * trigger element so each surface keeps its own styling.\n */\nexport function useAction(node: Node<\"action\">): UseAction {\n const endpoint = node.props.endpoint ?? \"\";\n const componentRef = node.props.ref ?? \"\";\n const method: Method = node.props.method ?? \"post\";\n const label = node.props.label ?? \"Run action\";\n const variant = node.props.variant ?? \"default\";\n const confirmation = node.props.confirmation;\n const inlineForm = node.props.form;\n const lazyForm = node.props.lazyForm === true;\n const hasForm = Boolean(inlineForm) || lazyForm;\n\n const http = useHttp<Record<string, never>, ActionResponse>({});\n const dispatch = useEffectDispatcher();\n const [isConfirming, setIsConfirming] = useState(false);\n const [isFilling, setIsFilling] = useState(false);\n const lazyNode = useLazyActionForm(endpoint, componentRef, isFilling && lazyForm);\n const formNode = lazyForm ? lazyNode : inlineForm;\n\n const submit = async (): Promise<void> => {\n if (!endpoint) {\n return;\n }\n\n if (method === \"get\") {\n router.visit(endpoint, { headers: withHeaders(componentRef) });\n setIsConfirming(false);\n\n return;\n }\n\n const ok = await runAction(\n () => http[method](endpoint, { headers: withHeaders(componentRef) }),\n dispatch,\n );\n\n if (ok) {\n setIsConfirming(false);\n }\n };\n\n const requestSubmit = (): void => {\n if (hasForm) {\n setIsFilling(true);\n\n return;\n }\n\n if (confirmation) {\n setIsConfirming(true);\n\n return;\n }\n\n void submit();\n };\n\n const confirmationTitle = confirmation?.title ?? label;\n const confirmationConfirmLabel = confirmation?.confirmLabel ?? label;\n const confirmationCancelLabel = confirmation?.cancelLabel ?? \"Cancel\";\n\n const overlays = (\n <>\n {isConfirming && confirmation && (\n <ConfirmDialog\n title={confirmationTitle}\n description={confirmation.description ?? undefined}\n confirmLabel={confirmationConfirmLabel}\n cancelLabel={confirmationCancelLabel}\n confirmVariant={variant}\n processing={http.processing}\n confirmDisabled={!endpoint}\n onConfirm={() => void submit()}\n onCancel={() => setIsConfirming(false)}\n />\n )}\n\n {isFilling && hasForm && (\n <ActionForm\n cancelLabel={confirmationCancelLabel}\n componentRef={componentRef}\n description={confirmation?.description ?? undefined}\n endpoint={endpoint}\n formNode={formNode}\n method={method}\n onClose={() => setIsFilling(false)}\n onSuccess={(response) => {\n dispatch(getActionEffects(response.effects));\n setIsFilling(false);\n }}\n submitLabel={confirmationConfirmLabel}\n title={confirmationTitle}\n />\n )}\n </>\n );\n\n return { processing: http.processing, requestSubmit, overlays };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA4BA,SAAgB,UAAU,MAAiC;CACzD,MAAM,WAAW,KAAK,MAAM,YAAY;CACxC,MAAM,eAAe,KAAK,MAAM,OAAO;CACvC,MAAM,SAAiB,KAAK,MAAM,UAAU;CAC5C,MAAM,QAAQ,KAAK,MAAM,SAAS;CAClC,MAAM,UAAU,KAAK,MAAM,WAAW;CACtC,MAAM,eAAe,KAAK,MAAM;CAChC,MAAM,aAAa,KAAK,MAAM;CAC9B,MAAM,WAAW,KAAK,MAAM,aAAa;CACzC,MAAM,UAAU,QAAQ,UAAU,KAAK;CAEvC,MAAM,OAAO,QAA+C,CAAC,CAAC;CAC9D,MAAM,WAAW,oBAAoB;CACrC,MAAM,CAAC,cAAc,mBAAmB,SAAS,KAAK;CACtD,MAAM,CAAC,WAAW,gBAAgB,SAAS,KAAK;CAChD,MAAM,WAAW,kBAAkB,UAAU,cAAc,aAAa,QAAQ;CAChF,MAAM,WAAW,WAAW,WAAW;CAEvC,MAAM,SAAS,YAA2B;EACxC,IAAI,CAAC,UACH;EAGF,IAAI,WAAW,OAAO;GACpB,OAAO,MAAM,UAAU,EAAE,SAAS,YAAY,YAAY,EAAE,CAAC;GAC7D,gBAAgB,KAAK;GAErB;EACF;EAOA,IAAI,MALa,gBACT,KAAK,QAAQ,UAAU,EAAE,SAAS,YAAY,YAAY,EAAE,CAAC,GACnE,QACF,GAGE,gBAAgB,KAAK;CAEzB;CAEA,MAAM,sBAA4B;EAChC,IAAI,SAAS;GACX,aAAa,IAAI;GAEjB;EACF;EAEA,IAAI,cAAc;GAChB,gBAAgB,IAAI;GAEpB;EACF;EAEA,OAAY;CACd;CAEA,MAAM,oBAAoB,cAAc,SAAS;CACjD,MAAM,2BAA2B,cAAc,gBAAgB;CAC/D,MAAM,0BAA0B,cAAc,eAAe;CAE7D,MAAM,WACJ,qBAAA,YAAA,EAAA,UAAA,CACG,gBAAgB,gBACf,oBAAC,eAAD;EACE,OAAO;EACP,aAAa,aAAa,eAAe,KAAA;EACzC,cAAc;EACd,aAAa;EACb,gBAAgB;EAChB,YAAY,KAAK;EACjB,iBAAiB,CAAC;EAClB,iBAAiB,KAAK,OAAO;EAC7B,gBAAgB,gBAAgB,KAAK;CACtC,CAAA,GAGF,aAAa,WACZ,oBAAC,YAAD;EACE,aAAa;EACC;EACd,aAAa,cAAc,eAAe,KAAA;EAChC;EACA;EACF;EACR,eAAe,aAAa,KAAK;EACjC,YAAY,aAAa;GACvB,SAAS,iBAAiB,SAAS,OAAO,CAAC;GAC3C,aAAa,KAAK;EACpB;EACA,aAAa;EACb,OAAO;CACR,CAAA,CAEH,EAAA,CAAA;CAGJ,OAAO;EAAE,YAAY,KAAK;EAAY;EAAe;CAAS;AAChE"}
1
+ {"version":3,"file":"use-action.js","names":[],"sources":["../../../resources/js/action/hooks/use-action.tsx"],"sourcesContent":["import { router } from \"@inertiajs/react\";\nimport type { Method } from \"@inertiajs/core\";\nimport { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { ConfirmDialog } from \"@lattice-php/lattice/ui/confirm-dialog\";\nimport { apiFetch } from \"@lattice-php/lattice/core/api\";\nimport { withHeaders } from \"@lattice-php/lattice/core/headers\";\nimport type { Node } from \"@lattice-php/lattice/core/types\";\nimport { translate } from \"@lattice-php/lattice/i18n\";\nimport { useEffectDispatcher } from \"@lattice-php/lattice/effects/use-effect-dispatcher\";\nimport { runAction } from \"@lattice-php/lattice/action/lib/run-action\";\nimport { ActionForm, useLazyActionForm } from \"@lattice-php/lattice/action/components/action-form\";\nimport { actionLabel } from \"@lattice-php/lattice/action/lib/action-label\";\n\ntype UseAction = {\n /** Whether the action request is in flight. */\n processing: boolean;\n /** Gate then run the action: open the form, confirm, or dispatch directly. */\n requestSubmit: () => void;\n /** The confirm dialog and action form rendered next to the trigger. */\n overlays: ReactNode;\n};\n\n/**\n * The shared action machinery behind the Action button, action menu items, and\n * action links: it gates submission (form → modal, confirmation → confirm,\n * otherwise dispatch) and renders the matching overlays. The host owns the\n * trigger element so each surface keeps its own styling.\n */\nexport function useAction(node: Node<\"action\" | \"action.bulk\">): UseAction {\n const endpoint = node.props.endpoint ?? \"\";\n const componentRef = node.props.ref ?? \"\";\n const method: Method = node.props.method ?? \"post\";\n const label = actionLabel(node);\n const variant = node.props.variant ?? \"default\";\n const confirmation = node.props.confirmation;\n const inlineForm = node.props.form;\n const lazyForm = node.props.lazyForm === true;\n const hasForm = Boolean(inlineForm) || lazyForm;\n\n const [processing, setProcessing] = useState(false);\n const dispatch = useEffectDispatcher();\n const [isConfirming, setIsConfirming] = useState(false);\n const [isFilling, setIsFilling] = useState(false);\n const lazyNode = useLazyActionForm(endpoint, componentRef, isFilling && lazyForm);\n const formNode = lazyForm ? lazyNode : inlineForm;\n\n const submit = async (): Promise<void> => {\n if (!endpoint) {\n return;\n }\n\n if (method === \"get\") {\n router.visit(endpoint, { headers: withHeaders(componentRef) });\n setIsConfirming(false);\n\n return;\n }\n\n setProcessing(true);\n\n const ok = await runAction(\n () => apiFetch(endpoint, { method, ref: componentRef, throwOnError: false }),\n dispatch,\n );\n\n setProcessing(false);\n\n if (ok) {\n setIsConfirming(false);\n }\n };\n\n const requestSubmit = (): void => {\n if (hasForm) {\n setIsFilling(true);\n\n return;\n }\n\n if (confirmation) {\n setIsConfirming(true);\n\n return;\n }\n\n void submit();\n };\n\n const confirmationTitle = confirmation?.title ?? label;\n const confirmationConfirmLabel = confirmation?.confirmLabel ?? label;\n const confirmationCancelLabel =\n confirmation?.cancelLabel ?? translate(\"lattice\", \"common.cancel\", \"Cancel\");\n\n const overlays = (\n <>\n {isConfirming && confirmation && (\n <ConfirmDialog\n title={confirmationTitle}\n description={confirmation.description ?? undefined}\n confirmLabel={confirmationConfirmLabel}\n cancelLabel={confirmationCancelLabel}\n confirmVariant={variant}\n processing={processing}\n confirmDisabled={!endpoint}\n onConfirm={() => void submit()}\n onCancel={() => setIsConfirming(false)}\n />\n )}\n\n {isFilling && hasForm && (\n <ActionForm\n cancelLabel={confirmationCancelLabel}\n componentRef={componentRef}\n description={confirmation?.description ?? undefined}\n endpoint={endpoint}\n formNode={formNode}\n method={method}\n onClose={() => setIsFilling(false)}\n onSuccess={() => {\n setIsFilling(false);\n }}\n placement={node.props.modalSide ?? \"center\"}\n submitLabel={confirmationConfirmLabel}\n title={confirmationTitle}\n width={node.props.modalWidth ?? undefined}\n />\n )}\n </>\n );\n\n return { processing, requestSubmit, overlays };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA6BA,SAAgB,UAAU,MAAiD;CACzE,MAAM,WAAW,KAAK,MAAM,YAAY;CACxC,MAAM,eAAe,KAAK,MAAM,OAAO;CACvC,MAAM,SAAiB,KAAK,MAAM,UAAU;CAC5C,MAAM,QAAQ,YAAY,IAAI;CAC9B,MAAM,UAAU,KAAK,MAAM,WAAW;CACtC,MAAM,eAAe,KAAK,MAAM;CAChC,MAAM,aAAa,KAAK,MAAM;CAC9B,MAAM,WAAW,KAAK,MAAM,aAAa;CACzC,MAAM,UAAU,QAAQ,UAAU,KAAK;CAEvC,MAAM,CAAC,YAAY,iBAAiB,SAAS,KAAK;CAClD,MAAM,WAAW,oBAAoB;CACrC,MAAM,CAAC,cAAc,mBAAmB,SAAS,KAAK;CACtD,MAAM,CAAC,WAAW,gBAAgB,SAAS,KAAK;CAChD,MAAM,WAAW,kBAAkB,UAAU,cAAc,aAAa,QAAQ;CAChF,MAAM,WAAW,WAAW,WAAW;CAEvC,MAAM,SAAS,YAA2B;EACxC,IAAI,CAAC,UACH;EAGF,IAAI,WAAW,OAAO;GACpB,OAAO,MAAM,UAAU,EAAE,SAAS,YAAY,YAAY,EAAE,CAAC;GAC7D,gBAAgB,KAAK;GAErB;EACF;EAEA,cAAc,IAAI;EAElB,MAAM,KAAK,MAAM,gBACT,SAAS,UAAU;GAAE;GAAQ,KAAK;GAAc,cAAc;EAAM,CAAC,GAC3E,QACF;EAEA,cAAc,KAAK;EAEnB,IAAI,IACF,gBAAgB,KAAK;CAEzB;CAEA,MAAM,sBAA4B;EAChC,IAAI,SAAS;GACX,aAAa,IAAI;GAEjB;EACF;EAEA,IAAI,cAAc;GAChB,gBAAgB,IAAI;GAEpB;EACF;EAEA,OAAY;CACd;CAEA,MAAM,oBAAoB,cAAc,SAAS;CACjD,MAAM,2BAA2B,cAAc,gBAAgB;CAC/D,MAAM,0BACJ,cAAc,eAAe,UAAU,WAAW,iBAAiB,QAAQ;CAuC7E,OAAO;EAAE;EAAY;EAAe,UApClC,qBAAA,YAAA,EAAA,UAAA,CACG,gBAAgB,gBACf,oBAAC,eAAD;GACE,OAAO;GACP,aAAa,aAAa,eAAe,KAAA;GACzC,cAAc;GACd,aAAa;GACb,gBAAgB;GACJ;GACZ,iBAAiB,CAAC;GAClB,iBAAiB,KAAK,OAAO;GAC7B,gBAAgB,gBAAgB,KAAK;EACtC,CAAA,GAGF,aAAa,WACZ,oBAAC,YAAD;GACE,aAAa;GACC;GACd,aAAa,cAAc,eAAe,KAAA;GAChC;GACA;GACF;GACR,eAAe,aAAa,KAAK;GACjC,iBAAiB;IACf,aAAa,KAAK;GACpB;GACA,WAAW,KAAK,MAAM,aAAa;GACnC,aAAa;GACb,OAAO;GACP,OAAO,KAAK,MAAM,cAAc,KAAA;EACjC,CAAA,CAEH,EAAA,CAGgC;CAAS;AAC/C"}
@@ -4,7 +4,5 @@ export { default as ActionGroupComponent } from './components/action-group.js';
4
4
  export { ActionInteractionProvider, ActionTrigger } from './components/action-trigger-provider.js';
5
5
  export { ActionMenuProvider, actionMenuItemClassName, useActionMenu, } from '../ui/action-menu-context.js';
6
6
  export { useAction } from './hooks/use-action.js';
7
- export { useClickBehavior } from '../ui/click-behavior.js';
8
- export type { ClickBehavior, TriggerState } from '../ui/click-behavior.js';
9
7
  export { runAction } from './lib/run-action.js';
10
8
  export { actionComponents } from './plugin.js';
@@ -1,4 +1,3 @@
1
- import { useClickBehavior } from "../ui/click-behavior.js";
2
1
  import { runAction } from "./lib/run-action.js";
3
2
  import { ActionForm } from "./components/action-form.js";
4
3
  import { useAction } from "./hooks/use-action.js";
@@ -7,4 +6,4 @@ import { ActionMenuProvider, actionMenuItemClassName, useActionMenu } from "../u
7
6
  import ActionComponent from "./components/action.js";
8
7
  import ActionGroupComponent from "./components/action-group.js";
9
8
  import { actionComponents } from "./plugin.js";
10
- export { ActionComponent, ActionForm, ActionGroupComponent, ActionInteractionProvider, ActionMenuProvider, ActionTrigger, actionComponents, actionMenuItemClassName, runAction, useAction, useActionMenu, useClickBehavior };
9
+ export { ActionComponent, ActionForm, ActionGroupComponent, ActionInteractionProvider, ActionMenuProvider, ActionTrigger, actionComponents, actionMenuItemClassName, runAction, useAction, useActionMenu };
@@ -0,0 +1,2 @@
1
+ import { Node } from '../../core/types.js';
2
+ export declare function actionLabel(node: Node<"action" | "action.bulk">): string;
@@ -0,0 +1,9 @@
1
+ import { translate } from "../../i18n/instance.js";
2
+ //#region resources/js/action/lib/action-label.ts
3
+ function actionLabel(node) {
4
+ return node.props.label ?? translate("lattice", "common.action.run", "Run action");
5
+ }
6
+ //#endregion
7
+ export { actionLabel };
8
+
9
+ //# sourceMappingURL=action-label.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-label.js","names":[],"sources":["../../../resources/js/action/lib/action-label.ts"],"sourcesContent":["import { translate } from \"@lattice-php/lattice/i18n\";\nimport type { Node } from \"@lattice-php/lattice/core/types\";\n\nexport function actionLabel(node: Node<\"action\" | \"action.bulk\">): string {\n return node.props.label ?? translate(\"lattice\", \"common.action.run\", \"Run action\");\n}\n"],"mappings":";;AAGA,SAAgB,YAAY,MAA8C;CACxE,OAAO,KAAK,MAAM,SAAS,UAAU,WAAW,qBAAqB,YAAY;AACnF"}
@@ -1,9 +1,9 @@
1
- import { ActionEffect, ActionResponse } from '../../effects/dispatch.js';
1
+ import { ActionEffect } from '../../effects/dispatch.js';
2
2
  /**
3
- * Runs an action request and dispatches the effects from its response, routing
4
- * any failure through dispatchActionError. Returns whether the request
5
- * succeeded so callers run their own post-success cleanup (closing a dialog,
6
- * reloading a table). Shared by the single action button and the bulk bar so
7
- * the invocation contract stays in one place.
3
+ * Runs an action request and dispatches the effects from its response body,
4
+ * whether the action succeeded or was rejected (non-2xx). Returns whether the
5
+ * response was ok so callers run their own post-success cleanup (closing a
6
+ * dialog, reloading a table) only on success; a rejected action leaves the
7
+ * dialog open. A thrown/network error routes through dispatchActionError.
8
8
  */
9
- export declare function runAction(request: () => Promise<ActionResponse>, dispatch: (effects: ActionEffect[]) => void): Promise<boolean>;
9
+ export declare function runAction(request: () => Promise<Response>, dispatch: (effects: ActionEffect[]) => void): Promise<boolean>;
@@ -1,16 +1,17 @@
1
1
  import { dispatchActionError, getActionEffects } from "../../effects/dispatch.js";
2
2
  //#region resources/js/action/lib/run-action.ts
3
3
  /**
4
- * Runs an action request and dispatches the effects from its response, routing
5
- * any failure through dispatchActionError. Returns whether the request
6
- * succeeded so callers run their own post-success cleanup (closing a dialog,
7
- * reloading a table). Shared by the single action button and the bulk bar so
8
- * the invocation contract stays in one place.
4
+ * Runs an action request and dispatches the effects from its response body,
5
+ * whether the action succeeded or was rejected (non-2xx). Returns whether the
6
+ * response was ok so callers run their own post-success cleanup (closing a
7
+ * dialog, reloading a table) only on success; a rejected action leaves the
8
+ * dialog open. A thrown/network error routes through dispatchActionError.
9
9
  */
10
10
  async function runAction(request, dispatch) {
11
11
  try {
12
- dispatch(getActionEffects((await request()).effects));
13
- return true;
12
+ const response = await request();
13
+ dispatch(getActionEffects((await response.json().catch(() => ({}))).effects));
14
+ return response.ok;
14
15
  } catch (error) {
15
16
  dispatchActionError(error);
16
17
  return false;
@@ -1 +1 @@
1
- {"version":3,"file":"run-action.js","names":[],"sources":["../../../resources/js/action/lib/run-action.ts"],"sourcesContent":["import {\n type ActionEffect,\n type ActionResponse,\n dispatchActionError,\n getActionEffects,\n} from \"@lattice-php/lattice/effects/dispatch\";\n\n/**\n * Runs an action request and dispatches the effects from its response, routing\n * any failure through dispatchActionError. Returns whether the request\n * succeeded so callers run their own post-success cleanup (closing a dialog,\n * reloading a table). Shared by the single action button and the bulk bar so\n * the invocation contract stays in one place.\n */\nexport async function runAction(\n request: () => Promise<ActionResponse>,\n dispatch: (effects: ActionEffect[]) => void,\n): Promise<boolean> {\n try {\n const response = await request();\n dispatch(getActionEffects(response.effects));\n\n return true;\n } catch (error) {\n dispatchActionError(error);\n\n return false;\n }\n}\n"],"mappings":";;;;;;;;;AAcA,eAAsB,UACpB,SACA,UACkB;CAClB,IAAI;EAEF,SAAS,kBAAiB,MADH,QAAQ,GACI,OAAO,CAAC;EAE3C,OAAO;CACT,SAAS,OAAO;EACd,oBAAoB,KAAK;EAEzB,OAAO;CACT;AACF"}
1
+ {"version":3,"file":"run-action.js","names":[],"sources":["../../../resources/js/action/lib/run-action.ts"],"sourcesContent":["import {\n type ActionEffect,\n type ActionResponse,\n dispatchActionError,\n getActionEffects,\n} from \"@lattice-php/lattice/effects/dispatch\";\n\n/**\n * Runs an action request and dispatches the effects from its response body,\n * whether the action succeeded or was rejected (non-2xx). Returns whether the\n * response was ok so callers run their own post-success cleanup (closing a\n * dialog, reloading a table) only on success; a rejected action leaves the\n * dialog open. A thrown/network error routes through dispatchActionError.\n */\nexport async function runAction(\n request: () => Promise<Response>,\n dispatch: (effects: ActionEffect[]) => void,\n): Promise<boolean> {\n try {\n const response = await request();\n const body = (await response.json().catch(() => ({}))) as ActionResponse;\n dispatch(getActionEffects(body.effects));\n\n return response.ok;\n } catch (error) {\n dispatchActionError(error);\n\n return false;\n }\n}\n"],"mappings":";;;;;;;;;AAcA,eAAsB,UACpB,SACA,UACkB;CAClB,IAAI;EACF,MAAM,WAAW,MAAM,QAAQ;EAE/B,SAAS,kBAAiB,MADN,SAAS,KAAK,EAAE,aAAa,CAAC,EAAE,GACrB,OAAO,CAAC;EAEvC,OAAO,SAAS;CAClB,SAAS,OAAO;EACd,oBAAoB,KAAK;EAEzB,OAAO;CACT;AACF"}
@@ -1,2 +1 @@
1
- export type ChatComponentType = "chat.box" | "chat.part.text" | "chat.part.tool-call";
2
1
  export declare const chatComponents: import('../core/registry.js').Plugin;
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","names":[],"sources":["../../resources/js/chat/plugin.ts"],"sourcesContent":["import {\n createPlugin,\n eagerComponent,\n type ComponentRegistryFor,\n} from \"@lattice-php/lattice/core/registry\";\nimport ChatBoxComponent from \"./components/chat-box\";\nimport { TextPart } from \"./parts/text\";\nimport { ToolCallPart } from \"./parts/tool-call\";\n\nexport type ChatComponentType = \"chat.box\" | \"chat.part.text\" | \"chat.part.tool-call\";\n\nexport const chatComponents = createPlugin({\n name: \"lattice/chat\",\n components: {\n \"chat.box\": eagerComponent(ChatBoxComponent),\n \"chat.part.text\": eagerComponent(TextPart),\n \"chat.part.tool-call\": eagerComponent(ToolCallPart),\n } satisfies ComponentRegistryFor<ChatComponentType>,\n});\n"],"mappings":";;;;;AAWA,IAAa,iBAAiB,aAAa;CACzC,MAAM;CACN,YAAY;EACV,YAAY,eAAe,OAAgB;EAC3C,kBAAkB,eAAe,QAAQ;EACzC,uBAAuB,eAAe,YAAY;CACpD;AACF,CAAC"}
1
+ {"version":3,"file":"plugin.js","names":[],"sources":["../../resources/js/chat/plugin.ts"],"sourcesContent":["import {\n createPlugin,\n eagerComponent,\n type ComponentRegistryFor,\n} from \"@lattice-php/lattice/core/registry\";\nimport ChatBoxComponent from \"./components/chat-box\";\nimport { TextPart } from \"./parts/text\";\nimport type { ChatNodeType } from \"@lattice-php/lattice/types/generated\";\nimport { ToolCallPart } from \"./parts/tool-call\";\n\nexport const chatComponents = createPlugin({\n name: \"lattice/chat\",\n components: {\n \"chat.box\": eagerComponent(ChatBoxComponent),\n \"chat.part.text\": eagerComponent(TextPart),\n \"chat.part.tool-call\": eagerComponent(ToolCallPart),\n } satisfies ComponentRegistryFor<ChatNodeType>,\n});\n"],"mappings":";;;;;AAUA,IAAa,iBAAiB,aAAa;CACzC,MAAM;CACN,YAAY;EACV,YAAY,eAAe,OAAgB;EAC3C,kBAAkB,eAAe,QAAQ;EACzC,uBAAuB,eAAe,YAAY;CACpD;AACF,CAAC"}
@@ -1,5 +1 @@
1
1
  export { CopyableText, copyToClipboard } from '../ui/copyable-text.js';
2
- export type CopiedValue = string | null;
3
- export type CopyFn = (text: string) => Promise<boolean>;
4
- export type UseClipboardReturn = [CopiedValue, CopyFn];
5
- export declare function useClipboard(): UseClipboardReturn;