@lattice-php/lattice 0.20.0 → 0.21.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 (199) hide show
  1. package/dist/action/components/action-form.d.ts +6 -1
  2. package/dist/action/components/action-form.js +34 -49
  3. package/dist/action/components/action-form.js.map +1 -1
  4. package/dist/action/components/action-trigger-provider.d.ts +1 -1
  5. package/dist/action/components/action-trigger-provider.js.map +1 -1
  6. package/dist/action/hooks/use-action.d.ts +1 -1
  7. package/dist/action/hooks/use-action.js +3 -1
  8. package/dist/action/hooks/use-action.js.map +1 -1
  9. package/dist/action/index.d.ts +0 -2
  10. package/dist/action/index.js +1 -2
  11. package/dist/chat/plugin.d.ts +0 -1
  12. package/dist/chat/plugin.js.map +1 -1
  13. package/dist/clipboard/index.d.ts +0 -4
  14. package/dist/clipboard/index.js +1 -15
  15. package/dist/{events → core}/event-names.js +1 -1
  16. package/dist/core/event-names.js.map +1 -0
  17. package/dist/core/hooks/use-window-event.d.ts +8 -0
  18. package/dist/core/hooks/use-window-event.js +22 -0
  19. package/dist/core/hooks/use-window-event.js.map +1 -0
  20. package/dist/core/index.js +3 -3
  21. package/dist/core/types.d.ts +8 -73
  22. package/dist/create-app.js +1 -1
  23. package/dist/effects/dispatch.js +1 -1
  24. package/dist/effects/dispatch.js.map +1 -1
  25. package/dist/effects/registry.d.ts +2 -18
  26. package/dist/effects/registry.js +4 -8
  27. package/dist/effects/registry.js.map +1 -1
  28. package/dist/event-bridge.js +10 -23
  29. package/dist/event-bridge.js.map +1 -1
  30. package/dist/form/components/fields/date-picker-field.js +1 -1
  31. package/dist/form/components/fields/file-upload.js +4 -6
  32. package/dist/form/components/fields/file-upload.js.map +1 -1
  33. package/dist/form/components/fields/select.js +2 -2
  34. package/dist/form/components/fields/select.js.map +1 -1
  35. package/dist/form/components/fields/table-rows.js +3 -15
  36. package/dist/form/components/fields/table-rows.js.map +1 -1
  37. package/dist/form/components/form.js +8 -23
  38. package/dist/form/components/form.js.map +1 -1
  39. package/dist/form/embed.d.ts +4 -0
  40. package/dist/form/embed.js +3 -1
  41. package/dist/form/lib/collect-fields.d.ts +7 -0
  42. package/dist/form/lib/collect-fields.js +19 -0
  43. package/dist/form/lib/collect-fields.js.map +1 -0
  44. package/dist/form/lib/field-errors.d.ts +3 -0
  45. package/dist/form/lib/field-errors.js +11 -0
  46. package/dist/form/lib/field-errors.js.map +1 -0
  47. package/dist/format/format-context.d.ts +6 -0
  48. package/dist/format/format-context.js +17 -0
  49. package/dist/format/format-context.js.map +1 -0
  50. package/dist/i18n/backend.js +1 -1
  51. package/dist/i18n/index.js +2 -2
  52. package/dist/i18n/instance.js +1 -1
  53. package/dist/i18n/locale-reload.d.ts +1 -1
  54. package/dist/i18n/locale-reload.js +7 -6
  55. package/dist/i18n/locale-reload.js.map +1 -1
  56. package/dist/i18n/locale.js +1 -1
  57. package/dist/i18n/locale.js.map +1 -1
  58. package/dist/i18n/page-props.js +1 -1
  59. package/dist/i18n/shared-props.js +1 -3
  60. package/dist/i18n/shared-props.js.map +1 -1
  61. package/dist/i18n/timezone.js +1 -1
  62. package/dist/i18n/timezone.js.map +1 -1
  63. package/dist/index.d.ts +6 -7
  64. package/dist/index.js +7 -8
  65. package/dist/lattice.css +105 -0
  66. package/dist/layout/components/callouts.js +1 -1
  67. package/dist/layout/components/sidebar.js +15 -48
  68. package/dist/layout/components/sidebar.js.map +1 -1
  69. package/dist/layout/plugin.d.ts +0 -1
  70. package/dist/layout/plugin.js.map +1 -1
  71. package/dist/lib/is-truthy.d.ts +2 -0
  72. package/dist/lib/is-truthy.js +9 -0
  73. package/dist/lib/is-truthy.js.map +1 -0
  74. package/dist/lib/use-debounced-callback.d.ts +10 -0
  75. package/dist/lib/use-debounced-callback.js +34 -0
  76. package/dist/lib/use-debounced-callback.js.map +1 -0
  77. package/dist/lib/use-media-query.d.ts +5 -0
  78. package/dist/lib/use-media-query.js +26 -0
  79. package/dist/lib/use-media-query.js.map +1 -0
  80. package/dist/lib/use-persistent-state.d.ts +13 -0
  81. package/dist/lib/use-persistent-state.js +62 -0
  82. package/dist/lib/use-persistent-state.js.map +1 -0
  83. package/dist/notifications/components/notifications.js +3 -1
  84. package/dist/notifications/components/notifications.js.map +1 -1
  85. package/dist/notifications/plugin.d.ts +0 -1
  86. package/dist/notifications/plugin.js.map +1 -1
  87. package/dist/provider-base.js +1 -1
  88. package/dist/realtime/listeners.d.ts +1 -1
  89. package/dist/realtime/listeners.js +1 -1
  90. package/dist/realtime/listeners.js.map +1 -1
  91. package/dist/registry.d.ts +5 -0
  92. package/dist/registry.js.map +1 -1
  93. package/dist/remote/components/data-list.js +1 -1
  94. package/dist/remote/plugin.d.ts +0 -1
  95. package/dist/remote/plugin.js.map +1 -1
  96. package/dist/table/components/bulk-bar.js +4 -2
  97. package/dist/table/components/bulk-bar.js.map +1 -1
  98. package/dist/table/components/cells/boolean-cell.js +1 -3
  99. package/dist/table/components/cells/boolean-cell.js.map +1 -1
  100. package/dist/table/components/cells/copyable-cell.d.ts +9 -0
  101. package/dist/table/components/cells/copyable-cell.js +17 -0
  102. package/dist/table/components/cells/copyable-cell.js.map +1 -0
  103. package/dist/table/components/cells/money-cell.js +7 -18
  104. package/dist/table/components/cells/money-cell.js.map +1 -1
  105. package/dist/table/components/cells/number-cell.js +14 -27
  106. package/dist/table/components/cells/number-cell.js.map +1 -1
  107. package/dist/table/components/cells/numeric-cell.d.ts +10 -0
  108. package/dist/table/components/cells/numeric-cell.js +25 -0
  109. package/dist/table/components/cells/numeric-cell.js.map +1 -0
  110. package/dist/table/components/cells/stack-cell.js +1 -1
  111. package/dist/table/components/cells/text-cell.js +4 -5
  112. package/dist/table/components/cells/text-cell.js.map +1 -1
  113. package/dist/table/components/filter-controls.js +1 -3
  114. package/dist/table/components/filter-controls.js.map +1 -1
  115. package/dist/table/components/table-cell.js.map +1 -1
  116. package/dist/table/components/table-search.js +4 -7
  117. package/dist/table/components/table-search.js.map +1 -1
  118. package/dist/table/components/table.js +2 -3
  119. package/dist/table/components/table.js.map +1 -1
  120. package/dist/table/hooks/use-column-visibility.js +29 -58
  121. package/dist/table/hooks/use-column-visibility.js.map +1 -1
  122. package/dist/table/hooks/use-table.js +5 -13
  123. package/dist/table/hooks/use-table.js.map +1 -1
  124. package/dist/table/lib/bulk.d.ts +2 -0
  125. package/dist/table/lib/bulk.js +3 -1
  126. package/dist/table/lib/bulk.js.map +1 -1
  127. package/dist/table/lib/query.js +1 -1
  128. package/dist/table/lib/query.js.map +1 -1
  129. package/dist/table/plugin.d.ts +0 -1
  130. package/dist/table/plugin.js.map +1 -1
  131. package/dist/table/types.d.ts +4 -57
  132. package/dist/toast/callout.js +1 -1
  133. package/dist/toast/callout.js.map +1 -1
  134. package/dist/toast/index.d.ts +1 -1
  135. package/dist/toast/index.js +2 -2
  136. package/dist/toast/toast.d.ts +0 -2
  137. package/dist/toast/toast.js +3 -10
  138. package/dist/toast/toast.js.map +1 -1
  139. package/dist/toast/toaster.js +2 -2
  140. package/dist/types/generated.d.ts +224 -80
  141. package/dist/types/index.d.ts +1 -1
  142. package/dist/ui/button.d.ts +2 -2
  143. package/dist/ui/chart-view.js +7 -30
  144. package/dist/ui/chart-view.js.map +1 -1
  145. package/dist/ui/click-behavior.d.ts +4 -4
  146. package/dist/ui/click-behavior.js.map +1 -1
  147. package/dist/ui/collapsible.js +3 -15
  148. package/dist/ui/collapsible.js.map +1 -1
  149. package/dist/ui/combobox.js +6 -3
  150. package/dist/ui/combobox.js.map +1 -1
  151. package/dist/ui/confirm-dialog.js +1 -1
  152. package/dist/ui/confirm-dialog.js.map +1 -1
  153. package/dist/ui/control.d.ts +1 -1
  154. package/dist/ui/dialog.d.ts +6 -2
  155. package/dist/ui/dialog.js +29 -5
  156. package/dist/ui/dialog.js.map +1 -1
  157. package/dist/ui/fragment.js +1 -1
  158. package/dist/ui/fragment.js.map +1 -1
  159. package/dist/ui/icon-button.d.ts +1 -1
  160. package/dist/ui/image-preview.js +1 -1
  161. package/dist/ui/image-preview.js.map +1 -1
  162. package/dist/ui/modal.js +3 -2
  163. package/dist/ui/modal.js.map +1 -1
  164. package/dist/ui/pill.d.ts +2 -0
  165. package/dist/ui/pill.js +10 -0
  166. package/dist/ui/pill.js.map +1 -0
  167. package/dist/ui/plugin.js.map +1 -1
  168. package/dist/ui/progress.js +1 -1
  169. package/dist/ui/section.js +2 -15
  170. package/dist/ui/section.js.map +1 -1
  171. package/dist/ui/segmented-pills.js +2 -1
  172. package/dist/ui/segmented-pills.js.map +1 -1
  173. package/dist/ui/tabs.js +12 -10
  174. package/dist/ui/tabs.js.map +1 -1
  175. package/dist/ui/tree-context.js +11 -13
  176. package/dist/ui/tree-context.js.map +1 -1
  177. package/dist/ui/tree.js +1 -1
  178. package/dist/ui/tree.js.map +1 -1
  179. package/dist/ui/use-collapsible-state.d.ts +7 -0
  180. package/dist/ui/use-collapsible-state.js +21 -0
  181. package/dist/ui/use-collapsible-state.js.map +1 -0
  182. package/dist-standalone/chunks/chart-view-C6R5iPra.js +36 -0
  183. package/dist-standalone/chunks/{date-picker-field-VbH9xrQO.js → date-picker-field-DhhEB-Mh.js} +1 -1
  184. package/dist-standalone/chunks/{page-props-AIa5UR6n.js → page-props-DOfg30l5.js} +1 -1
  185. package/dist-standalone/chunks/{rich-editor-field-DLQTyt36.js → rich-editor-field-DORXTHPw.js} +2 -2
  186. package/dist-standalone/chunks/{subscriptions-DY2tj9Im.js → subscriptions-B_ISgA7V.js} +1 -1
  187. package/dist-standalone/chunks/use-effect-dispatcher-D1wPPwHj.js +97 -0
  188. package/dist-standalone/lattice.css +1 -1
  189. package/dist-standalone/lattice.js +15 -15
  190. package/dist-standalone/manifest.json +8 -8
  191. package/package.json +1 -1
  192. package/dist/clipboard/index.js.map +0 -1
  193. package/dist/events/event-names.js.map +0 -1
  194. package/dist/table/components/table-action-node.d.ts +0 -4
  195. package/dist/table/components/table-action-node.js +0 -24
  196. package/dist/table/components/table-action-node.js.map +0 -1
  197. package/dist-standalone/chunks/chart-view-wJmnVu7L.js +0 -36
  198. package/dist-standalone/chunks/use-effect-dispatcher-feEs0GCP.js +0 -97
  199. /package/dist/{events → core}/event-names.d.ts +0 -0
