@lattice-php/lattice 0.16.0 → 0.17.1

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 (124) hide show
  1. package/dist/action/components/action-form.js +1 -1
  2. package/dist/action/use-action.js +1 -1
  3. package/dist/chat/types.d.ts +2 -10
  4. package/dist/core/components/button.js +34 -18
  5. package/dist/core/components/button.js.map +1 -1
  6. package/dist/core/components/control.d.ts +0 -2
  7. package/dist/core/components/control.js.map +1 -1
  8. package/dist/core/components/link.js +25 -29
  9. package/dist/core/components/link.js.map +1 -1
  10. package/dist/core/renderer.js +42 -2
  11. package/dist/core/renderer.js.map +1 -1
  12. package/dist/core/types.d.ts +22 -42
  13. package/dist/core/use-click-behavior.d.ts +42 -0
  14. package/dist/core/use-click-behavior.js +46 -0
  15. package/dist/core/use-click-behavior.js.map +1 -0
  16. package/dist/create-app.d.ts +8 -2
  17. package/dist/create-app.js +4 -2
  18. package/dist/create-app.js.map +1 -1
  19. package/dist/effects/registry.d.ts +9 -20
  20. package/dist/effects/registry.js +4 -6
  21. package/dist/effects/registry.js.map +1 -1
  22. package/dist/events/event-bridge.d.ts +1 -1
  23. package/dist/events/event-bridge.js +1 -1
  24. package/dist/events/event-bridge.js.map +1 -1
  25. package/dist/form/components/base/input-otp.d.ts +2 -1
  26. package/dist/form/components/base/input-otp.js.map +1 -1
  27. package/dist/form/components/conditions.d.ts +1 -1
  28. package/dist/form/components/conditions.js.map +1 -1
  29. package/dist/form/components/field-props.d.ts +2 -1
  30. package/dist/form/components/field-props.js.map +1 -1
  31. package/dist/form/components/field-scope.d.ts +2 -1
  32. package/dist/form/components/field-scope.js.map +1 -1
  33. package/dist/form/components/fields/row-action-menu.d.ts +2 -1
  34. package/dist/form/components/fields/row-action-menu.js.map +1 -1
  35. package/dist/form/components/fields/row-item.d.ts +2 -1
  36. package/dist/form/components/fields/row-item.js.map +1 -1
  37. package/dist/form/components/fields/table-rows.d.ts +2 -1
  38. package/dist/form/components/fields/table-rows.js.map +1 -1
  39. package/dist/form/components/fields/time-picker-columns.d.ts +2 -1
  40. package/dist/form/components/fields/time-picker-columns.js.map +1 -1
  41. package/dist/form/components/fields/time-picker.d.ts +2 -1
  42. package/dist/form/components/fields/time-picker.js.map +1 -1
  43. package/dist/form/components/fields/use-row-collection.d.ts +2 -1
  44. package/dist/form/components/fields/use-row-collection.js.map +1 -1
  45. package/dist/form/components/form-path.d.ts +0 -1
  46. package/dist/form/components/form-path.js +1 -1
  47. package/dist/form/components/form-path.js.map +1 -1
  48. package/dist/form/components/prefill-targets.d.ts +1 -1
  49. package/dist/form/components/prefill-targets.js.map +1 -1
  50. package/dist/form/components/types.d.ts +5 -1
  51. package/dist/form/components/use-field-commit.d.ts +2 -1
  52. package/dist/form/components/use-field-commit.js.map +1 -1
  53. package/dist/form/components/use-form-resolver.d.ts +2 -1
  54. package/dist/form/components/use-form-resolver.js.map +1 -1
  55. package/dist/format/date-time.d.ts +12 -0
  56. package/dist/format/date-time.js +30 -0
  57. package/dist/format/date-time.js.map +1 -0
  58. package/dist/format/value.js +1 -1
  59. package/dist/format/value.js.map +1 -1
  60. package/dist/i18n/config.d.ts +2 -1
  61. package/dist/i18n/config.js.map +1 -1
  62. package/dist/i18n/date-time.js +1 -1
  63. package/dist/i18n/date-time.js.map +1 -1
  64. package/dist/i18n/instance.d.ts +1 -1
  65. package/dist/i18n/instance.js.map +1 -1
  66. package/dist/i18n/locale-reload.d.ts +2 -1
  67. package/dist/i18n/locale-reload.js.map +1 -1
  68. package/dist/index.js +1 -1
  69. package/dist/layout/components.js +1 -1
  70. package/dist/layout/menu-item.js +43 -49
  71. package/dist/layout/menu-item.js.map +1 -1
  72. package/dist/notifications/api.d.ts +6 -0
  73. package/dist/notifications/api.js +21 -0
  74. package/dist/notifications/api.js.map +1 -0
  75. package/dist/notifications/components/empty-state.d.ts +1 -0
  76. package/dist/notifications/components/empty-state.js +21 -0
  77. package/dist/notifications/components/empty-state.js.map +1 -0
  78. package/dist/notifications/components/notification-item.d.ts +6 -0
  79. package/dist/notifications/components/notification-item.js +65 -0
  80. package/dist/notifications/components/notification-item.js.map +1 -0
  81. package/dist/notifications/components/notification-list.d.ts +10 -0
  82. package/dist/notifications/components/notification-list.js +38 -0
  83. package/dist/notifications/components/notification-list.js.map +1 -0
  84. package/dist/notifications/components/notifications-echo.d.ts +5 -0
  85. package/dist/notifications/components/notifications-echo.js +22 -0
  86. package/dist/notifications/components/notifications-echo.js.map +1 -0
  87. package/dist/notifications/components/notifications.d.ts +3 -0
  88. package/dist/notifications/components/notifications.js +109 -0
  89. package/dist/notifications/components/notifications.js.map +1 -0
  90. package/dist/notifications/index.d.ts +1 -0
  91. package/dist/notifications/index.js +11 -0
  92. package/dist/notifications/index.js.map +1 -0
  93. package/dist/notifications/store.d.ts +25 -0
  94. package/dist/notifications/store.js +100 -0
  95. package/dist/notifications/store.js.map +1 -0
  96. package/dist/notifications/types.d.ts +10 -0
  97. package/dist/notifications/types.js +0 -0
  98. package/dist/realtime/build-effects.d.ts +2 -1
  99. package/dist/realtime/build-effects.js.map +1 -1
  100. package/dist/realtime/subscriptions.js.map +1 -1
  101. package/dist/registry/eager.js +2 -1
  102. package/dist/registry/eager.js.map +1 -1
  103. package/dist/registry/lazy.js +2 -1
  104. package/dist/registry/lazy.js.map +1 -1
  105. package/dist/table/bulk.d.ts +2 -1
  106. package/dist/table/bulk.js.map +1 -1
  107. package/dist/table/components/bulk-bar.js +2 -2
  108. package/dist/table/components/cells/money-cell.js +1 -1
  109. package/dist/table/components/cells/number-cell.js +1 -1
  110. package/dist/table/components/cells/text-cell.js +1 -1
  111. package/dist/table/components/filter-controls.d.ts +0 -4
  112. package/dist/table/components/filter-controls.js.map +1 -1
  113. package/dist/table/components/table-cell.js.map +1 -1
  114. package/dist/table/format.d.ts +1 -12
  115. package/dist/table/format.js +2 -26
  116. package/dist/table/format.js.map +1 -1
  117. package/dist/table/types.d.ts +7 -17
  118. package/dist/types/generated.d.ts +119 -290
  119. package/dist/vite-client.d.ts +8 -0
  120. package/dist/vite.d.ts +35 -1
  121. package/dist/vite.js +60 -2
  122. package/dist/vite.js.map +1 -1
  123. package/package.json +17 -2
  124. package/resources/icons/bell.svg +16 -0
@@ -1,9 +1,9 @@
1
1
  import { Renderer } from "../../core/renderer.js";
2
2
  import { useT } from "../../i18n/instance.js";
3
- import { dispatchActionError } from "../../effects/dispatch.js";
4
3
  import { Button } from "../../core/components/button.js";
5
4
  import { Dialog, DialogContent, DialogHeader } from "../../core/components/dialog.js";
6
5
  import { Spinner } from "../../core/components/spinner.js";
6
+ import { dispatchActionError } from "../../effects/dispatch.js";
7
7
  import { apiFetch } from "../../core/api.js";
8
8
  import { Skeleton } from "../../core/components/skeleton.js";
9
9
  import { FormProvider } from "../../form/components/context.js";
@@ -1,7 +1,7 @@
1
1
  import { withHeaders } from "../core/headers.js";
2
+ import { ConfirmDialog } from "../core/components/confirm-dialog.js";
2
3
  import { getActionEffects } from "../effects/dispatch.js";
3
4
  import { useEffectDispatcher } from "../effects/use-effect-dispatcher.js";
4
- import { ConfirmDialog } from "../core/components/confirm-dialog.js";
5
5
  import { runAction } from "./run-action.js";
6
6
  import { ActionForm, useLazyActionForm } from "./components/action-form.js";
7
7
  import { router, useHttp } from "@inertiajs/react";
