@opendoor/partner-sdk-client-js-core 1.0.6 → 1.0.7-beta.56.1

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.
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Framework-agnostic form logic for the questionnaire engine.
3
+ *
4
+ * Handles condition evaluation, field validation, and page visibility.
5
+ */
6
+ import { isFieldCondition, isAndCondition, isOrCondition } from './types.js';
7
+ // ── Condition evaluation ──────────────────────────────────────────────────────
8
+ /**
9
+ * Recursively evaluates a Condition tree against the current answers.
10
+ */
11
+ export function evaluateCondition(condition, getAnswer) {
12
+ if (isAndCondition(condition)) {
13
+ return condition.and.every((c) => evaluateCondition(c, getAnswer));
14
+ }
15
+ if (isOrCondition(condition)) {
16
+ return condition.or.some((c) => evaluateCondition(c, getAnswer));
17
+ }
18
+ if (isFieldCondition(condition)) {
19
+ const answer = getAnswer(condition.field);
20
+ switch (condition.op) {
21
+ case 'eq':
22
+ return answer === condition.value;
23
+ case 'neq':
24
+ return answer !== condition.value;
25
+ case 'exists':
26
+ return answer !== undefined && answer !== null && answer !== '';
27
+ case 'notExists':
28
+ return answer === undefined || answer === null || answer === '';
29
+ case 'in': {
30
+ if (!Array.isArray(condition.value))
31
+ return false;
32
+ return condition.value.includes(answer);
33
+ }
34
+ case 'notIn': {
35
+ if (!Array.isArray(condition.value))
36
+ return true;
37
+ return !condition.value.includes(answer);
38
+ }
39
+ case 'contains': {
40
+ if (!Array.isArray(answer))
41
+ return false;
42
+ return answer.includes(condition.value);
43
+ }
44
+ default:
45
+ return false;
46
+ }
47
+ }
48
+ return false;
49
+ }
50
+ // ── Question visibility ───────────────────────────────────────────────────────
51
+ /**
52
+ * Returns the visible questions on a page, filtering out those whose
53
+ * showWhen condition evaluates to false.
54
+ */
55
+ export function getVisibleQuestions(page, getAnswer) {
56
+ return page.questions.filter((q) => !q.showWhen || evaluateCondition(q.showWhen, getAnswer));
57
+ }
58
+ // ── Page visibility ───────────────────────────────────────────────────────────
59
+ /**
60
+ * Returns the visible pages, filtering out those whose showWhen condition
61
+ * evaluates to false given current answers.
62
+ */
63
+ export function getVisiblePages(pages, getAnswer) {
64
+ return pages.filter((page) => !page.showWhen || evaluateCondition(page.showWhen, getAnswer));
65
+ }
66
+ // ── Field validation ──────────────────────────────────────────────────────────
67
+ /**
68
+ * Validates a single field. Returns an error message or undefined.
69
+ */
70
+ export function validateField(question, value) {
71
+ const isBlankString = typeof value === 'string' && value.trim() === '';
72
+ // Required check
73
+ if (question.required) {
74
+ if (value === undefined ||
75
+ value === null ||
76
+ value === '' ||
77
+ isBlankString) {
78
+ return 'This field is required';
79
+ }
80
+ if (Array.isArray(value) && value.length === 0) {
81
+ return 'Please select at least one option';
82
+ }
83
+ }
84
+ // Skip further validation if empty and not required
85
+ if (value === undefined || value === null || value === '' || isBlankString) {
86
+ return undefined;
87
+ }
88
+ // Number range checks
89
+ if (question.type === 'number' && typeof value === 'number') {
90
+ if (question.min !== undefined && value < question.min) {
91
+ return `Must be at least ${question.min}`;
92
+ }
93
+ if (question.max !== undefined && value > question.max) {
94
+ return `Must be at most ${question.max}`;
95
+ }
96
+ }
97
+ // Custom validator
98
+ if (question.validate) {
99
+ return question.validate(value);
100
+ }
101
+ return undefined;
102
+ }
103
+ // ── Page validation ───────────────────────────────────────────────────────────
104
+ /**
105
+ * Validates all visible questions on a page.
106
+ * Returns a ValidationResult with field-level errors.
107
+ */
108
+ export function validatePage(page, getAnswer) {
109
+ const errors = {};
110
+ const visible = getVisibleQuestions(page, getAnswer);
111
+ for (const question of visible) {
112
+ const value = getAnswer(question.key);
113
+ const error = validateField(question, value);
114
+ if (error) {
115
+ errors[question.key] = error;
116
+ }
117
+ }
118
+ return {
119
+ valid: Object.keys(errors).length === 0,
120
+ errors,
121
+ };
122
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Internal questionnaire engine for DTC Onboarding Flow.
3
+ *
4
+ * This module is NOT part of the public API. It is consumed by
5
+ * client-react and client-vue framework wrappers.
6
+ *
7
+ * Import via: '@opendoor/partner-sdk-client-js-core/internal/questionnaire'
8
+ */
9
+ export type { AnswerValue, Condition, FieldCondition, AndCondition, OrCondition, OptionConfig, QuestionConfig, QuestionStyle, PageConfig, EngineEvent, ValidationResult, } from './types.js';
10
+ export { isFieldCondition, isAndCondition, isOrCondition } from './types.js';
11
+ export { evaluateCondition, getVisibleQuestions, getVisiblePages, validateField, validatePage, } from './formLogic.js';
12
+ export { DtcOnboardingEngine } from './engine.js';
13
+ export type { DtcOnboardingOptions } from './engine.js';
14
+ export { dtcOnboardingPages } from './flows/dtcOnboarding.js';
15
+ import { DtcOnboardingEngine } from './engine.js';
16
+ import type { DtcOnboardingOptions } from './engine.js';
17
+ /**
18
+ * Create a DTC Onboarding engine instance with the default 17-page flow.
19
+ */
20
+ export declare function createDtcOnboardingFlow(options?: DtcOnboardingOptions): DtcOnboardingEngine;
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/internal/questionnaire/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,YAAY,EACV,WAAW,EACX,SAAS,EACT,cAAc,EACd,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,cAAc,EACd,aAAa,EACb,UAAU,EACV,WAAW,EACX,gBAAgB,GACjB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAG7E,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,EACf,aAAa,EACb,YAAY,GACb,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,YAAY,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAGxD,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAI9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAGxD;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,CAAC,EAAE,oBAAoB,GAC7B,mBAAmB,CAErB"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Internal questionnaire engine for DTC Onboarding Flow.
3
+ *
4
+ * This module is NOT part of the public API. It is consumed by
5
+ * client-react and client-vue framework wrappers.
6
+ *
7
+ * Import via: '@opendoor/partner-sdk-client-js-core/internal/questionnaire'
8
+ */
9
+ export { isFieldCondition, isAndCondition, isOrCondition } from './types.js';
10
+ // Form logic
11
+ export { evaluateCondition, getVisibleQuestions, getVisiblePages, validateField, validatePage, } from './formLogic.js';
12
+ // Engine
13
+ export { DtcOnboardingEngine } from './engine.js';
14
+ // Flow definitions
15
+ export { dtcOnboardingPages } from './flows/dtcOnboarding.js';
16
+ // ── Factory function ──────────────────────────────────────────────────────────
17
+ import { DtcOnboardingEngine } from './engine.js';
18
+ import { dtcOnboardingPages } from './flows/dtcOnboarding.js';
19
+ /**
20
+ * Create a DTC Onboarding engine instance with the default 17-page flow.
21
+ */
22
+ export function createDtcOnboardingFlow(options) {
23
+ return new DtcOnboardingEngine(dtcOnboardingPages, options);
24
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Type system for the DTC Onboarding questionnaire engine.
3
+ *
4
+ * These types define the declarative configuration for questionnaire flows.
5
+ * They are framework-agnostic — consumed by both React and Vue wrappers.
6
+ */
7
+ export type AnswerValue = string | number | boolean | string[] | undefined;
8
+ export interface FieldCondition {
9
+ field: string;
10
+ op: 'eq' | 'neq' | 'in' | 'notIn' | 'exists' | 'notExists' | 'contains';
11
+ value?: AnswerValue;
12
+ }
13
+ export interface AndCondition {
14
+ and: Condition[];
15
+ }
16
+ export interface OrCondition {
17
+ or: Condition[];
18
+ }
19
+ export type Condition = FieldCondition | AndCondition | OrCondition;
20
+ export declare function isFieldCondition(c: Condition): c is FieldCondition;
21
+ export declare function isAndCondition(c: Condition): c is AndCondition;
22
+ export declare function isOrCondition(c: Condition): c is OrCondition;
23
+ export interface OptionConfig {
24
+ label: string;
25
+ value: string | number;
26
+ description?: string;
27
+ /** Image identifier resolved to a URL by the framework layer. */
28
+ imageId?: string;
29
+ /** When true, selecting this option deselects all others ("None of the above"). */
30
+ exclusive?: boolean;
31
+ /** Sub-text shown below the description. */
32
+ helperText?: string;
33
+ }
34
+ export type QuestionStyle = 'radio-card' | 'checkbox-card' | 'image-card' | 'dropdown' | 'input' | 'currency' | 'phone' | 'searchable-dropdown' | 'toggle' | 'email';
35
+ export interface QuestionConfig {
36
+ /** Dot-notation answer key (e.g., 'home.above_grade_sq_ft'). */
37
+ key: string;
38
+ /** Display label for the question. */
39
+ label: string;
40
+ /** Additional description shown below the label. */
41
+ description?: string;
42
+ /** Placeholder text for input fields. */
43
+ placeholder?: string;
44
+ /** Data type of the answer value. */
45
+ type: 'string' | 'number' | 'boolean' | 'string[]';
46
+ /** UI presentation style. */
47
+ style: QuestionStyle;
48
+ /** Available options for select-type questions. */
49
+ options?: OptionConfig[];
50
+ /** Whether the field is required for page validation. */
51
+ required?: boolean;
52
+ /** Minimum value for number inputs. */
53
+ min?: number;
54
+ /** Maximum value for number inputs. */
55
+ max?: number;
56
+ /** Suffix text displayed inside the input (e.g., 'sq. ft.'). */
57
+ suffix?: string;
58
+ /** Hide this question when condition evaluates to false. */
59
+ showWhen?: Condition;
60
+ /** Custom validation function. Returns error message or undefined. */
61
+ validate?: (value: AnswerValue) => string | undefined;
62
+ /** Auto-advance to next page on selection (for radio-card pages). */
63
+ autoAdvance?: boolean;
64
+ /**
65
+ * Data source identifier for dynamic options.
66
+ * The framework layer is responsible for fetching data.
67
+ * E.g., 'homebuilders' triggers a call to getHomebuilders().
68
+ */
69
+ dataSource?: string;
70
+ /** Default value to pre-populate. */
71
+ defaultValue?: AnswerValue;
72
+ }
73
+ export interface PageConfig {
74
+ /** Unique page identifier used for navigation and tracking. */
75
+ id: string;
76
+ /** Page title displayed as the heading. */
77
+ title: string;
78
+ /** Optional subtitle displayed below the title. */
79
+ subtitle?: string;
80
+ /** Questions on this page (usually 1 for DTC flow, multiple for confirm-home-details). */
81
+ questions: QuestionConfig[];
82
+ /** Skip this page when condition evaluates to false. */
83
+ showWhen?: Condition;
84
+ /** Override the "Next" button label (e.g., "See my offer" on contact page). */
85
+ submitLabel?: string;
86
+ }
87
+ export type EngineEvent = {
88
+ type: 'answerChange';
89
+ key: string;
90
+ value: AnswerValue;
91
+ } | {
92
+ type: 'pageChange';
93
+ pageIndex: number;
94
+ pageId: string;
95
+ } | {
96
+ type: 'validationError';
97
+ errors: Record<string, string>;
98
+ } | {
99
+ type: 'submit';
100
+ answers: Record<string, AnswerValue>;
101
+ };
102
+ export interface ValidationResult {
103
+ valid: boolean;
104
+ errors: Record<string, string>;
105
+ }
106
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/internal/questionnaire/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Type system for the DTC Onboarding questionnaire engine.
3
+ *
4
+ * These types define the declarative configuration for questionnaire flows.
5
+ * They are framework-agnostic — consumed by both React and Vue wrappers.
6
+ */
7
+ // ── Type guards ───────────────────────────────────────────────────────────────
8
+ export function isFieldCondition(c) {
9
+ return 'field' in c;
10
+ }
11
+ export function isAndCondition(c) {
12
+ return 'and' in c;
13
+ }
14
+ export function isOrCondition(c) {
15
+ return 'or' in c;
16
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opendoor/partner-sdk-client-js-core",
3
- "version": "1.0.6",
3
+ "version": "1.0.7-beta.56.1",
4
4
  "description": "Framework-agnostic client SDK for Opendoor partner integrations",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -13,6 +13,10 @@
13
13
  "./internal/tracking": {
14
14
  "import": "./dist/tracking.js",
15
15
  "types": "./dist/tracking.d.ts"
16
+ },
17
+ "./internal/questionnaire": {
18
+ "import": "./dist/internal/questionnaire/index.js",
19
+ "types": "./dist/internal/questionnaire/index.d.ts"
16
20
  }
17
21
  },
18
22
  "files": [