@wise/dynamic-flow-client 5.9.2 → 5.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/build/controller/FlowController.js +6 -1
  2. package/build/controller/executeRefresh.js +8 -2
  3. package/build/controller/executeRequest.js +3 -0
  4. package/build/controller/executeSubmission.js +1 -0
  5. package/build/controller/getResponseType.js +3 -3
  6. package/build/domain/components/step/ExternalConfirmationComponent.js +5 -9
  7. package/build/domain/mappers/mapStepToComponent.js +1 -1
  8. package/build/domain/mappers/schema/blobSchemaToComponent.js +2 -2
  9. package/build/domain/mappers/schema/tests/test-utils.js +1 -1
  10. package/build/i18n/fr.json +1 -1
  11. package/build/main.css +4 -0
  12. package/build/main.js +35 -21
  13. package/build/main.mjs +35 -21
  14. package/build/renderers/mappers/externalComponentToProps.js +1 -1
  15. package/build/stories/spec/behavior/Copy.story.js +14 -2
  16. package/build/stories/spec/behavior/Link.story.js +40 -0
  17. package/build/stories/spec/behavior/Modal.story.js +4 -1
  18. package/build/stories/spec/layouts/Upsell.story.js +1 -1
  19. package/build/stories/spec/step/ScrollToBottom.story.js +103 -0
  20. package/build/test-utils/DynamicFlowWise.js +1 -1
  21. package/build/test-utils/DynamicFlowWiseModal.js +1 -1
  22. package/build/test-utils/openLinkInNewTab.js +15 -0
  23. package/build/tests/NoOp.test.js +194 -0
  24. package/build/tests/ScrollToBottom.test.js +122 -0
  25. package/build/tests/SingleFileUpload.test.js +81 -1
  26. package/build/tests/Submission.test.js +163 -18
  27. package/build/tests/Upsell.test.js +34 -6
  28. package/build/types/controller/FlowController.d.ts +0 -1
  29. package/build/types/controller/FlowController.d.ts.map +1 -1
  30. package/build/types/controller/executeRefresh.d.ts +1 -1
  31. package/build/types/controller/executeRefresh.d.ts.map +1 -1
  32. package/build/types/controller/executeRequest.d.ts +2 -0
  33. package/build/types/controller/executeRequest.d.ts.map +1 -1
  34. package/build/types/controller/executeSubmission.d.ts.map +1 -1
  35. package/build/types/controller/getResponseType.d.ts +2 -2
  36. package/build/types/controller/getResponseType.d.ts.map +1 -1
  37. package/build/types/domain/components/step/ExternalConfirmationComponent.d.ts +2 -3
  38. package/build/types/domain/components/step/ExternalConfirmationComponent.d.ts.map +1 -1
  39. package/build/types/domain/mappers/mapStepToComponent.d.ts.map +1 -1
  40. package/build/types/domain/mappers/schema/types.d.ts +1 -0
  41. package/build/types/domain/mappers/schema/types.d.ts.map +1 -1
  42. package/build/types/renderers/mappers/externalComponentToProps.d.ts.map +1 -1
  43. package/build/types/test-utils/openLinkInNewTab.d.ts.map +1 -0
  44. package/package.json +5 -5
  45. package/build/types/utils/openLinkInNewTab.d.ts.map +0 -1
  46. package/build/utils/openLinkInNewTab.js +0 -10
  47. /package/build/types/{utils → test-utils}/openLinkInNewTab.d.ts +0 -0
@@ -58,6 +58,7 @@ export const createFlowController = (props) => {
58
58
  features,
59
59
  httpClient,
60
60
  onBehavior,
61
+ onLink,
61
62
  onPoll,
62
63
  onValueChange,
63
64
  });
@@ -273,6 +274,10 @@ export const createFlowController = (props) => {
273
274
  rootComponent.setLoadingState('idle');
274
275
  break;
275
276
  }
277
+ case 'no-op': {
278
+ rootComponent.setLoadingState('idle');
279
+ break;
280
+ }
276
281
  }
277
282
  }