@@ -1,14 +1,6 @@
1
1
  import { Node } from '../core/types';
2
- import { ChatMessage as GeneratedChatMessage, ChatRole } from '../types/generated';
3
- export type { ChatRole };
4
- /**
5
- * The generated message shape, but `parts` is widened from the generated
6
- * `ChatNode` union to open `Node`s — a part is just a component node rendered by
7
- * `type`, so a message can also carry a consumer's custom part.
8
- */
9
- export type ChatMessage = Omit<GeneratedChatMessage, "parts"> & {
10
- parts: Node[];
11
- };
2
+ import { ChatMessage, ChatRole } from '../types/generated';
3
+ export type { ChatMessage, ChatRole };
12
4
  export type ChatStatus = "idle" | "submitted" | "streaming" | "error";
13
5
  export type ChatFrame = {
14
6
  type: "text";
@@ -1,8 +1,7 @@
1
1
  import { cn } from "../../lib/utils.js";
2
2
  import { IconRenderer } from "../../icons/icon-renderer.js";
3
3
  import { nodeIdentity } from "../test-id.js";
4
- import { getActionEffects } from "../../effects/dispatch.js";
5
- import { useEffectDispatcher } from "../../effects/use-effect-dispatcher.js";
4
+ import { ActionTrigger, useClickBehavior } from "../use-click-behavior.js";
6
5
  import { Link } from "@inertiajs/react";
7
6
  import "react";
8
7
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -45,34 +44,51 @@ function Button({ className, variant, size, asChild = false, ...props }) {
45
44
  });
46
45
  }
47
46
  var ButtonComponent = ({ node }) => {
48
- const { href, label, icon } = node.props;
49
- const effects = node.props.effects ?? [];
47
+ const { label, icon } = node.props;
50
48
  const variant = node.props.variant ?? "default";
51
49
  const testId = nodeIdentity(node);
52
- const dispatch = useEffectDispatcher();
53
- if (href) return /* @__PURE__ */ jsx(Button, {
50
+ const behavior = useClickBehavior(node.props);
51
+ const size = icon ? "icon" : "default";
52
+ const content = icon ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(IconRenderer, {
53
+ className: "size-lt-icon-md",
54
+ icon
55
+ }), label ? /* @__PURE__ */ jsx("span", {
56
+ className: "sr-only",
57
+ children: label
58
+ }) : null] }) : label;
59
+ const triggerButton = ({ onClick, processing }) => /* @__PURE__ */ jsx(Button, {
60
+ "data-test": testId,
61
+ disabled: processing,
62
+ onClick,
63
+ size,
64
+ variant,
65
+ children: content
66
+ });
67
+ if (behavior.kind === "navigate") return /* @__PURE__ */ jsx(Button, {
54
68
  asChild: true,
55
69
  "data-test": testId,
56
70
  variant,
57
- size: "default",
71
+ size,
58
72
  children: /* @__PURE__ */ jsx(Link, {
59
- href,
60
- children: label
73
+ href: behavior.href,
74
+ method: behavior.method,
75
+ children: content
61
76
  })
62
77
  });
78
+ if (behavior.kind === "action") return /* @__PURE__ */ jsx(ActionTrigger, {
79
+ action: behavior.action,
80
+ children: triggerButton
81
+ });
82
+ if (behavior.kind === "effects") return triggerButton({
83
+ onClick: behavior.onClick,
84
+ processing: false
85
+ });
63
86
  return /* @__PURE__ */ jsx(Button, {
64
87
  "data-test": testId,
65
- onClick: effects.length > 0 ? () => dispatch(getActionEffects(effects)) : void 0,
66
- size: icon ? "icon" : "default",
88
+ size,
67
89
  type: node.props.buttonType,
68
90
  variant,
69
- children: icon ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(IconRenderer, {
70
- className: "size-lt-icon-md",
71
- icon
72
- }), label ? /* @__PURE__ */ jsx("span", {
73
- className: "sr-only",
74
- children: label
75
- }) : null] }) : label
91
+ children: content
76
92
  });
77
93
  };
