@lattice-php/lattice 0.25.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/dist/action/components/action-form.js +3 -3
  2. package/dist/action/components/action-form.js.map +1 -1
  3. package/dist/action/components/action-group.js +1 -1
  4. package/dist/action/components/action-group.js.map +1 -1
  5. package/dist/action/components/action.js +3 -2
  6. package/dist/action/components/action.js.map +1 -1
  7. package/dist/action/hooks/use-action.js +4 -3
  8. package/dist/action/hooks/use-action.js.map +1 -1
  9. package/dist/appearance/index.d.ts +1 -1
  10. package/dist/appearance/index.js +8 -8
  11. package/dist/appearance/index.js.map +1 -1
  12. package/dist/chat/components/chat-box.js +1 -1
  13. package/dist/chat/components/prompt-input.js +1 -2
  14. package/dist/chat/components/prompt-input.js.map +1 -1
  15. package/dist/core/api.d.ts +13 -0
  16. package/dist/core/api.js +38 -4
  17. package/dist/core/api.js.map +1 -1
  18. package/dist/core/component-ref.d.ts +3 -0
  19. package/dist/core/component-ref.js +20 -2
  20. package/dist/core/component-ref.js.map +1 -1
  21. package/dist/core/headers.js +1 -1
  22. package/dist/core/index.js +4 -4
  23. package/dist/create-app.js +8 -3
  24. package/dist/create-app.js.map +1 -1
  25. package/dist/dnd/index.d.ts +16 -0
  26. package/dist/dnd/index.js +13 -0
  27. package/dist/form/components/base/submit-button.d.ts +4 -3
  28. package/dist/form/components/base/submit-button.js +2 -1
  29. package/dist/form/components/base/submit-button.js.map +1 -1
  30. package/dist/form/components/fields/date-picker-field.js +2 -2
  31. package/dist/form/components/fields/date-picker-field.js.map +1 -1
  32. package/dist/form/components/fields/file-upload.js +3 -2
  33. package/dist/form/components/fields/file-upload.js.map +1 -1
  34. package/dist/form/components/fields/number-input.js +1 -1
  35. package/dist/form/components/fields/number-input.js.map +1 -1
  36. package/dist/form/components/fields/rich-editor-field.js +1 -1
  37. package/dist/form/components/fields/rich-editor-field.js.map +1 -1
  38. package/dist/form/components/fields/row-action-menu.js +3 -3
  39. package/dist/form/components/fields/row-action-menu.js.map +1 -1
  40. package/dist/form/components/fields/row-actions.d.ts +1 -1
  41. package/dist/form/components/fields/row-actions.js +4 -4
  42. package/dist/form/components/fields/row-actions.js.map +1 -1
  43. package/dist/form/components/fields/select.js +4 -3
  44. package/dist/form/components/fields/select.js.map +1 -1
  45. package/dist/form/components/fields/toggle.js +1 -1
  46. package/dist/form/components/fields/toggle.js.map +1 -1
  47. package/dist/form/components/form.js +69 -29
  48. package/dist/form/components/form.js.map +1 -1
  49. package/dist/form/components/wizard.js +1 -1
  50. package/dist/form/components/wizard.js.map +1 -1
  51. package/dist/form/hooks/use-form-resolver.js +1 -1
  52. package/dist/form/hooks/use-form-resolver.js.map +1 -1
  53. package/dist/form/hooks/values.d.ts +8 -0
  54. package/dist/form/hooks/values.js +18 -1
  55. package/dist/form/hooks/values.js.map +1 -1
  56. package/dist/i18n/backend.js +1 -1
  57. package/dist/i18n/instance.js +1 -1
  58. package/dist/i18n/timezone.js +1 -1
  59. package/dist/index.d.ts +2 -2
  60. package/dist/index.js +5 -5
  61. package/dist/inertia.d.ts +7 -0
  62. package/dist/inertia.js +19 -1
  63. package/dist/inertia.js.map +1 -1
  64. package/dist/lattice.css +189 -116
  65. package/dist/notifications/components/notification-item.js +3 -1
  66. package/dist/notifications/components/notification-item.js.map +1 -1
  67. package/dist/notifications/components/notifications.js +2 -4
  68. package/dist/notifications/components/notifications.js.map +1 -1
  69. package/dist/remote/components/data-list.js +2 -2
  70. package/dist/table/components/bulk-bar.js +3 -1
  71. package/dist/table/components/bulk-bar.js.map +1 -1
  72. package/dist/table/components/cells/badge-cell.js +3 -6
  73. package/dist/table/components/cells/badge-cell.js.map +1 -1
  74. package/dist/table/components/cells/text-cell.js +6 -9
  75. package/dist/table/components/cells/text-cell.js.map +1 -1
  76. package/dist/table/components/column-filter-control.js +2 -2
  77. package/dist/table/components/column-filter-control.js.map +1 -1
  78. package/dist/table/components/filter-bar.js +1 -1
  79. package/dist/table/components/filter-bar.js.map +1 -1
  80. package/dist/table/components/pagination.js +6 -6
  81. package/dist/table/components/pagination.js.map +1 -1
  82. package/dist/table/hooks/use-table.js +3 -2
  83. package/dist/table/hooks/use-table.js.map +1 -1
  84. package/dist/table/lib/bulk.d.ts +3 -2
  85. package/dist/table/lib/bulk.js +2 -1
  86. package/dist/table/lib/bulk.js.map +1 -1
  87. package/dist/toast/toast.js +3 -1
  88. package/dist/toast/toast.js.map +1 -1
  89. package/dist/toast/variant-styles.js +15 -1
  90. package/dist/toast/variant-styles.js.map +1 -1
  91. package/dist/types/generated.d.ts +14 -9
  92. package/dist/ui/action-menu-context.d.ts +1 -1
  93. package/dist/ui/action-menu-context.js +1 -1
  94. package/dist/ui/action-menu-context.js.map +1 -1
  95. package/dist/ui/affix-group.js +1 -1
  96. package/dist/ui/affix-group.js.map +1 -1
  97. package/dist/ui/badge.d.ts +4 -7
  98. package/dist/ui/badge.js +11 -17
  99. package/dist/ui/badge.js.map +1 -1
  100. package/dist/ui/button.d.ts +6 -5
  101. package/dist/ui/button.js +154 -15
  102. package/dist/ui/button.js.map +1 -1
  103. package/dist/ui/chart-view.js +9 -7
  104. package/dist/ui/chart-view.js.map +1 -1
  105. package/dist/ui/checkbox.js +1 -1
  106. package/dist/ui/checkbox.js.map +1 -1
  107. package/dist/ui/confirm-dialog.d.ts +4 -3
  108. package/dist/ui/confirm-dialog.js +3 -2
  109. package/dist/ui/confirm-dialog.js.map +1 -1
  110. package/dist/ui/control.d.ts +1 -1
  111. package/dist/ui/control.js +1 -1
  112. package/dist/ui/control.js.map +1 -1
  113. package/dist/ui/dialog.js +1 -1
  114. package/dist/ui/dialog.js.map +1 -1
  115. package/dist/ui/dropdown-menu.d.ts +2 -2
  116. package/dist/ui/dropdown-menu.js +2 -2
  117. package/dist/ui/dropdown-menu.js.map +1 -1
  118. package/dist/ui/fragment.js +1 -1
  119. package/dist/ui/icon-button.d.ts +3 -3
  120. package/dist/ui/icon-button.js +6 -6
  121. package/dist/ui/icon-button.js.map +1 -1
  122. package/dist/ui/image-preview.js +1 -1
  123. package/dist/ui/image-preview.js.map +1 -1
  124. package/dist/ui/index.d.ts +2 -2
  125. package/dist/ui/index.js +2 -2
  126. package/dist/ui/info-tooltip.js +1 -1
  127. package/dist/ui/info-tooltip.js.map +1 -1
  128. package/dist/ui/input-otp.js +1 -1
  129. package/dist/ui/input-otp.js.map +1 -1
  130. package/dist/ui/label.js +1 -1
  131. package/dist/ui/label.js.map +1 -1
  132. package/dist/ui/password-input.js +1 -1
  133. package/dist/ui/password-input.js.map +1 -1
  134. package/dist/ui/progress.js +2 -2
  135. package/dist/ui/progress.js.map +1 -1
  136. package/dist/ui/text.js +1 -1
  137. package/dist/ui/tooltip.js +1 -1
  138. package/dist/ui/tooltip.js.map +1 -1
  139. package/dist-standalone/chunks/{chart-view-DEwGTt9d.js → chart-view-CpYsLvzR.js} +4 -4
  140. package/dist-standalone/chunks/date-picker-field-lX8N40Ii.js +1 -0
  141. package/dist-standalone/chunks/{page-props-D3FnL614.js → page-props-ClpVjpOc.js} +1 -1
  142. package/dist-standalone/chunks/{rich-editor-field-B9Nv1qia.js → rich-editor-field-C71csVJv.js} +2 -2
  143. package/dist-standalone/chunks/{subscriptions-C-0lSr7X.js → subscriptions-Bt2s3IF3.js} +1 -1
  144. package/dist-standalone/chunks/use-effect-dispatcher-DGFXSzA6.js +97 -0
  145. package/dist-standalone/lattice.css +1 -1
  146. package/dist-standalone/lattice.js +15 -15
  147. package/dist-standalone/manifest.json +8 -8
  148. package/package.json +9 -1
  149. package/dist-standalone/chunks/date-picker-field-CCQ9nWx0.js +0 -1
  150. package/dist-standalone/chunks/use-effect-dispatcher-EVZxmfoo.js +0 -97