@@ -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 {};
@@ -8,11 +8,13 @@ import { apiFetch } from "../../core/api.js";
8
8
  import { Skeleton } from "../../ui/skeleton.js";
9
9
  import { FormProvider } from "../../form/hooks/context.js";
10
10
  import { PrefillProvider } from "../../form/hooks/prefill-context.js";
11
- import { walkFields } from "../../form/lib/field-props.js";
12
11
  import { ResolvedNodesProvider } from "../../form/hooks/resolved-nodes.js";
13
12
  import { FormValuesProvider, useFormValues } from "../../form/hooks/values.js";
14
13
  import "../../form/lib/form-transport.js";
15
14
  import { useFormResolver } from "../../form/hooks/use-form-resolver.js";
15
+ import { collectFields } from "../../form/lib/collect-fields.js";
16
+ import { firstErrors } from "../../form/lib/field-errors.js";
17
+ import { useDebouncedCallback } from "../../lib/use-debounced-callback.js";
16
18
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
17
19
  import { jsx, jsxs } from "react/jsx-runtime";
18
20
  //#region resources/js/action/components/action-form.tsx
@@ -43,11 +45,6 @@ function useLazyActionForm(endpoint, componentRef, enabled) {
43
45
  ]);
44
46
  return node;
45
47
  }
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
48
  function ActionFormSkeleton() {
52
49
  return /* @__PURE__ */ jsxs("div", {
53
50
  className: "space-y-4",
@@ -59,22 +56,6 @@ function ActionFormSkeleton() {
59
56
  ]
60
57
  });
61
58
  }
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
59
  function ActionFormBody({ cancelLabel, componentRef, endpoint, extraData, fieldLabels, formNode, method, onClose, onSuccess, precognitive, submitLabel }) {
79
60
  const values = useFormValues();
80
61
  const valuesRef = useRef(values);
@@ -84,7 +65,6 @@ function ActionFormBody({ cancelLabel, componentRef, endpoint, extraData, fieldL
84
65
  const { nodes: resolvedNodes, markUserEdit } = useFormResolver(endpoint, componentRef, formNode.schema);
85
66
  const [errors, setErrors] = useState({});
86
67
  const [processing, setProcessing] = useState(false);
87
- const timer = useRef(void 0);
88
68
  const request = useCallback((extraHeaders) => apiFetch(endpoint, {
89
69
  body: JSON.stringify({
90
70
  ...valuesRef.current,
@@ -105,30 +85,25 @@ function ActionFormBody({ cancelLabel, componentRef, endpoint, extraData, fieldL
105
85
  [field]: void 0
106
86
  });
107
87
  }, []);
88
+ const runValidation = useDebouncedCallback((field) => {
89
+ request({
90
+ Precognition: "true",
91
+ "Precognition-Validate-Only": field
92
+ }).then(async (response) => {
93
+ if (response.status === 422) {
94
+ const body = await response.json();
95
+ setErrors((current) => ({
96
+ ...current,
97
+ ...firstErrors(body.errors)
98
+ }));
99
+ return;
100
+ }
101
+ clearErrors(field);
102
+ }).catch(() => {});
103
+ }, 250);
108
104
  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
- ]);
105
+ if (precognitive) runValidation(field);
106
+ }, [precognitive, runValidation]);
132
107
  const submit = useCallback(() => {
133
108
  setProcessing(true);
134
109
  request().then(async (response) => {
@@ -196,7 +171,16 @@ function ActionFormBody({ cancelLabel, componentRef, endpoint, extraData, fieldL
196
171
  }
197
172
  function ActionFormContent({ formNode, ...rest }) {
198
173
  const precognitive = Boolean(formNode.props?.precognitive);
199
- const { labels: fieldLabels, values: initialValues } = useMemo(() => collectFields(formNode), [formNode]);
174
+ const { labels: fieldLabels, values: initialValues } = useMemo(() => {
175
+ const { labels, values } = collectFields(formNode.schema);
176
+ return {
177
+ labels,
178
+ values: {
179
+ ...values,
180
+ ...formNode.props?.state
181
+ }
182
+ };
183
+ }, [formNode]);
200
184
  return /* @__PURE__ */ jsx(FormValuesProvider, {
201
185
  initial: initialValues,
202
186
  children: /* @__PURE__ */ jsx(ActionFormBody, {
@@ -207,7 +191,7 @@ function ActionFormContent({ formNode, ...rest }) {
207
191
  })
208
192
  });
209
193
  }
210
- function ActionForm({ description, formNode, onClose, title, ...rest }) {
194
+ function ActionForm({ description, formNode, onClose, placement, title, width, ...rest }) {
211
195
  const { t } = useT("lattice");
212
196
  return /* @__PURE__ */ jsx(Dialog, {
213
197
  open: true,
@@ -216,7 +200,8 @@ function ActionForm({ description, formNode, onClose, title, ...rest }) {
216
200
  },
217
201
  children: /* @__PURE__ */ jsxs(DialogContent, {
218
202
  ...description ? {} : { "aria-describedby": void 0 },
219
- className: "max-h-[min(680px,calc(100vh-2rem))] w-full max-w-lg overflow-y-auto",
203
+ placement,
204
+ width,
220
205
  children: [/* @__PURE__ */ jsx(DialogHeader, {
221
206
  closeLabel: t("common.close", "Close"),
222
207
  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 } from \"@lattice-php/lattice/effects/dispatch\";\nimport type { ActionResponse } from \"@lattice-php/lattice/effects/dispatch\";\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 [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 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 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"}
@@ -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 {};
@@ -75,8 +75,10 @@ function useAction(node) {
75
75
  dispatch(getActionEffects(response.effects));
76
76
  setIsFilling(false);
77
77
  },
78
+ placement: node.props.modalSide ?? "center",
78
79
  submitLabel: confirmationConfirmLabel,
79
- title: confirmationTitle
80
+ title: confirmationTitle,
81
+ width: node.props.modalWidth ?? void 0
80
82
  })] });
81
83
  return {
82
84
  processing: http.processing,
@@ -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, 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\" | \"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 = 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 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: http.processing, requestSubmit, overlays };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA4BA,SAAgB,UAAU,MAAiD;CACzE,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,WAAW,KAAK,MAAM,aAAa;EACnC,aAAa;EACb,OAAO;EACP,OAAO,KAAK,MAAM,cAAc,KAAA;CACjC,CAAA,CAEH,EAAA,CAAA;CAGJ,OAAO;EAAE,YAAY,KAAK;EAAY;EAAe;CAAS;AAChE"}
@@ -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 };
@@ -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;
@@ -1,16 +1,2 @@
1
1
  import { CopyableText, copyToClipboard } from "../ui/copyable-text.js";
2
- import { useState } from "react";
3
- //#region resources/js/clipboard/index.ts
4
- function useClipboard() {
5
- const [copiedText, setCopiedText] = useState(null);
6
- const copy = async (text) => {
7
- const copied = await copyToClipboard(text);
8
- setCopiedText(copied ? text : null);
9
- return copied;
10
- };
11
- return [copiedText, copy];
12
- }
13
- //#endregion
14
- export { CopyableText, copyToClipboard, useClipboard };
15
-
16
- //# sourceMappingURL=index.js.map
2
+ export { CopyableText, copyToClipboard };
@@ -1,4 +1,4 @@
1
- //#region resources/js/events/event-names.ts
1
+ //#region resources/js/core/event-names.ts
2
2
  /**
3
3
  * Single source of truth for the `lattice:*` DOM events the runtime dispatches
4
4
  * and listens for. The built-in effect handlers in effects/registry.ts bridge
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-names.js","names":[],"sources":["../../resources/js/core/event-names.ts"],"sourcesContent":["/**\n * Single source of truth for the `lattice:*` DOM events the runtime dispatches\n * and listens for. The built-in effect handlers in effects/registry.ts bridge\n * effects to these events; the rest are framework events with no PHP counterpart.\n */\nexport const LATTICE_EVENT = {\n callout: \"lattice:callout\",\n toast: \"lattice:toast\",\n reloadComponent: \"lattice:reload-component\",\n openModal: \"lattice:open-modal\",\n closeModal: \"lattice:close-modal\",\n resetForm: \"lattice:reset-form\",\n toggleSidebar: \"lattice:toggle-sidebar\",\n appearanceChange: \"lattice:appearance-change\",\n localeChange: \"lattice:locale-change\",\n timezoneChange: \"lattice:timezone-change\",\n actionError: \"lattice:action-error\",\n} as const;\n\nexport type ReloadComponentEvent = CustomEvent<{\n component?: string;\n type?: string;\n}>;\n"],"mappings":";;;;;;AAKA,IAAa,gBAAgB;CAC3B,SAAS;CACT,OAAO;CACP,iBAAiB;CACjB,WAAW;CACX,YAAY;CACZ,WAAW;CACX,eAAe;CACf,kBAAkB;CAClB,cAAc;CACd,gBAAgB;CAChB,aAAa;AACf"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Subscribe to a window event for the lifetime of the component. The handler is
3
+ * read through a ref, so a fresh handler each render never re-subscribes; pass
4
+ * `enabled: false` to detach without unmounting.
5
+ */
6
+ export declare function useWindowEvent(type: string, handler: (event: Event) => void, options?: {
7
+ enabled?: boolean;
8
+ }): void;
@@ -0,0 +1,22 @@
1
+ import { useEffect, useRef } from "react";
2
+ //#region resources/js/core/hooks/use-window-event.ts
3
+ /**
4
+ * Subscribe to a window event for the lifetime of the component. The handler is
5
+ * read through a ref, so a fresh handler each render never re-subscribes; pass
6
+ * `enabled: false` to detach without unmounting.
7
+ */
8
+ function useWindowEvent(type, handler, options = {}) {
9
+ const { enabled = true } = options;
10
+ const handlerRef = useRef(handler);
11
+ handlerRef.current = handler;
12
+ useEffect(() => {
13
+ if (!enabled || typeof window === "undefined") return;
14
+ const listener = (event) => handlerRef.current(event);
15
+ window.addEventListener(type, listener);
16
+ return () => window.removeEventListener(type, listener);
17
+ }, [type, enabled]);
18
+ }
19
+ //#endregion
20
+ export { useWindowEvent };
21
+
22
+ //# sourceMappingURL=use-window-event.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-window-event.js","names":[],"sources":["../../../resources/js/core/hooks/use-window-event.ts"],"sourcesContent":["import { useEffect, useRef } from \"react\";\n\n/**\n * Subscribe to a window event for the lifetime of the component. The handler is\n * read through a ref, so a fresh handler each render never re-subscribes; pass\n * `enabled: false` to detach without unmounting.\n */\nexport function useWindowEvent(\n type: string,\n handler: (event: Event) => void,\n options: { enabled?: boolean } = {},\n): void {\n const { enabled = true } = options;\n const handlerRef = useRef(handler);\n handlerRef.current = handler;\n\n useEffect(() => {\n if (!enabled || typeof window === \"undefined\") {\n return;\n }\n\n const listener = (event: Event): void => handlerRef.current(event);\n\n window.addEventListener(type, listener);\n\n return () => window.removeEventListener(type, listener);\n }, [type, enabled]);\n}\n"],"mappings":";;;;;;;AAOA,SAAgB,eACd,MACA,SACA,UAAiC,CAAC,GAC5B;CACN,MAAM,EAAE,UAAU,SAAS;CAC3B,MAAM,aAAa,OAAO,OAAO;CACjC,WAAW,UAAU;CAErB,gBAAgB;EACd,IAAI,CAAC,WAAW,OAAO,WAAW,aAChC;EAGF,MAAM,YAAY,UAAuB,WAAW,QAAQ,KAAK;EAEjE,OAAO,iBAAiB,MAAM,QAAQ;EAEtC,aAAa,OAAO,oBAAoB,MAAM,QAAQ;CACxD,GAAG,CAAC,MAAM,OAAO,CAAC;AACpB"}
@@ -1,10 +1,10 @@
1
1
  import { createPlugin, createRegistry, eagerComponent, extendRegistry, lazyComponent } from "./registry.js";
2
2
  import { RegistryContext, setDefaultRegistry, useColumnRegistry, useComponentRegistry, useEffectHandlerRegistry } from "./registry-context.js";
3
+ import { dataBindings, isRecord, materializeNode, materializeProps, materializeSchema, rowValue } from "./materialize.js";
4
+ import { LATTICE_REF_HEADER, withRefHeader } from "./component-ref.js";
5
+ import { withHeaders } from "./headers.js";
3
6
  import { toNodes } from "./nodes.js";
4
7
  import { CollapsedContext, useCollapsed } from "./collapsed-context.js";
5
8
  import { RenderNode, Renderer } from "./renderer.js";
6
- import { LATTICE_REF_HEADER, withRefHeader } from "./component-ref.js";
7
- import { withHeaders } from "./headers.js";
8
9
  import { ApiError, apiFetch, apiJson, clearRemoteTokenCache, invalidateRemoteToken, remoteFetch, remoteJson, remoteToken } from "./api.js";
9
- import { dataBindings, isRecord, materializeNode, materializeProps, materializeSchema, rowValue } from "./materialize.js";
10
10
  export { ApiError, CollapsedContext, LATTICE_REF_HEADER, RegistryContext, RenderNode, Renderer, apiFetch, apiJson, clearRemoteTokenCache, createPlugin, createRegistry, dataBindings, eagerComponent, extendRegistry, invalidateRemoteToken, isRecord, lazyComponent, materializeNode, materializeProps, materializeSchema, remoteFetch, remoteJson, remoteToken, rowValue, setDefaultRegistry, toNodes, useCollapsed, useColumnRegistry, useComponentRegistry, useEffectHandlerRegistry, withHeaders, withRefHeader };
@@ -1,81 +1,16 @@
1
1
  import { ComponentType as ReactComponentType, ReactNode } from 'react';
2
- import { ComponentPropsMap, NodeType, Option, PageContainer as KnownPageContainer, WireNode, Listen } from '../types/generated.js';
3
- export type { KnownPageContainer, NodeType, Option, WireNode };
4
- export type NodeProps = Record<string, unknown>;
5
- export type CommonNodeProps = {
6
- dataBindings?: Record<string, string> | null;
7
- hideWhenCollapsed?: boolean | null;
8
- };
9
- /**
10
- * Augmentable map of wire `type` string → props for consumer components. Apps
11
- * extend it via `declare module "@lattice-php/lattice"` so their custom components
12
- * gain strong prop types; the entries are generated by `php artisan lattice:typescript`.
13
- * Built-in components resolve through the generated `ComponentPropsMap` below, the
14
- * same way `ColumnProps` augments `ColumnPropsMap`. Types in neither fall back to a
15
- * loose bag.
16
- */
17
- export interface ComponentProps {
18
- }
19
- /**
20
- * Resolves a `type` string to its payload: consumer augmentations (`TAugment`)
21
- * win over generated built-ins (`TBuiltins`), then a loose fallback. The one
22
- * resolver behind the component, column and effect maps.
23
- */
24
- export type ResolveProps<TAugment, TBuiltins, TType extends string, TFallback> = TType extends keyof TAugment ? TAugment[TType] : TType extends keyof TBuiltins ? TBuiltins[TType] : TFallback;
25
- export type PropsOf<TType extends string> = ResolveProps<ComponentProps, ComponentPropsMap, TType, NodeProps> & CommonNodeProps;
26
- /**
27
- * The loose wire node an unknown/untyped `type` resolves to: the shape every node
28
- * shares, with a loose `props` bag. The single source of the wire-node shape;
29
- * generated `WireNode` aliases `Node` (i.e. `Node<string>`), which is this.
30
- */
31
- type LooseNode = {
32
- id?: string;
33
- key?: string;
34
- type: string;
35
- props?: NodeProps;
36
- schema?: Schema;
37
- };
38
- /**
39
- * Built-in `props` is required, not optional — the wire serializes the full prop
40
- * object, so reads need no optional chaining. Unknown types fall back to `LooseNode`.
41
- */
42
- export type NodeOfType<TType extends string = string> = string extends TType ? LooseNode : {
43
- id?: string;
44
- key?: string;
45
- props: PropsOf<TType>;
46
- schema?: Schema;
47
- type: TType;
48
- };
49
- export type Node<TType extends string = string> = NodeOfType<TType>;
50
- /**
51
- * Distributes so `NodeUnionOf<"a" | "b">` is `NodeOfType<"a"> | NodeOfType<"b">`
52
- * — a discriminated union where `node.type` narrows `node.props`. Domains build
53
- * their node union from a generated `…NodeType` string union with this.
54
- */
55
- export type NodeUnionOf<TTypes extends string> = TTypes extends string ? NodeOfType<TTypes> : never;
56
- export type Schema = Node[];
2
+ import { CommonNodeProps, ComponentProps, Node, NodeProps, NodeType, NodeUnionOf, Option, PageContainer as KnownPageContainer, ComponentPropsOf, ResolveProps, Schema, Breadcrumb, PageLayoutPayload, PagePayload as GeneratedPagePayload } from '../types/generated.js';
3
+ export type { CommonNodeProps, ComponentProps, KnownPageContainer, Node, NodeProps, NodeType, NodeUnionOf, Option, ComponentPropsOf, ResolveProps, Schema, };
57
4
  /** Its `schema` holds exactly one Outlet node. */
58
- export type LayoutPayload = {
59
- key: string;
60
- schema: Schema;
61
- };
5
+ export type LayoutPayload = PageLayoutPayload;
6
+ export type PageBreadcrumb = Breadcrumb;
62
7
  /**
63
- * The page payload the server hydrates onto `lattice` (see PHP `Page::toArray`).
64
- * Hand-written rather than generated: its `schema`/`layout.schema` are component
65
- * trees serialized eagerly during the request (to fire side effects before the
66
- * final encode), so they cannot be derived from a value object as typed `Node[]`.
8
+ * The page payload the server hydrates onto `lattice` the generated
9
+ * `PagePayload` (PHP `Http\PagePayload`), with `container` refined to the
10
+ * known-container union custom containers extend.
67
11
  */
68
- export type PagePayload = {
69
- breadcrumbs: PageBreadcrumb[];
12
+ export type PagePayload = Omit<GeneratedPagePayload, "container"> & {
70
13
  container: PageContainer;
71
- layout: LayoutPayload | null;
72
- listeners?: Listen[];
73
- schema: Schema;
74
- title: string | null;
75
- };
76
- export type PageBreadcrumb = {
77
- href: string;
78
- title: string;
79
14
  };
80
15
  export type PageContainer = KnownPageContainer | (string & {});
81
16
  export type RendererComponentProps<TType extends string = string> = {
@@ -1,8 +1,8 @@
1
1
  import { initializeTheme } from "./appearance/index.js";
2
2
  import { extendRegistry } from "./core/registry.js";
3
3
  import { setDefaultRegistry } from "./core/registry-context.js";
4
- import { i18nConfigFromPageProps } from "./i18n/shared-props.js";
5
4
  import { LocaleReload } from "./i18n/locale-reload.js";
5
+ import { i18nConfigFromPageProps } from "./i18n/shared-props.js";
6
6
  import { createLayoutResolver, createPageResolver, withVisitHeaders } from "./inertia.js";
7
7
  import { ProviderBase } from "./provider-base.js";
8
8
  import { registry } from "./registry.js";
@@ -1,4 +1,4 @@
1
- import { LATTICE_EVENT } from "../events/event-names.js";
1
+ import { LATTICE_EVENT } from "../core/event-names.js";
2
2
  import { builtinEffectHandlers } from "./registry.js";
3
3
  //#region resources/js/effects/dispatch.ts
4
4
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"dispatch.js","names":[],"sources":["../../resources/js/effects/dispatch.ts"],"sourcesContent":["import type { ActionResult, Effect } from \"@lattice-php/lattice/types/generated\";\nimport { LATTICE_EVENT } from \"@lattice-php/lattice/events/event-names\";\nimport { builtinEffectHandlers } from \"./registry\";\nimport type { EffectHandlerRegistry } from \"./registry\";\n\nexport type ActionEffect = Effect;\n\nexport type ActionResponse = Partial<ActionResult>;\n\n/**\n * Run each effect through its handler. Handlers default to the built-ins; the\n * Provider passes a merged registry that also includes consumer-registered\n * handlers. An effect with no handler is warned about (dev) and skipped.\n */\nexport function dispatchEffects(\n effects: ActionEffect[],\n handlers: EffectHandlerRegistry = builtinEffectHandlers,\n): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n for (const effect of effects) {\n const handler = handlers[effect.type];\n\n if (handler === undefined) {\n console.warn(`[lattice] No handler registered for effect type \"${effect.type}\".`);\n continue;\n }\n\n handler(effect);\n }\n}\n\nexport function dispatchActionError(error: unknown): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n window.dispatchEvent(new CustomEvent(LATTICE_EVENT.actionError, { detail: { error } }));\n}\n\nexport function getActionEffects(effects: unknown): ActionEffect[] {\n return Array.isArray(effects) ? effects.filter(isActionEffect) : [];\n}\n\n/**\n * Structural guard for an effect on the wire. Intentionally open: it accepts any\n * object with a string `type`, not only the built-in types, so consumer-registered\n * effects pass through getActionEffects() and reach their handlers. Dispatch warns\n * on (and skips) a type with no registered handler.\n */\nexport function isActionEffect(effect: unknown): effect is ActionEffect {\n return (\n typeof effect === \"object\" &&\n effect !== null &&\n \"type\" in effect &&\n typeof (effect as { type: unknown }).type === \"string\"\n );\n}\n"],"mappings":";;;;;;;;AAcA,SAAgB,gBACd,SACA,WAAkC,uBAC5B;CACN,IAAI,OAAO,WAAW,aACpB;CAGF,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,UAAU,SAAS,OAAO;EAEhC,IAAI,YAAY,KAAA,GAAW;GACzB,QAAQ,KAAK,oDAAoD,OAAO,KAAK,GAAG;GAChF;EACF;EAEA,QAAQ,MAAM;CAChB;AACF;AAEA,SAAgB,oBAAoB,OAAsB;CACxD,IAAI,OAAO,WAAW,aACpB;CAGF,OAAO,cAAc,IAAI,YAAY,cAAc,aAAa,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;AACxF;AAEA,SAAgB,iBAAiB,SAAkC;CACjE,OAAO,MAAM,QAAQ,OAAO,IAAI,QAAQ,OAAO,cAAc,IAAI,CAAC;AACpE;;;;;;;AAQA,SAAgB,eAAe,QAAyC;CACtE,OACE,OAAO,WAAW,YAClB,WAAW,QACX,UAAU,UACV,OAAQ,OAA6B,SAAS;AAElD"}
1
+ {"version":3,"file":"dispatch.js","names":[],"sources":["../../resources/js/effects/dispatch.ts"],"sourcesContent":["import type { ActionResult, Effect } from \"@lattice-php/lattice/types/generated\";\nimport { LATTICE_EVENT } from \"@lattice-php/lattice/core/event-names\";\nimport { builtinEffectHandlers } from \"./registry\";\nimport type { EffectHandlerRegistry } from \"./registry\";\n\nexport type ActionEffect = Effect;\n\nexport type ActionResponse = Partial<ActionResult>;\n\n/**\n * Run each effect through its handler. Handlers default to the built-ins; the\n * Provider passes a merged registry that also includes consumer-registered\n * handlers. An effect with no handler is warned about (dev) and skipped.\n */\nexport function dispatchEffects(\n effects: ActionEffect[],\n handlers: EffectHandlerRegistry = builtinEffectHandlers,\n): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n for (const effect of effects) {\n const handler = handlers[effect.type];\n\n if (handler === undefined) {\n console.warn(`[lattice] No handler registered for effect type \"${effect.type}\".`);\n continue;\n }\n\n handler(effect);\n }\n}\n\nexport function dispatchActionError(error: unknown): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n window.dispatchEvent(new CustomEvent(LATTICE_EVENT.actionError, { detail: { error } }));\n}\n\nexport function getActionEffects(effects: unknown): ActionEffect[] {\n return Array.isArray(effects) ? effects.filter(isActionEffect) : [];\n}\n\n/**\n * Structural guard for an effect on the wire. Intentionally open: it accepts any\n * object with a string `type`, not only the built-in types, so consumer-registered\n * effects pass through getActionEffects() and reach their handlers. Dispatch warns\n * on (and skips) a type with no registered handler.\n */\nexport function isActionEffect(effect: unknown): effect is ActionEffect {\n return (\n typeof effect === \"object\" &&\n effect !== null &&\n \"type\" in effect &&\n typeof (effect as { type: unknown }).type === \"string\"\n );\n}\n"],"mappings":";;;;;;;;AAcA,SAAgB,gBACd,SACA,WAAkC,uBAC5B;CACN,IAAI,OAAO,WAAW,aACpB;CAGF,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,UAAU,SAAS,OAAO;EAEhC,IAAI,YAAY,KAAA,GAAW;GACzB,QAAQ,KAAK,oDAAoD,OAAO,KAAK,GAAG;GAChF;EACF;EAEA,QAAQ,MAAM;CAChB;AACF;AAEA,SAAgB,oBAAoB,OAAsB;CACxD,IAAI,OAAO,WAAW,aACpB;CAGF,OAAO,cAAc,IAAI,YAAY,cAAc,aAAa,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;AACxF;AAEA,SAAgB,iBAAiB,SAAkC;CACjE,OAAO,MAAM,QAAQ,OAAO,IAAI,QAAQ,OAAO,cAAc,IAAI,CAAC;AACpE;;;;;;;AAQA,SAAgB,eAAe,QAAyC;CACtE,OACE,OAAO,WAAW,YAClB,WAAW,QACX,UAAU,UACV,OAAQ,OAA6B,SAAS;AAElD"}
@@ -1,17 +1,5 @@
1
- import { ResolveProps } from '../core/types.js';
2
- import { Effect, EffectPropsMap } from '../types/generated.js';
3
- /**
4
- * Consumer apps augment this via `declare module "@lattice-php/lattice"` to type
5
- * their custom effects' payloads; built-ins resolve through `EffectPropsMap`. The
6
- * effect counterpart of `ComponentProps`.
7
- */
8
- export interface EffectProps {
9
- }
10
- export type EffectPropsOf<TType extends string> = ResolveProps<EffectProps, EffectPropsMap, TType, Record<string, unknown>>;
11
- export type EffectOf<TType extends string> = string extends TType ? Effect : {
12
- type: TType;
13
- props: EffectPropsOf<TType>;
14
- };
1
+ import { EffectOf, EffectProps, EffectPropsMap, EffectPropsOf } from '../types/generated.js';
2
+ export type { EffectOf, EffectProps, EffectPropsOf };
15
3
  export type EffectHandler<TType extends string = string> = (effect: EffectOf<TType>) => void;
16
4
  export type EffectHandlerRegistry = Record<string, EffectHandler>;
17
5
  export type EffectHandlerRegistryFor<TTypes extends keyof EffectPropsMap & string> = Record<TTypes, EffectHandler>;
@@ -20,9 +8,5 @@ export type EffectHandlerRegistryFor<TTypes extends keyof EffectPropsMap & strin
20
8
  * register it through this — it erases the type parameter for the loose registry.
21
9
  */
22
10
  export declare function effectHandler<TType extends string>(_type: TType, fn: EffectHandler<TType>): EffectHandler;
23
- /**
24
- * Imperative effects act directly; the rest bridge to the `lattice:*` DOM events
25
- * that toast/callout/modal/fragment/form subscribe to.
26
- */
27
11
  export declare const builtinEffectHandlers: EffectHandlerRegistryFor<keyof EffectPropsMap & string>;
28
12
  export declare function mergeEffectHandlers(...registries: Array<EffectHandlerRegistry | undefined>): EffectHandlerRegistry;
@@ -1,4 +1,4 @@
1
- import { LATTICE_EVENT } from "../events/event-names.js";
1
+ import { LATTICE_EVENT } from "../core/event-names.js";
2
2
  import { setLocale } from "../i18n/locale.js";
3
3
  import { router } from "@inertiajs/react";
4
4
  //#region resources/js/effects/registry.ts
@@ -20,15 +20,11 @@ function triggerDownload(url) {
20
20
  function bridge(event) {
21
21
  return (effect) => window.dispatchEvent(new CustomEvent(event, { detail: effect.props }));
22
22
  }
23
- /**
24
- * Imperative effects act directly; the rest bridge to the `lattice:*` DOM events
25
- * that toast/callout/modal/fragment/form subscribe to.
26
- */
27
23
  var builtinEffectHandlers = {
28
24
  "reload-page": () => router.reload(),
29
- redirect: effectHandler("redirect", (effect) => router.visit(effect.props.url)),
30
- download: effectHandler("download", (effect) => triggerDownload(effect.props.url)),
31
- "locale-change": effectHandler("locale-change", (effect) => setLocale(effect.props.locale)),
25
+ redirect: (effect) => router.visit(effect.props.url),
26
+ download: (effect) => triggerDownload(effect.props.url),
27
+ "locale-change": (effect) => setLocale(effect.props.locale),
32
28
  toast: bridge(LATTICE_EVENT.toast),
33
29
  callout: bridge(LATTICE_EVENT.callout),
34
30
  "reload-component": bridge(LATTICE_EVENT.reloadComponent),