78
94
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"button.js","names":[],"sources":["../../../resources/js/core/components/button.tsx"],"sourcesContent":["import { Link } from \"@inertiajs/react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { getActionEffects } from \"@lattice-php/lattice/effects/dispatch\";\nimport { IconRenderer } from \"@lattice-php/lattice/icons\";\nimport { nodeIdentity } from \"@lattice-php/lattice/core/test-id\";\nimport type { RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { useEffectDispatcher } from \"@lattice-php/lattice/effects/use-effect-dispatcher\";\nimport type { ButtonVariant } from \"@lattice-php/lattice/types/generated\";\n\nexport type { ButtonVariant };\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lt-sm text-base font-medium transition-[color,box-shadow] disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-lt-icon-md [&_svg]:shrink-0 outline-none focus-visible:border-lt-ring focus-visible:ring-lt-ring/50 focus-visible:ring-[3px] aria-invalid:ring-lt-danger/20 dark:aria-invalid:ring-lt-danger/40 aria-invalid:border-lt-danger\",\n {\n variants: {\n variant: {\n default:\n \"bg-lt-primary text-lt-primary-fg shadow-lt-xs hover:bg-lt-primary-hover active:bg-lt-primary-active disabled:bg-lt-disabled disabled:text-lt-disabled-fg disabled:shadow-none\",\n destructive:\n \"bg-lt-danger text-lt-danger-fg shadow-lt-xs hover:bg-lt-danger-hover active:bg-lt-danger-active focus-visible:ring-lt-danger/20 dark:focus-visible:ring-lt-danger/40 disabled:bg-lt-disabled disabled:text-lt-disabled-fg disabled:shadow-none\",\n success:\n \"bg-lt-success text-lt-success-fg shadow-lt-xs hover:bg-lt-success-hover active:bg-lt-success-active disabled:bg-lt-disabled disabled:text-lt-disabled-fg disabled:shadow-none\",\n info: \"bg-lt-info text-lt-info-fg shadow-lt-xs hover:bg-lt-info-hover active:bg-lt-info-active disabled:bg-lt-disabled disabled:text-lt-disabled-fg disabled:shadow-none\",\n outline:\n \"border border-lt-input bg-lt-bg shadow-lt-xs hover:bg-lt-accent hover:text-lt-accent-fg disabled:bg-lt-disabled disabled:text-lt-disabled-fg disabled:border-transparent disabled:shadow-none\",\n secondary:\n \"bg-lt-secondary text-lt-secondary-fg shadow-lt-xs hover:bg-lt-secondary-hover active:bg-lt-secondary-active disabled:bg-lt-disabled disabled:text-lt-disabled-fg disabled:shadow-none\",\n ghost: \"hover:bg-lt-accent hover:text-lt-accent-fg disabled:text-lt-disabled-fg\",\n link: \"text-lt-primary underline-offset-4 hover:underline disabled:text-lt-disabled-fg disabled:no-underline\",\n },\n size: {\n default: \"h-lt-control-md px-4 py-2 has-[>svg]:px-3\",\n sm: \"h-lt-control-sm rounded-lt-sm px-3 has-[>svg]:px-2.5\",\n lg: \"h-lt-control-lg rounded-lt-sm px-6 has-[>svg]:px-4\",\n icon: \"size-lt-control-md\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nfunction Button({\n className,\n variant,\n size,\n asChild = false,\n ...props\n}: React.ComponentProps<\"button\"> &\n VariantProps<typeof buttonVariants> & {\n asChild?: boolean;\n }) {\n const Comp = asChild ? Slot : \"button\";\n\n return (\n <Comp\n data-slot=\"button\"\n className={cn(buttonVariants({ variant, size, className }))}\n {...props}\n />\n );\n}\n\nconst ButtonComponent: RendererComponent<\"button\"> = ({ node }) => {\n const { href, label, icon } = node.props;\n const effects = node.props.effects ?? [];\n const variant = node.props.variant ?? \"default\";\n const testId = nodeIdentity(node);\n const dispatch = useEffectDispatcher();\n\n if (href) {\n return (\n <Button asChild data-test={testId} variant={variant} size=\"default\">\n <Link href={href}>{label}</Link>\n </Button>\n );\n }\n\n const hasEffects = effects.length > 0;\n\n return (\n <Button\n data-test={testId}\n onClick={hasEffects ? () => dispatch(getActionEffects(effects)) : undefined}\n size={icon ? \"icon\" : \"default\"}\n type={node.props.buttonType}\n variant={variant}\n >\n {icon ? (\n <>\n <IconRenderer className=\"size-lt-icon-md\" icon={icon} />\n {label ? <span className=\"sr-only\">{label}</span> : null}\n </>\n ) : (\n label\n )}\n </Button>\n );\n};\n\nexport default ButtonComponent;\nexport { Button, buttonVariants };\n"],"mappings":";;;;;;;;;;;AAcA,IAAM,iBAAiB,IACrB,+bACA;CACE,UAAU;EACR,SAAS;GACP,SACE;GACF,aACE;GACF,SACE;GACF,MAAM;GACN,SACE;GACF,WACE;GACF,OAAO;GACP,MAAM;EACR;EACA,MAAM;GACJ,SAAS;GACT,IAAI;GACJ,IAAI;GACJ,MAAM;EACR;CACF;CACA,iBAAiB;EACf,SAAS;EACT,MAAM;CACR;AACF,CACF;AAEA,SAAS,OAAO,EACd,WACA,SACA,MACA,UAAU,OACV,GAAG,SAIA;CAGH,OACE,oBAHW,UAAU,OAAO,UAG5B;EACE,aAAU;EACV,WAAW,GAAG,eAAe;GAAE;GAAS;GAAM;EAAU,CAAC,CAAC;EAC1D,GAAI;CACL,CAAA;AAEL;AAEA,IAAM,mBAAgD,EAAE,WAAW;CACjE,MAAM,EAAE,MAAM,OAAO,SAAS,KAAK;CACnC,MAAM,UAAU,KAAK,MAAM,WAAW,CAAC;CACvC,MAAM,UAAU,KAAK,MAAM,WAAW;CACtC,MAAM,SAAS,aAAa,IAAI;CAChC,MAAM,WAAW,oBAAoB;CAErC,IAAI,MACF,OACE,oBAAC,QAAD;EAAQ,SAAA;EAAQ,aAAW;EAAiB;EAAS,MAAK;YACxD,oBAAC,MAAD;GAAY;aAAO;EAAY,CAAA;CACzB,CAAA;CAMZ,OACE,oBAAC,QAAD;EACE,aAAW;EACX,SALe,QAAQ,SAAS,UAKJ,SAAS,iBAAiB,OAAO,CAAC,IAAI,KAAA;EAClE,MAAM,OAAO,SAAS;EACtB,MAAM,KAAK,MAAM;EACR;YAER,OACC,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,cAAD;GAAc,WAAU;GAAwB;EAAO,CAAA,GACtD,QAAQ,oBAAC,QAAD;GAAM,WAAU;aAAW;EAAY,CAAA,IAAI,IACpD,EAAA,CAAA,IAEF;CAEI,CAAA;AAEZ"}
1
+ {"version":3,"file":"button.js","names":[],"sources":["../../../resources/js/core/components/button.tsx"],"sourcesContent":["import { Link } from \"@inertiajs/react\";\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { IconRenderer } from \"@lattice-php/lattice/icons\";\nimport { nodeIdentity } from \"@lattice-php/lattice/core/test-id\";\nimport type { RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport {\n ActionTrigger,\n type TriggerState,\n useClickBehavior,\n} from \"@lattice-php/lattice/core/use-click-behavior\";\nimport type { ButtonVariant } from \"@lattice-php/lattice/types/generated\";\n\nexport type { ButtonVariant };\n\nconst buttonVariants = cva(\n \"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-lt-sm text-base font-medium transition-[color,box-shadow] disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-lt-icon-md [&_svg]:shrink-0 outline-none focus-visible:border-lt-ring focus-visible:ring-lt-ring/50 focus-visible:ring-[3px] aria-invalid:ring-lt-danger/20 dark:aria-invalid:ring-lt-danger/40 aria-invalid:border-lt-danger\",\n {\n variants: {\n variant: {\n default:\n \"bg-lt-primary text-lt-primary-fg shadow-lt-xs hover:bg-lt-primary-hover active:bg-lt-primary-active disabled:bg-lt-disabled disabled:text-lt-disabled-fg disabled:shadow-none\",\n destructive:\n \"bg-lt-danger text-lt-danger-fg shadow-lt-xs hover:bg-lt-danger-hover active:bg-lt-danger-active focus-visible:ring-lt-danger/20 dark:focus-visible:ring-lt-danger/40 disabled:bg-lt-disabled disabled:text-lt-disabled-fg disabled:shadow-none\",\n success:\n \"bg-lt-success text-lt-success-fg shadow-lt-xs hover:bg-lt-success-hover active:bg-lt-success-active disabled:bg-lt-disabled disabled:text-lt-disabled-fg disabled:shadow-none\",\n info: \"bg-lt-info text-lt-info-fg shadow-lt-xs hover:bg-lt-info-hover active:bg-lt-info-active disabled:bg-lt-disabled disabled:text-lt-disabled-fg disabled:shadow-none\",\n outline:\n \"border border-lt-input bg-lt-bg shadow-lt-xs hover:bg-lt-accent hover:text-lt-accent-fg disabled:bg-lt-disabled disabled:text-lt-disabled-fg disabled:border-transparent disabled:shadow-none\",\n secondary:\n \"bg-lt-secondary text-lt-secondary-fg shadow-lt-xs hover:bg-lt-secondary-hover active:bg-lt-secondary-active disabled:bg-lt-disabled disabled:text-lt-disabled-fg disabled:shadow-none\",\n ghost: \"hover:bg-lt-accent hover:text-lt-accent-fg disabled:text-lt-disabled-fg\",\n link: \"text-lt-primary underline-offset-4 hover:underline disabled:text-lt-disabled-fg disabled:no-underline\",\n },\n size: {\n default: \"h-lt-control-md px-4 py-2 has-[>svg]:px-3\",\n sm: \"h-lt-control-sm rounded-lt-sm px-3 has-[>svg]:px-2.5\",\n lg: \"h-lt-control-lg rounded-lt-sm px-6 has-[>svg]:px-4\",\n icon: \"size-lt-control-md\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n size: \"default\",\n },\n },\n);\n\nfunction Button({\n className,\n variant,\n size,\n asChild = false,\n ...props\n}: React.ComponentProps<\"button\"> &\n VariantProps<typeof buttonVariants> & {\n asChild?: boolean;\n }) {\n const Comp = asChild ? Slot : \"button\";\n\n return (\n <Comp\n data-slot=\"button\"\n className={cn(buttonVariants({ variant, size, className }))}\n {...props}\n />\n );\n}\n\nconst ButtonComponent: RendererComponent<\"button\"> = ({ node }) => {\n const { label, icon } = node.props;\n const variant = node.props.variant ?? \"default\";\n const testId = nodeIdentity(node);\n const behavior = useClickBehavior(node.props);\n const size = icon ? \"icon\" : \"default\";\n const content = icon ? (\n <>\n <IconRenderer className=\"size-lt-icon-md\" icon={icon} />\n {label ? <span className=\"sr-only\">{label}</span> : null}\n </>\n ) : (\n label\n );\n\n const triggerButton = ({ onClick, processing }: TriggerState) => (\n <Button\n data-test={testId}\n disabled={processing}\n onClick={onClick}\n size={size}\n variant={variant}\n >\n {content}\n </Button>\n );\n\n if (behavior.kind === \"navigate\") {\n return (\n <Button asChild data-test={testId} variant={variant} size={size}>\n <Link href={behavior.href} method={behavior.method}>\n {content}\n </Link>\n </Button>\n );\n }\n\n if (behavior.kind === \"action\") {\n return <ActionTrigger action={behavior.action}>{triggerButton}</ActionTrigger>;\n }\n\n if (behavior.kind === \"effects\") {\n return triggerButton({ onClick: behavior.onClick, processing: false });\n }\n\n return (\n <Button data-test={testId} size={size} type={node.props.buttonType} variant={variant}>\n {content}\n </Button>\n );\n};\n\nexport default ButtonComponent;\nexport { Button, buttonVariants };\n"],"mappings":";;;;;;;;;;AAiBA,IAAM,iBAAiB,IACrB,+bACA;CACE,UAAU;EACR,SAAS;GACP,SACE;GACF,aACE;GACF,SACE;GACF,MAAM;GACN,SACE;GACF,WACE;GACF,OAAO;GACP,MAAM;EACR;EACA,MAAM;GACJ,SAAS;GACT,IAAI;GACJ,IAAI;GACJ,MAAM;EACR;CACF;CACA,iBAAiB;EACf,SAAS;EACT,MAAM;CACR;AACF,CACF;AAEA,SAAS,OAAO,EACd,WACA,SACA,MACA,UAAU,OACV,GAAG,SAIA;CAGH,OACE,oBAHW,UAAU,OAAO,UAG5B;EACE,aAAU;EACV,WAAW,GAAG,eAAe;GAAE;GAAS;GAAM;EAAU,CAAC,CAAC;EAC1D,GAAI;CACL,CAAA;AAEL;AAEA,IAAM,mBAAgD,EAAE,WAAW;CACjE,MAAM,EAAE,OAAO,SAAS,KAAK;CAC7B,MAAM,UAAU,KAAK,MAAM,WAAW;CACtC,MAAM,SAAS,aAAa,IAAI;CAChC,MAAM,WAAW,iBAAiB,KAAK,KAAK;CAC5C,MAAM,OAAO,OAAO,SAAS;CAC7B,MAAM,UAAU,OACd,qBAAA,YAAA,EAAA,UAAA,CACE,oBAAC,cAAD;EAAc,WAAU;EAAwB;CAAO,CAAA,GACtD,QAAQ,oBAAC,QAAD;EAAM,WAAU;YAAW;CAAY,CAAA,IAAI,IACpD,EAAA,CAAA,IAEF;CAGF,MAAM,iBAAiB,EAAE,SAAS,iBAChC,oBAAC,QAAD;EACE,aAAW;EACX,UAAU;EACD;EACH;EACG;YAER;CACK,CAAA;CAGV,IAAI,SAAS,SAAS,YACpB,OACE,oBAAC,QAAD;EAAQ,SAAA;EAAQ,aAAW;EAAiB;EAAe;YACzD,oBAAC,MAAD;GAAM,MAAM,SAAS;GAAM,QAAQ,SAAS;aACzC;EACG,CAAA;CACA,CAAA;CAIZ,IAAI,SAAS,SAAS,UACpB,OAAO,oBAAC,eAAD;EAAe,QAAQ,SAAS;YAAS;CAA6B,CAAA;CAG/E,IAAI,SAAS,SAAS,WACpB,OAAO,cAAc;EAAE,SAAS,SAAS;EAAS,YAAY;CAAM,CAAC;CAGvE,OACE,oBAAC,QAAD;EAAQ,aAAW;EAAc;EAAM,MAAM,KAAK,MAAM;EAAqB;YAC1E;CACK,CAAA;AAEZ"}
@@ -1,4 +1,3 @@
1
- import { VariantProps } from 'class-variance-authority';
2
1
  export declare const FOCUS_RING = "focus-visible:border-lt-ring focus-visible:ring-lt-ring/50 focus-visible:ring-[3px]";
