@lattice-php/lattice 0.24.0 → 0.26.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-form.js +37 -5
- package/dist/action/components/action-form.js.map +1 -1
- package/dist/action/components/action-group.js +1 -1
- package/dist/action/components/action-group.js.map +1 -1
- package/dist/action/components/action.js +3 -2
- package/dist/action/components/action.js.map +1 -1
- package/dist/action/hooks/use-action.js +2 -1
- package/dist/action/hooks/use-action.js.map +1 -1
- package/dist/appearance/index.d.ts +1 -1
- package/dist/appearance/index.js +8 -8
- package/dist/appearance/index.js.map +1 -1
- package/dist/chat/components/prompt-input.js +1 -2
- package/dist/chat/components/prompt-input.js.map +1 -1
- package/dist/create-app.js +2 -2
- package/dist/create-app.js.map +1 -1
- package/dist/form/components/base/submit-button.d.ts +4 -3
- package/dist/form/components/base/submit-button.js +2 -1
- package/dist/form/components/base/submit-button.js.map +1 -1
- package/dist/form/components/fields/date-picker-field.js +3 -3
- package/dist/form/components/fields/date-picker-field.js.map +1 -1
- package/dist/form/components/fields/number-input.js +1 -1
- package/dist/form/components/fields/number-input.js.map +1 -1
- package/dist/form/components/fields/rich-editor-field.js +1 -1
- package/dist/form/components/fields/rich-editor-field.js.map +1 -1
- package/dist/form/components/fields/row-action-menu.js +3 -3
- package/dist/form/components/fields/row-action-menu.js.map +1 -1
- package/dist/form/components/fields/row-actions.d.ts +1 -1
- package/dist/form/components/fields/row-actions.js +4 -4
- package/dist/form/components/fields/row-actions.js.map +1 -1
- package/dist/form/components/fields/toggle.js +1 -1
- package/dist/form/components/fields/toggle.js.map +1 -1
- package/dist/form/components/form.js +15 -5
- package/dist/form/components/form.js.map +1 -1
- package/dist/form/components/index.d.ts +1 -0
- package/dist/form/components/index.js +2 -1
- package/dist/form/components/wizard.d.ts +3 -0
- package/dist/form/components/wizard.js +148 -0
- package/dist/form/components/wizard.js.map +1 -0
- package/dist/form/embed.d.ts +1 -1
- package/dist/form/embed.js +2 -2
- package/dist/form/hooks/context.d.ts +7 -2
- package/dist/form/hooks/context.js +4 -1
- package/dist/form/hooks/context.js.map +1 -1
- package/dist/form/index.js +2 -1
- package/dist/form/lib/field-errors.d.ts +2 -0
- package/dist/form/lib/field-errors.js +5 -1
- package/dist/form/lib/field-errors.js.map +1 -1
- package/dist/form/lib/field-props.d.ts +6 -0
- package/dist/form/lib/field-props.js +7 -1
- package/dist/form/lib/field-props.js.map +1 -1
- package/dist/form/lib/prefill-targets.js +2 -3
- package/dist/form/lib/prefill-targets.js.map +1 -1
- package/dist/form/lib/wizard-steps.d.ts +5 -0
- package/dist/form/lib/wizard-steps.js +44 -0
- package/dist/form/lib/wizard-steps.js.map +1 -0
- package/dist/form/plugin.js +4 -1
- package/dist/form/plugin.js.map +1 -1
- package/dist/i18n/translatable.d.ts +1 -0
- package/dist/i18n/translatable.js +4 -1
- package/dist/i18n/translatable.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/lattice.css +183 -115
- package/dist/layout/components/callouts.js +3 -2
- package/dist/layout/components/callouts.js.map +1 -1
- package/dist/notifications/components/notification-item.js +10 -5
- package/dist/notifications/components/notification-item.js.map +1 -1
- package/dist/notifications/components/notifications.js +2 -4
- package/dist/notifications/components/notifications.js.map +1 -1
- package/dist/table/components/bulk-bar.js +2 -0
- package/dist/table/components/bulk-bar.js.map +1 -1
- package/dist/table/components/cells/badge-cell.js +3 -6
- package/dist/table/components/cells/badge-cell.js.map +1 -1
- package/dist/table/components/cells/text-cell.js +6 -9
- package/dist/table/components/cells/text-cell.js.map +1 -1
- package/dist/table/components/column-filter-control.js +2 -2
- package/dist/table/components/column-filter-control.js.map +1 -1
- package/dist/table/components/filter-bar.js +1 -1
- package/dist/table/components/filter-bar.js.map +1 -1
- package/dist/table/components/filter-controls.js +4 -1
- package/dist/table/components/filter-controls.js.map +1 -1
- package/dist/table/components/pagination.js +6 -6
- package/dist/table/components/pagination.js.map +1 -1
- package/dist/table/lib/bulk.d.ts +3 -2
- package/dist/table/lib/bulk.js +2 -1
- package/dist/table/lib/bulk.js.map +1 -1
- package/dist/toast/callout.js +6 -3
- package/dist/toast/callout.js.map +1 -1
- package/dist/toast/toast.js +3 -1
- package/dist/toast/toast.js.map +1 -1
- package/dist/toast/toaster.js +2 -2
- package/dist/toast/toaster.js.map +1 -1
- package/dist/toast/variant-styles.js +15 -1
- package/dist/toast/variant-styles.js.map +1 -1
- package/dist/types/generated.d.ts +31 -16
- package/dist/ui/action-menu-context.d.ts +1 -1
- package/dist/ui/action-menu-context.js +1 -1
- package/dist/ui/action-menu-context.js.map +1 -1
- package/dist/ui/affix-group.js +1 -1
- package/dist/ui/affix-group.js.map +1 -1
- package/dist/ui/badge.d.ts +4 -7
- package/dist/ui/badge.js +11 -17
- package/dist/ui/badge.js.map +1 -1
- package/dist/ui/button.d.ts +6 -5
- package/dist/ui/button.js +154 -15
- package/dist/ui/button.js.map +1 -1
- package/dist/ui/chart-view.js +9 -7
- package/dist/ui/chart-view.js.map +1 -1
- package/dist/ui/checkbox.js +1 -1
- package/dist/ui/checkbox.js.map +1 -1
- package/dist/ui/confirm-dialog.d.ts +4 -3
- package/dist/ui/confirm-dialog.js +3 -2
- package/dist/ui/confirm-dialog.js.map +1 -1
- package/dist/ui/control.d.ts +1 -1
- package/dist/ui/control.js +1 -1
- package/dist/ui/control.js.map +1 -1
- package/dist/ui/dialog.js +1 -1
- package/dist/ui/dialog.js.map +1 -1
- package/dist/ui/dropdown-menu.d.ts +2 -2
- package/dist/ui/dropdown-menu.js +2 -2
- package/dist/ui/dropdown-menu.js.map +1 -1
- package/dist/ui/icon-button.d.ts +3 -3
- package/dist/ui/icon-button.js +6 -6
- package/dist/ui/icon-button.js.map +1 -1
- package/dist/ui/image-preview.js +1 -1
- package/dist/ui/image-preview.js.map +1 -1
- package/dist/ui/index.d.ts +2 -2
- package/dist/ui/index.js +2 -2
- package/dist/ui/info-tooltip.js +1 -1
- package/dist/ui/info-tooltip.js.map +1 -1
- package/dist/ui/input-otp.js +1 -1
- package/dist/ui/input-otp.js.map +1 -1
- package/dist/ui/label.js +1 -1
- package/dist/ui/label.js.map +1 -1
- package/dist/ui/password-input.js +1 -1
- package/dist/ui/password-input.js.map +1 -1
- package/dist/ui/progress.js +2 -2
- package/dist/ui/progress.js.map +1 -1
- package/dist/ui/text.js +1 -1
- package/dist/ui/tooltip.js +1 -1
- package/dist/ui/tooltip.js.map +1 -1
- package/dist/vite-typescript-refresh.d.ts +9 -0
- package/dist/vite-typescript-refresh.js +47 -0
- package/dist/vite-typescript-refresh.js.map +1 -0
- package/dist/vite.d.ts +22 -1
- package/dist/vite.js +60 -7
- package/dist/vite.js.map +1 -1
- package/dist-standalone/chunks/{chart-view-DEwGTt9d.js → chart-view-8xEKTUJV.js} +1 -1
- package/dist-standalone/chunks/{date-picker-field-CCQ9nWx0.js → date-picker-field-cdhxtVSG.js} +1 -1
- package/dist-standalone/chunks/{rich-editor-field-B9Nv1qia.js → rich-editor-field-47ov3Bny.js} +1 -1
- package/dist-standalone/lattice.css +1 -1
- package/dist-standalone/lattice.js +15 -15
- package/dist-standalone/manifest.json +5 -5
- package/package.json +1 -1
|
@@ -14,7 +14,7 @@ import { FormValuesProvider, useFormValues } from "../../form/hooks/values.js";
|
|
|
14
14
|
import "../../form/lib/form-transport.js";
|
|
15
15
|
import { useFormResolver } from "../../form/hooks/use-form-resolver.js";
|
|
16
16
|
import { collectFields } from "../../form/lib/collect-fields.js";
|
|
17
|
-
import { firstErrors } from "../../form/lib/field-errors.js";
|
|
17
|
+
import { errorKeyBelongsTo, firstErrors } from "../../form/lib/field-errors.js";
|
|
18
18
|
import { useDebouncedCallback } from "../../lib/use-debounced-callback.js";
|
|
19
19
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
20
20
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -67,6 +67,7 @@ function ActionFormBody({ cancelLabel, componentRef, endpoint, extraData, fieldL
|
|
|
67
67
|
const dispatch = useEffectDispatcher();
|
|
68
68
|
const [errors, setErrors] = useState({});
|
|
69
69
|
const [processing, setProcessing] = useState(false);
|
|
70
|
+
const [validating, setValidating] = useState(false);
|
|
70
71
|
const request = useCallback((extraHeaders) => apiFetch(endpoint, {
|
|
71
72
|
body: JSON.stringify({
|
|
72
73
|
...valuesRef.current,
|
|
@@ -106,6 +107,31 @@ function ActionFormBody({ cancelLabel, componentRef, endpoint, extraData, fieldL
|
|
|
106
107
|
const validate = useCallback((field) => {
|
|
107
108
|
if (precognitive) runValidation(field);
|
|
108
109
|
}, [precognitive, runValidation]);
|
|
110
|
+
const touch = useCallback(() => {}, []);
|
|
111
|
+
const validateFields = useCallback((fields, options) => {
|
|
112
|
+
setValidating(true);
|
|
113
|
+
request({
|
|
114
|
+
Precognition: "true",
|
|
115
|
+
"Precognition-Validate-Only": fields.join(",")
|
|
116
|
+
}).then(async (response) => {
|
|
117
|
+
if (response.status === 422) {
|
|
118
|
+
const body = await response.json();
|
|
119
|
+
setErrors((current) => ({
|
|
120
|
+
...current,
|
|
121
|
+
...firstErrors(body.errors)
|
|
122
|
+
}));
|
|
123
|
+
options?.onValidationError?.();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (!response.ok) {
|
|
127
|
+
options?.onValidationError?.();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const cleared = fields.filter((field) => !field.includes("*"));
|
|
131
|
+
setErrors((current) => Object.fromEntries(Object.entries(current).filter(([key]) => !cleared.some((name) => errorKeyBelongsTo(key, name)))));
|
|
132
|
+
options?.onSuccess?.();
|
|
133
|
+
}).catch(() => options?.onValidationError?.()).finally(() => setValidating(false));
|
|
134
|
+
}, [request]);
|
|
109
135
|
const submit = useCallback(() => {
|
|
110
136
|
setProcessing(true);
|
|
111
137
|
request().then(async (response) => {
|
|
@@ -132,7 +158,10 @@ function ActionFormBody({ cancelLabel, componentRef, endpoint, extraData, fieldL
|
|
|
132
158
|
fieldLabels,
|
|
133
159
|
precognitive,
|
|
134
160
|
processing,
|
|
135
|
-
|
|
161
|
+
touch,
|
|
162
|
+
validate,
|
|
163
|
+
validateFields,
|
|
164
|
+
validating
|
|
136
165
|
}), [
|
|
137
166
|
clearErrors,
|
|
138
167
|
componentRef,
|
|
@@ -141,7 +170,10 @@ function ActionFormBody({ cancelLabel, componentRef, endpoint, extraData, fieldL
|
|
|
141
170
|
fieldLabels,
|
|
142
171
|
precognitive,
|
|
143
172
|
processing,
|
|
144
|
-
|
|
173
|
+
touch,
|
|
174
|
+
validate,
|
|
175
|
+
validateFields,
|
|
176
|
+
validating
|
|
145
177
|
]),
|
|
146
178
|
children: /* @__PURE__ */ jsxs("form", {
|
|
147
179
|
className: "flex flex-col gap-6",
|
|
@@ -162,9 +194,9 @@ function ActionFormBody({ cancelLabel, componentRef, endpoint, extraData, fieldL
|
|
|
162
194
|
disabled: processing,
|
|
163
195
|
onClick: onClose,
|
|
164
196
|
type: "button",
|
|
165
|
-
|
|
197
|
+
emphasis: "ghost",
|
|
166
198
|
children: cancelLabel
|
|
167
|
-
}), /* @__PURE__ */ jsxs(Button, {
|
|
199
|
+
}), formNode.props?.submitButton !== false && /* @__PURE__ */ jsxs(Button, {
|
|
168
200
|
"data-test": "action-form-submit",
|
|
169
201
|
disabled: processing,
|
|
170
202
|
type: "submit",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-form.js","names":[],"sources":["../../../resources/js/action/components/action-form.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { apiFetch } from \"@lattice-php/lattice/core/api\";\nimport { Button } from \"@lattice-php/lattice/ui/button\";\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n type DialogPlacement,\n} from \"@lattice-php/lattice/ui/dialog\";\nimport { Skeleton } from \"@lattice-php/lattice/ui/skeleton\";\nimport { Spinner } from \"@lattice-php/lattice/ui/spinner\";\nimport { Renderer } from \"@lattice-php/lattice/core/renderer\";\nimport type { Node } from \"@lattice-php/lattice/core/types\";\nimport type { ModalWidth } from \"@lattice-php/lattice/types/generated\";\nimport {\n collectFields,\n FORM_DEBOUNCE_MS,\n FormProvider,\n FormValuesProvider,\n firstErrors,\n PrefillProvider,\n ResolvedNodesProvider,\n useFormResolver,\n useFormValues,\n} from \"@lattice-php/lattice/form/embed\";\nimport type { FieldErrors } from \"@lattice-php/lattice/form/embed\";\nimport { useDebouncedCallback } from \"@lattice-php/lattice/lib/use-debounced-callback\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\nimport { dispatchActionError, getActionEffects } from \"@lattice-php/lattice/effects/dispatch\";\nimport type { ActionResponse } from \"@lattice-php/lattice/effects/dispatch\";\nimport { useEffectDispatcher } from \"@lattice-php/lattice/effects/use-effect-dispatcher\";\n\ntype ActionFormProps = {\n cancelLabel: string;\n componentRef: string;\n description?: string;\n endpoint: string;\n /** Extra payload merged into every request, e.g. a bulk action's selection. */\n extraData?: Record<string, unknown>;\n /** The form to render; null while a lazy schema is still being fetched. */\n formNode: Node | null;\n method: string;\n onClose: () => void;\n onSuccess: (response: ActionResponse) => void;\n /** Dialog placement for the form modal; sheets dock to a viewport edge. */\n placement?: DialogPlacement;\n submitLabel: string;\n title: string;\n width?: ModalWidth;\n};\n\n/**\n * Fetch a lazily-served form schema from the action endpoint while `enabled`,\n * so it can be prefilled per record. Returns null until it arrives.\n */\nexport function useLazyActionForm(\n endpoint: string,\n componentRef: string,\n enabled: boolean,\n): Node | null {\n const [node, setNode] = useState<Node | null>(null);\n\n useEffect(() => {\n if (!enabled) {\n setNode(null);\n\n return;\n }\n\n const controller = new AbortController();\n\n void apiFetch(endpoint, {\n body: JSON.stringify({ _form: true }),\n ref: componentRef,\n method: \"POST\",\n signal: controller.signal,\n throwOnError: false,\n })\n .then((response) => (response.ok ? (response.json() as Promise<Node>) : null))\n .then((fetched) => setNode(fetched))\n .catch(() => {});\n\n return () => controller.abort();\n }, [enabled, endpoint, componentRef]);\n\n return node;\n}\n\nfunction ActionFormSkeleton() {\n return (\n <div className=\"space-y-4\" data-lattice-action-form-loading>\n <Skeleton className=\"h-4 w-24\" />\n <Skeleton className=\"h-10 w-full\" />\n <Skeleton className=\"h-10 w-full\" />\n </div>\n );\n}\n\nfunction ActionFormBody({\n cancelLabel,\n componentRef,\n endpoint,\n extraData,\n fieldLabels,\n formNode,\n method,\n onClose,\n onSuccess,\n precognitive,\n submitLabel,\n}: Omit<ActionFormProps, \"description\" | \"title\"> & {\n fieldLabels: Record<string, string>;\n formNode: Node;\n precognitive: boolean;\n}) {\n const values = useFormValues();\n const valuesRef = useRef(values);\n valuesRef.current = values;\n const extraDataRef = useRef(extraData);\n extraDataRef.current = extraData;\n const { nodes: resolvedNodes, markUserEdit } = useFormResolver(\n endpoint,\n componentRef,\n formNode.schema,\n );\n\n const dispatch = useEffectDispatcher();\n const [errors, setErrors] = useState<FieldErrors>({});\n const [processing, setProcessing] = useState(false);\n\n const request = useCallback(\n (extraHeaders?: Record<string, string>): Promise<Response> =>\n apiFetch(endpoint, {\n body: JSON.stringify({ ...valuesRef.current, ...extraDataRef.current }),\n method,\n ref: componentRef,\n headers: extraHeaders,\n throwOnError: false,\n }),\n [componentRef, endpoint, method],\n );\n\n const clearErrors = useCallback((field: string) => {\n setErrors((current) =>\n current[field] === undefined ? current : { ...current, [field]: undefined },\n );\n }, []);\n\n const runValidation = useDebouncedCallback((field: string) => {\n void request({ Precognition: \"true\", \"Precognition-Validate-Only\": field })\n .then(async (response) => {\n if (response.status === 422) {\n const body = (await response.json()) as { errors?: Record<string, string[]> };\n setErrors((current) => ({ ...current, ...firstErrors(body.errors) }));\n\n return;\n }\n\n clearErrors(field);\n })\n .catch(() => {});\n }, FORM_DEBOUNCE_MS);\n\n const validate = useCallback(\n (field: string) => {\n if (precognitive) {\n runValidation(field);\n }\n },\n [precognitive, runValidation],\n );\n\n const submit = useCallback(() => {\n setProcessing(true);\n\n void request()\n .then(async (response) => {\n const body = (await response.json().catch(() => ({}))) as ActionResponse & {\n errors?: Record<string, string[]>;\n };\n\n dispatch(getActionEffects(body.effects));\n\n if (response.status === 422 && body.errors) {\n setErrors(firstErrors(body.errors));\n\n return;\n }\n\n if (!response.ok) {\n return;\n }\n\n onSuccess(body);\n })\n .catch((error: unknown) => dispatchActionError(error))\n .finally(() => setProcessing(false));\n }, [dispatch, onSuccess, request]);\n\n const context = useMemo(\n () => ({\n action: endpoint,\n clearErrors,\n componentRef,\n errors,\n fieldLabels,\n precognitive,\n processing,\n validate,\n }),\n [clearErrors, componentRef, endpoint, errors, fieldLabels, precognitive, processing, validate],\n );\n\n return (\n <FormProvider value={context}>\n <form\n className=\"flex flex-col gap-6\"\n onSubmit={(event) => {\n event.preventDefault();\n submit();\n }}\n >\n <PrefillProvider value={{ markUserEdit }}>\n <ResolvedNodesProvider nodes={resolvedNodes}>\n <Renderer nodes={formNode.schema ?? []} />\n </ResolvedNodesProvider>\n </PrefillProvider>\n\n <div className=\"flex justify-end gap-3\">\n <Button\n data-test=\"action-form-cancel\"\n disabled={processing}\n onClick={onClose}\n type=\"button\"\n variant=\"ghost\"\n >\n {cancelLabel}\n </Button>\n\n <Button data-test=\"action-form-submit\" disabled={processing} type=\"submit\">\n {processing && <Spinner />}\n {submitLabel}\n </Button>\n </div>\n </form>\n </FormProvider>\n );\n}\n\nfunction ActionFormContent({\n formNode,\n ...rest\n}: Omit<ActionFormProps, \"description\" | \"title\"> & { formNode: Node }) {\n const precognitive = Boolean(formNode.props?.precognitive);\n const { labels: fieldLabels, values: initialValues } = useMemo(() => {\n const { labels, values } = collectFields(formNode.schema);\n\n return {\n labels,\n values: { ...values, ...(formNode.props?.state as Record<string, unknown> | undefined) },\n };\n }, [formNode]);\n\n return (\n <FormValuesProvider initial={initialValues}>\n <ActionFormBody\n fieldLabels={fieldLabels}\n formNode={formNode}\n precognitive={precognitive}\n {...rest}\n />\n </FormValuesProvider>\n );\n}\n\nexport function ActionForm({\n description,\n formNode,\n onClose,\n placement,\n title,\n width,\n ...rest\n}: ActionFormProps) {\n const { t } = useT(\"lattice\");\n\n return (\n <Dialog\n open\n onOpenChange={(open) => {\n if (!open) {\n onClose();\n }\n }}\n >\n <DialogContent\n {...(description ? {} : { \"aria-describedby\": undefined })}\n placement={placement}\n width={width}\n >\n <DialogHeader\n closeLabel={t(\"common.close\", \"Close\")}\n description={description}\n title={title}\n />\n\n <div className=\"mt-6\">\n {formNode ? (\n <ActionFormContent formNode={formNode} onClose={onClose} {...rest} />\n ) : (\n <ActionFormSkeleton />\n )}\n </div>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"action-form.js","names":[],"sources":["../../../resources/js/action/components/action-form.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { apiFetch } from \"@lattice-php/lattice/core/api\";\nimport { Button } from \"@lattice-php/lattice/ui/button\";\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n type DialogPlacement,\n} from \"@lattice-php/lattice/ui/dialog\";\nimport { Skeleton } from \"@lattice-php/lattice/ui/skeleton\";\nimport { Spinner } from \"@lattice-php/lattice/ui/spinner\";\nimport { Renderer } from \"@lattice-php/lattice/core/renderer\";\nimport type { Node } from \"@lattice-php/lattice/core/types\";\nimport type { ModalWidth } from \"@lattice-php/lattice/types/generated\";\nimport {\n collectFields,\n FORM_DEBOUNCE_MS,\n FormProvider,\n FormValuesProvider,\n errorKeyBelongsTo,\n firstErrors,\n PrefillProvider,\n ResolvedNodesProvider,\n useFormResolver,\n useFormValues,\n} from \"@lattice-php/lattice/form/embed\";\nimport type { FieldErrors } from \"@lattice-php/lattice/form/embed\";\nimport { useDebouncedCallback } from \"@lattice-php/lattice/lib/use-debounced-callback\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\nimport { dispatchActionError, getActionEffects } from \"@lattice-php/lattice/effects/dispatch\";\nimport type { ActionResponse } from \"@lattice-php/lattice/effects/dispatch\";\nimport { useEffectDispatcher } from \"@lattice-php/lattice/effects/use-effect-dispatcher\";\n\ntype ActionFormProps = {\n cancelLabel: string;\n componentRef: string;\n description?: string;\n endpoint: string;\n /** Extra payload merged into every request, e.g. a bulk action's selection. */\n extraData?: Record<string, unknown>;\n /** The form to render; null while a lazy schema is still being fetched. */\n formNode: Node | null;\n method: string;\n onClose: () => void;\n onSuccess: (response: ActionResponse) => void;\n /** Dialog placement for the form modal; sheets dock to a viewport edge. */\n placement?: DialogPlacement;\n submitLabel: string;\n title: string;\n width?: ModalWidth;\n};\n\n/**\n * Fetch a lazily-served form schema from the action endpoint while `enabled`,\n * so it can be prefilled per record. Returns null until it arrives.\n */\nexport function useLazyActionForm(\n endpoint: string,\n componentRef: string,\n enabled: boolean,\n): Node | null {\n const [node, setNode] = useState<Node | null>(null);\n\n useEffect(() => {\n if (!enabled) {\n setNode(null);\n\n return;\n }\n\n const controller = new AbortController();\n\n void apiFetch(endpoint, {\n body: JSON.stringify({ _form: true }),\n ref: componentRef,\n method: \"POST\",\n signal: controller.signal,\n throwOnError: false,\n })\n .then((response) => (response.ok ? (response.json() as Promise<Node>) : null))\n .then((fetched) => setNode(fetched))\n .catch(() => {});\n\n return () => controller.abort();\n }, [enabled, endpoint, componentRef]);\n\n return node;\n}\n\nfunction ActionFormSkeleton() {\n return (\n <div className=\"space-y-4\" data-lattice-action-form-loading>\n <Skeleton className=\"h-4 w-24\" />\n <Skeleton className=\"h-10 w-full\" />\n <Skeleton className=\"h-10 w-full\" />\n </div>\n );\n}\n\nfunction ActionFormBody({\n cancelLabel,\n componentRef,\n endpoint,\n extraData,\n fieldLabels,\n formNode,\n method,\n onClose,\n onSuccess,\n precognitive,\n submitLabel,\n}: Omit<ActionFormProps, \"description\" | \"title\"> & {\n fieldLabels: Record<string, string>;\n formNode: Node;\n precognitive: boolean;\n}) {\n const values = useFormValues();\n const valuesRef = useRef(values);\n valuesRef.current = values;\n const extraDataRef = useRef(extraData);\n extraDataRef.current = extraData;\n const { nodes: resolvedNodes, markUserEdit } = useFormResolver(\n endpoint,\n componentRef,\n formNode.schema,\n );\n\n const dispatch = useEffectDispatcher();\n const [errors, setErrors] = useState<FieldErrors>({});\n const [processing, setProcessing] = useState(false);\n const [validating, setValidating] = useState(false);\n\n const request = useCallback(\n (extraHeaders?: Record<string, string>): Promise<Response> =>\n apiFetch(endpoint, {\n body: JSON.stringify({ ...valuesRef.current, ...extraDataRef.current }),\n method,\n ref: componentRef,\n headers: extraHeaders,\n throwOnError: false,\n }),\n [componentRef, endpoint, method],\n );\n\n const clearErrors = useCallback((field: string) => {\n setErrors((current) =>\n current[field] === undefined ? current : { ...current, [field]: undefined },\n );\n }, []);\n\n const runValidation = useDebouncedCallback((field: string) => {\n void request({ Precognition: \"true\", \"Precognition-Validate-Only\": field })\n .then(async (response) => {\n if (response.status === 422) {\n const body = (await response.json()) as { errors?: Record<string, string[]> };\n setErrors((current) => ({ ...current, ...firstErrors(body.errors) }));\n\n return;\n }\n\n clearErrors(field);\n })\n .catch(() => {});\n }, FORM_DEBOUNCE_MS);\n\n const validate = useCallback(\n (field: string) => {\n if (precognitive) {\n runValidation(field);\n }\n },\n [precognitive, runValidation],\n );\n\n const touch = useCallback(() => {}, []);\n\n const validateFields = useCallback(\n (fields: string[], options?: { onSuccess?: () => void; onValidationError?: () => void }) => {\n setValidating(true);\n\n void request({ Precognition: \"true\", \"Precognition-Validate-Only\": fields.join(\",\") })\n .then(async (response) => {\n if (response.status === 422) {\n const body = (await response.json()) as { errors?: Record<string, string[]> };\n setErrors((current) => ({ ...current, ...firstErrors(body.errors) }));\n options?.onValidationError?.();\n\n return;\n }\n\n if (!response.ok) {\n options?.onValidationError?.();\n\n return;\n }\n\n const cleared = fields.filter((field) => !field.includes(\"*\"));\n setErrors((current) =>\n Object.fromEntries(\n Object.entries(current).filter(\n ([key]) => !cleared.some((name) => errorKeyBelongsTo(key, name)),\n ),\n ),\n );\n options?.onSuccess?.();\n })\n .catch(() => options?.onValidationError?.())\n .finally(() => setValidating(false));\n },\n [request],\n );\n\n const submit = useCallback(() => {\n setProcessing(true);\n\n void request()\n .then(async (response) => {\n const body = (await response.json().catch(() => ({}))) as ActionResponse & {\n errors?: Record<string, string[]>;\n };\n\n dispatch(getActionEffects(body.effects));\n\n if (response.status === 422 && body.errors) {\n setErrors(firstErrors(body.errors));\n\n return;\n }\n\n if (!response.ok) {\n return;\n }\n\n onSuccess(body);\n })\n .catch((error: unknown) => dispatchActionError(error))\n .finally(() => setProcessing(false));\n }, [dispatch, onSuccess, request]);\n\n const context = useMemo(\n () => ({\n action: endpoint,\n clearErrors,\n componentRef,\n errors,\n fieldLabels,\n precognitive,\n processing,\n touch,\n validate,\n validateFields,\n validating,\n }),\n [\n clearErrors,\n componentRef,\n endpoint,\n errors,\n fieldLabels,\n precognitive,\n processing,\n touch,\n validate,\n validateFields,\n validating,\n ],\n );\n\n return (\n <FormProvider value={context}>\n <form\n className=\"flex flex-col gap-6\"\n onSubmit={(event) => {\n event.preventDefault();\n submit();\n }}\n >\n <PrefillProvider value={{ markUserEdit }}>\n <ResolvedNodesProvider nodes={resolvedNodes}>\n <Renderer nodes={formNode.schema ?? []} />\n </ResolvedNodesProvider>\n </PrefillProvider>\n\n <div className=\"flex justify-end gap-3\">\n <Button\n data-test=\"action-form-cancel\"\n disabled={processing}\n onClick={onClose}\n type=\"button\"\n emphasis=\"ghost\"\n >\n {cancelLabel}\n </Button>\n\n {formNode.props?.submitButton !== false && (\n <Button data-test=\"action-form-submit\" disabled={processing} type=\"submit\">\n {processing && <Spinner />}\n {submitLabel}\n </Button>\n )}\n </div>\n </form>\n </FormProvider>\n );\n}\n\nfunction ActionFormContent({\n formNode,\n ...rest\n}: Omit<ActionFormProps, \"description\" | \"title\"> & { formNode: Node }) {\n const precognitive = Boolean(formNode.props?.precognitive);\n const { labels: fieldLabels, values: initialValues } = useMemo(() => {\n const { labels, values } = collectFields(formNode.schema);\n\n return {\n labels,\n values: { ...values, ...(formNode.props?.state as Record<string, unknown> | undefined) },\n };\n }, [formNode]);\n\n return (\n <FormValuesProvider initial={initialValues}>\n <ActionFormBody\n fieldLabels={fieldLabels}\n formNode={formNode}\n precognitive={precognitive}\n {...rest}\n />\n </FormValuesProvider>\n );\n}\n\nexport function ActionForm({\n description,\n formNode,\n onClose,\n placement,\n title,\n width,\n ...rest\n}: ActionFormProps) {\n const { t } = useT(\"lattice\");\n\n return (\n <Dialog\n open\n onOpenChange={(open) => {\n if (!open) {\n onClose();\n }\n }}\n >\n <DialogContent\n {...(description ? {} : { \"aria-describedby\": undefined })}\n placement={placement}\n width={width}\n >\n <DialogHeader\n closeLabel={t(\"common.close\", \"Close\")}\n description={description}\n title={title}\n />\n\n <div className=\"mt-6\">\n {formNode ? (\n <ActionFormContent formNode={formNode} onClose={onClose} {...rest} />\n ) : (\n <ActionFormSkeleton />\n )}\n </div>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":"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"}
|
|
@@ -29,7 +29,7 @@ var ActionGroupComponent = ({ children, node }) => {
|
|
|
29
29
|
"data-test": nodeIdentity(node),
|
|
30
30
|
size: "icon",
|
|
31
31
|
type: "button",
|
|
32
|
-
|
|
32
|
+
emphasis: "ghost",
|
|
33
33
|
children: /* @__PURE__ */ jsx(Icon, {
|
|
34
34
|
name: "more-horizontal",
|
|
35
35
|
"aria-hidden": "true",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-group.js","names":[],"sources":["../../../resources/js/action/components/action-group.tsx"],"sourcesContent":["import { Icon } from \"@lattice-php/lattice/icons\";\nimport { Button } from \"@lattice-php/lattice/ui/button\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuTrigger,\n} from \"@lattice-php/lattice/ui/dropdown-menu\";\nimport { nodeIdentity } from \"@lattice-php/lattice/core/test-id\";\nimport type { RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\nimport { ActionMenuProvider } from \"@lattice-php/lattice/ui/action-menu-context\";\n\nconst ActionGroupComponent: RendererComponent<\"action.group\"> = ({ children, node }) => {\n const { t } = useT(\"lattice\");\n const label = node.props.label ?? t(\"common.action-group.label\", \"Actions\");\n const orientation = node.props.orientation;\n\n if (orientation) {\n return (\n <div\n aria-label={label}\n className={cn(\n \"inline-flex max-w-full gap-1\",\n orientation === \"vertical\" ? \"flex-col items-stretch\" : \"flex-row flex-wrap items-center\",\n )}\n data-lattice-component={node.id}\n role=\"group\"\n >\n {children}\n </div>\n );\n }\n\n return (\n <div className=\"inline-flex\" data-lattice-component={node.id}>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n aria-label={label}\n className=\"size-lt-control-sm text-lt-muted-fg shadow-none hover:text-lt-fg\"\n data-test={nodeIdentity(node)}\n size=\"icon\"\n type=\"button\"\n
|
|
1
|
+
{"version":3,"file":"action-group.js","names":[],"sources":["../../../resources/js/action/components/action-group.tsx"],"sourcesContent":["import { Icon } from \"@lattice-php/lattice/icons\";\nimport { Button } from \"@lattice-php/lattice/ui/button\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuTrigger,\n} from \"@lattice-php/lattice/ui/dropdown-menu\";\nimport { nodeIdentity } from \"@lattice-php/lattice/core/test-id\";\nimport type { RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\nimport { ActionMenuProvider } from \"@lattice-php/lattice/ui/action-menu-context\";\n\nconst ActionGroupComponent: RendererComponent<\"action.group\"> = ({ children, node }) => {\n const { t } = useT(\"lattice\");\n const label = node.props.label ?? t(\"common.action-group.label\", \"Actions\");\n const orientation = node.props.orientation;\n\n if (orientation) {\n return (\n <div\n aria-label={label}\n className={cn(\n \"inline-flex max-w-full gap-1\",\n orientation === \"vertical\" ? \"flex-col items-stretch\" : \"flex-row flex-wrap items-center\",\n )}\n data-lattice-component={node.id}\n role=\"group\"\n >\n {children}\n </div>\n );\n }\n\n return (\n <div className=\"inline-flex\" data-lattice-component={node.id}>\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n aria-label={label}\n className=\"size-lt-control-sm text-lt-muted-fg shadow-none hover:text-lt-fg\"\n data-test={nodeIdentity(node)}\n size=\"icon\"\n type=\"button\"\n emphasis=\"ghost\"\n >\n <Icon name=\"more-horizontal\" aria-hidden=\"true\" className=\"size-lt-icon-md\" />\n </Button>\n </DropdownMenuTrigger>\n\n <DropdownMenuContent\n align=\"end\"\n aria-label={label}\n className=\"min-w-44 gap-0.5 p-1.5\"\n sideOffset={8}\n >\n <ActionMenuProvider>{children}</ActionMenuProvider>\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n );\n};\n\nexport default ActionGroupComponent;\n"],"mappings":";;;;;;;;;AAaA,IAAM,wBAA2D,EAAE,UAAU,WAAW;CACtF,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,MAAM,QAAQ,KAAK,MAAM,SAAS,EAAE,6BAA6B,SAAS;CAC1E,MAAM,cAAc,KAAK,MAAM;CAE/B,IAAI,aACF,OACE,oBAAC,OAAD;EACE,cAAY;EACZ,WAAW,GACT,gCACA,gBAAgB,aAAa,2BAA2B,iCAC1D;EACA,0BAAwB,KAAK;EAC7B,MAAK;EAEJ;CACE,CAAA;CAIT,OACE,oBAAC,OAAD;EAAK,WAAU;EAAc,0BAAwB,KAAK;YACxD,qBAAC,cAAD,EAAA,UAAA,CACE,oBAAC,qBAAD;GAAqB,SAAA;aACnB,oBAAC,QAAD;IACE,cAAY;IACZ,WAAU;IACV,aAAW,aAAa,IAAI;IAC5B,MAAK;IACL,MAAK;IACL,UAAS;cAET,oBAAC,MAAD;KAAM,MAAK;KAAkB,eAAY;KAAO,WAAU;IAAmB,CAAA;GACvE,CAAA;EACW,CAAA,GAErB,oBAAC,qBAAD;GACE,OAAM;GACN,cAAY;GACZ,WAAU;GACV,YAAY;aAEZ,oBAAC,oBAAD,EAAqB,SAA6B,CAAA;EAC/B,CAAA,CACT,EAAA,CAAA;CACX,CAAA;AAET"}
|
|
@@ -12,7 +12,7 @@ var ActionComponent = ({ node }) => {
|
|
|
12
12
|
const icon = node.props.icon;
|
|
13
13
|
const label = actionLabel(node);
|
|
14
14
|
const isMenuItem = useActionMenu();
|
|
15
|
-
const variant = node.props
|
|
15
|
+
const { variant, emphasis } = node.props;
|
|
16
16
|
const { processing, requestSubmit, overlays } = useAction(node);
|
|
17
17
|
const testId = node.key ?? prefixedTestId("action", node.id);
|
|
18
18
|
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Button, {
|
|
@@ -22,7 +22,8 @@ var ActionComponent = ({ node }) => {
|
|
|
22
22
|
disabled: processing || !endpoint,
|
|
23
23
|
onClick: requestSubmit,
|
|
24
24
|
type: "button",
|
|
25
|
-
|
|
25
|
+
emphasis: isMenuItem ? "ghost" : emphasis,
|
|
26
|
+
variant: isMenuItem ? null : variant,
|
|
26
27
|
children: [processing ? /* @__PURE__ */ jsx(Spinner, { className: isMenuItem ? "size-lt-icon-sm" : void 0 }) : icon && /* @__PURE__ */ jsx(IconRenderer, {
|
|
27
28
|
className: isMenuItem ? "size-lt-icon-sm" : "size-lt-icon-md",
|
|
28
29
|
icon
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action.js","names":[],"sources":["../../../resources/js/action/components/action.tsx"],"sourcesContent":["import { Button } from \"@lattice-php/lattice/ui/button\";\nimport { Spinner } from \"@lattice-php/lattice/ui/spinner\";\nimport { prefixedTestId } from \"@lattice-php/lattice/core/test-id\";\nimport type { RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { IconRenderer } from \"@lattice-php/lattice/icons\";\nimport {\n actionMenuItemClassName,\n useActionMenu,\n} from \"@lattice-php/lattice/ui/action-menu-context\";\nimport { useAction } from \"@lattice-php/lattice/action/hooks/use-action\";\nimport { actionLabel } from \"@lattice-php/lattice/action/lib/action-label\";\n\nconst ActionComponent: RendererComponent<\"action\"> = ({ node }) => {\n const endpoint = node.props.endpoint ?? \"\";\n const icon = node.props.icon;\n const label = actionLabel(node);\n const isMenuItem = useActionMenu();\n const variant = node.props
|
|
1
|
+
{"version":3,"file":"action.js","names":[],"sources":["../../../resources/js/action/components/action.tsx"],"sourcesContent":["import { Button } from \"@lattice-php/lattice/ui/button\";\nimport { Spinner } from \"@lattice-php/lattice/ui/spinner\";\nimport { prefixedTestId } from \"@lattice-php/lattice/core/test-id\";\nimport type { RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport { IconRenderer } from \"@lattice-php/lattice/icons\";\nimport {\n actionMenuItemClassName,\n useActionMenu,\n} from \"@lattice-php/lattice/ui/action-menu-context\";\nimport { useAction } from \"@lattice-php/lattice/action/hooks/use-action\";\nimport { actionLabel } from \"@lattice-php/lattice/action/lib/action-label\";\n\nconst ActionComponent: RendererComponent<\"action\"> = ({ node }) => {\n const endpoint = node.props.endpoint ?? \"\";\n const icon = node.props.icon;\n const label = actionLabel(node);\n const isMenuItem = useActionMenu();\n const { variant, emphasis } = node.props;\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 emphasis={isMenuItem ? \"ghost\" : emphasis}\n variant={isMenuItem ? null : variant}\n >\n {processing ? (\n <Spinner className={isMenuItem ? \"size-lt-icon-sm\" : undefined} />\n ) : (\n icon && (\n <IconRenderer\n className={isMenuItem ? \"size-lt-icon-sm\" : \"size-lt-icon-md\"}\n icon={icon}\n />\n )\n )}\n {label}\n </Button>\n\n {overlays}\n </>\n );\n};\n\nexport default ActionComponent;\n"],"mappings":";;;;;;;;;AAYA,IAAM,mBAAgD,EAAE,WAAW;CACjE,MAAM,WAAW,KAAK,MAAM,YAAY;CACxC,MAAM,OAAO,KAAK,MAAM;CACxB,MAAM,QAAQ,YAAY,IAAI;CAC9B,MAAM,aAAa,cAAc;CACjC,MAAM,EAAE,SAAS,aAAa,KAAK;CACnC,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,UAAU,aAAa,UAAU;EACjC,SAAS,aAAa,OAAO;YAR/B,CAUG,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"}
|
|
@@ -21,7 +21,7 @@ function useAction(node) {
|
|
|
21
21
|
const componentRef = node.props.ref ?? "";
|
|
22
22
|
const method = node.props.method ?? "post";
|
|
23
23
|
const label = actionLabel(node);
|
|
24
|
-
const variant = node.props
|
|
24
|
+
const { variant, emphasis } = node.props;
|
|
25
25
|
const confirmation = node.props.confirmation;
|
|
26
26
|
const inlineForm = node.props.form;
|
|
27
27
|
const lazyForm = node.props.lazyForm === true;
|
|
@@ -71,6 +71,7 @@ function useAction(node) {
|
|
|
71
71
|
confirmLabel: confirmationConfirmLabel,
|
|
72
72
|
cancelLabel: confirmationCancelLabel,
|
|
73
73
|
confirmVariant: variant,
|
|
74
|
+
confirmEmphasis: emphasis,
|
|
74
75
|
processing,
|
|
75
76
|
confirmDisabled: !endpoint,
|
|
76
77
|
onConfirm: () => void submit(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-action.js","names":[],"sources":["../../../resources/js/action/hooks/use-action.tsx"],"sourcesContent":["import { router } from \"@inertiajs/react\";\nimport type { Method } from \"@inertiajs/core\";\nimport { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { ConfirmDialog } from \"@lattice-php/lattice/ui/confirm-dialog\";\nimport { apiFetch } from \"@lattice-php/lattice/core/api\";\nimport { withHeaders } from \"@lattice-php/lattice/core/headers\";\nimport type { Node } from \"@lattice-php/lattice/core/types\";\nimport { translate } from \"@lattice-php/lattice/i18n\";\nimport { useEffectDispatcher } from \"@lattice-php/lattice/effects/use-effect-dispatcher\";\nimport { runAction } from \"@lattice-php/lattice/action/lib/run-action\";\nimport { ActionForm, useLazyActionForm } from \"@lattice-php/lattice/action/components/action-form\";\nimport { actionLabel } from \"@lattice-php/lattice/action/lib/action-label\";\n\ntype UseAction = {\n /** Whether the action request is in flight. */\n processing: boolean;\n /** Gate then run the action: open the form, confirm, or dispatch directly. */\n requestSubmit: () => void;\n /** The confirm dialog and action form rendered next to the trigger. */\n overlays: ReactNode;\n};\n\n/**\n * The shared action machinery behind the Action button, action menu items, and\n * action links: it gates submission (form → modal, confirmation → confirm,\n * otherwise dispatch) and renders the matching overlays. The host owns the\n * trigger element so each surface keeps its own styling.\n */\nexport function useAction(node: Node<\"action\" | \"action.bulk\">): UseAction {\n const endpoint = node.props.endpoint ?? \"\";\n const componentRef = node.props.ref ?? \"\";\n const method: Method = node.props.method ?? \"post\";\n const label = actionLabel(node);\n const variant = node.props
|
|
1
|
+
{"version":3,"file":"use-action.js","names":[],"sources":["../../../resources/js/action/hooks/use-action.tsx"],"sourcesContent":["import { router } from \"@inertiajs/react\";\nimport type { Method } from \"@inertiajs/core\";\nimport { useState } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { ConfirmDialog } from \"@lattice-php/lattice/ui/confirm-dialog\";\nimport { apiFetch } from \"@lattice-php/lattice/core/api\";\nimport { withHeaders } from \"@lattice-php/lattice/core/headers\";\nimport type { Node } from \"@lattice-php/lattice/core/types\";\nimport { translate } from \"@lattice-php/lattice/i18n\";\nimport { useEffectDispatcher } from \"@lattice-php/lattice/effects/use-effect-dispatcher\";\nimport { runAction } from \"@lattice-php/lattice/action/lib/run-action\";\nimport { ActionForm, useLazyActionForm } from \"@lattice-php/lattice/action/components/action-form\";\nimport { actionLabel } from \"@lattice-php/lattice/action/lib/action-label\";\n\ntype UseAction = {\n /** Whether the action request is in flight. */\n processing: boolean;\n /** Gate then run the action: open the form, confirm, or dispatch directly. */\n requestSubmit: () => void;\n /** The confirm dialog and action form rendered next to the trigger. */\n overlays: ReactNode;\n};\n\n/**\n * The shared action machinery behind the Action button, action menu items, and\n * action links: it gates submission (form → modal, confirmation → confirm,\n * otherwise dispatch) and renders the matching overlays. The host owns the\n * trigger element so each surface keeps its own styling.\n */\nexport function useAction(node: Node<\"action\" | \"action.bulk\">): UseAction {\n const endpoint = node.props.endpoint ?? \"\";\n const componentRef = node.props.ref ?? \"\";\n const method: Method = node.props.method ?? \"post\";\n const label = actionLabel(node);\n const { variant, emphasis } = node.props;\n const confirmation = node.props.confirmation;\n const inlineForm = node.props.form;\n const lazyForm = node.props.lazyForm === true;\n const hasForm = Boolean(inlineForm) || lazyForm;\n\n const [processing, setProcessing] = useState(false);\n const dispatch = useEffectDispatcher();\n const [isConfirming, setIsConfirming] = useState(false);\n const [isFilling, setIsFilling] = useState(false);\n const lazyNode = useLazyActionForm(endpoint, componentRef, isFilling && lazyForm);\n const formNode = lazyForm ? lazyNode : inlineForm;\n\n const submit = async (): Promise<void> => {\n if (!endpoint) {\n return;\n }\n\n if (method === \"get\") {\n router.visit(endpoint, { headers: withHeaders(componentRef) });\n setIsConfirming(false);\n\n return;\n }\n\n setProcessing(true);\n\n const ok = await runAction(\n () => apiFetch(endpoint, { method, ref: componentRef, throwOnError: false }),\n dispatch,\n );\n\n setProcessing(false);\n\n if (ok) {\n setIsConfirming(false);\n }\n };\n\n const requestSubmit = (): void => {\n if (hasForm) {\n setIsFilling(true);\n\n return;\n }\n\n if (confirmation) {\n setIsConfirming(true);\n\n return;\n }\n\n void submit();\n };\n\n const confirmationTitle = confirmation?.title ?? label;\n const confirmationConfirmLabel = confirmation?.confirmLabel ?? label;\n const confirmationCancelLabel =\n confirmation?.cancelLabel ?? translate(\"lattice\", \"common.cancel\", \"Cancel\");\n\n const overlays = (\n <>\n {isConfirming && confirmation && (\n <ConfirmDialog\n title={confirmationTitle}\n description={confirmation.description ?? undefined}\n confirmLabel={confirmationConfirmLabel}\n cancelLabel={confirmationCancelLabel}\n confirmVariant={variant}\n confirmEmphasis={emphasis}\n processing={processing}\n confirmDisabled={!endpoint}\n onConfirm={() => void submit()}\n onCancel={() => setIsConfirming(false)}\n />\n )}\n\n {isFilling && hasForm && (\n <ActionForm\n cancelLabel={confirmationCancelLabel}\n componentRef={componentRef}\n description={confirmation?.description ?? undefined}\n endpoint={endpoint}\n formNode={formNode}\n method={method}\n onClose={() => setIsFilling(false)}\n onSuccess={() => {\n setIsFilling(false);\n }}\n placement={node.props.modalSide ?? \"center\"}\n submitLabel={confirmationConfirmLabel}\n title={confirmationTitle}\n width={node.props.modalWidth ?? undefined}\n />\n )}\n </>\n );\n\n return { processing, requestSubmit, overlays };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AA6BA,SAAgB,UAAU,MAAiD;CACzE,MAAM,WAAW,KAAK,MAAM,YAAY;CACxC,MAAM,eAAe,KAAK,MAAM,OAAO;CACvC,MAAM,SAAiB,KAAK,MAAM,UAAU;CAC5C,MAAM,QAAQ,YAAY,IAAI;CAC9B,MAAM,EAAE,SAAS,aAAa,KAAK;CACnC,MAAM,eAAe,KAAK,MAAM;CAChC,MAAM,aAAa,KAAK,MAAM;CAC9B,MAAM,WAAW,KAAK,MAAM,aAAa;CACzC,MAAM,UAAU,QAAQ,UAAU,KAAK;CAEvC,MAAM,CAAC,YAAY,iBAAiB,SAAS,KAAK;CAClD,MAAM,WAAW,oBAAoB;CACrC,MAAM,CAAC,cAAc,mBAAmB,SAAS,KAAK;CACtD,MAAM,CAAC,WAAW,gBAAgB,SAAS,KAAK;CAChD,MAAM,WAAW,kBAAkB,UAAU,cAAc,aAAa,QAAQ;CAChF,MAAM,WAAW,WAAW,WAAW;CAEvC,MAAM,SAAS,YAA2B;EACxC,IAAI,CAAC,UACH;EAGF,IAAI,WAAW,OAAO;GACpB,OAAO,MAAM,UAAU,EAAE,SAAS,YAAY,YAAY,EAAE,CAAC;GAC7D,gBAAgB,KAAK;GAErB;EACF;EAEA,cAAc,IAAI;EAElB,MAAM,KAAK,MAAM,gBACT,SAAS,UAAU;GAAE;GAAQ,KAAK;GAAc,cAAc;EAAM,CAAC,GAC3E,QACF;EAEA,cAAc,KAAK;EAEnB,IAAI,IACF,gBAAgB,KAAK;CAEzB;CAEA,MAAM,sBAA4B;EAChC,IAAI,SAAS;GACX,aAAa,IAAI;GAEjB;EACF;EAEA,IAAI,cAAc;GAChB,gBAAgB,IAAI;GAEpB;EACF;EAEA,OAAY;CACd;CAEA,MAAM,oBAAoB,cAAc,SAAS;CACjD,MAAM,2BAA2B,cAAc,gBAAgB;CAC/D,MAAM,0BACJ,cAAc,eAAe,UAAU,WAAW,iBAAiB,QAAQ;CAwC7E,OAAO;EAAE;EAAY;EAAe,UArClC,qBAAA,YAAA,EAAA,UAAA,CACG,gBAAgB,gBACf,oBAAC,eAAD;GACE,OAAO;GACP,aAAa,aAAa,eAAe,KAAA;GACzC,cAAc;GACd,aAAa;GACb,gBAAgB;GAChB,iBAAiB;GACL;GACZ,iBAAiB,CAAC;GAClB,iBAAiB,KAAK,OAAO;GAC7B,gBAAgB,gBAAgB,KAAK;EACtC,CAAA,GAGF,aAAa,WACZ,oBAAC,YAAD;GACE,aAAa;GACC;GACd,aAAa,cAAc,eAAe,KAAA;GAChC;GACA;GACF;GACR,eAAe,aAAa,KAAK;GACjC,iBAAiB;IACf,aAAa,KAAK;GACpB;GACA,WAAW,KAAK,MAAM,aAAa;GACnC,aAAa;GACb,OAAO;GACP,OAAO,KAAK,MAAM,cAAc,KAAA;EACjC,CAAA,CAEH,EAAA,CAGgC;CAAS;AAC/C"}
|
|
@@ -8,6 +8,6 @@ export type UseAppearanceReturn = {
|
|
|
8
8
|
};
|
|
9
9
|
export declare function isAppearance(value: unknown): value is Appearance;
|
|
10
10
|
export declare function updateAppearance(mode: Appearance): void;
|
|
11
|
-
export declare function
|
|
11
|
+
export declare function initializeAppearance(): void;
|
|
12
12
|
export declare function useAppearance(): UseAppearanceReturn;
|
|
13
13
|
export {};
|
package/dist/appearance/index.js
CHANGED
|
@@ -28,7 +28,7 @@ var isDarkMode = (appearance) => {
|
|
|
28
28
|
return appearance === "dark" || appearance === "system" && prefersDark();
|
|
29
29
|
};
|
|
30
30
|
var getSystemAppearance = () => prefersDark() ? "dark" : "light";
|
|
31
|
-
var
|
|
31
|
+
var applyAppearance = (appearance) => {
|
|
32
32
|
if (typeof document === "undefined") return;
|
|
33
33
|
const isDark = isDarkMode(appearance);
|
|
34
34
|
document.documentElement.classList.toggle("dark", isDark);
|
|
@@ -43,26 +43,26 @@ var mediaQuery = () => {
|
|
|
43
43
|
if (typeof window === "undefined") return null;
|
|
44
44
|
return window.matchMedia("(prefers-color-scheme: dark)");
|
|
45
45
|
};
|
|
46
|
-
var
|
|
47
|
-
|
|
46
|
+
var handleSystemAppearanceChange = () => {
|
|
47
|
+
applyAppearance(currentAppearance);
|
|
48
48
|
notify();
|
|
49
49
|
};
|
|
50
50
|
function updateAppearance(mode) {
|
|
51
51
|
currentAppearance = mode;
|
|
52
52
|
localStorage.setItem("appearance", mode);
|
|
53
53
|
setCookie("appearance", mode);
|
|
54
|
-
|
|
54
|
+
applyAppearance(mode);
|
|
55
55
|
notify();
|
|
56
56
|
}
|
|
57
|
-
function
|
|
57
|
+
function initializeAppearance() {
|
|
58
58
|
if (typeof window === "undefined") return;
|
|
59
59
|
if (!localStorage.getItem("appearance")) {
|
|
60
60
|
localStorage.setItem("appearance", "system");
|
|
61
61
|
setCookie("appearance", "system");
|
|
62
62
|
}
|
|
63
63
|
currentAppearance = getStoredAppearance();
|
|
64
|
-
|
|
65
|
-
mediaQuery()?.addEventListener("change",
|
|
64
|
+
applyAppearance(currentAppearance);
|
|
65
|
+
mediaQuery()?.addEventListener("change", handleSystemAppearanceChange);
|
|
66
66
|
}
|
|
67
67
|
function useAppearance() {
|
|
68
68
|
const appearance = useSyncExternalStore(subscribe, () => currentAppearance, () => "system");
|
|
@@ -74,6 +74,6 @@ function useAppearance() {
|
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
76
|
//#endregion
|
|
77
|
-
export {
|
|
77
|
+
export { initializeAppearance, isAppearance, updateAppearance, useAppearance };
|
|
78
78
|
|
|
79
79
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../resources/js/appearance/index.ts"],"sourcesContent":["import { useSyncExternalStore } from \"react\";\n\nconst appearances = [\"light\", \"dark\", \"system\"] as const;\n\nexport type Appearance = (typeof appearances)[number];\n\nexport type ResolvedAppearance = \"light\" | \"dark\";\n\nexport type UseAppearanceReturn = {\n readonly appearance: Appearance;\n readonly resolvedAppearance: ResolvedAppearance;\n readonly updateAppearance: (mode: Appearance) => void;\n};\n\nconst listeners = new Set<() => void>();\nlet currentAppearance: Appearance = \"system\";\n\nexport function isAppearance(value: unknown): value is Appearance {\n return appearances.some((appearance) => appearance === value);\n}\n\nconst prefersDark = (): boolean => {\n if (typeof window === \"undefined\") {\n return false;\n }\n\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n};\n\nconst setCookie = (name: string, value: string, days = 365): void => {\n if (typeof document === \"undefined\") {\n return;\n }\n\n const maxAge = days * 24 * 60 * 60;\n document.cookie = `${name}=${value};path=/;max-age=${maxAge};SameSite=Lax`;\n};\n\nconst getStoredAppearance = (): Appearance => {\n if (typeof window === \"undefined\") {\n return \"system\";\n }\n\n const stored = localStorage.getItem(\"appearance\");\n\n return isAppearance(stored) ? stored : \"system\";\n};\n\nconst isDarkMode = (appearance: Appearance): boolean => {\n return appearance === \"dark\" || (appearance === \"system\" && prefersDark());\n};\n\nconst getSystemAppearance = (): ResolvedAppearance => (prefersDark() ? \"dark\" : \"light\");\n\nconst
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../resources/js/appearance/index.ts"],"sourcesContent":["import { useSyncExternalStore } from \"react\";\n\nconst appearances = [\"light\", \"dark\", \"system\"] as const;\n\nexport type Appearance = (typeof appearances)[number];\n\nexport type ResolvedAppearance = \"light\" | \"dark\";\n\nexport type UseAppearanceReturn = {\n readonly appearance: Appearance;\n readonly resolvedAppearance: ResolvedAppearance;\n readonly updateAppearance: (mode: Appearance) => void;\n};\n\nconst listeners = new Set<() => void>();\nlet currentAppearance: Appearance = \"system\";\n\nexport function isAppearance(value: unknown): value is Appearance {\n return appearances.some((appearance) => appearance === value);\n}\n\nconst prefersDark = (): boolean => {\n if (typeof window === \"undefined\") {\n return false;\n }\n\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n};\n\nconst setCookie = (name: string, value: string, days = 365): void => {\n if (typeof document === \"undefined\") {\n return;\n }\n\n const maxAge = days * 24 * 60 * 60;\n document.cookie = `${name}=${value};path=/;max-age=${maxAge};SameSite=Lax`;\n};\n\nconst getStoredAppearance = (): Appearance => {\n if (typeof window === \"undefined\") {\n return \"system\";\n }\n\n const stored = localStorage.getItem(\"appearance\");\n\n return isAppearance(stored) ? stored : \"system\";\n};\n\nconst isDarkMode = (appearance: Appearance): boolean => {\n return appearance === \"dark\" || (appearance === \"system\" && prefersDark());\n};\n\nconst getSystemAppearance = (): ResolvedAppearance => (prefersDark() ? \"dark\" : \"light\");\n\nconst applyAppearance = (appearance: Appearance): void => {\n if (typeof document === \"undefined\") {\n return;\n }\n\n const isDark = isDarkMode(appearance);\n\n document.documentElement.classList.toggle(\"dark\", isDark);\n document.documentElement.style.colorScheme = isDark ? \"dark\" : \"light\";\n};\n\nconst subscribe = (callback: () => void) => {\n listeners.add(callback);\n\n return () => listeners.delete(callback);\n};\n\nconst notify = (): void => listeners.forEach((listener) => listener());\n\nconst mediaQuery = (): MediaQueryList | null => {\n if (typeof window === \"undefined\") {\n return null;\n }\n\n return window.matchMedia(\"(prefers-color-scheme: dark)\");\n};\n\nconst handleSystemAppearanceChange = (): void => {\n applyAppearance(currentAppearance);\n notify();\n};\n\nexport function updateAppearance(mode: Appearance): void {\n currentAppearance = mode;\n\n localStorage.setItem(\"appearance\", mode);\n\n setCookie(\"appearance\", mode);\n\n applyAppearance(mode);\n notify();\n}\n\nexport function initializeAppearance(): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n if (!localStorage.getItem(\"appearance\")) {\n localStorage.setItem(\"appearance\", \"system\");\n setCookie(\"appearance\", \"system\");\n }\n\n currentAppearance = getStoredAppearance();\n applyAppearance(currentAppearance);\n\n mediaQuery()?.addEventListener(\"change\", handleSystemAppearanceChange);\n}\n\nexport function useAppearance(): UseAppearanceReturn {\n const appearance: Appearance = useSyncExternalStore(\n subscribe,\n () => currentAppearance,\n () => \"system\",\n );\n const systemAppearance: ResolvedAppearance = useSyncExternalStore(\n subscribe,\n getSystemAppearance,\n () => \"light\",\n );\n\n const resolvedAppearance: ResolvedAppearance =\n appearance === \"system\" ? systemAppearance : appearance;\n\n return { appearance, resolvedAppearance, updateAppearance } as const;\n}\n"],"mappings":";;AAEA,IAAM,cAAc;CAAC;CAAS;CAAQ;AAAQ;AAY9C,IAAM,4BAAY,IAAI,IAAgB;AACtC,IAAI,oBAAgC;AAEpC,SAAgB,aAAa,OAAqC;CAChE,OAAO,YAAY,MAAM,eAAe,eAAe,KAAK;AAC9D;AAEA,IAAM,oBAA6B;CACjC,IAAI,OAAO,WAAW,aACpB,OAAO;CAGT,OAAO,OAAO,WAAW,8BAA8B,EAAE;AAC3D;AAEA,IAAM,aAAa,MAAc,OAAe,OAAO,QAAc;CACnE,IAAI,OAAO,aAAa,aACtB;CAGF,MAAM,SAAS,OAAO,KAAK,KAAK;CAChC,SAAS,SAAS,GAAG,KAAK,GAAG,MAAM,kBAAkB,OAAO;AAC9D;AAEA,IAAM,4BAAwC;CAC5C,IAAI,OAAO,WAAW,aACpB,OAAO;CAGT,MAAM,SAAS,aAAa,QAAQ,YAAY;CAEhD,OAAO,aAAa,MAAM,IAAI,SAAS;AACzC;AAEA,IAAM,cAAc,eAAoC;CACtD,OAAO,eAAe,UAAW,eAAe,YAAY,YAAY;AAC1E;AAEA,IAAM,4BAAiD,YAAY,IAAI,SAAS;AAEhF,IAAM,mBAAmB,eAAiC;CACxD,IAAI,OAAO,aAAa,aACtB;CAGF,MAAM,SAAS,WAAW,UAAU;CAEpC,SAAS,gBAAgB,UAAU,OAAO,QAAQ,MAAM;CACxD,SAAS,gBAAgB,MAAM,cAAc,SAAS,SAAS;AACjE;AAEA,IAAM,aAAa,aAAyB;CAC1C,UAAU,IAAI,QAAQ;CAEtB,aAAa,UAAU,OAAO,QAAQ;AACxC;AAEA,IAAM,eAAqB,UAAU,SAAS,aAAa,SAAS,CAAC;AAErE,IAAM,mBAA0C;CAC9C,IAAI,OAAO,WAAW,aACpB,OAAO;CAGT,OAAO,OAAO,WAAW,8BAA8B;AACzD;AAEA,IAAM,qCAA2C;CAC/C,gBAAgB,iBAAiB;CACjC,OAAO;AACT;AAEA,SAAgB,iBAAiB,MAAwB;CACvD,oBAAoB;CAEpB,aAAa,QAAQ,cAAc,IAAI;CAEvC,UAAU,cAAc,IAAI;CAE5B,gBAAgB,IAAI;CACpB,OAAO;AACT;AAEA,SAAgB,uBAA6B;CAC3C,IAAI,OAAO,WAAW,aACpB;CAGF,IAAI,CAAC,aAAa,QAAQ,YAAY,GAAG;EACvC,aAAa,QAAQ,cAAc,QAAQ;EAC3C,UAAU,cAAc,QAAQ;CAClC;CAEA,oBAAoB,oBAAoB;CACxC,gBAAgB,iBAAiB;CAEjC,WAAW,GAAG,iBAAiB,UAAU,4BAA4B;AACvE;AAEA,SAAgB,gBAAqC;CACnD,MAAM,aAAyB,qBAC7B,iBACM,yBACA,QACR;CACA,MAAM,mBAAuC,qBAC3C,WACA,2BACM,OACR;CAKA,OAAO;EAAE;EAAY,oBAFnB,eAAe,WAAW,mBAAmB;EAEN;CAAiB;AAC5D"}
|
|
@@ -24,7 +24,7 @@ function PromptInput({ onSubmit, disabled = false, placeholder }) {
|
|
|
24
24
|
className: "flex items-end gap-2 border-t border-lt-border p-3",
|
|
25
25
|
children: [/* @__PURE__ */ jsx("textarea", {
|
|
26
26
|
"aria-label": t("common.chat.input-label", "Message input"),
|
|
27
|
-
className: cn("min-h-[2.5rem] flex-1 resize-none rounded-lt-sm border border-lt-input bg-lt-bg px-3 py-2 text-sm text-lt-fg placeholder:text-lt-muted-fg focus:outline-none focus:ring-
|
|
27
|
+
className: cn("min-h-[2.5rem] flex-1 resize-none rounded-lt-sm border border-lt-input bg-lt-bg px-3 py-2 text-sm text-lt-fg placeholder:text-lt-muted-fg focus:outline-none focus:ring-[length:var(--lt-ring-width)] focus:ring-lt-ring/50 disabled:bg-lt-disabled disabled:text-lt-disabled-fg"),
|
|
28
28
|
"data-test": testIdentity("chat-input"),
|
|
29
29
|
disabled,
|
|
30
30
|
onChange: (e) => setValue(e.target.value),
|
|
@@ -38,7 +38,6 @@ function PromptInput({ onSubmit, disabled = false, placeholder }) {
|
|
|
38
38
|
onClick: submit,
|
|
39
39
|
size: "sm",
|
|
40
40
|
type: "button",
|
|
41
|
-
variant: "default",
|
|
42
41
|
children: t("common.chat.send", "Send")
|
|
43
42
|
})]
|
|
44
43
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt-input.js","names":[],"sources":["../../../resources/js/chat/components/prompt-input.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { KeyboardEvent, ReactNode } from \"react\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { testIdentity } from \"@lattice-php/lattice/core/test-id\";\nimport { Button } from \"@lattice-php/lattice/ui/button\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\n\ntype PromptInputProps = {\n onSubmit: (text: string) => void;\n disabled?: boolean;\n placeholder?: string;\n};\n\nexport function PromptInput({\n onSubmit,\n disabled = false,\n placeholder,\n}: PromptInputProps): ReactNode {\n const { t } = useT(\"lattice\");\n const [value, setValue] = useState(\"\");\n\n function submit(): void {\n const trimmed = value.trim();\n if (trimmed === \"\" || disabled) {\n return;\n }\n\n onSubmit(trimmed);\n setValue(\"\");\n }\n\n function handleKeyDown(event: KeyboardEvent<HTMLTextAreaElement>): void {\n if (event.key === \"Enter\" && !event.shiftKey) {\n event.preventDefault();\n submit();\n }\n }\n\n return (\n <div className=\"flex items-end gap-2 border-t border-lt-border p-3\">\n <textarea\n aria-label={t(\"common.chat.input-label\", \"Message input\")}\n className={cn(\n \"min-h-[2.5rem] flex-1 resize-none rounded-lt-sm border border-lt-input bg-lt-bg px-3 py-2 text-sm text-lt-fg placeholder:text-lt-muted-fg focus:outline-none focus:ring-
|
|
1
|
+
{"version":3,"file":"prompt-input.js","names":[],"sources":["../../../resources/js/chat/components/prompt-input.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { KeyboardEvent, ReactNode } from \"react\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { testIdentity } from \"@lattice-php/lattice/core/test-id\";\nimport { Button } from \"@lattice-php/lattice/ui/button\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\n\ntype PromptInputProps = {\n onSubmit: (text: string) => void;\n disabled?: boolean;\n placeholder?: string;\n};\n\nexport function PromptInput({\n onSubmit,\n disabled = false,\n placeholder,\n}: PromptInputProps): ReactNode {\n const { t } = useT(\"lattice\");\n const [value, setValue] = useState(\"\");\n\n function submit(): void {\n const trimmed = value.trim();\n if (trimmed === \"\" || disabled) {\n return;\n }\n\n onSubmit(trimmed);\n setValue(\"\");\n }\n\n function handleKeyDown(event: KeyboardEvent<HTMLTextAreaElement>): void {\n if (event.key === \"Enter\" && !event.shiftKey) {\n event.preventDefault();\n submit();\n }\n }\n\n return (\n <div className=\"flex items-end gap-2 border-t border-lt-border p-3\">\n <textarea\n aria-label={t(\"common.chat.input-label\", \"Message input\")}\n className={cn(\n \"min-h-[2.5rem] flex-1 resize-none rounded-lt-sm border border-lt-input bg-lt-bg px-3 py-2 text-sm text-lt-fg placeholder:text-lt-muted-fg focus:outline-none focus:ring-[length:var(--lt-ring-width)] focus:ring-lt-ring/50 disabled:bg-lt-disabled disabled:text-lt-disabled-fg\",\n )}\n data-test={testIdentity(\"chat-input\")}\n disabled={disabled}\n onChange={(e) => setValue(e.target.value)}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n rows={1}\n value={value}\n />\n <Button\n data-test={testIdentity(\"chat-send\")}\n disabled={disabled}\n onClick={submit}\n size=\"sm\"\n type=\"button\"\n >\n {t(\"common.chat.send\", \"Send\")}\n </Button>\n </div>\n );\n}\n"],"mappings":";;;;;;;AAaA,SAAgB,YAAY,EAC1B,UACA,WAAW,OACX,eAC8B;CAC9B,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,MAAM,CAAC,OAAO,YAAY,SAAS,EAAE;CAErC,SAAS,SAAe;EACtB,MAAM,UAAU,MAAM,KAAK;EAC3B,IAAI,YAAY,MAAM,UACpB;EAGF,SAAS,OAAO;EAChB,SAAS,EAAE;CACb;CAEA,SAAS,cAAc,OAAiD;EACtE,IAAI,MAAM,QAAQ,WAAW,CAAC,MAAM,UAAU;GAC5C,MAAM,eAAe;GACrB,OAAO;EACT;CACF;CAEA,OACE,qBAAC,OAAD;EAAK,WAAU;YAAf,CACE,oBAAC,YAAD;GACE,cAAY,EAAE,2BAA2B,eAAe;GACxD,WAAW,GACT,kRACF;GACA,aAAW,aAAa,YAAY;GAC1B;GACV,WAAW,MAAM,SAAS,EAAE,OAAO,KAAK;GACxC,WAAW;GACE;GACb,MAAM;GACC;EACR,CAAA,GACD,oBAAC,QAAD;GACE,aAAW,aAAa,WAAW;GACzB;GACV,SAAS;GACT,MAAK;GACL,MAAK;aAEJ,EAAE,oBAAoB,MAAM;EACvB,CAAA,CACL;;AAET"}
|
package/dist/create-app.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { initializeAppearance } from "./appearance/index.js";
|
|
2
2
|
import { extendRegistry } from "./core/registry.js";
|
|
3
3
|
import { setDefaultRegistry } from "./core/registry-context.js";
|
|
4
4
|
import "./i18n/instance.js";
|
|
@@ -77,7 +77,7 @@ function createLatticeApp({ registry: registry$1, plugins, sprite, pages = {}, d
|
|
|
77
77
|
}) : shell;
|
|
78
78
|
}
|
|
79
79
|
});
|
|
80
|
-
|
|
80
|
+
initializeAppearance();
|
|
81
81
|
return app;
|
|
82
82
|
}
|
|
83
83
|
//#endregion
|
package/dist/create-app.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-app.js","names":[],"sources":["../resources/js/create-app.tsx"],"sourcesContent":["import type { Page as InertiaPage, VisitOptions } from \"@inertiajs/core\";\nimport { createInertiaApp } from \"@inertiajs/react\";\nimport { useEffect, useState, type ReactElement, type ReactNode } from \"react\";\nimport {
|
|
1
|
+
{"version":3,"file":"create-app.js","names":[],"sources":["../resources/js/create-app.tsx"],"sourcesContent":["import type { Page as InertiaPage, VisitOptions } from \"@inertiajs/core\";\nimport { createInertiaApp } from \"@inertiajs/react\";\nimport { useEffect, useState, type ReactElement, type ReactNode } from \"react\";\nimport { initializeAppearance } from \"./appearance\";\nimport { extendRegistry, type Plugin, type PluginI18n, type Registry } from \"./core/registry\";\nimport { setDefaultRegistry } from \"./core/registry-context\";\nimport type { SpriteValue } from \"./icons/sprite\";\nimport { DEFAULT_NAMESPACE } from \"./i18n/instance\";\nimport { LocaleReload } from \"./i18n/locale-reload\";\nimport { i18nConfigFromPageProps } from \"./i18n/shared-props\";\nimport {\n createLayoutResolver,\n createPageResolver,\n withVisitHeaders,\n type CreateLayoutResolverOptions,\n type PageModules,\n} from \"./inertia\";\nimport { ProviderBase } from \"./provider-base\";\nimport { registry as defaultRegistry } from \"./registry\";\n\ntype InertiaAppOptions = NonNullable<Parameters<typeof createInertiaApp>[0]>;\n\nexport type CreateLatticeAppI18nOptions = {\n /**\n * i18next namespaces to load, e.g. `[\"lattice\", \"app\"]`. Defaults to the\n * package's own. Namespaces declared by plugins are merged in on top.\n */\n namespaces?: string[];\n};\n\nexport type CreateLatticeAppOptions = Omit<\n InertiaAppOptions,\n \"resolve\" | \"layout\" | \"setup\" | \"withApp\" | \"pages\"\n> & {\n registry?: Registry;\n /**\n * Component-package plugins to merge onto the registry — pass the\n * Composer-discovered `virtual:lattice/plugins` here to register vendor\n * components with no manual `extendRegistry` call. A plugin's declared\n * i18n namespace joins the i18n bootstrap automatically.\n */\n plugins?: Plugin[];\n sprite?: SpriteValue;\n /** Normal Inertia page modules, e.g. `import.meta.glob('./pages/**\\/*.tsx')`. */\n pages?: PageModules;\n defaultLayout?: CreateLayoutResolverOptions[\"defaultLayout\"];\n /**\n * Lattice's i18n bootstrap, on by default: when the backend shares the\n * `lattice.i18n` prop, the first render waits for the translation setup (no\n * flash of untranslated fallbacks), `LocaleReload` re-fetches the page after\n * a locale switch, and every visit carries the locale/timezone headers. The\n * i18next chunk only loads when the backend actually shares the prop. Pass\n * `false` to opt out entirely.\n */\n i18n?: CreateLatticeAppI18nOptions | false;\n /**\n * App bootstrap that needs the initial page — e.g. `configureEcho` from a\n * shared connection prop. Runs on the client before the first render; a\n * returned promise delays that render until it resolves.\n */\n boot?: (context: { page: InertiaPage }) => void | Promise<void>;\n /**\n * Compose providers or siblings around the app. Applied inside the Lattice\n * Provider, so the registry, sprite, and toaster contexts are available.\n */\n wrap?: (app: ReactElement) => ReactElement;\n};\n\nfunction withPluginNamespaces(\n options: CreateLatticeAppI18nOptions,\n entries: PluginI18n[],\n): CreateLatticeAppI18nOptions {\n if (entries.length === 0) {\n return options;\n }\n\n const namespaces = new Set([\n ...(options.namespaces ?? [DEFAULT_NAMESPACE]),\n ...entries.map((entry) => entry.namespace),\n ]);\n\n return { ...options, namespaces: [...namespaces] };\n}\n\n// Fail-open: a failed bootstrap renders the app anyway (with fallback strings)\n// rather than a blank page.\nfunction AwaitReady({ ready, children }: { ready: Promise<unknown>; children: ReactNode }) {\n const [isReady, setReady] = useState(false);\n\n useEffect(() => {\n let active = true;\n const reveal = (): void => {\n if (active) {\n setReady(true);\n }\n };\n\n void ready.then(reveal, reveal);\n\n return () => {\n active = false;\n };\n }, [ready]);\n\n return isReady ? children : null;\n}\n\n/**\n * Bootstrap an Inertia app with the Lattice shell: the page/layout resolvers\n * (server-driven Lattice pages and normal Inertia pages alike), the Provider —\n * registry, sprite, flash toasts via Lattice's own Toaster — theme\n * initialization, and the i18n bootstrap. Forwards any other createInertiaApp\n * option.\n */\nexport function createLatticeApp({\n registry,\n plugins,\n sprite,\n pages = {},\n defaultLayout,\n strictMode = true,\n i18n,\n boot,\n wrap,\n ...inertiaOptions\n}: CreateLatticeAppOptions = {}) {\n const baseRegistry = registry ?? defaultRegistry;\n const activeRegistry =\n plugins && plugins.length > 0 ? extendRegistry(baseRegistry, ...plugins) : baseRegistry;\n\n setDefaultRegistry(activeRegistry);\n\n const i18nEnabled = i18n !== false;\n const pluginI18n = plugins?.flatMap((plugin) => (plugin.i18n ? [plugin.i18n] : [])) ?? [];\n const i18nOptions = i18nEnabled ? withPluginNamespaces(i18n ?? {}, pluginI18n) : {};\n const userVisitOptions = inertiaOptions.defaults?.visitOptions;\n const defaults = i18nEnabled\n ? {\n ...inertiaOptions.defaults,\n visitOptions: (href: string, options: VisitOptions): VisitOptions =>\n withVisitHeaders(href, userVisitOptions?.(href, options) ?? options),\n }\n : inertiaOptions.defaults;\n\n const app = createInertiaApp({\n ...inertiaOptions,\n defaults,\n strictMode,\n resolve: createPageResolver(pages),\n layout: createLayoutResolver({ defaultLayout }),\n withApp: (node: ReactElement, { ssr, page }: { ssr: boolean; page: InertiaPage }) => {\n const pending: Promise<unknown>[] = [];\n\n if (!ssr) {\n // Loaded on demand so apps without the shared i18n prop never ship the\n // i18next backend; the disabled-config call still stores the timezone.\n if (i18nEnabled && i18nConfigFromPageProps(page.props) !== undefined) {\n pending.push(\n import(\"./i18n/page-props\").then((module) =>\n module.configureI18nFromPageProps(page.props, i18nOptions),\n ),\n );\n }\n\n const booted = boot?.({ page });\n\n if (booted) {\n pending.push(booted);\n }\n }\n\n const inner = (\n <>\n {node}\n {i18nEnabled ? <LocaleReload /> : null}\n </>\n );\n\n // The whole shell waits, not just the page: anything rendering a\n // translated string (the Toaster included) would otherwise initialize\n // i18next before the backend can register — init runs exactly once,\n // first caller wins.\n const shell = (\n <ProviderBase registry={activeRegistry} sprite={sprite}>\n {wrap ? wrap(inner) : inner}\n </ProviderBase>\n );\n\n return pending.length > 0 ? (\n <AwaitReady ready={Promise.all(pending)}>{shell}</AwaitReady>\n ) : (\n shell\n );\n },\n });\n\n initializeAppearance();\n\n return app;\n}\n"],"mappings":";;;;;;;;;;;;;AAoEA,SAAS,qBACP,SACA,SAC6B;CAC7B,IAAI,QAAQ,WAAW,GACrB,OAAO;CAGT,MAAM,aAAa,IAAI,IAAI,CACzB,GAAI,QAAQ,cAAc,CAAA,SAAkB,GAC5C,GAAG,QAAQ,KAAK,UAAU,MAAM,SAAS,CAC3C,CAAC;CAED,OAAO;EAAE,GAAG;EAAS,YAAY,CAAC,GAAG,UAAU;CAAE;AACnD;AAIA,SAAS,WAAW,EAAE,OAAO,YAA8D;CACzF,MAAM,CAAC,SAAS,YAAY,SAAS,KAAK;CAE1C,gBAAgB;EACd,IAAI,SAAS;EACb,MAAM,eAAqB;GACzB,IAAI,QACF,SAAS,IAAI;EAEjB;EAEA,MAAW,KAAK,QAAQ,MAAM;EAE9B,aAAa;GACX,SAAS;EACX;CACF,GAAG,CAAC,KAAK,CAAC;CAEV,OAAO,UAAU,WAAW;AAC9B;;;;;;;;AASA,SAAgB,iBAAiB,EAC/B,UAAA,YACA,SACA,QACA,QAAQ,CAAC,GACT,eACA,aAAa,MACb,MACA,MACA,MACA,GAAG,mBACwB,CAAC,GAAG;CAC/B,MAAM,eAAe,cAAY;CACjC,MAAM,iBACJ,WAAW,QAAQ,SAAS,IAAI,eAAe,cAAc,GAAG,OAAO,IAAI;CAE7E,mBAAmB,cAAc;CAEjC,MAAM,cAAc,SAAS;CAC7B,MAAM,aAAa,SAAS,SAAS,WAAY,OAAO,OAAO,CAAC,OAAO,IAAI,IAAI,CAAC,CAAE,KAAK,CAAC;CACxF,MAAM,cAAc,cAAc,qBAAqB,QAAQ,CAAC,GAAG,UAAU,IAAI,CAAC;CAClF,MAAM,mBAAmB,eAAe,UAAU;CAClD,MAAM,WAAW,cACb;EACE,GAAG,eAAe;EAClB,eAAe,MAAc,YAC3B,iBAAiB,MAAM,mBAAmB,MAAM,OAAO,KAAK,OAAO;CACvE,IACA,eAAe;CAEnB,MAAM,MAAM,iBAAiB;EAC3B,GAAG;EACH;EACA;EACA,SAAS,mBAAmB,KAAK;EACjC,QAAQ,qBAAqB,EAAE,cAAc,CAAC;EAC9C,UAAU,MAAoB,EAAE,KAAK,WAAgD;GACnF,MAAM,UAA8B,CAAC;GAErC,IAAI,CAAC,KAAK;IAGR,IAAI,eAAe,wBAAwB,KAAK,KAAK,MAAM,KAAA,GACzD,QAAQ,KACN,OAAO,wBAAqB,MAAM,WAChC,OAAO,2BAA2B,KAAK,OAAO,WAAW,CAC3D,CACF;IAGF,MAAM,SAAS,OAAO,EAAE,KAAK,CAAC;IAE9B,IAAI,QACF,QAAQ,KAAK,MAAM;GAEvB;GAEA,MAAM,QACJ,qBAAA,YAAA,EAAA,UAAA,CACG,MACA,cAAc,oBAAC,cAAD,CAAe,CAAA,IAAI,IAClC,EAAA,CAAA;GAOJ,MAAM,QACJ,oBAAC,cAAD;IAAc,UAAU;IAAwB;cAC7C,OAAO,KAAK,KAAK,IAAI;GACV,CAAA;GAGhB,OAAO,QAAQ,SAAS,IACtB,oBAAC,YAAD;IAAY,OAAO,QAAQ,IAAI,OAAO;cAAI;GAAkB,CAAA,IAE5D;EAEJ;CACF,CAAC;CAED,qBAAqB;CAErB,OAAO;AACT"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export declare function FormSubmitButton({ label, summaryLabel, variant, }: {
|
|
1
|
+
import { Emphasis, Variant } from '../../../types/generated.js';
|
|
2
|
+
export declare function FormSubmitButton({ label, summaryLabel, variant, emphasis, }: {
|
|
3
3
|
label: string;
|
|
4
4
|
summaryLabel: string;
|
|
5
|
-
variant?:
|
|
5
|
+
variant?: Variant | null;
|
|
6
|
+
emphasis?: Emphasis | null;
|
|
6
7
|
}): import("react").JSX.Element;
|
|
@@ -3,7 +3,7 @@ import { Spinner } from "../../../ui/spinner.js";
|
|
|
3
3
|
import { useFormContext } from "../../hooks/context.js";
|
|
4
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
5
|
//#region resources/js/form/components/base/submit-button.tsx
|
|
6
|
-
function FormSubmitButton({ label, summaryLabel, variant
|
|
6
|
+
function FormSubmitButton({ label, summaryLabel, variant, emphasis }) {
|
|
7
7
|
const { componentId, errors, fieldLabels, processing } = useFormContext();
|
|
8
8
|
const invalidFields = Object.entries(errors).filter(([, message]) => Boolean(message)).map(([name, message]) => ({
|
|
9
9
|
label: fieldLabels[name] ?? name,
|
|
@@ -16,6 +16,7 @@ function FormSubmitButton({ label, summaryLabel, variant = "default" }) {
|
|
|
16
16
|
"data-lattice-form": componentId,
|
|
17
17
|
"data-test": "form-submit",
|
|
18
18
|
disabled: processing || hasErrors,
|
|
19
|
+
emphasis,
|
|
19
20
|
type: "submit",
|
|
20
21
|
variant,
|
|
21
22
|
children: [processing && /* @__PURE__ */ jsx(Spinner, {}), label]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"submit-button.js","names":[],"sources":["../../../../resources/js/form/components/base/submit-button.tsx"],"sourcesContent":["import { Button } from \"@lattice-php/lattice/ui/button\";\nimport { Spinner } from \"@lattice-php/lattice/ui/spinner\";\nimport type {
|
|
1
|
+
{"version":3,"file":"submit-button.js","names":[],"sources":["../../../../resources/js/form/components/base/submit-button.tsx"],"sourcesContent":["import { Button } from \"@lattice-php/lattice/ui/button\";\nimport { Spinner } from \"@lattice-php/lattice/ui/spinner\";\nimport type { Emphasis, Variant } from \"@lattice-php/lattice/types/generated\";\nimport { useFormContext } from \"@lattice-php/lattice/form/hooks/context\";\n\nexport function FormSubmitButton({\n label,\n summaryLabel,\n variant,\n emphasis,\n}: {\n label: string;\n summaryLabel: string;\n variant?: Variant | null;\n emphasis?: Emphasis | null;\n}) {\n const { componentId, errors, fieldLabels, processing } = useFormContext();\n\n const invalidFields = Object.entries(errors)\n .filter(([, message]) => Boolean(message))\n .map(([name, message]) => ({ label: fieldLabels[name] ?? name, message: message as string }));\n const hasErrors = invalidFields.length > 0;\n\n return (\n <span className=\"group relative inline-flex flex-col\">\n <Button\n data-lattice-form={componentId}\n data-test=\"form-submit\"\n disabled={processing || hasErrors}\n emphasis={emphasis}\n type=\"submit\"\n variant={variant}\n >\n {processing && <Spinner />}\n {label}\n </Button>\n\n {hasErrors && (\n <div\n className=\"pointer-events-none absolute bottom-full left-1/2 z-lt-popover mb-2 w-max max-w-xs -translate-x-1/2 rounded-lt border border-lt-border bg-lt-surface p-3 text-left text-sm opacity-0 shadow-lt-md transition-opacity group-hover:opacity-100\"\n role=\"tooltip\"\n >\n <p className=\"mb-1 font-medium text-lt-fg\">{summaryLabel}</p>\n <ul className=\"space-y-1\">\n {invalidFields.map((field) => (\n <li className=\"text-lt-muted-fg\" key={field.label}>\n <span className=\"font-medium text-lt-fg\">{field.label}</span> — {field.message}\n </li>\n ))}\n </ul>\n </div>\n )}\n </span>\n );\n}\n"],"mappings":";;;;;AAKA,SAAgB,iBAAiB,EAC/B,OACA,cACA,SACA,YAMC;CACD,MAAM,EAAE,aAAa,QAAQ,aAAa,eAAe,eAAe;CAExE,MAAM,gBAAgB,OAAO,QAAQ,MAAM,EACxC,QAAQ,GAAG,aAAa,QAAQ,OAAO,CAAC,EACxC,KAAK,CAAC,MAAM,cAAc;EAAE,OAAO,YAAY,SAAS;EAAe;CAAkB,EAAE;CAC9F,MAAM,YAAY,cAAc,SAAS;CAEzC,OACE,qBAAC,QAAD;EAAM,WAAU;YAAhB,CACE,qBAAC,QAAD;GACE,qBAAmB;GACnB,aAAU;GACV,UAAU,cAAc;GACd;GACV,MAAK;GACI;aANX,CAQG,cAAc,oBAAC,SAAD,CAAU,CAAA,GACxB,KACK;MAEP,aACC,qBAAC,OAAD;GACE,WAAU;GACV,MAAK;aAFP,CAIE,oBAAC,KAAD;IAAG,WAAU;cAA+B;GAAgB,CAAA,GAC5D,oBAAC,MAAD;IAAI,WAAU;cACX,cAAc,KAAK,UAClB,qBAAC,MAAD;KAAI,WAAU;eAAd;MACE,oBAAC,QAAD;OAAM,WAAU;iBAA0B,MAAM;MAAY,CAAA;MAAC;MAAI,MAAM;KACrE;OAFkC,MAAM,KAExC,CACL;GACC,CAAA,CACD;IAEH;;AAEV"}
|
|
@@ -88,7 +88,7 @@ function DatePickerField({ mode, label, name, testId, value, min, max, step, dis
|
|
|
88
88
|
disabled: disabled || readOnly,
|
|
89
89
|
size: "icon",
|
|
90
90
|
type: "button",
|
|
91
|
-
|
|
91
|
+
color: "secondary",
|
|
92
92
|
children: /* @__PURE__ */ jsx(Icon, {
|
|
93
93
|
name: "calendar",
|
|
94
94
|
className: "size-lt-icon-md",
|
|
@@ -108,9 +108,9 @@ function DatePickerField({ mode, label, name, testId, value, min, max, step, dis
|
|
|
108
108
|
children: [
|
|
109
109
|
/* @__PURE__ */ jsx(Button, {
|
|
110
110
|
...api.getPrevTriggerProps(),
|
|
111
|
+
emphasis: "ghost",
|
|
111
112
|
size: "icon",
|
|
112
113
|
type: "button",
|
|
113
|
-
variant: "ghost",
|
|
114
114
|
children: /* @__PURE__ */ jsx(Icon, {
|
|
115
115
|
name: "chevron-left",
|
|
116
116
|
className: "size-lt-icon-md",
|
|
@@ -123,9 +123,9 @@ function DatePickerField({ mode, label, name, testId, value, min, max, step, dis
|
|
|
123
123
|
}),
|
|
124
124
|
/* @__PURE__ */ jsx(Button, {
|
|
125
125
|
...api.getNextTriggerProps(),
|
|
126
|
+
emphasis: "ghost",
|
|
126
127
|
size: "icon",
|
|
127
128
|
type: "button",
|
|
128
|
-
variant: "ghost",
|
|
129
129
|
children: /* @__PURE__ */ jsx(Icon, {
|
|
130
130
|
name: "chevron-right",
|
|
131
131
|
className: "size-lt-icon-md",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"date-picker-field.js","names":[],"sources":["../../../../resources/js/form/components/fields/date-picker-field.tsx"],"sourcesContent":["import type { DateValue } from \"@internationalized/date\";\nimport * as datePicker from \"@zag-js/date-picker\";\nimport { normalizeProps, useMachine } from \"@zag-js/react\";\nimport { useId, useMemo } from \"react\";\nimport { Button } from \"@lattice-php/lattice/ui/button\";\nimport { Icon } from \"@lattice-php/lattice/icons\";\nimport { useLocale } from \"@lattice-php/lattice/i18n\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { Input } from \"@lattice-php/lattice/ui/input\";\nimport {\n formatDateDisplayValue,\n formatDateTimeDisplayValue,\n formatDateTimeValue,\n formatDateValue,\n formatTimeInputValue,\n parseDateDisplayValue,\n parseDateTimeDisplayValue,\n parseDateTimeValue,\n parseDateValue,\n} from \"./date-picker-value\";\nimport { TimePicker } from \"./time-picker\";\nimport { parseTimeString } from \"./time-picker-columns\";\n\nexport type DatePickerFieldProps = {\n mode: \"date\" | \"date-time\";\n label: string;\n name: string;\n testId: string;\n value: unknown;\n min?: string | null;\n max?: string | null;\n step?: number | null;\n disabled: boolean;\n readOnly: boolean;\n autoFocus?: boolean;\n tabIndex?: number | null;\n timezone?: string;\n onChange: (value: string) => void;\n onBlur?: () => void;\n};\n\nexport function DatePickerField({\n mode,\n label,\n name,\n testId,\n value,\n min,\n max,\n step,\n disabled,\n readOnly,\n autoFocus = false,\n tabIndex,\n timezone = \"UTC\",\n onChange,\n onBlur,\n}: DatePickerFieldProps) {\n const id = useId();\n const { locale } = useLocale();\n const selected = useMemo(\n () =>\n [mode === \"date\" ? parseDateValue(value) : parseDateTimeValue(value, timezone)].filter(\n Boolean,\n ) as DateValue[],\n [mode, timezone, value],\n );\n const service = useMachine(datePicker.machine, {\n id,\n name,\n value: selected.length > 0 ? selected : undefined,\n min: min ? parseDateValue(min) : undefined,\n max: max ? parseDateValue(max) : undefined,\n disabled,\n readOnly,\n locale,\n selectionMode: \"single\",\n timeZone: timezone,\n closeOnSelect: mode === \"date\",\n format(date) {\n return mode === \"date\"\n ? formatDateDisplayValue(date, locale)\n : formatDateTimeDisplayValue(date, locale, timezone);\n },\n parse(text) {\n return mode === \"date\"\n ? parseDateDisplayValue(text, locale)\n : parseDateTimeDisplayValue(text, locale, timezone);\n },\n onValueChange(details) {\n const next = details.value[0];\n\n onChange(mode === \"date\" ? formatDateValue(next) : formatDateTimeValue(next, timezone));\n },\n onOpenChange(details) {\n if (!details.open) {\n onBlur?.();\n }\n },\n });\n const api = datePicker.connect(service, normalizeProps);\n const { name: _inputName, onInput, ...inputProps } = api.getInputProps();\n const submittedValue =\n mode === \"date\" ? formatDateValue(selected[0]) : formatDateTimeValue(selected[0], timezone);\n\n return (\n <div {...api.getRootProps()} className={cn(\"relative\", api.open && \"z-lt-popover\")}>\n <input type=\"hidden\" name={name} value={submittedValue} data-test={`${testId}-value`} />\n <div {...api.getControlProps()} className=\"flex gap-2\">\n <Input\n {...inputProps}\n aria-label={label}\n autoFocus={autoFocus}\n data-test={testId}\n disabled={disabled}\n id={name}\n onInput={(event) => {\n onInput?.(event);\n\n if (mode !== \"date\") {\n return;\n }\n\n const normalized = normalizeDateInputValue(event.currentTarget.value);\n\n if (!normalized) {\n return;\n }\n\n const next = parseDateValue(normalized);\n\n if (!next) {\n return;\n }\n\n event.currentTarget.value = normalized;\n api.setValue([next]);\n event.currentTarget.value = formatDateDisplayValue(next, locale);\n onChange(formatDateValue(next));\n }}\n readOnly={readOnly}\n tabIndex={tabIndex ?? undefined}\n />\n <Button\n {...api.getTriggerProps()}\n aria-label={`Open ${label || name} calendar`}\n disabled={disabled || readOnly}\n size=\"icon\"\n type=\"button\"\n variant=\"secondary\"\n >\n <Icon name=\"calendar\" className=\"size-lt-icon-md\" aria-hidden=\"true\" />\n </Button>\n </div>\n {api.open ? (\n <div\n {...api.getPositionerProps()}\n className=\"absolute z-lt-popover mt-2 rounded-lt-sm border border-lt-border bg-lt-popover p-3 text-lt-popover-fg shadow-lt-md\"\n >\n <div {...api.getContentProps()} className=\"grid gap-3\">\n <div className=\"flex items-center justify-between gap-2\">\n <Button {...api.getPrevTriggerProps()} size=\"icon\" type=\"button\" variant=\"ghost\">\n <Icon name=\"chevron-left\" className=\"size-lt-icon-md\" aria-hidden=\"true\" />\n </Button>\n <div {...api.getRangeTextProps()} className=\"text-sm font-medium text-lt-fg\" />\n <Button {...api.getNextTriggerProps()} size=\"icon\" type=\"button\" variant=\"ghost\">\n <Icon name=\"chevron-right\" className=\"size-lt-icon-md\" aria-hidden=\"true\" />\n </Button>\n </div>\n <table {...api.getTableProps()} className=\"w-full border-collapse text-sm\">\n <thead {...api.getTableHeadProps()}>\n <tr {...api.getTableRowProps()}>\n {api.weekDays.map((day) => (\n <th\n {...api.getTableHeaderProps()}\n aria-label={day.long}\n key={day.value.toString()}\n className=\"size-8 text-center text-xs font-medium text-lt-muted-fg\"\n >\n {day.narrow}\n </th>\n ))}\n </tr>\n </thead>\n <tbody {...api.getTableBodyProps()}>\n {api.weeks.map((week, weekIndex) => (\n <tr {...api.getTableRowProps()} key={weekIndex}>\n {week.map((day) => {\n const state = api.getDayTableCellState({ value: day });\n\n return (\n <td\n {...api.getDayTableCellProps({ value: day })}\n key={day.toString()}\n className=\"p-0 text-center\"\n >\n <button\n {...api.getDayTableCellTriggerProps({ value: day })}\n type=\"button\"\n className={cn(\n \"size-8 rounded-lt-sm text-sm text-lt-fg hover:bg-lt-muted\",\n state.selected && \"bg-lt-primary text-lt-primary-fg\",\n state.outsideRange && \"text-lt-muted-fg\",\n state.disabled && \"cursor-not-allowed opacity-40\",\n )}\n >\n {day.day}\n </button>\n </td>\n );\n })}\n </tr>\n ))}\n </tbody>\n </table>\n {mode === \"date-time\" ? (\n <TimePicker\n value={parseTimeString(formatTimeInputValue(selected[0], timezone))}\n onChange={(next) =>\n api.setTime({ hour: next.hour, minute: next.minute, second: next.second })\n }\n step={step}\n disabled={disabled}\n readOnly={readOnly}\n testId={`${testId}-time`}\n />\n ) : null}\n </div>\n </div>\n ) : null}\n </div>\n );\n}\n\nfunction normalizeDateInputValue(value: string): string | undefined {\n const compact = value.replace(/\\D/g, \"\");\n\n if (compact.length !== 8) {\n return undefined;\n }\n\n return `${compact.slice(0, 4)}-${compact.slice(4, 6)}-${compact.slice(6, 8)}`;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"date-picker-field.js","names":[],"sources":["../../../../resources/js/form/components/fields/date-picker-field.tsx"],"sourcesContent":["import type { DateValue } from \"@internationalized/date\";\nimport * as datePicker from \"@zag-js/date-picker\";\nimport { normalizeProps, useMachine } from \"@zag-js/react\";\nimport { useId, useMemo } from \"react\";\nimport { Button } from \"@lattice-php/lattice/ui/button\";\nimport { Icon } from \"@lattice-php/lattice/icons\";\nimport { useLocale } from \"@lattice-php/lattice/i18n\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { Input } from \"@lattice-php/lattice/ui/input\";\nimport {\n formatDateDisplayValue,\n formatDateTimeDisplayValue,\n formatDateTimeValue,\n formatDateValue,\n formatTimeInputValue,\n parseDateDisplayValue,\n parseDateTimeDisplayValue,\n parseDateTimeValue,\n parseDateValue,\n} from \"./date-picker-value\";\nimport { TimePicker } from \"./time-picker\";\nimport { parseTimeString } from \"./time-picker-columns\";\n\nexport type DatePickerFieldProps = {\n mode: \"date\" | \"date-time\";\n label: string;\n name: string;\n testId: string;\n value: unknown;\n min?: string | null;\n max?: string | null;\n step?: number | null;\n disabled: boolean;\n readOnly: boolean;\n autoFocus?: boolean;\n tabIndex?: number | null;\n timezone?: string;\n onChange: (value: string) => void;\n onBlur?: () => void;\n};\n\nexport function DatePickerField({\n mode,\n label,\n name,\n testId,\n value,\n min,\n max,\n step,\n disabled,\n readOnly,\n autoFocus = false,\n tabIndex,\n timezone = \"UTC\",\n onChange,\n onBlur,\n}: DatePickerFieldProps) {\n const id = useId();\n const { locale } = useLocale();\n const selected = useMemo(\n () =>\n [mode === \"date\" ? parseDateValue(value) : parseDateTimeValue(value, timezone)].filter(\n Boolean,\n ) as DateValue[],\n [mode, timezone, value],\n );\n const service = useMachine(datePicker.machine, {\n id,\n name,\n value: selected.length > 0 ? selected : undefined,\n min: min ? parseDateValue(min) : undefined,\n max: max ? parseDateValue(max) : undefined,\n disabled,\n readOnly,\n locale,\n selectionMode: \"single\",\n timeZone: timezone,\n closeOnSelect: mode === \"date\",\n format(date) {\n return mode === \"date\"\n ? formatDateDisplayValue(date, locale)\n : formatDateTimeDisplayValue(date, locale, timezone);\n },\n parse(text) {\n return mode === \"date\"\n ? parseDateDisplayValue(text, locale)\n : parseDateTimeDisplayValue(text, locale, timezone);\n },\n onValueChange(details) {\n const next = details.value[0];\n\n onChange(mode === \"date\" ? formatDateValue(next) : formatDateTimeValue(next, timezone));\n },\n onOpenChange(details) {\n if (!details.open) {\n onBlur?.();\n }\n },\n });\n const api = datePicker.connect(service, normalizeProps);\n const { name: _inputName, onInput, ...inputProps } = api.getInputProps();\n const submittedValue =\n mode === \"date\" ? formatDateValue(selected[0]) : formatDateTimeValue(selected[0], timezone);\n\n return (\n <div {...api.getRootProps()} className={cn(\"relative\", api.open && \"z-lt-popover\")}>\n <input type=\"hidden\" name={name} value={submittedValue} data-test={`${testId}-value`} />\n <div {...api.getControlProps()} className=\"flex gap-2\">\n <Input\n {...inputProps}\n aria-label={label}\n autoFocus={autoFocus}\n data-test={testId}\n disabled={disabled}\n id={name}\n onInput={(event) => {\n onInput?.(event);\n\n if (mode !== \"date\") {\n return;\n }\n\n const normalized = normalizeDateInputValue(event.currentTarget.value);\n\n if (!normalized) {\n return;\n }\n\n const next = parseDateValue(normalized);\n\n if (!next) {\n return;\n }\n\n event.currentTarget.value = normalized;\n api.setValue([next]);\n event.currentTarget.value = formatDateDisplayValue(next, locale);\n onChange(formatDateValue(next));\n }}\n readOnly={readOnly}\n tabIndex={tabIndex ?? undefined}\n />\n <Button\n {...api.getTriggerProps()}\n aria-label={`Open ${label || name} calendar`}\n disabled={disabled || readOnly}\n size=\"icon\"\n type=\"button\"\n color=\"secondary\"\n >\n <Icon name=\"calendar\" className=\"size-lt-icon-md\" aria-hidden=\"true\" />\n </Button>\n </div>\n {api.open ? (\n <div\n {...api.getPositionerProps()}\n className=\"absolute z-lt-popover mt-2 rounded-lt-sm border border-lt-border bg-lt-popover p-3 text-lt-popover-fg shadow-lt-md\"\n >\n <div {...api.getContentProps()} className=\"grid gap-3\">\n <div className=\"flex items-center justify-between gap-2\">\n <Button {...api.getPrevTriggerProps()} emphasis=\"ghost\" size=\"icon\" type=\"button\">\n <Icon name=\"chevron-left\" className=\"size-lt-icon-md\" aria-hidden=\"true\" />\n </Button>\n <div {...api.getRangeTextProps()} className=\"text-sm font-medium text-lt-fg\" />\n <Button {...api.getNextTriggerProps()} emphasis=\"ghost\" size=\"icon\" type=\"button\">\n <Icon name=\"chevron-right\" className=\"size-lt-icon-md\" aria-hidden=\"true\" />\n </Button>\n </div>\n <table {...api.getTableProps()} className=\"w-full border-collapse text-sm\">\n <thead {...api.getTableHeadProps()}>\n <tr {...api.getTableRowProps()}>\n {api.weekDays.map((day) => (\n <th\n {...api.getTableHeaderProps()}\n aria-label={day.long}\n key={day.value.toString()}\n className=\"size-8 text-center text-xs font-medium text-lt-muted-fg\"\n >\n {day.narrow}\n </th>\n ))}\n </tr>\n </thead>\n <tbody {...api.getTableBodyProps()}>\n {api.weeks.map((week, weekIndex) => (\n <tr {...api.getTableRowProps()} key={weekIndex}>\n {week.map((day) => {\n const state = api.getDayTableCellState({ value: day });\n\n return (\n <td\n {...api.getDayTableCellProps({ value: day })}\n key={day.toString()}\n className=\"p-0 text-center\"\n >\n <button\n {...api.getDayTableCellTriggerProps({ value: day })}\n type=\"button\"\n className={cn(\n \"size-8 rounded-lt-sm text-sm text-lt-fg hover:bg-lt-muted\",\n state.selected && \"bg-lt-primary text-lt-primary-fg\",\n state.outsideRange && \"text-lt-muted-fg\",\n state.disabled && \"cursor-not-allowed opacity-40\",\n )}\n >\n {day.day}\n </button>\n </td>\n );\n })}\n </tr>\n ))}\n </tbody>\n </table>\n {mode === \"date-time\" ? (\n <TimePicker\n value={parseTimeString(formatTimeInputValue(selected[0], timezone))}\n onChange={(next) =>\n api.setTime({ hour: next.hour, minute: next.minute, second: next.second })\n }\n step={step}\n disabled={disabled}\n readOnly={readOnly}\n testId={`${testId}-time`}\n />\n ) : null}\n </div>\n </div>\n ) : null}\n </div>\n );\n}\n\nfunction normalizeDateInputValue(value: string): string | undefined {\n const compact = value.replace(/\\D/g, \"\");\n\n if (compact.length !== 8) {\n return undefined;\n }\n\n return `${compact.slice(0, 4)}-${compact.slice(4, 6)}-${compact.slice(6, 8)}`;\n}\n"],"mappings":"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"}
|