278
283
  catch (error) {
@@ -301,7 +306,7 @@ export const createFlowController = (props) => {
301
306
  updateStep(Object.assign(Object.assign({}, command.step), { errors }), command.etag);
302
307
  }
303
308
  break;
304
- case 'noop':
309
+ case 'no-op':
305
310
  rootComponent.setLoadingState('idle');
306
311
  break;
307
312
  case 'error': {
@@ -1,5 +1,6 @@
1
1
  import { getErrorMessage } from './getErrorMessage';
2
2
  import { assertStepResponseBody, isErrorResponseBody, parseResponseBodyAsJsonElement, } from './response-utils';
3
+ import { getResponseType } from './getResponseType';
3
4
  export const executeRefresh = async (props) => {
4
5
  const { abortSignal, url, model, etag, analytics, httpClient, trackEvent, logEvent } = props;
5
6
  trackEvent('Refresh Triggered', analytics);
@@ -12,7 +13,7 @@ export const executeRefresh = async (props) => {
12
13
  });
13
14
  if (response.status === 304) {
14
15
  trackEvent('Refresh Succeeded', analytics);
15
- return { type: 'noop' };
16
+ return { type: 'no-op' };
16
17
  }
17
18
  if (!response.ok) {
18
19
  const responseBody = await parseResponseBodyAsJsonElement(response).catch(() => ({}));
@@ -23,6 +24,11 @@ export const executeRefresh = async (props) => {
23
24
  }
24
25
  const newEtag = response.headers.get('etag') || null;
25
26
  const body = await parseResponseBodyAsJsonElement(response);
27
+ const responseType = getResponseType(response.headers, body);
28
+ if (responseType === 'no-op') {
29
+ trackEvent('Refresh Succeeded', analytics);
30
+ return { type: 'no-op' };
31
+ }
26
32
  assertStepResponseBody(body);
27
33
  trackEvent('Refresh Succeeded', analytics);
28
34
  return { type: 'refresh-step', step: body, etag: newEtag };
@@ -30,7 +36,7 @@ export const executeRefresh = async (props) => {
30
36
  catch (error) {
31
37
  if (error instanceof DOMException && error.name === 'AbortError') {
32
38
  trackEvent('Refresh Aborted', analytics);
33
- return { type: 'noop' };
39
+ return { type: 'no-op' };
34
40
  }
35
41
  trackEvent('Refresh Failed', analytics);
36
42
  logEvent('error', 'Dynamic Flow - Refresh Failed', Object.assign(Object.assign({}, analytics), { errorMessage: getErrorMessage(error) }));
@@ -60,6 +60,9 @@ export const executeRequest = async (props) => {
60
60
  assertModalResponseBody(responseBody);
61
61
  return { type: 'behavior', behavior: Object.assign(Object.assign({}, responseBody), { type: 'modal' }) };
62
62
  }
63
+ case 'no-op': {
64
+ return { type: 'no-op' };
65
+ }
63
66
  default: {
64
67
  throw new Error(`Unsupported response type: ${String(responseType)}`);
65
68
  }
@@ -48,6 +48,7 @@ export const executeSubmission = async (props) => {
48
48
  trackSubmissionEvent('Action Succeeded', { actionId });
49
49
  return Object.assign(Object.assign({}, command), { result: recursiveMerge(command.result, result) });
50
50
  }
51
+ case 'no-op':
51
52
  default: {
52
53
  trackSubmissionEvent('Action Succeeded', { actionId });
53
54
  return command;
@@ -1,7 +1,7 @@
1
1
  import { isObject } from '../utils/type-validators';
2
- const responseTypes = ['step', 'action', 'exit', 'modal', 'subflow'];
2
+ const responseTypes = ['step', 'action', 'exit', 'modal', 'subflow', 'no-op'];
3
3
  /**
4
- * Returns either 'step', 'action', or 'exit' based on the response headers and body.
4
+ * Returns either 'step', 'action', 'exit', 'modal', 'subflow', or 'no-op' based on the response headers and body.
5
5
  * This function takes a response body parameter because, for legacy reasons, we still need to consider the body
6
6
  * to determine the response type, in cases where the response headers are not set.
7
7
  * Ideally it should just be a matter of checking the "X-Df-Response-Type" response header.
@@ -30,6 +30,6 @@ export const getResponseTypeFromHeader = (headers) => {
30
30
  };
31
31
  function assertDFResponseType(type) {
32
32
  if (!responseTypes.includes(type)) {
33
- throw new Error("Unsupported X-Df-Response-Type. Allowed values are 'step', 'action', 'exit', 'error', 'modal', 'subflow'.");
33
+ throw new Error("Unsupported X-Df-Response-Type. Allowed values are 'step', 'action', 'exit', 'modal', 'subflow', 'no-op'.");
34
34
  }
35
35
  }
@@ -1,5 +1,5 @@
1
1
  import { getInputUpdateFunction } from '../utils/component-utils';
2
- export const createExternalConfirmation = (uid, url, onComponentUpdate) => {
2
+ export const createExternalConfirmation = (uid, url, onLink, onComponentUpdate) => {
3
3
  const update = getInputUpdateFunction(onComponentUpdate);
4
4
  return {
5
5
  type: 'external-confirmation',
@@ -7,15 +7,11 @@ export const createExternalConfirmation = (uid, url, onComponentUpdate) => {
7
7
  uid,
8
8
  url,
9
9
  status: 'initial',
10
- onSuccess() {
11
- update(this, (draft) => {
12
- draft.status = 'success';
13
- });
14
- },
15
- onFailure() {
16
- if (this.status === 'initial') {
10
+ open() {
11
+ if (this.status === 'initial' || this.status === 'failure') {
12
+ const success = onLink(this.url);
17
13
  update(this, (draft) => {
18
- draft.status = 'failure';
14
+ draft.status = success ? 'success' : 'failure';
19
15
  });
20
16
  }
21
17
  },
@@ -57,7 +57,7 @@ export const mapStepToComponent = (_a) => {
57
57
  : undefined;
58
58
  const stepPrefetch = getStepPrefetch(restProps.httpClient, flowRequestCache, submissionBehaviors);
59
59
  const externalConfirmation = (external === null || external === void 0 ? void 0 : external.url)
60
- ? createExternalConfirmation(`${uid}-external-confirmation`, external === null || external === void 0 ? void 0 : external.url, onComponentUpdate)
60
+ ? createExternalConfirmation(`${uid}-external-confirmation`, external === null || external === void 0 ? void 0 : external.url, restProps.onLink, onComponentUpdate)
61
61
  : undefined;
62
62
  const mapperProps = Object.assign(Object.assign({}, restProps), { features,
63
63
  trackEvent,
@@ -3,12 +3,12 @@ import { createUploadInputComponent } from '../../components/UploadInputComponen
3
3
  import { getRequiredCheck } from '../../features/validation/value-checks';
4
4
  import { mapCommonSchemaProps } from './utils/mapCommonSchemaProps';
5
5
  export const blobSchemaToComponent = (schemaMapperProps, mapperProps) => {
6
- const { schema, localValue, required = false, onPersistAsync } = schemaMapperProps;
6
+ const { schema, localValue, model, required = false, onPersistAsync } = schemaMapperProps;
7
7
  const { accepts, cameraConfig, maxSize, source, validationMessages } = schema;
8
8
  const { getErrorMessageFunctions, onComponentUpdate, onValueChange } = mapperProps;
9
9
  const errorMessageFunctions = getErrorMessageFunctions(validationMessages);
10
10
  const validLocalValue = isFile(localValue) ? localValue : null;
11
- const value = onPersistAsync ? validLocalValue : null;
11
+ const value = onPersistAsync && model !== null ? validLocalValue : null;
12
12
  return createUploadInputComponent(Object.assign(Object.assign({}, mapCommonSchemaProps(schemaMapperProps)), { accepts, autoComplete: 'off', cameraConfig, format: 'blob', maxSize,
13
13
  source,
14
14
  value, checks: schema.hidden ? [] : [getRequiredCheck(required, errorMessageFunctions)], schemaOnChange: undefined, onValueChange }), onComponentUpdate);
@@ -4,7 +4,7 @@ import { mockErrorMessageFunctions } from '../../../features/validation/spec-uti
4
4
  import { FeatureFlags } from '../../utils/FeatureFlags';
5
5
  import { makeRequestCache } from '../../../features/prefetch/request-cache';
6
6
  export const getStepMapperProps = () => (Object.assign(Object.assign({}, getMockMapperProps()), { uid: 'root', flowRequestCache: makeRequestCache(), etag: null, loadingState: 'idle', trackEvent: vi.fn(), onPoll: vi.fn() }));
7
- export const getMockMapperProps = (mapperProps = {}) => (Object.assign({ step: {}, stepLocalValue: null, features: new FeatureFlags({}), onComponentUpdate: vi.fn(), onBehavior: vi.fn(), onValueChange: vi.fn(), getErrorMessageFunctions: vi.fn().mockReturnValue(mockErrorMessageFunctions), trackEvent: vi.fn(), logEvent: vi.fn(), httpClient: vi.fn(), registerSubmissionBehavior: vi.fn() }, mapperProps));
7
+ export const getMockMapperProps = (mapperProps = {}) => (Object.assign({ step: {}, stepLocalValue: null, features: new FeatureFlags({}), onComponentUpdate: vi.fn(), onBehavior: vi.fn(), onLink: vi.fn(), onValueChange: vi.fn(), getErrorMessageFunctions: vi.fn().mockReturnValue(mockErrorMessageFunctions), trackEvent: vi.fn(), logEvent: vi.fn(), httpClient: vi.fn(), registerSubmissionBehavior: vi.fn() }, mapperProps));
8
8
  export const getMockSchemaMapperProps = (schemaMapperProps) => (Object.assign({ uid: getRandomId(), localValue: null, model: null, required: false, validationErrors: null }, schemaMapperProps));
9
9
  export const getMockRendererMapperProps = () => ({
10
10
  render: vi.fn(),
@@ -24,7 +24,7 @@
24
24
  "dynamicFlows.ExternalConfirmation.open": "Ouvrir dans un nouvel onglet",
25
25
  "dynamicFlows.ExternalConfirmation.title": "Veuillez confirmer",
26
26
  "dynamicFlows.FileUploadSchema.maxFileSizeError": "Nous sommes désolés, ce fichier est trop volumineux. Veuillez télécharger un fichier plus petit.",
27
- "dynamicFlows.FileUploadSchema.wrongFileTypeError": "Nous sommes désolés, ce format de fichier n'est pas pris en charge. Veuillez en télécharger un autre.",
27
+ "dynamicFlows.FileUploadSchema.wrongFileTypeError": "Désolé, ce format de fichier n'est pas pris en charge. Veuillez en importer un autre.",
28
28
  "dynamicFlows.Help.ariaLabel": "Cliquez ici pour plus d'informations.",
29
29
  "dynamicFlows.MultiSelect.summary": "{first} et {count} de plus",
30
30
  "dynamicFlows.MultipleFileUploadSchema.maxFileSizeError": "Nous sommes désolés, ce fichier est trop volumineux. Veuillez télécharger un fichier plus petit.",
package/build/main.css CHANGED
@@ -212,6 +212,10 @@
212
212
  display: block !important;
213
213
  /* left-aligned by default */
214
214
  }
215
+ .df-button.circular {
216
+ display: flex;
217
+ justify-content: center;
218
+ }
215
219
  .df-button.small.align-center {
216
220
  display: block !important;
217
221
  margin-left: auto;
package/build/main.js CHANGED
@@ -266,7 +266,7 @@ var fr_default = {
266
266
  "dynamicFlows.ExternalConfirmation.open": "Ouvrir dans un nouvel onglet",
267
267
  "dynamicFlows.ExternalConfirmation.title": "Veuillez confirmer",
268
268
  "dynamicFlows.FileUploadSchema.maxFileSizeError": "Nous sommes d\xE9sol\xE9s, ce fichier est trop volumineux. Veuillez t\xE9l\xE9charger un fichier plus petit.",
269
- "dynamicFlows.FileUploadSchema.wrongFileTypeError": "Nous sommes d\xE9sol\xE9s, ce format de fichier n'est pas pris en charge. Veuillez en t\xE9l\xE9charger un autre.",
269
+ "dynamicFlows.FileUploadSchema.wrongFileTypeError": "D\xE9sol\xE9, ce format de fichier n'est pas pris en charge. Veuillez en importer un autre.",
270
270
  "dynamicFlows.Help.ariaLabel": "Cliquez ici pour plus d'informations.",
271
271
  "dynamicFlows.MultiSelect.summary": "{first} et {count} de plus",
272
272
  "dynamicFlows.MultipleFileUploadSchema.maxFileSizeError": "Nous sommes d\xE9sol\xE9s, ce fichier est trop volumineux. Veuillez t\xE9l\xE9charger un fichier plus petit.",
@@ -2496,7 +2496,7 @@ var modalToComponent = (uid, { content, title }, mapperProps, schemaComponents)
2496
2496
  );
2497
2497
 
2498
2498
  // src/domain/components/step/ExternalConfirmationComponent.ts
2499
- var createExternalConfirmation = (uid, url, onComponentUpdate) => {
2499
+ var createExternalConfirmation = (uid, url, onLink, onComponentUpdate) => {
2500
2500
  const update = getInputUpdateFunction(onComponentUpdate);
2501
2501
  return {
2502
2502
  type: "external-confirmation",
@@ -2504,15 +2504,11 @@ var createExternalConfirmation = (uid, url, onComponentUpdate) => {
2504
2504
  uid,
2505
2505
  url,
2506
2506
  status: "initial",
2507
- onSuccess() {
2508
- update(this, (draft) => {
2509
- draft.status = "success";
2510
- });
2511
- },
2512
- onFailure() {
2513
- if (this.status === "initial") {
2507
+ open() {
2508
+ if (this.status === "initial" || this.status === "failure") {
2509
+ const success = onLink(this.url);
2514
2510
  update(this, (draft) => {
2515
- draft.status = "failure";
2511
+ draft.status = success ? "success" : "failure";
2516
2512
  });
2517
2513
  }
2518
2514
  },
@@ -4445,12 +4441,12 @@ var initialiseBase64Value = async (uploadComponent) => {
4445
4441
 
4446
4442
  // src/domain/mappers/schema/blobSchemaToComponent.ts
4447
4443
  var blobSchemaToComponent = (schemaMapperProps, mapperProps) => {
4448
- const { schema, localValue, required = false, onPersistAsync } = schemaMapperProps;
4444
+ const { schema, localValue, model, required = false, onPersistAsync } = schemaMapperProps;
4449
4445
  const { accepts, cameraConfig, maxSize, source, validationMessages } = schema;
4450
4446
  const { getErrorMessageFunctions, onComponentUpdate, onValueChange } = mapperProps;
4451
4447
  const errorMessageFunctions = getErrorMessageFunctions(validationMessages);
4452
4448
  const validLocalValue = isFile(localValue) ? localValue : null;
4453
- const value = onPersistAsync ? validLocalValue : null;
4449
+ const value = onPersistAsync && model !== null ? validLocalValue : null;
4454
4450
  return createUploadInputComponent(
4455
4451
  __spreadProps(__spreadValues({}, mapCommonSchemaProps(schemaMapperProps)), {
4456
4452
  accepts,
@@ -6006,7 +6002,7 @@ var handleErrorResponse = async (response, actionId, trackEvent) => {
6006
6002
  };
6007
6003
 
6008
6004
  // src/controller/getResponseType.ts
6009
- var responseTypes = ["step", "action", "exit", "modal", "subflow"];
6005
+ var responseTypes = ["step", "action", "exit", "modal", "subflow", "no-op"];
6010
6006
  var getResponseType = (headers, body) => {
6011
6007
  const headerResponseType = getResponseTypeFromHeader(headers);
6012
6008
  if (headerResponseType) {
@@ -6031,7 +6027,7 @@ var getResponseTypeFromHeader = (headers) => {
6031
6027
  function assertDFResponseType(type) {
6032
6028
  if (!responseTypes.includes(type)) {
6033
6029
  throw new Error(
6034
- "Unsupported X-Df-Response-Type. Allowed values are 'step', 'action', 'exit', 'error', 'modal', 'subflow'."
6030
+ "Unsupported X-Df-Response-Type. Allowed values are 'step', 'action', 'exit', 'modal', 'subflow', 'no-op'."
6035
6031
  );
6036
6032
  }
6037
6033
  }
@@ -6109,6 +6105,9 @@ var executeRequest = async (props) => {
6109
6105
  assertModalResponseBody(responseBody);
6110
6106
  return { type: "behavior", behavior: __spreadProps(__spreadValues({}, responseBody), { type: "modal" }) };
6111
6107
  }
6108
+ case "no-op": {
6109
+ return { type: "no-op" };
6110
+ }
6112
6111
  default: {
6113
6112
  throw new Error(`Unsupported response type: ${String(responseType)}`);
6114
6113
  }
@@ -6177,6 +6176,7 @@ var executeSubmission = async (props) => {
6177
6176
  trackSubmissionEvent("Action Succeeded", { actionId });
6178
6177
  return __spreadProps(__spreadValues({}, command), { result: recursiveMerge(command.result, result) });
6179
6178
  }
6179
+ case "no-op":
6180
6180
  default: {
6181
6181
  trackSubmissionEvent("Action Succeeded", { actionId });
6182
6182
  return command;
@@ -6291,7 +6291,12 @@ var mapStepToComponent = (_a) => {
6291
6291
  }) : void 0;
6292
6292
  const stepRefreshAfter = refreshAfter ? getStepRefreshAfter({ refreshAfter, logEvent, onBehavior }) : void 0;
6293
6293
  const stepPrefetch = getStepPrefetch(restProps.httpClient, flowRequestCache, submissionBehaviors);
6294
- const externalConfirmation = (external == null ? void 0 : external.url) ? createExternalConfirmation(`${uid}-external-confirmation`, external == null ? void 0 : external.url, onComponentUpdate) : void 0;
6294
+ const externalConfirmation = (external == null ? void 0 : external.url) ? createExternalConfirmation(
6295
+ `${uid}-external-confirmation`,
6296
+ external == null ? void 0 : external.url,
6297
+ restProps.onLink,
6298
+ onComponentUpdate
6299
+ ) : void 0;
6295
6300
  const mapperProps = __spreadProps(__spreadValues({}, restProps), {
6296
6301
  features,
6297
6302
  trackEvent,
@@ -6503,7 +6508,7 @@ var executeRefresh = async (props) => {
6503
6508
  });
6504
6509
  if (response.status === 304) {
6505
6510
  trackEvent("Refresh Succeeded", analytics);
6506
- return { type: "noop" };
6511
+ return { type: "no-op" };
6507
6512
  }
6508
6513
  if (!response.ok) {
6509
6514
  const responseBody = await parseResponseBodyAsJsonElement(response).catch(() => ({}));
@@ -6518,13 +6523,18 @@ var executeRefresh = async (props) => {
6518
6523
  }
6519
6524
  const newEtag = response.headers.get("etag") || null;
6520
6525
  const body = await parseResponseBodyAsJsonElement(response);
6526
+ const responseType = getResponseType(response.headers, body);
6527
+ if (responseType === "no-op") {
6528
+ trackEvent("Refresh Succeeded", analytics);
6529
+ return { type: "no-op" };
6530
+ }
6521
6531
  assertStepResponseBody(body);
6522
6532
  trackEvent("Refresh Succeeded", analytics);
6523
6533
  return { type: "refresh-step", step: body, etag: newEtag };
6524
6534
  } catch (error) {
6525
6535
  if (error instanceof DOMException && error.name === "AbortError") {
6526
6536
  trackEvent("Refresh Aborted", analytics);
6527
- return { type: "noop" };
6537
+ return { type: "no-op" };
6528
6538
  }
6529
6539
  trackEvent("Refresh Failed", analytics);
6530
6540
  logEvent("error", "Dynamic Flow - Refresh Failed", __spreadProps(__spreadValues({}, analytics), {
@@ -6640,6 +6650,7 @@ var createFlowController = (props) => {
6640
6650
  features,
6641
6651
  httpClient,
6642
6652
  onBehavior,
6653
+ onLink,
6643
6654
  onPoll,
6644
6655
  onValueChange
6645
6656
  });
@@ -6889,6 +6900,10 @@ var createFlowController = (props) => {
6889
6900
  rootComponent.setLoadingState("idle");
6890
6901
  break;
6891
6902
  }
6903
+ case "no-op": {
6904
+ rootComponent.setLoadingState("idle");
6905
+ break;
6906
+ }
6892
6907
  }
6893
6908
  } catch (error) {
6894
6909
  closeWithError(error);
@@ -6920,7 +6935,7 @@ var createFlowController = (props) => {
6920
6935
  updateStep(__spreadProps(__spreadValues({}, command.step), { errors }), command.etag);
6921
6936
  }
6922
6937
  break;
6923
- case "noop":
6938
+ case "no-op":
6924
6939
  rootComponent.setLoadingState("idle");
6925
6940
  break;
6926
6941
  case "error": {
@@ -7514,9 +7529,8 @@ var externalComponentToProps = (component, rendererMapperProps) => {
7514
7529
  type: "external-confirmation",
7515
7530
  uid: component.uid,
7516
7531
  url: component.url,
7517
- status: component.status,
7518
- onSuccess: component.onSuccess.bind(component),
7519
- onFailure: component.onFailure.bind(component),
7532
+ visible: component.status === "failure",
7533
+ open: component.open.bind(component),
7520
7534
  onCancel: component.onCancel.bind(component)
7521
7535
  }, rendererMapperProps);
7522
7536
  };
package/build/main.mjs CHANGED
@@ -236,7 +236,7 @@ var fr_default = {
236
236
  "dynamicFlows.ExternalConfirmation.open": "Ouvrir dans un nouvel onglet",
237
237
  "dynamicFlows.ExternalConfirmation.title": "Veuillez confirmer",
238
238
  "dynamicFlows.FileUploadSchema.maxFileSizeError": "Nous sommes d\xE9sol\xE9s, ce fichier est trop volumineux. Veuillez t\xE9l\xE9charger un fichier plus petit.",
239
- "dynamicFlows.FileUploadSchema.wrongFileTypeError": "Nous sommes d\xE9sol\xE9s, ce format de fichier n'est pas pris en charge. Veuillez en t\xE9l\xE9charger un autre.",
239
+ "dynamicFlows.FileUploadSchema.wrongFileTypeError": "D\xE9sol\xE9, ce format de fichier n'est pas pris en charge. Veuillez en importer un autre.",
240
240
  "dynamicFlows.Help.ariaLabel": "Cliquez ici pour plus d'informations.",
241
241
  "dynamicFlows.MultiSelect.summary": "{first} et {count} de plus",
242
242
  "dynamicFlows.MultipleFileUploadSchema.maxFileSizeError": "Nous sommes d\xE9sol\xE9s, ce fichier est trop volumineux. Veuillez t\xE9l\xE9charger un fichier plus petit.",
@@ -2466,7 +2466,7 @@ var modalToComponent = (uid, { content, title }, mapperProps, schemaComponents)
2466
2466
  );
2467
2467
 
2468
2468
  // src/domain/components/step/ExternalConfirmationComponent.ts
2469
- var createExternalConfirmation = (uid, url, onComponentUpdate) => {
2469
+ var createExternalConfirmation = (uid, url, onLink, onComponentUpdate) => {
2470
2470
  const update = getInputUpdateFunction(onComponentUpdate);
2471
2471
  return {
2472
2472
  type: "external-confirmation",
@@ -2474,15 +2474,11 @@ var createExternalConfirmation = (uid, url, onComponentUpdate) => {
2474
2474
  uid,
2475
2475
  url,
2476
2476
  status: "initial",
2477
- onSuccess() {
2478
- update(this, (draft) => {
2479
- draft.status = "success";
2480
- });
2481
- },
2482
- onFailure() {
2483
- if (this.status === "initial") {
2477
+ open() {
2478
+ if (this.status === "initial" || this.status === "failure") {
2479
+ const success = onLink(this.url);
2484
2480
  update(this, (draft) => {
2485
- draft.status = "failure";
2481
+ draft.status = success ? "success" : "failure";
2486
2482
  });
2487
2483
  }
2488
2484
  },
@@ -4415,12 +4411,12 @@ var initialiseBase64Value = async (uploadComponent) => {
4415
4411
 
4416
4412
  // src/domain/mappers/schema/blobSchemaToComponent.ts
4417
4413
  var blobSchemaToComponent = (schemaMapperProps, mapperProps) => {
4418
- const { schema, localValue, required = false, onPersistAsync } = schemaMapperProps;
4414
+ const { schema, localValue, model, required = false, onPersistAsync } = schemaMapperProps;
4419
4415
  const { accepts, cameraConfig, maxSize, source, validationMessages } = schema;
4420
4416
  const { getErrorMessageFunctions, onComponentUpdate, onValueChange } = mapperProps;
4421
4417
  const errorMessageFunctions = getErrorMessageFunctions(validationMessages);
4422
4418
  const validLocalValue = isFile(localValue) ? localValue : null;
4423
- const value = onPersistAsync ? validLocalValue : null;
4419
+ const value = onPersistAsync && model !== null ? validLocalValue : null;
4424
4420
  return createUploadInputComponent(
4425
4421
  __spreadProps(__spreadValues({}, mapCommonSchemaProps(schemaMapperProps)), {
4426
4422
  accepts,
@@ -5976,7 +5972,7 @@ var handleErrorResponse = async (response, actionId, trackEvent) => {
5976
5972
  };
5977
5973
 
5978
5974
  // src/controller/getResponseType.ts
5979
- var responseTypes = ["step", "action", "exit", "modal", "subflow"];
5975
+ var responseTypes = ["step", "action", "exit", "modal", "subflow", "no-op"];
5980
5976
  var getResponseType = (headers, body) => {
5981
5977
  const headerResponseType = getResponseTypeFromHeader(headers);
5982
5978
  if (headerResponseType) {
@@ -6001,7 +5997,7 @@ var getResponseTypeFromHeader = (headers) => {
6001
5997
  function assertDFResponseType(type) {
6002
5998
  if (!responseTypes.includes(type)) {
6003
5999
  throw new Error(
6004
- "Unsupported X-Df-Response-Type. Allowed values are 'step', 'action', 'exit', 'error', 'modal', 'subflow'."
6000
+ "Unsupported X-Df-Response-Type. Allowed values are 'step', 'action', 'exit', 'modal', 'subflow', 'no-op'."
6005
6001
  );
6006
6002
  }
6007
6003
  }
@@ -6079,6 +6075,9 @@ var executeRequest = async (props) => {
6079
6075
  assertModalResponseBody(responseBody);
6080
6076
  return { type: "behavior", behavior: __spreadProps(__spreadValues({}, responseBody), { type: "modal" }) };
6081
6077
  }
6078
+ case "no-op": {
6079
+ return { type: "no-op" };
6080
+ }
6082
6081
  default: {
6083
6082
  throw new Error(`Unsupported response type: ${String(responseType)}`);
6084
6083
  }
@@ -6147,6 +6146,7 @@ var executeSubmission = async (props) => {
6147
6146
  trackSubmissionEvent("Action Succeeded", { actionId });
6148
6147
  return __spreadProps(__spreadValues({}, command), { result: recursiveMerge(command.result, result) });
6149
6148
  }
6149
+ case "no-op":
6150
6150
  default: {
6151
6151
  trackSubmissionEvent("Action Succeeded", { actionId });
6152
6152
  return command;
@@ -6261,7 +6261,12 @@ var mapStepToComponent = (_a) => {
6261
6261
  }) : void 0;
6262
6262
  const stepRefreshAfter = refreshAfter ? getStepRefreshAfter({ refreshAfter, logEvent, onBehavior }) : void 0;
6263
6263
  const stepPrefetch = getStepPrefetch(restProps.httpClient, flowRequestCache, submissionBehaviors);
6264
- const externalConfirmation = (external == null ? void 0 : external.url) ? createExternalConfirmation(`${uid}-external-confirmation`, external == null ? void 0 : external.url, onComponentUpdate) : void 0;
6264
+ const externalConfirmation = (external == null ? void 0 : external.url) ? createExternalConfirmation(
6265
+ `${uid}-external-confirmation`,
6266
+ external == null ? void 0 : external.url,
6267
+ restProps.onLink,
6268
+ onComponentUpdate
6269
+ ) : void 0;
6265
6270
  const mapperProps = __spreadProps(__spreadValues({}, restProps), {
6266
6271
  features,
6267
6272
  trackEvent,
@@ -6473,7 +6478,7 @@ var executeRefresh = async (props) => {
6473
6478
  });
6474
6479
  if (response.status === 304) {
6475
6480
  trackEvent("Refresh Succeeded", analytics);
6476
- return { type: "noop" };
6481
+ return { type: "no-op" };
6477
6482
  }
6478
6483
  if (!response.ok) {
6479
6484
  const responseBody = await parseResponseBodyAsJsonElement(response).catch(() => ({}));
@@ -6488,13 +6493,18 @@ var executeRefresh = async (props) => {
6488
6493
  }
6489
6494
  const newEtag = response.headers.get("etag") || null;
6490
6495
  const body = await parseResponseBodyAsJsonElement(response);
6496
+ const responseType = getResponseType(response.headers, body);
6497
+ if (responseType === "no-op") {
6498
+ trackEvent("Refresh Succeeded", analytics);
6499
+ return { type: "no-op" };
6500
+ }
6491
6501
  assertStepResponseBody(body);
6492
6502
  trackEvent("Refresh Succeeded", analytics);
6493
6503
  return { type: "refresh-step", step: body, etag: newEtag };
6494
6504
  } catch (error) {
6495
6505
  if (error instanceof DOMException && error.name === "AbortError") {
6496
6506
  trackEvent("Refresh Aborted", analytics);
6497
- return { type: "noop" };
6507
+ return { type: "no-op" };
6498
6508
  }
6499
6509
  trackEvent("Refresh Failed", analytics);
6500
6510
  logEvent("error", "Dynamic Flow - Refresh Failed", __spreadProps(__spreadValues({}, analytics), {
@@ -6610,6 +6620,7 @@ var createFlowController = (props) => {
6610
6620
  features,
6611
6621
  httpClient,
6612
6622
  onBehavior,
6623
+ onLink,
6613
6624
  onPoll,
6614
6625
  onValueChange
6615
6626
  });
@@ -6859,6 +6870,10 @@ var createFlowController = (props) => {
6859
6870
  rootComponent.setLoadingState("idle");
6860
6871
  break;
6861
6872
  }
6873
+ case "no-op": {
6874
+ rootComponent.setLoadingState("idle");
6875
+ break;
6876
+ }
6862
6877
  }
6863
6878
  } catch (error) {
6864
6879
  closeWithError(error);
@@ -6890,7 +6905,7 @@ var createFlowController = (props) => {
6890
6905
  updateStep(__spreadProps(__spreadValues({}, command.step), { errors }), command.etag);
6891
6906
  }
6892
6907
  break;
6893
- case "noop":
6908
+ case "no-op":
6894
6909
  rootComponent.setLoadingState("idle");
6895
6910
  break;
6896
6911
  case "error": {
@@ -7484,9 +7499,8 @@ var externalComponentToProps = (component, rendererMapperProps) => {
7484
7499
  type: "external-confirmation",
7485
7500
  uid: component.uid,
7486
7501
  url: component.url,
7487
- status: component.status,
7488
- onSuccess: component.onSuccess.bind(component),
7489
- onFailure: component.onFailure.bind(component),
7502
+ visible: component.status === "failure",
7503
+ open: component.open.bind(component),
7490
7504
  onCancel: component.onCancel.bind(component)
7491
7505
  }, rendererMapperProps);
7492
7506
  };
@@ -1,3 +1,3 @@
1
1
  export const externalComponentToProps = (component, rendererMapperProps) => {
2
- return Object.assign({ type: 'external-confirmation', uid: component.uid, url: component.url, status: component.status, onSuccess: component.onSuccess.bind(component), onFailure: component.onFailure.bind(component), onCancel: component.onCancel.bind(component) }, rendererMapperProps);
2
+ return Object.assign({ type: 'external-confirmation', uid: component.uid, url: component.url, visible: component.status === 'failure', open: component.open.bind(component), onCancel: component.onCancel.bind(component) }, rendererMapperProps);
3
3
  };
@@ -7,8 +7,11 @@ export default {
7
7
  chromatic: { disableSnapshot: true },
8
8
  },
9
9
  };
10
+ const httpClient = async () => new Promise((resolve) => {
11
+ setTimeout(resolve, 1000);
12
+ });
10
13
  export function Copy() {
11
- return renderWithStep(copyStep);
14
+ return renderWithStep(copyStep, httpClient);
12
15
  }
13
16
  const copyStep = {
14
17
  id: 'copy',
@@ -49,11 +52,20 @@ const copyStep = {
49
52
  {
50
53
  type: 'button',
51
54
  title: 'Click to copy',
52
- control: 'primary',
55
+ control: 'secondary',
53
56
  behavior: {
54
57
  type: 'copy',
55
58
  content: 'This is a button',
56
59
  },
57
60
  },
61
+ {
62
+ type: 'button',
63
+ title: 'Submit',
64
+ control: 'primary',
65
+ behavior: {
66
+ type: 'action',
67
+ action: { url: '/submit' },
68
+ },
69
+ },
58
70
  ],
59
71
  };
@@ -0,0 +1,40 @@
1
+ import DynamicFlowWise from '../../../test-utils/DynamicFlowWise';
2
+ import { renderWithStep } from '../../utils/render-utils';
3
+ export default {
4
+ component: DynamicFlowWise,
5
+ title: 'Spec/Behavior',
6
+ parameters: {
7
+ chromatic: { disableSnapshot: true },
8
+ },
9
+ };
10
+ const httpClient = async () => new Promise((resolve) => {
11
+ setTimeout(resolve, 1000);
12
+ });
13
+ export function Link() {
14
+ return renderWithStep(step, httpClient);
15
+ }
16
+ const step = {
17
+ id: 'copy',
18
+ title: 'Link Behavior',
19
+ schemas: [],
20
+ layout: [
21
+ {
22
+ type: 'button',
23
+ title: 'Open Google',
24
+ control: 'secondary',
25
+ behavior: {
26
+ type: 'link',
27
+ url: 'https://www.google.com',
28
+ },
29
+ },
30
+ {
31
+ type: 'button',
32
+ title: 'Submit',
33
+ control: 'primary',
34
+ behavior: {
35
+ type: 'action',
36
+ action: { url: '/submit' },
37
+ },
38
+ },
39
+ ],
40
+ };
@@ -7,8 +7,11 @@ export default {
7
7
  chromatic: { disableSnapshot: true },
8
8
  },
9
9
  };
10
+ const httpClient = async () => new Promise((resolve) => {
11
+ setTimeout(resolve, 1000);
12
+ });
10
13
  export function Modal() {
11
- return renderWithStep(initialStep);
14
+ return renderWithStep(initialStep, httpClient);
12
15
  }
13
16
  const initialStep = {
14
17
  id: 'modal',
@@ -33,7 +33,7 @@ const getStep = (urns, dismissable) => ({
33
33
  },
34
34
  callToAction: {
35
35
  title: 'Do the thing!',
36
- behavior: { type: 'action', action: { url: '/submit' } },
36
+ behavior: { type: 'link', url: 'https://google.com' },
37
37
  },
38
38
  onDismiss: dismissable ? { url: '/upsell-dismissed', method: 'POST' } : undefined,
39
39
  margin: 'lg',