3
2
  /**
4
3
  * The shared chrome for single-line form/table controls (text inputs, native
@@ -9,4 +8,3 @@ export declare const FOCUS_RING = "focus-visible:border-lt-ring focus-visible:ri
9
8
  export declare const controlSurface: (props?: ({
10
9
  density?: "compact" | "comfortable" | null | undefined;
11
10
  } & import('class-variance-authority/types').ClassProp) | undefined) => string;
12
- export type ControlSurfaceVariants = VariantProps<typeof controlSurface>;
@@ -1 +1 @@
1
- {"version":3,"file":"control.js","names":[],"sources":["../../../resources/js/core/components/control.ts"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\n\nexport const FOCUS_RING =\n \"focus-visible:border-lt-ring focus-visible:ring-lt-ring/50 focus-visible:ring-[3px]\";\n\n/**\n * The shared chrome for single-line form/table controls (text inputs, native\n * selects, and the Select/filter trigger buttons). `density` is the only axis\n * that differs between forms (comfortable) and table filters (compact); the\n * border, focus ring, invalid, and disabled treatment are unified.\n */\nexport const controlSurface = cva(\n [\n \"w-full min-w-0 rounded-lt-sm border border-lt-input shadow-lt-xs outline-none transition-[color,box-shadow]\",\n \"placeholder:text-lt-muted-fg\",\n FOCUS_RING,\n \"aria-invalid:border-lt-danger aria-invalid:ring-lt-danger/20 dark:aria-invalid:ring-lt-danger/40\",\n \"disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-lt-disabled disabled:text-lt-disabled-fg\",\n ],\n {\n variants: {\n density: {\n comfortable: \"h-lt-control-md bg-transparent px-3 py-1 text-base\",\n compact: \"h-lt-control-md bg-lt-bg px-2 text-sm font-normal\",\n },\n },\n defaultVariants: { density: \"comfortable\" },\n },\n);\n\nexport type ControlSurfaceVariants = VariantProps<typeof controlSurface>;\n"],"mappings":";;AAEA,IAAa,aACX;;;;;;;AAQF,IAAa,iBAAiB,IAC5B;CACE;CACA;CACA;CACA;CACA;AACF,GACA;CACE,UAAU,EACR,SAAS;EACP,aAAa;EACb,SAAS;CACX,EACF;CACA,iBAAiB,EAAE,SAAS,cAAc;AAC5C,CACF"}
1
+ {"version":3,"file":"control.js","names":[],"sources":["../../../resources/js/core/components/control.ts"],"sourcesContent":["import { cva } from \"class-variance-authority\";\n\nexport const FOCUS_RING =\n \"focus-visible:border-lt-ring focus-visible:ring-lt-ring/50 focus-visible:ring-[3px]\";\n\n/**\n * The shared chrome for single-line form/table controls (text inputs, native\n * selects, and the Select/filter trigger buttons). `density` is the only axis\n * that differs between forms (comfortable) and table filters (compact); the\n * border, focus ring, invalid, and disabled treatment are unified.\n */\nexport const controlSurface = cva(\n [\n \"w-full min-w-0 rounded-lt-sm border border-lt-input shadow-lt-xs outline-none transition-[color,box-shadow]\",\n \"placeholder:text-lt-muted-fg\",\n FOCUS_RING,\n \"aria-invalid:border-lt-danger aria-invalid:ring-lt-danger/20 dark:aria-invalid:ring-lt-danger/40\",\n \"disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-lt-disabled disabled:text-lt-disabled-fg\",\n ],\n {\n variants: {\n density: {\n comfortable: \"h-lt-control-md bg-transparent px-3 py-1 text-base\",\n compact: \"h-lt-control-md bg-lt-bg px-2 text-sm font-normal\",\n },\n },\n defaultVariants: { density: \"comfortable\" },\n },\n);\n"],"mappings":";;AAEA,IAAa,aACX;;;;;;;AAQF,IAAa,iBAAiB,IAC5B;CACE;CACA;CACA;CACA;CACA;AACF,GACA;CACE,UAAU,EACR,SAAS;EACP,aAAa;EACb,SAAS;CACX,EACF;CACA,iBAAiB,EAAE,SAAS,cAAc;AAC5C,CACF"}
@@ -1,7 +1,7 @@
1
1
  import { cn } from "../../lib/utils.js";
2
2
  import { IconRenderer } from "../../icons/icon-renderer.js";
3
3
  import { nodeIdentity } from "../test-id.js";
4
- import { useAction } from "../../action/use-action.js";
4
+ import { ActionTrigger, useClickBehavior } from "../use-click-behavior.js";
5
5
  import { actionMenuItemClassName, useActionMenu } from "../../action/components/action-menu-context.js";
6
6
  import { Link } from "@inertiajs/react";
7
7
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
@@ -14,18 +14,6 @@ function TextLink({ className = "", children, ...props }) {
14
14
  children
15
15
  });
16
16
  }
