@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.
- package/README.md +10 -0
- package/dist/action/components/action-form.d.ts +6 -1
- package/dist/action/components/action-form.js +48 -58
- package/dist/action/components/action-form.js.map +1 -1
- package/dist/action/components/action-trigger-provider.d.ts +1 -1
- package/dist/action/components/action-trigger-provider.js.map +1 -1
- package/dist/action/components/action.js +2 -1
- package/dist/action/components/action.js.map +1 -1
- package/dist/action/hooks/use-action.d.ts +1 -1
- package/dist/action/hooks/use-action.js +42 -33
- package/dist/action/hooks/use-action.js.map +1 -1
- package/dist/action/index.d.ts +0 -2
- package/dist/action/index.js +1 -2
- package/dist/action/lib/action-label.d.ts +2 -0
- package/dist/action/lib/action-label.js +9 -0
- package/dist/action/lib/action-label.js.map +1 -0
- package/dist/action/lib/run-action.d.ts +7 -7
- package/dist/action/lib/run-action.js +8 -7
- package/dist/action/lib/run-action.js.map +1 -1
- package/dist/chat/plugin.d.ts +0 -1
- package/dist/chat/plugin.js.map +1 -1
- package/dist/clipboard/index.d.ts +0 -4
- package/dist/clipboard/index.js +1 -15
- package/dist/{events → core}/event-names.js +1 -1
- package/dist/core/event-names.js.map +1 -0
- package/dist/core/hooks/use-window-event.d.ts +8 -0
- package/dist/core/hooks/use-window-event.js +22 -0
- package/dist/core/hooks/use-window-event.js.map +1 -0
- package/dist/core/index.js +3 -3
- package/dist/core/types.d.ts +8 -73
- package/dist/create-app.js +1 -1
- package/dist/effects/dispatch.js +1 -1
- package/dist/effects/dispatch.js.map +1 -1
- package/dist/effects/registry.d.ts +2 -18
- package/dist/effects/registry.js +4 -8
- package/dist/effects/registry.js.map +1 -1
- package/dist/event-bridge.js +10 -23
- package/dist/event-bridge.js.map +1 -1
- package/dist/form/components/fields/date-picker-field.js +1 -1
- package/dist/form/components/fields/file-upload.js +4 -6
- package/dist/form/components/fields/file-upload.js.map +1 -1
- package/dist/form/components/fields/select.js +2 -2
- package/dist/form/components/fields/select.js.map +1 -1
- package/dist/form/components/fields/table-rows.js +3 -15
- package/dist/form/components/fields/table-rows.js.map +1 -1
- package/dist/form/components/form.js +11 -24
- package/dist/form/components/form.js.map +1 -1
- package/dist/form/embed.d.ts +4 -0
- package/dist/form/embed.js +3 -1
- package/dist/form/lib/collect-fields.d.ts +7 -0
- package/dist/form/lib/collect-fields.js +19 -0
- package/dist/form/lib/collect-fields.js.map +1 -0
- package/dist/form/lib/field-errors.d.ts +3 -0
- package/dist/form/lib/field-errors.js +11 -0
- package/dist/form/lib/field-errors.js.map +1 -0
- package/dist/format/format-context.d.ts +6 -0
- package/dist/format/format-context.js +17 -0
- package/dist/format/format-context.js.map +1 -0
- package/dist/format/number.d.ts +1 -1
- package/dist/format/number.js.map +1 -1
- package/dist/format/value.d.ts +1 -1
- package/dist/format/value.js.map +1 -1
- package/dist/i18n/backend.js +1 -1
- package/dist/i18n/index.js +2 -2
- package/dist/i18n/instance.js +1 -1
- package/dist/i18n/locale-reload.d.ts +1 -1
- package/dist/i18n/locale-reload.js +7 -6
- package/dist/i18n/locale-reload.js.map +1 -1
- package/dist/i18n/locale.js +1 -1
- package/dist/i18n/locale.js.map +1 -1
- package/dist/i18n/page-props.js +1 -1
- package/dist/i18n/shared-props.js +1 -3
- package/dist/i18n/shared-props.js.map +1 -1
- package/dist/i18n/timezone.js +1 -1
- package/dist/i18n/timezone.js.map +1 -1
- package/dist/index.d.ts +6 -7
- package/dist/index.js +7 -8
- package/dist/lattice.css +105 -0
- package/dist/layout/components/callouts.js +1 -1
- package/dist/layout/components/sidebar.js +15 -48
- package/dist/layout/components/sidebar.js.map +1 -1
- package/dist/layout/plugin.d.ts +0 -1
- package/dist/layout/plugin.js.map +1 -1
- package/dist/lib/is-truthy.d.ts +2 -0
- package/dist/lib/is-truthy.js +9 -0
- package/dist/lib/is-truthy.js.map +1 -0
- package/dist/lib/use-debounced-callback.d.ts +10 -0
- package/dist/lib/use-debounced-callback.js +34 -0
- package/dist/lib/use-debounced-callback.js.map +1 -0
- package/dist/lib/use-media-query.d.ts +5 -0
- package/dist/lib/use-media-query.js +26 -0
- package/dist/lib/use-media-query.js.map +1 -0
- package/dist/lib/use-persistent-state.d.ts +13 -0
- package/dist/lib/use-persistent-state.js +62 -0
- package/dist/lib/use-persistent-state.js.map +1 -0
- package/dist/notifications/components/notifications.js +4 -1
- package/dist/notifications/components/notifications.js.map +1 -1
- package/dist/notifications/plugin.d.ts +0 -1
- package/dist/notifications/plugin.js.map +1 -1
- package/dist/provider-base.js +1 -1
- package/dist/realtime/listeners.d.ts +1 -1
- package/dist/realtime/listeners.js +1 -1
- package/dist/realtime/listeners.js.map +1 -1
- package/dist/registry.d.ts +5 -0
- package/dist/registry.js.map +1 -1
- package/dist/remote/components/data-list.js +1 -1
- package/dist/remote/plugin.d.ts +0 -1
- package/dist/remote/plugin.js.map +1 -1
- package/dist/table/components/bulk-bar.js +18 -15
- package/dist/table/components/bulk-bar.js.map +1 -1
- package/dist/table/components/cells/boolean-cell.js +1 -3
- package/dist/table/components/cells/boolean-cell.js.map +1 -1
- package/dist/table/components/cells/copyable-cell.d.ts +9 -0
- package/dist/table/components/cells/copyable-cell.js +17 -0
- package/dist/table/components/cells/copyable-cell.js.map +1 -0
- package/dist/table/components/cells/money-cell.js +7 -18
- package/dist/table/components/cells/money-cell.js.map +1 -1
- package/dist/table/components/cells/number-cell.js +14 -27
- package/dist/table/components/cells/number-cell.js.map +1 -1
- package/dist/table/components/cells/numeric-cell.d.ts +10 -0
- package/dist/table/components/cells/numeric-cell.js +25 -0
- package/dist/table/components/cells/numeric-cell.js.map +1 -0
- package/dist/table/components/cells/stack-cell.js +1 -1
- package/dist/table/components/cells/text-cell.js +4 -5
- package/dist/table/components/cells/text-cell.js.map +1 -1
- package/dist/table/components/filter-controls.js +1 -3
- package/dist/table/components/filter-controls.js.map +1 -1
- package/dist/table/components/table-cell.js.map +1 -1
- package/dist/table/components/table-search.js +4 -7
- package/dist/table/components/table-search.js.map +1 -1
- package/dist/table/components/table.js +2 -3
- package/dist/table/components/table.js.map +1 -1
- package/dist/table/hooks/use-column-visibility.js +29 -58
- package/dist/table/hooks/use-column-visibility.js.map +1 -1
- package/dist/table/hooks/use-table.js +5 -13
- package/dist/table/hooks/use-table.js.map +1 -1
- package/dist/table/lib/bulk.d.ts +2 -0
- package/dist/table/lib/bulk.js +5 -2
- package/dist/table/lib/bulk.js.map +1 -1
- package/dist/table/lib/query.js +1 -1
- package/dist/table/lib/query.js.map +1 -1
- package/dist/table/plugin.d.ts +0 -1
- package/dist/table/plugin.js.map +1 -1
- package/dist/table/types.d.ts +4 -57
- package/dist/toast/callout.js +1 -1
- package/dist/toast/callout.js.map +1 -1
- package/dist/toast/index.d.ts +1 -1
- package/dist/toast/index.js +2 -2
- package/dist/toast/toast.d.ts +0 -2
- package/dist/toast/toast.js +3 -10
- package/dist/toast/toast.js.map +1 -1
- package/dist/toast/toaster.js +2 -2
- package/dist/types/generated.d.ts +224 -80
- package/dist/types/index.d.ts +1 -1
- package/dist/ui/button.d.ts +2 -2
- package/dist/ui/chart-view.js +7 -30
- package/dist/ui/chart-view.js.map +1 -1
- package/dist/ui/click-behavior.d.ts +4 -4
- package/dist/ui/click-behavior.js.map +1 -1
- package/dist/ui/collapsible.js +3 -15
- package/dist/ui/collapsible.js.map +1 -1
- package/dist/ui/combobox.js +6 -3
- package/dist/ui/combobox.js.map +1 -1
- package/dist/ui/confirm-dialog.js +1 -1
- package/dist/ui/confirm-dialog.js.map +1 -1
- package/dist/ui/control.d.ts +1 -1
- package/dist/ui/dialog.d.ts +6 -2
- package/dist/ui/dialog.js +29 -5
- package/dist/ui/dialog.js.map +1 -1
- package/dist/ui/fragment.js +1 -1
- package/dist/ui/fragment.js.map +1 -1
- package/dist/ui/icon-button.d.ts +1 -1
- package/dist/ui/image-preview.js +1 -1
- package/dist/ui/image-preview.js.map +1 -1
- package/dist/ui/modal.js +6 -3
- package/dist/ui/modal.js.map +1 -1
- package/dist/ui/pill.d.ts +2 -0
- package/dist/ui/pill.js +10 -0
- package/dist/ui/pill.js.map +1 -0
- package/dist/ui/plugin.js.map +1 -1
- package/dist/ui/progress.js +1 -1
- package/dist/ui/section.js +2 -15
- package/dist/ui/section.js.map +1 -1
- package/dist/ui/segmented-pills.js +2 -1
- package/dist/ui/segmented-pills.js.map +1 -1
- package/dist/ui/tabs.js +12 -10
- package/dist/ui/tabs.js.map +1 -1
- package/dist/ui/tree-context.js +11 -13
- package/dist/ui/tree-context.js.map +1 -1
- package/dist/ui/tree.js +1 -1
- package/dist/ui/tree.js.map +1 -1
- package/dist/ui/use-collapsible-state.d.ts +7 -0
- package/dist/ui/use-collapsible-state.js +21 -0
- package/dist/ui/use-collapsible-state.js.map +1 -0
- package/dist-standalone/chunks/chart-view-C6R5iPra.js +36 -0
- package/dist-standalone/chunks/{date-picker-field-VbH9xrQO.js → date-picker-field-DhhEB-Mh.js} +1 -1
- package/dist-standalone/chunks/{page-props-AIa5UR6n.js → page-props-DOfg30l5.js} +1 -1
- package/dist-standalone/chunks/{rich-editor-field-DLQTyt36.js → rich-editor-field-DORXTHPw.js} +2 -2
- package/dist-standalone/chunks/{subscriptions-DY2tj9Im.js → subscriptions-DK_s_F2g.js} +1 -1
- package/dist-standalone/chunks/use-effect-dispatcher-EVZxmfoo.js +97 -0
- package/dist-standalone/lattice.css +1 -1
- package/dist-standalone/lattice.js +15 -15
- package/dist-standalone/manifest.json +8 -8
- package/package.json +3 -3
- package/dist/clipboard/index.js.map +0 -1
- package/dist/events/event-names.js.map +0 -1
- package/dist/table/components/table-action-node.d.ts +0 -4
- package/dist/table/components/table-action-node.js +0 -24
- package/dist/table/components/table-action-node.js.map +0 -1
- package/dist-standalone/chunks/chart-view-wJmnVu7L.js +0 -36
- package/dist-standalone/chunks/use-effect-dispatcher-feEs0GCP.js +0 -97
- /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 (
|
|
110
|
-
|
|
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
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
118
|
+
if (!response.ok) return;
|
|
119
|
+
onSuccess(body);
|
|
144
120
|
}).catch((error) => dispatchActionError(error)).finally(() => setProcessing(false));
|
|
145
|
-
}, [
|
|
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(() =>
|
|
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
|
-
|
|
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
|
|
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
|
|
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 {
|
|
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 {
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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"}
|
package/dist/action/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/action/index.js
CHANGED
|
@@ -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
|
|
9
|
+
export { ActionComponent, ActionForm, ActionGroupComponent, ActionInteractionProvider, ActionMenuProvider, ActionTrigger, actionComponents, actionMenuItemClassName, runAction, useAction, useActionMenu };
|
|
@@ -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
|
|
1
|
+
import { ActionEffect } from '../../effects/dispatch.js';
|
|
2
2
|
/**
|
|
3
|
-
* Runs an action request and dispatches the effects from its response,
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* reloading a table)
|
|
7
|
-
*
|
|
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<
|
|
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,
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* reloading a table)
|
|
8
|
-
*
|
|
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
|
-
|
|
13
|
-
|
|
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
|
|
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"}
|
package/dist/chat/plugin.d.ts
CHANGED
package/dist/chat/plugin.js.map
CHANGED
|
@@ -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 {
|
|
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;
|