@lattice-php/lattice 0.18.0 → 0.20.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 (255) hide show
  1. package/README.md +1 -1
  2. package/dist/core/index.js +3 -3
  3. package/dist/core/nodes.d.ts +5 -0
  4. package/dist/core/nodes.js +8 -1
  5. package/dist/core/nodes.js.map +1 -1
  6. package/dist/core/renderer.js +1 -3
  7. package/dist/core/renderer.js.map +1 -1
  8. package/dist/core/types.d.ts +16 -4
  9. package/dist/create-app.d.ts +31 -3
  10. package/dist/create-app.js +48 -10
  11. package/dist/create-app.js.map +1 -1
  12. package/dist/effects/registry.d.ts +3 -3
  13. package/dist/effects/registry.js +4 -4
  14. package/dist/effects/registry.js.map +1 -1
  15. package/dist/events/event-names.d.ts +0 -3
  16. package/dist/events/event-names.js +0 -3
  17. package/dist/events/event-names.js.map +1 -1
  18. package/dist/form/components/base/field.d.ts +2 -2
  19. package/dist/form/components/base/field.js +2 -2
  20. package/dist/form/components/base/field.js.map +1 -1
  21. package/dist/form/components/fields/color-picker-field.d.ts +2 -0
  22. package/dist/form/components/fields/color-picker-field.js +57 -0
  23. package/dist/form/components/fields/color-picker-field.js.map +1 -0
  24. package/dist/form/components/fields/date-input.js +1 -1
  25. package/dist/form/components/fields/date-picker-field.js +1 -1
  26. package/dist/form/components/fields/date-time-input.js +1 -1
  27. package/dist/form/components/fields/file-upload.js +7 -12
  28. package/dist/form/components/fields/file-upload.js.map +1 -1
  29. package/dist/form/components/fields/number-input.js +1 -1
  30. package/dist/form/components/fields/rich-editor-field.js +36 -275
  31. package/dist/form/components/fields/rich-editor-field.js.map +1 -1
  32. package/dist/form/components/fields/row-action-menu.js +2 -2
  33. package/dist/form/components/fields/row-action-menu.js.map +1 -1
  34. package/dist/form/components/fields/row-actions.js +3 -1
  35. package/dist/form/components/fields/row-actions.js.map +1 -1
  36. package/dist/form/components/fields/row-item.js +2 -1
  37. package/dist/form/components/fields/row-item.js.map +1 -1
  38. package/dist/form/components/fields/select.js +59 -16
  39. package/dist/form/components/fields/select.js.map +1 -1
  40. package/dist/form/components/fields/table-rows.js +5 -4
  41. package/dist/form/components/fields/table-rows.js.map +1 -1
  42. package/dist/form/components/fields/text-input.js +8 -1
  43. package/dist/form/components/fields/text-input.js.map +1 -1
  44. package/dist/form/components/fields/time-input.js +1 -1
  45. package/dist/form/components/index.d.ts +1 -0
  46. package/dist/form/components/index.js +2 -1
  47. package/dist/form/embed.d.ts +1 -0
  48. package/dist/form/embed.js +2 -1
  49. package/dist/form/hooks/use-form-resolver.js.map +1 -1
  50. package/dist/form/index.js +2 -1
  51. package/dist/form/lib/conditions.d.ts +4 -10
  52. package/dist/form/lib/conditions.js.map +1 -1
  53. package/dist/form/lib/field-props.d.ts +4 -18
  54. package/dist/form/lib/field-props.js.map +1 -1
  55. package/dist/form/plugin.js +2 -0
  56. package/dist/form/plugin.js.map +1 -1
  57. package/dist/form/rich-editor/builtins.d.ts +6 -0
  58. package/dist/form/rich-editor/builtins.js +400 -0
  59. package/dist/form/rich-editor/builtins.js.map +1 -0
  60. package/dist/form/rich-editor/index.d.ts +7 -0
  61. package/dist/form/rich-editor/index.js +3 -0
  62. package/dist/form/rich-editor/registry.d.ts +67 -0
  63. package/dist/form/rich-editor/registry.js +79 -0
  64. package/dist/form/rich-editor/registry.js.map +1 -0
  65. package/dist/form/rich-editor/toolbar-button.d.ts +12 -0
  66. package/dist/form/rich-editor/toolbar-button.js +28 -0
  67. package/dist/form/rich-editor/toolbar-button.js.map +1 -0
  68. package/dist/form/types.d.ts +1 -6
  69. package/dist/format/date-time.d.ts +3 -2
  70. package/dist/format/date-time.js.map +1 -1
  71. package/dist/i18n/backend.js +1 -1
  72. package/dist/i18n/date-time.d.ts +3 -2
  73. package/dist/i18n/date-time.js.map +1 -1
  74. package/dist/i18n/index.js +3 -2
  75. package/dist/i18n/instance.js +1 -1
  76. package/dist/i18n/page-props.d.ts +3 -2
  77. package/dist/i18n/page-props.js +1 -15
  78. package/dist/i18n/page-props.js.map +1 -1
  79. package/dist/i18n/shared-props.d.ts +7 -0
  80. package/dist/i18n/shared-props.js +25 -0
  81. package/dist/i18n/shared-props.js.map +1 -0
  82. package/dist/icons/icon-renderer.js.map +1 -1
  83. package/dist/index.d.ts +5 -3
  84. package/dist/index.js +7 -6
  85. package/dist/lattice.css +119 -57
  86. package/dist/layout/components/breadcrumbs.js +3 -1
  87. package/dist/layout/components/breadcrumbs.js.map +1 -1
  88. package/dist/layout/components/callouts.js +1 -1
  89. package/dist/lib/color.d.ts +9 -0
  90. package/dist/lib/color.js +48 -0
  91. package/dist/lib/color.js.map +1 -0
  92. package/dist/notifications/api.d.ts +6 -6
  93. package/dist/notifications/api.js.map +1 -1
  94. package/dist/notifications/types.d.ts +2 -10
  95. package/dist/provider-base.js +1 -1
  96. package/dist/realtime/build-effects.js +5 -5
  97. package/dist/realtime/build-effects.js.map +1 -1
  98. package/dist/realtime/listeners.d.ts +2 -2
  99. package/dist/realtime/listeners.js.map +1 -1
  100. package/dist/realtime/subscriptions.d.ts +2 -2
  101. package/dist/realtime/subscriptions.js.map +1 -1
  102. package/dist/registry.js.map +1 -1
  103. package/dist/table/components/bulk-bar.js +5 -5
  104. package/dist/table/components/bulk-bar.js.map +1 -1
  105. package/dist/table/components/cells/badge-cell.js +4 -1
  106. package/dist/table/components/cells/badge-cell.js.map +1 -1
  107. package/dist/table/components/cells/boolean-cell.js +3 -1
  108. package/dist/table/components/cells/boolean-cell.js.map +1 -1
  109. package/dist/table/components/cells/icon-cell.js +5 -2
  110. package/dist/table/components/cells/icon-cell.js.map +1 -1
  111. package/dist/table/components/cells/image-cell.js +8 -9
  112. package/dist/table/components/cells/image-cell.js.map +1 -1
  113. package/dist/table/components/cells/money-cell.js +9 -1
  114. package/dist/table/components/cells/money-cell.js.map +1 -1
  115. package/dist/table/components/cells/number-cell.js +9 -1
  116. package/dist/table/components/cells/number-cell.js.map +1 -1
  117. package/dist/table/components/cells/text-cell.js +4 -1
  118. package/dist/table/components/cells/text-cell.js.map +1 -1
  119. package/dist/table/components/column-filter-control.js +25 -32
  120. package/dist/table/components/column-filter-control.js.map +1 -1
  121. package/dist/table/components/column-header.d.ts +3 -3
  122. package/dist/table/components/column-header.js +5 -3
  123. package/dist/table/components/column-header.js.map +1 -1
  124. package/dist/table/components/column-visibility-menu.js +6 -11
  125. package/dist/table/components/column-visibility-menu.js.map +1 -1
  126. package/dist/table/components/filter-bar.d.ts +2 -2
  127. package/dist/table/components/filter-bar.js +13 -22
  128. package/dist/table/components/filter-bar.js.map +1 -1
  129. package/dist/table/components/filter-controls.d.ts +2 -1
  130. package/dist/table/components/filter-controls.js +36 -35
  131. package/dist/table/components/filter-controls.js.map +1 -1
  132. package/dist/table/components/filter-value-input.d.ts +0 -1
  133. package/dist/table/components/filter-value-input.js +21 -20
  134. package/dist/table/components/filter-value-input.js.map +1 -1
  135. package/dist/table/components/pagination.js +16 -20
  136. package/dist/table/components/pagination.js.map +1 -1
  137. package/dist/table/components/sort-bar.d.ts +3 -3
  138. package/dist/table/components/sort-bar.js +11 -12
  139. package/dist/table/components/sort-bar.js.map +1 -1
  140. package/dist/table/components/table-search.d.ts +9 -0
  141. package/dist/table/components/table-search.js +75 -0
  142. package/dist/table/components/table-search.js.map +1 -0
  143. package/dist/table/components/table.js +72 -19
  144. package/dist/table/components/table.js.map +1 -1
  145. package/dist/table/hooks/use-expanded-rows.d.ts +8 -0
  146. package/dist/table/hooks/use-expanded-rows.js +24 -0
  147. package/dist/table/hooks/use-expanded-rows.js.map +1 -0
  148. package/dist/table/hooks/use-table.d.ts +5 -3
  149. package/dist/table/hooks/use-table.js +56 -44
  150. package/dist/table/hooks/use-table.js.map +1 -1
  151. package/dist/table/lib/payload.d.ts +3 -2
  152. package/dist/table/lib/payload.js +16 -10
  153. package/dist/table/lib/payload.js.map +1 -1
  154. package/dist/table/lib/query.d.ts +5 -5
  155. package/dist/table/lib/query.js +20 -18
  156. package/dist/table/lib/query.js.map +1 -1
  157. package/dist/table/types.d.ts +3 -26
  158. package/dist/toast/callout.js +1 -3
  159. package/dist/toast/callout.js.map +1 -1
  160. package/dist/toast/toast.d.ts +1 -5
  161. package/dist/toast/toast.js +2 -7
  162. package/dist/toast/toast.js.map +1 -1
  163. package/dist/toast/toaster.js +2 -2
  164. package/dist/toast/toaster.js.map +1 -1
  165. package/dist/types/generated.d.ts +263 -188
  166. package/dist/ui/affix-group.d.ts +2 -1
  167. package/dist/ui/affix-group.js +9 -7
  168. package/dist/ui/affix-group.js.map +1 -1
  169. package/dist/ui/avatar.d.ts +3 -0
  170. package/dist/ui/avatar.js +52 -0
  171. package/dist/ui/avatar.js.map +1 -0
  172. package/dist/ui/button.d.ts +3 -1
  173. package/dist/ui/button.js +10 -3
  174. package/dist/ui/button.js.map +1 -1
  175. package/dist/ui/chart-view.js +161 -11
  176. package/dist/ui/chart-view.js.map +1 -1
  177. package/dist/ui/click-behavior.d.ts +1 -1
  178. package/dist/ui/click-behavior.js.map +1 -1
  179. package/dist/ui/collapsible.js +1 -1
  180. package/dist/ui/color-picker.d.ts +8 -0
  181. package/dist/ui/color-picker.js +59 -0
  182. package/dist/ui/color-picker.js.map +1 -0
  183. package/dist/ui/combobox.d.ts +11 -2
  184. package/dist/ui/combobox.js +51 -8
  185. package/dist/ui/combobox.js.map +1 -1
  186. package/dist/ui/copyable-text.d.ts +7 -0
  187. package/dist/ui/copyable-text.js +21 -13
  188. package/dist/ui/copyable-text.js.map +1 -1
  189. package/dist/ui/fragment.js +1 -1
  190. package/dist/ui/fragment.js.map +1 -1
  191. package/dist/ui/heading.js +39 -21
  192. package/dist/ui/heading.js.map +1 -1
  193. package/dist/ui/icon-button.d.ts +22 -0
  194. package/dist/ui/icon-button.js +51 -0
  195. package/dist/ui/icon-button.js.map +1 -0
  196. package/dist/ui/icon.js +10 -11
  197. package/dist/ui/icon.js.map +1 -1
  198. package/dist/ui/image-preview.d.ts +12 -0
  199. package/dist/ui/image-preview.js +65 -0
  200. package/dist/ui/image-preview.js.map +1 -0
  201. package/dist/ui/image.d.ts +3 -0
  202. package/dist/ui/image.js +21 -0
  203. package/dist/ui/image.js.map +1 -0
  204. package/dist/ui/index.d.ts +2 -1
  205. package/dist/ui/index.js +4 -3
  206. package/dist/ui/info-tooltip.js +1 -1
  207. package/dist/ui/info-tooltip.js.map +1 -1
  208. package/dist/ui/input.d.ts +3 -1
  209. package/dist/ui/input.js +2 -2
  210. package/dist/ui/input.js.map +1 -1
  211. package/dist/ui/modal.js +14 -2
  212. package/dist/ui/modal.js.map +1 -1
  213. package/dist/ui/native-select.d.ts +10 -0
  214. package/dist/ui/native-select.js +23 -0
  215. package/dist/ui/native-select.js.map +1 -0
  216. package/dist/ui/plugin.js +11 -1
  217. package/dist/ui/plugin.js.map +1 -1
  218. package/dist/ui/progress.d.ts +3 -0
  219. package/dist/ui/progress.js +114 -0
  220. package/dist/ui/progress.js.map +1 -0
  221. package/dist/ui/section.js +1 -1
  222. package/dist/ui/separator.d.ts +3 -0
  223. package/dist/ui/separator.js +15 -0
  224. package/dist/ui/separator.js.map +1 -0
  225. package/dist/ui/text.js +5 -10
  226. package/dist/ui/text.js.map +1 -1
  227. package/dist/ui/tooltip.js +1 -1
  228. package/dist/ui/tree-context.d.ts +33 -0
  229. package/dist/ui/tree-context.js +137 -0
  230. package/dist/ui/tree-context.js.map +1 -0
  231. package/dist/ui/tree.d.ts +19 -0
  232. package/dist/ui/tree.js +187 -0
  233. package/dist/ui/tree.js.map +1 -0
  234. package/dist-standalone/chunks/chart-view-wJmnVu7L.js +36 -0
  235. package/dist-standalone/chunks/date-picker-field-VbH9xrQO.js +1 -0
  236. package/dist-standalone/chunks/dist-PaT1gYi_.js +2 -0
  237. package/dist-standalone/chunks/jsx-runtime-Drll_-DD.js +1 -0
  238. package/dist-standalone/chunks/locale-CXqr7LPR.js +1 -0
  239. package/dist-standalone/chunks/notifications-echo-DsfNc4Ge.js +1 -0
  240. package/dist-standalone/chunks/page-props-AIa5UR6n.js +1 -0
  241. package/dist-standalone/chunks/react-B8IZ02wI.js +1 -0
  242. package/dist-standalone/chunks/rich-editor-field-DLQTyt36.js +154 -0
  243. package/dist-standalone/chunks/subscriptions-DY2tj9Im.js +1 -0
  244. package/dist-standalone/chunks/test-id-Dh3L6Gfh.js +1 -0
  245. package/dist-standalone/chunks/use-effect-dispatcher-feEs0GCP.js +97 -0
  246. package/dist-standalone/chunks/with-selector-BwDoCrJJ.js +1 -0
  247. package/dist-standalone/lattice.css +3 -0
  248. package/dist-standalone/lattice.js +66 -0
  249. package/dist-standalone/manifest.json +20 -0
  250. package/dist-standalone/sprite.svg +1 -0
  251. package/package.json +11 -2
  252. package/resources/icons/code-xml.svg +17 -0
  253. package/resources/icons/heading.svg +17 -0
  254. package/dist/action/hooks/use-click-behavior.d.ts +0 -3
  255. package/dist/action/hooks/use-click-behavior.js +0 -3
