diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/SubmitConfirmation.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/SubmitConfirmation.mdx
new file mode 100644
index 00000000000..ed7703ba9a6
--- /dev/null
+++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/SubmitConfirmation.mdx
@@ -0,0 +1,25 @@
+---
+title: 'SubmitConfirmation'
+description: '`Form.SubmitConfirmation` can be used to prevent the `Form.Handler` from submitting, and makes it possible to show a confirmation dialog in different scenarios.'
+showTabs: true
+tabs:
+ - title: Info
+ key: '/info'
+ - title: Demos
+ key: '/demos'
+ - title: Properties
+ key: '/properties'
+breadcrumb:
+ - text: Forms
+ href: /uilib/extensions/forms/
+ - text: Form
+ href: /uilib/extensions/forms/Form
+ - text: SubmitConfirmation
+ href: /uilib/extensions/forms/Form/SubmitConfirmation/
+---
+
+import Info from 'Docs/uilib/extensions/forms/Form/SubmitConfirmation/info'
+import Demos from 'Docs/uilib/extensions/forms/Form/SubmitConfirmation/demos'
+
+
+
diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/SubmitConfirmation/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/SubmitConfirmation/Examples.tsx
new file mode 100644
index 00000000000..fe9ea87d6eb
--- /dev/null
+++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/SubmitConfirmation/Examples.tsx
@@ -0,0 +1,152 @@
+import { Dialog, Flex, Section } from '@dnb/eufemia/src'
+import ComponentBox from '../../../../../../shared/tags/ComponentBox'
+import { Field, Form } from '@dnb/eufemia/src/extensions/forms'
+
+export const WithDialog = () => {
+ return (
+
+ {
+ await new Promise((resolve) => setTimeout(resolve, 2000))
+ }}
+ >
+
+
+
+
+
+ true}
+ renderWithState={({ connectWithDialog }) => {
+ return (
+
+ )
+ }}
+ />
+
+
+ )
+}
+
+export const WithStateContent = () => {
+ return (
+
+ {
+ await new Promise((resolve) => setTimeout(resolve, 2000))
+ }}
+ >
+ true}
+ onStateChange={({ confirmationState }) => {
+ console.log('onStateChange', confirmationState)
+ }}
+ renderWithState={({ confirmationState, connectWithDialog }) => {
+ let content = null
+
+ switch (confirmationState) {
+ case 'readyToBeSubmitted':
+ content = <>Is waiting ...>
+ break
+ case 'submitInProgress':
+ content = <>Submitting...>
+ break
+ case 'submissionComplete':
+ content = <>Complete!>
+ break
+ default:
+ content = (
+
+
+
+
+ )
+ break
+ }
+
+ return (
+ <>
+ {content}
+
+ >
+ )
+ }}
+ />
+
+
+ )
+}
+
+export const WithCustomReturnStatus = () => {
+ return (
+
+ {
+ await new Promise((resolve) => setTimeout(resolve, 2000))
+ return {
+ customStatus: 'My custom status',
+ }
+ }}
+ >
+
+
+
+
+
+ {
+ if (submitState && submitState.customStatus) {
+ setConfirmationState('readyToBeSubmitted')
+ }
+ }}
+ renderWithState={({ connectWithDialog, submitState }) => {
+ return (
+
+ )
+ }}
+ />
+
+
+ )
+}
diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/SubmitConfirmation/demos.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/SubmitConfirmation/demos.mdx
new file mode 100644
index 00000000000..8bf405a3caa
--- /dev/null
+++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/SubmitConfirmation/demos.mdx
@@ -0,0 +1,21 @@
+---
+showTabs: true
+---
+
+import * as Examples from './Examples'
+
+## Demos
+
+### With confirmation dialog
+
+
+
+### Enable and disable the confirmation mechanism
+
+This example makes first an ordinary submit request. But when the custom status is returned, the dialog component will be shown.
+
+
+
+### Render different content based on the submit state
+
+
diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/SubmitConfirmation/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/SubmitConfirmation/info.mdx
new file mode 100644
index 00000000000..2447c4b04f4
--- /dev/null
+++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/SubmitConfirmation/info.mdx
@@ -0,0 +1,99 @@
+---
+showTabs: true
+---
+
+## Description
+
+`Form.SubmitConfirmation` can be used to prevent the [Form.Handler](/uilib/extensions/forms/Form/Handler/) from submitting, and makes it possible to show a confirmation dialog in different scenarios.
+
+```jsx
+import { Dialog } from '@dnb/eufemia'
+import { Form } from '@dnb/eufemia/extensions/forms'
+render(
+
{
+ // Your submit request
+ }}
+ >
+ Content...
+
+ {
+ // Your preventSubmitWhen logic
+ }}
+ onStateChange={(parameters) => {
+ // Your onStateChange logic
+ }}
+ renderWithState={(parameters) => {
+ return 'Your content'
+ }}
+ />
+ ,
+)
+```
+
+The `renderWithState` function is called whenever the submit confirmation state changes. It receives an object as the first parameter, which contains:
+
+- `connectWithDialog` lets you connect the submit confirmation with a [Dialog](/uilib/components/dialog).
+- `submitHandler` is a function that can be called to submit the form.
+- `cancelHandler` is a function that can be called to cancel the form.
+- `setConfirmationState` is a function that can be called to update the submit state.
+- `confirmationState` is a string that can be used to determine the current state of the submit confirmation:
+ - `idle`
+ - `readyToBeSubmitted`
+ - `submitInProgress`
+ - `submissionComplete`
+- `submitState` is the state of the `onSubmit` form event:
+ - `error`
+ - `info`
+ - `warning`
+ - `success`
+ - `customStatus` Your custom status.
+- `data` is the data that was submitted.
+
+## Connect with a Dialog
+
+You can connect the submit confirmation with a [Dialog](/uilib/components/dialog) by using the `connectWithDialog` property. This property is an object that contains the `openState`, `onConfirm`, `onDecline`, and `onClose` properties, which you can spread to the Dialog component.
+
+```jsx
+import { Dialog } from '@dnb/eufemia'
+import { Form } from '@dnb/eufemia/extensions/forms'
+
+render(
+
+ {
+ return (
+
+ )
+ }}
+ />
+ ,
+)
+```
+
+## Using the submitHandler and cancelHandler
+
+In addition to `connectWithDialog`, there are the `submitHandler` and `cancelHandler` functions, available to handle the submission and cancellation processes:
+
+```jsx
+ {
+ return (
+ <>
+
+
+ >
+ )
+ }}
+/>
+```
+
+## Accessibility
+
+When the `cancelHandler` is called or the `onSubmit` event is completed, the [Form.SubmitButton](/uilib/extensions/forms/Form/SubmitButton/) will regain focus.
diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/SubmitConfirmation/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/SubmitConfirmation/properties.mdx
new file mode 100644
index 00000000000..22eeca7c5f3
--- /dev/null
+++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/SubmitConfirmation/properties.mdx
@@ -0,0 +1,10 @@
+---
+showTabs: true
+---
+
+import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable'
+import { SubmitConfirmationProperties } from '@dnb/eufemia/src/extensions/forms/Form/SubmitConfirmation/SubmitConfirmationDocs'
+
+## Properties
+
+
diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/components copy.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/components copy.mdx
deleted file mode 100644
index fb9a74ac013..00000000000
--- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/components copy.mdx
+++ /dev/null
@@ -1,10 +0,0 @@
----
-showTabs: true
-hideInMenu: true
----
-
-import ListFormComponents from './ListFormComponents'
-
-## Components
-
-
diff --git a/packages/dnb-eufemia/src/extensions/forms/DataContext/Context.ts b/packages/dnb-eufemia/src/extensions/forms/DataContext/Context.ts
index 9050268e772..281e9c39477 100644
--- a/packages/dnb-eufemia/src/extensions/forms/DataContext/Context.ts
+++ b/packages/dnb-eufemia/src/extensions/forms/DataContext/Context.ts
@@ -12,13 +12,10 @@ import {
FormError,
ValueProps,
OnChange,
+ OnSubmitParams,
} from '../types'
import { Props as ProviderProps } from './Provider'
-type HandleSubmitProps = {
- formElement?: HTMLFormElement
-}
-
export type MountState = {
isPreMounted?: boolean
isMounted?: boolean
@@ -109,7 +106,7 @@ export interface ContextState {
filterDataHandler?: FilterDataHandler
visibleDataHandler?: VisibleDataHandler
validateData: () => void
- handleSubmit: (props?: HandleSubmitProps) => void
+ handleSubmit: () => Promise
scrollToTop: () => void
setShowAllErrors: (showAllErrors: boolean) => void
hasErrors: () => boolean
@@ -118,7 +115,10 @@ export interface ContextState {
setFieldState: (path: Path, fieldState: SubmitState) => void
setFieldError: (path: Path, error: Error | FormError) => void
setMountedFieldState: (path: Path, options: MountState) => void
- setFormState?: (state: SubmitState) => void
+ setFormState?: (
+ state: SubmitState,
+ options?: { keepPending?: boolean }
+ ) => void
setSubmitState?: (state: EventStateObject) => void
addOnChangeHandler?: (callback: OnChange) => void
handleSubmitCall: ({
@@ -134,7 +134,9 @@ export interface ContextState {
enableAsyncBehavior: boolean
skipFieldValidation?: boolean
skipErrorCheck?: boolean
- }) => void
+ }) => Promise
+ getSubmitData?: () => unknown
+ getSubmitOptions?: () => OnSubmitParams
setFieldEventListener?: (
path: EventListenerCall['path'],
type: EventListenerCall['type'],
@@ -143,12 +145,16 @@ export interface ContextState {
setVisibleError?: (path: Path, hasError: boolean) => void
setFieldProps?: (path: Path, props: unknown) => void
setValueProps?: (path: Path, props: unknown) => void
- setHandleSubmit?: (callback: HandleSubmitCallback) => void
+ setHandleSubmit?: (
+ callback: HandleSubmitCallback,
+ params?: { remove?: boolean }
+ ) => void
setFieldConnection?: (path: Path, connections: FieldConnections) => void
fieldPropsRef?: React.MutableRefObject>
valuePropsRef?: React.MutableRefObject>
fieldConnectionsRef?: React.RefObject>
mountedFieldsRef?: React.MutableRefObject>
+ formElementRef?: React.MutableRefObject
showAllErrors: boolean
hasVisibleError: boolean
formState: SubmitState
diff --git a/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/Provider.tsx b/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/Provider.tsx
index d82927a2910..211011539d6 100644
--- a/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/Provider.tsx
+++ b/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/Provider.tsx
@@ -25,6 +25,8 @@ import {
OnChange,
EventReturnWithStateObject,
ValueProps,
+ OnSubmitParams,
+ OnSubmitReturn,
} from '../../types'
import type { IsolationProviderProps } from '../../Form/Isolation/Isolation'
import { debounce } from '../../../../shared/helpers'
@@ -223,6 +225,9 @@ export default function Provider(
throw new Error('DataContext (Form.Handler) can not be nested')
}
+ // - Element
+ const formElementRef = useRef(null)
+
// - Locale
const translation = useTranslation().Field
@@ -250,16 +255,23 @@ export default function Provider(
}, [])
const submitStateRef = useRef>({})
const setSubmitState = useCallback((state: EventStateObject) => {
- Object.assign(submitStateRef.current, state)
+ submitStateRef.current = { ...submitStateRef.current, ...state }
forceUpdate()
}, [])
// - Progress
const formStateRef = useRef()
- const setFormState = useCallback((formState: SubmitState) => {
- formStateRef.current = formState
- forceUpdate()
- }, [])
+ const keepPending = useRef(false)
+ const setFormState = useCallback(
+ (formState: SubmitState, options = {}) => {
+ if (typeof options?.keepPending === 'boolean') {
+ keepPending.current = options?.keepPending
+ }
+ formStateRef.current = formState
+ forceUpdate()
+ },
+ []
+ )
// - States (e.g. error) reported by fields, based on their direct validation rules
const fieldErrorRef = useRef>({})
@@ -933,7 +945,7 @@ export default function Provider(
skipErrorCheck,
} = args
- setSubmitState({ error: undefined })
+ setSubmitState({ error: undefined, customStatus: undefined })
const asyncBehaviorIsEnabled =
(skipErrorCheck
@@ -964,42 +976,52 @@ export default function Provider(
}
}
+ let result: EventStateObject
+
if (
!(skipErrorCheck ? false : hasErrors()) &&
!hasFieldState('pending') &&
(skipFieldValidation ? true : !hasFieldState('error'))
) {
- let result: EventStateObject | unknown
-
+ let submitResult: OnSubmitReturn
try {
if (isolate) {
- result = await onCommit?.(internalDataRef.current, {
+ submitResult = await onCommit?.(internalDataRef.current, {
clearData,
})
} else {
- result = await onSubmit()
+ submitResult = await onSubmit()
}
- if (result instanceof Error) {
- throw result
+ if (submitResult instanceof Error) {
+ throw submitResult
}
} catch (error) {
- result = { error }
+ submitResult = { error }
}
- const state = result as EventStateObject
+ result = submitResult as EventStateObject
if (asyncBehaviorIsEnabled) {
- setFormState(state?.error ? 'abort' : 'complete')
+ if (result?.error) {
+ setFormState('abort')
+ } else if (keepPending.current !== true) {
+ setFormState('complete')
+ }
}
// Force the state to be set by a custom status
- if (state?.['status']) {
- setFormState(state?.['status'])
+ if (result?.['status']) {
+ setFormState(result?.['status'])
}
- if (state?.error || state?.warning || state?.info) {
- setSubmitState(state)
+ if (
+ result?.error ||
+ result?.warning ||
+ result?.info ||
+ result?.customStatus
+ ) {
+ setSubmitState(result)
}
} else {
if (asyncBehaviorIsEnabled) {
@@ -1024,6 +1046,8 @@ export default function Provider(
setShowAllErrors(true)
}
+
+ return result
},
[
clearData,
@@ -1041,9 +1065,14 @@ export default function Provider(
const handleSubmitListenersRef = useRef>([])
const setHandleSubmit: ContextState['setHandleSubmit'] = useCallback(
- (callback) => {
- if (!handleSubmitListenersRef.current.includes(callback)) {
- handleSubmitListenersRef.current.push(callback)
+ (callback, { remove = false } = {}) => {
+ const listeners = handleSubmitListenersRef.current
+ if (remove) {
+ handleSubmitListenersRef.current = listeners.filter(
+ (item) => item !== callback
+ )
+ } else if (!listeners.includes(callback)) {
+ listeners.push(callback)
}
},
[]
@@ -1057,98 +1086,112 @@ export default function Provider(
return stop
}, [])
- /**
- * Request to submit the whole form
- */
- const handleSubmit = useCallback(
- async ({ formElement = null } = {}) => {
- handleSubmitCall({
- enableAsyncBehavior: isAsync(onSubmit),
- onSubmit: async () => {
- if (handleSubmitListeners()) {
- return // stop here
- }
+ const getSubmitData = useCallback(() => {
+ // - Mutate the data context
+ const data = internalDataRef.current
+ const mutatedData = transformOut
+ ? mutateDataHandler(data, transformOut)
+ : data
- // - Mutate the data context
- const data = internalDataRef.current
- const mutatedData = transformOut
- ? mutateDataHandler(data, transformOut)
- : data
- const filteredData = filterSubmitData
- ? filterDataHandler(mutatedData, filterSubmitData)
- : mutatedData // @deprecated – can be removed in v11
-
- const reduceToVisibleFields: VisibleDataHandler = (
- data,
- options
- ) => {
- return visibleDataHandler(
- transformOut ? mutateDataHandler(data, transformOut) : data,
- options
- )
- }
+ // @deprecated – can be removed in v11 (use only mutatedData instead)
+ const filteredData = filterSubmitData
+ ? filterDataHandler(mutatedData, filterSubmitData)
+ : mutatedData
- const options = {
- filterData,
- reduceToVisibleFields,
- resetForm: () => {
- formElement?.reset?.()
+ return filteredData
+ }, [
+ filterDataHandler,
+ filterSubmitData,
+ mutateDataHandler,
+ transformOut,
+ ])
- if (typeof window !== 'undefined') {
- if (sessionStorageId) {
- window.sessionStorage.removeItem(sessionStorageId)
- }
- }
+ const getSubmitOptions = useCallback(() => {
+ const reduceToVisibleFields: VisibleDataHandler = (
+ data,
+ options
+ ) => {
+ return visibleDataHandler(
+ transformOut ? mutateDataHandler(data, transformOut) : data,
+ options
+ )
+ }
+ const formElement = formElementRef.current
+ const options: OnSubmitParams = {
+ filterData,
+ reduceToVisibleFields,
+ resetForm: () => {
+ formElement?.reset?.()
- forceUpdate() // in order to fill "empty fields" again with their internal states
- },
- clearData,
+ if (typeof window !== 'undefined') {
+ if (sessionStorageId) {
+ window.sessionStorage.removeItem(sessionStorageId)
}
+ }
- let result = undefined
+ forceUpdate() // in order to fill "empty fields" again with their internal states
+ },
+ clearData,
+ }
- if (isAsync(onSubmit)) {
- result = await onSubmit(filteredData, options)
- } else {
- result = onSubmit?.(filteredData, options)
- }
+ return options
+ }, [
+ clearData,
+ filterData,
+ mutateDataHandler,
+ sessionStorageId,
+ transformOut,
+ visibleDataHandler,
+ ])
- const completeResult = await onSubmitComplete?.(
- filteredData,
- result
- )
- if (completeResult) {
- result =
- Object.keys(result).length > 0
- ? { ...result, ...completeResult }
- : completeResult
- }
+ /**
+ * Request to submit the whole form
+ */
+ const handleSubmit = useCallback<
+ ContextState['handleSubmit']
+ >(async () => {
+ return await handleSubmitCall({
+ enableAsyncBehavior: isAsync(onSubmit),
+ onSubmit: async () => {
+ if (handleSubmitListeners()) {
+ return // stop here
+ }
- if (scrollTopOnSubmit) {
- scrollToTop()
- }
+ const data = getSubmitData()
+ const options = getSubmitOptions()
+ let result = undefined
- return result
- },
- })
- },
- [
- clearData,
- filterData,
- filterDataHandler,
- filterSubmitData,
- handleSubmitCall,
- handleSubmitListeners,
- mutateDataHandler,
- onSubmit,
- onSubmitComplete,
- scrollToTop,
- scrollTopOnSubmit,
- sessionStorageId,
- transformOut,
- visibleDataHandler,
- ]
- )
+ if (isAsync(onSubmit)) {
+ result = await onSubmit(data, options)
+ } else {
+ result = onSubmit?.(data, options)
+ }
+
+ const completeResult = await onSubmitComplete?.(data, result)
+ if (completeResult) {
+ result =
+ Object.keys(result).length > 0
+ ? { ...result, ...completeResult }
+ : completeResult
+ }
+
+ if (scrollTopOnSubmit) {
+ scrollToTop()
+ }
+
+ return result
+ },
+ })
+ }, [
+ getSubmitData,
+ getSubmitOptions,
+ handleSubmitCall,
+ handleSubmitListeners,
+ onSubmit,
+ onSubmitComplete,
+ scrollToTop,
+ scrollTopOnSubmit,
+ ])
// Collect listeners to be called during form submit
const fieldEventListenersRef = useRef>([])
@@ -1201,6 +1244,7 @@ export default function Provider(
info: undefined,
warning: undefined,
error: undefined,
+ customStatus: undefined,
})
}, [setFormState, setSubmitState])
@@ -1248,6 +1292,8 @@ export default function Provider(
clearData,
visibleDataHandler,
filterDataHandler,
+ getSubmitData,
+ getSubmitOptions,
addOnChangeHandler,
setHandleSubmit,
scrollToTop,
@@ -1268,6 +1314,7 @@ export default function Provider(
fieldPropsRef,
valuePropsRef,
mountedFieldsRef,
+ formElementRef,
ajvInstance: ajvRef.current,
/** Additional */
diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Element/Element.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/Element/Element.tsx
index 6d72764526f..d26e9914899 100644
--- a/packages/dnb-eufemia/src/extensions/forms/Form/Element/Element.tsx
+++ b/packages/dnb-eufemia/src/extensions/forms/Form/Element/Element.tsx
@@ -25,7 +25,11 @@ export default function FormElement({
event?.preventDefault()
const formElement = event.target as HTMLFormElement
- dataContext?.handleSubmit?.({ formElement })
+
+ if (dataContext.hasContext) {
+ dataContext.formElementRef.current = formElement
+ dataContext.handleSubmit()
+ }
if (typeof onSubmit === 'function') {
onSubmit(event)
diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/SubmitConfirmation/SubmitConfirmation.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/SubmitConfirmation/SubmitConfirmation.tsx
new file mode 100644
index 00000000000..4cbca8098c0
--- /dev/null
+++ b/packages/dnb-eufemia/src/extensions/forms/Form/SubmitConfirmation/SubmitConfirmation.tsx
@@ -0,0 +1,212 @@
+import React, {
+ useCallback,
+ useContext,
+ useMemo,
+ useReducer,
+ useRef,
+} from 'react'
+import DataContext from '../../DataContext/Context'
+import SharedProvider from '../../../../shared/Provider'
+import { ContextProps } from '../../../../shared/Context'
+import { HeightAnimation } from '../../../../components'
+import {
+ DialogContentProps,
+ DialogProps,
+} from '../../../../components/dialog/types'
+import { EventStateObject } from '../../types'
+import { removeUndefinedProps } from '../../../../shared/component-helper'
+
+export type ConfirmationState =
+ | 'idle'
+ | 'readyToBeSubmitted'
+ | 'submitInProgress'
+ | 'submissionComplete'
+
+export type ConfirmParams = {
+ data: unknown
+ confirmationState: ConfirmationState
+ submitState: EventStateObject | undefined
+ connectWithDialog: Pick<
+ DialogProps & DialogContentProps,
+ 'openState' | 'onConfirm' | 'onDecline' | 'onClose'
+ >
+ setConfirmationState: (state: ConfirmationState) => void
+ submitHandler: () => void | Promise
+ cancelHandler: () => void | Promise
+}
+
+export type ConfirmProps = {
+ preventSubmitWhen?: (params: ConfirmParams) => boolean
+ onStateChange?: (params: ConfirmParams) => void | Promise
+ onSubmitResult?: (params: ConfirmParams) => void
+ renderWithState?: (params: ConfirmParams) => React.ReactNode
+ children?: React.ReactNode
+}
+
+function SubmitConfirmation(props: ConfirmProps) {
+ const [, forceUpdate] = useReducer(() => ({}), {})
+
+ const {
+ preventSubmitWhen,
+ onStateChange,
+ onSubmitResult,
+ renderWithState,
+ children,
+ } = props
+
+ const {
+ setFormState,
+ setHandleSubmit,
+ handleSubmit: handleFinalSubmit,
+ submitState,
+ formElementRef,
+ internalDataRef,
+ } = useContext(DataContext)
+
+ const confirmationStateRef = useRef('idle')
+ const submitStateRef = useRef()
+ const preventSubmitRef = useRef(undefined)
+
+ const validatePreventSubmit = useCallback(() => {
+ return (preventSubmitRef.current = preventSubmitWhen?.(
+ getParamsRef.current()
+ ))
+ }, [preventSubmitWhen])
+
+ const setConfirmationState = useCallback(
+ async (state: ConfirmationState) => {
+ confirmationStateRef.current = state
+ await onStateChange?.(getParamsRef.current())
+ if (typeof window !== 'undefined') {
+ window.requestAnimationFrame(() => {
+ switch (state) {
+ case 'idle':
+ setFormState('complete', { keepPending: false })
+ break
+ case 'readyToBeSubmitted':
+ setFormState('pending', { keepPending: true })
+ break
+ case 'submitInProgress':
+ setFormState('pending', { keepPending: true })
+ break
+ case 'submissionComplete':
+ setFormState('complete', { keepPending: false })
+ break
+ default:
+ forceUpdate()
+ }
+ })
+ }
+ },
+ [onStateChange, setFormState]
+ )
+
+ const getParamsRef = useRef(() => {
+ const confirmationState = confirmationStateRef.current
+
+ const connectWithDialog = {
+ openState: confirmationState === 'readyToBeSubmitted',
+ onConfirm: submitHandler,
+ onDecline: cancelHandler,
+ onClose: ({ triggeredBy }) => {
+ if (triggeredBy === 'keyboard') {
+ cancelHandler()
+ }
+ },
+ }
+
+ return {
+ data: internalDataRef.current,
+ confirmationState,
+ setConfirmationState,
+ submitHandler,
+ cancelHandler,
+ connectWithDialog,
+ submitState: submitStateRef.current,
+ } satisfies ConfirmParams
+ })
+
+ useMemo(() => {
+ if (Object.keys(removeUndefinedProps(submitState)).length > 0) {
+ submitStateRef.current = {
+ ...submitState,
+ } as EventStateObject
+ onSubmitResult?.(getParamsRef.current())
+ }
+ }, [submitState, onSubmitResult])
+
+ const setFocusOnButton = useCallback(() => {
+ try {
+ const form = formElementRef.current
+ const element = (form.querySelector('.dnb-forms-submit-button') ||
+ form) as HTMLElement
+ element.focus()
+ } catch (e) {
+ //
+ }
+ }, [formElementRef])
+
+ const cancelHandler = useCallback(async () => {
+ await setConfirmationState('idle')
+ setFocusOnButton()
+ }, [setFocusOnButton, setConfirmationState])
+
+ const handleSubmit = useCallback(
+ async ({ preventSubmit }) => {
+ if (confirmationStateRef.current === 'submitInProgress') {
+ return // stop here
+ }
+
+ if (validatePreventSubmit() !== true) {
+ await setConfirmationState('submitInProgress')
+ return // stop here
+ }
+
+ submitStateRef.current = undefined
+
+ // Prevent the form form from being submitted
+ preventSubmit()
+
+ await setConfirmationState('readyToBeSubmitted')
+ },
+ [setConfirmationState, validatePreventSubmit]
+ )
+ setHandleSubmit?.(handleSubmit)
+
+ const submitHandler = useCallback(async () => {
+ setHandleSubmit?.(handleSubmit, { remove: true })
+
+ await setConfirmationState('submitInProgress')
+ await handleFinalSubmit()
+ await setConfirmationState('submissionComplete')
+
+ setFocusOnButton()
+ }, [
+ handleFinalSubmit,
+ handleSubmit,
+ setFocusOnButton,
+ setHandleSubmit,
+ setConfirmationState,
+ ])
+
+ const sharedProviderParams: ContextProps = {
+ formElement: {
+ disabled: false,
+ },
+ }
+
+ return (
+ <>
+ {children}
+
+
+
+ {renderWithState?.(getParamsRef.current())}
+
+
+ >
+ )
+}
+
+SubmitConfirmation._supportsSpacingProps = 'children'
+export default SubmitConfirmation
diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/SubmitConfirmation/SubmitConfirmationDocs.ts b/packages/dnb-eufemia/src/extensions/forms/Form/SubmitConfirmation/SubmitConfirmationDocs.ts
new file mode 100644
index 00000000000..4ebe9542c5e
--- /dev/null
+++ b/packages/dnb-eufemia/src/extensions/forms/Form/SubmitConfirmation/SubmitConfirmationDocs.ts
@@ -0,0 +1,24 @@
+import { PropertiesTableProps } from '../../../../shared/types'
+
+export const SubmitConfirmationProperties: PropertiesTableProps = {
+ preventSubmitWhen: {
+ doc: 'Use this function to prevent the original `onSubmit` from being called. It receives an object as the first parameter. Read more about the parameters in the info section. It should return a boolean value that determines whether the confirmation routine (submit prevention) should be active or not. It defaults to be active by default.',
+ type: 'function',
+ status: 'optional',
+ },
+ onSubmitResult: {
+ doc: 'This function is called whenever the `onSubmit` event returns a result. It receives an object as the first parameter, including the `submitState`. Read more about the parameters in the info section.',
+ type: 'function',
+ status: 'optional',
+ },
+ onStateChange: {
+ doc: 'This function is called whenever the submit confirmation state changes. It takes an object as the first parameter. Read more about the parameters in the info section.',
+ type: 'function',
+ status: 'optional',
+ },
+ renderWithState: {
+ doc: 'This function is called whenever the submit confirmation state changes. It receives an object as the first parameter. Read more about the parameters in the info section. The function is expected to return a React Element to render.',
+ type: 'function',
+ status: 'optional',
+ },
+}
diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/SubmitConfirmation/__tests__/SubmitConfirmation.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/SubmitConfirmation/__tests__/SubmitConfirmation.test.tsx
new file mode 100644
index 00000000000..d2aa2604e73
--- /dev/null
+++ b/packages/dnb-eufemia/src/extensions/forms/Form/SubmitConfirmation/__tests__/SubmitConfirmation.test.tsx
@@ -0,0 +1,647 @@
+import React from 'react'
+import { act, fireEvent, render, waitFor } from '@testing-library/react'
+import { Form } from '../../..'
+import { Button, Dialog } from '../../../../../components'
+import { ConfirmParams } from '../SubmitConfirmation'
+import userEvent from '@testing-library/user-event'
+
+describe('Form.SubmitConfirmation', () => {
+ describe('with preventSubmitWhen', () => {
+ it('should keep pending state when confirmationState is "readyToBeSubmitted"', async () => {
+ const onSubmit = jest.fn()
+ const confirmationStateRef: React.MutableRefObject<
+ ConfirmParams['confirmationState']
+ > = React.createRef()
+ const submitHandlerRef: React.MutableRefObject<
+ ConfirmParams['submitHandler']
+ > = React.createRef()
+
+ render(
+
+ true}
+ onStateChange={({ submitHandler }) => {
+ submitHandlerRef.current = submitHandler
+ }}
+ renderWithState={({ confirmationState }) => {
+ confirmationStateRef.current = confirmationState
+ return null
+ }}
+ >
+
+
+
+ )
+
+ const submitButton = document.querySelector(
+ '.dnb-forms-submit-button'
+ )
+ await userEvent.click(submitButton)
+ expect(onSubmit).toHaveBeenCalledTimes(0)
+
+ await waitFor(() => {
+ expect(
+ submitButton.querySelector(
+ '.dnb-forms-submit-indicator--state-pending'
+ )
+ ).toBeTruthy()
+ })
+
+ await act(submitHandlerRef.current)
+ expect(onSubmit).toHaveBeenCalledTimes(1)
+
+ await waitFor(() => {
+ expect(
+ submitButton.querySelector(
+ '.dnb-forms-submit-indicator--state-pending'
+ )
+ ).toBeFalsy()
+ })
+ })
+
+ it('should keep pending state when confirmationState is "readyToBeSubmitted" and onSubmit is async', async () => {
+ const onSubmit = jest.fn(async () => null)
+ const confirmationStateRef: React.MutableRefObject<
+ ConfirmParams['confirmationState']
+ > = React.createRef()
+ const submitHandlerRef: React.MutableRefObject<
+ ConfirmParams['submitHandler']
+ > = React.createRef()
+
+ render(
+
+ true}
+ onStateChange={({ submitHandler }) => {
+ submitHandlerRef.current = submitHandler
+ }}
+ renderWithState={({ confirmationState }) => {
+ confirmationStateRef.current = confirmationState
+ return null
+ }}
+ >
+
+
+
+ )
+
+ const submitButton = document.querySelector(
+ '.dnb-forms-submit-button'
+ )
+ await userEvent.click(submitButton)
+ expect(onSubmit).toHaveBeenCalledTimes(0)
+
+ expect(
+ submitButton.querySelector(
+ '.dnb-forms-submit-indicator--state-pending'
+ )
+ ).toBeTruthy()
+
+ await act(submitHandlerRef.current)
+ expect(onSubmit).toHaveBeenCalledTimes(1)
+
+ await waitFor(() => {
+ expect(
+ submitButton.querySelector(
+ '.dnb-forms-submit-indicator--state-pending'
+ )
+ ).toBeFalsy()
+ })
+ })
+
+ describe('focus handling', () => {
+ it('should set focus on submit button when submitHandler is called', async () => {
+ const submitHandlerRef: React.MutableRefObject<
+ ConfirmParams['submitHandler']
+ > = React.createRef()
+
+ render(
+
+ true}
+ onStateChange={({ submitHandler }) => {
+ submitHandlerRef.current = submitHandler
+ }}
+ >
+
+
+
+ )
+
+ expect(document.body).toHaveFocus()
+
+ const submitButton = document.querySelector(
+ '.dnb-forms-submit-button'
+ ) as HTMLButtonElement
+
+ const focus = jest.fn()
+ jest.spyOn(submitButton, 'focus').mockImplementation(focus)
+
+ await userEvent.click(submitButton)
+
+ await waitFor(() => {
+ expect(
+ submitButton.querySelector(
+ '.dnb-forms-submit-indicator--state-pending'
+ )
+ ).toBeTruthy()
+ })
+ expect(focus).toHaveBeenCalledTimes(1)
+
+ await act(submitHandlerRef.current)
+
+ await waitFor(() => {
+ expect(
+ submitButton.querySelector(
+ '.dnb-forms-submit-indicator--state-pending'
+ )
+ ).toBeFalsy()
+ })
+ expect(focus).toHaveBeenCalledTimes(2)
+ expect(focus).toHaveBeenLastCalledWith()
+ })
+
+ it('should set focus on submit button when cancelHandler is called', async () => {
+ const cancelHandlerRef: React.MutableRefObject<
+ ConfirmParams['cancelHandler']
+ > = React.createRef()
+
+ render(
+
+ true}
+ onStateChange={({ cancelHandler }) => {
+ cancelHandlerRef.current = cancelHandler
+ }}
+ >
+
+
+
+ )
+
+ expect(document.body).toHaveFocus()
+
+ const submitButton = document.querySelector(
+ '.dnb-forms-submit-button'
+ ) as HTMLButtonElement
+
+ const focus = jest.fn()
+ jest.spyOn(submitButton, 'focus').mockImplementation(focus)
+
+ await userEvent.click(submitButton)
+
+ await waitFor(() => {
+ expect(
+ submitButton.querySelector(
+ '.dnb-forms-submit-indicator--state-pending'
+ )
+ ).toBeTruthy()
+ })
+ expect(focus).toHaveBeenCalledTimes(1)
+
+ await act(cancelHandlerRef.current)
+
+ await waitFor(() => {
+ expect(
+ submitButton.querySelector(
+ '.dnb-forms-submit-indicator--state-pending'
+ )
+ ).toBeFalsy()
+ })
+ expect(focus).toHaveBeenCalledTimes(2)
+ expect(focus).toHaveBeenLastCalledWith()
+ })
+ })
+
+ it('when inactive, it should not prevent "onSubmit" from being called', async () => {
+ const onSubmit = jest.fn()
+ let preventSubmit = false
+
+ render(
+
+ preventSubmit}
+ onStateChange={({
+ confirmationState,
+ setConfirmationState,
+ }) => {
+ switch (confirmationState) {
+ case 'submitInProgress':
+ setConfirmationState('idle')
+ break
+ }
+ }}
+ >
+ content
+
+
+ )
+
+ fireEvent.submit(document.querySelector('form'))
+ expect(onSubmit).toHaveBeenCalledTimes(1)
+
+ preventSubmit = true
+
+ fireEvent.submit(document.querySelector('form'))
+ expect(onSubmit).toHaveBeenCalledTimes(1)
+
+ preventSubmit = false
+
+ fireEvent.submit(document.querySelector('form'))
+ expect(onSubmit).toHaveBeenCalledTimes(2)
+ })
+
+ it('should get result from "onSubmit" including "customStatus"', async () => {
+ const error = new Error('Error message')
+ const customStatus = 'custom'
+
+ const onSubmit = jest.fn(() => {
+ return { error, customStatus }
+ })
+ const preventSubmitWhen = jest.fn(() => {
+ return false
+ })
+ const onSubmitResult = jest.fn()
+
+ render(
+
+
+ content
+
+
+ )
+
+ await act(async () => {
+ fireEvent.submit(document.querySelector('form'))
+ })
+ expect(onSubmit).toHaveBeenCalledTimes(1)
+ expect(preventSubmitWhen).toHaveBeenCalledTimes(1)
+ expect(onSubmitResult).toHaveBeenCalledTimes(1)
+ expect(onSubmitResult).toHaveBeenLastCalledWith(
+ expect.objectContaining({
+ submitState: { error, customStatus },
+ })
+ )
+
+ await act(async () => {
+ fireEvent.submit(document.querySelector('form'))
+ })
+ expect(onSubmit).toHaveBeenCalledTimes(2)
+ expect(preventSubmitWhen).toHaveBeenCalledTimes(1)
+ expect(onSubmitResult).toHaveBeenCalledTimes(2)
+ expect(onSubmitResult).toHaveBeenLastCalledWith(
+ expect.objectContaining({
+ submitState: { error, customStatus },
+ })
+ )
+ })
+ })
+
+ describe('connectWithDialog', () => {
+ it('should provide "connectWithDialog"', async () => {
+ const onSubmit = jest.fn(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 100))
+ })
+ const confirmationStateRef: React.MutableRefObject<
+ ConfirmParams['confirmationState']
+ > = React.createRef()
+
+ render(
+
+
+
+ true}
+ onStateChange={async ({
+ confirmationState,
+ setConfirmationState,
+ }) => {
+ confirmationStateRef.current = confirmationState
+ await new Promise((resolve) => setTimeout(resolve, 100))
+ switch (confirmationState) {
+ case 'submissionComplete':
+ setConfirmationState('idle')
+ break
+ }
+ }}
+ renderWithState={({ connectWithDialog }) => {
+ return (
+
+ )
+ }}
+ />
+
+ )
+
+ expect(confirmationStateRef.current).toBe(null)
+
+ const submitButton = document.querySelector(
+ '.dnb-forms-submit-button'
+ )
+ await userEvent.click(submitButton)
+ expect(onSubmit).toHaveBeenCalledTimes(0)
+ expect(confirmationStateRef.current).toBe('readyToBeSubmitted')
+ await waitFor(() => {
+ expect(
+ submitButton.querySelector(
+ '.dnb-forms-submit-indicator--state-pending'
+ )
+ ).toBeTruthy()
+ })
+
+ const [cancelButton] = Array.from(
+ document.querySelectorAll('.dnb-dialog button')
+ )
+ await userEvent.click(cancelButton)
+ expect(confirmationStateRef.current).toBe('idle')
+
+ await waitFor(() => {
+ expect(submitButton).not.toBeDisabled()
+ })
+
+ await userEvent.click(submitButton)
+ expect(confirmationStateRef.current).toBe('readyToBeSubmitted')
+
+ const [, confirmButton] = Array.from(
+ document.querySelectorAll('.dnb-dialog button')
+ )
+ await userEvent.click(confirmButton)
+ await waitFor(() => {
+ expect(onSubmit).toHaveBeenCalledTimes(1)
+ })
+ expect(confirmationStateRef.current).toBe('submitInProgress')
+
+ await waitFor(() => {
+ expect(confirmationStateRef.current).toBe('submissionComplete')
+ })
+ await waitFor(() => {
+ expect(confirmationStateRef.current).toBe('idle')
+ })
+ })
+
+ it('should support esc key', async () => {
+ const onSubmit = jest.fn(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 100))
+ })
+ const confirmationStateRef: React.MutableRefObject<
+ ConfirmParams['confirmationState']
+ > = React.createRef()
+
+ render(
+
+
+
+ true}
+ onStateChange={({
+ confirmationState,
+ setConfirmationState,
+ }) => {
+ switch (confirmationState) {
+ case 'submissionComplete':
+ setConfirmationState('idle')
+ break
+ }
+ }}
+ renderWithState={({
+ confirmationState,
+ connectWithDialog,
+ }) => {
+ confirmationStateRef.current = confirmationState
+ return (
+
+ )
+ }}
+ />
+
+ )
+
+ expect(confirmationStateRef.current).toBe('idle')
+
+ const submitButton = document.querySelector(
+ '.dnb-forms-submit-button'
+ )
+ await userEvent.click(submitButton)
+ expect(onSubmit).toHaveBeenCalledTimes(0)
+ expect(confirmationStateRef.current).toBe('readyToBeSubmitted')
+ expect(
+ submitButton.querySelector(
+ '.dnb-forms-submit-indicator--state-pending'
+ )
+ ).toBeTruthy()
+
+ await act(async () => {
+ document.dispatchEvent(
+ new KeyboardEvent('keydown', { keyCode: 27 })
+ )
+ })
+ await waitFor(() => {
+ expect(confirmationStateRef.current).toBe('idle')
+ })
+ })
+ })
+
+ it('should render given content during the state changes', async () => {
+ const onSubmit = jest.fn(async () => {
+ await new Promise((resolve) => setTimeout(resolve, 100))
+ })
+
+ render(
+
+
+
+ true}
+ onStateChange={({ confirmationState, setConfirmationState }) => {
+ switch (confirmationState) {
+ case 'submissionComplete':
+ setConfirmationState('idle')
+ break
+ }
+ }}
+ renderWithState={({ confirmationState, connectWithDialog }) => {
+ let content = null
+
+ switch (confirmationState) {
+ case 'readyToBeSubmitted':
+ content =
+ break
+ case 'submitInProgress':
+ content =
+ break
+ }
+
+ return (
+ <>
+ {content}
+
+ >
+ )
+ }}
+ />
+
+ )
+
+ const submitButton = document.querySelector('.dnb-forms-submit-button')
+ await userEvent.click(submitButton)
+ expect(onSubmit).toHaveBeenCalledTimes(0)
+ expect(document.querySelector('output')).toHaveTextContent(
+ 'readyToBeSubmitted'
+ )
+ expect(
+ submitButton.querySelector(
+ '.dnb-forms-submit-indicator--state-pending'
+ )
+ ).toBeTruthy()
+
+ const [cancelButton] = Array.from(
+ document.querySelectorAll('.dnb-dialog button')
+ )
+ await userEvent.click(cancelButton)
+ expect(document.querySelector('output')).toHaveTextContent(
+ 'readyToBeSubmitted'
+ )
+ await waitFor(() => {
+ expect(document.querySelector('output')).toBeNull()
+ })
+
+ await userEvent.click(submitButton)
+ expect(document.querySelector('output')).toHaveTextContent(
+ 'readyToBeSubmitted'
+ )
+
+ const [, confirmButton] = Array.from(
+ document.querySelectorAll('.dnb-dialog button')
+ )
+ await userEvent.click(confirmButton)
+ expect(onSubmit).toHaveBeenCalledTimes(1)
+ expect(document.querySelector('output')).toHaveTextContent(
+ 'submitInProgress'
+ )
+
+ await waitFor(() => {
+ expect(document.querySelector('output')).toBeNull()
+ })
+ })
+
+ it('should prevent "onSubmit" from being called', () => {
+ const onSubmit = jest.fn()
+
+ const { rerender } = render(
+
+ true}>
+ content
+
+
+ )
+
+ const form = document.querySelector('form')
+ fireEvent.submit(form)
+
+ expect(onSubmit).toHaveBeenCalledTimes(0)
+
+ rerender(
+
+ true}>
+ content
+
+
+
+ )
+
+ fireEvent.click(document.querySelector('button'))
+ expect(onSubmit).toHaveBeenCalledTimes(0)
+ })
+
+ it('should call onSubmit when submitHandler is called', async () => {
+ const onSubmit = jest.fn()
+ const submitHandlerRef: React.MutableRefObject<
+ ConfirmParams['submitHandler']
+ > = React.createRef()
+
+ render(
+
+ true}
+ onStateChange={({ submitHandler }) => {
+ submitHandlerRef.current = submitHandler
+ }}
+ >
+ content
+
+
+ )
+
+ fireEvent.submit(document.querySelector('form'))
+ expect(onSubmit).toHaveBeenCalledTimes(0)
+
+ await act(submitHandlerRef.current)
+ expect(onSubmit).toHaveBeenCalledTimes(1)
+ })
+
+ it('should set confirmationState to idle when cancelHandler is called', async () => {
+ const confirmationStateRef: React.MutableRefObject<
+ ConfirmParams['confirmationState']
+ > = React.createRef()
+ const cancelHandlerRef: React.MutableRefObject<
+ ConfirmParams['cancelHandler']
+ > = React.createRef()
+
+ render(
+
+ true}
+ onStateChange={({ cancelHandler }) => {
+ cancelHandlerRef.current = cancelHandler
+ }}
+ renderWithState={({ confirmationState }) => {
+ confirmationStateRef.current = confirmationState
+ return null
+ }}
+ >
+ content
+
+
+ )
+
+ expect(confirmationStateRef.current).toBe('idle')
+ fireEvent.submit(document.querySelector('form'))
+ expect(confirmationStateRef.current).toBe('readyToBeSubmitted')
+
+ await act(cancelHandlerRef.current)
+ expect(confirmationStateRef.current).toBe('readyToBeSubmitted')
+ await waitFor(() => {
+ expect(confirmationStateRef.current).toBe('idle')
+ })
+ })
+
+ it('should not disable buttons when disabled is set to true', () => {
+ render(
+
+ true}
+ renderWithState={() => {
+ return
+ }}
+ />
+
+ )
+
+ expect(document.querySelector('button')).not.toBeDisabled()
+ })
+})
diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/SubmitConfirmation/index.ts b/packages/dnb-eufemia/src/extensions/forms/Form/SubmitConfirmation/index.ts
new file mode 100644
index 00000000000..1d850c27c76
--- /dev/null
+++ b/packages/dnb-eufemia/src/extensions/forms/Form/SubmitConfirmation/index.ts
@@ -0,0 +1,2 @@
+export { default } from './SubmitConfirmation'
+export * from './SubmitConfirmation'
diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/SubmitConfirmation/stories/SubmitConfirmation.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/SubmitConfirmation/stories/SubmitConfirmation.stories.tsx
new file mode 100644
index 00000000000..a97da46b440
--- /dev/null
+++ b/packages/dnb-eufemia/src/extensions/forms/Form/SubmitConfirmation/stories/SubmitConfirmation.stories.tsx
@@ -0,0 +1,91 @@
+import React from 'react'
+import { Field, Form } from '../../..'
+import { Dialog, Flex, Section } from '../../../../../components'
+
+export default {
+ title: 'Eufemia/Extensions/Forms/Form/SubmitConfirmation',
+}
+
+export function SubmitConfirmation() {
+ return (
+ {
+ console.log('Now we are submitting...', data)
+ await new Promise((resolve) => setTimeout(resolve, 3000))
+ return {
+ // error: new Error('Error message'),
+ customStatus: 'custom',
+ }
+ }}
+ >
+
+
+
+
+
+ {
+ // if (submitState?.customStatus) {
+ // setConfirmationState('readyToBeSubmitted')
+ // return false
+ // }
+ // return true
+ // }}
+ onSubmitResult={({ submitState, setConfirmationState }) => {
+ if (submitState?.customStatus) {
+ setConfirmationState('readyToBeSubmitted')
+ }
+ }}
+ renderWithState={({
+ submitState,
+ confirmationState,
+ connectWithDialog,
+ }) => {
+ let content = null
+
+ switch (confirmationState) {
+ case 'readyToBeSubmitted':
+ content = <>Is waiting ...>
+ break
+ case 'submitInProgress':
+ content = <>Submitting...>
+ break
+ }
+
+ return (
+ <>
+ {content}
+
+ >
+ )
+ }}
+ />
+
+ )
+}
diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/index.ts b/packages/dnb-eufemia/src/extensions/forms/Form/index.ts
index ddb4dd9a3bb..ae7fd03bd51 100644
--- a/packages/dnb-eufemia/src/extensions/forms/Form/index.ts
+++ b/packages/dnb-eufemia/src/extensions/forms/Form/index.ts
@@ -5,6 +5,7 @@ export { default as Element } from './Element'
export { default as Appearance } from './Appearance'
export { default as SubmitButton } from './SubmitButton'
export { default as SubmitIndicator } from './SubmitIndicator'
+export { default as SubmitConfirmation } from './SubmitConfirmation'
export { default as ButtonRow } from './ButtonRow'
export { default as MainHeading } from './MainHeading'
export { default as SubHeading } from './SubHeading'
diff --git a/packages/dnb-eufemia/src/extensions/forms/types.ts b/packages/dnb-eufemia/src/extensions/forms/types.ts
index 9e058eea074..fc1435518d1 100644
--- a/packages/dnb-eufemia/src/extensions/forms/types.ts
+++ b/packages/dnb-eufemia/src/extensions/forms/types.ts
@@ -530,6 +530,7 @@ export type EventStateObjectOr = {
warning?: EventStateObjectWarning
info?: EventStateObjectInfo
pending?: EventStateObjectStatus
+ customStatus?: unknown
}
export type EventStateObjectEitherOr =
@@ -537,6 +538,7 @@ export type EventStateObjectEitherOr =
| { warning: EventStateObjectWarning }
| { info: EventStateObjectInfo }
| { status: EventStateObjectStatus }
+ | { customStatus: unknown }
export type EventStateObject = EventStateObjectOr &
EventStateObjectEitherOr
@@ -574,6 +576,10 @@ export type OnSubmitParams = {
clearData: () => void
}
+export type OnSubmitReturn =
+ | EventReturnWithStateObject
+ | void
+ | Promise
export type OnSubmit = (
data: Data,
{
@@ -582,10 +588,7 @@ export type OnSubmit = (
resetForm,
clearData,
}: OnSubmitParams
-) =>
- | EventReturnWithStateObject
- | void
- | Promise
+) => OnSubmitReturn
export type OnCommit = (
data: Data,