@lattice-php/lattice 0.30.0 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/action/components/action-form.js +26 -25
- package/dist/action/components/action-form.js.map +1 -1
- package/dist/action/components/action-group.js.map +1 -1
- package/dist/action/components/action-trigger-provider.js.map +1 -1
- package/dist/action/components/action.js.map +1 -1
- package/dist/action/lib/run-action.js +2 -1
- package/dist/action/lib/run-action.js.map +1 -1
- package/dist/appearance/index.js.map +1 -1
- package/dist/chat/components/chat-box.js +4 -2
- package/dist/chat/components/chat-box.js.map +1 -1
- package/dist/chat/components/message-list.js.map +1 -1
- package/dist/chat/components/message.js.map +1 -1
- package/dist/chat/components/prompt-input.js.map +1 -1
- package/dist/chat/hooks/use-chat.js +2 -1
- package/dist/chat/hooks/use-chat.js.map +1 -1
- package/dist/chat/lib/transport.js.map +1 -1
- package/dist/chat/parts/text.js.map +1 -1
- package/dist/chat/parts/tool-call.js.map +1 -1
- package/dist/core/api.js.map +1 -1
- package/dist/core/event-names.d.ts +1 -0
- package/dist/core/event-names.js +1 -0
- package/dist/core/event-names.js.map +1 -1
- package/dist/core/hooks/column-sizing.js.map +1 -1
- package/dist/core/hooks/use-column-resizing.js.map +1 -1
- package/dist/core/materialize.js.map +1 -1
- package/dist/core/registry-context.js.map +1 -1
- package/dist/core/renderer.js +2 -1
- package/dist/core/renderer.js.map +1 -1
- package/dist/core/test-id.js.map +1 -1
- package/dist/core/upload.js.map +1 -1
- package/dist/create-app.js +1 -1
- package/dist/create-app.js.map +1 -1
- package/dist/effects/registry.js +2 -1
- package/dist/effects/registry.js.map +1 -1
- package/dist/form/components/base/field.js.map +1 -1
- package/dist/form/components/base/submit-button.js.map +1 -1
- package/dist/form/components/fields/add-row-menu.js.map +1 -1
- package/dist/form/components/fields/builder.js +2 -1
- package/dist/form/components/fields/builder.js.map +1 -1
- package/dist/form/components/fields/checkbox.js.map +1 -1
- package/dist/form/components/fields/choice.js.map +1 -1
- package/dist/form/components/fields/color-picker-field.js.map +1 -1
- package/dist/form/components/fields/date-input.js.map +1 -1
- package/dist/form/components/fields/date-picker-field.js.map +1 -1
- package/dist/form/components/fields/date-picker-value.js.map +1 -1
- package/dist/form/components/fields/date-picker.js.map +1 -1
- package/dist/form/components/fields/date-time-input.js.map +1 -1
- package/dist/form/components/fields/file-upload.js +3 -2
- package/dist/form/components/fields/file-upload.js.map +1 -1
- package/dist/form/components/fields/number-input.js.map +1 -1
- package/dist/form/components/fields/otp-input.js.map +1 -1
- package/dist/form/components/fields/password-input.js.map +1 -1
- package/dist/form/components/fields/repeater.js +2 -1
- package/dist/form/components/fields/repeater.js.map +1 -1
- package/dist/form/components/fields/rich-editor-field.js +6 -5
- package/dist/form/components/fields/rich-editor-field.js.map +1 -1
- package/dist/form/components/fields/rich-editor.js.map +1 -1
- package/dist/form/components/fields/row-action-menu.js.map +1 -1
- package/dist/form/components/fields/row-actions.js.map +1 -1
- package/dist/form/components/fields/row-item.js.map +1 -1
- package/dist/form/components/fields/row-templates.js.map +1 -1
- package/dist/form/components/fields/select.js.map +1 -1
- package/dist/form/components/fields/simple-field.js.map +1 -1
- package/dist/form/components/fields/table-rows.js.map +1 -1
- package/dist/form/components/fields/text-input.js.map +1 -1
- package/dist/form/components/fields/textarea.js.map +1 -1
- package/dist/form/components/fields/time-input.js.map +1 -1
- package/dist/form/components/fields/time-picker-columns.js.map +1 -1
- package/dist/form/components/fields/time-picker.js +0 -1
- package/dist/form/components/fields/time-picker.js.map +1 -1
- package/dist/form/components/fields/toggle.js.map +1 -1
- package/dist/form/components/fields/use-flip-reorder.js.map +1 -1
- package/dist/form/components/form.js +7 -5
- package/dist/form/components/form.js.map +1 -1
- package/dist/form/components/wizard.js +1 -1
- package/dist/form/components/wizard.js.map +1 -1
- package/dist/form/hooks/resolved-nodes.js.map +1 -1
- package/dist/form/hooks/use-dependent-field.js +2 -1
- package/dist/form/hooks/use-dependent-field.js.map +1 -1
- package/dist/form/hooks/use-form-resolver.js +2 -1
- package/dist/form/hooks/use-form-resolver.js.map +1 -1
- package/dist/form/hooks/values.js +2 -1
- package/dist/form/hooks/values.js.map +1 -1
- package/dist/form/lib/conditions.js +1 -1
- package/dist/form/lib/conditions.js.map +1 -1
- package/dist/form/lib/field-dom-name.js.map +1 -1
- package/dist/form/lib/field-props.js +1 -1
- package/dist/form/lib/field-props.js.map +1 -1
- package/dist/form/lib/form-path.js.map +1 -1
- package/dist/form/lib/form-transport.js.map +1 -1
- package/dist/form/lib/prefill-targets.js.map +1 -1
- package/dist/form/lib/wizard-steps.js.map +1 -1
- package/dist/form/rich-editor/builtins.js.map +1 -1
- package/dist/format/date-time.d.ts +1 -0
- package/dist/format/date-time.js +5 -1
- package/dist/format/date-time.js.map +1 -1
- package/dist/format/number.js.map +1 -1
- package/dist/i18n/config.js.map +1 -1
- package/dist/i18n/date-time-formatter.d.ts +13 -0
- package/dist/i18n/date-time-formatter.js +28 -0
- package/dist/i18n/date-time-formatter.js.map +1 -0
- package/dist/i18n/date-time.js +1 -1
- package/dist/i18n/date-time.js.map +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/i18n/instance.js +2 -0
- package/dist/i18n/instance.js.map +1 -1
- package/dist/i18n/locale.js +1 -1
- package/dist/i18n/locale.js.map +1 -1
- package/dist/i18n/timezone.js.map +1 -1
- package/dist/i18n/translatable.js.map +1 -1
- package/dist/icons/icon-renderer.js.map +1 -1
- package/dist/icons/sprite.js.map +1 -1
- package/dist/layout/components/breadcrumbs.js.map +1 -1
- package/dist/layout/components/callouts.d.ts +2 -1
- package/dist/layout/components/callouts.js +6 -2
- package/dist/layout/components/callouts.js.map +1 -1
- package/dist/layout/components/dropdown.js.map +1 -1
- package/dist/layout/components/menu-item.js.map +1 -1
- package/dist/layout/components/menu.js.map +1 -1
- package/dist/layout/components/popover.js.map +1 -1
- package/dist/layout/components/schema-layout.js.map +1 -1
- package/dist/layout/components/sidebar.js.map +1 -1
- package/dist/lib/color.js +1 -1
- package/dist/lib/color.js.map +1 -1
- package/dist/lib/use-media-query.js.map +1 -1
- package/dist/notifications/components/empty-state.js.map +1 -1
- package/dist/notifications/components/notification-item.js.map +1 -1
- package/dist/notifications/components/notification-list.js.map +1 -1
- package/dist/notifications/components/notifications.js.map +1 -1
- package/dist/notifications/store.js.map +1 -1
- package/dist/page.js.map +1 -1
- package/dist/provider-base.js.map +1 -1
- package/dist/realtime/listeners.js.map +1 -1
- package/dist/remote/components/data-list.js.map +1 -1
- package/dist/table/components/bulk-bar.js.map +1 -1
- package/dist/table/components/cells/badge-cell.js.map +1 -1
- package/dist/table/components/cells/boolean-cell.js.map +1 -1
- package/dist/table/components/cells/icon-cell.js.map +1 -1
- package/dist/table/components/cells/numeric-cell.js.map +1 -1
- package/dist/table/components/cells/stack-cell.js.map +1 -1
- package/dist/table/components/cells/text-cell.js.map +1 -1
- package/dist/table/components/column-filter-control.js.map +1 -1
- package/dist/table/components/column-header.js.map +1 -1
- package/dist/table/components/column-visibility-menu.js +2 -1
- package/dist/table/components/column-visibility-menu.js.map +1 -1
- package/dist/table/components/filter-bar.js +2 -1
- package/dist/table/components/filter-bar.js.map +1 -1
- package/dist/table/components/filter-controls.js +6 -5
- package/dist/table/components/filter-controls.js.map +1 -1
- package/dist/table/components/filter-value-input.js.map +1 -1
- package/dist/table/components/pagination.d.ts +5 -1
- package/dist/table/components/pagination.js +25 -6
- package/dist/table/components/pagination.js.map +1 -1
- package/dist/table/components/sort-bar.js.map +1 -1
- package/dist/table/components/table-cell.js +2 -1
- package/dist/table/components/table-cell.js.map +1 -1
- package/dist/table/components/table-search.js.map +1 -1
- package/dist/table/components/table.js +6 -2
- package/dist/table/components/table.js.map +1 -1
- package/dist/table/hooks/use-column-visibility.js.map +1 -1
- package/dist/table/hooks/use-table-selection.js +2 -1
- package/dist/table/hooks/use-table-selection.js.map +1 -1
- package/dist/table/hooks/use-table.d.ts +2 -0
- package/dist/table/hooks/use-table.js +12 -0
- package/dist/table/hooks/use-table.js.map +1 -1
- package/dist/table/lib/bulk.js.map +1 -1
- package/dist/table/lib/filter-values.js.map +1 -1
- package/dist/table/lib/format.js.map +1 -1
- package/dist/table/lib/payload.d.ts +2 -0
- package/dist/table/lib/payload.js +18 -3
- package/dist/table/lib/payload.js.map +1 -1
- package/dist/table/lib/query.js +2 -1
- package/dist/table/lib/query.js.map +1 -1
- package/dist/toast/callout.d.ts +1 -0
- package/dist/toast/callout.js +9 -1
- package/dist/toast/callout.js.map +1 -1
- package/dist/toast/index.d.ts +1 -1
- package/dist/toast/index.js +2 -2
- package/dist/toast/toaster.js.map +1 -1
- package/dist/types/generated.d.ts +9 -1
- package/dist/ui/affix-group.js.map +1 -1
- package/dist/ui/avatar.js.map +1 -1
- package/dist/ui/badge.js.map +1 -1
- package/dist/ui/button.js.map +1 -1
- package/dist/ui/card.js.map +1 -1
- package/dist/ui/chart-view.js.map +1 -1
- package/dist/ui/chart.js.map +1 -1
- package/dist/ui/checkbox.js.map +1 -1
- package/dist/ui/collapsible.js.map +1 -1
- package/dist/ui/color-picker.js.map +1 -1
- package/dist/ui/combobox.js.map +1 -1
- package/dist/ui/confirm-dialog.js.map +1 -1
- package/dist/ui/copyable-text.js.map +1 -1
- package/dist/ui/dialog.js.map +1 -1
- package/dist/ui/dropdown-menu.js.map +1 -1
- package/dist/ui/floating-panel.js.map +1 -1
- package/dist/ui/fragment.js +2 -1
- package/dist/ui/fragment.js.map +1 -1
- package/dist/ui/heading.js.map +1 -1
- package/dist/ui/icon-button.js.map +1 -1
- package/dist/ui/icon.js.map +1 -1
- package/dist/ui/image-preview.js.map +1 -1
- package/dist/ui/info-tooltip.js.map +1 -1
- package/dist/ui/input-error.js.map +1 -1
- package/dist/ui/input-otp.js.map +1 -1
- package/dist/ui/link.js.map +1 -1
- package/dist/ui/modal.js.map +1 -1
- package/dist/ui/password-input.js.map +1 -1
- package/dist/ui/progress.js.map +1 -1
- package/dist/ui/section.js +4 -2
- package/dist/ui/section.js.map +1 -1
- package/dist/ui/segmented-control.js.map +1 -1
- package/dist/ui/segmented-pills.js.map +1 -1
- package/dist/ui/tabs.js.map +1 -1
- package/dist/ui/text.js.map +1 -1
- package/dist/ui/tooltip.js.map +1 -1
- package/dist/vite-typescript-refresh.js.map +1 -1
- package/dist/vite.js.map +1 -1
- package/dist-standalone/chunks/chart-view-ZCTbih8A.js +59 -0
- package/dist-standalone/chunks/date-picker-field-CCnkf4SC.js +1 -0
- package/dist-standalone/chunks/dist-CK9gk4iB.js +1 -0
- package/dist-standalone/chunks/dist-DqlcM-vw.js +2 -0
- package/dist-standalone/chunks/icon-button-DcP25cn4.js +41 -0
- package/dist-standalone/chunks/instance-t58Awajh.js +1 -0
- package/dist-standalone/chunks/jsx-runtime-CN5ucv1d.js +1 -0
- package/dist-standalone/chunks/locale-CgtcxthU.js +1 -0
- package/dist-standalone/chunks/materialize-CWqnSEHR.js +1 -0
- package/dist-standalone/chunks/{notifications-echo-DsfNc4Ge.js → notifications-echo-uU87iJAV.js} +1 -1
- package/dist-standalone/chunks/number-BvVKQqOG.js +1 -0
- package/dist-standalone/chunks/page-props-B4GewgLI.js +1 -0
- package/dist-standalone/chunks/react-SIfiwpqq.js +1 -0
- package/dist-standalone/chunks/rich-editor-field-BtbU5XFS.js +158 -0
- package/dist-standalone/chunks/shared-props-B_wT1GcG.js +1 -0
- package/dist-standalone/chunks/subscriptions-BdHDnVA3.js +1 -0
- package/dist-standalone/chunks/time-picker-DSs9fDWe.js +1 -0
- package/dist-standalone/chunks/timezone-BukvzD0d.js +1 -0
- package/dist-standalone/chunks/translatable-DLw4h3x8.js +1 -0
- package/dist-standalone/chunks/use-effect-dispatcher-CiWYRvsF.js +97 -0
- package/dist-standalone/chunks/{with-selector-BwDoCrJJ.js → with-selector-zFUY-tQj.js} +1 -1
- package/dist-standalone/lattice.css +2 -2
- package/dist-standalone/lattice.js +4 -48
- package/dist-standalone/manifest.json +24 -15
- package/dist-standalone/sprite.svg +1 -1
- package/package.json +6 -6
- package/resources/icons/align-center.svg +1 -1
- package/resources/icons/align-justify.svg +1 -1
- package/resources/icons/align-left.svg +1 -1
- package/resources/icons/align-right.svg +1 -1
- package/resources/icons/arrow-down.svg +1 -1
- package/resources/icons/arrow-up.svg +1 -1
- package/resources/icons/bell.svg +1 -1
- package/resources/icons/bold.svg +1 -1
- package/resources/icons/calendar.svg +5 -5
- package/resources/icons/check.svg +1 -1
- package/resources/icons/chevron-down.svg +1 -1
- package/resources/icons/chevron-left.svg +1 -1
- package/resources/icons/chevron-right.svg +1 -1
- package/resources/icons/chevrons-up-down.svg +1 -1
- package/resources/icons/circle-alert.svg +1 -1
- package/resources/icons/circle-check.svg +1 -1
- package/resources/icons/circle-help.svg +1 -1
- package/resources/icons/circle-x.svg +1 -1
- package/resources/icons/clock.svg +1 -1
- package/resources/icons/code-xml.svg +1 -1
- package/resources/icons/code.svg +1 -1
- package/resources/icons/columns-3.svg +1 -1
- package/resources/icons/copy.svg +1 -1
- package/resources/icons/external-link.svg +1 -1
- package/resources/icons/eye-off.svg +1 -1
- package/resources/icons/eye.svg +1 -1
- package/resources/icons/filter.svg +1 -1
- package/resources/icons/heading-1.svg +1 -1
- package/resources/icons/heading-2.svg +1 -1
- package/resources/icons/heading-3.svg +1 -1
- package/resources/icons/heading.svg +1 -1
- package/resources/icons/highlighter.svg +1 -1
- package/resources/icons/info.svg +1 -1
- package/resources/icons/italic.svg +1 -1
- package/resources/icons/layout-dashboard.svg +1 -1
- package/resources/icons/link.svg +1 -1
- package/resources/icons/list-ordered.svg +1 -1
- package/resources/icons/list.svg +1 -1
- package/resources/icons/loader-2.svg +1 -1
- package/resources/icons/minus.svg +1 -1
- package/resources/icons/more-horizontal.svg +1 -1
- package/resources/icons/panel-left.svg +1 -1
- package/resources/icons/pencil-line.svg +1 -1
- package/resources/icons/pencil.svg +1 -1
- package/resources/icons/plus.svg +1 -1
- package/resources/icons/quote.svg +1 -1
- package/resources/icons/rotate-ccw.svg +1 -1
- package/resources/icons/rows-3.svg +1 -1
- package/resources/icons/search.svg +1 -1
- package/resources/icons/send.svg +1 -1
- package/resources/icons/settings.svg +1 -1
- package/resources/icons/smile.svg +1 -1
- package/resources/icons/strikethrough.svg +1 -1
- package/resources/icons/table.svg +1 -1
- package/resources/icons/trash-2.svg +1 -1
- package/resources/icons/underline.svg +1 -1
- package/resources/icons/x.svg +1 -1
- package/dist-standalone/chunks/chart-view-CpYsLvzR.js +0 -36
- package/dist-standalone/chunks/date-picker-field-lX8N40Ii.js +0 -1
- package/dist-standalone/chunks/dist-PaT1gYi_.js +0 -2
- package/dist-standalone/chunks/jsx-runtime-Drll_-DD.js +0 -1
- package/dist-standalone/chunks/locale-CXqr7LPR.js +0 -1
- package/dist-standalone/chunks/page-props-ClpVjpOc.js +0 -1
- package/dist-standalone/chunks/react-B8IZ02wI.js +0 -1
- package/dist-standalone/chunks/rich-editor-field-C71csVJv.js +0 -154
- package/dist-standalone/chunks/subscriptions-B1STcwPE.js +0 -1
- package/dist-standalone/chunks/use-effect-dispatcher-I8So0l_I.js +0 -97
|
@@ -149,32 +149,33 @@ function ActionFormBody({ cancelLabel, componentRef, endpoint, extraData, fieldL
|
|
|
149
149
|
onSuccess,
|
|
150
150
|
request
|
|
151
151
|
]);
|
|
152
|
+
const context = useMemo(() => ({
|
|
153
|
+
action: endpoint,
|
|
154
|
+
clearErrors,
|
|
155
|
+
componentRef,
|
|
156
|
+
errors,
|
|
157
|
+
fieldLabels,
|
|
158
|
+
precognitive,
|
|
159
|
+
processing,
|
|
160
|
+
touch,
|
|
161
|
+
validate,
|
|
162
|
+
validateFields,
|
|
163
|
+
validating
|
|
164
|
+
}), [
|
|
165
|
+
clearErrors,
|
|
166
|
+
componentRef,
|
|
167
|
+
endpoint,
|
|
168
|
+
errors,
|
|
169
|
+
fieldLabels,
|
|
170
|
+
precognitive,
|
|
171
|
+
processing,
|
|
172
|
+
touch,
|
|
173
|
+
validate,
|
|
174
|
+
validateFields,
|
|
175
|
+
validating
|
|
176
|
+
]);
|
|
152
177
|
return /* @__PURE__ */ jsx(FormProvider, {
|
|
153
|
-
value:
|
|
154
|
-
action: endpoint,
|
|
155
|
-
clearErrors,
|
|
156
|
-
componentRef,
|
|
157
|
-
errors,
|
|
158
|
-
fieldLabels,
|
|
159
|
-
precognitive,
|
|
160
|
-
processing,
|
|
161
|
-
touch,
|
|
162
|
-
validate,
|
|
163
|
-
validateFields,
|
|
164
|
-
validating
|
|
165
|
-
}), [
|
|
166
|
-
clearErrors,
|
|
167
|
-
componentRef,
|
|
168
|
-
endpoint,
|
|
169
|
-
errors,
|
|
170
|
-
fieldLabels,
|
|
171
|
-
precognitive,
|
|
172
|
-
processing,
|
|
173
|
-
touch,
|
|
174
|
-
validate,
|
|
175
|
-
validateFields,
|
|
176
|
-
validating
|
|
177
|
-
]),
|
|
178
|
+
value: context,
|
|
178
179
|
children: /* @__PURE__ */ jsxs("form", {
|
|
179
180
|
className: "flex flex-col gap-6",
|
|
180
181
|
onSubmit: (event) => {
|
|
@@ -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({ _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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"}
|
|
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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"}
|
|
@@ -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 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;
|
|
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;EACxD,UAAA,qBAAC,cAAD,EAAA,UAAA,CACE,oBAAC,qBAAD;GAAqB,SAAA;GACnB,UAAA,oBAAC,QAAD;IACE,cAAY;IACZ,WAAU;IACV,aAAW,aAAa,IAAI;IAC5B,MAAK;IACL,MAAK;IACL,UAAS;IAET,UAAA,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;GAEZ,UAAA,oBAAC,oBAAD,EAAqB,SAA6B,CAAA;EAC/B,CAAA,CACT,EAAA,CAAA;CACX,CAAA;AAET"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"action-trigger-provider.js","names":[],"sources":["../../../resources/js/action/components/action-trigger-provider.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { Node } from \"@lattice-php/lattice/core/types\";\nimport { ActionTriggerProvider, type TriggerState } from \"@lattice-php/lattice/ui/click-behavior\";\nimport { useAction } from \"@lattice-php/lattice/action/hooks/use-action\";\n\nexport function ActionTrigger({\n action,\n children,\n}: {\n action: Node<\"action\" | \"action.bulk\">;\n children: (trigger: TriggerState) => ReactNode;\n}) {\n const { processing, requestSubmit, overlays } = useAction(action);\n\n return (\n <>\n {children({ onClick: requestSubmit, processing })}\n {overlays}\n </>\n );\n}\n\nexport function ActionInteractionProvider({ children }: { children: ReactNode }) {\n return (\n <ActionTriggerProvider\n render={(props) => <ActionTrigger action={props.action}>{props.children}</ActionTrigger>}\n >\n {children}\n </ActionTriggerProvider>\n );\n}\n"],"mappings":";;;;AAKA,SAAgB,cAAc,EAC5B,QACA,YAIC;CACD,MAAM,EAAE,YAAY,eAAe,aAAa,UAAU,MAAM;CAEhE,OACE,qBAAA,UAAA,EAAA,UAAA,CACG,SAAS;EAAE,SAAS;EAAe;CAAW,CAAC,GAC/C,QACD,EAAA,CAAA;AAEN;AAEA,SAAgB,0BAA0B,EAAE,YAAqC;CAC/E,OACE,oBAAC,uBAAD;EACE,SAAS,UAAU,oBAAC,eAAD;GAAe,QAAQ,MAAM;
|
|
1
|
+
{"version":3,"file":"action-trigger-provider.js","names":[],"sources":["../../../resources/js/action/components/action-trigger-provider.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport type { Node } from \"@lattice-php/lattice/core/types\";\nimport { ActionTriggerProvider, type TriggerState } from \"@lattice-php/lattice/ui/click-behavior\";\nimport { useAction } from \"@lattice-php/lattice/action/hooks/use-action\";\n\nexport function ActionTrigger({\n action,\n children,\n}: {\n action: Node<\"action\" | \"action.bulk\">;\n children: (trigger: TriggerState) => ReactNode;\n}) {\n const { processing, requestSubmit, overlays } = useAction(action);\n\n return (\n <>\n {children({ onClick: requestSubmit, processing })}\n {overlays}\n </>\n );\n}\n\nexport function ActionInteractionProvider({ children }: { children: ReactNode }) {\n return (\n <ActionTriggerProvider\n render={(props) => <ActionTrigger action={props.action}>{props.children}</ActionTrigger>}\n >\n {children}\n </ActionTriggerProvider>\n );\n}\n"],"mappings":";;;;AAKA,SAAgB,cAAc,EAC5B,QACA,YAIC;CACD,MAAM,EAAE,YAAY,eAAe,aAAa,UAAU,MAAM;CAEhE,OACE,qBAAA,UAAA,EAAA,UAAA,CACG,SAAS;EAAE,SAAS;EAAe;CAAW,CAAC,GAC/C,QACD,EAAA,CAAA;AAEN;AAEA,SAAgB,0BAA0B,EAAE,YAAqC;CAC/E,OACE,oBAAC,uBAAD;EACE,SAAS,UAAU,oBAAC,eAAD;GAAe,QAAQ,MAAM;GAAS,UAAA,MAAM;EAAwB,CAAA;EAEtF;CACoB,CAAA;AAE3B"}
|
|
@@ -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, 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;
|
|
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;EAR/B,UAAA,CAUG,aACC,oBAAC,SAAD,EAAS,WAAW,aAAa,oBAAoB,KAAA,EAAY,CAAA,IAEjE,QACE,oBAAC,cAAD;GACE,WAAW,aAAa,oBAAoB;GACtC;EACP,CAAA,GAGJ,KACK;CAEP,CAAA,GAAA,QACD,EAAA,CAAA;AAEN"}
|
|
@@ -10,7 +10,8 @@ import { dispatchActionError, getActionEffects } from "../../effects/dispatch.js
|
|
|
10
10
|
async function runAction(request, dispatch) {
|
|
11
11
|
try {
|
|
12
12
|
const response = await request();
|
|
13
|
-
|
|
13
|
+
const body = await response.json().catch(() => ({}));
|
|
14
|
+
dispatch(getActionEffects(body.effects));
|
|
14
15
|
return response.ok;
|
|
15
16
|
} catch (error) {
|
|
16
17
|
dispatchActionError(error);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-action.js","names":[],"sources":["../../../resources/js/action/lib/run-action.ts"],"sourcesContent":["import {\n type ActionEffect,\n type ActionResponse,\n dispatchActionError,\n getActionEffects,\n} from \"@lattice-php/lattice/effects/dispatch\";\n\n/**\n * Runs an action request and dispatches the effects from its response body,\n * whether the action succeeded or was rejected (non-2xx). Returns whether the\n * response was ok so callers run their own post-success cleanup (closing a\n * dialog, reloading a table) only on success; a rejected action leaves the\n * dialog open. A thrown/network error routes through dispatchActionError.\n */\nexport async function runAction(\n request: () => Promise<Response>,\n dispatch: (effects: ActionEffect[]) => void,\n): Promise<boolean> {\n try {\n const response = await request();\n const body = (await response.json().catch(() => ({}))) as ActionResponse;\n dispatch(getActionEffects(body.effects));\n\n return response.ok;\n } catch (error) {\n dispatchActionError(error);\n\n return false;\n }\n}\n"],"mappings":";;;;;;;;;AAcA,eAAsB,UACpB,SACA,UACkB;CAClB,IAAI;EACF,MAAM,WAAW,MAAM,QAAQ;
|
|
1
|
+
{"version":3,"file":"run-action.js","names":[],"sources":["../../../resources/js/action/lib/run-action.ts"],"sourcesContent":["import {\n type ActionEffect,\n type ActionResponse,\n dispatchActionError,\n getActionEffects,\n} from \"@lattice-php/lattice/effects/dispatch\";\n\n/**\n * Runs an action request and dispatches the effects from its response body,\n * whether the action succeeded or was rejected (non-2xx). Returns whether the\n * response was ok so callers run their own post-success cleanup (closing a\n * dialog, reloading a table) only on success; a rejected action leaves the\n * dialog open. A thrown/network error routes through dispatchActionError.\n */\nexport async function runAction(\n request: () => Promise<Response>,\n dispatch: (effects: ActionEffect[]) => void,\n): Promise<boolean> {\n try {\n const response = await request();\n const body = (await response.json().catch(() => ({}))) as ActionResponse;\n dispatch(getActionEffects(body.effects));\n\n return response.ok;\n } catch (error) {\n dispatchActionError(error);\n\n return false;\n }\n}\n"],"mappings":";;;;;;;;;AAcA,eAAsB,UACpB,SACA,UACkB;CAClB,IAAI;EACF,MAAM,WAAW,MAAM,QAAQ;EAC/B,MAAM,OAAQ,MAAM,SAAS,KAAK,CAAC,CAAC,aAAa,CAAC,EAAE;EACpD,SAAS,iBAAiB,KAAK,OAAO,CAAC;EAEvC,OAAO,SAAS;CAClB,SAAS,OAAO;EACd,oBAAoB,KAAK;EAEzB,OAAO;CACT;AACF"}
|
|
@@ -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 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,
|
|
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,CAAC,CAAC;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,CAAC,EAAE,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"}
|
|
@@ -22,12 +22,14 @@ var ChatBox = ({ node }) => {
|
|
|
22
22
|
if (seededRef.current || !props.historyEndpoint) return;
|
|
23
23
|
seededRef.current = true;
|
|
24
24
|
if (props.remote) {
|
|
25
|
-
|
|
25
|
+
const payload = await remoteJson(props.historyEndpoint, { remote: props.remote });
|
|
26
|
+
setMessages(payload.messages);
|
|
26
27
|
return;
|
|
27
28
|
}
|
|
28
29
|
const response = await apiFetch(props.historyEndpoint, { throwOnError: false });
|
|
29
30
|
if (!response.ok) return;
|
|
30
|
-
|
|
31
|
+
const payload = await response.json();
|
|
32
|
+
setMessages(payload.messages);
|
|
31
33
|
}, [
|
|
32
34
|
props.historyEndpoint,
|
|
33
35
|
props.remote,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat-box.js","names":[],"sources":["../../../resources/js/chat/components/chat-box.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef } from \"react\";\nimport { apiFetch, remoteJson } from \"@lattice-php/lattice/core/api\";\nimport { testIdentity } from \"@lattice-php/lattice/core/test-id\";\nimport type { RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport type { ChatBox as ChatBoxProps } from \"@lattice-php/lattice/types/generated\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport {\n createRemoteNdjsonChatTransport,\n ndjsonChatTransport,\n} from \"@lattice-php/lattice/chat/lib/transport\";\nimport { useChat } from \"@lattice-php/lattice/chat/hooks/use-chat\";\nimport type { ChatMessage } from \"@lattice-php/lattice/chat/types\";\nimport { MessageList } from \"./message-list\";\nimport { PromptInput } from \"./prompt-input\";\n\ntype ChatHistoryResponse = { messages: ChatMessage[] };\n\nexport const ChatBox: RendererComponent<\"chat.box\"> = ({ node }) => {\n const props: ChatBoxProps = node.props;\n const { t } = useT(\"lattice\");\n const transport = useMemo(\n () => (props.remote ? createRemoteNdjsonChatTransport(props.remote) : ndjsonChatTransport),\n [props.remote],\n );\n const { messages, status, error, sendMessage, setMessages } = useChat({\n endpoint: props.streamEndpoint ?? \"\",\n transport,\n });\n\n const seededRef = useRef(false);\n\n const seedHistory = useCallback(async (): Promise<void> => {\n if (seededRef.current || !props.historyEndpoint) {\n return;\n }\n\n seededRef.current = true;\n\n if (props.remote) {\n const payload = await remoteJson<ChatHistoryResponse>(props.historyEndpoint, {\n remote: props.remote,\n });\n setMessages(payload.messages);\n\n return;\n }\n\n const response = await apiFetch(props.historyEndpoint, { throwOnError: false });\n\n if (!response.ok) {\n return;\n }\n\n const payload = (await response.json()) as ChatHistoryResponse;\n setMessages(payload.messages);\n }, [props.historyEndpoint, props.remote, setMessages]);\n\n useEffect(() => {\n void seedHistory().catch(() => {});\n }, [seedHistory]);\n\n const busy = status === \"submitted\" || status === \"streaming\";\n const disabled = busy || !props.streamEndpoint;\n\n return (\n <div\n className={cn(\n \"flex flex-col overflow-hidden border border-lt-border bg-lt-bg\",\n props.fill\n ? \"sticky top-0 h-full min-h-[28rem] w-full\"\n : \"h-[28rem] w-80 rounded-lt shadow-lt-lg\",\n )}\n data-test={testIdentity(\"chat-box\")}\n >\n <div className=\"flex items-center border-b border-lt-border px-3 py-2\">\n <span className=\"text-sm font-medium text-lt-fg\">\n {props.title ?? t(\"common.chat.title\", \"Chat\")}\n </span>\n </div>\n <div className=\"flex-1 overflow-y-auto\">\n <MessageList messages={messages} />\n </div>\n {error ? (\n <div className=\"border-t border-lt-danger/40 px-3 py-2 text-xs text-lt-danger\">{error}</div>\n ) : null}\n <PromptInput\n disabled={disabled}\n onSubmit={sendMessage}\n placeholder={props.placeholder ?? undefined}\n />\n </div>\n );\n};\n\nexport default ChatBox;\n"],"mappings":";;;;;;;;;;;AAkBA,IAAa,WAA0C,EAAE,WAAW;CAClE,MAAM,QAAsB,KAAK;CACjC,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,MAAM,YAAY,cACT,MAAM,SAAS,gCAAgC,MAAM,MAAM,IAAI,qBACtE,CAAC,MAAM,MAAM,CACf;CACA,MAAM,EAAE,UAAU,QAAQ,OAAO,aAAa,gBAAgB,QAAQ;EACpE,UAAU,MAAM,kBAAkB;EAClC;CACF,CAAC;CAED,MAAM,YAAY,OAAO,KAAK;CAE9B,MAAM,cAAc,YAAY,YAA2B;EACzD,IAAI,UAAU,WAAW,CAAC,MAAM,iBAC9B;EAGF,UAAU,UAAU;EAEpB,IAAI,MAAM,QAAQ;
|
|
1
|
+
{"version":3,"file":"chat-box.js","names":[],"sources":["../../../resources/js/chat/components/chat-box.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef } from \"react\";\nimport { apiFetch, remoteJson } from \"@lattice-php/lattice/core/api\";\nimport { testIdentity } from \"@lattice-php/lattice/core/test-id\";\nimport type { RendererComponent } from \"@lattice-php/lattice/core/types\";\nimport type { ChatBox as ChatBoxProps } from \"@lattice-php/lattice/types/generated\";\nimport { useT } from \"@lattice-php/lattice/i18n\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport {\n createRemoteNdjsonChatTransport,\n ndjsonChatTransport,\n} from \"@lattice-php/lattice/chat/lib/transport\";\nimport { useChat } from \"@lattice-php/lattice/chat/hooks/use-chat\";\nimport type { ChatMessage } from \"@lattice-php/lattice/chat/types\";\nimport { MessageList } from \"./message-list\";\nimport { PromptInput } from \"./prompt-input\";\n\ntype ChatHistoryResponse = { messages: ChatMessage[] };\n\nexport const ChatBox: RendererComponent<\"chat.box\"> = ({ node }) => {\n const props: ChatBoxProps = node.props;\n const { t } = useT(\"lattice\");\n const transport = useMemo(\n () => (props.remote ? createRemoteNdjsonChatTransport(props.remote) : ndjsonChatTransport),\n [props.remote],\n );\n const { messages, status, error, sendMessage, setMessages } = useChat({\n endpoint: props.streamEndpoint ?? \"\",\n transport,\n });\n\n const seededRef = useRef(false);\n\n const seedHistory = useCallback(async (): Promise<void> => {\n if (seededRef.current || !props.historyEndpoint) {\n return;\n }\n\n seededRef.current = true;\n\n if (props.remote) {\n const payload = await remoteJson<ChatHistoryResponse>(props.historyEndpoint, {\n remote: props.remote,\n });\n setMessages(payload.messages);\n\n return;\n }\n\n const response = await apiFetch(props.historyEndpoint, { throwOnError: false });\n\n if (!response.ok) {\n return;\n }\n\n const payload = (await response.json()) as ChatHistoryResponse;\n setMessages(payload.messages);\n }, [props.historyEndpoint, props.remote, setMessages]);\n\n useEffect(() => {\n void seedHistory().catch(() => {});\n }, [seedHistory]);\n\n const busy = status === \"submitted\" || status === \"streaming\";\n const disabled = busy || !props.streamEndpoint;\n\n return (\n <div\n className={cn(\n \"flex flex-col overflow-hidden border border-lt-border bg-lt-bg\",\n props.fill\n ? \"sticky top-0 h-full min-h-[28rem] w-full\"\n : \"h-[28rem] w-80 rounded-lt shadow-lt-lg\",\n )}\n data-test={testIdentity(\"chat-box\")}\n >\n <div className=\"flex items-center border-b border-lt-border px-3 py-2\">\n <span className=\"text-sm font-medium text-lt-fg\">\n {props.title ?? t(\"common.chat.title\", \"Chat\")}\n </span>\n </div>\n <div className=\"flex-1 overflow-y-auto\">\n <MessageList messages={messages} />\n </div>\n {error ? (\n <div className=\"border-t border-lt-danger/40 px-3 py-2 text-xs text-lt-danger\">{error}</div>\n ) : null}\n <PromptInput\n disabled={disabled}\n onSubmit={sendMessage}\n placeholder={props.placeholder ?? undefined}\n />\n </div>\n );\n};\n\nexport default ChatBox;\n"],"mappings":";;;;;;;;;;;AAkBA,IAAa,WAA0C,EAAE,WAAW;CAClE,MAAM,QAAsB,KAAK;CACjC,MAAM,EAAE,MAAM,KAAK,SAAS;CAC5B,MAAM,YAAY,cACT,MAAM,SAAS,gCAAgC,MAAM,MAAM,IAAI,qBACtE,CAAC,MAAM,MAAM,CACf;CACA,MAAM,EAAE,UAAU,QAAQ,OAAO,aAAa,gBAAgB,QAAQ;EACpE,UAAU,MAAM,kBAAkB;EAClC;CACF,CAAC;CAED,MAAM,YAAY,OAAO,KAAK;CAE9B,MAAM,cAAc,YAAY,YAA2B;EACzD,IAAI,UAAU,WAAW,CAAC,MAAM,iBAC9B;EAGF,UAAU,UAAU;EAEpB,IAAI,MAAM,QAAQ;GAChB,MAAM,UAAU,MAAM,WAAgC,MAAM,iBAAiB,EAC3E,QAAQ,MAAM,OAChB,CAAC;GACD,YAAY,QAAQ,QAAQ;GAE5B;EACF;EAEA,MAAM,WAAW,MAAM,SAAS,MAAM,iBAAiB,EAAE,cAAc,MAAM,CAAC;EAE9E,IAAI,CAAC,SAAS,IACZ;EAGF,MAAM,UAAW,MAAM,SAAS,KAAK;EACrC,YAAY,QAAQ,QAAQ;CAC9B,GAAG;EAAC,MAAM;EAAiB,MAAM;EAAQ;CAAW,CAAC;CAErD,gBAAgB;EACd,YAAiB,CAAC,CAAC,YAAY,CAAC,CAAC;CACnC,GAAG,CAAC,WAAW,CAAC;CAGhB,MAAM,WADO,WAAW,eAAe,WAAW,eACzB,CAAC,MAAM;CAEhC,OACE,qBAAC,OAAD;EACE,WAAW,GACT,kEACA,MAAM,OACF,6CACA,wCACN;EACA,aAAW,aAAa,UAAU;EAPpC,UAAA;GASE,oBAAC,OAAD;IAAK,WAAU;IACb,UAAA,oBAAC,QAAD;KAAM,WAAU;KACb,UAAA,MAAM,SAAS,EAAE,qBAAqB,MAAM;IACzC,CAAA;GACH,CAAA;GACL,oBAAC,OAAD;IAAK,WAAU;IACb,UAAA,oBAAC,aAAD,EAAuB,SAAW,CAAA;GAC/B,CAAA;GACJ,QACC,oBAAC,OAAD;IAAK,WAAU;IAAiE,UAAA;GAAW,CAAA,IACzF;GACJ,oBAAC,aAAD;IACY;IACV,UAAU;IACV,aAAa,MAAM,eAAe,KAAA;GACnC,CAAA;EACE;;AAET"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message-list.js","names":[],"sources":["../../../resources/js/chat/components/message-list.tsx"],"sourcesContent":["import { useEffect, useRef } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { testIdentity } from \"@lattice-php/lattice/core/test-id\";\nimport type { ChatMessage } from \"@lattice-php/lattice/chat/types\";\nimport { Message } from \"./message\";\n\nexport function MessageList({ messages }: { messages: ChatMessage[] }): ReactNode {\n const bottomRef = useRef<HTMLDivElement>(null);\n const lastMessage = messages.at(-1);\n const lastPartLength =\n lastMessage?.parts.at(-1) !== undefined ? JSON.stringify(lastMessage.parts.at(-1)).length : 0;\n\n useEffect(() => {\n if (bottomRef.current && typeof bottomRef.current.scrollIntoView === \"function\") {\n bottomRef.current.scrollIntoView({ behavior: \"smooth\" });\n }\n }, [messages.length, lastPartLength]);\n\n return (\n <div\n className=\"flex flex-col gap-2 overflow-y-auto p-3\"\n data-test={testIdentity(\"chat-messages\")}\n >\n {messages.map((message) => (\n <Message key={message.id} message={message} />\n ))}\n <div ref={bottomRef} />\n </div>\n );\n}\n"],"mappings":";;;;;AAMA,SAAgB,YAAY,EAAE,YAAoD;CAChF,MAAM,YAAY,OAAuB,IAAI;CAC7C,MAAM,cAAc,SAAS,GAAG,EAAE;CAClC,MAAM,iBACJ,aAAa,MAAM,GAAG,EAAE,MAAM,KAAA,IAAY,KAAK,UAAU,YAAY,MAAM,GAAG,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"message-list.js","names":[],"sources":["../../../resources/js/chat/components/message-list.tsx"],"sourcesContent":["import { useEffect, useRef } from \"react\";\nimport type { ReactNode } from \"react\";\nimport { testIdentity } from \"@lattice-php/lattice/core/test-id\";\nimport type { ChatMessage } from \"@lattice-php/lattice/chat/types\";\nimport { Message } from \"./message\";\n\nexport function MessageList({ messages }: { messages: ChatMessage[] }): ReactNode {\n const bottomRef = useRef<HTMLDivElement>(null);\n const lastMessage = messages.at(-1);\n const lastPartLength =\n lastMessage?.parts.at(-1) !== undefined ? JSON.stringify(lastMessage.parts.at(-1)).length : 0;\n\n useEffect(() => {\n if (bottomRef.current && typeof bottomRef.current.scrollIntoView === \"function\") {\n bottomRef.current.scrollIntoView({ behavior: \"smooth\" });\n }\n }, [messages.length, lastPartLength]);\n\n return (\n <div\n className=\"flex flex-col gap-2 overflow-y-auto p-3\"\n data-test={testIdentity(\"chat-messages\")}\n >\n {messages.map((message) => (\n <Message key={message.id} message={message} />\n ))}\n <div ref={bottomRef} />\n </div>\n );\n}\n"],"mappings":";;;;;AAMA,SAAgB,YAAY,EAAE,YAAoD;CAChF,MAAM,YAAY,OAAuB,IAAI;CAC7C,MAAM,cAAc,SAAS,GAAG,EAAE;CAClC,MAAM,iBACJ,aAAa,MAAM,GAAG,EAAE,MAAM,KAAA,IAAY,KAAK,UAAU,YAAY,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS;CAE9F,gBAAgB;EACd,IAAI,UAAU,WAAW,OAAO,UAAU,QAAQ,mBAAmB,YACnE,UAAU,QAAQ,eAAe,EAAE,UAAU,SAAS,CAAC;CAE3D,GAAG,CAAC,SAAS,QAAQ,cAAc,CAAC;CAEpC,OACE,qBAAC,OAAD;EACE,WAAU;EACV,aAAW,aAAa,eAAe;EAFzC,UAAA,CAIG,SAAS,KAAK,YACb,oBAAC,SAAD,EAAmC,QAAU,GAA/B,QAAQ,EAAuB,CAC9C,GACD,oBAAC,OAAD,EAAK,KAAK,UAAY,CAAA,CACnB;;AAET"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"message.js","names":[],"sources":["../../../resources/js/chat/components/message.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { testIdentity } from \"@lattice-php/lattice/core/test-id\";\nimport { RenderNode } from \"@lattice-php/lattice/core/renderer\";\nimport type { ChatMessage } from \"@lattice-php/lattice/chat/types\";\n\nexport function Message({ message }: { message: ChatMessage }): ReactNode {\n const isUser = message.role === \"user\";\n\n return (\n <div\n className={cn(\"flex flex-col gap-1\", isUser ? \"items-end\" : \"items-start\")}\n data-test={testIdentity(`chat-message-${message.role}`)}\n >\n <div\n className={cn(\n \"max-w-[80%] rounded-lt px-3 py-2 text-sm\",\n isUser ? \"bg-lt-primary text-lt-primary-fg\" : \"bg-lt-muted text-lt-fg\",\n )}\n >\n {message.parts.map((part, index) => (\n <RenderNode key={part.key ?? index} node={part} />\n ))}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;AAMA,SAAgB,QAAQ,EAAE,WAAgD;CACxE,MAAM,SAAS,QAAQ,SAAS;CAEhC,OACE,oBAAC,OAAD;EACE,WAAW,GAAG,uBAAuB,SAAS,cAAc,aAAa;EACzE,aAAW,aAAa,gBAAgB,QAAQ,MAAM;
|
|
1
|
+
{"version":3,"file":"message.js","names":[],"sources":["../../../resources/js/chat/components/message.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\nimport { cn } from \"@lattice-php/lattice/lib/utils\";\nimport { testIdentity } from \"@lattice-php/lattice/core/test-id\";\nimport { RenderNode } from \"@lattice-php/lattice/core/renderer\";\nimport type { ChatMessage } from \"@lattice-php/lattice/chat/types\";\n\nexport function Message({ message }: { message: ChatMessage }): ReactNode {\n const isUser = message.role === \"user\";\n\n return (\n <div\n className={cn(\"flex flex-col gap-1\", isUser ? \"items-end\" : \"items-start\")}\n data-test={testIdentity(`chat-message-${message.role}`)}\n >\n <div\n className={cn(\n \"max-w-[80%] rounded-lt px-3 py-2 text-sm\",\n isUser ? \"bg-lt-primary text-lt-primary-fg\" : \"bg-lt-muted text-lt-fg\",\n )}\n >\n {message.parts.map((part, index) => (\n <RenderNode key={part.key ?? index} node={part} />\n ))}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;AAMA,SAAgB,QAAQ,EAAE,WAAgD;CACxE,MAAM,SAAS,QAAQ,SAAS;CAEhC,OACE,oBAAC,OAAD;EACE,WAAW,GAAG,uBAAuB,SAAS,cAAc,aAAa;EACzE,aAAW,aAAa,gBAAgB,QAAQ,MAAM;EAEtD,UAAA,oBAAC,OAAD;GACE,WAAW,GACT,4CACA,SAAS,qCAAqC,wBAChD;GAEC,UAAA,QAAQ,MAAM,KAAK,MAAM,UACxB,oBAAC,YAAD,EAAoC,MAAM,KAAO,GAAhC,KAAK,OAAO,KAAoB,CAClD;EACE,CAAA;CACF,CAAA;AAET"}
|
|
@@ -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-[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;
|
|
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;EAAf,UAAA,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;GAEJ,UAAA,EAAE,oBAAoB,MAAM;EACvB,CAAA,CACL;;AAET"}
|
|
@@ -138,7 +138,8 @@ function useChat({ endpoint, transport = ndjsonChatTransport, initialMessages =
|
|
|
138
138
|
const userText = lastUserTextRef.current;
|
|
139
139
|
if (userText === null) return;
|
|
140
140
|
const current = messagesRef.current;
|
|
141
|
-
|
|
141
|
+
const history = lastIsAssistant(current) ? current.slice(0, -2) : current;
|
|
142
|
+
runTurn(history, userText);
|
|
142
143
|
}, [runTurn]);
|
|
143
144
|
useEffect(() => {
|
|
144
145
|
return () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-chat.js","names":[],"sources":["../../../resources/js/chat/hooks/use-chat.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { ndjsonChatTransport } from \"@lattice-php/lattice/chat/lib/transport\";\nimport type {\n ChatFrame,\n ChatMessage,\n ChatStatus,\n ChatTransport,\n UseChatReturn,\n} from \"@lattice-php/lattice/chat/types\";\n\nexport type UseChatOptions = {\n endpoint: string;\n transport?: ChatTransport;\n initialMessages?: ChatMessage[];\n generateId?: () => string;\n};\n\nlet idCounter = 0;\n\nfunction defaultGenerateId(): string {\n idCounter += 1;\n\n return `chat-${idCounter}`;\n}\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof DOMException && error.name === \"AbortError\";\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction lastIsAssistant(messages: ChatMessage[]): boolean {\n const last = messages.at(-1);\n\n return last !== undefined && last.role === \"assistant\";\n}\n\n/**\n * Pure reducer for a single streamed frame: a `text` frame appends to the open\n * assistant text part (opening one if the last part is not text); a `part` frame\n * pushes a complete part, which closes the open text part. Operates immutably on\n * the last assistant message.\n */\nexport function foldFrame(messages: ChatMessage[], frame: ChatFrame): ChatMessage[] {\n if (!lastIsAssistant(messages)) {\n return messages;\n }\n\n const index = messages.length - 1;\n const target = messages[index];\n\n if (frame.type === \"text\") {\n const parts = [...target.parts];\n const openIndex = parts.length - 1;\n const open = parts[openIndex];\n\n if (open !== undefined && open.type === \"chat.part.text\") {\n const openText = (open.props as { text: string }).text;\n parts[openIndex] = { type: \"chat.part.text\", props: { text: `${openText}${frame.value}` } };\n } else {\n parts.push({ type: \"chat.part.text\", props: { text: frame.value } });\n }\n\n const next = [...messages];\n next[index] = { ...target, parts };\n\n return next;\n }\n\n if (frame.type === \"part\") {\n const next = [...messages];\n next[index] = { ...target, parts: [...target.parts, frame.part] };\n\n return next;\n }\n\n return messages;\n}\n\nexport function useChat({\n endpoint,\n transport = ndjsonChatTransport,\n initialMessages = [],\n generateId = defaultGenerateId,\n}: UseChatOptions): UseChatReturn {\n const [messages, setMessagesState] = useState<ChatMessage[]>(initialMessages);\n const [status, setStatus] = useState<ChatStatus>(\"idle\");\n const [error, setError] = useState<string | null>(null);\n\n const abortRef = useRef<AbortController | null>(null);\n const lastUserTextRef = useRef<string | null>(null);\n const messagesRef = useRef<ChatMessage[]>(messages);\n messagesRef.current = messages;\n\n const commitMessages = useCallback((next: ChatMessage[]): void => {\n messagesRef.current = next;\n setMessagesState(next);\n }, []);\n\n const runTurn = useCallback(\n async (history: ChatMessage[], userText: string): Promise<void> => {\n abortRef.current?.abort();\n const controller = new AbortController();\n abortRef.current = controller;\n\n setError(null);\n setStatus(\"submitted\");\n\n const userMessage: ChatMessage = {\n id: generateId(),\n role: \"user\",\n parts: [{ type: \"chat.part.text\", props: { text: userText } }],\n };\n const assistantMessage: ChatMessage = { id: generateId(), role: \"assistant\", parts: [] };\n commitMessages([...history, userMessage, assistantMessage]);\n setStatus(\"streaming\");\n\n try {\n for await (const frame of transport({\n url: endpoint,\n body: { message: userText },\n signal: controller.signal,\n })) {\n if (frame.type === \"done\") {\n setStatus(\"idle\");\n continue;\n }\n\n if (frame.type === \"error\") {\n setError(frame.message ?? \"Chat failed\");\n setStatus(\"error\");\n continue;\n }\n\n commitMessages(foldFrame(messagesRef.current, frame));\n }\n\n setStatus((current) =>\n current === \"streaming\" || current === \"submitted\" ? \"idle\" : current,\n );\n } catch (caught) {\n if (isAbortError(caught)) {\n setStatus(\"idle\");\n\n return;\n }\n\n setError(errorMessage(caught));\n setStatus(\"error\");\n }\n },\n [commitMessages, endpoint, generateId, transport],\n );\n\n const sendMessage = useCallback(\n (text: string): void => {\n const trimmed = text.trim();\n if (trimmed === \"\") {\n return;\n }\n\n lastUserTextRef.current = trimmed;\n void runTurn(messagesRef.current, trimmed);\n },\n [runTurn],\n );\n\n const stop = useCallback((): void => {\n abortRef.current?.abort();\n setStatus(\"idle\");\n }, []);\n\n const regenerate = useCallback((): void => {\n const userText = lastUserTextRef.current;\n if (userText === null) {\n return;\n }\n\n const current = messagesRef.current;\n const history = lastIsAssistant(current) ? current.slice(0, -2) : current;\n void runTurn(history, userText);\n }, [runTurn]);\n\n useEffect(() => {\n return () => {\n abortRef.current?.abort();\n };\n }, []);\n\n return { messages, status, error, sendMessage, setMessages: commitMessages, stop, regenerate };\n}\n"],"mappings":";;;AAiBA,IAAI,YAAY;AAEhB,SAAS,oBAA4B;CACnC,aAAa;CAEb,OAAO,QAAQ;AACjB;AAEA,SAAS,aAAa,OAAyB;CAC7C,OAAO,iBAAiB,gBAAgB,MAAM,SAAS;AACzD;AAEA,SAAS,aAAa,OAAwB;CAC5C,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC9D;AAEA,SAAS,gBAAgB,UAAkC;CACzD,MAAM,OAAO,SAAS,GAAG,EAAE;CAE3B,OAAO,SAAS,KAAA,KAAa,KAAK,SAAS;AAC7C;;;;;;;AAQA,SAAgB,UAAU,UAAyB,OAAiC;CAClF,IAAI,CAAC,gBAAgB,QAAQ,GAC3B,OAAO;CAGT,MAAM,QAAQ,SAAS,SAAS;CAChC,MAAM,SAAS,SAAS;CAExB,IAAI,MAAM,SAAS,QAAQ;EACzB,MAAM,QAAQ,CAAC,GAAG,OAAO,KAAK;EAC9B,MAAM,YAAY,MAAM,SAAS;EACjC,MAAM,OAAO,MAAM;EAEnB,IAAI,SAAS,KAAA,KAAa,KAAK,SAAS,kBAEtC,MAAM,aAAa;GAAE,MAAM;GAAkB,OAAO,EAAE,MAAM,GAD1C,KAAK,MAA2B,OACwB,MAAM,QAAQ;EAAE;OAE1F,MAAM,KAAK;GAAE,MAAM;GAAkB,OAAO,EAAE,MAAM,MAAM,MAAM;EAAE,CAAC;EAGrE,MAAM,OAAO,CAAC,GAAG,QAAQ;EACzB,KAAK,SAAS;GAAE,GAAG;GAAQ;EAAM;EAEjC,OAAO;CACT;CAEA,IAAI,MAAM,SAAS,QAAQ;EACzB,MAAM,OAAO,CAAC,GAAG,QAAQ;EACzB,KAAK,SAAS;GAAE,GAAG;GAAQ,OAAO,CAAC,GAAG,OAAO,OAAO,MAAM,IAAI;EAAE;EAEhE,OAAO;CACT;CAEA,OAAO;AACT;AAEA,SAAgB,QAAQ,EACtB,UACA,YAAY,qBACZ,kBAAkB,CAAC,GACnB,aAAa,qBACmB;CAChC,MAAM,CAAC,UAAU,oBAAoB,SAAwB,eAAe;CAC5E,MAAM,CAAC,QAAQ,aAAa,SAAqB,MAAM;CACvD,MAAM,CAAC,OAAO,YAAY,SAAwB,IAAI;CAEtD,MAAM,WAAW,OAA+B,IAAI;CACpD,MAAM,kBAAkB,OAAsB,IAAI;CAClD,MAAM,cAAc,OAAsB,QAAQ;CAClD,YAAY,UAAU;CAEtB,MAAM,iBAAiB,aAAa,SAA8B;EAChE,YAAY,UAAU;EACtB,iBAAiB,IAAI;CACvB,GAAG,CAAC,CAAC;CAEL,MAAM,UAAU,YACd,OAAO,SAAwB,aAAoC;EACjE,SAAS,SAAS,MAAM;EACxB,MAAM,aAAa,IAAI,gBAAgB;EACvC,SAAS,UAAU;EAEnB,SAAS,IAAI;EACb,UAAU,WAAW;EAErB,MAAM,cAA2B;GAC/B,IAAI,WAAW;GACf,MAAM;GACN,OAAO,CAAC;IAAE,MAAM;IAAkB,OAAO,EAAE,MAAM,SAAS;GAAE,CAAC;EAC/D;EACA,MAAM,mBAAgC;GAAE,IAAI,WAAW;GAAG,MAAM;GAAa,OAAO,CAAC;EAAE;EACvF,eAAe;GAAC,GAAG;GAAS;GAAa;EAAgB,CAAC;EAC1D,UAAU,WAAW;EAErB,IAAI;GACF,WAAW,MAAM,SAAS,UAAU;IAClC,KAAK;IACL,MAAM,EAAE,SAAS,SAAS;IAC1B,QAAQ,WAAW;GACrB,CAAC,GAAG;IACF,IAAI,MAAM,SAAS,QAAQ;KACzB,UAAU,MAAM;KAChB;IACF;IAEA,IAAI,MAAM,SAAS,SAAS;KAC1B,SAAS,MAAM,WAAW,aAAa;KACvC,UAAU,OAAO;KACjB;IACF;IAEA,eAAe,UAAU,YAAY,SAAS,KAAK,CAAC;GACtD;GAEA,WAAW,YACT,YAAY,eAAe,YAAY,cAAc,SAAS,OAChE;EACF,SAAS,QAAQ;GACf,IAAI,aAAa,MAAM,GAAG;IACxB,UAAU,MAAM;IAEhB;GACF;GAEA,SAAS,aAAa,MAAM,CAAC;GAC7B,UAAU,OAAO;EACnB;CACF,GACA;EAAC;EAAgB;EAAU;EAAY;CAAS,CAClD;CAEA,MAAM,cAAc,aACjB,SAAuB;EACtB,MAAM,UAAU,KAAK,KAAK;EAC1B,IAAI,YAAY,IACd;EAGF,gBAAgB,UAAU;EAC1B,QAAa,YAAY,SAAS,OAAO;CAC3C,GACA,CAAC,OAAO,CACV;CAEA,MAAM,OAAO,kBAAwB;EACnC,SAAS,SAAS,MAAM;EACxB,UAAU,MAAM;CAClB,GAAG,CAAC,CAAC;CAEL,MAAM,aAAa,kBAAwB;EACzC,MAAM,WAAW,gBAAgB;EACjC,IAAI,aAAa,MACf;EAGF,MAAM,UAAU,YAAY;
|
|
1
|
+
{"version":3,"file":"use-chat.js","names":[],"sources":["../../../resources/js/chat/hooks/use-chat.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { ndjsonChatTransport } from \"@lattice-php/lattice/chat/lib/transport\";\nimport type {\n ChatFrame,\n ChatMessage,\n ChatStatus,\n ChatTransport,\n UseChatReturn,\n} from \"@lattice-php/lattice/chat/types\";\n\nexport type UseChatOptions = {\n endpoint: string;\n transport?: ChatTransport;\n initialMessages?: ChatMessage[];\n generateId?: () => string;\n};\n\nlet idCounter = 0;\n\nfunction defaultGenerateId(): string {\n idCounter += 1;\n\n return `chat-${idCounter}`;\n}\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof DOMException && error.name === \"AbortError\";\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction lastIsAssistant(messages: ChatMessage[]): boolean {\n const last = messages.at(-1);\n\n return last !== undefined && last.role === \"assistant\";\n}\n\n/**\n * Pure reducer for a single streamed frame: a `text` frame appends to the open\n * assistant text part (opening one if the last part is not text); a `part` frame\n * pushes a complete part, which closes the open text part. Operates immutably on\n * the last assistant message.\n */\nexport function foldFrame(messages: ChatMessage[], frame: ChatFrame): ChatMessage[] {\n if (!lastIsAssistant(messages)) {\n return messages;\n }\n\n const index = messages.length - 1;\n const target = messages[index];\n\n if (frame.type === \"text\") {\n const parts = [...target.parts];\n const openIndex = parts.length - 1;\n const open = parts[openIndex];\n\n if (open !== undefined && open.type === \"chat.part.text\") {\n const openText = (open.props as { text: string }).text;\n parts[openIndex] = { type: \"chat.part.text\", props: { text: `${openText}${frame.value}` } };\n } else {\n parts.push({ type: \"chat.part.text\", props: { text: frame.value } });\n }\n\n const next = [...messages];\n next[index] = { ...target, parts };\n\n return next;\n }\n\n if (frame.type === \"part\") {\n const next = [...messages];\n next[index] = { ...target, parts: [...target.parts, frame.part] };\n\n return next;\n }\n\n return messages;\n}\n\nexport function useChat({\n endpoint,\n transport = ndjsonChatTransport,\n initialMessages = [],\n generateId = defaultGenerateId,\n}: UseChatOptions): UseChatReturn {\n const [messages, setMessagesState] = useState<ChatMessage[]>(initialMessages);\n const [status, setStatus] = useState<ChatStatus>(\"idle\");\n const [error, setError] = useState<string | null>(null);\n\n const abortRef = useRef<AbortController | null>(null);\n const lastUserTextRef = useRef<string | null>(null);\n const messagesRef = useRef<ChatMessage[]>(messages);\n messagesRef.current = messages;\n\n const commitMessages = useCallback((next: ChatMessage[]): void => {\n messagesRef.current = next;\n setMessagesState(next);\n }, []);\n\n const runTurn = useCallback(\n async (history: ChatMessage[], userText: string): Promise<void> => {\n abortRef.current?.abort();\n const controller = new AbortController();\n abortRef.current = controller;\n\n setError(null);\n setStatus(\"submitted\");\n\n const userMessage: ChatMessage = {\n id: generateId(),\n role: \"user\",\n parts: [{ type: \"chat.part.text\", props: { text: userText } }],\n };\n const assistantMessage: ChatMessage = { id: generateId(), role: \"assistant\", parts: [] };\n commitMessages([...history, userMessage, assistantMessage]);\n setStatus(\"streaming\");\n\n try {\n for await (const frame of transport({\n url: endpoint,\n body: { message: userText },\n signal: controller.signal,\n })) {\n if (frame.type === \"done\") {\n setStatus(\"idle\");\n continue;\n }\n\n if (frame.type === \"error\") {\n setError(frame.message ?? \"Chat failed\");\n setStatus(\"error\");\n continue;\n }\n\n commitMessages(foldFrame(messagesRef.current, frame));\n }\n\n setStatus((current) =>\n current === \"streaming\" || current === \"submitted\" ? \"idle\" : current,\n );\n } catch (caught) {\n if (isAbortError(caught)) {\n setStatus(\"idle\");\n\n return;\n }\n\n setError(errorMessage(caught));\n setStatus(\"error\");\n }\n },\n [commitMessages, endpoint, generateId, transport],\n );\n\n const sendMessage = useCallback(\n (text: string): void => {\n const trimmed = text.trim();\n if (trimmed === \"\") {\n return;\n }\n\n lastUserTextRef.current = trimmed;\n void runTurn(messagesRef.current, trimmed);\n },\n [runTurn],\n );\n\n const stop = useCallback((): void => {\n abortRef.current?.abort();\n setStatus(\"idle\");\n }, []);\n\n const regenerate = useCallback((): void => {\n const userText = lastUserTextRef.current;\n if (userText === null) {\n return;\n }\n\n const current = messagesRef.current;\n const history = lastIsAssistant(current) ? current.slice(0, -2) : current;\n void runTurn(history, userText);\n }, [runTurn]);\n\n useEffect(() => {\n return () => {\n abortRef.current?.abort();\n };\n }, []);\n\n return { messages, status, error, sendMessage, setMessages: commitMessages, stop, regenerate };\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport.js","names":[],"sources":["../../../resources/js/chat/lib/transport.ts"],"sourcesContent":["import { apiFetch, remoteFetch, type RemoteAccess } from \"@lattice-php/lattice/core/api\";\nimport type { ChatFrame, ChatTransportRequest } from \"@lattice-php/lattice/chat/types\";\n\nfunction parseFrame(line: string): ChatFrame | null {\n try {\n return JSON.parse(line) as ChatFrame;\n } catch {\n return null;\n }\n}\n\nexport async function* ndjsonChatTransport({\n url,\n body,\n signal,\n}: ChatTransportRequest): AsyncGenerator<ChatFrame> {\n const res = await apiFetch(url, {\n method: \"POST\",\n signal,\n headers: { Accept: \"application/x-ndjson\" },\n body: JSON.stringify(body),\n throwOnError: false,\n });\n\n if (!res.ok || !res.body) {\n throw new Error(`Chat stream failed (${res.status})`);\n }\n\n yield* readNdjsonFrames(res.body);\n}\n\nexport function createRemoteNdjsonChatTransport(remote: RemoteAccess) {\n return async function* remoteNdjsonChatTransport({\n url,\n body,\n signal,\n }: ChatTransportRequest): AsyncGenerator<ChatFrame> {\n const res = await remoteFetch(url, {\n remote,\n method: \"POST\",\n signal,\n headers: {\n Accept: \"application/x-ndjson\",\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n throwOnError: false,\n });\n\n if (!res.ok || !res.body) {\n throw new Error(`Chat stream failed (${res.status})`);\n }\n\n yield* readNdjsonFrames(res.body);\n };\n}\n\nasync function* readNdjsonFrames(body: ReadableStream<Uint8Array>): AsyncGenerator<ChatFrame> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() ?? \"\";\n\n for (const line of lines) {\n if (line.trim() === \"\") {\n continue;\n }\n\n const frame = parseFrame(line);\n if (frame) {\n yield frame;\n }\n }\n }\n\n buffer += decoder.decode();\n if (buffer.trim() !== \"\") {\n const frame = parseFrame(buffer);\n if (frame) {\n yield frame;\n }\n }\n } finally {\n await reader.cancel().catch(() => {});\n }\n}\n"],"mappings":";;AAGA,SAAS,WAAW,MAAgC;CAClD,IAAI;EACF,OAAO,KAAK,MAAM,IAAI;CACxB,QAAQ;EACN,OAAO;CACT;AACF;AAEA,gBAAuB,oBAAoB,EACzC,KACA,MACA,UACkD;CAClD,MAAM,MAAM,MAAM,SAAS,KAAK;EAC9B,QAAQ;EACR;EACA,SAAS,EAAE,QAAQ,uBAAuB;EAC1C,MAAM,KAAK,UAAU,IAAI;EACzB,cAAc;CAChB,CAAC;CAED,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,MAClB,MAAM,IAAI,MAAM,uBAAuB,IAAI,OAAO,EAAE;CAGtD,OAAO,iBAAiB,IAAI,IAAI;AAClC;AAEA,SAAgB,gCAAgC,QAAsB;CACpE,OAAO,gBAAgB,0BAA0B,EAC/C,KACA,MACA,UACkD;EAClD,MAAM,MAAM,MAAM,YAAY,KAAK;GACjC;GACA,QAAQ;GACR;GACA,SAAS;IACP,QAAQ;IACR,gBAAgB;GAClB;GACA,MAAM,KAAK,UAAU,IAAI;GACzB,cAAc;EAChB,CAAC;EAED,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,MAClB,MAAM,IAAI,MAAM,uBAAuB,IAAI,OAAO,EAAE;EAGtD,OAAO,iBAAiB,IAAI,IAAI;CAClC;AACF;AAEA,gBAAgB,iBAAiB,MAA6D;CAC5F,MAAM,SAAS,KAAK,UAAU;CAC9B,MAAM,UAAU,IAAI,YAAY;CAChC,IAAI,SAAS;CAEb,IAAI;EACF,SAAS;GACP,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,KAAK;GAC1C,IAAI,MACF;GAGF,UAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;GAChD,MAAM,QAAQ,OAAO,MAAM,IAAI;GAC/B,SAAS,MAAM,IAAI,KAAK;GAExB,KAAK,MAAM,QAAQ,OAAO;IACxB,IAAI,KAAK,KAAK,MAAM,IAClB;IAGF,MAAM,QAAQ,WAAW,IAAI;IAC7B,IAAI,OACF,MAAM;GAEV;EACF;EAEA,UAAU,QAAQ,OAAO;EACzB,IAAI,OAAO,KAAK,MAAM,IAAI;GACxB,MAAM,QAAQ,WAAW,MAAM;GAC/B,IAAI,OACF,MAAM;EAEV;CACF,UAAU;EACR,MAAM,OAAO,OAAO,
|
|
1
|
+
{"version":3,"file":"transport.js","names":[],"sources":["../../../resources/js/chat/lib/transport.ts"],"sourcesContent":["import { apiFetch, remoteFetch, type RemoteAccess } from \"@lattice-php/lattice/core/api\";\nimport type { ChatFrame, ChatTransportRequest } from \"@lattice-php/lattice/chat/types\";\n\nfunction parseFrame(line: string): ChatFrame | null {\n try {\n return JSON.parse(line) as ChatFrame;\n } catch {\n return null;\n }\n}\n\nexport async function* ndjsonChatTransport({\n url,\n body,\n signal,\n}: ChatTransportRequest): AsyncGenerator<ChatFrame> {\n const res = await apiFetch(url, {\n method: \"POST\",\n signal,\n headers: { Accept: \"application/x-ndjson\" },\n body: JSON.stringify(body),\n throwOnError: false,\n });\n\n if (!res.ok || !res.body) {\n throw new Error(`Chat stream failed (${res.status})`);\n }\n\n yield* readNdjsonFrames(res.body);\n}\n\nexport function createRemoteNdjsonChatTransport(remote: RemoteAccess) {\n return async function* remoteNdjsonChatTransport({\n url,\n body,\n signal,\n }: ChatTransportRequest): AsyncGenerator<ChatFrame> {\n const res = await remoteFetch(url, {\n remote,\n method: \"POST\",\n signal,\n headers: {\n Accept: \"application/x-ndjson\",\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(body),\n throwOnError: false,\n });\n\n if (!res.ok || !res.body) {\n throw new Error(`Chat stream failed (${res.status})`);\n }\n\n yield* readNdjsonFrames(res.body);\n };\n}\n\nasync function* readNdjsonFrames(body: ReadableStream<Uint8Array>): AsyncGenerator<ChatFrame> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() ?? \"\";\n\n for (const line of lines) {\n if (line.trim() === \"\") {\n continue;\n }\n\n const frame = parseFrame(line);\n if (frame) {\n yield frame;\n }\n }\n }\n\n buffer += decoder.decode();\n if (buffer.trim() !== \"\") {\n const frame = parseFrame(buffer);\n if (frame) {\n yield frame;\n }\n }\n } finally {\n await reader.cancel().catch(() => {});\n }\n}\n"],"mappings":";;AAGA,SAAS,WAAW,MAAgC;CAClD,IAAI;EACF,OAAO,KAAK,MAAM,IAAI;CACxB,QAAQ;EACN,OAAO;CACT;AACF;AAEA,gBAAuB,oBAAoB,EACzC,KACA,MACA,UACkD;CAClD,MAAM,MAAM,MAAM,SAAS,KAAK;EAC9B,QAAQ;EACR;EACA,SAAS,EAAE,QAAQ,uBAAuB;EAC1C,MAAM,KAAK,UAAU,IAAI;EACzB,cAAc;CAChB,CAAC;CAED,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,MAClB,MAAM,IAAI,MAAM,uBAAuB,IAAI,OAAO,EAAE;CAGtD,OAAO,iBAAiB,IAAI,IAAI;AAClC;AAEA,SAAgB,gCAAgC,QAAsB;CACpE,OAAO,gBAAgB,0BAA0B,EAC/C,KACA,MACA,UACkD;EAClD,MAAM,MAAM,MAAM,YAAY,KAAK;GACjC;GACA,QAAQ;GACR;GACA,SAAS;IACP,QAAQ;IACR,gBAAgB;GAClB;GACA,MAAM,KAAK,UAAU,IAAI;GACzB,cAAc;EAChB,CAAC;EAED,IAAI,CAAC,IAAI,MAAM,CAAC,IAAI,MAClB,MAAM,IAAI,MAAM,uBAAuB,IAAI,OAAO,EAAE;EAGtD,OAAO,iBAAiB,IAAI,IAAI;CAClC;AACF;AAEA,gBAAgB,iBAAiB,MAA6D;CAC5F,MAAM,SAAS,KAAK,UAAU;CAC9B,MAAM,UAAU,IAAI,YAAY;CAChC,IAAI,SAAS;CAEb,IAAI;EACF,SAAS;GACP,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,KAAK;GAC1C,IAAI,MACF;GAGF,UAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;GAChD,MAAM,QAAQ,OAAO,MAAM,IAAI;GAC/B,SAAS,MAAM,IAAI,KAAK;GAExB,KAAK,MAAM,QAAQ,OAAO;IACxB,IAAI,KAAK,KAAK,MAAM,IAClB;IAGF,MAAM,QAAQ,WAAW,IAAI;IAC7B,IAAI,OACF,MAAM;GAEV;EACF;EAEA,UAAU,QAAQ,OAAO;EACzB,IAAI,OAAO,KAAK,MAAM,IAAI;GACxB,MAAM,QAAQ,WAAW,MAAM;GAC/B,IAAI,OACF,MAAM;EAEV;CACF,UAAU;EACR,MAAM,OAAO,OAAO,CAAC,CAAC,YAAY,CAAC,CAAC;CACtC;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text.js","names":[],"sources":["../../../resources/js/chat/parts/text.tsx"],"sourcesContent":["import type { RendererComponent } from \"@lattice-php/lattice/core/types\";\n\nexport const TextPart: RendererComponent<\"chat.part.text\"> = ({ node }) => {\n return <div className=\"whitespace-pre-wrap text-sm\">{node.props.text}</div>;\n};\n\nexport default TextPart;\n"],"mappings":";;AAEA,IAAa,YAAiD,EAAE,WAAW;CACzE,OAAO,oBAAC,OAAD;EAAK,WAAU;
|
|
1
|
+
{"version":3,"file":"text.js","names":[],"sources":["../../../resources/js/chat/parts/text.tsx"],"sourcesContent":["import type { RendererComponent } from \"@lattice-php/lattice/core/types\";\n\nexport const TextPart: RendererComponent<\"chat.part.text\"> = ({ node }) => {\n return <div className=\"whitespace-pre-wrap text-sm\">{node.props.text}</div>;\n};\n\nexport default TextPart;\n"],"mappings":";;AAEA,IAAa,YAAiD,EAAE,WAAW;CACzE,OAAO,oBAAC,OAAD;EAAK,WAAU;EAA+B,UAAA,KAAK,MAAM;CAAU,CAAA;AAC5E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-call.js","names":[],"sources":["../../../resources/js/chat/parts/tool-call.tsx"],"sourcesContent":["import { testIdentity } from \"@lattice-php/lattice/core/test-id\";\nimport type { RendererComponent } from \"@lattice-php/lattice/core/types\";\n\nexport const ToolCallPart: RendererComponent<\"chat.part.tool-call\"> = ({ node }) => {\n return (\n <div\n className=\"mt-1 inline-flex items-center gap-1 rounded-lt-sm bg-lt-muted px-2 py-0.5 font-mono text-xs text-lt-muted-fg\"\n data-test={testIdentity(\"chat-tool-call\")}\n >\n 🔧 {node.props.name}({JSON.stringify(node.props.args)})\n </div>\n );\n};\n\nexport default ToolCallPart;\n"],"mappings":";;;AAGA,IAAa,gBAA0D,EAAE,WAAW;CAClF,OACE,qBAAC,OAAD;EACE,WAAU;EACV,aAAW,aAAa,gBAAgB;
|
|
1
|
+
{"version":3,"file":"tool-call.js","names":[],"sources":["../../../resources/js/chat/parts/tool-call.tsx"],"sourcesContent":["import { testIdentity } from \"@lattice-php/lattice/core/test-id\";\nimport type { RendererComponent } from \"@lattice-php/lattice/core/types\";\n\nexport const ToolCallPart: RendererComponent<\"chat.part.tool-call\"> = ({ node }) => {\n return (\n <div\n className=\"mt-1 inline-flex items-center gap-1 rounded-lt-sm bg-lt-muted px-2 py-0.5 font-mono text-xs text-lt-muted-fg\"\n data-test={testIdentity(\"chat-tool-call\")}\n >\n 🔧 {node.props.name}({JSON.stringify(node.props.args)})\n </div>\n );\n};\n\nexport default ToolCallPart;\n"],"mappings":";;;AAGA,IAAa,gBAA0D,EAAE,WAAW;CAClF,OACE,qBAAC,OAAD;EACE,WAAU;EACV,aAAW,aAAa,gBAAgB;EAF1C,UAAA;GAGC;GACK,KAAK,MAAM;GAAK;GAAE,KAAK,UAAU,KAAK,MAAM,IAAI;GAAE;EACnD;;AAET"}
|
package/dist/core/api.js.map
CHANGED
|
@@ -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 { 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\n/**\n * Exported so the few transports that cannot go through apiFetch — an XHR that\n * reports upload progress, for instance — still send the same CSRF header.\n */\nexport function 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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"}
|
|
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\n/**\n * Exported so the few transports that cannot go through apiFetch — an XHR that\n * reports upload progress, for instance — still send the same CSRF header.\n */\nexport function 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,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export declare const LATTICE_EVENT: {
|
|
7
7
|
readonly callout: "lattice:callout";
|
|
8
|
+
readonly retractCallout: "lattice:retract-callout";
|
|
8
9
|
readonly toast: "lattice:toast";
|
|
9
10
|
readonly reloadComponent: "lattice:reload-component";
|
|
10
11
|
readonly openModal: "lattice:open-modal";
|
package/dist/core/event-names.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"event-names.js","names":[],"sources":["../../resources/js/core/event-names.ts"],"sourcesContent":["/**\n * Single source of truth for the `lattice:*` DOM events the runtime dispatches\n * and listens for. The built-in effect handlers in effects/registry.ts bridge\n * effects to these events; the rest are framework events with no PHP counterpart.\n */\nexport const LATTICE_EVENT = {\n callout: \"lattice:callout\",\n toast: \"lattice:toast\",\n reloadComponent: \"lattice:reload-component\",\n openModal: \"lattice:open-modal\",\n closeModal: \"lattice:close-modal\",\n resetForm: \"lattice:reset-form\",\n toggleSidebar: \"lattice:toggle-sidebar\",\n appearanceChange: \"lattice:appearance-change\",\n localeChange: \"lattice:locale-change\",\n timezoneChange: \"lattice:timezone-change\",\n actionError: \"lattice:action-error\",\n} as const;\n\nexport type ReloadComponentEvent = CustomEvent<{\n component?: string;\n type?: string;\n}>;\n"],"mappings":";;;;;;AAKA,IAAa,gBAAgB;CAC3B,SAAS;CACT,OAAO;CACP,iBAAiB;CACjB,WAAW;CACX,YAAY;CACZ,WAAW;CACX,eAAe;CACf,kBAAkB;CAClB,cAAc;CACd,gBAAgB;CAChB,aAAa;AACf"}
|
|
1
|
+
{"version":3,"file":"event-names.js","names":[],"sources":["../../resources/js/core/event-names.ts"],"sourcesContent":["/**\n * Single source of truth for the `lattice:*` DOM events the runtime dispatches\n * and listens for. The built-in effect handlers in effects/registry.ts bridge\n * effects to these events; the rest are framework events with no PHP counterpart.\n */\nexport const LATTICE_EVENT = {\n callout: \"lattice:callout\",\n retractCallout: \"lattice:retract-callout\",\n toast: \"lattice:toast\",\n reloadComponent: \"lattice:reload-component\",\n openModal: \"lattice:open-modal\",\n closeModal: \"lattice:close-modal\",\n resetForm: \"lattice:reset-form\",\n toggleSidebar: \"lattice:toggle-sidebar\",\n appearanceChange: \"lattice:appearance-change\",\n localeChange: \"lattice:locale-change\",\n timezoneChange: \"lattice:timezone-change\",\n actionError: \"lattice:action-error\",\n} as const;\n\nexport type ReloadComponentEvent = CustomEvent<{\n component?: string;\n type?: string;\n}>;\n"],"mappings":";;;;;;AAKA,IAAa,gBAAgB;CAC3B,SAAS;CACT,gBAAgB;CAChB,OAAO;CACP,iBAAiB;CACjB,WAAW;CACX,YAAY;CACZ,WAAW;CACX,eAAe;CACf,kBAAkB;CAClB,cAAc;CACd,gBAAgB;CAChB,aAAa;AACf"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"column-sizing.js","names":[],"sources":["../../../resources/js/core/hooks/column-sizing.ts"],"sourcesContent":["import type { ColumnWidth } from \"@lattice-php/lattice/types/generated\";\n\nexport type SizableColumn = {\n key: string;\n label?: string | null;\n width: ColumnWidth;\n};\n\nexport const DEFAULT_COLUMN_WIDTH: ColumnWidth = \"md\";\n\nconst tracks: Record<\n ColumnWidth,\n { track: string; minPx: number; defaultPx: number; maxPx: number }\n> = {\n xs: { track: \"minmax(4rem, 0.35fr)\", minPx: 64, defaultPx: 96, maxPx: 1024 },\n sm: { track: \"minmax(6rem, 0.5fr)\", minPx: 96, defaultPx: 128, maxPx: 1024 },\n md: { track: \"minmax(8rem, 1fr)\", minPx: 128, defaultPx: 176, maxPx: 1024 },\n lg: { track: \"minmax(12rem, 1.5fr)\", minPx: 192, defaultPx: 240, maxPx: 1024 },\n xl: { track: \"minmax(16rem, 2fr)\", minPx: 256, defaultPx: 320, maxPx: 1024 },\n};\n\nexport function columnWidthTrack(width: ColumnWidth): string {\n return tracks[width].track;\n}\n\nexport function minColumnWidthPx(column: SizableColumn): number {\n return tracks[column.width].minPx;\n}\n\nexport function defaultColumnWidthPx(column: SizableColumn): number {\n return tracks[column.width].defaultPx;\n}\n\nexport function maxColumnWidthPx(column: SizableColumn): number {\n return tracks[column.width].maxPx;\n}\n\nexport function buildColumnGridTemplate({\n columns,\n leadingTracks = [],\n trailingTracks = [],\n overrides = {},\n}: {\n columns: SizableColumn[];\n leadingTracks?: string[];\n trailingTracks?: string[];\n overrides?: Record<string, number | undefined>;\n}): string {\n return [\n ...leadingTracks,\n ...columns.map((column) => {\n const override = overrides[column.key];\n\n if (override !== undefined) {\n return `${Math.min(maxColumnWidthPx(column), Math.max(minColumnWidthPx(column), override))}px`;\n }\n\n return columnWidthTrack(column.width);\n }),\n ...trailingTracks,\n ].join(\" \");\n}\n"],"mappings":";AAQA,IAAa,uBAAoC;AAEjD,IAAM,SAGF;CACF,IAAI;EAAE,OAAO;EAAwB,OAAO;EAAI,WAAW;EAAI,OAAO;CAAK;CAC3E,IAAI;EAAE,OAAO;EAAuB,OAAO;EAAI,WAAW;EAAK,OAAO;CAAK;CAC3E,IAAI;EAAE,OAAO;EAAqB,OAAO;EAAK,WAAW;EAAK,OAAO;CAAK;CAC1E,IAAI;EAAE,OAAO;EAAwB,OAAO;EAAK,WAAW;EAAK,OAAO;CAAK;CAC7E,IAAI;EAAE,OAAO;EAAsB,OAAO;EAAK,WAAW;EAAK,OAAO;CAAK;AAC7E;AAEA,SAAgB,iBAAiB,OAA4B;CAC3D,OAAO,OAAO,
|
|
1
|
+
{"version":3,"file":"column-sizing.js","names":[],"sources":["../../../resources/js/core/hooks/column-sizing.ts"],"sourcesContent":["import type { ColumnWidth } from \"@lattice-php/lattice/types/generated\";\n\nexport type SizableColumn = {\n key: string;\n label?: string | null;\n width: ColumnWidth;\n};\n\nexport const DEFAULT_COLUMN_WIDTH: ColumnWidth = \"md\";\n\nconst tracks: Record<\n ColumnWidth,\n { track: string; minPx: number; defaultPx: number; maxPx: number }\n> = {\n xs: { track: \"minmax(4rem, 0.35fr)\", minPx: 64, defaultPx: 96, maxPx: 1024 },\n sm: { track: \"minmax(6rem, 0.5fr)\", minPx: 96, defaultPx: 128, maxPx: 1024 },\n md: { track: \"minmax(8rem, 1fr)\", minPx: 128, defaultPx: 176, maxPx: 1024 },\n lg: { track: \"minmax(12rem, 1.5fr)\", minPx: 192, defaultPx: 240, maxPx: 1024 },\n xl: { track: \"minmax(16rem, 2fr)\", minPx: 256, defaultPx: 320, maxPx: 1024 },\n};\n\nexport function columnWidthTrack(width: ColumnWidth): string {\n return tracks[width].track;\n}\n\nexport function minColumnWidthPx(column: SizableColumn): number {\n return tracks[column.width].minPx;\n}\n\nexport function defaultColumnWidthPx(column: SizableColumn): number {\n return tracks[column.width].defaultPx;\n}\n\nexport function maxColumnWidthPx(column: SizableColumn): number {\n return tracks[column.width].maxPx;\n}\n\nexport function buildColumnGridTemplate({\n columns,\n leadingTracks = [],\n trailingTracks = [],\n overrides = {},\n}: {\n columns: SizableColumn[];\n leadingTracks?: string[];\n trailingTracks?: string[];\n overrides?: Record<string, number | undefined>;\n}): string {\n return [\n ...leadingTracks,\n ...columns.map((column) => {\n const override = overrides[column.key];\n\n if (override !== undefined) {\n return `${Math.min(maxColumnWidthPx(column), Math.max(minColumnWidthPx(column), override))}px`;\n }\n\n return columnWidthTrack(column.width);\n }),\n ...trailingTracks,\n ].join(\" \");\n}\n"],"mappings":";AAQA,IAAa,uBAAoC;AAEjD,IAAM,SAGF;CACF,IAAI;EAAE,OAAO;EAAwB,OAAO;EAAI,WAAW;EAAI,OAAO;CAAK;CAC3E,IAAI;EAAE,OAAO;EAAuB,OAAO;EAAI,WAAW;EAAK,OAAO;CAAK;CAC3E,IAAI;EAAE,OAAO;EAAqB,OAAO;EAAK,WAAW;EAAK,OAAO;CAAK;CAC1E,IAAI;EAAE,OAAO;EAAwB,OAAO;EAAK,WAAW;EAAK,OAAO;CAAK;CAC7E,IAAI;EAAE,OAAO;EAAsB,OAAO;EAAK,WAAW;EAAK,OAAO;CAAK;AAC7E;AAEA,SAAgB,iBAAiB,OAA4B;CAC3D,OAAO,OAAO,MAAM,CAAC;AACvB;AAEA,SAAgB,iBAAiB,QAA+B;CAC9D,OAAO,OAAO,OAAO,MAAM,CAAC;AAC9B;AAEA,SAAgB,qBAAqB,QAA+B;CAClE,OAAO,OAAO,OAAO,MAAM,CAAC;AAC9B;AAEA,SAAgB,iBAAiB,QAA+B;CAC9D,OAAO,OAAO,OAAO,MAAM,CAAC;AAC9B;AAEA,SAAgB,wBAAwB,EACtC,SACA,gBAAgB,CAAC,GACjB,iBAAiB,CAAC,GAClB,YAAY,CAAC,KAMJ;CACT,OAAO;EACL,GAAG;EACH,GAAG,QAAQ,KAAK,WAAW;GACzB,MAAM,WAAW,UAAU,OAAO;GAElC,IAAI,aAAa,KAAA,GACf,OAAO,GAAG,KAAK,IAAI,iBAAiB,MAAM,GAAG,KAAK,IAAI,iBAAiB,MAAM,GAAG,QAAQ,CAAC,EAAE;GAG7F,OAAO,iBAAiB,OAAO,KAAK;EACtC,CAAC;EACD,GAAG;CACL,CAAC,CAAC,KAAK,GAAG;AACZ"}
|