diff --git a/packages/javascript/src/index.ts b/packages/javascript/src/index.ts index 3a3a33bf..54fd8f10 100644 --- a/packages/javascript/src/index.ts +++ b/packages/javascript/src/index.ts @@ -58,6 +58,7 @@ export { } from './models/embedded-flow'; export type { EmbeddedFlowComponent, + EmbeddedFlowComponentAction, EmbeddedFlowResponseData, EmbeddedFlowExecuteRequestConfig, FlowExecutionError, diff --git a/packages/javascript/src/models/__tests__/embedded-flow.test.ts b/packages/javascript/src/models/__tests__/embedded-flow.test.ts new file mode 100644 index 00000000..6ce05492 --- /dev/null +++ b/packages/javascript/src/models/__tests__/embedded-flow.test.ts @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2026, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import {describe, expect, it} from 'vitest'; +import { + EmbeddedFlowComponent, + EmbeddedFlowComponentAction, + EmbeddedFlowComponentType, + EmbeddedFlowEventType, +} from '../embedded-flow'; + +describe('EmbeddedFlowComponentAction', () => { + it('accepts a ref-only action (defaults to SUBMIT semantics at the renderer)', () => { + const action: EmbeddedFlowComponentAction = {ref: 'action_signup'}; + expect(action.ref).toBe('action_signup'); + expect(action.eventType).toBeUndefined(); + }); + + it('accepts an explicit TRIGGER eventType', () => { + const action: EmbeddedFlowComponentAction = { + eventType: EmbeddedFlowEventType.Trigger, + ref: 'action_signup', + }; + expect(action.eventType).toBe('TRIGGER'); + }); + + it('accepts a string eventType for forward-compatibility', () => { + const action: EmbeddedFlowComponentAction = { + eventType: 'CUSTOM_EVENT', + ref: 'action_signup', + }; + expect(action.eventType).toBe('CUSTOM_EVENT'); + }); +}); + +describe('EmbeddedFlowComponent.action', () => { + it('is optional — a plain rich-text component has no action', () => { + const component: EmbeddedFlowComponent = { + id: 'text_1', + label: '
Hello
', + type: EmbeddedFlowComponentType.RichText, + }; + expect(component.action).toBeUndefined(); + }); + + it('can carry an action wiring on a RICH_TEXT component', () => { + const component: EmbeddedFlowComponent = { + action: {eventType: EmbeddedFlowEventType.Submit, ref: 'action_signup'}, + id: 'text_1', + label: 'Have an account? Sign in
', + type: EmbeddedFlowComponentType.RichText, + }; + expect(component.action?.ref).toBe('action_signup'); + expect(component.action?.eventType).toBe('SUBMIT'); + }); +}); diff --git a/packages/javascript/src/models/embedded-flow.ts b/packages/javascript/src/models/embedded-flow.ts index f2541a04..780194b8 100644 --- a/packages/javascript/src/models/embedded-flow.ts +++ b/packages/javascript/src/models/embedded-flow.ts @@ -244,6 +244,23 @@ export enum EmbeddedFlowEventType { Trigger = 'TRIGGER', } +/** + * Optional action wiring for otherwise-passive components such as RICH_TEXT. When + * set on a RICH_TEXT component, sentinel-marked anchors (``) + * inside the sanitized HTML dispatch a flow action instead of navigating. + * + * @experimental This interface may change in future versions + */ +export interface EmbeddedFlowComponentAction { + /** Reference of the flow action to dispatch when the sentinel anchor is clicked. */ + ref: string; + /** + * Event type controlling submission semantics. `TRIGGER` bypasses client-side + * validation; `SUBMIT` runs validation. Defaults to `SUBMIT` when omitted. + */ + eventType?: EmbeddedFlowEventType | string; +} + /** * Enhanced component interface for embedded flow components. * @@ -268,6 +285,14 @@ export enum EmbeddedFlowEventType { * @experimental This interface may change in future versions */ export interface EmbeddedFlowComponent { + /** + * Optional flow-action wiring for otherwise-passive components. On RICH_TEXT + * components, sentinel-marked anchors (``) inside the + * sanitized HTML dispatch this action. When absent, the component remains pure + * display. + */ + action?: EmbeddedFlowComponentAction; + /** * Alignment of children along the cross axis (for Stack components). */ diff --git a/packages/react/src/components/presentation/auth/AuthOptionFactory.tsx b/packages/react/src/components/presentation/auth/AuthOptionFactory.tsx index c7b9c590..90c5262d 100644 --- a/packages/react/src/components/presentation/auth/AuthOptionFactory.tsx +++ b/packages/react/src/components/presentation/auth/AuthOptionFactory.tsx @@ -21,6 +21,7 @@ import { FieldType, FlowMetadataResponse, EmbeddedFlowComponent, + EmbeddedFlowComponentAction, EmbeddedFlowComponentType, EmbeddedFlowTextVariant, EmbeddedFlowEventType, @@ -523,10 +524,69 @@ const createAuthComponentFromFlow = ( } case EmbeddedFlowComponentType.RichText: { + const richTextAction: EmbeddedFlowComponentAction | undefined = component.action; + + const dispatchRichTextAction = (): void => { + if (!richTextAction || !options.onSubmit) { + return; + } + const eventTypeValue = String(richTextAction.eventType ?? EmbeddedFlowEventType.Submit); + const shouldSkipValidation: boolean = eventTypeValue.toUpperCase() === String(EmbeddedFlowEventType.Trigger); + const syntheticAction: EmbeddedFlowComponent = { + eventType: eventTypeValue, + id: `${component.id}_action`, + ref: richTextAction.ref, + type: EmbeddedFlowComponentType.Action, + }; + const formData: RecordHave an account? Sign in
'), + onSubmit, + ); + + const anchor = container.querySelectorHave an account? Sign in
', { + eventType: EmbeddedFlowEventType.Submit, + ref: 'action_signin', + }), + onSubmit, + ); + + container.querySelectorHave an account? Sign up
', {ref: 'action_signup'}), + onSubmit, + ); + + container.querySelectorNot a link
', {ref: 'action_signup'}), + onSubmit, + ); + + container.querySelector