@@ -1,3 +1,4 @@
1
+ import { apiFetch } from "../../core/api.js";
1
2
  import { useT } from "../../i18n/instance.js";
2
3
  import { Renderer } from "../../core/renderer.js";
3
4
  import { dispatchActionError, getActionEffects } from "../../effects/dispatch.js";
@@ -5,7 +6,6 @@ import { useEffectDispatcher } from "../../effects/use-effect-dispatcher.js";
5
6
  import { Button } from "../../ui/button.js";
6
7
  import { Dialog, DialogContent, DialogHeader } from "../../ui/dialog.js";
7
8
  import { Spinner } from "../../ui/spinner.js";
8
- import { apiFetch } from "../../core/api.js";
9
9
  import { Skeleton } from "../../ui/skeleton.js";
10
10
  import { FormProvider } from "../../form/hooks/context.js";
11
11
  import { PrefillProvider } from "../../form/hooks/prefill-context.js";
@@ -32,7 +32,7 @@ function useLazyActionForm(endpoint, componentRef, enabled) {
32
32
  }
33
33
  const controller = new AbortController();
34
34
  apiFetch(endpoint, {
35
- body: JSON.stringify({ _form: true }),
35
+ body: JSON.stringify({ _sub: "schema" }),
36
36
  ref: componentRef,
37
37
  method: "POST",
38
38
  signal: controller.signal,
@@ -194,7 +194,7 @@ function ActionFormBody({ cancelLabel, componentRef, endpoint, extraData, fieldL
194
194
  disabled: processing,
195
195
  onClick: onClose,
196
196
  type: "button",
197
- variant: "ghost",
197
+ emphasis: "ghost",
198
198
  children: cancelLabel
199
199
  }), formNode.props?.submitButton !== false && /* @__PURE__ */ jsxs(Button, {
200
200
  "data-test": "action-form-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 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 variant=\"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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"}
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({ _sub: \"schema\" }),\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
- variant: "ghost",
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 variant=\"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,SAAQ;cAER,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"}
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.variant ?? "default";
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
- variant: isMenuItem ? "ghost" : variant,
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.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":";;;;;;;;;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,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"}
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"}
@@ -1,8 +1,8 @@
1
- import { translate } from "../../i18n/instance.js";
2
1
  import { withHeaders } from "../../core/headers.js";
2
+ import { apiFetch } from "../../core/api.js";
3
+ import { translate } from "../../i18n/instance.js";
3
4
  import { useEffectDispatcher } from "../../effects/use-effect-dispatcher.js";
4
5
  import { ConfirmDialog } from "../../ui/confirm-dialog.js";
5
- import { apiFetch } from "../../core/api.js";
6
6
  import { runAction } from "../lib/run-action.js";
7
7
  import { ActionForm, useLazyActionForm } from "../components/action-form.js";
8
8
  import { actionLabel } from "../lib/action-label.js";
@@ -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.variant ?? "default";
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.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 [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 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,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,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;CAuC7E,OAAO;EAAE;EAAY;EAAe,UApClC,qBAAA,YAAA,EAAA,UAAA,CACG,gBAAgB,gBACf,oBAAC,eAAD;GACE,OAAO;GACP,aAAa,aAAa,eAAe,KAAA;GACzC,cAAc;GACd,aAAa;GACb,gBAAgB;GACJ;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"}
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 initializeTheme(): void;
11
+ export declare function initializeAppearance(): void;
12
12
  export declare function useAppearance(): UseAppearanceReturn;
13
13
  export {};
@@ -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 applyTheme = (appearance) => {
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 handleSystemThemeChange = () => {
47
- applyTheme(currentAppearance);
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
- applyTheme(mode);
54
+ applyAppearance(mode);
55
55
  notify();
56
56
  }
57
- function initializeTheme() {
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
- applyTheme(currentAppearance);
65
- mediaQuery()?.addEventListener("change", handleSystemThemeChange);
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 { initializeTheme, isAppearance, updateAppearance, useAppearance };
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 applyTheme = (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 handleSystemThemeChange = (): void => {\n applyTheme(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 applyTheme(mode);\n notify();\n}\n\nexport function initializeTheme(): 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 applyTheme(currentAppearance);\n\n mediaQuery()?.addEventListener(\"change\", handleSystemThemeChange);\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,cAAc,eAAiC;CACnD,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,gCAAsC;CAC1C,WAAW,iBAAiB;CAC5B,OAAO;AACT;AAEA,SAAgB,iBAAiB,MAAwB;CACvD,oBAAoB;CAEpB,aAAa,QAAQ,cAAc,IAAI;CAEvC,UAAU,cAAc,IAAI;CAE5B,WAAW,IAAI;CACf,OAAO;AACT;AAEA,SAAgB,kBAAwB;CACtC,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,WAAW,iBAAiB;CAE5B,WAAW,GAAG,iBAAiB,UAAU,uBAAuB;AAClE;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"}
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"}
@@ -1,7 +1,7 @@
1
+ import { apiFetch, remoteJson } from "../../core/api.js";
1
2
  import { useT } from "../../i18n/instance.js";
2
3
  import { cn } from "../../lib/utils.js";
3
4
  import { testIdentity } from "../../core/test-id.js";
4
- import { apiFetch, remoteJson } from "../../core/api.js";
5
5
  import { createRemoteNdjsonChatTransport, ndjsonChatTransport } from "../lib/transport.js";
6
6
  import { useChat } from "../hooks/use-chat.js";
7
7
  import { MessageList } from "./message-list.js";
@@ -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-1 focus:ring-lt-ring disabled:opacity-50"),
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 focus:ring-lt-ring disabled:opacity-50\",\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 variant=\"default\"\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,kNACF;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;GACL,SAAQ;aAEP,EAAE,oBAAoB,MAAM;EACvB,CAAA,CACL;;AAET"}
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"}
@@ -16,6 +16,19 @@ export type RemoteInit = Omit<RequestInit, "credentials" | "headers"> & {
16
16
  retryOnUnauthorized?: boolean;
17
17
  throwOnError?: boolean;
18
18
  };
19
+ /**
20
+ * Boot paths call this with the server-minted URL so subdirectory installs
21
+ * refresh against the right path; the default covers root installs.
22
+ */
23
+ export declare function setRefRefreshEndpoint(url: string): void;
24
+ /**
25
+ * Trade an expired-but-authentic ref for a fresh one. Deduped per original ref
26
+ * so a burst of 403s from one component costs a single round-trip. The renewed
27
+ * token lands in the component-ref map, so every later request that resolves
28
+ * its ref through withHeaders picks it up — this is the transport-agnostic
29
+ * primitive both the fetch funnel below and the Inertia form retry build on.
30
+ */
31
+ export declare function refreshRef(componentRef: string): Promise<string | null>;
19
32
  export declare function apiFetch(url: string, init?: ApiInit): Promise<Response>;
20
33
  export declare function apiJson<T>(url: string, init?: ApiInit): Promise<T>;
21
34
  export declare function clearRemoteTokenCache(): void;
package/dist/core/api.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { latestRef, storeRefreshedRef } from "./component-ref.js";
1
2
  import { localeHeader } from "../i18n/locale.js";
2
3
  import { withHeaders } from "./headers.js";
3
4
  //#region resources/js/core/api.ts
@@ -31,18 +32,51 @@ function defaultHeaders(method) {
31
32
  "X-XSRF-TOKEN": xsrfToken()
32
33
  };
33
34
  }
35
+ var refRefreshEndpoint = "/lattice/refs/refresh";
36
+ /**
37
+ * Boot paths call this with the server-minted URL so subdirectory installs
38
+ * refresh against the right path; the default covers root installs.
39
+ */
40
+ function setRefRefreshEndpoint(url) {
41
+ refRefreshEndpoint = url;
42
+ }
43
+ var pendingRefRefreshes = /* @__PURE__ */ new Map();
44
+ /**
45
+ * Trade an expired-but-authentic ref for a fresh one. Deduped per original ref
46
+ * so a burst of 403s from one component costs a single round-trip. The renewed
47
+ * token lands in the component-ref map, so every later request that resolves
48
+ * its ref through withHeaders picks it up — this is the transport-agnostic
49
+ * primitive both the fetch funnel below and the Inertia form retry build on.
50
+ */
51
+ async function refreshRef(componentRef) {
52
+ const pending = pendingRefRefreshes.get(componentRef);
53
+ if (pending) return pending;
54
+ const request = apiJson(refRefreshEndpoint, {
55
+ method: "POST",
56
+ body: JSON.stringify({ ref: latestRef(componentRef) })
57
+ }).then((data) => {
58
+ storeRefreshedRef(componentRef, data.ref);
59
+ return data.ref;
60
+ }).catch(() => null).finally(() => {
61
+ pendingRefRefreshes.delete(componentRef);
62
+ });
63
+ pendingRefRefreshes.set(componentRef, request);
64
+ return request;
65
+ }
34
66
  async function apiFetch(url, init = {}) {
35
- const { ref, headers, throwOnError = true, method, ...rest } = init;
67
+ const { ref = "", headers, throwOnError = true, method, ...rest } = init;
36
68
  const normalizedMethod = method?.toUpperCase();
37
- const response = await fetch(url, {
69
+ const request = () => fetch(url, {
38
70
  credentials: "same-origin",
39
71
  ...rest,
40
72
  method: normalizedMethod,
41
- headers: withHeaders(ref ?? "", {
73
+ headers: withHeaders(ref, {
42
74
  ...defaultHeaders(normalizedMethod),
43
75
  ...headers
44
76
  })
45
77
  });
78
+ let response = await request();
79
+ if (ref !== "" && response.status === 403 && await refreshRef(ref) !== null) response = await request();
46
80
  if (throwOnError && !response.ok) throw new ApiError(response);
47
81
  return response;
48
82
  }
@@ -122,6 +156,6 @@ async function remoteJson(url, init) {
122
156
  return (await remoteFetch(url, init)).json();
123
157
  }
124
158
  //#endregion
125
- export { ApiError, apiFetch, apiJson, clearRemoteTokenCache, invalidateRemoteToken, remoteFetch, remoteJson, remoteToken };
159
+ export { ApiError, apiFetch, apiJson, clearRemoteTokenCache, invalidateRemoteToken, refreshRef, remoteFetch, remoteJson, remoteToken, setRefRefreshEndpoint };
126
160
 
127
161
  //# sourceMappingURL=api.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"api.js","names":[],"sources":["../../resources/js/core/api.ts"],"sourcesContent":["/**\n * The single funnel for raw-fetch HTTP, so the cross-cutting header, credential,\n * and error policy has one home and call sites only declare intent. Returns a\n * Response (not parsed data) so one primitive serves JSON, the chat's streaming\n * getReader(), and multipart alike. Not for the Inertia world (router/useHttp),\n * which carries its own headers.\n */\n\nimport { withHeaders } from \"./headers\";\nimport { localeHeader } from \"@lattice-php/lattice/i18n/locale\";\nimport type {\n BrowserToken as GeneratedBrowserToken,\n RemoteAccess as GeneratedRemoteAccess,\n} from \"@lattice-php/lattice/types/generated\";\n\nexport class ApiError extends Error {\n constructor(readonly response: Response) {\n super(`HTTP ${response.status}`);\n }\n}\n\nfunction xsrfToken(): string {\n const match = document.cookie.match(/XSRF-TOKEN=([^;]+)/);\n\n return match ? decodeURIComponent(match[1]) : \"\";\n}\n\nexport type ApiInit = Omit<RequestInit, \"headers\"> & {\n ref?: string;\n headers?: Record<string, string>;\n throwOnError?: boolean;\n};\n\nexport type BrowserToken = GeneratedBrowserToken;\nexport type RemoteAccess = GeneratedRemoteAccess;\n\nexport type RemoteInit = Omit<RequestInit, \"credentials\" | \"headers\"> & {\n headers?: Record<string, string>;\n remote: RemoteAccess;\n retryOnUnauthorized?: boolean;\n throwOnError?: boolean;\n};\n\ntype CachedBrowserToken = {\n expiresAt: number;\n token: BrowserToken;\n};\n\nconst tokenRefreshSkewMs = 10_000;\nconst remoteTokenCache = new Map<string, CachedBrowserToken>();\nconst pendingRemoteTokens = new Map<string, Promise<BrowserToken>>();\n\nfunction defaultHeaders(method: string | undefined): Record<string, string> {\n const isWrite = method !== undefined && method !== \"GET\" && method !== \"HEAD\";\n\n if (!isWrite) {\n return { Accept: \"application/json\" };\n }\n\n return {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n \"X-Requested-With\": \"XMLHttpRequest\",\n \"X-XSRF-TOKEN\": xsrfToken(),\n };\n}\n\nexport async function apiFetch(url: string, init: ApiInit = {}): Promise<Response> {\n const { ref, headers, throwOnError = true, method, ...rest } = init;\n // Some servers reject a lower-case method line, and fetch only normalizes the\n // standard verbs, so upper-case it here.\n const normalizedMethod = method?.toUpperCase();\n const response = await fetch(url, {\n credentials: \"same-origin\",\n ...rest,\n method: normalizedMethod,\n headers: withHeaders(ref ?? \"\", { ...defaultHeaders(normalizedMethod), ...headers }),\n });\n\n if (throwOnError && !response.ok) {\n throw new ApiError(response);\n }\n\n return response;\n}\n\nexport async function apiJson<T>(url: string, init?: ApiInit): Promise<T> {\n return (await apiFetch(url, init)).json() as Promise<T>;\n}\n\nfunction remoteTokenKey(remote: RemoteAccess): string {\n return [remote.source, remote.audience, [...remote.scopes].sort().join(\" \")].join(\"\\u001f\");\n}\n\nexport function clearRemoteTokenCache(): void {\n remoteTokenCache.clear();\n pendingRemoteTokens.clear();\n}\n\nexport function invalidateRemoteToken(remote: RemoteAccess): void {\n remoteTokenCache.delete(remoteTokenKey(remote));\n}\n\nexport async function remoteToken(remote: RemoteAccess): Promise<BrowserToken> {\n const key = remoteTokenKey(remote);\n const cached = remoteTokenCache.get(key);\n\n if (cached && cached.expiresAt > Date.now() + tokenRefreshSkewMs) {\n return cached.token;\n }\n\n const pending = pendingRemoteTokens.get(key);\n\n if (pending) {\n return pending;\n }\n\n const request = apiJson<BrowserToken>(remote.tokenEndpoint, {\n method: \"POST\",\n ref: remote.ref,\n body: JSON.stringify({\n nodeId: remote.nodeId,\n nodeType: remote.nodeType,\n audience: remote.audience,\n scopes: remote.scopes,\n }),\n })\n .then((token) => {\n remoteTokenCache.set(key, {\n token,\n expiresAt: Date.now() + Math.max(0, token.expiresIn) * 1000,\n });\n\n return token;\n })\n .finally(() => {\n pendingRemoteTokens.delete(key);\n });\n\n pendingRemoteTokens.set(key, request);\n\n return request;\n}\n\nfunction isUnauthorized(response: Response): boolean {\n return response.status === 401 || response.status === 403;\n}\n\nasync function fetchRemoteWithToken(\n url: string,\n init: Omit<RemoteInit, \"remote\" | \"retryOnUnauthorized\" | \"throwOnError\">,\n token: BrowserToken,\n): Promise<Response> {\n const { headers, ...rest } = init;\n\n return fetch(url, {\n ...rest,\n credentials: \"omit\",\n headers: {\n Accept: \"application/json\",\n ...localeHeader(),\n ...headers,\n Authorization: `${token.tokenType} ${token.accessToken}`,\n },\n });\n}\n\nexport async function remoteFetch(url: string, init: RemoteInit): Promise<Response> {\n const { remote, retryOnUnauthorized = true, throwOnError = true, ...request } = init;\n let token = await remoteToken(remote);\n let response = await fetchRemoteWithToken(url, request, token);\n\n if (retryOnUnauthorized && isUnauthorized(response)) {\n invalidateRemoteToken(remote);\n token = await remoteToken(remote);\n response = await fetchRemoteWithToken(url, request, token);\n }\n\n if (throwOnError && !response.ok) {\n throw new ApiError(response);\n }\n\n return response;\n}\n\nexport async function remoteJson<T>(url: string, init: RemoteInit): Promise<T> {\n return (await remoteFetch(url, init)).json() as Promise<T>;\n}\n"],"mappings":";;;;;;;;;;AAeA,IAAa,WAAb,cAA8B,MAAM;CACb;CAArB,YAAY,UAA6B;EACvC,MAAM,QAAQ,SAAS,QAAQ;EADZ,KAAA,WAAA;CAErB;AACF;AAEA,SAAS,YAAoB;CAC3B,MAAM,QAAQ,SAAS,OAAO,MAAM,oBAAoB;CAExD,OAAO,QAAQ,mBAAmB,MAAM,EAAE,IAAI;AAChD;AAuBA,IAAM,qBAAqB;AAC3B,IAAM,mCAAmB,IAAI,IAAgC;AAC7D,IAAM,sCAAsB,IAAI,IAAmC;AAEnE,SAAS,eAAe,QAAoD;CAG1E,IAAI,EAFY,WAAW,KAAA,KAAa,WAAW,SAAS,WAAW,SAGrE,OAAO,EAAE,QAAQ,mBAAmB;CAGtC,OAAO;EACL,QAAQ;EACR,gBAAgB;EAChB,oBAAoB;EACpB,gBAAgB,UAAU;CAC5B;AACF;AAEA,eAAsB,SAAS,KAAa,OAAgB,CAAC,GAAsB;CACjF,MAAM,EAAE,KAAK,SAAS,eAAe,MAAM,QAAQ,GAAG,SAAS;CAG/D,MAAM,mBAAmB,QAAQ,YAAY;CAC7C,MAAM,WAAW,MAAM,MAAM,KAAK;EAChC,aAAa;EACb,GAAG;EACH,QAAQ;EACR,SAAS,YAAY,OAAO,IAAI;GAAE,GAAG,eAAe,gBAAgB;GAAG,GAAG;EAAQ,CAAC;CACrF,CAAC;CAED,IAAI,gBAAgB,CAAC,SAAS,IAC5B,MAAM,IAAI,SAAS,QAAQ;CAG7B,OAAO;AACT;AAEA,eAAsB,QAAW,KAAa,MAA4B;CACxE,QAAQ,MAAM,SAAS,KAAK,IAAI,GAAG,KAAK;AAC1C;AAEA,SAAS,eAAe,QAA8B;CACpD,OAAO;EAAC,OAAO;EAAQ,OAAO;EAAU,CAAC,GAAG,OAAO,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG;CAAC,EAAE,KAAK,GAAQ;AAC5F;AAEA,SAAgB,wBAA8B;CAC5C,iBAAiB,MAAM;CACvB,oBAAoB,MAAM;AAC5B;AAEA,SAAgB,sBAAsB,QAA4B;CAChE,iBAAiB,OAAO,eAAe,MAAM,CAAC;AAChD;AAEA,eAAsB,YAAY,QAA6C;CAC7E,MAAM,MAAM,eAAe,MAAM;CACjC,MAAM,SAAS,iBAAiB,IAAI,GAAG;CAEvC,IAAI,UAAU,OAAO,YAAY,KAAK,IAAI,IAAI,oBAC5C,OAAO,OAAO;CAGhB,MAAM,UAAU,oBAAoB,IAAI,GAAG;CAE3C,IAAI,SACF,OAAO;CAGT,MAAM,UAAU,QAAsB,OAAO,eAAe;EAC1D,QAAQ;EACR,KAAK,OAAO;EACZ,MAAM,KAAK,UAAU;GACnB,QAAQ,OAAO;GACf,UAAU,OAAO;GACjB,UAAU,OAAO;GACjB,QAAQ,OAAO;EACjB,CAAC;CACH,CAAC,EACE,MAAM,UAAU;EACf,iBAAiB,IAAI,KAAK;GACxB;GACA,WAAW,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,MAAM,SAAS,IAAI;EACzD,CAAC;EAED,OAAO;CACT,CAAC,EACA,cAAc;EACb,oBAAoB,OAAO,GAAG;CAChC,CAAC;CAEH,oBAAoB,IAAI,KAAK,OAAO;CAEpC,OAAO;AACT;AAEA,SAAS,eAAe,UAA6B;CACnD,OAAO,SAAS,WAAW,OAAO,SAAS,WAAW;AACxD;AAEA,eAAe,qBACb,KACA,MACA,OACmB;CACnB,MAAM,EAAE,SAAS,GAAG,SAAS;CAE7B,OAAO,MAAM,KAAK;EAChB,GAAG;EACH,aAAa;EACb,SAAS;GACP,QAAQ;GACR,GAAG,aAAa;GAChB,GAAG;GACH,eAAe,GAAG,MAAM,UAAU,GAAG,MAAM;EAC7C;CACF,CAAC;AACH;AAEA,eAAsB,YAAY,KAAa,MAAqC;CAClF,MAAM,EAAE,QAAQ,sBAAsB,MAAM,eAAe,MAAM,GAAG,YAAY;CAChF,IAAI,QAAQ,MAAM,YAAY,MAAM;CACpC,IAAI,WAAW,MAAM,qBAAqB,KAAK,SAAS,KAAK;CAE7D,IAAI,uBAAuB,eAAe,QAAQ,GAAG;EACnD,sBAAsB,MAAM;EAC5B,QAAQ,MAAM,YAAY,MAAM;EAChC,WAAW,MAAM,qBAAqB,KAAK,SAAS,KAAK;CAC3D;CAEA,IAAI,gBAAgB,CAAC,SAAS,IAC5B,MAAM,IAAI,SAAS,QAAQ;CAG7B,OAAO;AACT;AAEA,eAAsB,WAAc,KAAa,MAA8B;CAC7E,QAAQ,MAAM,YAAY,KAAK,IAAI,GAAG,KAAK;AAC7C"}
1
+ {"version":3,"file":"api.js","names":[],"sources":["../../resources/js/core/api.ts"],"sourcesContent":["/**\n * The single funnel for raw-fetch HTTP, so the cross-cutting header, credential,\n * and error policy has one home and call sites only declare intent. Returns a\n * Response (not parsed data) so one primitive serves JSON, the chat's streaming\n * getReader(), and multipart alike. Not for the Inertia world (router/useHttp),\n * which carries its own headers.\n */\n\nimport { latestRef, storeRefreshedRef } from \"./component-ref\";\nimport { withHeaders } from \"./headers\";\nimport { localeHeader } from \"@lattice-php/lattice/i18n/locale\";\nimport type {\n BrowserToken as GeneratedBrowserToken,\n RemoteAccess as GeneratedRemoteAccess,\n} from \"@lattice-php/lattice/types/generated\";\n\nexport class ApiError extends Error {\n constructor(readonly response: Response) {\n super(`HTTP ${response.status}`);\n }\n}\n\nfunction xsrfToken(): string {\n const match = document.cookie.match(/XSRF-TOKEN=([^;]+)/);\n\n return match ? decodeURIComponent(match[1]) : \"\";\n}\n\nexport type ApiInit = Omit<RequestInit, \"headers\"> & {\n ref?: string;\n headers?: Record<string, string>;\n throwOnError?: boolean;\n};\n\nexport type BrowserToken = GeneratedBrowserToken;\nexport type RemoteAccess = GeneratedRemoteAccess;\n\nexport type RemoteInit = Omit<RequestInit, \"credentials\" | \"headers\"> & {\n headers?: Record<string, string>;\n remote: RemoteAccess;\n retryOnUnauthorized?: boolean;\n throwOnError?: boolean;\n};\n\ntype CachedBrowserToken = {\n expiresAt: number;\n token: BrowserToken;\n};\n\nconst tokenRefreshSkewMs = 10_000;\nconst remoteTokenCache = new Map<string, CachedBrowserToken>();\nconst pendingRemoteTokens = new Map<string, Promise<BrowserToken>>();\n\nfunction defaultHeaders(method: string | undefined): Record<string, string> {\n const isWrite = method !== undefined && method !== \"GET\" && method !== \"HEAD\";\n\n if (!isWrite) {\n return { Accept: \"application/json\" };\n }\n\n return {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n \"X-Requested-With\": \"XMLHttpRequest\",\n \"X-XSRF-TOKEN\": xsrfToken(),\n };\n}\n\nlet refRefreshEndpoint = \"/lattice/refs/refresh\";\n\n/**\n * Boot paths call this with the server-minted URL so subdirectory installs\n * refresh against the right path; the default covers root installs.\n */\nexport function setRefRefreshEndpoint(url: string): void {\n refRefreshEndpoint = url;\n}\n\nconst pendingRefRefreshes = new Map<string, Promise<string | null>>();\n\n/**\n * Trade an expired-but-authentic ref for a fresh one. Deduped per original ref\n * so a burst of 403s from one component costs a single round-trip. The renewed\n * token lands in the component-ref map, so every later request that resolves\n * its ref through withHeaders picks it up — this is the transport-agnostic\n * primitive both the fetch funnel below and the Inertia form retry build on.\n */\nexport async function refreshRef(componentRef: string): Promise<string | null> {\n const pending = pendingRefRefreshes.get(componentRef);\n\n if (pending) {\n return pending;\n }\n\n const request = apiJson<{ ref: string }>(refRefreshEndpoint, {\n method: \"POST\",\n body: JSON.stringify({ ref: latestRef(componentRef) }),\n })\n .then((data) => {\n storeRefreshedRef(componentRef, data.ref);\n\n return data.ref;\n })\n .catch(() => null)\n .finally(() => {\n pendingRefRefreshes.delete(componentRef);\n });\n\n pendingRefRefreshes.set(componentRef, request);\n\n return request;\n}\n\nexport async function apiFetch(url: string, init: ApiInit = {}): Promise<Response> {\n const { ref = \"\", headers, throwOnError = true, method, ...rest } = init;\n // Some servers reject a lower-case method line, and fetch only normalizes the\n // standard verbs, so upper-case it here.\n const normalizedMethod = method?.toUpperCase();\n const request = () =>\n fetch(url, {\n credentials: \"same-origin\",\n ...rest,\n method: normalizedMethod,\n // withHeaders resolves the latest refreshed token for the ref, so the\n // retry below picks up the renewal without threading it through.\n headers: withHeaders(ref, { ...defaultHeaders(normalizedMethod), ...headers }),\n });\n\n let response = await request();\n\n if (ref !== \"\" && response.status === 403 && (await refreshRef(ref)) !== null) {\n response = await request();\n }\n\n if (throwOnError && !response.ok) {\n throw new ApiError(response);\n }\n\n return response;\n}\n\nexport async function apiJson<T>(url: string, init?: ApiInit): Promise<T> {\n return (await apiFetch(url, init)).json() as Promise<T>;\n}\n\nfunction remoteTokenKey(remote: RemoteAccess): string {\n return [remote.source, remote.audience, [...remote.scopes].sort().join(\" \")].join(\"\\u001f\");\n}\n\nexport function clearRemoteTokenCache(): void {\n remoteTokenCache.clear();\n pendingRemoteTokens.clear();\n}\n\nexport function invalidateRemoteToken(remote: RemoteAccess): void {\n remoteTokenCache.delete(remoteTokenKey(remote));\n}\n\nexport async function remoteToken(remote: RemoteAccess): Promise<BrowserToken> {\n const key = remoteTokenKey(remote);\n const cached = remoteTokenCache.get(key);\n\n if (cached && cached.expiresAt > Date.now() + tokenRefreshSkewMs) {\n return cached.token;\n }\n\n const pending = pendingRemoteTokens.get(key);\n\n if (pending) {\n return pending;\n }\n\n const request = apiJson<BrowserToken>(remote.tokenEndpoint, {\n method: \"POST\",\n ref: remote.ref,\n body: JSON.stringify({\n nodeId: remote.nodeId,\n nodeType: remote.nodeType,\n audience: remote.audience,\n scopes: remote.scopes,\n }),\n })\n .then((token) => {\n remoteTokenCache.set(key, {\n token,\n expiresAt: Date.now() + Math.max(0, token.expiresIn) * 1000,\n });\n\n return token;\n })\n .finally(() => {\n pendingRemoteTokens.delete(key);\n });\n\n pendingRemoteTokens.set(key, request);\n\n return request;\n}\n\nfunction isUnauthorized(response: Response): boolean {\n return response.status === 401 || response.status === 403;\n}\n\nasync function fetchRemoteWithToken(\n url: string,\n init: Omit<RemoteInit, \"remote\" | \"retryOnUnauthorized\" | \"throwOnError\">,\n token: BrowserToken,\n): Promise<Response> {\n const { headers, ...rest } = init;\n\n return fetch(url, {\n ...rest,\n credentials: \"omit\",\n headers: {\n Accept: \"application/json\",\n ...localeHeader(),\n ...headers,\n Authorization: `${token.tokenType} ${token.accessToken}`,\n },\n });\n}\n\nexport async function remoteFetch(url: string, init: RemoteInit): Promise<Response> {\n const { remote, retryOnUnauthorized = true, throwOnError = true, ...request } = init;\n let token = await remoteToken(remote);\n let response = await fetchRemoteWithToken(url, request, token);\n\n if (retryOnUnauthorized && isUnauthorized(response)) {\n invalidateRemoteToken(remote);\n token = await remoteToken(remote);\n response = await fetchRemoteWithToken(url, request, token);\n }\n\n if (throwOnError && !response.ok) {\n throw new ApiError(response);\n }\n\n return response;\n}\n\nexport async function remoteJson<T>(url: string, init: RemoteInit): Promise<T> {\n return (await remoteFetch(url, init)).json() as Promise<T>;\n}\n"],"mappings":"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"}
@@ -5,4 +5,7 @@
5
5
  * header.
6
6
  */
7
7
  export declare const LATTICE_REF_HEADER = "X-Lattice-Ref";
8
+ export declare function latestRef(componentRef: string): string;
9
+ export declare function storeRefreshedRef(componentRef: string, refreshed: string): void;
10
+ export declare function clearRefreshedRefs(): void;
8
11
  export declare function withRefHeader(componentRef: string): Record<string, string>;
@@ -6,10 +6,28 @@
6
6
  * header.
7
7
  */
8
8
  var LATTICE_REF_HEADER = "X-Lattice-Ref";
9
+ /**
10
+ * Refs are sealed with a lifetime and baked into node props at render time, so
11
+ * a long-lived tab cannot pick up a renewed token through React state. Renewed
12
+ * tokens are instead kept here, keyed by the original prop value, and resolved
13
+ * whenever a ref travels — every consumer keeps passing the ref it was rendered
14
+ * with.
15
+ */
16
+ var refreshedRefs = /* @__PURE__ */ new Map();
17
+ function latestRef(componentRef) {
18
+ return refreshedRefs.get(componentRef) ?? componentRef;
19
+ }
20
+ function storeRefreshedRef(componentRef, refreshed) {
21
+ refreshedRefs.set(componentRef, refreshed);
22
+ }
23
+ function clearRefreshedRefs() {
24
+ refreshedRefs.clear();
25
+ }
9
26
  function withRefHeader(componentRef) {
10
- return componentRef ? { [LATTICE_REF_HEADER]: componentRef } : {};
27
+ const ref = latestRef(componentRef);
28
+ return ref ? { [LATTICE_REF_HEADER]: ref } : {};
11
29
  }
12
30
  //#endregion
13
- export { LATTICE_REF_HEADER, withRefHeader };
31
+ export { LATTICE_REF_HEADER, clearRefreshedRefs, latestRef, storeRefreshedRef, withRefHeader };
14
32
 
15
33
  //# sourceMappingURL=component-ref.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"component-ref.js","names":[],"sources":["../../resources/js/core/component-ref.ts"],"sourcesContent":["/**\n * The signed component reference travels to the server as the `X-Lattice-Ref`\n * request header on every interactive request (GET and writes alike). This must\n * match ComponentReferenceSigner::token() on the PHP side, which reads the same\n * header.\n */\nexport const LATTICE_REF_HEADER = \"X-Lattice-Ref\";\n\nexport function withRefHeader(componentRef: string): Record<string, string> {\n return componentRef ? { [LATTICE_REF_HEADER]: componentRef } : {};\n}\n"],"mappings":";;;;;;;AAMA,IAAa,qBAAqB;AAElC,SAAgB,cAAc,cAA8C;CAC1E,OAAO,eAAe,GAAG,qBAAqB,aAAa,IAAI,CAAC;AAClE"}
1
+ {"version":3,"file":"component-ref.js","names":[],"sources":["../../resources/js/core/component-ref.ts"],"sourcesContent":["/**\n * The signed component reference travels to the server as the `X-Lattice-Ref`\n * request header on every interactive request (GET and writes alike). This must\n * match ComponentReferenceSigner::token() on the PHP side, which reads the same\n * header.\n */\nexport const LATTICE_REF_HEADER = \"X-Lattice-Ref\";\n\n/**\n * Refs are sealed with a lifetime and baked into node props at render time, so\n * a long-lived tab cannot pick up a renewed token through React state. Renewed\n * tokens are instead kept here, keyed by the original prop value, and resolved\n * whenever a ref travels — every consumer keeps passing the ref it was rendered\n * with.\n */\nconst refreshedRefs = new Map<string, string>();\n\nexport function latestRef(componentRef: string): string {\n return refreshedRefs.get(componentRef) ?? componentRef;\n}\n\nexport function storeRefreshedRef(componentRef: string, refreshed: string): void {\n refreshedRefs.set(componentRef, refreshed);\n}\n\nexport function clearRefreshedRefs(): void {\n refreshedRefs.clear();\n}\n\nexport function withRefHeader(componentRef: string): Record<string, string> {\n const ref = latestRef(componentRef);\n\n return ref ? { [LATTICE_REF_HEADER]: ref } : {};\n}\n"],"mappings":";;;;;;;AAMA,IAAa,qBAAqB;;;;;;;;AASlC,IAAM,gCAAgB,IAAI,IAAoB;AAE9C,SAAgB,UAAU,cAA8B;CACtD,OAAO,cAAc,IAAI,YAAY,KAAK;AAC5C;AAEA,SAAgB,kBAAkB,cAAsB,WAAyB;CAC/E,cAAc,IAAI,cAAc,SAAS;AAC3C;AAEA,SAAgB,qBAA2B;CACzC,cAAc,MAAM;AACtB;AAEA,SAAgB,cAAc,cAA8C;CAC1E,MAAM,MAAM,UAAU,YAAY;CAElC,OAAO,MAAM,GAAG,qBAAqB,IAAI,IAAI,CAAC;AAChD"}
@@ -1,5 +1,5 @@
1
- import { localeHeader } from "../i18n/locale.js";
2
1
  import { withRefHeader } from "./component-ref.js";
2
+ import { localeHeader } from "../i18n/locale.js";
3
3
  //#region resources/js/core/headers.ts
4
4
  function withHeaders(componentRef = "", headers = {}) {
5
5
  return {
@@ -1,13 +1,13 @@
1
- import { createPlugin, createRegistry, eagerComponent, extendRegistry, lazyComponent } from "./registry.js";
2
- import { RegistryContext, setDefaultRegistry, useColumnRegistry, useComponentRegistry, useEffectHandlerRegistry } from "./registry-context.js";
3
- import { dataBindings, isRecord, materializeNode, materializeProps, materializeSchema, rowValue } from "./materialize.js";
4
1
  import { LATTICE_REF_HEADER, withRefHeader } from "./component-ref.js";
5
2
  import { withHeaders } from "./headers.js";
3
+ import { ApiError, apiFetch, apiJson, clearRemoteTokenCache, invalidateRemoteToken, remoteFetch, remoteJson, remoteToken } from "./api.js";
4
+ import { dataBindings, isRecord, materializeNode, materializeProps, materializeSchema, rowValue } from "./materialize.js";
5
+ import { createPlugin, createRegistry, eagerComponent, extendRegistry, lazyComponent } from "./registry.js";
6
+ import { RegistryContext, setDefaultRegistry, useColumnRegistry, useComponentRegistry, useEffectHandlerRegistry } from "./registry-context.js";
6
7
  import { cn } from "../lib/utils.js";
7
8
  import { toNodes } from "./nodes.js";
8
9
  import { CollapsedContext, useCollapsed } from "./collapsed-context.js";
9
10
  import { RenderNode, Renderer } from "./renderer.js";
10
11
  import { nodeIdentity } from "./test-id.js";
11
12
  import { usePersistentState } from "../lib/use-persistent-state.js";
12
- import { ApiError, apiFetch, apiJson, clearRemoteTokenCache, invalidateRemoteToken, remoteFetch, remoteJson, remoteToken } from "./api.js";
13
13
  export { ApiError, CollapsedContext, LATTICE_REF_HEADER, RegistryContext, RenderNode, Renderer, apiFetch, apiJson, clearRemoteTokenCache, cn, createPlugin, createRegistry, dataBindings, eagerComponent, extendRegistry, invalidateRemoteToken, isRecord, lazyComponent, materializeNode, materializeProps, materializeSchema, nodeIdentity, remoteFetch, remoteJson, remoteToken, rowValue, setDefaultRegistry, toNodes, useCollapsed, useColumnRegistry, useComponentRegistry, useEffectHandlerRegistry, usePersistentState, withHeaders, withRefHeader };