@lattice-php/lattice 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/action/components/action.js +16 -85
- package/dist/action/components/action.js.map +1 -1
- package/dist/action/use-action.d.ts +18 -0
- package/dist/action/use-action.js +90 -0
- package/dist/action/use-action.js.map +1 -0
- package/dist/core/components/control.d.ts +0 -1
- package/dist/core/components/control.js +0 -1
- package/dist/core/components/control.js.map +1 -1
- package/dist/core/components/link.js +24 -3
- package/dist/core/components/link.js.map +1 -1
- package/dist/core/components/tabs.js +8 -3
- package/dist/core/components/tabs.js.map +1 -1
- package/dist/core/renderer.d.ts +0 -2
- package/dist/core/renderer.js +0 -2
- package/dist/core/renderer.js.map +1 -1
- package/dist/effects/dispatch.d.ts +0 -1
- package/dist/effects/dispatch.js.map +1 -1
- package/dist/effects/registry.js +0 -1
- package/dist/effects/registry.js.map +1 -1
- package/dist/form/components/base/input-otp.d.ts +0 -1
- package/dist/form/components/base/input-otp.js +0 -1
- package/dist/form/components/base/input-otp.js.map +1 -1
- package/dist/form/components/field-props.d.ts +0 -1
- package/dist/form/components/field-props.js +0 -1
- package/dist/form/components/field-props.js.map +1 -1
- package/dist/form/components/fields/repeater-rows.d.ts +0 -2
- package/dist/form/components/fields/repeater-rows.js +0 -2
- package/dist/form/components/fields/repeater-rows.js.map +1 -1
- package/dist/form/components/fields/simple-field.d.ts +0 -1
- package/dist/form/components/fields/simple-field.js +0 -1
- package/dist/form/components/fields/simple-field.js.map +1 -1
- package/dist/form/components/use-controlled-field.d.ts +0 -1
- package/dist/form/components/use-controlled-field.js +0 -1
- package/dist/form/components/use-controlled-field.js.map +1 -1
- package/dist/index.js +3 -3
- package/dist/layout/menu-item.js +21 -0
- package/dist/layout/menu-item.js.map +1 -1
- package/dist/table/components/cells/boolean-cell.d.ts +0 -1
- package/dist/table/components/cells/boolean-cell.js +0 -1
- package/dist/table/components/cells/boolean-cell.js.map +1 -1
- package/dist/table/components/cells/numeric.d.ts +0 -1
- package/dist/table/components/cells/numeric.js +0 -1
- package/dist/table/components/cells/numeric.js.map +1 -1
- package/dist/table/components/cells/stack-cell.d.ts +0 -1
- package/dist/table/components/cells/stack-cell.js +0 -1
- package/dist/table/components/cells/stack-cell.js.map +1 -1
- package/dist/table/registry.d.ts +0 -2
- package/dist/table/registry.js.map +1 -1
- package/dist/toast/callout.d.ts +0 -2
- package/dist/toast/callout.js +0 -2
- package/dist/toast/callout.js.map +1 -1
- package/dist/toast/toast.d.ts +0 -3
- package/dist/toast/toast.js +0 -3
- package/dist/toast/toast.js.map +1 -1
- package/dist/types/generated.d.ts +4 -0
- package/package.json +1 -1
|
@@ -1,101 +1,32 @@
|
|
|
1
|
-
import { withHeaders } from "../../core/headers.js";
|
|
2
1
|
import { IconRenderer } from "../../icons/icon-renderer.js";
|
|
3
2
|
import { prefixedTestId } from "../../core/test-id.js";
|
|
4
|
-
import { getActionEffects } from "../../effects/dispatch.js";
|
|
5
|
-
import { useEffectDispatcher } from "../../effects/use-effect-dispatcher.js";
|
|
6
3
|
import { Button } from "../../core/components/button.js";
|
|
7
4
|
import { Spinner } from "../../core/components/spinner.js";
|
|
8
|
-
import {
|
|
9
|
-
import { runAction } from "../run-action.js";
|
|
10
|
-
import { ActionForm, useLazyActionForm } from "./action-form.js";
|
|
5
|
+
import { useAction } from "../use-action.js";
|
|
11
6
|
import { actionMenuItemClassName, useActionMenu } from "./action-menu-context.js";
|
|
12
|
-
import {
|
|
13
|
-
import { useState } from "react";
|
|
14
|
-
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
7
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
15
8
|
//#region resources/js/action/components/action.tsx
|
|
16
9
|
var ActionComponent = ({ node }) => {
|
|
17
10
|
const endpoint = node.props.endpoint ?? "";
|
|
18
11
|
const icon = node.props.icon;
|
|
19
12
|
const label = node.props.label ?? "Run action";
|
|
20
|
-
const componentRef = node.props.ref ?? "";
|
|
21
|
-
const method = node.props.method ?? "post";
|
|
22
|
-
const http = useHttp({});
|
|
23
|
-
const dispatch = useEffectDispatcher();
|
|
24
|
-
const [isConfirming, setIsConfirming] = useState(false);
|
|
25
|
-
const [isFilling, setIsFilling] = useState(false);
|
|
26
13
|
const isMenuItem = useActionMenu();
|
|
27
|
-
const confirmation = node.props.confirmation;
|
|
28
14
|
const variant = node.props.variant ?? "default";
|
|
29
|
-
const
|
|
30
|
-
const lazyForm = node.props.lazyForm === true;
|
|
31
|
-
const hasForm = Boolean(inlineForm) || lazyForm;
|
|
32
|
-
const lazyNode = useLazyActionForm(endpoint, componentRef, isFilling && lazyForm);
|
|
33
|
-
const formNode = lazyForm ? lazyNode : inlineForm;
|
|
34
|
-
const submit = async () => {
|
|
35
|
-
if (!endpoint) return;
|
|
36
|
-
if (method === "get") {
|
|
37
|
-
router.visit(endpoint, { headers: withHeaders(componentRef) });
|
|
38
|
-
setIsConfirming(false);
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
if (await runAction(() => http[method](endpoint, { headers: withHeaders(componentRef) }), dispatch)) setIsConfirming(false);
|
|
42
|
-
};
|
|
43
|
-
const requestSubmit = () => {
|
|
44
|
-
if (hasForm) {
|
|
45
|
-
setIsFilling(true);
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
if (confirmation) {
|
|
49
|
-
setIsConfirming(true);
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
submit();
|
|
53
|
-
};
|
|
54
|
-
const confirmationTitle = confirmation?.title ?? label;
|
|
55
|
-
const confirmationConfirmLabel = confirmation?.confirmLabel ?? label;
|
|
56
|
-
const confirmationCancelLabel = confirmation?.cancelLabel ?? "Cancel";
|
|
15
|
+
const { processing, requestSubmit, overlays } = useAction(node);
|
|
57
16
|
const testId = node.key ?? prefixedTestId("action", node.id);
|
|
58
|
-
return /* @__PURE__ */ jsxs(Fragment
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}),
|
|
72
|
-
isConfirming && confirmation && /* @__PURE__ */ jsx(ConfirmDialog, {
|
|
73
|
-
title: confirmationTitle,
|
|
74
|
-
description: confirmation.description ?? void 0,
|
|
75
|
-
confirmLabel: confirmationConfirmLabel,
|
|
76
|
-
cancelLabel: confirmationCancelLabel,
|
|
77
|
-
confirmVariant: variant,
|
|
78
|
-
processing: http.processing,
|
|
79
|
-
confirmDisabled: !endpoint,
|
|
80
|
-
onConfirm: () => void submit(),
|
|
81
|
-
onCancel: () => setIsConfirming(false)
|
|
82
|
-
}),
|
|
83
|
-
isFilling && hasForm && /* @__PURE__ */ jsx(ActionForm, {
|
|
84
|
-
cancelLabel: confirmationCancelLabel,
|
|
85
|
-
componentRef,
|
|
86
|
-
description: confirmation?.description ?? void 0,
|
|
87
|
-
endpoint,
|
|
88
|
-
formNode,
|
|
89
|
-
method,
|
|
90
|
-
onClose: () => setIsFilling(false),
|
|
91
|
-
onSuccess: (response) => {
|
|
92
|
-
dispatch(getActionEffects(response.effects));
|
|
93
|
-
setIsFilling(false);
|
|
94
|
-
},
|
|
95
|
-
submitLabel: confirmationConfirmLabel,
|
|
96
|
-
title: confirmationTitle
|
|
97
|
-
})
|
|
98
|
-
] });
|
|
17
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Button, {
|
|
18
|
+
className: isMenuItem ? actionMenuItemClassName : void 0,
|
|
19
|
+
"data-lattice-component": node.id,
|
|
20
|
+
"data-test": testId,
|
|
21
|
+
disabled: processing || !endpoint,
|
|
22
|
+
onClick: requestSubmit,
|
|
23
|
+
type: "button",
|
|
24
|
+
variant: isMenuItem ? "ghost" : variant,
|
|
25
|
+
children: [processing ? /* @__PURE__ */ jsx(Spinner, { className: isMenuItem ? "size-lt-icon-sm" : void 0 }) : icon && /* @__PURE__ */ jsx(IconRenderer, {
|
|
26
|
+
className: isMenuItem ? "size-lt-icon-sm" : "size-lt-icon-md",
|
|
27
|
+
icon
|
|
28
|
+
}), label]
|
|
29
|
+
}), overlays] });
|
|
99
30
|
};
|
|
100
31
|
//#endregion
|
|
101
32
|
export { ActionComponent as default };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action.js","names":[],"sources":["../../../resources/js/action/components/action.tsx"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"action.js","names":[],"sources":["../../../resources/js/action/components/action.tsx"],"sourcesContent":["import { Button } from \"@lattice-php/lattice/core/components/button\";\nimport { Spinner } from \"@lattice-php/lattice/core/components/spinner\";\nimport { prefixedTestId } from \"@lattice-php/lattice/core/test-id\";\nimport type { RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { IconRenderer } from \"@lattice-php/lattice/icons\";\nimport { useAction } from \"../use-action\";\nimport { actionMenuItemClassName, useActionMenu } from \"./action-menu-context\";\n\nconst ActionComponent: RendererComponent<\"action\"> = ({ node }) => {\n const endpoint = node.props.endpoint ?? \"\";\n const icon = node.props.icon;\n const label = node.props.label ?? \"Run action\";\n const isMenuItem = useActionMenu();\n const variant = node.props.variant ?? \"default\";\n const { processing, requestSubmit, overlays } = useAction(node);\n const testId = node.key ?? prefixedTestId(\"action\", node.id);\n\n return (\n <>\n <Button\n className={isMenuItem ? actionMenuItemClassName : undefined}\n data-lattice-component={node.id}\n data-test={testId}\n disabled={processing || !endpoint}\n onClick={requestSubmit}\n type=\"button\"\n variant={isMenuItem ? \"ghost\" : variant}\n >\n {processing ? (\n <Spinner className={isMenuItem ? \"size-lt-icon-sm\" : undefined} />\n ) : (\n icon && (\n <IconRenderer\n className={isMenuItem ? \"size-lt-icon-sm\" : \"size-lt-icon-md\"}\n icon={icon}\n />\n )\n )}\n {label}\n </Button>\n\n {overlays}\n </>\n );\n};\n\nexport default ActionComponent;\n"],"mappings":";;;;;;;;AAQA,IAAM,mBAAgD,EAAE,WAAW;CACjE,MAAM,WAAW,KAAK,MAAM,YAAY;CACxC,MAAM,OAAO,KAAK,MAAM;CACxB,MAAM,QAAQ,KAAK,MAAM,SAAS;CAClC,MAAM,aAAa,cAAc;CACjC,MAAM,UAAU,KAAK,MAAM,WAAW;CACtC,MAAM,EAAE,YAAY,eAAe,aAAa,UAAU,IAAI;CAC9D,MAAM,SAAS,KAAK,OAAO,eAAe,UAAU,KAAK,EAAE;CAE3D,OACE,qBAAA,UAAA,EAAA,UAAA,CACE,qBAAC,QAAD;EACE,WAAW,aAAa,0BAA0B,KAAA;EAClD,0BAAwB,KAAK;EAC7B,aAAW;EACX,UAAU,cAAc,CAAC;EACzB,SAAS;EACT,MAAK;EACL,SAAS,aAAa,UAAU;YAPlC,CASG,aACC,oBAAC,SAAD,EAAS,WAAW,aAAa,oBAAoB,KAAA,EAAY,CAAA,IAEjE,QACE,oBAAC,cAAD;GACE,WAAW,aAAa,oBAAoB;GACtC;EACP,CAAA,GAGJ,KACK;KAEP,QACD,EAAA,CAAA;AAEN"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { Node } from '../core/types';
|
|
3
|
+
type UseAction = {
|
|
4
|
+
/** Whether the action request is in flight. */
|
|
5
|
+
processing: boolean;
|
|
6
|
+
/** Gate then run the action: open the form, confirm, or dispatch directly. */
|
|
7
|
+
requestSubmit: () => void;
|
|
8
|
+
/** The confirm dialog and action form rendered next to the trigger. */
|
|
9
|
+
overlays: ReactNode;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* The shared action machinery behind the Action button, action menu items, and
|
|
13
|
+
* action links: it gates submission (form → modal, confirmation → confirm,
|
|
14
|
+
* otherwise dispatch) and renders the matching overlays. The host owns the
|
|
15
|
+
* trigger element so each surface keeps its own styling.
|
|
16
|
+
*/
|
|
17
|
+
export declare function useAction(node: Node<"action">): UseAction;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { withHeaders } from "../core/headers.js";
|
|
2
|
+
import { getActionEffects } from "../effects/dispatch.js";
|
|
3
|
+
import { useEffectDispatcher } from "../effects/use-effect-dispatcher.js";
|
|
4
|
+
import { ConfirmDialog } from "../core/components/confirm-dialog.js";
|
|
5
|
+
import { runAction } from "./run-action.js";
|
|
6
|
+
import { ActionForm, useLazyActionForm } from "./components/action-form.js";
|
|
7
|
+
import { router, useHttp } from "@inertiajs/react";
|
|
8
|
+
import { useState } from "react";
|
|
9
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
//#region resources/js/action/use-action.tsx
|
|
11
|
+
/**
|
|
12
|
+
* The shared action machinery behind the Action button, action menu items, and
|
|
13
|
+
* action links: it gates submission (form → modal, confirmation → confirm,
|
|
14
|
+
* otherwise dispatch) and renders the matching overlays. The host owns the
|
|
15
|
+
* trigger element so each surface keeps its own styling.
|
|
16
|
+
*/
|
|
17
|
+
function useAction(node) {
|
|
18
|
+
const endpoint = node.props.endpoint ?? "";
|
|
19
|
+
const componentRef = node.props.ref ?? "";
|
|
20
|
+
const method = node.props.method ?? "post";
|
|
21
|
+
const label = node.props.label ?? "Run action";
|
|
22
|
+
const variant = node.props.variant ?? "default";
|
|
23
|
+
const confirmation = node.props.confirmation;
|
|
24
|
+
const inlineForm = node.props.form;
|
|
25
|
+
const lazyForm = node.props.lazyForm === true;
|
|
26
|
+
const hasForm = Boolean(inlineForm) || lazyForm;
|
|
27
|
+
const http = useHttp({});
|
|
28
|
+
const dispatch = useEffectDispatcher();
|
|
29
|
+
const [isConfirming, setIsConfirming] = useState(false);
|
|
30
|
+
const [isFilling, setIsFilling] = useState(false);
|
|
31
|
+
const lazyNode = useLazyActionForm(endpoint, componentRef, isFilling && lazyForm);
|
|
32
|
+
const formNode = lazyForm ? lazyNode : inlineForm;
|
|
33
|
+
const submit = async () => {
|
|
34
|
+
if (!endpoint) return;
|
|
35
|
+
if (method === "get") {
|
|
36
|
+
router.visit(endpoint, { headers: withHeaders(componentRef) });
|
|
37
|
+
setIsConfirming(false);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (await runAction(() => http[method](endpoint, { headers: withHeaders(componentRef) }), dispatch)) setIsConfirming(false);
|
|
41
|
+
};
|
|
42
|
+
const requestSubmit = () => {
|
|
43
|
+
if (hasForm) {
|
|
44
|
+
setIsFilling(true);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (confirmation) {
|
|
48
|
+
setIsConfirming(true);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
submit();
|
|
52
|
+
};
|
|
53
|
+
const confirmationTitle = confirmation?.title ?? label;
|
|
54
|
+
const confirmationConfirmLabel = confirmation?.confirmLabel ?? label;
|
|
55
|
+
const confirmationCancelLabel = confirmation?.cancelLabel ?? "Cancel";
|
|
56
|
+
const overlays = /* @__PURE__ */ jsxs(Fragment$1, { children: [isConfirming && confirmation && /* @__PURE__ */ jsx(ConfirmDialog, {
|
|
57
|
+
title: confirmationTitle,
|
|
58
|
+
description: confirmation.description ?? void 0,
|
|
59
|
+
confirmLabel: confirmationConfirmLabel,
|
|
60
|
+
cancelLabel: confirmationCancelLabel,
|
|
61
|
+
confirmVariant: variant,
|
|
62
|
+
processing: http.processing,
|
|
63
|
+
confirmDisabled: !endpoint,
|
|
64
|
+
onConfirm: () => void submit(),
|
|
65
|
+
onCancel: () => setIsConfirming(false)
|
|
66
|
+
}), isFilling && hasForm && /* @__PURE__ */ jsx(ActionForm, {
|
|
67
|
+
cancelLabel: confirmationCancelLabel,
|
|
68
|
+
componentRef,
|
|
69
|
+
description: confirmation?.description ?? void 0,
|
|
70
|
+
endpoint,
|
|
71
|
+
formNode,
|
|
72
|
+
method,
|
|
73
|
+
onClose: () => setIsFilling(false),
|
|
74
|
+
onSuccess: (response) => {
|
|
75
|
+
dispatch(getActionEffects(response.effects));
|
|
76
|
+
setIsFilling(false);
|
|
77
|
+
},
|
|
78
|
+
submitLabel: confirmationConfirmLabel,
|
|
79
|
+
title: confirmationTitle
|
|
80
|
+
})] });
|
|
81
|
+
return {
|
|
82
|
+
processing: http.processing,
|
|
83
|
+
requestSubmit,
|
|
84
|
+
overlays
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
export { useAction };
|
|
89
|
+
|
|
90
|
+
//# sourceMappingURL=use-action.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-action.js","names":[],"sources":["../../resources/js/action/use-action.tsx"],"sourcesContent":["import { router, useHttp } from \"@inertiajs/react\";\nimport type { Method } from \"@inertiajs/core\";\nimport { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { ConfirmDialog } from \"@lattice-php/lattice/core/components/confirm-dialog\";\nimport { withHeaders } from \"@lattice-php/lattice/core/headers\";\nimport type { Node } from \"@lattice-php/lattice/core/types\";\nimport { getActionEffects } from \"@lattice-php/lattice/effects/dispatch\";\nimport type { ActionResponse } from \"@lattice-php/lattice/effects/dispatch\";\nimport { useEffectDispatcher } from \"@lattice-php/lattice/effects/use-effect-dispatcher\";\nimport { runAction } from \"./run-action\";\nimport { ActionForm, useLazyActionForm } from \"./components/action-form\";\n\ntype UseAction = {\n /** Whether the action request is in flight. */\n processing: boolean;\n /** Gate then run the action: open the form, confirm, or dispatch directly. */\n requestSubmit: () => void;\n /** The confirm dialog and action form rendered next to the trigger. */\n overlays: ReactNode;\n};\n\n/**\n * The shared action machinery behind the Action button, action menu items, and\n * action links: it gates submission (form → modal, confirmation → confirm,\n * otherwise dispatch) and renders the matching overlays. The host owns the\n * trigger element so each surface keeps its own styling.\n */\nexport function useAction(node: Node<\"action\">): UseAction {\n const endpoint = node.props.endpoint ?? \"\";\n const componentRef = node.props.ref ?? \"\";\n const method: Method = node.props.method ?? \"post\";\n const label = node.props.label ?? \"Run action\";\n const variant = node.props.variant ?? \"default\";\n const confirmation = node.props.confirmation;\n const inlineForm = node.props.form;\n const lazyForm = node.props.lazyForm === true;\n const hasForm = Boolean(inlineForm) || lazyForm;\n\n const http = useHttp<Record<string, never>, ActionResponse>({});\n const dispatch = useEffectDispatcher();\n const [isConfirming, setIsConfirming] = useState(false);\n const [isFilling, setIsFilling] = useState(false);\n const lazyNode = useLazyActionForm(endpoint, componentRef, isFilling && lazyForm);\n const formNode = lazyForm ? lazyNode : inlineForm;\n\n const submit = async (): Promise<void> => {\n if (!endpoint) {\n return;\n }\n\n if (method === \"get\") {\n router.visit(endpoint, { headers: withHeaders(componentRef) });\n setIsConfirming(false);\n\n return;\n }\n\n const ok = await runAction(\n () => http[method](endpoint, { headers: withHeaders(componentRef) }),\n dispatch,\n );\n\n if (ok) {\n setIsConfirming(false);\n }\n };\n\n const requestSubmit = (): void => {\n if (hasForm) {\n setIsFilling(true);\n\n return;\n }\n\n if (confirmation) {\n setIsConfirming(true);\n\n return;\n }\n\n void submit();\n };\n\n const confirmationTitle = confirmation?.title ?? label;\n const confirmationConfirmLabel = confirmation?.confirmLabel ?? label;\n const confirmationCancelLabel = confirmation?.cancelLabel ?? \"Cancel\";\n\n const overlays = (\n <>\n {isConfirming && confirmation && (\n <ConfirmDialog\n title={confirmationTitle}\n description={confirmation.description ?? undefined}\n confirmLabel={confirmationConfirmLabel}\n cancelLabel={confirmationCancelLabel}\n confirmVariant={variant}\n processing={http.processing}\n confirmDisabled={!endpoint}\n onConfirm={() => void submit()}\n onCancel={() => setIsConfirming(false)}\n />\n )}\n\n {isFilling && hasForm && (\n <ActionForm\n cancelLabel={confirmationCancelLabel}\n componentRef={componentRef}\n description={confirmation?.description ?? undefined}\n endpoint={endpoint}\n formNode={formNode}\n method={method}\n onClose={() => setIsFilling(false)}\n onSuccess={(response) => {\n dispatch(getActionEffects(response.effects));\n setIsFilling(false);\n }}\n submitLabel={confirmationConfirmLabel}\n title={confirmationTitle}\n />\n )}\n </>\n );\n\n return { processing: http.processing, requestSubmit, overlays };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA4BA,SAAgB,UAAU,MAAiC;CACzD,MAAM,WAAW,KAAK,MAAM,YAAY;CACxC,MAAM,eAAe,KAAK,MAAM,OAAO;CACvC,MAAM,SAAiB,KAAK,MAAM,UAAU;CAC5C,MAAM,QAAQ,KAAK,MAAM,SAAS;CAClC,MAAM,UAAU,KAAK,MAAM,WAAW;CACtC,MAAM,eAAe,KAAK,MAAM;CAChC,MAAM,aAAa,KAAK,MAAM;CAC9B,MAAM,WAAW,KAAK,MAAM,aAAa;CACzC,MAAM,UAAU,QAAQ,UAAU,KAAK;CAEvC,MAAM,OAAO,QAA+C,CAAC,CAAC;CAC9D,MAAM,WAAW,oBAAoB;CACrC,MAAM,CAAC,cAAc,mBAAmB,SAAS,KAAK;CACtD,MAAM,CAAC,WAAW,gBAAgB,SAAS,KAAK;CAChD,MAAM,WAAW,kBAAkB,UAAU,cAAc,aAAa,QAAQ;CAChF,MAAM,WAAW,WAAW,WAAW;CAEvC,MAAM,SAAS,YAA2B;EACxC,IAAI,CAAC,UACH;EAGF,IAAI,WAAW,OAAO;GACpB,OAAO,MAAM,UAAU,EAAE,SAAS,YAAY,YAAY,EAAE,CAAC;GAC7D,gBAAgB,KAAK;GAErB;EACF;EAOA,IAAI,MALa,gBACT,KAAK,QAAQ,UAAU,EAAE,SAAS,YAAY,YAAY,EAAE,CAAC,GACnE,QACF,GAGE,gBAAgB,KAAK;CAEzB;CAEA,MAAM,sBAA4B;EAChC,IAAI,SAAS;GACX,aAAa,IAAI;GAEjB;EACF;EAEA,IAAI,cAAc;GAChB,gBAAgB,IAAI;GAEpB;EACF;EAEA,OAAY;CACd;CAEA,MAAM,oBAAoB,cAAc,SAAS;CACjD,MAAM,2BAA2B,cAAc,gBAAgB;CAC/D,MAAM,0BAA0B,cAAc,eAAe;CAE7D,MAAM,WACJ,qBAAA,YAAA,EAAA,UAAA,CACG,gBAAgB,gBACf,oBAAC,eAAD;EACE,OAAO;EACP,aAAa,aAAa,eAAe,KAAA;EACzC,cAAc;EACd,aAAa;EACb,gBAAgB;EAChB,YAAY,KAAK;EACjB,iBAAiB,CAAC;EAClB,iBAAiB,KAAK,OAAO;EAC7B,gBAAgB,gBAAgB,KAAK;CACtC,CAAA,GAGF,aAAa,WACZ,oBAAC,YAAD;EACE,aAAa;EACC;EACd,aAAa,cAAc,eAAe,KAAA;EAChC;EACA;EACF;EACR,eAAe,aAAa,KAAK;EACjC,YAAY,aAAa;GACvB,SAAS,iBAAiB,SAAS,OAAO,CAAC;GAC3C,aAAa,KAAK;EACpB;EACA,aAAa;EACb,OAAO;CACR,CAAA,CAEH,EAAA,CAAA;CAGJ,OAAO;EAAE,YAAY,KAAK;EAAY;EAAe;CAAS;AAChE"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { VariantProps } from 'class-variance-authority';
|
|
2
|
-
/** The focus-visible ring shared by every interactive control surface. */
|
|
3
2
|
export declare const FOCUS_RING = "focus-visible:border-lt-ring focus-visible:ring-lt-ring/50 focus-visible:ring-[3px]";
|
|
4
3
|
/**
|
|
5
4
|
* The shared chrome for single-line form/table controls (text inputs, native
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { cva } from "class-variance-authority";
|
|
2
2
|
//#region resources/js/core/components/control.ts
|
|
3
|
-
/** The focus-visible ring shared by every interactive control surface. */
|
|
4
3
|
var FOCUS_RING = "focus-visible:border-lt-ring focus-visible:ring-lt-ring/50 focus-visible:ring-[3px]";
|
|
5
4
|
/**
|
|
6
5
|
* The shared chrome for single-line form/table controls (text inputs, native
|
|
@@ -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\
|
|
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,19 +1,40 @@
|
|
|
1
1
|
import { cn } from "../../lib/utils.js";
|
|
2
2
|
import { nodeIdentity } from "../test-id.js";
|
|
3
|
+
import { useAction } from "../../action/use-action.js";
|
|
3
4
|
import { actionMenuItemClassName, useActionMenu } from "../../action/components/action-menu-context.js";
|
|
4
5
|
import { Link } from "@inertiajs/react";
|
|
5
|
-
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
6
7
|
//#region resources/js/core/components/link.tsx
|
|
8
|
+
var textLinkClassName = "text-lt-fg underline decoration-lt-border underline-offset-4 transition-colors duration-300 ease-out hover:decoration-current! dark:decoration-lt-border";
|
|
7
9
|
function TextLink({ className = "", children, ...props }) {
|
|
8
10
|
return /* @__PURE__ */ jsx(Link, {
|
|
9
|
-
className: cn(
|
|
11
|
+
className: cn(textLinkClassName, className),
|
|
10
12
|
...props,
|
|
11
13
|
children
|
|
12
14
|
});
|
|
13
15
|
}
|
|
16
|
+
function ActionLink({ action, className, children, testId }) {
|
|
17
|
+
const { processing, requestSubmit, overlays } = useAction(action);
|
|
18
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("button", {
|
|
19
|
+
className: cn(textLinkClassName, className),
|
|
20
|
+
"data-test": testId,
|
|
21
|
+
disabled: processing,
|
|
22
|
+
onClick: requestSubmit,
|
|
23
|
+
type: "button",
|
|
24
|
+
children
|
|
25
|
+
}), overlays] });
|
|
26
|
+
}
|
|
14
27
|
var LinkComponent = ({ node }) => {
|
|
28
|
+
const isMenuItem = useActionMenu();
|
|
29
|
+
const action = node.props.action;
|
|
30
|
+
if (action) return /* @__PURE__ */ jsx(ActionLink, {
|
|
31
|
+
action,
|
|
32
|
+
className: isMenuItem ? actionMenuItemClassName : void 0,
|
|
33
|
+
testId: nodeIdentity(node),
|
|
34
|
+
children: node.props.label
|
|
35
|
+
});
|
|
15
36
|
return /* @__PURE__ */ jsx(TextLink, {
|
|
16
|
-
className:
|
|
37
|
+
className: isMenuItem ? actionMenuItemClassName : void 0,
|
|
17
38
|
"data-test": nodeIdentity(node),
|
|
18
39
|
href: node.props.href ?? "#",
|
|
19
40
|
method: node.props.method ?? "get",
|
|
@@ -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 } 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 actionMenuItemClassName,\n useActionMenu,\n} from \"@lattice-php/lattice/action/components/action-menu-context\";\n\nfunction TextLink({ className = \"\", children, ...props }: ComponentProps<typeof Link>) {\n return (\n <Link
|
|
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 {\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 className,\n children,\n testId,\n}: {\n action: Node<\"action\">;\n className?: string;\n children: ReactNode;\n testId?: string;\n}) {\n const { processing, requestSubmit, overlays } = useAction(action);\n\n return (\n <>\n <button\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\nconst LinkComponent: RendererComponent<\"link\"> = ({ node }) => {\n const isMenuItem = useActionMenu();\n const action = node.props.action;\n\n if (action) {\n return (\n <ActionLink\n action={action as Node<\"action\">}\n className={isMenuItem ? actionMenuItemClassName : undefined}\n testId={nodeIdentity(node)}\n >\n {node.props.label}\n </ActionLink>\n );\n }\n\n return (\n <TextLink\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 {node.props.label}\n </TextLink>\n );\n};\n\nexport default LinkComponent;\nexport { TextLink };\n"],"mappings":";;;;;;;AAWA,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,UACA,UAMC;CACD,MAAM,EAAE,YAAY,eAAe,aAAa,UAAU,MAAM;CAEhE,OACE,qBAAA,UAAA,EAAA,UAAA,CACE,oBAAC,UAAD;EACE,WAAW,GAAG,mBAAmB,SAAS;EAC1C,aAAW;EACX,UAAU;EACV,SAAS;EACT,MAAK;EAEJ;CACK,CAAA,GACP,QACD,EAAA,CAAA;AAEN;AAEA,IAAM,iBAA4C,EAAE,WAAW;CAC7D,MAAM,aAAa,cAAc;CACjC,MAAM,SAAS,KAAK,MAAM;CAE1B,IAAI,QACF,OACE,oBAAC,YAAD;EACU;EACR,WAAW,aAAa,0BAA0B,KAAA;EAClD,QAAQ,aAAa,IAAI;YAExB,KAAK,MAAM;CACF,CAAA;CAIhB,OACE,oBAAC,UAAD;EACE,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,KAAK,MAAM;CACJ,CAAA;AAEd"}
|
|
@@ -48,6 +48,8 @@ var TabsComponent = ({ children, node }) => {
|
|
|
48
48
|
const queryKey = node.props.queryKey;
|
|
49
49
|
const orientation = node.props.orientation;
|
|
50
50
|
const isVertical = orientation === "vertical";
|
|
51
|
+
const alignment = node.props.alignment;
|
|
52
|
+
const isStretched = !isVertical && alignment === "stretch";
|
|
51
53
|
const serverActiveValue = node.props.activeValue;
|
|
52
54
|
const defaultValue = node.props.defaultValue ?? firstValue;
|
|
53
55
|
const tablistRef = useRef(null);
|
|
@@ -87,12 +89,15 @@ var TabsComponent = ({ children, node }) => {
|
|
|
87
89
|
setActiveValue: selectTabValue
|
|
88
90
|
},
|
|
89
91
|
children: /* @__PURE__ */ jsxs("div", {
|
|
90
|
-
className: cn("gap-6", isVertical ? "flex" : "grid"),
|
|
92
|
+
className: cn("gap-6", isVertical ? cn("flex", alignment === "end" && "flex-row-reverse") : "grid"),
|
|
91
93
|
"data-lattice-tabs": node.key ?? node.id,
|
|
92
94
|
children: [/* @__PURE__ */ jsx("div", {
|
|
93
95
|
"aria-label": t("a11y.tabs", "Tabs"),
|
|
94
96
|
"aria-orientation": orientation,
|
|
95
|
-
className: cn("gap-1 rounded-lt bg-lt-muted p-1", isVertical
|
|
97
|
+
className: cn("gap-1 rounded-lt bg-lt-muted p-1", isVertical && "flex flex-col", isStretched && "flex w-full", !isVertical && !isStretched && cn("inline-flex w-fit max-w-full overflow-x-auto", {
|
|
98
|
+
"justify-self-center": alignment === "center",
|
|
99
|
+
"justify-self-end": alignment === "end"
|
|
100
|
+
})),
|
|
96
101
|
ref: tablistRef,
|
|
97
102
|
role: "tablist",
|
|
98
103
|
children: tabs.map((tab) => {
|
|
@@ -101,7 +106,7 @@ var TabsComponent = ({ children, node }) => {
|
|
|
101
106
|
"aria-controls": `${tab.value}-panel`,
|
|
102
107
|
"aria-selected": isActive,
|
|
103
108
|
"data-test": `tab-${tab.value}`,
|
|
104
|
-
className: cn("whitespace-nowrap rounded-lt-sm px-3 py-1.5 text-sm font-medium transition-colors", isActive ? "bg-lt-bg text-lt-fg shadow-lt-xs" : "text-lt-muted-fg hover:bg-lt-bg/60 hover:text-lt-fg", isVertical && "text-left"),
|
|
109
|
+
className: cn("whitespace-nowrap rounded-lt-sm px-3 py-1.5 text-sm font-medium transition-colors", isActive ? "bg-lt-bg text-lt-fg shadow-lt-xs" : "text-lt-muted-fg hover:bg-lt-bg/60 hover:text-lt-fg", isVertical && "text-left", isStretched && "flex-1"),
|
|
105
110
|
id: `${tab.value}-tab`,
|
|
106
111
|
onClick: () => selectTab(tab),
|
|
107
112
|
onKeyDown: onTablistKeyDown,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tabs.js","names":[],"sources":["../../../resources/js/core/components/tabs.tsx"],"sourcesContent":["import { router } from \"@inertiajs/react\";\nimport { createContext, useContext, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { KeyboardEvent, ReactNode } from \"react\";\nimport type { Node, RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport type { Tab } from \"@lattice-php/lattice/types/generated\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\n\ntype TabsContextValue = {\n activeValue: string;\n setActiveValue: (value: string) => void;\n};\n\nconst TabsContext = createContext<TabsContextValue | null>(null);\n\nfunction useTabsContext(): TabsContextValue {\n const context = useContext(TabsContext);\n\n if (!context) {\n return {\n activeValue: \"\",\n setActiveValue: () => {},\n };\n }\n\n return context;\n}\n\ntype TabItem = {\n confirm?: {\n required?: boolean;\n };\n label: string;\n value: string;\n};\n\nfunction getTabs(node: Node): TabItem[] {\n return (node.schema ?? [])\n .filter((child) => child.type === \"tab\")\n .map((child) => {\n const props = child.props as unknown as Tab;\n\n return {\n confirm: props.confirm ? { required: props.confirm.required } : undefined,\n label: props.label,\n value: props.value,\n };\n })\n .filter((tab) => tab.value !== \"\");\n}\n\nfunction queryValue(queryKey: string, tabs: Array<{ value: string }>): string | null {\n if (typeof window === \"undefined\") {\n return null;\n }\n\n const value = new URLSearchParams(window.location.search).get(queryKey);\n\n if (!value || !tabs.some((tab) => tab.value === value)) {\n return null;\n }\n\n return value;\n}\n\nfunction replaceQueryValue(queryKey: string, value: string): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n const url = new URL(window.location.href);\n url.searchParams.set(queryKey, value);\n window.history.replaceState(window.history.state, \"\", `${url.pathname}${url.search}${url.hash}`);\n}\n\nfunction queryUrl(queryKey: string, value: string): string {\n if (typeof window === \"undefined\") {\n return \"\";\n }\n\n const url = new URL(window.location.href);\n url.searchParams.set(queryKey, value);\n\n return `${url.pathname}${url.search}${url.hash}`;\n}\n\nexport const TabsComponent: RendererComponent<\"tabs\"> = ({ children, node }) => {\n const { t } = useT(\"lattice\");\n const tabs = useMemo(() => getTabs(node), [node]);\n const firstValue = tabs[0]?.value ?? \"\";\n const queryKey = node.props.queryKey;\n const orientation = node.props.orientation;\n const isVertical = orientation === \"vertical\";\n const serverActiveValue = node.props.activeValue;\n const defaultValue = node.props.defaultValue ?? firstValue;\n const tablistRef = useRef<HTMLDivElement>(null);\n const [activeValue, setActiveTabValue] = useState(\n () => serverActiveValue || (queryValue(queryKey, tabs) ?? defaultValue) || firstValue,\n );\n\n function selectTab(tab: TabItem): void {\n if (tab.confirm?.required) {\n router.visit(queryUrl(queryKey, tab.value), {\n preserveScroll: true,\n });\n\n return;\n }\n\n setActiveTabValue(tab.value);\n replaceQueryValue(queryKey, tab.value);\n }\n\n function selectTabValue(value: string): void {\n const tab = tabs.find((item) => item.value === value);\n\n if (tab) {\n selectTab(tab);\n }\n }\n\n function onTablistKeyDown(event: KeyboardEvent<HTMLButtonElement>): void {\n const nextKey = isVertical ? \"ArrowDown\" : \"ArrowRight\";\n const prevKey = isVertical ? \"ArrowUp\" : \"ArrowLeft\";\n\n if (\n event.key !== nextKey &&\n event.key !== prevKey &&\n event.key !== \"Home\" &&\n event.key !== \"End\"\n ) {\n return;\n }\n\n const buttons = Array.from(\n tablistRef.current?.querySelectorAll<HTMLButtonElement>('[role=\"tab\"]') ?? [],\n );\n\n if (buttons.length === 0) {\n return;\n }\n\n event.preventDefault();\n const current = buttons.indexOf(document.activeElement as HTMLButtonElement);\n\n let index: number;\n if (event.key === \"Home\") {\n index = 0;\n } else if (event.key === \"End\") {\n index = buttons.length - 1;\n } else {\n const delta = event.key === nextKey ? 1 : -1;\n const base = current < 0 ? 0 : current;\n index = (base + delta + buttons.length) % buttons.length;\n }\n\n buttons[index]?.focus();\n }\n\n return (\n <TabsContext.Provider value={{ activeValue, setActiveValue: selectTabValue }}>\n <div\n className={cn(\"gap-6\", isVertical ? \"flex\" : \"grid\")}\n data-lattice-tabs={node.key ?? node.id}\n >\n <div\n aria-label={t(\"a11y.tabs\", \"Tabs\")}\n aria-orientation={orientation}\n className={cn(\n \"gap-1 rounded-lt bg-lt-muted p-1\",\n isVertical ? \"flex flex-col\" : \"inline-flex w-fit max-w-full overflow-x-auto\",\n )}\n ref={tablistRef}\n role=\"tablist\"\n >\n {tabs.map((tab) => {\n const isActive = activeValue === tab.value;\n\n return (\n <button\n aria-controls={`${tab.value}-panel`}\n aria-selected={isActive}\n data-test={`tab-${tab.value}`}\n className={cn(\n \"whitespace-nowrap rounded-lt-sm px-3 py-1.5 text-sm font-medium transition-colors\",\n isActive\n ? \"bg-lt-bg text-lt-fg shadow-lt-xs\"\n : \"text-lt-muted-fg hover:bg-lt-bg/60 hover:text-lt-fg\",\n isVertical && \"text-left\",\n )}\n id={`${tab.value}-tab`}\n key={tab.value}\n onClick={() => selectTab(tab)}\n onKeyDown={onTablistKeyDown}\n role=\"tab\"\n tabIndex={isActive ? 0 : -1}\n type=\"button\"\n >\n {tab.label}\n </button>\n );\n })}\n </div>\n\n <div className={cn(\"min-w-0\", isVertical && \"flex-1\")}>{children}</div>\n </div>\n </TabsContext.Provider>\n );\n};\n\nconst TabComponent: RendererComponent<\"tab\"> = ({ children, node }) => {\n const { activeValue } = useTabsContext();\n const value = node.props.value;\n const isActive = activeValue === value;\n const [hasOpened, setHasOpened] = useState(isActive);\n\n useEffect(() => {\n if (isActive) {\n setHasOpened(true);\n }\n }, [isActive]);\n\n return (\n <section\n aria-labelledby={`${value}-tab`}\n className={cn(\"space-y-8\", !isActive && \"hidden\")}\n hidden={!isActive}\n id={`${value}-panel`}\n role=\"tabpanel\"\n tabIndex={0}\n >\n {hasOpened ? (children as ReactNode) : null}\n </section>\n );\n};\n\nexport default TabComponent;\n"],"mappings":";;;;;;AAaA,IAAM,cAAc,cAAuC,IAAI;AAE/D,SAAS,iBAAmC;CAC1C,MAAM,UAAU,WAAW,WAAW;CAEtC,IAAI,CAAC,SACH,OAAO;EACL,aAAa;EACb,sBAAsB,CAAC;CACzB;CAGF,OAAO;AACT;AAUA,SAAS,QAAQ,MAAuB;CACtC,QAAQ,KAAK,UAAU,CAAC,GACrB,QAAQ,UAAU,MAAM,SAAS,KAAK,EACtC,KAAK,UAAU;EACd,MAAM,QAAQ,MAAM;EAEpB,OAAO;GACL,SAAS,MAAM,UAAU,EAAE,UAAU,MAAM,QAAQ,SAAS,IAAI,KAAA;GAChE,OAAO,MAAM;GACb,OAAO,MAAM;EACf;CACF,CAAC,EACA,QAAQ,QAAQ,IAAI,UAAU,EAAE;AACrC;AAEA,SAAS,WAAW,UAAkB,MAA+C;CACnF,IAAI,OAAO,WAAW,aACpB,OAAO;CAGT,MAAM,QAAQ,IAAI,gBAAgB,OAAO,SAAS,MAAM,EAAE,IAAI,QAAQ;CAEtE,IAAI,CAAC,SAAS,CAAC,KAAK,MAAM,QAAQ,IAAI,UAAU,KAAK,GACnD,OAAO;CAGT,OAAO;AACT;AAEA,SAAS,kBAAkB,UAAkB,OAAqB;CAChE,IAAI,OAAO,WAAW,aACpB;CAGF,MAAM,MAAM,IAAI,IAAI,OAAO,SAAS,IAAI;CACxC,IAAI,aAAa,IAAI,UAAU,KAAK;CACpC,OAAO,QAAQ,aAAa,OAAO,QAAQ,OAAO,IAAI,GAAG,IAAI,WAAW,IAAI,SAAS,IAAI,MAAM;AACjG;AAEA,SAAS,SAAS,UAAkB,OAAuB;CACzD,IAAI,OAAO,WAAW,aACpB,OAAO;CAGT,MAAM,MAAM,IAAI,IAAI,OAAO,SAAS,IAAI;CACxC,IAAI,aAAa,IAAI,UAAU,KAAK;CAEpC,OAAO,GAAG,IAAI,WAAW,IAAI,SAAS,IAAI;AAC5C;AAEA,IAAa,iBAA4C,EAAE,UAAU,WAAW;CAC9E,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,MAAM,OAAO,cAAc,QAAQ,IAAI,GAAG,CAAC,IAAI,CAAC;CAChD,MAAM,aAAa,KAAK,IAAI,SAAS;CACrC,MAAM,WAAW,KAAK,MAAM;CAC5B,MAAM,cAAc,KAAK,MAAM;CAC/B,MAAM,aAAa,gBAAgB;CACnC,MAAM,oBAAoB,KAAK,MAAM;CACrC,MAAM,eAAe,KAAK,MAAM,gBAAgB;CAChD,MAAM,aAAa,OAAuB,IAAI;CAC9C,MAAM,CAAC,aAAa,qBAAqB,eACjC,sBAAsB,WAAW,UAAU,IAAI,KAAK,iBAAiB,UAC7E;CAEA,SAAS,UAAU,KAAoB;EACrC,IAAI,IAAI,SAAS,UAAU;GACzB,OAAO,MAAM,SAAS,UAAU,IAAI,KAAK,GAAG,EAC1C,gBAAgB,KAClB,CAAC;GAED;EACF;EAEA,kBAAkB,IAAI,KAAK;EAC3B,kBAAkB,UAAU,IAAI,KAAK;CACvC;CAEA,SAAS,eAAe,OAAqB;EAC3C,MAAM,MAAM,KAAK,MAAM,SAAS,KAAK,UAAU,KAAK;EAEpD,IAAI,KACF,UAAU,GAAG;CAEjB;CAEA,SAAS,iBAAiB,OAA+C;EACvE,MAAM,UAAU,aAAa,cAAc;EAC3C,MAAM,UAAU,aAAa,YAAY;EAEzC,IACE,MAAM,QAAQ,WACd,MAAM,QAAQ,WACd,MAAM,QAAQ,UACd,MAAM,QAAQ,OAEd;EAGF,MAAM,UAAU,MAAM,KACpB,WAAW,SAAS,iBAAoC,gBAAc,KAAK,CAAC,CAC9E;EAEA,IAAI,QAAQ,WAAW,GACrB;EAGF,MAAM,eAAe;EACrB,MAAM,UAAU,QAAQ,QAAQ,SAAS,aAAkC;EAE3E,IAAI;EACJ,IAAI,MAAM,QAAQ,QAChB,QAAQ;OACH,IAAI,MAAM,QAAQ,OACvB,QAAQ,QAAQ,SAAS;OACpB;GACL,MAAM,QAAQ,MAAM,QAAQ,UAAU,IAAI;GAE1C,UADa,UAAU,IAAI,IAAI,WACf,QAAQ,QAAQ,UAAU,QAAQ;EACpD;EAEA,QAAQ,QAAQ,MAAM;CACxB;CAEA,OACE,oBAAC,YAAY,UAAb;EAAsB,OAAO;GAAE;GAAa,gBAAgB;EAAe;YACzE,qBAAC,OAAD;GACE,WAAW,GAAG,SAAS,aAAa,SAAS,MAAM;GACnD,qBAAmB,KAAK,OAAO,KAAK;aAFtC,CAIE,oBAAC,OAAD;IACE,cAAY,EAAE,aAAa,MAAM;IACjC,oBAAkB;IAClB,WAAW,GACT,oCACA,aAAa,kBAAkB,8CACjC;IACA,KAAK;IACL,MAAK;cAEJ,KAAK,KAAK,QAAQ;KACjB,MAAM,WAAW,gBAAgB,IAAI;KAErC,OACE,oBAAC,UAAD;MACE,iBAAe,GAAG,IAAI,MAAM;MAC5B,iBAAe;MACf,aAAW,OAAO,IAAI;MACtB,WAAW,GACT,qFACA,WACI,qCACA,uDACJ,cAAc,WAChB;MACA,IAAI,GAAG,IAAI,MAAM;MAEjB,eAAe,UAAU,GAAG;MAC5B,WAAW;MACX,MAAK;MACL,UAAU,WAAW,IAAI;MACzB,MAAK;gBAEJ,IAAI;KACC,GARD,IAAI,KAQH;IAEZ,CAAC;GACE,CAAA,GAEL,oBAAC,OAAD;IAAK,WAAW,GAAG,WAAW,cAAc,QAAQ;IAAI;GAAc,CAAA,CACnE;;CACe,CAAA;AAE1B;AAEA,IAAM,gBAA0C,EAAE,UAAU,WAAW;CACrE,MAAM,EAAE,gBAAgB,eAAe;CACvC,MAAM,QAAQ,KAAK,MAAM;CACzB,MAAM,WAAW,gBAAgB;CACjC,MAAM,CAAC,WAAW,gBAAgB,SAAS,QAAQ;CAEnD,gBAAgB;EACd,IAAI,UACF,aAAa,IAAI;CAErB,GAAG,CAAC,QAAQ,CAAC;CAEb,OACE,oBAAC,WAAD;EACE,mBAAiB,GAAG,MAAM;EAC1B,WAAW,GAAG,aAAa,CAAC,YAAY,QAAQ;EAChD,QAAQ,CAAC;EACT,IAAI,GAAG,MAAM;EACb,MAAK;EACL,UAAU;YAET,YAAa,WAAyB;CAChC,CAAA;AAEb"}
|
|
1
|
+
{"version":3,"file":"tabs.js","names":[],"sources":["../../../resources/js/core/components/tabs.tsx"],"sourcesContent":["import { router } from \"@inertiajs/react\";\nimport { createContext, useContext, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { KeyboardEvent, ReactNode } from \"react\";\nimport type { Node, RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport type { Tab } from \"@lattice-php/lattice/types/generated\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\n\ntype TabsContextValue = {\n activeValue: string;\n setActiveValue: (value: string) => void;\n};\n\nconst TabsContext = createContext<TabsContextValue | null>(null);\n\nfunction useTabsContext(): TabsContextValue {\n const context = useContext(TabsContext);\n\n if (!context) {\n return {\n activeValue: \"\",\n setActiveValue: () => {},\n };\n }\n\n return context;\n}\n\ntype TabItem = {\n confirm?: {\n required?: boolean;\n };\n label: string;\n value: string;\n};\n\nfunction getTabs(node: Node): TabItem[] {\n return (node.schema ?? [])\n .filter((child) => child.type === \"tab\")\n .map((child) => {\n const props = child.props as unknown as Tab;\n\n return {\n confirm: props.confirm ? { required: props.confirm.required } : undefined,\n label: props.label,\n value: props.value,\n };\n })\n .filter((tab) => tab.value !== \"\");\n}\n\nfunction queryValue(queryKey: string, tabs: Array<{ value: string }>): string | null {\n if (typeof window === \"undefined\") {\n return null;\n }\n\n const value = new URLSearchParams(window.location.search).get(queryKey);\n\n if (!value || !tabs.some((tab) => tab.value === value)) {\n return null;\n }\n\n return value;\n}\n\nfunction replaceQueryValue(queryKey: string, value: string): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n const url = new URL(window.location.href);\n url.searchParams.set(queryKey, value);\n window.history.replaceState(window.history.state, \"\", `${url.pathname}${url.search}${url.hash}`);\n}\n\nfunction queryUrl(queryKey: string, value: string): string {\n if (typeof window === \"undefined\") {\n return \"\";\n }\n\n const url = new URL(window.location.href);\n url.searchParams.set(queryKey, value);\n\n return `${url.pathname}${url.search}${url.hash}`;\n}\n\nexport const TabsComponent: RendererComponent<\"tabs\"> = ({ children, node }) => {\n const { t } = useT(\"lattice\");\n const tabs = useMemo(() => getTabs(node), [node]);\n const firstValue = tabs[0]?.value ?? \"\";\n const queryKey = node.props.queryKey;\n const orientation = node.props.orientation;\n const isVertical = orientation === \"vertical\";\n const alignment = node.props.alignment;\n const isStretched = !isVertical && alignment === \"stretch\";\n const serverActiveValue = node.props.activeValue;\n const defaultValue = node.props.defaultValue ?? firstValue;\n const tablistRef = useRef<HTMLDivElement>(null);\n const [activeValue, setActiveTabValue] = useState(\n () => serverActiveValue || (queryValue(queryKey, tabs) ?? defaultValue) || firstValue,\n );\n\n function selectTab(tab: TabItem): void {\n if (tab.confirm?.required) {\n router.visit(queryUrl(queryKey, tab.value), {\n preserveScroll: true,\n });\n\n return;\n }\n\n setActiveTabValue(tab.value);\n replaceQueryValue(queryKey, tab.value);\n }\n\n function selectTabValue(value: string): void {\n const tab = tabs.find((item) => item.value === value);\n\n if (tab) {\n selectTab(tab);\n }\n }\n\n function onTablistKeyDown(event: KeyboardEvent<HTMLButtonElement>): void {\n const nextKey = isVertical ? \"ArrowDown\" : \"ArrowRight\";\n const prevKey = isVertical ? \"ArrowUp\" : \"ArrowLeft\";\n\n if (\n event.key !== nextKey &&\n event.key !== prevKey &&\n event.key !== \"Home\" &&\n event.key !== \"End\"\n ) {\n return;\n }\n\n const buttons = Array.from(\n tablistRef.current?.querySelectorAll<HTMLButtonElement>('[role=\"tab\"]') ?? [],\n );\n\n if (buttons.length === 0) {\n return;\n }\n\n event.preventDefault();\n const current = buttons.indexOf(document.activeElement as HTMLButtonElement);\n\n let index: number;\n if (event.key === \"Home\") {\n index = 0;\n } else if (event.key === \"End\") {\n index = buttons.length - 1;\n } else {\n const delta = event.key === nextKey ? 1 : -1;\n const base = current < 0 ? 0 : current;\n index = (base + delta + buttons.length) % buttons.length;\n }\n\n buttons[index]?.focus();\n }\n\n return (\n <TabsContext.Provider value={{ activeValue, setActiveValue: selectTabValue }}>\n <div\n className={cn(\n \"gap-6\",\n isVertical ? cn(\"flex\", alignment === \"end\" && \"flex-row-reverse\") : \"grid\",\n )}\n data-lattice-tabs={node.key ?? node.id}\n >\n <div\n aria-label={t(\"a11y.tabs\", \"Tabs\")}\n aria-orientation={orientation}\n className={cn(\n \"gap-1 rounded-lt bg-lt-muted p-1\",\n isVertical && \"flex flex-col\",\n isStretched && \"flex w-full\",\n !isVertical &&\n !isStretched &&\n cn(\"inline-flex w-fit max-w-full overflow-x-auto\", {\n \"justify-self-center\": alignment === \"center\",\n \"justify-self-end\": alignment === \"end\",\n }),\n )}\n ref={tablistRef}\n role=\"tablist\"\n >\n {tabs.map((tab) => {\n const isActive = activeValue === tab.value;\n\n return (\n <button\n aria-controls={`${tab.value}-panel`}\n aria-selected={isActive}\n data-test={`tab-${tab.value}`}\n className={cn(\n \"whitespace-nowrap rounded-lt-sm px-3 py-1.5 text-sm font-medium transition-colors\",\n isActive\n ? \"bg-lt-bg text-lt-fg shadow-lt-xs\"\n : \"text-lt-muted-fg hover:bg-lt-bg/60 hover:text-lt-fg\",\n isVertical && \"text-left\",\n isStretched && \"flex-1\",\n )}\n id={`${tab.value}-tab`}\n key={tab.value}\n onClick={() => selectTab(tab)}\n onKeyDown={onTablistKeyDown}\n role=\"tab\"\n tabIndex={isActive ? 0 : -1}\n type=\"button\"\n >\n {tab.label}\n </button>\n );\n })}\n </div>\n\n <div className={cn(\"min-w-0\", isVertical && \"flex-1\")}>{children}</div>\n </div>\n </TabsContext.Provider>\n );\n};\n\nconst TabComponent: RendererComponent<\"tab\"> = ({ children, node }) => {\n const { activeValue } = useTabsContext();\n const value = node.props.value;\n const isActive = activeValue === value;\n const [hasOpened, setHasOpened] = useState(isActive);\n\n useEffect(() => {\n if (isActive) {\n setHasOpened(true);\n }\n }, [isActive]);\n\n return (\n <section\n aria-labelledby={`${value}-tab`}\n className={cn(\"space-y-8\", !isActive && \"hidden\")}\n hidden={!isActive}\n id={`${value}-panel`}\n role=\"tabpanel\"\n tabIndex={0}\n >\n {hasOpened ? (children as ReactNode) : null}\n </section>\n );\n};\n\nexport default TabComponent;\n"],"mappings":";;;;;;AAaA,IAAM,cAAc,cAAuC,IAAI;AAE/D,SAAS,iBAAmC;CAC1C,MAAM,UAAU,WAAW,WAAW;CAEtC,IAAI,CAAC,SACH,OAAO;EACL,aAAa;EACb,sBAAsB,CAAC;CACzB;CAGF,OAAO;AACT;AAUA,SAAS,QAAQ,MAAuB;CACtC,QAAQ,KAAK,UAAU,CAAC,GACrB,QAAQ,UAAU,MAAM,SAAS,KAAK,EACtC,KAAK,UAAU;EACd,MAAM,QAAQ,MAAM;EAEpB,OAAO;GACL,SAAS,MAAM,UAAU,EAAE,UAAU,MAAM,QAAQ,SAAS,IAAI,KAAA;GAChE,OAAO,MAAM;GACb,OAAO,MAAM;EACf;CACF,CAAC,EACA,QAAQ,QAAQ,IAAI,UAAU,EAAE;AACrC;AAEA,SAAS,WAAW,UAAkB,MAA+C;CACnF,IAAI,OAAO,WAAW,aACpB,OAAO;CAGT,MAAM,QAAQ,IAAI,gBAAgB,OAAO,SAAS,MAAM,EAAE,IAAI,QAAQ;CAEtE,IAAI,CAAC,SAAS,CAAC,KAAK,MAAM,QAAQ,IAAI,UAAU,KAAK,GACnD,OAAO;CAGT,OAAO;AACT;AAEA,SAAS,kBAAkB,UAAkB,OAAqB;CAChE,IAAI,OAAO,WAAW,aACpB;CAGF,MAAM,MAAM,IAAI,IAAI,OAAO,SAAS,IAAI;CACxC,IAAI,aAAa,IAAI,UAAU,KAAK;CACpC,OAAO,QAAQ,aAAa,OAAO,QAAQ,OAAO,IAAI,GAAG,IAAI,WAAW,IAAI,SAAS,IAAI,MAAM;AACjG;AAEA,SAAS,SAAS,UAAkB,OAAuB;CACzD,IAAI,OAAO,WAAW,aACpB,OAAO;CAGT,MAAM,MAAM,IAAI,IAAI,OAAO,SAAS,IAAI;CACxC,IAAI,aAAa,IAAI,UAAU,KAAK;CAEpC,OAAO,GAAG,IAAI,WAAW,IAAI,SAAS,IAAI;AAC5C;AAEA,IAAa,iBAA4C,EAAE,UAAU,WAAW;CAC9E,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,MAAM,OAAO,cAAc,QAAQ,IAAI,GAAG,CAAC,IAAI,CAAC;CAChD,MAAM,aAAa,KAAK,IAAI,SAAS;CACrC,MAAM,WAAW,KAAK,MAAM;CAC5B,MAAM,cAAc,KAAK,MAAM;CAC/B,MAAM,aAAa,gBAAgB;CACnC,MAAM,YAAY,KAAK,MAAM;CAC7B,MAAM,cAAc,CAAC,cAAc,cAAc;CACjD,MAAM,oBAAoB,KAAK,MAAM;CACrC,MAAM,eAAe,KAAK,MAAM,gBAAgB;CAChD,MAAM,aAAa,OAAuB,IAAI;CAC9C,MAAM,CAAC,aAAa,qBAAqB,eACjC,sBAAsB,WAAW,UAAU,IAAI,KAAK,iBAAiB,UAC7E;CAEA,SAAS,UAAU,KAAoB;EACrC,IAAI,IAAI,SAAS,UAAU;GACzB,OAAO,MAAM,SAAS,UAAU,IAAI,KAAK,GAAG,EAC1C,gBAAgB,KAClB,CAAC;GAED;EACF;EAEA,kBAAkB,IAAI,KAAK;EAC3B,kBAAkB,UAAU,IAAI,KAAK;CACvC;CAEA,SAAS,eAAe,OAAqB;EAC3C,MAAM,MAAM,KAAK,MAAM,SAAS,KAAK,UAAU,KAAK;EAEpD,IAAI,KACF,UAAU,GAAG;CAEjB;CAEA,SAAS,iBAAiB,OAA+C;EACvE,MAAM,UAAU,aAAa,cAAc;EAC3C,MAAM,UAAU,aAAa,YAAY;EAEzC,IACE,MAAM,QAAQ,WACd,MAAM,QAAQ,WACd,MAAM,QAAQ,UACd,MAAM,QAAQ,OAEd;EAGF,MAAM,UAAU,MAAM,KACpB,WAAW,SAAS,iBAAoC,gBAAc,KAAK,CAAC,CAC9E;EAEA,IAAI,QAAQ,WAAW,GACrB;EAGF,MAAM,eAAe;EACrB,MAAM,UAAU,QAAQ,QAAQ,SAAS,aAAkC;EAE3E,IAAI;EACJ,IAAI,MAAM,QAAQ,QAChB,QAAQ;OACH,IAAI,MAAM,QAAQ,OACvB,QAAQ,QAAQ,SAAS;OACpB;GACL,MAAM,QAAQ,MAAM,QAAQ,UAAU,IAAI;GAE1C,UADa,UAAU,IAAI,IAAI,WACf,QAAQ,QAAQ,UAAU,QAAQ;EACpD;EAEA,QAAQ,QAAQ,MAAM;CACxB;CAEA,OACE,oBAAC,YAAY,UAAb;EAAsB,OAAO;GAAE;GAAa,gBAAgB;EAAe;YACzE,qBAAC,OAAD;GACE,WAAW,GACT,SACA,aAAa,GAAG,QAAQ,cAAc,SAAS,kBAAkB,IAAI,MACvE;GACA,qBAAmB,KAAK,OAAO,KAAK;aALtC,CAOE,oBAAC,OAAD;IACE,cAAY,EAAE,aAAa,MAAM;IACjC,oBAAkB;IAClB,WAAW,GACT,oCACA,cAAc,iBACd,eAAe,eACf,CAAC,cACC,CAAC,eACD,GAAG,gDAAgD;KACjD,uBAAuB,cAAc;KACrC,oBAAoB,cAAc;IACpC,CAAC,CACL;IACA,KAAK;IACL,MAAK;cAEJ,KAAK,KAAK,QAAQ;KACjB,MAAM,WAAW,gBAAgB,IAAI;KAErC,OACE,oBAAC,UAAD;MACE,iBAAe,GAAG,IAAI,MAAM;MAC5B,iBAAe;MACf,aAAW,OAAO,IAAI;MACtB,WAAW,GACT,qFACA,WACI,qCACA,uDACJ,cAAc,aACd,eAAe,QACjB;MACA,IAAI,GAAG,IAAI,MAAM;MAEjB,eAAe,UAAU,GAAG;MAC5B,WAAW;MACX,MAAK;MACL,UAAU,WAAW,IAAI;MACzB,MAAK;gBAEJ,IAAI;KACC,GARD,IAAI,KAQH;IAEZ,CAAC;GACE,CAAA,GAEL,oBAAC,OAAD;IAAK,WAAW,GAAG,WAAW,cAAc,QAAQ;IAAI;GAAc,CAAA,CACnE;;CACe,CAAA;AAE1B;AAEA,IAAM,gBAA0C,EAAE,UAAU,WAAW;CACrE,MAAM,EAAE,gBAAgB,eAAe;CACvC,MAAM,QAAQ,KAAK,MAAM;CACzB,MAAM,WAAW,gBAAgB;CACjC,MAAM,CAAC,WAAW,gBAAgB,SAAS,QAAQ;CAEnD,gBAAgB;EACd,IAAI,UACF,aAAa,IAAI;CAErB,GAAG,CAAC,QAAQ,CAAC;CAEb,OACE,oBAAC,WAAD;EACE,mBAAiB,GAAG,MAAM;EAC1B,WAAW,GAAG,aAAa,CAAC,YAAY,QAAQ;EAChD,QAAQ,CAAC;EACT,IAAI,GAAG,MAAM;EACb,MAAK;EACL,UAAU;YAET,YAAa,WAAyB;CAChC,CAAA;AAEb"}
|
package/dist/core/renderer.d.ts
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
2
|
import { Node } from './types';
|
|
3
|
-
/** Renders a schema (list of nodes) against the active component registry. */
|
|
4
3
|
export declare function Renderer({ nodes }: {
|
|
5
4
|
nodes: Node[];
|
|
6
5
|
}): ReactNode;
|
|
7
|
-
/** Renders a single node against the active component registry. */
|
|
8
6
|
export declare function RenderNode({ node }: {
|
|
9
7
|
node: Node;
|
|
10
8
|
}): ReactNode;
|
package/dist/core/renderer.js
CHANGED
|
@@ -9,11 +9,9 @@ function MissingComponent({ node }) {
|
|
|
9
9
|
function nodeKey(node, index) {
|
|
10
10
|
return node.key ?? node.id ?? `${node.type}-${index}`;
|
|
11
11
|
}
|
|
12
|
-
/** Renders a schema (list of nodes) against the active component registry. */
|
|
13
12
|
function Renderer({ nodes }) {
|
|
14
13
|
return nodes.map((node, index) => /* @__PURE__ */ jsx(NodeRenderer, { node }, nodeKey(node, index)));
|
|
15
14
|
}
|
|
16
|
-
/** Renders a single node against the active component registry. */
|
|
17
15
|
function RenderNode({ node }) {
|
|
18
16
|
return /* @__PURE__ */ jsx(NodeRenderer, { node });
|
|
19
17
|
}
|
|
@@ -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\
|
|
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,7 +1,6 @@
|
|
|
1
1
|
import { ActionResult, Effect } from '../types/generated';
|
|
2
2
|
import { EffectHandlerRegistry } from './registry';
|
|
3
3
|
export type ActionEffect = Effect;
|
|
4
|
-
/** The server's action response. Fields are optional: a handler may omit any of them. */
|
|
5
4
|
export type ActionResponse = Partial<ActionResult>;
|
|
6
5
|
/**
|
|
7
6
|
* Run each effect through its handler. Handlers default to the built-ins; the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch.js","names":[],"sources":["../../resources/js/effects/dispatch.ts"],"sourcesContent":["import type { ActionResult, Effect } from \"@lattice-php/lattice/types/generated\";\nimport { LATTICE_EVENT } from \"@lattice-php/lattice/events/event-names\";\nimport { builtinEffectHandlers } from \"./registry\";\nimport type { EffectHandlerRegistry } from \"./registry\";\n\nexport type ActionEffect = Effect;\n\
|
|
1
|
+
{"version":3,"file":"dispatch.js","names":[],"sources":["../../resources/js/effects/dispatch.ts"],"sourcesContent":["import type { ActionResult, Effect } from \"@lattice-php/lattice/types/generated\";\nimport { LATTICE_EVENT } from \"@lattice-php/lattice/events/event-names\";\nimport { builtinEffectHandlers } from \"./registry\";\nimport type { EffectHandlerRegistry } from \"./registry\";\n\nexport type ActionEffect = Effect;\n\nexport type ActionResponse = Partial<ActionResult>;\n\n/**\n * Run each effect through its handler. Handlers default to the built-ins; the\n * Provider passes a merged registry that also includes consumer-registered\n * handlers. An effect with no handler is warned about (dev) and skipped.\n */\nexport function dispatchEffects(\n effects: ActionEffect[],\n handlers: EffectHandlerRegistry = builtinEffectHandlers,\n): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n for (const effect of effects) {\n const handler = handlers[effect.type];\n\n if (handler === undefined) {\n console.warn(`[lattice] No handler registered for effect type \"${effect.type}\".`);\n continue;\n }\n\n handler(effect);\n }\n}\n\nexport function dispatchActionError(error: unknown): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n window.dispatchEvent(new CustomEvent(LATTICE_EVENT.actionError, { detail: { error } }));\n}\n\nexport function getActionEffects(effects: unknown): ActionEffect[] {\n return Array.isArray(effects) ? effects.filter(isActionEffect) : [];\n}\n\n/**\n * Structural guard for an effect on the wire. Intentionally open: it accepts any\n * object with a string `type`, not only the built-in types, so consumer-registered\n * effects pass through getActionEffects() and reach their handlers. Dispatch warns\n * on (and skips) a type with no registered handler.\n */\nexport function isActionEffect(effect: unknown): effect is ActionEffect {\n return (\n typeof effect === \"object\" &&\n effect !== null &&\n \"type\" in effect &&\n typeof (effect as { type: unknown }).type === \"string\"\n );\n}\n"],"mappings":";;;;;;;;AAcA,SAAgB,gBACd,SACA,WAAkC,uBAC5B;CACN,IAAI,OAAO,WAAW,aACpB;CAGF,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,UAAU,SAAS,OAAO;EAEhC,IAAI,YAAY,KAAA,GAAW;GACzB,QAAQ,KAAK,oDAAoD,OAAO,KAAK,GAAG;GAChF;EACF;EAEA,QAAQ,MAAM;CAChB;AACF;AAEA,SAAgB,oBAAoB,OAAsB;CACxD,IAAI,OAAO,WAAW,aACpB;CAGF,OAAO,cAAc,IAAI,YAAY,cAAc,aAAa,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;AACxF;AAEA,SAAgB,iBAAiB,SAAkC;CACjE,OAAO,MAAM,QAAQ,OAAO,IAAI,QAAQ,OAAO,cAAc,IAAI,CAAC;AACpE;;;;;;;AAQA,SAAgB,eAAe,QAAyC;CACtE,OACE,OAAO,WAAW,YAClB,WAAW,QACX,UAAU,UACV,OAAQ,OAA6B,SAAS;AAElD"}
|
package/dist/effects/registry.js
CHANGED
|
@@ -18,7 +18,6 @@ function triggerDownload(url) {
|
|
|
18
18
|
link.click();
|
|
19
19
|
link.remove();
|
|
20
20
|
}
|
|
21
|
-
/** Re-broadcast an effect as its `lattice:*` DOM event for feature components. */
|
|
22
21
|
function bridge(event) {
|
|
23
22
|
return (effect) => window.dispatchEvent(new CustomEvent(event, { detail: effect }));
|
|
24
23
|
}
|
|
@@ -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 } 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 * Augmentable map of effect `type` → payload (the effect's fields minus `type`).\n * Consumer apps extend it via `declare module \"@lattice-php/lattice\"` so their\n * custom effects type their handler's payload; built-ins resolve through the\n * generated `Effect` union.\n */\nexport interface EffectProps {}\n\
|
|
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 } 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 * Augmentable map of effect `type` → payload (the effect's fields minus `type`).\n * Consumer apps extend it via `declare module \"@lattice-php/lattice\"` so their\n * custom effects type their handler's payload; built-ins resolve through the\n * generated `Effect` union.\n */\nexport interface EffectProps {}\n\ntype EffectPayloads = { [TEffect in Effect as TEffect[\"type\"]]: Omit<TEffect, \"type\"> };\n\ntype EffectPayloadOf<TType extends string> = ResolveProps<\n EffectProps,\n EffectPayloads,\n TType,\n Record<string, unknown>\n>;\n\n/**\n * Resolves an effect `type` to its full shape — `{ type, ...payload }` — the way\n * `Node<T>` pairs a type with its props, so built-ins and consumer effects alike\n * always carry `type`. An unknown type falls back to the loose `Effect` union.\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\n/**\n * Registers a typed effect handler, erasing the type parameter for the registry.\n * Mirrors `eagerComponent`/`columnCell`: author against `EffectHandler<\"my.type\">`\n * for a typed payload, register through this.\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 * Built-in effect handlers. Imperative effects act directly; the rest bridge to\n * the `lattice:*` DOM events that toast/callout/modal/fragment/form subscribe to\n * — preserving today's behavior exactly.\n */\nexport const builtinEffectHandlers: EffectHandlerRegistry = {\n reloadPage: () => router.reload(),\n redirect: effectHandler(\"redirect\", (effect) => router.visit(effect.url)),\n download: effectHandler(\"download\", (effect) => triggerDownload(effect.url)),\n localeChange: effectHandler(\"localeChange\", (effect) => setLocale(effect.locale)),\n toast: bridge(LATTICE_EVENT.toast),\n callout: bridge(LATTICE_EVENT.callout),\n reloadComponent: bridge(LATTICE_EVENT.reloadComponent),\n openModal: bridge(LATTICE_EVENT.openModal),\n closeModal: bridge(LATTICE_EVENT.closeModal),\n resetForm: bridge(LATTICE_EVENT.resetForm),\n toggleSidebar: 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":";;;;;;;;;AAyCA,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;;;;;;AAOA,IAAa,wBAA+C;CAC1D,kBAAkB,OAAO,OAAO;CAChC,UAAU,cAAc,aAAa,WAAW,OAAO,MAAM,OAAO,GAAG,CAAC;CACxE,UAAU,cAAc,aAAa,WAAW,gBAAgB,OAAO,GAAG,CAAC;CAC3E,cAAc,cAAc,iBAAiB,WAAW,UAAU,OAAO,MAAM,CAAC;CAChF,OAAO,OAAO,cAAc,KAAK;CACjC,SAAS,OAAO,cAAc,OAAO;CACrC,iBAAiB,OAAO,cAAc,eAAe;CACrD,WAAW,OAAO,cAAc,SAAS;CACzC,YAAY,OAAO,cAAc,UAAU;CAC3C,WAAW,OAAO,cAAc,SAAS;CACzC,eAAe,OAAO,cAAc,aAAa;AACnD;AAEA,SAAgB,oBACd,GAAG,YACoB;CACvB,OAAO,WAAW,QACf,QAAQ,cAAc;EAAE,GAAG;EAAQ,GAAG;CAAS,IAChD,CAAC,CACH;AACF"}
|
|
@@ -3,5 +3,4 @@ import { ComponentProps } from 'react';
|
|
|
3
3
|
export type InputOTPProps = Omit<ComponentProps<typeof OTPInput>, "children" | "maxLength" | "render"> & {
|
|
4
4
|
length: number;
|
|
5
5
|
};
|
|
6
|
-
/** A segmented one-time-code input styled to match Lattice's control surfaces. */
|
|
7
6
|
export declare function InputOTP({ length, containerClassName, pattern, ...props }: InputOTPProps): import("react").JSX.Element;
|
|
@@ -11,7 +11,6 @@ function Slot({ char, hasFakeCaret, isActive }) {
|
|
|
11
11
|
}) : null]
|
|
12
12
|
});
|
|
13
13
|
}
|
|
14
|
-
/** A segmented one-time-code input styled to match Lattice's control surfaces. */
|
|
15
14
|
function InputOTP({ length, containerClassName, pattern = REGEXP_ONLY_DIGITS, ...props }) {
|
|
16
15
|
return /* @__PURE__ */ jsx(OTPInput, {
|
|
17
16
|
maxLength: length,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input-otp.js","names":[],"sources":["../../../../resources/js/form/components/base/input-otp.tsx"],"sourcesContent":["import { OTPInput, REGEXP_ONLY_DIGITS, type SlotProps } from \"input-otp\";\nimport type { ComponentProps } from \"react\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\n\nfunction Slot({ char, hasFakeCaret, isActive }: SlotProps) {\n return (\n <div\n className={cn(\n \"relative flex h-lt-control-md w-10 items-center justify-center border-y border-r border-lt-input text-base shadow-lt-xs transition-all first:rounded-l-lt-sm first:border-l last:rounded-r-lt-sm\",\n isActive && \"z-10 border-lt-ring ring-lt-ring/50 ring-[3px]\",\n )}\n >\n {char}\n {hasFakeCaret ? (\n <div className=\"pointer-events-none absolute inset-0 flex items-center justify-center\">\n <div className=\"bg-lt-fg h-5 w-px animate-pulse\" />\n </div>\n ) : null}\n </div>\n );\n}\n\nexport type InputOTPProps = Omit<\n ComponentProps<typeof OTPInput>,\n \"children\" | \"maxLength\" | \"render\"\n> & {\n length: number;\n};\n\
|
|
1
|
+
{"version":3,"file":"input-otp.js","names":[],"sources":["../../../../resources/js/form/components/base/input-otp.tsx"],"sourcesContent":["import { OTPInput, REGEXP_ONLY_DIGITS, type SlotProps } from \"input-otp\";\nimport type { ComponentProps } from \"react\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\n\nfunction Slot({ char, hasFakeCaret, isActive }: SlotProps) {\n return (\n <div\n className={cn(\n \"relative flex h-lt-control-md w-10 items-center justify-center border-y border-r border-lt-input text-base shadow-lt-xs transition-all first:rounded-l-lt-sm first:border-l last:rounded-r-lt-sm\",\n isActive && \"z-10 border-lt-ring ring-lt-ring/50 ring-[3px]\",\n )}\n >\n {char}\n {hasFakeCaret ? (\n <div className=\"pointer-events-none absolute inset-0 flex items-center justify-center\">\n <div className=\"bg-lt-fg h-5 w-px animate-pulse\" />\n </div>\n ) : null}\n </div>\n );\n}\n\nexport type InputOTPProps = Omit<\n ComponentProps<typeof OTPInput>,\n \"children\" | \"maxLength\" | \"render\"\n> & {\n length: number;\n};\n\nexport function InputOTP({\n length,\n containerClassName,\n pattern = REGEXP_ONLY_DIGITS,\n ...props\n}: InputOTPProps) {\n return (\n <OTPInput\n maxLength={length}\n pattern={pattern}\n containerClassName={cn(\n \"flex items-center gap-2 has-[:disabled]:opacity-50\",\n containerClassName,\n )}\n render={({ slots }) => (\n <div className=\"flex items-center\">\n {slots.map((slot, index) => (\n <Slot key={index} {...slot} />\n ))}\n </div>\n )}\n {...props}\n />\n );\n}\n"],"mappings":";;;;AAIA,SAAS,KAAK,EAAE,MAAM,cAAc,YAAuB;CACzD,OACE,qBAAC,OAAD;EACE,WAAW,GACT,oMACA,YAAY,gDACd;YAJF,CAMG,MACA,eACC,oBAAC,OAAD;GAAK,WAAU;aACb,oBAAC,OAAD,EAAK,WAAU,kCAAmC,CAAA;EAC/C,CAAA,IACH,IACD;;AAET;AASA,SAAgB,SAAS,EACvB,QACA,oBACA,UAAU,oBACV,GAAG,SACa;CAChB,OACE,oBAAC,UAAD;EACE,WAAW;EACF;EACT,oBAAoB,GAClB,sDACA,kBACF;EACA,SAAS,EAAE,YACT,oBAAC,OAAD;GAAK,WAAU;aACZ,MAAM,KAAK,MAAM,UAChB,oBAAC,MAAD,EAAkB,GAAI,KAAO,GAAlB,KAAkB,CAC9B;EACE,CAAA;EAEP,GAAI;CACL,CAAA;AAEL"}
|
|
@@ -24,5 +24,4 @@ export type FieldProps = {
|
|
|
24
24
|
value?: unknown;
|
|
25
25
|
};
|
|
26
26
|
export declare function fieldProps(node: Node): FieldProps;
|
|
27
|
-
/** The single schema traversal shared by the label/value and dependency-watch collectors. */
|
|
28
27
|
export declare function walkFields(nodes: Node[] | undefined, visit: (props: FieldProps, node: Node) => void): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"field-props.js","names":[],"sources":["../../../resources/js/form/components/field-props.ts"],"sourcesContent":["import type { Node } from \"@lattice-php/lattice/core/types\";\nimport type { FieldConditions } from \"./conditions\";\n\n/**\n * The props every form-field node shares (the PHP Field base). Nodes flow through\n * the form framework loosely typed via the generic schema, so this is the typed\n * lens the shared hooks read them through. Everything is optional because the\n * lens is also applied to non-field nodes while walking the schema.\n */\nexport type FieldProps = {\n conditions?: FieldConditions | null;\n dependsOnAny?: boolean | null;\n dependsOnKeys?: string[] | null;\n disabled?: boolean | null;\n helperText?: string | null;\n hidden?: boolean | null;\n label?: string | null;\n name?: string;\n editablePrefill?: boolean | null;\n prefillRefreshOn?: string[] | null;\n prefillResetOn?: string[] | null;\n readOnly?: boolean | null;\n required?: boolean | null;\n tooltip?: string | null;\n value?: unknown;\n};\n\nexport function fieldProps(node: Node): FieldProps {\n return node.props as FieldProps;\n}\n\
|
|
1
|
+
{"version":3,"file":"field-props.js","names":[],"sources":["../../../resources/js/form/components/field-props.ts"],"sourcesContent":["import type { Node } from \"@lattice-php/lattice/core/types\";\nimport type { FieldConditions } from \"./conditions\";\n\n/**\n * The props every form-field node shares (the PHP Field base). Nodes flow through\n * the form framework loosely typed via the generic schema, so this is the typed\n * lens the shared hooks read them through. Everything is optional because the\n * lens is also applied to non-field nodes while walking the schema.\n */\nexport type FieldProps = {\n conditions?: FieldConditions | null;\n dependsOnAny?: boolean | null;\n dependsOnKeys?: string[] | null;\n disabled?: boolean | null;\n helperText?: string | null;\n hidden?: boolean | null;\n label?: string | null;\n name?: string;\n editablePrefill?: boolean | null;\n prefillRefreshOn?: string[] | null;\n prefillResetOn?: string[] | null;\n readOnly?: boolean | null;\n required?: boolean | null;\n tooltip?: string | null;\n value?: unknown;\n};\n\nexport function fieldProps(node: Node): FieldProps {\n return node.props as FieldProps;\n}\n\nexport function walkFields(\n nodes: Node[] | undefined,\n visit: (props: FieldProps, node: Node) => void,\n): void {\n for (const child of nodes ?? []) {\n visit(fieldProps(child), child);\n walkFields(child.schema, visit);\n }\n}\n"],"mappings":";AA2BA,SAAgB,WAAW,MAAwB;CACjD,OAAO,KAAK;AACd;AAEA,SAAgB,WACd,OACA,OACM;CACN,KAAK,MAAM,SAAS,SAAS,CAAC,GAAG;EAC/B,MAAM,WAAW,KAAK,GAAG,KAAK;EAC9B,WAAW,MAAM,QAAQ,KAAK;CAChC;AACF"}
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
export type RepeaterRow = Record<string, unknown>;
|
|
2
2
|
export declare const ROW_ID_KEY = "__rowId";
|
|
3
|
-
/** Stamp a stable client-only id on a row (no-op if it already has one). */
|
|
4
3
|
export declare function withRowId(row: RepeaterRow): RepeaterRow;
|
|
5
4
|
/** Ensure every row has a stable id; returns the SAME array reference if none were missing. */
|
|
6
5
|
export declare function ensureRowIds(rows: RepeaterRow[]): RepeaterRow[];
|
|
7
|
-
/** Seed the row list from a stored value, falling back to `defaultItems` blank rows. */
|
|
8
6
|
export declare function seedRows(value: unknown, defaultItems: number): RepeaterRow[];
|
|
9
7
|
export declare function addRow(rows: RepeaterRow[]): RepeaterRow[];
|
|
10
8
|
export declare function removeRow(rows: RepeaterRow[], index: number): RepeaterRow[];
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
//#region resources/js/form/components/fields/repeater-rows.ts
|
|
2
2
|
var ROW_ID_KEY = "__rowId";
|
|
3
3
|
var rowIdCounter = 0;
|
|
4
|
-
/** Stamp a stable client-only id on a row (no-op if it already has one). */
|
|
5
4
|
function withRowId(row) {
|
|
6
5
|
return row["__rowId"] ? row : {
|
|
7
6
|
...row,
|
|
@@ -13,7 +12,6 @@ function ensureRowIds(rows) {
|
|
|
13
12
|
if (rows.every((row) => Boolean(row["__rowId"]))) return rows;
|
|
14
13
|
return rows.map(withRowId);
|
|
15
14
|
}
|
|
16
|
-
/** Seed the row list from a stored value, falling back to `defaultItems` blank rows. */
|
|
17
15
|
function seedRows(value, defaultItems) {
|
|
18
16
|
if (Array.isArray(value) && value.length > 0) return value.map((row) => row && typeof row === "object" ? { ...row } : {});
|
|
19
17
|
return Array.from({ length: Math.max(0, defaultItems) }, () => ({}));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repeater-rows.js","names":[],"sources":["../../../../resources/js/form/components/fields/repeater-rows.ts"],"sourcesContent":["export type RepeaterRow = Record<string, unknown>;\n\nexport const ROW_ID_KEY = \"__rowId\";\n\nlet rowIdCounter = 0;\n\
|
|
1
|
+
{"version":3,"file":"repeater-rows.js","names":[],"sources":["../../../../resources/js/form/components/fields/repeater-rows.ts"],"sourcesContent":["export type RepeaterRow = Record<string, unknown>;\n\nexport const ROW_ID_KEY = \"__rowId\";\n\nlet rowIdCounter = 0;\n\nexport function withRowId(row: RepeaterRow): RepeaterRow {\n return row[ROW_ID_KEY] ? row : { ...row, [ROW_ID_KEY]: `r${rowIdCounter++}` };\n}\n\n/** Ensure every row has a stable id; returns the SAME array reference if none were missing. */\nexport function ensureRowIds(rows: RepeaterRow[]): RepeaterRow[] {\n if (rows.every((row) => Boolean(row[ROW_ID_KEY]))) {\n return rows;\n }\n return rows.map(withRowId);\n}\n\nexport function seedRows(value: unknown, defaultItems: number): RepeaterRow[] {\n if (Array.isArray(value) && value.length > 0) {\n return value.map((row) => (row && typeof row === \"object\" ? { ...(row as RepeaterRow) } : {}));\n }\n\n return Array.from({ length: Math.max(0, defaultItems) }, () => ({}));\n}\n\nexport function addRow(rows: RepeaterRow[]): RepeaterRow[] {\n return [...rows, {}];\n}\n\nexport function removeRow(rows: RepeaterRow[], index: number): RepeaterRow[] {\n return rows.filter((_, i) => i !== index);\n}\n\nexport function duplicateRow(rows: RepeaterRow[], index: number): RepeaterRow[] {\n const source = rows[index];\n if (!source) {\n return rows;\n }\n\n const { [ROW_ID_KEY]: _id, ...copy } = source;\n return [...rows.slice(0, index + 1), withRowId(copy), ...rows.slice(index + 1)];\n}\n\nexport function moveRow(rows: RepeaterRow[], from: number, to: number): RepeaterRow[] {\n const next = [...rows];\n const [moved] = next.splice(from, 1);\n next.splice(to, 0, moved);\n return next;\n}\n"],"mappings":";AAEA,IAAa,aAAa;AAE1B,IAAI,eAAe;AAEnB,SAAgB,UAAU,KAA+B;CACvD,OAAO,IAAA,aAAkB,MAAM;EAAE,GAAG;GAAM,aAAa,IAAI;CAAiB;AAC9E;;AAGA,SAAgB,aAAa,MAAoC;CAC/D,IAAI,KAAK,OAAO,QAAQ,QAAQ,IAAA,UAAe,CAAC,GAC9C,OAAO;CAET,OAAO,KAAK,IAAI,SAAS;AAC3B;AAEA,SAAgB,SAAS,OAAgB,cAAqC;CAC5E,IAAI,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,GACzC,OAAO,MAAM,KAAK,QAAS,OAAO,OAAO,QAAQ,WAAW,EAAE,GAAI,IAAoB,IAAI,CAAC,CAAE;CAG/F,OAAO,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,GAAG,YAAY,EAAE,UAAU,CAAC,EAAE;AACrE;AAEA,SAAgB,OAAO,MAAoC;CACzD,OAAO,CAAC,GAAG,MAAM,CAAC,CAAC;AACrB;AAEA,SAAgB,UAAU,MAAqB,OAA8B;CAC3E,OAAO,KAAK,QAAQ,GAAG,MAAM,MAAM,KAAK;AAC1C;AAEA,SAAgB,aAAa,MAAqB,OAA8B;CAC9E,MAAM,SAAS,KAAK;CACpB,IAAI,CAAC,QACH,OAAO;CAGT,MAAM,GAAG,aAAa,KAAK,GAAG,SAAS;CACvC,OAAO;EAAC,GAAG,KAAK,MAAM,GAAG,QAAQ,CAAC;EAAG,UAAU,IAAI;EAAG,GAAG,KAAK,MAAM,QAAQ,CAAC;CAAC;AAChF;AAEA,SAAgB,QAAQ,MAAqB,MAAc,IAA2B;CACpF,MAAM,OAAO,CAAC,GAAG,IAAI;CACrB,MAAM,CAAC,SAAS,KAAK,OAAO,MAAM,CAAC;CACnC,KAAK,OAAO,IAAI,GAAG,KAAK;CACxB,OAAO;AACT"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
2
|
import { Node } from '../../../core/types';
|
|
3
3
|
import { ControlledField } from '../use-controlled-field';
|
|
4
|
-
/** The shared shell for single-input fields; the control is a render prop receiving the resolved field state. */
|
|
5
4
|
export declare function SimpleField({ node, label, children, }: {
|
|
6
5
|
node: Node;
|
|
7
6
|
label: string;
|
|
@@ -3,7 +3,6 @@ import { FormFieldFrame } from "../base/field.js";
|
|
|
3
3
|
import { useControlledField } from "../use-controlled-field.js";
|
|
4
4
|
import { jsx } from "react/jsx-runtime";
|
|
5
5
|
//#region resources/js/form/components/fields/simple-field.tsx
|
|
6
|
-
/** The shared shell for single-input fields; the control is a render prop receiving the resolved field state. */
|
|
7
6
|
function SimpleField({ node, label, children }) {
|
|
8
7
|
const field = useControlledField(node);
|
|
9
8
|
if (field.hidden) return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"simple-field.js","names":[],"sources":["../../../../resources/js/form/components/fields/simple-field.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { Node } from \"@lattice-php/lattice/core/types\";\nimport { FormFieldFrame } from \"../base/field\";\nimport { fieldProps } from \"../field-props\";\nimport { type ControlledField, useControlledField } from \"../use-controlled-field\";\n\
|
|
1
|
+
{"version":3,"file":"simple-field.js","names":[],"sources":["../../../../resources/js/form/components/fields/simple-field.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { Node } from \"@lattice-php/lattice/core/types\";\nimport { FormFieldFrame } from \"../base/field\";\nimport { fieldProps } from \"../field-props\";\nimport { type ControlledField, useControlledField } from \"../use-controlled-field\";\n\nexport function SimpleField({\n node,\n label,\n children,\n}: {\n node: Node;\n label: string;\n children: (field: ControlledField) => ReactNode;\n}) {\n const field = useControlledField(node);\n\n if (field.hidden) {\n return null;\n }\n\n return (\n <FormFieldFrame\n error={field.error}\n helperText={fieldProps(node).helperText ?? undefined}\n tooltip={fieldProps(node).tooltip ?? undefined}\n label={label}\n name={field.name}\n required={field.required}\n >\n {children(field)}\n </FormFieldFrame>\n );\n}\n"],"mappings":";;;;;AAMA,SAAgB,YAAY,EAC1B,MACA,OACA,YAKC;CACD,MAAM,QAAQ,mBAAmB,IAAI;CAErC,IAAI,MAAM,QACR,OAAO;CAGT,OACE,oBAAC,gBAAD;EACE,OAAO,MAAM;EACb,YAAY,WAAW,IAAI,EAAE,cAAc,KAAA;EAC3C,SAAS,WAAW,IAAI,EAAE,WAAW,KAAA;EAC9B;EACP,MAAM,MAAM;EACZ,UAAU,MAAM;YAEf,SAAS,KAAK;CACD,CAAA;AAEpB"}
|
|
@@ -6,7 +6,6 @@ import { useFormValue } from "./values.js";
|
|
|
6
6
|
import { useDependentField } from "./use-dependent-field.js";
|
|
7
7
|
import { useFieldCommit } from "./use-field-commit.js";
|
|
8
8
|
//#region resources/js/form/components/use-controlled-field.ts
|
|
9
|
-
/** Shared wiring read by every store-controlled, condition-aware field. */
|
|
10
9
|
function useControlledField(node) {
|
|
11
10
|
const { errors } = useFormContext();
|
|
12
11
|
const scope = useFieldScope();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-controlled-field.js","names":[],"sources":["../../../resources/js/form/components/use-controlled-field.ts"],"sourcesContent":["import type { Node } from \"@lattice-php/lattice/core/types\";\nimport { testIdentity } from \"@lattice-php/lattice/core/test-id\";\nimport type { FieldState } from \"./conditions\";\nimport { fieldProps } from \"./field-props\";\nimport { useFieldScope } from \"./field-scope\";\nimport { useFormContext } from \"./context\";\nimport { useDependentField } from \"./use-dependent-field\";\nimport { useFieldCommit } from \"./use-field-commit\";\nimport { useFormValue } from \"./values\";\n\nexport type ControlledField = FieldState & {\n localName: string;\n name: string;\n testId?: string;\n value: string;\n error?: string;\n commit: (value: unknown) => void;\n};\n\
|
|
1
|
+
{"version":3,"file":"use-controlled-field.js","names":[],"sources":["../../../resources/js/form/components/use-controlled-field.ts"],"sourcesContent":["import type { Node } from \"@lattice-php/lattice/core/types\";\nimport { testIdentity } from \"@lattice-php/lattice/core/test-id\";\nimport type { FieldState } from \"./conditions\";\nimport { fieldProps } from \"./field-props\";\nimport { useFieldScope } from \"./field-scope\";\nimport { useFormContext } from \"./context\";\nimport { useDependentField } from \"./use-dependent-field\";\nimport { useFieldCommit } from \"./use-field-commit\";\nimport { useFormValue } from \"./values\";\n\nexport type ControlledField = FieldState & {\n localName: string;\n name: string;\n testId?: string;\n value: string;\n error?: string;\n commit: (value: unknown) => void;\n};\n\nexport function useControlledField(node: Node): ControlledField {\n const { errors } = useFormContext();\n const scope = useFieldScope();\n const state = useDependentField(node);\n const props = fieldProps(node);\n const localName = props.name ?? \"\";\n\n const globalValue = useFormValue(localName);\n const storedValue = scope ? scope.getValue(localName) : globalValue;\n const currentValue = storedValue !== undefined ? storedValue : props.value;\n const value =\n typeof currentValue === \"string\" || typeof currentValue === \"number\"\n ? String(currentValue)\n : \"\";\n\n const domName = scope ? scope.scopedName(localName) : localName;\n const errorKey = scope ? scope.errorKey(localName) : localName;\n\n const { commit: commitField } = useFieldCommit();\n const commit = (next: unknown): void => commitField(localName, next);\n\n return {\n ...state,\n localName,\n name: domName,\n testId: testIdentity(localName),\n value,\n error: errors[errorKey],\n commit,\n };\n}\n"],"mappings":";;;;;;;;AAmBA,SAAgB,mBAAmB,MAA6B;CAC9D,MAAM,EAAE,WAAW,eAAe;CAClC,MAAM,QAAQ,cAAc;CAC5B,MAAM,QAAQ,kBAAkB,IAAI;CACpC,MAAM,QAAQ,WAAW,IAAI;CAC7B,MAAM,YAAY,MAAM,QAAQ;CAEhC,MAAM,cAAc,aAAa,SAAS;CAC1C,MAAM,cAAc,QAAQ,MAAM,SAAS,SAAS,IAAI;CACxD,MAAM,eAAe,gBAAgB,KAAA,IAAY,cAAc,MAAM;CACrE,MAAM,QACJ,OAAO,iBAAiB,YAAY,OAAO,iBAAiB,WACxD,OAAO,YAAY,IACnB;CAEN,MAAM,UAAU,QAAQ,MAAM,WAAW,SAAS,IAAI;CACtD,MAAM,WAAW,QAAQ,MAAM,SAAS,SAAS,IAAI;CAErD,MAAM,EAAE,QAAQ,gBAAgB,eAAe;CAC/C,MAAM,UAAU,SAAwB,YAAY,WAAW,IAAI;CAEnE,OAAO;EACL,GAAG;EACH;EACA,MAAM;EACN,QAAQ,aAAa,SAAS;EAC9B;EACA,OAAO,OAAO;EACd;CACF;AACF"}
|
package/dist/index.js
CHANGED
|
@@ -12,14 +12,14 @@ import { onCallout } from "./toast/callout.js";
|
|
|
12
12
|
import { EventBridge } from "./events/event-bridge.js";
|
|
13
13
|
import Callouts from "./layout/callouts.js";
|
|
14
14
|
import { createPlugin, createRegistry, eagerComponent, extendRegistry, lazyComponent } from "./core/registry.js";
|
|
15
|
+
import { builtinEffectHandlers, effectHandler, mergeEffectHandlers } from "./effects/registry.js";
|
|
16
|
+
import { dispatchActionError, dispatchEffects, getActionEffects, isActionEffect } from "./effects/dispatch.js";
|
|
17
|
+
import { useEffectDispatcher } from "./effects/use-effect-dispatcher.js";
|
|
15
18
|
import { OutletContext } from "./layout/context.js";
|
|
16
19
|
import { layoutComponents } from "./layout/components.js";
|
|
17
20
|
import SchemaLayout from "./layout/schema-layout.js";
|
|
18
21
|
import { RealtimeListeners } from "./realtime/listeners.js";
|
|
19
22
|
import { createLayoutResolver, createPageResolver, pageComponentName, withVisitHeaders } from "./inertia.js";
|
|
20
|
-
import { builtinEffectHandlers, effectHandler, mergeEffectHandlers } from "./effects/registry.js";
|
|
21
|
-
import { dispatchActionError, dispatchEffects, getActionEffects, isActionEffect } from "./effects/dispatch.js";
|
|
22
|
-
import { useEffectDispatcher } from "./effects/use-effect-dispatcher.js";
|
|
23
23
|
import { useChat } from "./chat/use-chat.js";
|
|
24
24
|
import { chatPlugin } from "./chat/index.js";
|
|
25
25
|
import { registry } from "./registry.js";
|
package/dist/layout/menu-item.js
CHANGED
|
@@ -4,6 +4,7 @@ import { IconRenderer } from "../icons/icon-renderer.js";
|
|
|
4
4
|
import { useCollapsed } from "../core/collapsed-context.js";
|
|
5
5
|
import { prefixedNodeTestId } from "../core/test-id.js";
|
|
6
6
|
import { Popover } from "./popover.js";
|
|
7
|
+
import { useAction } from "../action/use-action.js";
|
|
7
8
|
import { Link, usePage } from "@inertiajs/react";
|
|
8
9
|
import { useState } from "react";
|
|
9
10
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -48,6 +49,14 @@ var MenuItemComponent = ({ children, node }) => {
|
|
|
48
49
|
className: "ml-auto"
|
|
49
50
|
}) : null
|
|
50
51
|
] });
|
|
52
|
+
const action = node.props.action;
|
|
53
|
+
if (action && href === "" && !children) return /* @__PURE__ */ jsx(ActionMenuItem, {
|
|
54
|
+
action,
|
|
55
|
+
className: cn(rowClass, "w-full", collapsed && "group relative justify-center", iconOnly && "justify-center"),
|
|
56
|
+
label: collapsed || iconOnly ? label : void 0,
|
|
57
|
+
testId,
|
|
58
|
+
children: content
|
|
59
|
+
});
|
|
51
60
|
if (href === "") {
|
|
52
61
|
if (!children) return collapsed ? null : /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("span", {
|
|
53
62
|
className: "flex items-center gap-2 px-3 py-2 text-xs font-semibold tracking-wide text-lt-muted-fg uppercase",
|
|
@@ -116,6 +125,18 @@ function FlyoutGroup({ children, icon, label, testId }) {
|
|
|
116
125
|
})
|
|
117
126
|
}) });
|
|
118
127
|
}
|
|
128
|
+
function ActionMenuItem({ action, children, className, label, testId }) {
|
|
129
|
+
const { processing, requestSubmit, overlays } = useAction(action);
|
|
130
|
+
return /* @__PURE__ */ jsxs("li", { children: [/* @__PURE__ */ jsx("button", {
|
|
131
|
+
"aria-label": label,
|
|
132
|
+
className,
|
|
133
|
+
"data-test": testId,
|
|
134
|
+
disabled: processing,
|
|
135
|
+
onClick: requestSubmit,
|
|
136
|
+
type: "button",
|
|
137
|
+
children
|
|
138
|
+
}), overlays] });
|
|
139
|
+
}
|
|
119
140
|
//#endregion
|
|
120
141
|
export { MenuItemComponent as default };
|
|
121
142
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"menu-item.js","names":[],"sources":["../../resources/js/layout/menu-item.tsx"],"sourcesContent":["import { Link, usePage } from \"@inertiajs/react\";\nimport { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { prefixedNodeTestId } from \"@lattice-php/lattice/core/test-id\";\nimport type { RendererComponent, Schema } from \"@lattice-php/lattice/core/types\";\nimport { Icon, IconRenderer } from \"@lattice-php/lattice/icons\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport type { Affix } from \"@lattice-php/lattice/types/generated\";\nimport { useSidebarCollapsed } from \"./context\";\nimport { Popover } from \"./popover\";\n\nconst rowClass =\n \"flex items-center gap-2 rounded-lt-sm px-3 py-2 text-base font-medium text-lt-fg transition-colors hover:bg-lt-muted\";\n\nfunction MenuAffix({ 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 schemaContainsPath(schema: Schema | undefined, path: string): boolean {\n return (schema ?? []).some(\n (child) => child.props?.href === path || schemaContainsPath(child.schema, path),\n );\n}\n\nconst MenuItemComponent: RendererComponent<\"menu-item\"> = ({ children, node }) => {\n const collapsed = useSidebarCollapsed();\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 flyoutIcon = icon ?? prefix?.icon ?? null;\n const href = node.props.href ?? \"\";\n const currentPath = usePage().url.split(\"?\")[0];\n const testId = prefixedNodeTestId(\"menu\", node);\n\n const content = icon ? (\n <IconRenderer className=\"size-lt-icon-md shrink-0\" icon={icon} />\n ) : (\n <>\n {prefix ? <MenuAffix affix={prefix} /> : null}\n {collapsed ? (\n <span\n className=\"pointer-events-none absolute top-1/2 left-full z-50 ml-2 hidden -translate-y-1/2 rounded-lt-sm border border-lt-border bg-lt-popover px-2 py-1 text-sm whitespace-nowrap text-lt-popover-fg shadow-lt-md group-hover:block\"\n role=\"tooltip\"\n >\n {label}\n </span>\n ) : (\n <span>{label}</span>\n )}\n {suffix ? <MenuAffix affix={suffix} className=\"ml-auto\" /> : null}\n </>\n );\n\n if (href === \"\") {\n if (!children) {\n return collapsed ? null : (\n <li>\n <span className=\"flex items-center gap-2 px-3 py-2 text-xs font-semibold tracking-wide text-lt-muted-fg uppercase\">\n {content}\n </span>\n </li>\n );\n }\n\n if (collapsed) {\n return (\n <FlyoutGroup icon={flyoutIcon} label={label} testId={testId}>\n {children}\n </FlyoutGroup>\n );\n }\n\n return (\n <CollapsibleItem\n content={content}\n defaultOpen={schemaContainsPath(node.schema, currentPath)}\n testId={testId}\n >\n {children}\n </CollapsibleItem>\n );\n }\n\n const active = currentPath === href;\n const method = node.props.method ?? \"get\";\n\n return (\n <li>\n <Link\n aria-current={active ? \"page\" : undefined}\n aria-label={collapsed || iconOnly ? label : undefined}\n as={method === \"get\" ? undefined : \"button\"}\n className={cn(\n rowClass,\n \"w-full\",\n collapsed && \"group relative justify-center\",\n iconOnly && \"justify-center\",\n active && \"bg-lt-muted font-medium\",\n )}\n data-test={testId}\n href={href}\n method={method}\n >\n {content}\n </Link>\n </li>\n );\n};\n\nfunction CollapsibleItem({\n children,\n content,\n defaultOpen,\n testId,\n}: {\n children: ReactNode;\n content: ReactNode;\n defaultOpen: boolean;\n testId?: string;\n}) {\n const [open, setOpen] = useState(defaultOpen);\n\n return (\n <li>\n <button\n aria-expanded={open}\n className={cn(rowClass, \"w-full\")}\n data-test={testId}\n onClick={() => setOpen((value) => !value)}\n type=\"button\"\n >\n {content}\n <Icon\n name=\"chevron-right\"\n className={cn(\n \"ml-auto size-lt-icon-md shrink-0 transition-transform\",\n open && \"rotate-90\",\n )}\n />\n </button>\n {open ? <ul className=\"mt-1 flex flex-col gap-1 pl-3\">{children}</ul> : null}\n </li>\n );\n}\n\nfunction FlyoutGroup({\n children,\n icon,\n label,\n testId,\n}: {\n children: ReactNode;\n icon?: string | null;\n label: string;\n testId?: string;\n}) {\n return (\n <li>\n <Popover\n className=\"min-w-48\"\n placement=\"right\"\n testId={testId}\n trigger={\n icon ? (\n <IconRenderer className=\"size-lt-icon-md shrink-0\" icon={icon} />\n ) : (\n <span>{label}</span>\n )\n }\n triggerClassName={cn(rowClass, \"justify-center\")}\n triggerLabel={label}\n >\n <ul className=\"flex flex-col gap-1\">\n <li className=\"px-3 py-1.5 text-xs font-semibold tracking-wide text-lt-muted-fg uppercase\">\n {label}\n </li>\n {children}\n </ul>\n </Popover>\n </li>\n );\n}\n\nexport default MenuItemComponent;\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"menu-item.js","names":[],"sources":["../../resources/js/layout/menu-item.tsx"],"sourcesContent":["import { Link, usePage } from \"@inertiajs/react\";\nimport { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { useAction } from \"@lattice-php/lattice/action/use-action\";\nimport { prefixedNodeTestId } from \"@lattice-php/lattice/core/test-id\";\nimport type { Node, RendererComponent, Schema } from \"@lattice-php/lattice/core/types\";\nimport { Icon, IconRenderer } from \"@lattice-php/lattice/icons\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport type { Affix } from \"@lattice-php/lattice/types/generated\";\nimport { useSidebarCollapsed } from \"./context\";\nimport { Popover } from \"./popover\";\n\nconst rowClass =\n \"flex items-center gap-2 rounded-lt-sm px-3 py-2 text-base font-medium text-lt-fg transition-colors hover:bg-lt-muted\";\n\nfunction MenuAffix({ 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 schemaContainsPath(schema: Schema | undefined, path: string): boolean {\n return (schema ?? []).some(\n (child) => child.props?.href === path || schemaContainsPath(child.schema, path),\n );\n}\n\nconst MenuItemComponent: RendererComponent<\"menu-item\"> = ({ children, node }) => {\n const collapsed = useSidebarCollapsed();\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 flyoutIcon = icon ?? prefix?.icon ?? null;\n const href = node.props.href ?? \"\";\n const currentPath = usePage().url.split(\"?\")[0];\n const testId = prefixedNodeTestId(\"menu\", node);\n\n const content = icon ? (\n <IconRenderer className=\"size-lt-icon-md shrink-0\" icon={icon} />\n ) : (\n <>\n {prefix ? <MenuAffix affix={prefix} /> : null}\n {collapsed ? (\n <span\n className=\"pointer-events-none absolute top-1/2 left-full z-50 ml-2 hidden -translate-y-1/2 rounded-lt-sm border border-lt-border bg-lt-popover px-2 py-1 text-sm whitespace-nowrap text-lt-popover-fg shadow-lt-md group-hover:block\"\n role=\"tooltip\"\n >\n {label}\n </span>\n ) : (\n <span>{label}</span>\n )}\n {suffix ? <MenuAffix affix={suffix} className=\"ml-auto\" /> : null}\n </>\n );\n\n const action = node.props.action;\n\n if (action && href === \"\" && !children) {\n return (\n <ActionMenuItem\n action={action as Node<\"action\">}\n className={cn(\n rowClass,\n \"w-full\",\n collapsed && \"group relative justify-center\",\n iconOnly && \"justify-center\",\n )}\n label={collapsed || iconOnly ? label : undefined}\n testId={testId}\n >\n {content}\n </ActionMenuItem>\n );\n }\n\n if (href === \"\") {\n if (!children) {\n return collapsed ? null : (\n <li>\n <span className=\"flex items-center gap-2 px-3 py-2 text-xs font-semibold tracking-wide text-lt-muted-fg uppercase\">\n {content}\n </span>\n </li>\n );\n }\n\n if (collapsed) {\n return (\n <FlyoutGroup icon={flyoutIcon} label={label} testId={testId}>\n {children}\n </FlyoutGroup>\n );\n }\n\n return (\n <CollapsibleItem\n content={content}\n defaultOpen={schemaContainsPath(node.schema, currentPath)}\n testId={testId}\n >\n {children}\n </CollapsibleItem>\n );\n }\n\n const active = currentPath === href;\n const method = node.props.method ?? \"get\";\n\n return (\n <li>\n <Link\n aria-current={active ? \"page\" : undefined}\n aria-label={collapsed || iconOnly ? label : undefined}\n as={method === \"get\" ? undefined : \"button\"}\n className={cn(\n rowClass,\n \"w-full\",\n collapsed && \"group relative justify-center\",\n iconOnly && \"justify-center\",\n active && \"bg-lt-muted font-medium\",\n )}\n data-test={testId}\n href={href}\n method={method}\n >\n {content}\n </Link>\n </li>\n );\n};\n\nfunction CollapsibleItem({\n children,\n content,\n defaultOpen,\n testId,\n}: {\n children: ReactNode;\n content: ReactNode;\n defaultOpen: boolean;\n testId?: string;\n}) {\n const [open, setOpen] = useState(defaultOpen);\n\n return (\n <li>\n <button\n aria-expanded={open}\n className={cn(rowClass, \"w-full\")}\n data-test={testId}\n onClick={() => setOpen((value) => !value)}\n type=\"button\"\n >\n {content}\n <Icon\n name=\"chevron-right\"\n className={cn(\n \"ml-auto size-lt-icon-md shrink-0 transition-transform\",\n open && \"rotate-90\",\n )}\n />\n </button>\n {open ? <ul className=\"mt-1 flex flex-col gap-1 pl-3\">{children}</ul> : null}\n </li>\n );\n}\n\nfunction FlyoutGroup({\n children,\n icon,\n label,\n testId,\n}: {\n children: ReactNode;\n icon?: string | null;\n label: string;\n testId?: string;\n}) {\n return (\n <li>\n <Popover\n className=\"min-w-48\"\n placement=\"right\"\n testId={testId}\n trigger={\n icon ? (\n <IconRenderer className=\"size-lt-icon-md shrink-0\" icon={icon} />\n ) : (\n <span>{label}</span>\n )\n }\n triggerClassName={cn(rowClass, \"justify-center\")}\n triggerLabel={label}\n >\n <ul className=\"flex flex-col gap-1\">\n <li className=\"px-3 py-1.5 text-xs font-semibold tracking-wide text-lt-muted-fg uppercase\">\n {label}\n </li>\n {children}\n </ul>\n </Popover>\n </li>\n );\n}\n\nfunction ActionMenuItem({\n action,\n children,\n className,\n label,\n testId,\n}: {\n action: Node<\"action\">;\n children: ReactNode;\n className: string;\n label?: string;\n testId?: string;\n}) {\n const { processing, requestSubmit, overlays } = useAction(action);\n\n return (\n <li>\n <button\n aria-label={label}\n className={className}\n data-test={testId}\n disabled={processing}\n onClick={requestSubmit}\n type=\"button\"\n >\n {children}\n </button>\n {overlays}\n </li>\n );\n}\n\nexport default MenuItemComponent;\n"],"mappings":"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"}
|
|
@@ -5,7 +5,6 @@ import { jsx } from "react/jsx-runtime";
|
|
|
5
5
|
function isTruthy(value) {
|
|
6
6
|
return value === true || value === 1 || value === "1" || value === "true";
|
|
7
7
|
}
|
|
8
|
-
/** Renders a boolean column as a check (truthy) or cross (falsy) icon. */
|
|
9
8
|
var BooleanCell = ({ value }) => {
|
|
10
9
|
const truthy = isTruthy(value);
|
|
11
10
|
return /* @__PURE__ */ jsx("span", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"boolean-cell.js","names":[],"sources":["../../../../resources/js/table/components/cells/boolean-cell.tsx"],"sourcesContent":["import { Icon } from \"@lattice-php/lattice/icons\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport type { ColumnCellComponent } from \"../../registry\";\n\nfunction isTruthy(value: unknown): boolean {\n return value === true || value === 1 || value === \"1\" || value === \"true\";\n}\n\
|
|
1
|
+
{"version":3,"file":"boolean-cell.js","names":[],"sources":["../../../../resources/js/table/components/cells/boolean-cell.tsx"],"sourcesContent":["import { Icon } from \"@lattice-php/lattice/icons\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport type { ColumnCellComponent } from \"../../registry\";\n\nfunction isTruthy(value: unknown): boolean {\n return value === true || value === 1 || value === \"1\" || value === \"true\";\n}\n\nexport const BooleanCell: ColumnCellComponent<\"column.boolean\"> = ({ value }) => {\n const truthy = isTruthy(value);\n\n return (\n <span aria-label={String(truthy)} role=\"img\">\n <Icon\n name={truthy ? \"check\" : \"x\"}\n className={cn(\"size-lt-icon-md\", truthy ? \"text-lt-success\" : \"text-lt-muted-fg\")}\n />\n </span>\n );\n};\n"],"mappings":";;;;AAIA,SAAS,SAAS,OAAyB;CACzC,OAAO,UAAU,QAAQ,UAAU,KAAK,UAAU,OAAO,UAAU;AACrE;AAEA,IAAa,eAAsD,EAAE,YAAY;CAC/E,MAAM,SAAS,SAAS,KAAK;CAE7B,OACE,oBAAC,QAAD;EAAM,cAAY,OAAO,MAAM;EAAG,MAAK;YACrC,oBAAC,MAAD;GACE,MAAM,SAAS,UAAU;GACzB,WAAW,GAAG,mBAAmB,SAAS,oBAAoB,kBAAkB;EACjF,CAAA;CACG,CAAA;AAEV"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
//#region resources/js/table/components/cells/numeric.ts
|
|
2
|
-
/** Parses a cell value to a finite number, or null when it isn't numeric. */
|
|
3
2
|
function numericValue(value) {
|
|
4
3
|
const number = typeof value === "number" ? value : Number(value);
|
|
5
4
|
return value !== null && value !== void 0 && value !== "" && !Number.isNaN(number) ? number : null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"numeric.js","names":[],"sources":["../../../../resources/js/table/components/cells/numeric.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"numeric.js","names":[],"sources":["../../../../resources/js/table/components/cells/numeric.ts"],"sourcesContent":["export function numericValue(value: unknown): number | null {\n const number = typeof value === \"number\" ? value : Number(value);\n\n return value !== null && value !== undefined && value !== \"\" && !Number.isNaN(number)\n ? number\n : null;\n}\n"],"mappings":";AAAA,SAAgB,aAAa,OAA+B;CAC1D,MAAM,SAAS,OAAO,UAAU,WAAW,QAAQ,OAAO,KAAK;CAE/D,OAAO,UAAU,QAAQ,UAAU,KAAA,KAAa,UAAU,MAAM,CAAC,OAAO,MAAM,MAAM,IAChF,SACA;AACN"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { TextCell } from "./text-cell.js";
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
//#region resources/js/table/components/cells/stack-cell.tsx
|
|
4
|
-
/** Renders a stack column's nested columns as stacked text rows. */
|
|
5
4
|
var StackCell = ({ column, row }) => /* @__PURE__ */ jsx("div", {
|
|
6
5
|
className: "grid gap-1",
|
|
7
6
|
children: (column.columns ?? []).map((stackedColumn) => /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(TextCell, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stack-cell.js","names":[],"sources":["../../../../resources/js/table/components/cells/stack-cell.tsx"],"sourcesContent":["import type { ColumnCellComponent } from \"../../registry\";\nimport type { ColumnPropsOf } from \"../../types\";\nimport { TextCell } from \"./text-cell\";\n\
|
|
1
|
+
{"version":3,"file":"stack-cell.js","names":[],"sources":["../../../../resources/js/table/components/cells/stack-cell.tsx"],"sourcesContent":["import type { ColumnCellComponent } from \"../../registry\";\nimport type { ColumnPropsOf } from \"../../types\";\nimport { TextCell } from \"./text-cell\";\n\nexport const StackCell: ColumnCellComponent<\"column.stack\"> = ({ column, row }) => (\n <div className=\"grid gap-1\">\n {(column.columns ?? []).map((stackedColumn) => (\n <span key={stackedColumn.key}>\n <TextCell\n column={stackedColumn}\n props={(stackedColumn.props ?? {}) as ColumnPropsOf<\"column.text\">}\n row={row}\n value={row[stackedColumn.key]}\n />\n </span>\n ))}\n </div>\n);\n"],"mappings":";;;AAIA,IAAa,aAAkD,EAAE,QAAQ,UACvE,oBAAC,OAAD;CAAK,WAAU;YACX,OAAO,WAAW,CAAC,GAAG,KAAK,kBAC3B,oBAAC,QAAD,EAAA,UACE,oBAAC,UAAD;EACE,QAAQ;EACR,OAAQ,cAAc,SAAS,CAAC;EAC3B;EACL,OAAO,IAAI,cAAc;CAC1B,CAAA,EACG,GAPK,cAAc,GAOnB,CACP;AACE,CAAA"}
|
package/dist/table/registry.d.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
2
|
import { ColumnPropsOf, TableColumn, TableRow } from './types';
|
|
3
3
|
export type ColumnCellArgs<TType extends string = string> = {
|
|
4
|
-
/** The full wire column (key, label, type, nested columns). */
|
|
5
4
|
column: TableColumn;
|
|
6
|
-
/** The column's `props`, resolved to the type's shape. */
|
|
7
5
|
props: ColumnPropsOf<TType>;
|
|
8
6
|
row: TableRow;
|
|
9
7
|
value: unknown;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","names":[],"sources":["../../resources/js/table/registry.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { ColumnPropsOf, TableColumn, TableRow } from \"./types\";\n\nexport type ColumnCellArgs<TType extends string = string> = {\n
|
|
1
|
+
{"version":3,"file":"registry.js","names":[],"sources":["../../resources/js/table/registry.ts"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { ColumnPropsOf, TableColumn, TableRow } from \"./types\";\n\nexport type ColumnCellArgs<TType extends string = string> = {\n column: TableColumn;\n props: ColumnPropsOf<TType>;\n row: TableRow;\n value: unknown;\n};\n\nexport type ColumnCellComponent<TType extends string = string> = (\n args: ColumnCellArgs<TType>,\n) => ReactNode;\n\nexport type ColumnRegistry = Record<string, ColumnCellComponent>;\n\n/**\n * Registers a typed column cell, erasing the type parameter so it fits the\n * registry. Mirrors `eagerComponent`/`lazyComponent` for the component registry:\n * author against `ColumnCellComponent<\"my.type\">` for typed `props`, register\n * through this.\n */\nexport function columnCell<TType extends string>(\n cell: ColumnCellComponent<TType>,\n): ColumnCellComponent {\n return cell as ColumnCellComponent;\n}\n"],"mappings":";;;;;;;AAsBA,SAAgB,WACd,MACqB;CACrB,OAAO;AACT"}
|
package/dist/toast/callout.d.ts
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { Callout } from '../types/generated';
|
|
2
2
|
export type { Callout };
|
|
3
|
-
/** Coerce a raw callout value (effect detail `{ callout: {...} }`) into a Callout. */
|
|
4
3
|
export declare function normalizeCallout(detail: unknown): Callout | null;
|
|
5
|
-
/** Subscribe to the `lattice:callout` bus. Returns an unsubscribe function. */
|
|
6
4
|
export declare function onCallout(callback: (callout: Callout) => void): () => void;
|
package/dist/toast/callout.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { LATTICE_EVENT } from "../events/event-names.js";
|
|
2
2
|
import { isVariant } from "./toast.js";
|
|
3
3
|
//#region resources/js/toast/callout.ts
|
|
4
|
-
/** Coerce a raw callout value (effect detail `{ callout: {...} }`) into a Callout. */
|
|
5
4
|
function normalizeCallout(detail) {
|
|
6
5
|
if (typeof detail !== "object" || detail === null) return null;
|
|
7
6
|
const value = detail.callout;
|
|
@@ -16,7 +15,6 @@ function normalizeCallout(detail) {
|
|
|
16
15
|
variant: isVariant(callout.variant) ? callout.variant : "info"
|
|
17
16
|
};
|
|
18
17
|
}
|
|
19
|
-
/** Subscribe to the `lattice:callout` bus. Returns an unsubscribe function. */
|
|
20
18
|
function onCallout(callback) {
|
|
21
19
|
const listener = (event) => {
|
|
22
20
|
const callout = normalizeCallout(event.detail);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"callout.js","names":[],"sources":["../../resources/js/toast/callout.ts"],"sourcesContent":["import type { Callout } from \"@lattice-php/lattice/types/generated\";\nimport { LATTICE_EVENT } from \"@lattice-php/lattice/events/event-names\";\nimport { isVariant } from \"@lattice-php/lattice/toast/toast\";\n\nexport type { Callout };\n\
|
|
1
|
+
{"version":3,"file":"callout.js","names":[],"sources":["../../resources/js/toast/callout.ts"],"sourcesContent":["import type { Callout } from \"@lattice-php/lattice/types/generated\";\nimport { LATTICE_EVENT } from \"@lattice-php/lattice/events/event-names\";\nimport { isVariant } from \"@lattice-php/lattice/toast/toast\";\n\nexport type { Callout };\n\nexport function normalizeCallout(detail: unknown): Callout | null {\n if (typeof detail !== \"object\" || detail === null) {\n return null;\n }\n\n const value = (detail as { callout?: unknown }).callout;\n\n if (typeof value !== \"object\" || value === null) {\n return null;\n }\n\n const callout = value as Record<string, unknown>;\n\n if (typeof callout.message !== \"string\" || callout.message === \"\") {\n return null;\n }\n\n return {\n action: (callout.action as Callout[\"action\"]) ?? null,\n dismissible: callout.dismissible !== false,\n message: callout.message,\n title: typeof callout.title === \"string\" ? callout.title : null,\n variant: isVariant(callout.variant) ? callout.variant : \"info\",\n };\n}\n\nexport function onCallout(callback: (callout: Callout) => void): () => void {\n const listener = (event: Event): void => {\n const callout = normalizeCallout((event as CustomEvent).detail);\n\n if (callout) {\n callback(callout);\n }\n };\n\n window.addEventListener(LATTICE_EVENT.callout, listener);\n\n return () => window.removeEventListener(LATTICE_EVENT.callout, listener);\n}\n"],"mappings":";;;AAMA,SAAgB,iBAAiB,QAAiC;CAChE,IAAI,OAAO,WAAW,YAAY,WAAW,MAC3C,OAAO;CAGT,MAAM,QAAS,OAAiC;CAEhD,IAAI,OAAO,UAAU,YAAY,UAAU,MACzC,OAAO;CAGT,MAAM,UAAU;CAEhB,IAAI,OAAO,QAAQ,YAAY,YAAY,QAAQ,YAAY,IAC7D,OAAO;CAGT,OAAO;EACL,QAAS,QAAQ,UAAgC;EACjD,aAAa,QAAQ,gBAAgB;EACrC,SAAS,QAAQ;EACjB,OAAO,OAAO,QAAQ,UAAU,WAAW,QAAQ,QAAQ;EAC3D,SAAS,UAAU,QAAQ,OAAO,IAAI,QAAQ,UAAU;CAC1D;AACF;AAEA,SAAgB,UAAU,UAAkD;CAC1E,MAAM,YAAY,UAAuB;EACvC,MAAM,UAAU,iBAAkB,MAAsB,MAAM;EAE9D,IAAI,SACF,SAAS,OAAO;CAEpB;CAEA,OAAO,iBAAiB,cAAc,SAAS,QAAQ;CAEvD,aAAa,OAAO,oBAAoB,cAAc,SAAS,QAAQ;AACzE"}
|
package/dist/toast/toast.d.ts
CHANGED
|
@@ -6,9 +6,6 @@ export declare function isVariant(value: unknown): value is Variant;
|
|
|
6
6
|
* ToastMessage, defaulting optional fields, or null when it is malformed.
|
|
7
7
|
*/
|
|
8
8
|
export declare function normalizeToastMessage(value: unknown): ToastMessage | null;
|
|
9
|
-
/** Coerce a `lattice:toast` event detail (`{ toast: {...} }`) into a ToastMessage. */
|
|
10
9
|
export declare function normalizeToast(detail: unknown): ToastMessage | null;
|
|
11
|
-
/** Emit a toast onto the shared `lattice:toast` bus. */
|
|
12
10
|
export declare function showToast(toast: ToastMessage): void;
|
|
13
|
-
/** Subscribe to the shared toast bus. Returns an unsubscribe function. */
|
|
14
11
|
export declare function onToast(callback: (toast: ToastMessage) => void): () => void;
|
package/dist/toast/toast.js
CHANGED
|
@@ -29,17 +29,14 @@ function normalizeToastMessage(value) {
|
|
|
29
29
|
variant: isVariant(toast.variant) ? toast.variant : "success"
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
|
-
/** Coerce a `lattice:toast` event detail (`{ toast: {...} }`) into a ToastMessage. */
|
|
33
32
|
function normalizeToast(detail) {
|
|
34
33
|
if (typeof detail !== "object" || detail === null) return null;
|
|
35
34
|
return normalizeToastMessage(detail.toast);
|
|
36
35
|
}
|
|
37
|
-
/** Emit a toast onto the shared `lattice:toast` bus. */
|
|
38
36
|
function showToast(toast) {
|
|
39
37
|
if (typeof window === "undefined") return;
|
|
40
38
|
window.dispatchEvent(new CustomEvent(LATTICE_EVENT.toast, { detail: { toast } }));
|
|
41
39
|
}
|
|
42
|
-
/** Subscribe to the shared toast bus. Returns an unsubscribe function. */
|
|
43
40
|
function onToast(callback) {
|
|
44
41
|
const listener = (event) => {
|
|
45
42
|
const toast = normalizeToast(event.detail);
|
package/dist/toast/toast.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toast.js","names":[],"sources":["../../resources/js/toast/toast.ts"],"sourcesContent":["import type { ToastMessage, Variant } from \"@lattice-php/lattice/types/generated\";\nimport { LATTICE_EVENT } from \"@lattice-php/lattice/events/event-names\";\nimport { isTranslatable } from \"@lattice-php/lattice/i18n/translatable\";\n\nexport type { ToastMessage, Variant };\n\nconst variants = [\"success\", \"info\", \"warning\", \"error\"] as const satisfies readonly Variant[];\n\nexport function isVariant(value: unknown): value is Variant {\n return variants.some((variant) => variant === value);\n}\n\n/**\n * Coerce a raw toast value (server flash payload or event detail) into a\n * ToastMessage, defaulting optional fields, or null when it is malformed.\n */\nexport function normalizeToastMessage(value: unknown): ToastMessage | null {\n if (typeof value !== \"object\" || value === null) {\n return null;\n }\n\n const toast = value as Record<string, unknown>;\n\n const rawMessage = toast.message;\n const message =\n typeof rawMessage === \"string\" ? rawMessage : isTranslatable(rawMessage) ? rawMessage : null;\n\n if (message === null || message === \"\") {\n return null;\n }\n\n return {\n action: (toast.action as ToastMessage[\"action\"]) ?? null,\n dismissible: toast.dismissible !== false,\n duration: typeof toast.duration === \"number\" ? toast.duration : null,\n message,\n persistent: toast.persistent === true,\n variant: isVariant(toast.variant) ? toast.variant : \"success\",\n };\n}\n\
|
|
1
|
+
{"version":3,"file":"toast.js","names":[],"sources":["../../resources/js/toast/toast.ts"],"sourcesContent":["import type { ToastMessage, Variant } from \"@lattice-php/lattice/types/generated\";\nimport { LATTICE_EVENT } from \"@lattice-php/lattice/events/event-names\";\nimport { isTranslatable } from \"@lattice-php/lattice/i18n/translatable\";\n\nexport type { ToastMessage, Variant };\n\nconst variants = [\"success\", \"info\", \"warning\", \"error\"] as const satisfies readonly Variant[];\n\nexport function isVariant(value: unknown): value is Variant {\n return variants.some((variant) => variant === value);\n}\n\n/**\n * Coerce a raw toast value (server flash payload or event detail) into a\n * ToastMessage, defaulting optional fields, or null when it is malformed.\n */\nexport function normalizeToastMessage(value: unknown): ToastMessage | null {\n if (typeof value !== \"object\" || value === null) {\n return null;\n }\n\n const toast = value as Record<string, unknown>;\n\n const rawMessage = toast.message;\n const message =\n typeof rawMessage === \"string\" ? rawMessage : isTranslatable(rawMessage) ? rawMessage : null;\n\n if (message === null || message === \"\") {\n return null;\n }\n\n return {\n action: (toast.action as ToastMessage[\"action\"]) ?? null,\n dismissible: toast.dismissible !== false,\n duration: typeof toast.duration === \"number\" ? toast.duration : null,\n message,\n persistent: toast.persistent === true,\n variant: isVariant(toast.variant) ? toast.variant : \"success\",\n };\n}\n\nexport function normalizeToast(detail: unknown): ToastMessage | null {\n if (typeof detail !== \"object\" || detail === null) {\n return null;\n }\n\n return normalizeToastMessage((detail as { toast?: unknown }).toast);\n}\n\nexport function showToast(toast: ToastMessage): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n window.dispatchEvent(new CustomEvent(LATTICE_EVENT.toast, { detail: { toast } }));\n}\n\nexport function onToast(callback: (toast: ToastMessage) => void): () => void {\n const listener = (event: Event): void => {\n const toast = normalizeToast((event as CustomEvent).detail);\n\n if (toast) {\n callback(toast);\n }\n };\n\n window.addEventListener(LATTICE_EVENT.toast, listener);\n\n return () => window.removeEventListener(LATTICE_EVENT.toast, listener);\n}\n"],"mappings":";;;AAMA,IAAM,WAAW;CAAC;CAAW;CAAQ;CAAW;AAAO;AAEvD,SAAgB,UAAU,OAAkC;CAC1D,OAAO,SAAS,MAAM,YAAY,YAAY,KAAK;AACrD;;;;;AAMA,SAAgB,sBAAsB,OAAqC;CACzE,IAAI,OAAO,UAAU,YAAY,UAAU,MACzC,OAAO;CAGT,MAAM,QAAQ;CAEd,MAAM,aAAa,MAAM;CACzB,MAAM,UACJ,OAAO,eAAe,WAAW,aAAa,eAAe,UAAU,IAAI,aAAa;CAE1F,IAAI,YAAY,QAAQ,YAAY,IAClC,OAAO;CAGT,OAAO;EACL,QAAS,MAAM,UAAqC;EACpD,aAAa,MAAM,gBAAgB;EACnC,UAAU,OAAO,MAAM,aAAa,WAAW,MAAM,WAAW;EAChE;EACA,YAAY,MAAM,eAAe;EACjC,SAAS,UAAU,MAAM,OAAO,IAAI,MAAM,UAAU;CACtD;AACF;AAEA,SAAgB,eAAe,QAAsC;CACnE,IAAI,OAAO,WAAW,YAAY,WAAW,MAC3C,OAAO;CAGT,OAAO,sBAAuB,OAA+B,KAAK;AACpE;AAEA,SAAgB,UAAU,OAA2B;CACnD,IAAI,OAAO,WAAW,aACpB;CAGF,OAAO,cAAc,IAAI,YAAY,cAAc,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;AAClF;AAEA,SAAgB,QAAQ,UAAqD;CAC3E,MAAM,YAAY,UAAuB;EACvC,MAAM,QAAQ,eAAgB,MAAsB,MAAM;EAE1D,IAAI,OACF,SAAS,KAAK;CAElB;CAEA,OAAO,iBAAiB,cAAc,OAAO,QAAQ;CAErD,aAAa,OAAO,oBAAoB,cAAc,OAAO,QAAQ;AACvE"}
|
|
@@ -669,6 +669,7 @@ export type LayoutNode = {
|
|
|
669
669
|
schema?: Node[];
|
|
670
670
|
};
|
|
671
671
|
export type Link = {
|
|
672
|
+
action: Node | null;
|
|
672
673
|
href: string | null;
|
|
673
674
|
label: string;
|
|
674
675
|
method: HttpMethod | null;
|
|
@@ -685,6 +686,7 @@ export type LocaleChangeEffect = {
|
|
|
685
686
|
};
|
|
686
687
|
export type Menu = Record<string, never>;
|
|
687
688
|
export type MenuItem = {
|
|
689
|
+
action: Node | null;
|
|
688
690
|
href: string | null;
|
|
689
691
|
icon: string | null;
|
|
690
692
|
label: string;
|
|
@@ -1033,10 +1035,12 @@ export type TableSort = {
|
|
|
1033
1035
|
};
|
|
1034
1036
|
export type Tabs = {
|
|
1035
1037
|
activeValue: string;
|
|
1038
|
+
alignment: TabsAlignment;
|
|
1036
1039
|
defaultValue: string | null;
|
|
1037
1040
|
orientation: Orientation;
|
|
1038
1041
|
queryKey: string;
|
|
1039
1042
|
};
|
|
1043
|
+
export type TabsAlignment = "start" | "center" | "end" | "stretch";
|
|
1040
1044
|
export type Text = {
|
|
1041
1045
|
align: Align | null;
|
|
1042
1046
|
color: Color;
|