17
- function ActionLink({ action, ariaLabel, className, children, testId }) {
18
- const { processing, requestSubmit, overlays } = useAction(action);
19
- return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
20
- "aria-label": ariaLabel,
21
- className: cn(textLinkClassName, className),
22
- "data-test": testId,
23
- disabled: processing,
24
- onClick: requestSubmit,
25
- type: "button",
26
- children
27
- }), overlays] });
28
- }
29
17
  function LinkAffix({ affix, className }) {
30
18
  if (affix.icon) return /* @__PURE__ */ jsx(IconRenderer, {
31
19
  className: cn("size-lt-icon-md shrink-0", className),
@@ -69,12 +57,10 @@ function labelWithTextAffixes(label, prefix, suffix) {
69
57
  }
70
58
  var LinkComponent = ({ node }) => {
71
59
  const isMenuItem = useActionMenu();
72
- const action = node.props.action;
73
- const icon = node.props.icon;
74
- const label = node.props.label;
75
- const prefix = node.props.prefix;
76
- const suffix = node.props.suffix;
77
- const iconOnly = Boolean(icon);
60
+ const { icon, label, prefix, suffix } = node.props;
61
+ const behavior = useClickBehavior(node.props);
62
+ const className = isMenuItem ? actionMenuItemClassName : void 0;
63
+ const testId = nodeIdentity(node);
78
64
  const content = /* @__PURE__ */ jsx(LinkContent, {
79
65
  icon,
80
66
  isMenuItem,
@@ -82,20 +68,30 @@ var LinkComponent = ({ node }) => {
82
68
  prefix,
83
69
  suffix
84
70
  });
85
- const ariaLabel = iconOnly ? label : labelWithTextAffixes(label, prefix, suffix);
86
- if (action) return /* @__PURE__ */ jsx(ActionLink, {
87
- action,
88
- ariaLabel,
89
- className: isMenuItem ? actionMenuItemClassName : void 0,
90
- testId: nodeIdentity(node),
71
+ const ariaLabel = icon ? label : labelWithTextAffixes(label, prefix, suffix);
72
+ const triggerButton = ({ onClick, processing }) => /* @__PURE__ */ jsx("button", {
73
+ "aria-label": ariaLabel,
74
+ className: cn(textLinkClassName, className),
75
+ "data-test": testId,
76
+ disabled: processing,
77
+ onClick,
78
+ type: "button",
91
79
  children: content
92
80
  });
81
+ if (behavior.kind === "action") return /* @__PURE__ */ jsx(ActionTrigger, {
82
+ action: behavior.action,
83
+ children: triggerButton
84
+ });
85
+ if (behavior.kind === "effects") return triggerButton({
86
+ onClick: behavior.onClick,
87
+ processing: false
88
+ });
93
89
  return /* @__PURE__ */ jsx(TextLink, {
94
90
  "aria-label": ariaLabel,
95
- className: isMenuItem ? actionMenuItemClassName : void 0,
96
- "data-test": nodeIdentity(node),
97
- href: node.props.href ?? "#",
98
- method: node.props.method ?? "get",
91
+ className,
92
+ "data-test": testId,
93
+ href: behavior.kind === "navigate" ? behavior.href : "#",
94
+ method: behavior.kind === "navigate" ? behavior.method : "get",
99
95
  tabIndex: node.props.tabIndex ?? void 0,
100
96
  children: content
101
97
  });
@@ -1 +1 @@
1
- {"version":3,"file":"link.js","names":[],"sources":["../../../resources/js/core/components/link.tsx"],"sourcesContent":["import { Link } from \"@inertiajs/react\";\nimport type { ComponentProps, ReactNode } from \"react\";\nimport { useAction } from \"@lattice-php/lattice/action/use-action\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { nodeIdentity } from \"@lattice-php/lattice/core/test-id\";\nimport type { Node, RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { IconRenderer } from \"@lattice-php/lattice/icons\";\nimport type { Affix } from \"@lattice-php/lattice/types/generated\";\nimport {\n actionMenuItemClassName,\n useActionMenu,\n} from \"@lattice-php/lattice/action/components/action-menu-context\";\n\nconst textLinkClassName =\n \"text-lt-fg underline decoration-lt-border underline-offset-4 transition-colors duration-300 ease-out hover:decoration-current! dark:decoration-lt-border\";\n\nfunction TextLink({ className = \"\", children, ...props }: ComponentProps<typeof Link>) {\n return (\n <Link className={cn(textLinkClassName, className)} {...props}>\n {children}\n </Link>\n );\n}\n\nfunction ActionLink({\n action,\n ariaLabel,\n className,\n children,\n testId,\n}: {\n action: Node<\"action\">;\n ariaLabel?: string;\n className?: string;\n children: ReactNode;\n testId?: string;\n}) {\n const { processing, requestSubmit, overlays } = useAction(action);\n\n return (\n <>\n <button\n aria-label={ariaLabel}\n className={cn(textLinkClassName, className)}\n data-test={testId}\n disabled={processing}\n onClick={requestSubmit}\n type=\"button\"\n >\n {children}\n </button>\n {overlays}\n </>\n );\n}\n\nfunction LinkAffix({ affix, className }: { affix: Affix; className?: string }) {\n if (affix.icon) {\n return <IconRenderer className={cn(\"size-lt-icon-md shrink-0\", className)} icon={affix.icon} />;\n }\n\n return <span className={cn(\"shrink-0 text-sm text-lt-muted-fg\", className)}>{affix.text}</span>;\n}\n\nfunction LinkContent({\n icon,\n isMenuItem,\n label,\n prefix,\n suffix,\n}: {\n icon?: string | null;\n isMenuItem: boolean;\n label: string;\n prefix?: Affix | null;\n suffix?: Affix | null;\n}) {\n if (icon) {\n return <IconRenderer className=\"size-lt-icon-md shrink-0\" icon={icon} />;\n }\n\n if (isMenuItem) {\n return (\n <>\n {prefix ? <LinkAffix affix={prefix} /> : null}\n <span>{label}</span>\n {suffix ? <LinkAffix affix={suffix} className=\"ml-auto\" /> : null}\n </>\n );\n }\n\n if (prefix || suffix) {\n return (\n <span className=\"inline-flex items-center gap-1.5 align-baseline\">\n {prefix ? <LinkAffix affix={prefix} /> : null}\n <span>{label}</span>\n {suffix ? <LinkAffix affix={suffix} /> : null}\n </span>\n );\n }\n\n return label;\n}\n\nfunction labelWithTextAffixes(label: string, prefix?: Affix | null, suffix?: Affix | null) {\n if (!prefix?.text && !suffix?.text) {\n return undefined;\n }\n\n return [prefix?.text, label, suffix?.text]\n .filter((part): part is string => Boolean(part))\n .join(\" \");\n}\n\nconst LinkComponent: RendererComponent<\"link\"> = ({ node }) => {\n const isMenuItem = useActionMenu();\n const action = node.props.action;\n const icon = node.props.icon;\n const label = node.props.label;\n const prefix = node.props.prefix;\n const suffix = node.props.suffix;\n const iconOnly = Boolean(icon);\n const content = (\n <LinkContent\n icon={icon}\n isMenuItem={isMenuItem}\n label={label}\n prefix={prefix}\n suffix={suffix}\n />\n );\n const ariaLabel = iconOnly ? label : labelWithTextAffixes(label, prefix, suffix);\n\n if (action) {\n return (\n <ActionLink\n action={action as Node<\"action\">}\n ariaLabel={ariaLabel}\n className={isMenuItem ? actionMenuItemClassName : undefined}\n testId={nodeIdentity(node)}\n >\n {content}\n </ActionLink>\n );\n }\n\n return (\n <TextLink\n aria-label={ariaLabel}\n className={isMenuItem ? actionMenuItemClassName : undefined}\n data-test={nodeIdentity(node)}\n href={node.props.href ?? \"#\"}\n method={node.props.method ?? \"get\"}\n tabIndex={node.props.tabIndex ?? undefined}\n >\n {content}\n </TextLink>\n );\n};\n\nexport default LinkComponent;\nexport { TextLink };\n"],"mappings":";;;;;;;;AAaA,IAAM,oBACJ;AAEF,SAAS,SAAS,EAAE,YAAY,IAAI,UAAU,GAAG,SAAsC;CACrF,OACE,oBAAC,MAAD;EAAM,WAAW,GAAG,mBAAmB,SAAS;EAAG,GAAI;EACpD;CACG,CAAA;AAEV;AAEA,SAAS,WAAW,EAClB,QACA,WACA,WACA,UACA,UAOC;CACD,MAAM,EAAE,YAAY,eAAe,aAAa,UAAU,MAAM;CAEhE,OACE,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,UAAD;EACE,cAAY;EACZ,WAAW,GAAG,mBAAmB,SAAS;EAC1C,aAAW;EACX,UAAU;EACV,SAAS;EACT,MAAK;EAEJ;CACK,CAAA,GACP,QACD,EAAA,CAAA;AAEN;AAEA,SAAS,UAAU,EAAE,OAAO,aAAmD;CAC7E,IAAI,MAAM,MACR,OAAO,oBAAC,cAAD;EAAc,WAAW,GAAG,4BAA4B,SAAS;EAAG,MAAM,MAAM;CAAO,CAAA;CAGhG,OAAO,oBAAC,QAAD;EAAM,WAAW,GAAG,qCAAqC,SAAS;YAAI,MAAM;CAAW,CAAA;AAChG;AAEA,SAAS,YAAY,EACnB,MACA,YACA,OACA,QACA,UAOC;CACD,IAAI,MACF,OAAO,oBAAC,cAAD;EAAc,WAAU;EAAiC;CAAO,CAAA;CAGzE,IAAI,YACF,OACE,qBAAA,UAAA,EAAA,UAAA;EACG,SAAS,oBAAC,WAAD,EAAW,OAAO,OAAS,CAAA,IAAI;EACzC,oBAAC,QAAD,EAAA,UAAO,MAAY,CAAA;EAClB,SAAS,oBAAC,WAAD;GAAW,OAAO;GAAQ,WAAU;EAAW,CAAA,IAAI;CAC7D,EAAA,CAAA;CAIN,IAAI,UAAU,QACZ,OACE,qBAAC,QAAD;EAAM,WAAU;YAAhB;GACG,SAAS,oBAAC,WAAD,EAAW,OAAO,OAAS,CAAA,IAAI;GACzC,oBAAC,QAAD,EAAA,UAAO,MAAY,CAAA;GAClB,SAAS,oBAAC,WAAD,EAAW,OAAO,OAAS,CAAA,IAAI;EACrC;;CAIV,OAAO;AACT;AAEA,SAAS,qBAAqB,OAAe,QAAuB,QAAuB;CACzF,IAAI,CAAC,QAAQ,QAAQ,CAAC,QAAQ,MAC5B;CAGF,OAAO;EAAC,QAAQ;EAAM;EAAO,QAAQ;CAAI,EACtC,QAAQ,SAAyB,QAAQ,IAAI,CAAC,EAC9C,KAAK,GAAG;AACb;AAEA,IAAM,iBAA4C,EAAE,WAAW;CAC7D,MAAM,aAAa,cAAc;CACjC,MAAM,SAAS,KAAK,MAAM;CAC1B,MAAM,OAAO,KAAK,MAAM;CACxB,MAAM,QAAQ,KAAK,MAAM;CACzB,MAAM,SAAS,KAAK,MAAM;CAC1B,MAAM,SAAS,KAAK,MAAM;CAC1B,MAAM,WAAW,QAAQ,IAAI;CAC7B,MAAM,UACJ,oBAAC,aAAD;EACQ;EACM;EACL;EACC;EACA;CACT,CAAA;CAEH,MAAM,YAAY,WAAW,QAAQ,qBAAqB,OAAO,QAAQ,MAAM;CAE/E,IAAI,QACF,OACE,oBAAC,YAAD;EACU;EACG;EACX,WAAW,aAAa,0BAA0B,KAAA;EAClD,QAAQ,aAAa,IAAI;YAExB;CACS,CAAA;CAIhB,OACE,oBAAC,UAAD;EACE,cAAY;EACZ,WAAW,aAAa,0BAA0B,KAAA;EAClD,aAAW,aAAa,IAAI;EAC5B,MAAM,KAAK,MAAM,QAAQ;EACzB,QAAQ,KAAK,MAAM,UAAU;EAC7B,UAAU,KAAK,MAAM,YAAY,KAAA;YAEhC;CACO,CAAA;AAEd"}
1
+ {"version":3,"file":"link.js","names":[],"sources":["../../../resources/js/core/components/link.tsx"],"sourcesContent":["import { Link } from \"@inertiajs/react\";\nimport type { ComponentProps } from \"react\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { nodeIdentity } from \"@lattice-php/lattice/core/test-id\";\nimport type { RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport {\n ActionTrigger,\n type TriggerState,\n useClickBehavior,\n} from \"@lattice-php/lattice/core/use-click-behavior\";\nimport { IconRenderer } from \"@lattice-php/lattice/icons\";\nimport type { Affix } from \"@lattice-php/lattice/types/generated\";\nimport {\n actionMenuItemClassName,\n useActionMenu,\n} from \"@lattice-php/lattice/action/components/action-menu-context\";\n\nconst textLinkClassName =\n \"text-lt-fg underline decoration-lt-border underline-offset-4 transition-colors duration-300 ease-out hover:decoration-current! dark:decoration-lt-border\";\n\nfunction TextLink({ className = \"\", children, ...props }: ComponentProps<typeof Link>) {\n return (\n <Link className={cn(textLinkClassName, className)} {...props}>\n {children}\n </Link>\n );\n}\n\nfunction LinkAffix({ affix, className }: { affix: Affix; className?: string }) {\n if (affix.icon) {\n return <IconRenderer className={cn(\"size-lt-icon-md shrink-0\", className)} icon={affix.icon} />;\n }\n\n return <span className={cn(\"shrink-0 text-sm text-lt-muted-fg\", className)}>{affix.text}</span>;\n}\n\nfunction LinkContent({\n icon,\n isMenuItem,\n label,\n prefix,\n suffix,\n}: {\n icon?: string | null;\n isMenuItem: boolean;\n label: string;\n prefix?: Affix | null;\n suffix?: Affix | null;\n}) {\n if (icon) {\n return <IconRenderer className=\"size-lt-icon-md shrink-0\" icon={icon} />;\n }\n\n if (isMenuItem) {\n return (\n <>\n {prefix ? <LinkAffix affix={prefix} /> : null}\n <span>{label}</span>\n {suffix ? <LinkAffix affix={suffix} className=\"ml-auto\" /> : null}\n </>\n );\n }\n\n if (prefix || suffix) {\n return (\n <span className=\"inline-flex items-center gap-1.5 align-baseline\">\n {prefix ? <LinkAffix affix={prefix} /> : null}\n <span>{label}</span>\n {suffix ? <LinkAffix affix={suffix} /> : null}\n </span>\n );\n }\n\n return label;\n}\n\nfunction labelWithTextAffixes(label: string, prefix?: Affix | null, suffix?: Affix | null) {\n if (!prefix?.text && !suffix?.text) {\n return undefined;\n }\n\n return [prefix?.text, label, suffix?.text]\n .filter((part): part is string => Boolean(part))\n .join(\" \");\n}\n\nconst LinkComponent: RendererComponent<\"link\"> = ({ node }) => {\n const isMenuItem = useActionMenu();\n const { icon, label, prefix, suffix } = node.props;\n const behavior = useClickBehavior(node.props);\n const className = isMenuItem ? actionMenuItemClassName : undefined;\n const testId = nodeIdentity(node);\n const content = (\n <LinkContent\n icon={icon}\n isMenuItem={isMenuItem}\n label={label}\n prefix={prefix}\n suffix={suffix}\n />\n );\n const ariaLabel = icon ? label : labelWithTextAffixes(label, prefix, suffix);\n\n const triggerButton = ({ onClick, processing }: TriggerState) => (\n <button\n aria-label={ariaLabel}\n className={cn(textLinkClassName, className)}\n data-test={testId}\n disabled={processing}\n onClick={onClick}\n type=\"button\"\n >\n {content}\n </button>\n );\n\n if (behavior.kind === \"action\") {\n return <ActionTrigger action={behavior.action}>{triggerButton}</ActionTrigger>;\n }\n\n if (behavior.kind === \"effects\") {\n return triggerButton({ onClick: behavior.onClick, processing: false });\n }\n\n return (\n <TextLink\n aria-label={ariaLabel}\n className={className}\n data-test={testId}\n href={behavior.kind === \"navigate\" ? behavior.href : \"#\"}\n method={behavior.kind === \"navigate\" ? behavior.method : \"get\"}\n tabIndex={node.props.tabIndex ?? undefined}\n >\n {content}\n </TextLink>\n );\n};\n\nexport default LinkComponent;\nexport { TextLink };\n"],"mappings":";;;;;;;;AAiBA,IAAM,oBACJ;AAEF,SAAS,SAAS,EAAE,YAAY,IAAI,UAAU,GAAG,SAAsC;CACrF,OACE,oBAAC,MAAD;EAAM,WAAW,GAAG,mBAAmB,SAAS;EAAG,GAAI;EACpD;CACG,CAAA;AAEV;AAEA,SAAS,UAAU,EAAE,OAAO,aAAmD;CAC7E,IAAI,MAAM,MACR,OAAO,oBAAC,cAAD;EAAc,WAAW,GAAG,4BAA4B,SAAS;EAAG,MAAM,MAAM;CAAO,CAAA;CAGhG,OAAO,oBAAC,QAAD;EAAM,WAAW,GAAG,qCAAqC,SAAS;YAAI,MAAM;CAAW,CAAA;AAChG;AAEA,SAAS,YAAY,EACnB,MACA,YACA,OACA,QACA,UAOC;CACD,IAAI,MACF,OAAO,oBAAC,cAAD;EAAc,WAAU;EAAiC;CAAO,CAAA;CAGzE,IAAI,YACF,OACE,qBAAA,UAAA,EAAA,UAAA;EACG,SAAS,oBAAC,WAAD,EAAW,OAAO,OAAS,CAAA,IAAI;EACzC,oBAAC,QAAD,EAAA,UAAO,MAAY,CAAA;EAClB,SAAS,oBAAC,WAAD;GAAW,OAAO;GAAQ,WAAU;EAAW,CAAA,IAAI;CAC7D,EAAA,CAAA;CAIN,IAAI,UAAU,QACZ,OACE,qBAAC,QAAD;EAAM,WAAU;YAAhB;GACG,SAAS,oBAAC,WAAD,EAAW,OAAO,OAAS,CAAA,IAAI;GACzC,oBAAC,QAAD,EAAA,UAAO,MAAY,CAAA;GAClB,SAAS,oBAAC,WAAD,EAAW,OAAO,OAAS,CAAA,IAAI;EACrC;;CAIV,OAAO;AACT;AAEA,SAAS,qBAAqB,OAAe,QAAuB,QAAuB;CACzF,IAAI,CAAC,QAAQ,QAAQ,CAAC,QAAQ,MAC5B;CAGF,OAAO;EAAC,QAAQ;EAAM;EAAO,QAAQ;CAAI,EACtC,QAAQ,SAAyB,QAAQ,IAAI,CAAC,EAC9C,KAAK,GAAG;AACb;AAEA,IAAM,iBAA4C,EAAE,WAAW;CAC7D,MAAM,aAAa,cAAc;CACjC,MAAM,EAAE,MAAM,OAAO,QAAQ,WAAW,KAAK;CAC7C,MAAM,WAAW,iBAAiB,KAAK,KAAK;CAC5C,MAAM,YAAY,aAAa,0BAA0B,KAAA;CACzD,MAAM,SAAS,aAAa,IAAI;CAChC,MAAM,UACJ,oBAAC,aAAD;EACQ;EACM;EACL;EACC;EACA;CACT,CAAA;CAEH,MAAM,YAAY,OAAO,QAAQ,qBAAqB,OAAO,QAAQ,MAAM;CAE3E,MAAM,iBAAiB,EAAE,SAAS,iBAChC,oBAAC,UAAD;EACE,cAAY;EACZ,WAAW,GAAG,mBAAmB,SAAS;EAC1C,aAAW;EACX,UAAU;EACD;EACT,MAAK;YAEJ;CACK,CAAA;CAGV,IAAI,SAAS,SAAS,UACpB,OAAO,oBAAC,eAAD;EAAe,QAAQ,SAAS;YAAS;CAA6B,CAAA;CAG/E,IAAI,SAAS,SAAS,WACpB,OAAO,cAAc;EAAE,SAAS,SAAS;EAAS,YAAY;CAAM,CAAC;CAGvE,OACE,oBAAC,UAAD;EACE,cAAY;EACD;EACX,aAAW;EACX,MAAM,SAAS,SAAS,aAAa,SAAS,OAAO;EACrD,QAAQ,SAAS,SAAS,aAAa,SAAS,SAAS;EACzD,UAAU,KAAK,MAAM,YAAY,KAAA;YAEhC;CACO,CAAA;AAEd"}
@@ -1,10 +1,50 @@
1
1
  import { useComponentRegistry } from "./registry-context.js";
2
2
  import { useCollapsed } from "./collapsed-context.js";
3
3
  import { Suspense } from "react";
4
- import { jsx } from "react/jsx-runtime";
4
+ import { jsx, jsxs } from "react/jsx-runtime";
5
5
  //#region resources/js/core/renderer.tsx
6
+ function MissingComponentIcon() {
7
+ return /* @__PURE__ */ jsxs("svg", {
8
+ "aria-hidden": "true",
9
+ className: "size-lt-icon-md shrink-0",
10
+ fill: "none",
11
+ stroke: "currentColor",
12
+ strokeLinecap: "round",
13
+ strokeLinejoin: "round",
14
+ strokeWidth: "2",
15
+ viewBox: "0 0 24 24",
16
+ children: [
17
+ /* @__PURE__ */ jsx("rect", {
18
+ height: "18",
19
+ rx: "2",
20
+ strokeDasharray: "4 3",
21
+ width: "18",
22
+ x: "3",
23
+ y: "3"
24
+ }),
25
+ /* @__PURE__ */ jsx("path", { d: "M12 8v4" }),
26
+ /* @__PURE__ */ jsx("path", { d: "M12 16h.01" })
27
+ ]
28
+ });
29
+ }
30
+ /**
31
+ * Fallback for a node whose type has no registered renderer. Always renders a
32
+ * visible, muted marker — icon-only survives tight spots like table cells — so
33
+ * the gap is never invisible. Shows the type inline in development; keeps it
34
+ * screen-reader-only (plus a hover tooltip) in production.
35
+ */
6
36
  function MissingComponent({ node }) {
7
- return null;
37
+ node.type;
38
+ const label = `Missing component: ${node.type}`;
39
+ return /* @__PURE__ */ jsxs("span", {
40
+ className: "inline-flex items-center gap-1.5 align-middle text-lt-muted-fg",
41
+ "data-lattice-missing-component": node.type,
42
+ title: label,
43
+ children: [/* @__PURE__ */ jsx(MissingComponentIcon, {}), /* @__PURE__ */ jsx("span", {
44
+ className: "sr-only",
45
+ children: label
46
+ })]
47
+ });
8
48
  }
9
49
  function nodeKey(node, index) {
10
50
  return node.key ?? node.id ?? `${node.type}-${index}`;
@@ -1 +1 @@
1
- {"version":3,"file":"renderer.js","names":[],"sources":["../../resources/js/core/renderer.tsx"],"sourcesContent":["import { Suspense } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { Node } from \"./types\";\nimport { useCollapsed } from \"./collapsed-context\";\nimport { useComponentRegistry } from \"./registry-context\";\n\nfunction MissingComponent({ node }: { node: Node }) {\n if (!import.meta.env.DEV) {\n return null;\n }\n\n return <div data-lattice-missing-component={node.type}>Missing component: {node.type}</div>;\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\nfunction 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":";;;;;AAMA,SAAS,iBAAiB,EAAE,QAAwB;CAEhD,OAAO;AAIX;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,SAAS,aAAa,EAAE,QAAwB;CAC9C,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"}
1
+ {"version":3,"file":"renderer.js","names":[],"sources":["../../resources/js/core/renderer.tsx"],"sourcesContent":["import { 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\nfunction 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,SAAS,aAAa,EAAE,QAAwB;CAC9C,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"}
@@ -1,58 +1,33 @@
1
1
  import { ComponentType as ReactComponentType, ReactNode } from 'react';
2
- import { Node as WireNode, NodeType, Option, PageContainer as KnownPageContainer, ListenerPayload } from '../types/generated';
3
- export type { KnownPageContainer, NodeType, WireNode };
4
- /** Loose props bag for nodes whose `type` is not a generated built-in. */
2
+ import { ComponentPropsMap, NodeType, Option, PageContainer as KnownPageContainer, WireNode, ListenerPayload } from '../types/generated';
3
+ export type { KnownPageContainer, NodeType, Option, WireNode };
5
4
  export type NodeProps = Record<string, unknown>;
6
5
  export type CommonNodeProps = {
7
6
  dataBindings?: Record<string, string> | null;
8
7
  hideWhenCollapsed?: boolean | null;
9
8
  };
10
- /** A `{ label, value }` pair used by the choice, select and segmented controls. */
11
- export type { Option };
12
9
  /**
13
- * A node whose `type` is not one of the generated built-ins. This is the escape
14
- * hatch that keeps the renderer open to components registered by consumers.
15
- */
16
- export type LooseNode<TType extends string = string> = {
17
- id?: string;
18
- key?: string;
19
- props?: NodeProps;
20
- schema?: Schema;
21
- type: TType;
22
- };
23
- /**
24
- * Augmentable map of wire `type` string → props. Consumer apps extend it via
25
- * `declare module "@lattice-php/lattice"` so their custom components gain strong
26
- * prop types; the entries are generated by `php artisan lattice:typescript`.
27
- * Built-in components resolve through the generated `WireNode` union below, so
28
- * this interface starts empty. Types listed in neither fall back to a loose bag.
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.
29
16
  */
30
17
  export interface ComponentProps {
31
18
  }
32
19
  /**
33
- * Resolves a registry `type` string to its payload: consumer augmentations
34
- * (`TAugment`) first, then generated built-ins (`TBuiltins`), then a loose
35
- * fallback. Shared by the component, column, chat-part and effect type maps so
36
- * every extensible kind resolves the same way.
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.
37
23
  */
38
24
  export type ResolveProps<TAugment, TBuiltins, TType extends string, TFallback> = TType extends keyof TAugment ? TAugment[TType] : TType extends keyof TBuiltins ? TBuiltins[TType] : TFallback;
39
- type WireNodeProps = {
40
- [TNode in WireNode as TNode["type"]]: TNode extends {
41
- props: infer TProps;
42
- } ? TProps : never;
43
- };
44
- /**
45
- * Resolves a wire `type` to its props: consumer augmentations
46
- * (`ComponentProps`) first, then generated built-ins, then a loose bag.
47
- */
48
- export type PropsOf<TType extends string> = ResolveProps<ComponentProps, WireNodeProps, TType, NodeProps> & CommonNodeProps;
25
+ export type PropsOf<TType extends string> = ResolveProps<ComponentProps, ComponentPropsMap, TType, NodeProps> & CommonNodeProps;
49
26
  /**
50
- * Resolves a wire `type` string to its node shape: built-ins narrow to their
51
- * generated props, unknown types fall back to a loose props bag so custom
52
- * components still type-check. Built-in `props` is always present — the wire
53
- * serializes the full prop object, so reads need no optional chaining.
27
+ * 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`.
54
29
  */
55
- export type NodeOfType<TType extends string = string> = string extends TType ? LooseNode : {
30
+ export type NodeOfType<TType extends string = string> = string extends TType ? WireNode : {
56
31
  id?: string;
57
32
  key?: string;
58
33
  props: PropsOf<TType>;
@@ -60,9 +35,14 @@ export type NodeOfType<TType extends string = string> = string extends TType ? L
60
35
  type: TType;
61
36
  };
62
37
  export type Node<TType extends string = string> = NodeOfType<TType>;
63
- /** An ordered list of component nodes — the content of a page, form, or container. */
38
+ /**
39
+ * Distributes so `NodeUnionOf<"a" | "b">` is `NodeOfType<"a"> | NodeOfType<"b">`
40
+ * — a discriminated union where `node.type` narrows `node.props`. Domains build
41
+ * their node union from a generated `…NodeType` string union with this.
42
+ */
43
+ export type NodeUnionOf<TTypes extends string> = TTypes extends string ? NodeOfType<TTypes> : never;
64
44
  export type Schema = Node[];
65
- /** A server-composed layout shell: its key plus a schema containing one Outlet. */
45
+ /** Its `schema` holds exactly one Outlet node. */
66
46
  export type LayoutPayload = {
67
47
  key: string;
68
48
  schema: Schema;
@@ -0,0 +1,42 @@
1
+ import { Method } from '@inertiajs/core';
2
+ import { ReactNode } from 'react';
3
+ import { Node } from './types';
4
+ import { Effect } from '../types/generated';
5
+ export type ClickBehavior = {
6
+ kind: "navigate";
7
+ href: string;
8
+ method: Method;
9
+ } | {
10
+ kind: "action";
11
+ action: Node<"action">;
12
+ } | {
13
+ kind: "effects";
14
+ onClick: () => void;
15
+ } | {
16
+ kind: "none";
17
+ };
18
+ /**
19
+ * The shared click model for Link/Button/MenuItem: a clickable carries exactly
20
+ * one behavior — navigate to an href, run a server action, or dispatch client
21
+ * effects. This resolves it without touching the action machinery, so plain
22
+ * links/labels stay cheap; the `action` behavior renders through {@link ActionTrigger}.
23
+ */
24
+ export declare function useClickBehavior(props: {
25
+ href?: string | null;
26
+ method?: Method | null;
27
+ action?: Node | null;
28
+ effects?: Effect[] | null;
29
+ }): ClickBehavior;
30
+ export type TriggerState = {
31
+ onClick: () => void;
32
+ processing: boolean;
33
+ };
34
+ /**
35
+ * Runs a server action's gate (confirm/form) and renders its overlays, handing
36
+ * the host a click handler + processing state to render its own element with.
37
+ * Only mounted for the `action` behavior, so a plain link never runs `useAction`.
38
+ */
39
+ export declare function ActionTrigger({ action, children, }: {
40
+ action: Node<"action">;
41
+ children: (trigger: TriggerState) => ReactNode;
42
+ }): import("react").JSX.Element;
@@ -0,0 +1,46 @@
1
+ import { getActionEffects } from "../effects/dispatch.js";
2
+ import { useEffectDispatcher } from "../effects/use-effect-dispatcher.js";
3
+ import { useAction } from "../action/use-action.js";
4
+ import { Fragment, jsxs } from "react/jsx-runtime";
5
+ //#region resources/js/core/use-click-behavior.tsx
6
+ /**
7
+ * The shared click model for Link/Button/MenuItem: a clickable carries exactly
8
+ * one behavior — navigate to an href, run a server action, or dispatch client
9
+ * effects. This resolves it without touching the action machinery, so plain
10
+ * links/labels stay cheap; the `action` behavior renders through {@link ActionTrigger}.
11
+ */
12
+ function useClickBehavior(props) {
13
+ const dispatch = useEffectDispatcher();
14
+ const action = props.action ?? null;
15
+ const effects = props.effects ?? [];
16
+ if (action) return {
17
+ kind: "action",
18
+ action
19
+ };
20
+ if (effects.length > 0) return {
21
+ kind: "effects",
22
+ onClick: () => dispatch(getActionEffects(effects))
23
+ };
24
+ if (props.href != null && props.href !== "") return {
25
+ kind: "navigate",
26
+ href: props.href,
27
+ method: props.method ?? "get"
28
+ };
29
+ return { kind: "none" };
30
+ }
31
+ /**
32
+ * Runs a server action's gate (confirm/form) and renders its overlays, handing
33
+ * the host a click handler + processing state to render its own element with.
34
+ * Only mounted for the `action` behavior, so a plain link never runs `useAction`.
35
+ */
36
+ function ActionTrigger({ action, children }) {
37
+ const { processing, requestSubmit, overlays } = useAction(action);
38
+ return /* @__PURE__ */ jsxs(Fragment, { children: [children({
39
+ onClick: requestSubmit,
40
+ processing
41
+ }), overlays] });
42
+ }
43
+ //#endregion
44
+ export { ActionTrigger, useClickBehavior };
45
+
46
+ //# sourceMappingURL=use-click-behavior.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-click-behavior.js","names":[],"sources":["../../resources/js/core/use-click-behavior.tsx"],"sourcesContent":["import type { Method } from \"@inertiajs/core\";\nimport type { ReactNode } from \"react\";\nimport { useAction } from \"@lattice-php/lattice/action/use-action\";\nimport type { Node } from \"@lattice-php/lattice/core/types\";\nimport { getActionEffects } from \"@lattice-php/lattice/effects/dispatch\";\nimport { useEffectDispatcher } from \"@lattice-php/lattice/effects/use-effect-dispatcher\";\nimport type { Effect } from \"@lattice-php/lattice/types/generated\";\n\nexport type ClickBehavior =\n | { kind: \"navigate\"; href: string; method: Method }\n | { kind: \"action\"; action: Node<\"action\"> }\n | { kind: \"effects\"; onClick: () => void }\n | { kind: \"none\" };\n\n/**\n * The shared click model for Link/Button/MenuItem: a clickable carries exactly\n * one behavior — navigate to an href, run a server action, or dispatch client\n * effects. This resolves it without touching the action machinery, so plain\n * links/labels stay cheap; the `action` behavior renders through {@link ActionTrigger}.\n */\nexport function useClickBehavior(props: {\n href?: string | null;\n method?: Method | null;\n action?: Node | null;\n effects?: Effect[] | null;\n}): ClickBehavior {\n const dispatch = useEffectDispatcher();\n const action = (props.action ?? null) as Node<\"action\"> | null;\n const effects = props.effects ?? [];\n\n if (action) {\n return { kind: \"action\", action };\n }\n\n if (effects.length > 0) {\n return { kind: \"effects\", onClick: () => dispatch(getActionEffects(effects)) };\n }\n\n if (props.href != null && props.href !== \"\") {\n return { kind: \"navigate\", href: props.href, method: props.method ?? \"get\" };\n }\n\n return { kind: \"none\" };\n}\n\nexport type TriggerState = { onClick: () => void; processing: boolean };\n\n/**\n * Runs a server action's gate (confirm/form) and renders its overlays, handing\n * the host a click handler + processing state to render its own element with.\n * Only mounted for the `action` behavior, so a plain link never runs `useAction`.\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"],"mappings":";;;;;;;;;;;AAoBA,SAAgB,iBAAiB,OAKf;CAChB,MAAM,WAAW,oBAAoB;CACrC,MAAM,SAAU,MAAM,UAAU;CAChC,MAAM,UAAU,MAAM,WAAW,CAAC;CAElC,IAAI,QACF,OAAO;EAAE,MAAM;EAAU;CAAO;CAGlC,IAAI,QAAQ,SAAS,GACnB,OAAO;EAAE,MAAM;EAAW,eAAe,SAAS,iBAAiB,OAAO,CAAC;CAAE;CAG/E,IAAI,MAAM,QAAQ,QAAQ,MAAM,SAAS,IACvC,OAAO;EAAE,MAAM;EAAY,MAAM,MAAM;EAAM,QAAQ,MAAM,UAAU;CAAM;CAG7E,OAAO,EAAE,MAAM,OAAO;AACxB;;;;;;AASA,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"}
@@ -1,11 +1,17 @@
1
1
  import { createInertiaApp } from '@inertiajs/react';
2
2
  import { ReactElement } from 'react';
3
- import { Registry } from './core/registry';
3
+ import { Plugin, Registry } from './core/registry';
4
4
  import { SpriteValue } from './icons/sprite';
5
5
  import { CreateLayoutResolverOptions, PageModules } from './inertia';
6
6
  type InertiaAppOptions = NonNullable<Parameters<typeof createInertiaApp>[0]>;
7
7
  export type CreateLatticeAppOptions = Omit<InertiaAppOptions, "resolve" | "layout" | "setup" | "withApp" | "pages"> & {
8
8
  registry?: Registry;
9
+ /**
10
+ * Component-package plugins to merge onto the registry — pass the
11
+ * Composer-discovered `virtual:lattice/plugins` here to register vendor
12
+ * components with no manual `extendRegistry` call.
13
+ */
14
+ plugins?: Plugin[];
9
15
  sprite?: SpriteValue;
10
16
  /** Normal Inertia page modules, e.g. `import.meta.glob('./pages/**\/*.tsx')`. */
11
17
  pages?: PageModules;
@@ -17,5 +23,5 @@ export type CreateLatticeAppOptions = Omit<InertiaAppOptions, "resolve" | "layou
17
23
  * registry, sprite, flash toasts via Lattice's own Toaster — and theme
18
24
  * initialization. Forwards any other createInertiaApp option.
19
25
  */
20
- export declare function createLatticeApp({ registry, 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>)>;
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>)>;
21
27
  export {};
@@ -1,4 +1,5 @@
1
1
  import { initializeTheme } from "./appearance/index.js";
2
+ import { extendRegistry } from "./core/registry.js";
2
3
  import { setDefaultRegistry } from "./core/registry-context.js";
3
4
  import { createLayoutResolver, createPageResolver } from "./inertia.js";
4
5
  import { ProviderBase } from "./provider-base.js";
@@ -12,8 +13,9 @@ import { jsx } from "react/jsx-runtime";
12
13
  * registry, sprite, flash toasts via Lattice's own Toaster — and theme
13
14
  * initialization. Forwards any other createInertiaApp option.
14
15
  */
15
- function createLatticeApp({ registry, sprite, pages = {}, defaultLayout, strictMode = true, ...inertiaOptions } = {}) {
16
- const activeRegistry = registry ?? lazyRegistry;
16
+ function createLatticeApp({ registry, plugins, sprite, pages = {}, defaultLayout, strictMode = true, ...inertiaOptions } = {}) {
17
+ const baseRegistry = registry ?? lazyRegistry;
18
+ const activeRegistry = plugins && plugins.length > 0 ? extendRegistry(baseRegistry, ...plugins) : baseRegistry;
17
19
  setDefaultRegistry(activeRegistry);
18
20
  const app = createInertiaApp({
19
21
  ...inertiaOptions,