package/README.md CHANGED
@@ -30,7 +30,7 @@ composer require lattice-php/lattice
30
30
  npm install @lattice-php/lattice
31
31
  ```
32
32
 
33
- See [Installation](https://latticephp.com/introduction/installation/) for the full setup.
33
+ See [Installation](https://latticephp.com/introduction/installation/) for the full setup. No Node toolchain? Lattice also ships prebuilt assets — publish them with `php artisan lattice:assets` and skip the npm step entirely; see [No-build setup](https://latticephp.com/introduction/no-build/).
34
34
 
35
35
  ## Documentation
36
36
 
@@ -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 { LATTICE_REF_HEADER, withRefHeader } from "./component-ref.js";
4
- import { withHeaders } from "./headers.js";
3
+ import { toNodes } from "./nodes.js";
5
4
  import { CollapsedContext, useCollapsed } from "./collapsed-context.js";
6
5
  import { RenderNode, Renderer } from "./renderer.js";
6
+ import { LATTICE_REF_HEADER, withRefHeader } from "./component-ref.js";
7
+ import { withHeaders } from "./headers.js";
7
8
  import { ApiError, apiFetch, apiJson, clearRemoteTokenCache, invalidateRemoteToken, remoteFetch, remoteJson, remoteToken } from "./api.js";
8
- import { toNodes } from "./nodes.js";
9
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 };
@@ -4,3 +4,8 @@ import { Node } from './types.js';
4
4
  * anything that isn't an object carrying a string `type`.
5
5
  */
6
6
  export declare function toNodes(value: unknown): Node[];
7
+ /**
8
+ * Stable list key for a node: the reconciliation key, then the id, then a
9
+ * type-scoped index fallback so keyless template children never collide.
10
+ */
11
+ export declare function nodeKey(node: Node, index: number): string;
@@ -7,7 +7,14 @@ function toNodes(value) {
7
7
  if (!Array.isArray(value)) return [];
8
8
  return value.filter((node) => typeof node === "object" && node !== null && "type" in node && typeof node.type === "string");
9
9
  }
10
+ /**
11
+ * Stable list key for a node: the reconciliation key, then the id, then a
12
+ * type-scoped index fallback so keyless template children never collide.
13
+ */
14
+ function nodeKey(node, index) {
15
+ return node.key ?? node.id ?? `${node.type}-${index}`;
16
+ }
10
17
  //#endregion
11
- export { toNodes };
18
+ export { nodeKey, toNodes };
12
19
 
13
20
  //# sourceMappingURL=nodes.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"nodes.js","names":[],"sources":["../../resources/js/core/nodes.ts"],"sourcesContent":["import type { Node } from \"@lattice-php/lattice/core/types\";\n\n/**\n * Keep only the well-formed component nodes from an untyped value, dropping\n * anything that isn't an object carrying a string `type`.\n */\nexport function toNodes(value: unknown): Node[] {\n if (!Array.isArray(value)) {\n return [];\n }\n\n return value.filter(\n (node): node is Node =>\n typeof node === \"object\" && node !== null && \"type\" in node && typeof node.type === \"string\",\n );\n}\n"],"mappings":";;;;;AAMA,SAAgB,QAAQ,OAAwB;CAC9C,IAAI,CAAC,MAAM,QAAQ,KAAK,GACtB,OAAO,CAAC;CAGV,OAAO,MAAM,QACV,SACC,OAAO,SAAS,YAAY,SAAS,QAAQ,UAAU,QAAQ,OAAO,KAAK,SAAS,QACxF;AACF"}
1
+ {"version":3,"file":"nodes.js","names":[],"sources":["../../resources/js/core/nodes.ts"],"sourcesContent":["import type { Node } from \"@lattice-php/lattice/core/types\";\n\n/**\n * Keep only the well-formed component nodes from an untyped value, dropping\n * anything that isn't an object carrying a string `type`.\n */\nexport function toNodes(value: unknown): Node[] {\n if (!Array.isArray(value)) {\n return [];\n }\n\n return value.filter(\n (node): node is Node =>\n typeof node === \"object\" && node !== null && \"type\" in node && typeof node.type === \"string\",\n );\n}\n\n/**\n * Stable list key for a node: the reconciliation key, then the id, then a\n * type-scoped index fallback so keyless template children never collide.\n */\nexport function nodeKey(node: Node, index: number): string {\n return node.key ?? node.id ?? `${node.type}-${index}`;\n}\n"],"mappings":";;;;;AAMA,SAAgB,QAAQ,OAAwB;CAC9C,IAAI,CAAC,MAAM,QAAQ,KAAK,GACtB,OAAO,CAAC;CAGV,OAAO,MAAM,QACV,SACC,OAAO,SAAS,YAAY,SAAS,QAAQ,UAAU,QAAQ,OAAO,KAAK,SAAS,QACxF;AACF;;;;;AAMA,SAAgB,QAAQ,MAAY,OAAuB;CACzD,OAAO,KAAK,OAAO,KAAK,MAAM,GAAG,KAAK,KAAK,GAAG;AAChD"}
@@ -1,4 +1,5 @@
1
1
  import { useComponentRegistry } from "./registry-context.js";
2
+ import { nodeKey } from "./nodes.js";
2
3
  import { useCollapsed } from "./collapsed-context.js";
3
4
  import { Suspense, memo } from "react";
4
5
  import { jsx, jsxs } from "react/jsx-runtime";
@@ -46,9 +47,6 @@ function MissingComponent({ node }) {
46
47
  })]
47
48
  });
48
49
  }
49
- function nodeKey(node, index) {
50
- return node.key ?? node.id ?? `${node.type}-${index}`;
51
- }
52
50
  function Renderer({ nodes }) {
53
51
  return nodes.map((node, index) => /* @__PURE__ */ jsx(NodeRenderer, { node }, nodeKey(node, index)));
54
52
  }
@@ -1 +1 @@
1
- {"version":3,"file":"renderer.js","names":[],"sources":["../../resources/js/core/renderer.tsx"],"sourcesContent":["import { memo, Suspense } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { Node } from \"./types\";\nimport { useCollapsed } from \"./collapsed-context\";\nimport { useComponentRegistry } from \"./registry-context\";\n\nconst warnedMissingTypes = new Set<string>();\n\nfunction warnMissingComponent(type: string): void {\n if (!import.meta.env.DEV || warnedMissingTypes.has(type)) {\n return;\n }\n\n warnedMissingTypes.add(type);\n console.warn(\n `[lattice] No component registered for node type \"${type}\" — Lattice rendered a fallback ` +\n \"placeholder. Likely causes: your app registry was not passed to \" +\n \"createLatticeApp({ registry }), or the registry key does not match the PHP \" +\n \"#[AsComponent]/AsField type.\",\n );\n}\n\nfunction MissingComponentIcon() {\n return (\n <svg\n aria-hidden=\"true\"\n className=\"size-lt-icon-md shrink-0\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth=\"2\"\n viewBox=\"0 0 24 24\"\n >\n <rect height=\"18\" rx=\"2\" strokeDasharray=\"4 3\" width=\"18\" x=\"3\" y=\"3\" />\n <path d=\"M12 8v4\" />\n <path d=\"M12 16h.01\" />\n </svg>\n );\n}\n\n/**\n * Fallback for a node whose type has no registered renderer. Always renders a\n * visible, muted marker — icon-only survives tight spots like table cells — so\n * the gap is never invisible. Shows the type inline in development; keeps it\n * screen-reader-only (plus a hover tooltip) in production.\n */\nfunction MissingComponent({ node }: { node: Node }) {\n warnMissingComponent(node.type);\n\n const label = `Missing component: ${node.type}`;\n\n return (\n <span\n className=\"inline-flex items-center gap-1.5 align-middle text-lt-muted-fg\"\n data-lattice-missing-component={node.type}\n title={label}\n >\n <MissingComponentIcon />\n <span className={import.meta.env.DEV ? \"text-sm\" : \"sr-only\"}>{label}</span>\n </span>\n );\n}\n\nfunction nodeKey(node: Node, index: number): string {\n return node.key ?? node.id ?? `${node.type}-${index}`;\n}\n\nexport function Renderer({ nodes }: { nodes: Node[] }): ReactNode {\n return nodes.map((node, index) => <NodeRenderer key={nodeKey(node, index)} node={node} />);\n}\n\nexport function RenderNode({ node }: { node: Node }): ReactNode {\n return <NodeRenderer node={node} />;\n}\n\nconst NodeRenderer = memo(function NodeRenderer({ node }: { node: Node }) {\n const collapsed = useCollapsed();\n const registry = useComponentRegistry();\n const registration = registry[node.type];\n\n if (collapsed && node.props?.hideWhenCollapsed === true) {\n return null;\n }\n\n if (!registration) {\n return <MissingComponent node={node} />;\n }\n\n const Component = registration.component;\n const children = node.schema?.length ? <Renderer nodes={node.schema} /> : null;\n const renderedComponent = <Component node={node}>{children}</Component>;\n\n if (registration.mode === \"lazy\") {\n const FallbackComponent = registration.fallback;\n const fallback = FallbackComponent ? (\n <FallbackComponent node={node}>{null}</FallbackComponent>\n ) : null;\n\n return <Suspense fallback={fallback}>{renderedComponent}</Suspense>;\n }\n\n return renderedComponent;\n});\n"],"mappings":";;;;;AAsBA,SAAS,uBAAuB;CAC9B,OACE,qBAAC,OAAD;EACE,eAAY;EACZ,WAAU;EACV,MAAK;EACL,QAAO;EACP,eAAc;EACd,gBAAe;EACf,aAAY;EACZ,SAAQ;YARV;GAUE,oBAAC,QAAD;IAAM,QAAO;IAAK,IAAG;IAAI,iBAAgB;IAAM,OAAM;IAAK,GAAE;IAAI,GAAE;GAAK,CAAA;GACvE,oBAAC,QAAD,EAAM,GAAE,UAAW,CAAA;GACnB,oBAAC,QAAD,EAAM,GAAE,aAAc,CAAA;EACnB;;AAET;;;;;;;AAQA,SAAS,iBAAiB,EAAE,QAAwB;CAClD,KAA0B;CAE1B,MAAM,QAAQ,sBAAsB,KAAK;CAEzC,OACE,qBAAC,QAAD;EACE,WAAU;EACV,kCAAgC,KAAK;EACrC,OAAO;YAHT,CAKE,oBAAC,sBAAD,CAAuB,CAAA,GACvB,oBAAC,QAAD;GAAM,WAA6C;aAAY;EAAY,CAAA,CACvE;;AAEV;AAEA,SAAS,QAAQ,MAAY,OAAuB;CAClD,OAAO,KAAK,OAAO,KAAK,MAAM,GAAG,KAAK,KAAK,GAAG;AAChD;AAEA,SAAgB,SAAS,EAAE,SAAuC;CAChE,OAAO,MAAM,KAAK,MAAM,UAAU,oBAAC,cAAD,EAA+C,KAAO,GAAnC,QAAQ,MAAM,KAAK,CAAgB,CAAC;AAC3F;AAEA,SAAgB,WAAW,EAAE,QAAmC;CAC9D,OAAO,oBAAC,cAAD,EAAoB,KAAO,CAAA;AACpC;AAEA,IAAM,eAAe,KAAK,SAAS,aAAa,EAAE,QAAwB;CACxE,MAAM,YAAY,aAAa;CAE/B,MAAM,eADW,qBACI,EAAS,KAAK;CAEnC,IAAI,aAAa,KAAK,OAAO,sBAAsB,MACjD,OAAO;CAGT,IAAI,CAAC,cACH,OAAO,oBAAC,kBAAD,EAAwB,KAAO,CAAA;CAGxC,MAAM,YAAY,aAAa;CAE/B,MAAM,oBAAoB,oBAAC,WAAD;EAAiB;EAAO,UADjC,KAAK,QAAQ,SAAS,oBAAC,UAAD,EAAU,OAAO,KAAK,OAAS,CAAA,IAAI;CACJ,CAAA;CAEtE,IAAI,aAAa,SAAS,QAAQ;EAChC,MAAM,oBAAoB,aAAa;EAKvC,OAAO,oBAAC,UAAD;GAAoB,UAJV,oBACf,oBAAC,mBAAD;IAAyB;cAAO;GAAwB,CAAA,IACtD;aAEkC;EAA4B,CAAA;CACpE;CAEA,OAAO;AACT,CAAC"}
1
+ {"version":3,"file":"renderer.js","names":[],"sources":["../../resources/js/core/renderer.tsx"],"sourcesContent":["import { memo, Suspense } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { Node } from \"./types\";\nimport { nodeKey } from \"./nodes\";\nimport { useCollapsed } from \"./collapsed-context\";\nimport { useComponentRegistry } from \"./registry-context\";\n\nconst warnedMissingTypes = new Set<string>();\n\nfunction warnMissingComponent(type: string): void {\n if (!import.meta.env.DEV || warnedMissingTypes.has(type)) {\n return;\n }\n\n warnedMissingTypes.add(type);\n console.warn(\n `[lattice] No component registered for node type \"${type}\" — Lattice rendered a fallback ` +\n \"placeholder. Likely causes: your app registry was not passed to \" +\n \"createLatticeApp({ registry }), or the registry key does not match the PHP \" +\n \"#[AsComponent]/AsField type.\",\n );\n}\n\nfunction MissingComponentIcon() {\n return (\n <svg\n aria-hidden=\"true\"\n className=\"size-lt-icon-md shrink-0\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth=\"2\"\n viewBox=\"0 0 24 24\"\n >\n <rect height=\"18\" rx=\"2\" strokeDasharray=\"4 3\" width=\"18\" x=\"3\" y=\"3\" />\n <path d=\"M12 8v4\" />\n <path d=\"M12 16h.01\" />\n </svg>\n );\n}\n\n/**\n * Fallback for a node whose type has no registered renderer. Always renders a\n * visible, muted marker — icon-only survives tight spots like table cells — so\n * the gap is never invisible. Shows the type inline in development; keeps it\n * screen-reader-only (plus a hover tooltip) in production.\n */\nfunction MissingComponent({ node }: { node: Node }) {\n warnMissingComponent(node.type);\n\n const label = `Missing component: ${node.type}`;\n\n return (\n <span\n className=\"inline-flex items-center gap-1.5 align-middle text-lt-muted-fg\"\n data-lattice-missing-component={node.type}\n title={label}\n >\n <MissingComponentIcon />\n <span className={import.meta.env.DEV ? \"text-sm\" : \"sr-only\"}>{label}</span>\n </span>\n );\n}\n\nexport function Renderer({ nodes }: { nodes: Node[] }): ReactNode {\n return nodes.map((node, index) => <NodeRenderer key={nodeKey(node, index)} node={node} />);\n}\n\nexport function RenderNode({ node }: { node: Node }): ReactNode {\n return <NodeRenderer node={node} />;\n}\n\nconst NodeRenderer = memo(function NodeRenderer({ node }: { node: Node }) {\n const collapsed = useCollapsed();\n const registry = useComponentRegistry();\n const registration = registry[node.type];\n\n if (collapsed && node.props?.hideWhenCollapsed === true) {\n return null;\n }\n\n if (!registration) {\n return <MissingComponent node={node} />;\n }\n\n const Component = registration.component;\n const children = node.schema?.length ? <Renderer nodes={node.schema} /> : null;\n const renderedComponent = <Component node={node}>{children}</Component>;\n\n if (registration.mode === \"lazy\") {\n const FallbackComponent = registration.fallback;\n const fallback = FallbackComponent ? (\n <FallbackComponent node={node}>{null}</FallbackComponent>\n ) : null;\n\n return <Suspense fallback={fallback}>{renderedComponent}</Suspense>;\n }\n\n return renderedComponent;\n});\n"],"mappings":";;;;;;AAuBA,SAAS,uBAAuB;CAC9B,OACE,qBAAC,OAAD;EACE,eAAY;EACZ,WAAU;EACV,MAAK;EACL,QAAO;EACP,eAAc;EACd,gBAAe;EACf,aAAY;EACZ,SAAQ;YARV;GAUE,oBAAC,QAAD;IAAM,QAAO;IAAK,IAAG;IAAI,iBAAgB;IAAM,OAAM;IAAK,GAAE;IAAI,GAAE;GAAK,CAAA;GACvE,oBAAC,QAAD,EAAM,GAAE,UAAW,CAAA;GACnB,oBAAC,QAAD,EAAM,GAAE,aAAc,CAAA;EACnB;;AAET;;;;;;;AAQA,SAAS,iBAAiB,EAAE,QAAwB;CAClD,KAA0B;CAE1B,MAAM,QAAQ,sBAAsB,KAAK;CAEzC,OACE,qBAAC,QAAD;EACE,WAAU;EACV,kCAAgC,KAAK;EACrC,OAAO;YAHT,CAKE,oBAAC,sBAAD,CAAuB,CAAA,GACvB,oBAAC,QAAD;GAAM,WAA6C;aAAY;EAAY,CAAA,CACvE;;AAEV;AAEA,SAAgB,SAAS,EAAE,SAAuC;CAChE,OAAO,MAAM,KAAK,MAAM,UAAU,oBAAC,cAAD,EAA+C,KAAO,GAAnC,QAAQ,MAAM,KAAK,CAAgB,CAAC;AAC3F;AAEA,SAAgB,WAAW,EAAE,QAAmC;CAC9D,OAAO,oBAAC,cAAD,EAAoB,KAAO,CAAA;AACpC;AAEA,IAAM,eAAe,KAAK,SAAS,aAAa,EAAE,QAAwB;CACxE,MAAM,YAAY,aAAa;CAE/B,MAAM,eADW,qBACI,EAAS,KAAK;CAEnC,IAAI,aAAa,KAAK,OAAO,sBAAsB,MACjD,OAAO;CAGT,IAAI,CAAC,cACH,OAAO,oBAAC,kBAAD,EAAwB,KAAO,CAAA;CAGxC,MAAM,YAAY,aAAa;CAE/B,MAAM,oBAAoB,oBAAC,WAAD;EAAiB;EAAO,UADjC,KAAK,QAAQ,SAAS,oBAAC,UAAD,EAAU,OAAO,KAAK,OAAS,CAAA,IAAI;CACJ,CAAA;CAEtE,IAAI,aAAa,SAAS,QAAQ;EAChC,MAAM,oBAAoB,aAAa;EAKvC,OAAO,oBAAC,UAAD;GAAoB,UAJV,oBACf,oBAAC,mBAAD;IAAyB;cAAO;GAAwB,CAAA,IACtD;aAEkC;EAA4B,CAAA;CACpE;CAEA,OAAO;AACT,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import { ComponentType as ReactComponentType, ReactNode } from 'react';
2
- import { ComponentPropsMap, NodeType, Option, PageContainer as KnownPageContainer, WireNode, ListenerPayload } from '../types/generated.js';
2
+ import { ComponentPropsMap, NodeType, Option, PageContainer as KnownPageContainer, WireNode, Listen } from '../types/generated.js';
3
3
  export type { KnownPageContainer, NodeType, Option, WireNode };
4
4
  export type NodeProps = Record<string, unknown>;
5
5
  export type CommonNodeProps = {
@@ -23,11 +23,23 @@ export interface ComponentProps {
23
23
  */
24
24
  export type ResolveProps<TAugment, TBuiltins, TType extends string, TFallback> = TType extends keyof TAugment ? TAugment[TType] : TType extends keyof TBuiltins ? TBuiltins[TType] : TFallback;
25
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
+ };
26
38
  /**
27
39
  * Built-in `props` is required, not optional — the wire serializes the full prop
28
- * object, so reads need no optional chaining. Unknown types fall back to `WireNode`.
40
+ * object, so reads need no optional chaining. Unknown types fall back to `LooseNode`.
29
41
  */
30
- export type NodeOfType<TType extends string = string> = string extends TType ? WireNode : {
42
+ export type NodeOfType<TType extends string = string> = string extends TType ? LooseNode : {
31
43
  id?: string;
32
44
  key?: string;
33
45
  props: PropsOf<TType>;
@@ -57,7 +69,7 @@ export type PagePayload = {
57
69
  breadcrumbs: PageBreadcrumb[];
58
70
  container: PageContainer;
59
71
  layout: LayoutPayload | null;
60
- listeners?: ListenerPayload[];
72
+ listeners?: Listen[];
61
73
  schema: Schema;
62
74
  title: string | null;
63
75
  };
@@ -1,9 +1,14 @@
1
+ import { Page as InertiaPage } from '@inertiajs/core';
1
2
  import { createInertiaApp } from '@inertiajs/react';
2
3
  import { ReactElement } from 'react';
3
4
  import { Plugin, Registry } from './core/registry.js';
4
5
  import { SpriteValue } from './icons/sprite.js';
5
6
  import { CreateLayoutResolverOptions, PageModules } from './inertia.js';
6
7
  type InertiaAppOptions = NonNullable<Parameters<typeof createInertiaApp>[0]>;
8
+ export type CreateLatticeAppI18nOptions = {
9
+ /** i18next namespaces to load, e.g. `["lattice", "app"]`. Defaults to the package's own. */
10
+ namespaces?: string[];
11
+ };
7
12
  export type CreateLatticeAppOptions = Omit<InertiaAppOptions, "resolve" | "layout" | "setup" | "withApp" | "pages"> & {
8
13
  registry?: Registry;
9
14
  /**
@@ -16,12 +21,35 @@ export type CreateLatticeAppOptions = Omit<InertiaAppOptions, "resolve" | "layou
16
21
  /** Normal Inertia page modules, e.g. `import.meta.glob('./pages/**\/*.tsx')`. */
17
22
  pages?: PageModules;
18
23
  defaultLayout?: CreateLayoutResolverOptions["defaultLayout"];
24
+ /**
25
+ * Lattice's i18n bootstrap, on by default: when the backend shares the
26
+ * `lattice.i18n` prop, the first render waits for the translation setup (no
27
+ * flash of untranslated fallbacks), `LocaleReload` re-fetches the page after
28
+ * a locale switch, and every visit carries the locale/timezone headers. The
29
+ * i18next chunk only loads when the backend actually shares the prop. Pass
30
+ * `false` to opt out entirely.
31
+ */
32
+ i18n?: CreateLatticeAppI18nOptions | false;
33
+ /**
34
+ * App bootstrap that needs the initial page — e.g. `configureEcho` from a
35
+ * shared connection prop. Runs on the client before the first render; a
36
+ * returned promise delays that render until it resolves.
37
+ */
38
+ boot?: (context: {
39
+ page: InertiaPage;
40
+ }) => void | Promise<void>;
41
+ /**
42
+ * Compose providers or siblings around the app. Applied inside the Lattice
43
+ * Provider, so the registry, sprite, and toaster contexts are available.
44
+ */
45
+ wrap?: (app: ReactElement) => ReactElement;
19
46
  };
20
47
  /**
21
48
  * Bootstrap an Inertia app with the Lattice shell: the page/layout resolvers
22
49
  * (server-driven Lattice pages and normal Inertia pages alike), the Provider —
23
- * registry, sprite, flash toasts via Lattice's own Toaster — and theme
24
- * initialization. Forwards any other createInertiaApp option.
50
+ * registry, sprite, flash toasts via Lattice's own Toaster — theme
51
+ * initialization, and the i18n bootstrap. Forwards any other createInertiaApp
52
+ * option.
25
53
  */
26
- export declare function createLatticeApp({ registry, plugins, sprite, pages, defaultLayout, strictMode, ...inertiaOptions }?: CreateLatticeAppOptions): Promise<void | ((page: import('@inertiajs/core').Page<import('@inertiajs/core').PageProps>, renderToString: (element: ReactElement) => string) => Promise<import('@inertiajs/core').InertiaAppSSRResponse>)>;
54
+ export declare function createLatticeApp({ registry, plugins, sprite, pages, defaultLayout, strictMode, i18n, boot, wrap, ...inertiaOptions }?: CreateLatticeAppOptions): Promise<void | ((page: InertiaPage<import('@inertiajs/core').PageProps>, renderToString: (element: ReactElement) => string) => Promise<import('@inertiajs/core').InertiaAppSSRResponse>)>;
27
55
  export {};
@@ -1,32 +1,70 @@
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 { createLayoutResolver, createPageResolver } from "./inertia.js";
4
+ import { i18nConfigFromPageProps } from "./i18n/shared-props.js";
5
+ import { LocaleReload } from "./i18n/locale-reload.js";
6
+ import { createLayoutResolver, createPageResolver, withVisitHeaders } from "./inertia.js";
5
7
  import { ProviderBase } from "./provider-base.js";
6
8
  import { registry } from "./registry.js";
7
9
  import { createInertiaApp } from "@inertiajs/react";
8
- import { jsx } from "react/jsx-runtime";
10
+ import { useEffect, useState } from "react";
11
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
9
12
  //#region resources/js/create-app.tsx
13
+ function AwaitReady({ ready, children }) {
14
+ const [isReady, setReady] = useState(false);
15
+ useEffect(() => {
16
+ let active = true;
17
+ const reveal = () => {
18
+ if (active) setReady(true);
19
+ };
20
+ ready.then(reveal, reveal);
21
+ return () => {
22
+ active = false;
23
+ };
24
+ }, [ready]);
25
+ return isReady ? children : null;
26
+ }
10
27
  /**
11
28
  * Bootstrap an Inertia app with the Lattice shell: the page/layout resolvers
12
29
  * (server-driven Lattice pages and normal Inertia pages alike), the Provider —
13
- * registry, sprite, flash toasts via Lattice's own Toaster — and theme
14
- * initialization. Forwards any other createInertiaApp option.
30
+ * registry, sprite, flash toasts via Lattice's own Toaster — theme
31
+ * initialization, and the i18n bootstrap. Forwards any other createInertiaApp
32
+ * option.
15
33
  */
16
- function createLatticeApp({ registry: registry$1, plugins, sprite, pages = {}, defaultLayout, strictMode = true, ...inertiaOptions } = {}) {
34
+ function createLatticeApp({ registry: registry$1, plugins, sprite, pages = {}, defaultLayout, strictMode = true, i18n, boot, wrap, ...inertiaOptions } = {}) {
17
35
  const baseRegistry = registry$1 ?? registry;
18
36
  const activeRegistry = plugins && plugins.length > 0 ? extendRegistry(baseRegistry, ...plugins) : baseRegistry;
19
37
  setDefaultRegistry(activeRegistry);
38
+ const i18nEnabled = i18n !== false;
39
+ const userVisitOptions = inertiaOptions.defaults?.visitOptions;
40
+ const defaults = i18nEnabled ? {
41
+ ...inertiaOptions.defaults,
42
+ visitOptions: (href, options) => withVisitHeaders(href, userVisitOptions?.(href, options) ?? options)
43
+ } : inertiaOptions.defaults;
20
44
  const app = createInertiaApp({
21
45
  ...inertiaOptions,
46
+ defaults,
22
47
  strictMode,
23
48
  resolve: createPageResolver(pages),
24
49
  layout: createLayoutResolver({ defaultLayout }),
25
- withApp: (node) => /* @__PURE__ */ jsx(ProviderBase, {
26
- registry: activeRegistry,
27
- sprite,
28
- children: node
29
- })
50
+ withApp: (node, { ssr, page }) => {
51
+ const pending = [];
52
+ if (!ssr) {
53
+ if (i18nEnabled && i18nConfigFromPageProps(page.props) !== void 0) pending.push(import("./i18n/page-props.js").then((module) => module.configureI18nFromPageProps(page.props, i18n ?? {})));
54
+ const booted = boot?.({ page });
55
+ if (booted) pending.push(booted);
56
+ }
57
+ const inner = /* @__PURE__ */ jsxs(Fragment$1, { children: [node, i18nEnabled ? /* @__PURE__ */ jsx(LocaleReload, {}) : null] });
58
+ const shell = /* @__PURE__ */ jsx(ProviderBase, {
59
+ registry: activeRegistry,
60
+ sprite,
61
+ children: wrap ? wrap(inner) : inner
62
+ });
63
+ return pending.length > 0 ? /* @__PURE__ */ jsx(AwaitReady, {
64
+ ready: Promise.all(pending),
65
+ children: shell
66
+ }) : shell;
67
+ }
30
68
  });
31
69
  initializeTheme();
32
70
  return app;
@@ -1 +1 @@
1
- {"version":3,"file":"create-app.js","names":[],"sources":["../resources/js/create-app.tsx"],"sourcesContent":["import { createInertiaApp } from \"@inertiajs/react\";\nimport type { ReactElement } from \"react\";\nimport { initializeTheme } from \"./appearance\";\nimport { extendRegistry, type Plugin, type Registry } from \"./core/registry\";\nimport { setDefaultRegistry } from \"./core/registry-context\";\nimport type { SpriteValue } from \"./icons/sprite\";\nimport {\n createLayoutResolver,\n createPageResolver,\n type CreateLayoutResolverOptions,\n type PageModules,\n} from \"./inertia\";\nimport { ProviderBase } from \"./provider-base\";\nimport { registry as defaultRegistry } from \"./registry\";\n\ntype InertiaAppOptions = NonNullable<Parameters<typeof createInertiaApp>[0]>;\n\nexport type CreateLatticeAppOptions = Omit<\n InertiaAppOptions,\n \"resolve\" | \"layout\" | \"setup\" | \"withApp\" | \"pages\"\n> & {\n registry?: Registry;\n /**\n * Component-package plugins to merge onto the registry — pass the\n * Composer-discovered `virtual:lattice/plugins` here to register vendor\n * components with no manual `extendRegistry` call.\n */\n plugins?: Plugin[];\n sprite?: SpriteValue;\n /** Normal Inertia page modules, e.g. `import.meta.glob('./pages/**\\/*.tsx')`. */\n pages?: PageModules;\n defaultLayout?: CreateLayoutResolverOptions[\"defaultLayout\"];\n};\n\n/**\n * Bootstrap an Inertia app with the Lattice shell: the page/layout resolvers\n * (server-driven Lattice pages and normal Inertia pages alike), the Provider —\n * registry, sprite, flash toasts via Lattice's own Toaster — and theme\n * initialization. Forwards any other createInertiaApp option.\n */\nexport function createLatticeApp({\n registry,\n plugins,\n sprite,\n pages = {},\n defaultLayout,\n strictMode = true,\n ...inertiaOptions\n}: CreateLatticeAppOptions = {}) {\n const baseRegistry = registry ?? defaultRegistry;\n const activeRegistry =\n plugins && plugins.length > 0 ? extendRegistry(baseRegistry, ...plugins) : baseRegistry;\n\n setDefaultRegistry(activeRegistry);\n\n const app = createInertiaApp({\n ...inertiaOptions,\n strictMode,\n resolve: createPageResolver(pages),\n layout: createLayoutResolver({ defaultLayout }),\n withApp: (node: ReactElement): ReactElement => (\n <ProviderBase registry={activeRegistry} sprite={sprite}>\n {node}\n </ProviderBase>\n ),\n });\n\n initializeTheme();\n\n return app;\n}\n"],"mappings":";;;;;;;;;;;;;;;AAwCA,SAAgB,iBAAiB,EAC/B,UAAA,YACA,SACA,QACA,QAAQ,CAAC,GACT,eACA,aAAa,MACb,GAAG,mBACwB,CAAC,GAAG;CAC/B,MAAM,eAAe,cAAY;CACjC,MAAM,iBACJ,WAAW,QAAQ,SAAS,IAAI,eAAe,cAAc,GAAG,OAAO,IAAI;CAE7E,mBAAmB,cAAc;CAEjC,MAAM,MAAM,iBAAiB;EAC3B,GAAG;EACH;EACA,SAAS,mBAAmB,KAAK;EACjC,QAAQ,qBAAqB,EAAE,cAAc,CAAC;EAC9C,UAAU,SACR,oBAAC,cAAD;GAAc,UAAU;GAAwB;aAC7C;EACW,CAAA;CAElB,CAAC;CAED,gBAAgB;CAEhB,OAAO;AACT"}
1
+ {"version":3,"file":"create-app.js","names":[],"sources":["../resources/js/create-app.tsx"],"sourcesContent":["import type { Page as InertiaPage, VisitOptions } from \"@inertiajs/core\";\nimport { createInertiaApp } from \"@inertiajs/react\";\nimport { useEffect, useState, type ReactElement, type ReactNode } from \"react\";\nimport { initializeTheme } from \"./appearance\";\nimport { extendRegistry, type Plugin, type Registry } from \"./core/registry\";\nimport { setDefaultRegistry } from \"./core/registry-context\";\nimport type { SpriteValue } from \"./icons/sprite\";\nimport { LocaleReload } from \"./i18n/locale-reload\";\nimport { i18nConfigFromPageProps } from \"./i18n/shared-props\";\nimport {\n createLayoutResolver,\n createPageResolver,\n withVisitHeaders,\n type CreateLayoutResolverOptions,\n type PageModules,\n} from \"./inertia\";\nimport { ProviderBase } from \"./provider-base\";\nimport { registry as defaultRegistry } from \"./registry\";\n\ntype InertiaAppOptions = NonNullable<Parameters<typeof createInertiaApp>[0]>;\n\nexport type CreateLatticeAppI18nOptions = {\n /** i18next namespaces to load, e.g. `[\"lattice\", \"app\"]`. Defaults to the package's own. */\n namespaces?: string[];\n};\n\nexport type CreateLatticeAppOptions = Omit<\n InertiaAppOptions,\n \"resolve\" | \"layout\" | \"setup\" | \"withApp\" | \"pages\"\n> & {\n registry?: Registry;\n /**\n * Component-package plugins to merge onto the registry — pass the\n * Composer-discovered `virtual:lattice/plugins` here to register vendor\n * components with no manual `extendRegistry` call.\n */\n plugins?: Plugin[];\n sprite?: SpriteValue;\n /** Normal Inertia page modules, e.g. `import.meta.glob('./pages/**\\/*.tsx')`. */\n pages?: PageModules;\n defaultLayout?: CreateLayoutResolverOptions[\"defaultLayout\"];\n /**\n * Lattice's i18n bootstrap, on by default: when the backend shares the\n * `lattice.i18n` prop, the first render waits for the translation setup (no\n * flash of untranslated fallbacks), `LocaleReload` re-fetches the page after\n * a locale switch, and every visit carries the locale/timezone headers. The\n * i18next chunk only loads when the backend actually shares the prop. Pass\n * `false` to opt out entirely.\n */\n i18n?: CreateLatticeAppI18nOptions | false;\n /**\n * App bootstrap that needs the initial page — e.g. `configureEcho` from a\n * shared connection prop. Runs on the client before the first render; a\n * returned promise delays that render until it resolves.\n */\n boot?: (context: { page: InertiaPage }) => void | Promise<void>;\n /**\n * Compose providers or siblings around the app. Applied inside the Lattice\n * Provider, so the registry, sprite, and toaster contexts are available.\n */\n wrap?: (app: ReactElement) => ReactElement;\n};\n\n// Fail-open: a failed bootstrap renders the app anyway (with fallback strings)\n// rather than a blank page.\nfunction AwaitReady({ ready, children }: { ready: Promise<unknown>; children: ReactNode }) {\n const [isReady, setReady] = useState(false);\n\n useEffect(() => {\n let active = true;\n const reveal = (): void => {\n if (active) {\n setReady(true);\n }\n };\n\n void ready.then(reveal, reveal);\n\n return () => {\n active = false;\n };\n }, [ready]);\n\n return isReady ? children : null;\n}\n\n/**\n * Bootstrap an Inertia app with the Lattice shell: the page/layout resolvers\n * (server-driven Lattice pages and normal Inertia pages alike), the Provider —\n * registry, sprite, flash toasts via Lattice's own Toaster — theme\n * initialization, and the i18n bootstrap. Forwards any other createInertiaApp\n * option.\n */\nexport function createLatticeApp({\n registry,\n plugins,\n sprite,\n pages = {},\n defaultLayout,\n strictMode = true,\n i18n,\n boot,\n wrap,\n ...inertiaOptions\n}: CreateLatticeAppOptions = {}) {\n const baseRegistry = registry ?? defaultRegistry;\n const activeRegistry =\n plugins && plugins.length > 0 ? extendRegistry(baseRegistry, ...plugins) : baseRegistry;\n\n setDefaultRegistry(activeRegistry);\n\n const i18nEnabled = i18n !== false;\n const userVisitOptions = inertiaOptions.defaults?.visitOptions;\n const defaults = i18nEnabled\n ? {\n ...inertiaOptions.defaults,\n visitOptions: (href: string, options: VisitOptions): VisitOptions =>\n withVisitHeaders(href, userVisitOptions?.(href, options) ?? options),\n }\n : inertiaOptions.defaults;\n\n const app = createInertiaApp({\n ...inertiaOptions,\n defaults,\n strictMode,\n resolve: createPageResolver(pages),\n layout: createLayoutResolver({ defaultLayout }),\n withApp: (node: ReactElement, { ssr, page }: { ssr: boolean; page: InertiaPage }) => {\n const pending: Promise<unknown>[] = [];\n\n if (!ssr) {\n // Loaded on demand so apps without the shared i18n prop never ship the\n // i18next backend; the disabled-config call still stores the timezone.\n if (i18nEnabled && i18nConfigFromPageProps(page.props) !== undefined) {\n pending.push(\n import(\"./i18n/page-props\").then((module) =>\n module.configureI18nFromPageProps(page.props, i18n ?? {}),\n ),\n );\n }\n\n const booted = boot?.({ page });\n\n if (booted) {\n pending.push(booted);\n }\n }\n\n const inner = (\n <>\n {node}\n {i18nEnabled ? <LocaleReload /> : null}\n </>\n );\n\n // The whole shell waits, not just the page: anything rendering a\n // translated string (the Toaster included) would otherwise initialize\n // i18next before the backend can register — init runs exactly once,\n // first caller wins.\n const shell = (\n <ProviderBase registry={activeRegistry} sprite={sprite}>\n {wrap ? wrap(inner) : inner}\n </ProviderBase>\n );\n\n return pending.length > 0 ? (\n <AwaitReady ready={Promise.all(pending)}>{shell}</AwaitReady>\n ) : (\n shell\n );\n },\n });\n\n initializeTheme();\n\n return app;\n}\n"],"mappings":";;;;;;;;;;;;AAiEA,SAAS,WAAW,EAAE,OAAO,YAA8D;CACzF,MAAM,CAAC,SAAS,YAAY,SAAS,KAAK;CAE1C,gBAAgB;EACd,IAAI,SAAS;EACb,MAAM,eAAqB;GACzB,IAAI,QACF,SAAS,IAAI;EAEjB;EAEA,MAAW,KAAK,QAAQ,MAAM;EAE9B,aAAa;GACX,SAAS;EACX;CACF,GAAG,CAAC,KAAK,CAAC;CAEV,OAAO,UAAU,WAAW;AAC9B;;;;;;;;AASA,SAAgB,iBAAiB,EAC/B,UAAA,YACA,SACA,QACA,QAAQ,CAAC,GACT,eACA,aAAa,MACb,MACA,MACA,MACA,GAAG,mBACwB,CAAC,GAAG;CAC/B,MAAM,eAAe,cAAY;CACjC,MAAM,iBACJ,WAAW,QAAQ,SAAS,IAAI,eAAe,cAAc,GAAG,OAAO,IAAI;CAE7E,mBAAmB,cAAc;CAEjC,MAAM,cAAc,SAAS;CAC7B,MAAM,mBAAmB,eAAe,UAAU;CAClD,MAAM,WAAW,cACb;EACE,GAAG,eAAe;EAClB,eAAe,MAAc,YAC3B,iBAAiB,MAAM,mBAAmB,MAAM,OAAO,KAAK,OAAO;CACvE,IACA,eAAe;CAEnB,MAAM,MAAM,iBAAiB;EAC3B,GAAG;EACH;EACA;EACA,SAAS,mBAAmB,KAAK;EACjC,QAAQ,qBAAqB,EAAE,cAAc,CAAC;EAC9C,UAAU,MAAoB,EAAE,KAAK,WAAgD;GACnF,MAAM,UAA8B,CAAC;GAErC,IAAI,CAAC,KAAK;IAGR,IAAI,eAAe,wBAAwB,KAAK,KAAK,MAAM,KAAA,GACzD,QAAQ,KACN,OAAO,wBAAqB,MAAM,WAChC,OAAO,2BAA2B,KAAK,OAAO,QAAQ,CAAC,CAAC,CAC1D,CACF;IAGF,MAAM,SAAS,OAAO,EAAE,KAAK,CAAC;IAE9B,IAAI,QACF,QAAQ,KAAK,MAAM;GAEvB;GAEA,MAAM,QACJ,qBAAA,YAAA,EAAA,UAAA,CACG,MACA,cAAc,oBAAC,cAAD,CAAe,CAAA,IAAI,IAClC,EAAA,CAAA;GAOJ,MAAM,QACJ,oBAAC,cAAD;IAAc,UAAU;IAAwB;cAC7C,OAAO,KAAK,KAAK,IAAI;GACV,CAAA;GAGhB,OAAO,QAAQ,SAAS,IACtB,oBAAC,YAAD;IAAY,OAAO,QAAQ,IAAI,OAAO;cAAI;GAAkB,CAAA,IAE5D;EAEJ;CACF,CAAC;CAED,gBAAgB;CAEhB,OAAO;AACT"}
@@ -7,10 +7,11 @@ import { Effect, EffectPropsMap } from '../types/generated.js';
7
7
  */
8
8
  export interface EffectProps {
9
9
  }
10
- type EffectPayloadOf<TType extends string> = ResolveProps<EffectProps, EffectPropsMap, TType, Record<string, unknown>>;
10
+ export type EffectPropsOf<TType extends string> = ResolveProps<EffectProps, EffectPropsMap, TType, Record<string, unknown>>;
11
11
  export type EffectOf<TType extends string> = string extends TType ? Effect : {
12
12
  type: TType;
13
- } & EffectPayloadOf<TType>;
13
+ props: EffectPropsOf<TType>;
14
+ };
14
15
  export type EffectHandler<TType extends string = string> = (effect: EffectOf<TType>) => void;
15
16
  export type EffectHandlerRegistry = Record<string, EffectHandler>;
16
17
  export type EffectHandlerRegistryFor<TTypes extends keyof EffectPropsMap & string> = Record<TTypes, EffectHandler>;
@@ -25,4 +26,3 @@ export declare function effectHandler<TType extends string>(_type: TType, fn: Ef
25
26
  */
26
27
  export declare const builtinEffectHandlers: EffectHandlerRegistryFor<keyof EffectPropsMap & string>;
27
28
  export declare function mergeEffectHandlers(...registries: Array<EffectHandlerRegistry | undefined>): EffectHandlerRegistry;
28
- export {};
@@ -18,7 +18,7 @@ function triggerDownload(url) {
18
18
  link.remove();
19
19
  }
20
20
  function bridge(event) {
21
- return (effect) => window.dispatchEvent(new CustomEvent(event, { detail: effect }));
21
+ return (effect) => window.dispatchEvent(new CustomEvent(event, { detail: effect.props }));
22
22
  }
23
23
  /**
24
24
  * Imperative effects act directly; the rest bridge to the `lattice:*` DOM events
@@ -26,9 +26,9 @@ function bridge(event) {
26
26
  */
27
27
  var builtinEffectHandlers = {
28
28
  "reload-page": () => router.reload(),
29
- redirect: effectHandler("redirect", (effect) => router.visit(effect.url)),
30
- download: effectHandler("download", (effect) => triggerDownload(effect.url)),
31
- "locale-change": effectHandler("locale-change", (effect) => setLocale(effect.locale)),
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)),
32
32
  toast: bridge(LATTICE_EVENT.toast),
33
33
  callout: bridge(LATTICE_EVENT.callout),
34
34
  "reload-component": bridge(LATTICE_EVENT.reloadComponent),
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","names":[],"sources":["../../resources/js/effects/registry.ts"],"sourcesContent":["import { router } from \"@inertiajs/react\";\nimport type { ResolveProps } from \"@lattice-php/lattice/core/types\";\nimport type { Effect, EffectPropsMap } from \"@lattice-php/lattice/types/generated\";\nimport { LATTICE_EVENT } from \"@lattice-php/lattice/events/event-names\";\nimport { setLocale } from \"@lattice-php/lattice/i18n/locale\";\n\n/**\n * Consumer apps augment this via `declare module \"@lattice-php/lattice\"` to type\n * their custom effects' payloads; built-ins resolve through `EffectPropsMap`. The\n * effect counterpart of `ComponentProps`.\n */\nexport interface EffectProps {}\n\ntype EffectPayloadOf<TType extends string> = ResolveProps<\n EffectProps,\n EffectPropsMap,\n TType,\n Record<string, unknown>\n>;\n\nexport type EffectOf<TType extends string> = string extends TType\n ? Effect\n : { type: TType } & EffectPayloadOf<TType>;\n\nexport type EffectHandler<TType extends string = string> = (effect: EffectOf<TType>) => void;\n\nexport type EffectHandlerRegistry = Record<string, EffectHandler>;\n\nexport type EffectHandlerRegistryFor<TTypes extends keyof EffectPropsMap & string> = Record<\n TTypes,\n EffectHandler\n>;\n\n/**\n * Author a handler against `EffectHandler<\"my.type\">` for a typed payload, then\n * register it through this — it erases the type parameter for the loose registry.\n */\nexport function effectHandler<TType extends string>(\n _type: TType,\n fn: EffectHandler<TType>,\n): EffectHandler {\n return fn as unknown as EffectHandler;\n}\n\nfunction triggerDownload(url: string): void {\n const link = document.createElement(\"a\");\n link.href = url;\n link.rel = \"noopener\";\n document.body.appendChild(link);\n link.click();\n link.remove();\n}\n\nfunction bridge(event: string): EffectHandler {\n return (effect) => window.dispatchEvent(new CustomEvent(event, { detail: effect }));\n}\n\n/**\n * Imperative effects act directly; the rest bridge to the `lattice:*` DOM events\n * that toast/callout/modal/fragment/form subscribe to.\n */\nexport const builtinEffectHandlers: EffectHandlerRegistryFor<keyof EffectPropsMap & string> = {\n \"reload-page\": () => router.reload(),\n redirect: effectHandler(\"redirect\", (effect) => router.visit(effect.url)),\n download: effectHandler(\"download\", (effect) => triggerDownload(effect.url)),\n \"locale-change\": effectHandler(\"locale-change\", (effect) => setLocale(effect.locale)),\n toast: bridge(LATTICE_EVENT.toast),\n callout: bridge(LATTICE_EVENT.callout),\n \"reload-component\": bridge(LATTICE_EVENT.reloadComponent),\n \"open-modal\": bridge(LATTICE_EVENT.openModal),\n \"close-modal\": bridge(LATTICE_EVENT.closeModal),\n \"reset-form\": bridge(LATTICE_EVENT.resetForm),\n \"toggle-sidebar\": bridge(LATTICE_EVENT.toggleSidebar),\n};\n\nexport function mergeEffectHandlers(\n ...registries: Array<EffectHandlerRegistry | undefined>\n): EffectHandlerRegistry {\n return registries.reduce<EffectHandlerRegistry>(\n (merged, registry) => ({ ...merged, ...registry }),\n {},\n );\n}\n"],"mappings":";;;;;;;;AAqCA,SAAgB,cACd,OACA,IACe;CACf,OAAO;AACT;AAEA,SAAS,gBAAgB,KAAmB;CAC1C,MAAM,OAAO,SAAS,cAAc,GAAG;CACvC,KAAK,OAAO;CACZ,KAAK,MAAM;CACX,SAAS,KAAK,YAAY,IAAI;CAC9B,KAAK,MAAM;CACX,KAAK,OAAO;AACd;AAEA,SAAS,OAAO,OAA8B;CAC5C,QAAQ,WAAW,OAAO,cAAc,IAAI,YAAY,OAAO,EAAE,QAAQ,OAAO,CAAC,CAAC;AACpF;;;;;AAMA,IAAa,wBAAiF;CAC5F,qBAAqB,OAAO,OAAO;CACnC,UAAU,cAAc,aAAa,WAAW,OAAO,MAAM,OAAO,GAAG,CAAC;CACxE,UAAU,cAAc,aAAa,WAAW,gBAAgB,OAAO,GAAG,CAAC;CAC3E,iBAAiB,cAAc,kBAAkB,WAAW,UAAU,OAAO,MAAM,CAAC;CACpF,OAAO,OAAO,cAAc,KAAK;CACjC,SAAS,OAAO,cAAc,OAAO;CACrC,oBAAoB,OAAO,cAAc,eAAe;CACxD,cAAc,OAAO,cAAc,SAAS;CAC5C,eAAe,OAAO,cAAc,UAAU;CAC9C,cAAc,OAAO,cAAc,SAAS;CAC5C,kBAAkB,OAAO,cAAc,aAAa;AACtD;AAEA,SAAgB,oBACd,GAAG,YACoB;CACvB,OAAO,WAAW,QACf,QAAQ,cAAc;EAAE,GAAG;EAAQ,GAAG;CAAS,IAChD,CAAC,CACH;AACF"}
1
+ {"version":3,"file":"registry.js","names":[],"sources":["../../resources/js/effects/registry.ts"],"sourcesContent":["import { router } from \"@inertiajs/react\";\nimport type { ResolveProps } from \"@lattice-php/lattice/core/types\";\nimport type { Effect, EffectPropsMap } from \"@lattice-php/lattice/types/generated\";\nimport { LATTICE_EVENT } from \"@lattice-php/lattice/events/event-names\";\nimport { setLocale } from \"@lattice-php/lattice/i18n/locale\";\n\n/**\n * Consumer apps augment this via `declare module \"@lattice-php/lattice\"` to type\n * their custom effects' payloads; built-ins resolve through `EffectPropsMap`. The\n * effect counterpart of `ComponentProps`.\n */\nexport interface EffectProps {}\n\nexport type EffectPropsOf<TType extends string> = ResolveProps<\n EffectProps,\n EffectPropsMap,\n TType,\n Record<string, unknown>\n>;\n\nexport type EffectOf<TType extends string> = string extends TType\n ? Effect\n : { type: TType; props: EffectPropsOf<TType> };\n\nexport type EffectHandler<TType extends string = string> = (effect: EffectOf<TType>) => void;\n\nexport type EffectHandlerRegistry = Record<string, EffectHandler>;\n\nexport type EffectHandlerRegistryFor<TTypes extends keyof EffectPropsMap & string> = Record<\n TTypes,\n EffectHandler\n>;\n\n/**\n * Author a handler against `EffectHandler<\"my.type\">` for a typed payload, then\n * register it through this — it erases the type parameter for the loose registry.\n */\nexport function effectHandler<TType extends string>(\n _type: TType,\n fn: EffectHandler<TType>,\n): EffectHandler {\n return fn as unknown as EffectHandler;\n}\n\nfunction triggerDownload(url: string): void {\n const link = document.createElement(\"a\");\n link.href = url;\n link.rel = \"noopener\";\n document.body.appendChild(link);\n link.click();\n link.remove();\n}\n\nfunction bridge(event: string): EffectHandler {\n return (effect) => window.dispatchEvent(new CustomEvent(event, { detail: effect.props }));\n}\n\n/**\n * Imperative effects act directly; the rest bridge to the `lattice:*` DOM events\n * that toast/callout/modal/fragment/form subscribe to.\n */\nexport const builtinEffectHandlers: EffectHandlerRegistryFor<keyof EffectPropsMap & string> = {\n \"reload-page\": () => router.reload(),\n redirect: effectHandler(\"redirect\", (effect) => router.visit(effect.props.url)),\n download: effectHandler(\"download\", (effect) => triggerDownload(effect.props.url)),\n \"locale-change\": effectHandler(\"locale-change\", (effect) => setLocale(effect.props.locale)),\n toast: bridge(LATTICE_EVENT.toast),\n callout: bridge(LATTICE_EVENT.callout),\n \"reload-component\": bridge(LATTICE_EVENT.reloadComponent),\n \"open-modal\": bridge(LATTICE_EVENT.openModal),\n \"close-modal\": bridge(LATTICE_EVENT.closeModal),\n \"reset-form\": bridge(LATTICE_EVENT.resetForm),\n \"toggle-sidebar\": bridge(LATTICE_EVENT.toggleSidebar),\n};\n\nexport function mergeEffectHandlers(\n ...registries: Array<EffectHandlerRegistry | undefined>\n): EffectHandlerRegistry {\n return registries.reduce<EffectHandlerRegistry>(\n (merged, registry) => ({ ...merged, ...registry }),\n {},\n );\n}\n"],"mappings":";;;;;;;;AAqCA,SAAgB,cACd,OACA,IACe;CACf,OAAO;AACT;AAEA,SAAS,gBAAgB,KAAmB;CAC1C,MAAM,OAAO,SAAS,cAAc,GAAG;CACvC,KAAK,OAAO;CACZ,KAAK,MAAM;CACX,SAAS,KAAK,YAAY,IAAI;CAC9B,KAAK,MAAM;CACX,KAAK,OAAO;AACd;AAEA,SAAS,OAAO,OAA8B;CAC5C,QAAQ,WAAW,OAAO,cAAc,IAAI,YAAY,OAAO,EAAE,QAAQ,OAAO,MAAM,CAAC,CAAC;AAC1F;;;;;AAMA,IAAa,wBAAiF;CAC5F,qBAAqB,OAAO,OAAO;CACnC,UAAU,cAAc,aAAa,WAAW,OAAO,MAAM,OAAO,MAAM,GAAG,CAAC;CAC9E,UAAU,cAAc,aAAa,WAAW,gBAAgB,OAAO,MAAM,GAAG,CAAC;CACjF,iBAAiB,cAAc,kBAAkB,WAAW,UAAU,OAAO,MAAM,MAAM,CAAC;CAC1F,OAAO,OAAO,cAAc,KAAK;CACjC,SAAS,OAAO,cAAc,OAAO;CACrC,oBAAoB,OAAO,cAAc,eAAe;CACxD,cAAc,OAAO,cAAc,SAAS;CAC5C,eAAe,OAAO,cAAc,UAAU;CAC9C,cAAc,OAAO,cAAc,SAAS;CAC5C,kBAAkB,OAAO,cAAc,aAAa;AACtD;AAEA,SAAgB,oBACd,GAAG,YACoB;CACvB,OAAO,WAAW,QACf,QAAQ,cAAc;EAAE,GAAG;EAAQ,GAAG;CAAS,IAChD,CAAC,CACH;AACF"}
@@ -7,9 +7,6 @@ export declare const LATTICE_EVENT: {
7
7
  readonly callout: "lattice:callout";
8
8
  readonly toast: "lattice:toast";
9
9
  readonly reloadComponent: "lattice:reload-component";
10
- readonly reloadPage: "lattice:reload-page";
11
- readonly redirect: "lattice:redirect";
12
- readonly download: "lattice:download";
13
10
  readonly openModal: "lattice:open-modal";
14
11
  readonly closeModal: "lattice:close-modal";
15
12
  readonly resetForm: "lattice:reset-form";
@@ -8,9 +8,6 @@ var LATTICE_EVENT = {
8
8
  callout: "lattice:callout",
9
9
  toast: "lattice:toast",
10
10
  reloadComponent: "lattice:reload-component",
11
- reloadPage: "lattice:reload-page",
12
- redirect: "lattice:redirect",
13
- download: "lattice:download",
14
11
  openModal: "lattice:open-modal",
15
12
  closeModal: "lattice:close-modal",
16
13
  resetForm: "lattice:reset-form",
@@ -1 +1 @@
1
- {"version":3,"file":"event-names.js","names":[],"sources":["../../resources/js/events/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 reloadPage: \"lattice:reload-page\",\n redirect: \"lattice:redirect\",\n download: \"lattice:download\",\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,YAAY;CACZ,UAAU;CACV,UAAU;CACV,WAAW;CACX,YAAY;CACZ,WAAW;CACX,eAAe;CACf,kBAAkB;CAClB,cAAc;CACd,gBAAgB;CAChB,aAAa;AACf"}
1
+ {"version":3,"file":"event-names.js","names":[],"sources":["../../resources/js/events/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"}
@@ -1,10 +1,10 @@
1
- import { FormLabelAction } from '../../types.js';
1
+ import { LabelAction } from '../../types.js';
2
2
  export declare function FormFieldFrame({ children, error, helperText, label, labelAction, name, required, tooltip, }: {
3
3
  children: React.ReactNode;
4
4
  error?: string;
5
5
  helperText?: string;
6
6
  label: string;
7
- labelAction?: FormLabelAction;
7
+ labelAction?: LabelAction;
8
8
  name: string;
9
9
  required?: boolean;
10
10
  tooltip?: string;
@@ -1,8 +1,8 @@
1
+ import { useInTableCell } from "../../hooks/row-layout-context.js";
1
2
  import { InfoTooltip } from "../../../ui/info-tooltip.js";
2
3
  import { TextLink } from "../../../ui/link.js";
3
4
  import InputError from "../../../ui/input-error.js";
4
5
  import { Label } from "../../../ui/label.js";
5
- import { useInTableCell } from "../../hooks/row-layout-context.js";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
7
7
  //#region resources/js/form/components/base/field.tsx
8
8
  function FormFieldFrame({ children, error, helperText, label, labelAction, name, required, tooltip }) {
@@ -36,7 +36,7 @@ function FormFieldFrame({ children, error, helperText, label, labelAction, name,
36
36
  /* @__PURE__ */ jsx(InfoTooltip, { content: tooltip }),
37
37
  labelAction && /* @__PURE__ */ jsx(TextLink, {
38
38
  href: labelAction.href,
39
- tabIndex: labelAction.tabIndex,
39
+ tabIndex: labelAction.tabIndex ?? void 0,
40
40
  className: "ml-auto text-sm",
41
41
  children: labelAction.label
42
42
  })
@@ -1 +1 @@
1
- {"version":3,"file":"field.js","names":[],"sources":["../../../../resources/js/form/components/base/field.tsx"],"sourcesContent":["import InputError from \"@lattice-php/lattice/ui/input-error\";\nimport { InfoTooltip } from \"@lattice-php/lattice/ui/info-tooltip\";\nimport { TextLink } from \"@lattice-php/lattice/ui/link\";\nimport { Label } from \"@lattice-php/lattice/ui/label\";\nimport { useInTableCell } from \"@lattice-php/lattice/form/hooks/row-layout-context\";\nimport type { FormLabelAction } from \"@lattice-php/lattice/form/types\";\n\nexport function FormFieldFrame({\n children,\n error,\n helperText,\n label,\n labelAction,\n name,\n required,\n tooltip,\n}: {\n children: React.ReactNode;\n error?: string;\n helperText?: string;\n label: string;\n labelAction?: FormLabelAction;\n name: string;\n required?: boolean;\n tooltip?: string;\n}) {\n const bare = useInTableCell();\n\n if (bare) {\n return (\n <div className=\"grid gap-1\">\n <Label htmlFor={name} className=\"sr-only\">\n {label}\n </Label>\n {children}\n <InputError message={error} />\n </div>\n );\n }\n\n return (\n <div className=\"grid gap-2\">\n <div className=\"flex min-h-5 items-center\">\n <Label htmlFor={name}>{label}</Label>\n {required && (\n <span aria-hidden=\"true\" className=\"ml-0.5 text-lt-danger\">\n *\n </span>\n )}\n <InfoTooltip content={tooltip} />\n {labelAction && (\n <TextLink\n href={labelAction.href}\n tabIndex={labelAction.tabIndex}\n className=\"ml-auto text-sm\"\n >\n {labelAction.label}\n </TextLink>\n )}\n </div>\n\n {children}\n\n {helperText && <p className=\"text-sm text-lt-muted-fg\">{helperText}</p>}\n\n <InputError message={error} />\n </div>\n );\n}\n"],"mappings":";;;;;;;AAOA,SAAgB,eAAe,EAC7B,UACA,OACA,YACA,OACA,aACA,MACA,UACA,WAUC;CAGD,IAFa,eAET,GACF,OACE,qBAAC,OAAD;EAAK,WAAU;YAAf;GACE,oBAAC,OAAD;IAAO,SAAS;IAAM,WAAU;cAC7B;GACI,CAAA;GACN;GACD,oBAAC,YAAD,EAAY,SAAS,MAAQ,CAAA;EAC1B;;CAIT,OACE,qBAAC,OAAD;EAAK,WAAU;YAAf;GACE,qBAAC,OAAD;IAAK,WAAU;cAAf;KACE,oBAAC,OAAD;MAAO,SAAS;gBAAO;KAAa,CAAA;KACnC,YACC,oBAAC,QAAD;MAAM,eAAY;MAAO,WAAU;gBAAwB;KAErD,CAAA;KAER,oBAAC,aAAD,EAAa,SAAS,QAAU,CAAA;KAC/B,eACC,oBAAC,UAAD;MACE,MAAM,YAAY;MAClB,UAAU,YAAY;MACtB,WAAU;gBAET,YAAY;KACL,CAAA;IAET;;GAEJ;GAEA,cAAc,oBAAC,KAAD;IAAG,WAAU;cAA4B;GAAc,CAAA;GAEtE,oBAAC,YAAD,EAAY,SAAS,MAAQ,CAAA;EAC1B;;AAET"}
1
+ {"version":3,"file":"field.js","names":[],"sources":["../../../../resources/js/form/components/base/field.tsx"],"sourcesContent":["import InputError from \"@lattice-php/lattice/ui/input-error\";\nimport { InfoTooltip } from \"@lattice-php/lattice/ui/info-tooltip\";\nimport { TextLink } from \"@lattice-php/lattice/ui/link\";\nimport { Label } from \"@lattice-php/lattice/ui/label\";\nimport { useInTableCell } from \"@lattice-php/lattice/form/hooks/row-layout-context\";\nimport type { LabelAction } from \"@lattice-php/lattice/form/types\";\n\nexport function FormFieldFrame({\n children,\n error,\n helperText,\n label,\n labelAction,\n name,\n required,\n tooltip,\n}: {\n children: React.ReactNode;\n error?: string;\n helperText?: string;\n label: string;\n labelAction?: LabelAction;\n name: string;\n required?: boolean;\n tooltip?: string;\n}) {\n const bare = useInTableCell();\n\n if (bare) {\n return (\n <div className=\"grid gap-1\">\n <Label htmlFor={name} className=\"sr-only\">\n {label}\n </Label>\n {children}\n <InputError message={error} />\n </div>\n );\n }\n\n return (\n <div className=\"grid gap-2\">\n <div className=\"flex min-h-5 items-center\">\n <Label htmlFor={name}>{label}</Label>\n {required && (\n <span aria-hidden=\"true\" className=\"ml-0.5 text-lt-danger\">\n *\n </span>\n )}\n <InfoTooltip content={tooltip} />\n {labelAction && (\n <TextLink\n href={labelAction.href}\n tabIndex={labelAction.tabIndex ?? undefined}\n className=\"ml-auto text-sm\"\n >\n {labelAction.label}\n </TextLink>\n )}\n </div>\n\n {children}\n\n {helperText && <p className=\"text-sm text-lt-muted-fg\">{helperText}</p>}\n\n <InputError message={error} />\n </div>\n );\n}\n"],"mappings":";;;;;;;AAOA,SAAgB,eAAe,EAC7B,UACA,OACA,YACA,OACA,aACA,MACA,UACA,WAUC;CAGD,IAFa,eAET,GACF,OACE,qBAAC,OAAD;EAAK,WAAU;YAAf;GACE,oBAAC,OAAD;IAAO,SAAS;IAAM,WAAU;cAC7B;GACI,CAAA;GACN;GACD,oBAAC,YAAD,EAAY,SAAS,MAAQ,CAAA;EAC1B;;CAIT,OACE,qBAAC,OAAD;EAAK,WAAU;YAAf;GACE,qBAAC,OAAD;IAAK,WAAU;cAAf;KACE,oBAAC,OAAD;MAAO,SAAS;gBAAO;KAAa,CAAA;KACnC,YACC,oBAAC,QAAD;MAAM,eAAY;MAAO,WAAU;gBAAwB;KAErD,CAAA;KAER,oBAAC,aAAD,EAAa,SAAS,QAAU,CAAA;KAC/B,eACC,oBAAC,UAAD;MACE,MAAM,YAAY;MAClB,UAAU,YAAY,YAAY,KAAA;MAClC,WAAU;gBAET,YAAY;KACL,CAAA;IAET;;GAEJ;GAEA,cAAc,oBAAC,KAAD;IAAG,WAAU;cAA4B;GAAc,CAAA;GAEtE,oBAAC,YAAD,EAAY,SAAS,MAAQ,CAAA;EAC1B;;AAET"}
@@ -0,0 +1,2 @@
1
+ import { RendererComponent } from '../../../core/types.js';
2
+ export declare const ColorPickerFieldComponent: RendererComponent<"field.color-picker">;
@@ -0,0 +1,57 @@
1
+ import { cn } from "../../../lib/utils.js";
2
+ import { Popover, PopoverContent, PopoverTrigger } from "../../../ui/popover.js";
3
+ import { controlSurface } from "../../../ui/control.js";
4
+ import { ColorPicker, normalizeHex } from "../../../ui/color-picker.js";
5
+ import { SimpleField } from "./simple-field.js";
6
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
+ //#region resources/js/form/components/fields/color-picker-field.tsx
8
+ var ColorPickerFieldComponent = ({ node }) => {
9
+ const props = node.props;
10
+ return /* @__PURE__ */ jsx(SimpleField, {
11
+ node,
12
+ label: props.label ?? "",
13
+ children: ({ name, value, readOnly, disabled, commit }) => {
14
+ const hex = normalizeHex(value);
15
+ return /* @__PURE__ */ jsxs(Popover, { children: [
16
+ /* @__PURE__ */ jsx("input", {
17
+ name,
18
+ type: "hidden",
19
+ value: hex ?? ""
20
+ }),
21
+ /* @__PURE__ */ jsx(PopoverTrigger, {
22
+ asChild: true,
23
+ children: /* @__PURE__ */ jsx("button", {
24
+ className: cn(controlSurface(), "flex items-center gap-2 text-left"),
25
+ "data-test": `color-picker-${name}`,
26
+ disabled: disabled || readOnly,
27
+ id: name,
28
+ type: "button",
29
+ children: hex ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
30
+ "aria-hidden": "true",
31
+ className: "size-4 shrink-0 rounded-full border border-lt-border",
32
+ style: { background: hex }
33
+ }), /* @__PURE__ */ jsx("span", {
34
+ className: "tabular-nums",
35
+ children: hex
36
+ })] }) : /* @__PURE__ */ jsx("span", {
37
+ className: "text-lt-muted-fg",
38
+ children: props.placeholder ?? ""
39
+ })
40
+ })
41
+ }),
42
+ /* @__PURE__ */ jsx(PopoverContent, {
43
+ className: "p-3",
44
+ children: /* @__PURE__ */ jsx(ColorPicker, {
45
+ onChange: (next) => commit(next),
46
+ palette: props.palette,
47
+ value: hex ?? "#6b7280"
48
+ })
49
+ })
50
+ ] });
51
+ }
52
+ });
53
+ };
54
+ //#endregion
55
+ export { ColorPickerFieldComponent };
56
+
57
+ //# sourceMappingURL=color-picker-field.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color-picker-field.js","names":[],"sources":["../../../../resources/js/form/components/fields/color-picker-field.tsx"],"sourcesContent":["import type { RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { ColorPicker, normalizeHex } from \"@lattice-php/lattice/ui/color-picker\";\nimport { controlSurface } from \"@lattice-php/lattice/ui/control\";\nimport { Popover, PopoverContent, PopoverTrigger } from \"@lattice-php/lattice/ui/popover\";\nimport { SimpleField } from \"./simple-field\";\n\nexport const ColorPickerFieldComponent: RendererComponent<\"field.color-picker\"> = ({ node }) => {\n const props = node.props;\n\n return (\n <SimpleField node={node} label={props.label ?? \"\"}>\n {({ name, value, readOnly, disabled, commit }) => {\n const hex = normalizeHex(value);\n\n return (\n <Popover>\n <input name={name} type=\"hidden\" value={hex ?? \"\"} />\n <PopoverTrigger asChild>\n <button\n className={cn(controlSurface(), \"flex items-center gap-2 text-left\")}\n data-test={`color-picker-${name}`}\n disabled={disabled || readOnly}\n id={name}\n type=\"button\"\n >\n {hex ? (\n <>\n <span\n aria-hidden=\"true\"\n className=\"size-4 shrink-0 rounded-full border border-lt-border\"\n style={{ background: hex }}\n />\n <span className=\"tabular-nums\">{hex}</span>\n </>\n ) : (\n <span className=\"text-lt-muted-fg\">{props.placeholder ?? \"\"}</span>\n )}\n </button>\n </PopoverTrigger>\n <PopoverContent className=\"p-3\">\n <ColorPicker\n onChange={(next) => commit(next)}\n palette={props.palette}\n value={hex ?? \"#6b7280\"}\n />\n </PopoverContent>\n </Popover>\n );\n }}\n </SimpleField>\n );\n};\n"],"mappings":";;;;;;;AAOA,IAAa,6BAAsE,EAAE,WAAW;CAC9F,MAAM,QAAQ,KAAK;CAEnB,OACE,oBAAC,aAAD;EAAmB;EAAM,OAAO,MAAM,SAAS;aAC3C,EAAE,MAAM,OAAO,UAAU,UAAU,aAAa;GAChD,MAAM,MAAM,aAAa,KAAK;GAE9B,OACE,qBAAC,SAAD,EAAA,UAAA;IACE,oBAAC,SAAD;KAAa;KAAM,MAAK;KAAS,OAAO,OAAO;IAAK,CAAA;IACpD,oBAAC,gBAAD;KAAgB,SAAA;eACd,oBAAC,UAAD;MACE,WAAW,GAAG,eAAe,GAAG,mCAAmC;MACnE,aAAW,gBAAgB;MAC3B,UAAU,YAAY;MACtB,IAAI;MACJ,MAAK;gBAEJ,MACC,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,QAAD;OACE,eAAY;OACZ,WAAU;OACV,OAAO,EAAE,YAAY,IAAI;MAC1B,CAAA,GACD,oBAAC,QAAD;OAAM,WAAU;iBAAgB;MAAU,CAAA,CAC1C,EAAA,CAAA,IAEF,oBAAC,QAAD;OAAM,WAAU;iBAAoB,MAAM,eAAe;MAAS,CAAA;KAE9D,CAAA;IACM,CAAA;IAChB,oBAAC,gBAAD;KAAgB,WAAU;eACxB,oBAAC,aAAD;MACE,WAAW,SAAS,OAAO,IAAI;MAC/B,SAAS,MAAM;MACf,OAAO,OAAO;KACf,CAAA;IACa,CAAA;GACT,EAAA,CAAA;EAEb;CACW,CAAA;AAEjB"}
@@ -1,5 +1,5 @@
1
- import { DatePicker } from "./date-picker.js";
2
1
  import { SimpleField } from "./simple-field.js";
2
+ import { DatePicker } from "./date-picker.js";
3
3
  import { jsx } from "react/jsx-runtime";
4
4
  //#region resources/js/form/components/fields/date-input.tsx
5
5
  var DateInputComponent = ({ node }) => {
@@ -1,6 +1,6 @@
1
- import { useLocale } from "../../../i18n/locale.js";
2
1
  import { cn } from "../../../lib/utils.js";
3
2
  import { Icon } from "../../../icons/sprite.js";
3
+ import { useLocale } from "../../../i18n/locale.js";
4
4
  import { Button } from "../../../ui/button.js";
5
5
  import { Input } from "../../../ui/input.js";
6
6
  import { parseTimeString } from "./time-picker-columns.js";
@@ -1,6 +1,6 @@
1
1
  import { useTimezone } from "../../../i18n/timezone.js";
2
- import { DatePicker } from "./date-picker.js";
3
2
  import { SimpleField } from "./simple-field.js";
3
+ import { DatePicker } from "./date-picker.js";
4
4
  import { jsx } from "react/jsx-runtime";
5
5
  //#region resources/js/form/components/fields/date-time-input.tsx
6
6
  var DateTimeInputComponent = ({ node }) => {
@@ -1,4 +1,3 @@
1
- import { Icon } from "../../../icons/sprite.js";
2
1
  import { useT } from "../../../i18n/instance.js";
3
2
  import { testIdentity } from "../../../core/test-id.js";
4
3
  import { apiFetch } from "../../../core/api.js";
@@ -7,6 +6,7 @@ import { useFieldScope } from "../../hooks/field-scope.js";
7
6
  import { useFormValues, useSetFormValue } from "../../hooks/values.js";
8
7
  import { FormFieldFrame } from "../base/field.js";
9
8
  import { useDependentField } from "../../hooks/use-dependent-field.js";
9
+ import { IconButton } from "../../../ui/icon-button.js";
10
10
  import { useEffect, useId, useMemo, useRef, useState } from "react";
11
11
  import { jsx, jsxs } from "react/jsx-runtime";
12
12
  //#region resources/js/form/components/fields/file-upload.tsx
@@ -116,7 +116,7 @@ var FileUploadComponent = ({ node }) => {
116
116
  const sign = await response.json();
117
117
  await new Promise((resolve) => {
118
118
  const request = new XMLHttpRequest();
119
- request.open(sign.method, sign.url, true);
119
+ request.open(sign.method.toUpperCase(), sign.url, true);
120
120
  Object.entries(sign.headers).forEach(([key, value]) => request.setRequestHeader(key, String(value)));
121
121
  request.upload.onprogress = (event) => {
122
122
  if (event.lengthComputable) {
@@ -230,18 +230,13 @@ var FileUploadComponent = ({ node }) => {
230
230
  })
231
231
  ]
232
232
  }),
233
- (!item.existing || !scope) && /* @__PURE__ */ jsx("button", {
234
- "aria-label": t("form.file-upload.remove", "Remove {{name}}", { name: item.name }),
235
- className: "inline-flex size-7 shrink-0 items-center justify-center rounded-lt-sm text-lt-muted-fg transition-colors hover:bg-lt-accent hover:text-lt-accent-fg disabled:pointer-events-none disabled:opacity-50",
233
+ (!item.existing || !scope) && /* @__PURE__ */ jsx(IconButton, {
234
+ size: "sm",
235
+ icon: "x",
236
+ label: t("form.file-upload.remove", "Remove {{name}}", { name: item.name }),
236
237
  "data-test": testIdentity(item.existing ? `${name}-remove-existing` : `${name}-remove`),
237
238
  disabled: locked,
238
- onClick: () => removeItem(item.id),
239
- type: "button",
240
- children: /* @__PURE__ */ jsx(Icon, {
241
- name: "x",
242
- "aria-hidden": "true",
243
- className: "size-lt-icon-sm"
244
- })
239
+ onClick: () => removeItem(item.id)
245
240
  }),
246
241
  signed && !item.existing && item.key && item.status === "ready" && /* @__PURE__ */ jsx("input", {
247
242
  "data-test": testIdentity(`${name}-uploaded`),