@onboarding-yak/react-native 0.0.0-stage → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/units.js ADDED
@@ -0,0 +1,37 @@
1
+ const decimalsOf = (value) => (String(value).split('.')[1] ?? '').length;
2
+ /** The base unit first, then up to two switchable units with ranges derived from the base when left out. */
3
+ export function unitChoices(input) {
4
+ const step = input?.step && input.step > 0 ? input.step : 1;
5
+ const base = { unit: input?.unit ?? '', factor: 1, min: input?.min ?? 0, max: input?.max ?? 100, step };
6
+ const extra = (input?.units ?? []).filter((option) => option && typeof option.unit === 'string' && option.unit && Number.isFinite(option.factor) && option.factor > 0).slice(0, 2)
7
+ .map((option) => ({ unit: option.unit, factor: option.factor, min: option.min ?? Math.round(base.min * option.factor), max: option.max ?? Math.round(base.max * option.factor), step: option.step && option.step > 0 ? option.step : 1, display: option.display }));
8
+ return [base, ...extra];
9
+ }
10
+ export function unitValues(choice) {
11
+ const list = [];
12
+ for (let value = choice.min; value <= choice.max + 1e-9 && list.length < 2000; value += choice.step)
13
+ list.push(Math.round(value * 100) / 100);
14
+ return list;
15
+ }
16
+ /** 69 → 5′ 9″ for feet-inches; otherwise the number as is. */
17
+ export function unitLabel(value, choice) {
18
+ if (choice.display !== 'feet-inches')
19
+ return String(value);
20
+ const inches = Math.round(value);
21
+ return `${Math.floor(inches / 12)}′ ${inches % 12}″`;
22
+ }
23
+ /** Answers are always reported in the base unit, rounded to the base step's precision. */
24
+ export function toBaseUnit(value, choice, base) {
25
+ if (choice.factor === 1)
26
+ return value;
27
+ const places = decimalsOf(base.step);
28
+ return Math.round((value / choice.factor) * 10 ** places) / 10 ** places;
29
+ }
30
+ export function nearestIndex(values, target) {
31
+ let best = 0;
32
+ for (let index = 1; index < values.length; index += 1)
33
+ if (Math.abs(values[index] - target) < Math.abs(values[best] - target))
34
+ best = index;
35
+ return best;
36
+ }
37
+ //# sourceMappingURL=units.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"units.js","sourceRoot":"","sources":["../src/units.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,52 @@
1
+ export type VariableType = 'text' | 'number' | 'money' | 'date' | 'choice' | 'list' | 'country';
2
+ export type DateStyle = 'long' | 'medium' | 'short' | 'numeric';
3
+ /** How a question's saved answer is shown when recalled: a currency turns a number into money; dates pick a style. */
4
+ export type VariableFormat = {
5
+ currency?: string;
6
+ dateStyle?: DateStyle;
7
+ };
8
+ /** What the app receives for each variable: the raw value for logic or storage, and the text the onboarding showed. */
9
+ export type VariableValue = {
10
+ type: VariableType;
11
+ value: string | number | string[];
12
+ unit?: string;
13
+ currency?: string;
14
+ display: string;
15
+ };
16
+ /** Always available in copy; question answers can't use these names. */
17
+ export declare const BUILT_IN_VARIABLES: readonly ["app_name", "first_name", "today", "weekday"];
18
+ export declare const CURRENCY_SYMBOLS: Record<string, string>;
19
+ /** The variable type a question produces, from its answer format. */
20
+ export declare function variableType(screen: {
21
+ answerType?: string;
22
+ multiple?: unknown;
23
+ input?: {
24
+ format?: string;
25
+ };
26
+ variableFormat?: VariableFormat;
27
+ }): VariableType;
28
+ /** 3000 → "3,000"; 70.5 → "70.5". Done by hand so it reads the same on every device and JavaScript engine. */
29
+ export declare function formatNumber(value: number): string;
30
+ export declare function formatMoney(value: number, currency: string): string;
31
+ /** "1995-01-04" → "January 4, 1995" (long), "Jan 4, 1995" (medium), "Jan 4" (short), "1/4/1995" (numeric). */
32
+ export declare function formatDate(iso: string, style?: DateStyle): string;
33
+ /** ["Dairy", "Nuts", "Gluten"] → "Dairy, Nuts and Gluten". */
34
+ export declare function joinList(items: string[]): string;
35
+ /** A question's answer as a typed variable. `display` from a number wheel or field carries the unit the person chose (e.g. "154 lb"). */
36
+ export declare function variableValue(screen: {
37
+ answerType?: string;
38
+ multiple?: unknown;
39
+ input?: {
40
+ format?: string;
41
+ unit?: string;
42
+ };
43
+ variableFormat?: VariableFormat;
44
+ }, answer: string, picks?: string[], display?: string): VariableValue | null;
45
+ /** Values for the built-in variables. */
46
+ export declare function builtInValues(appName: string | undefined, values: Record<string, string>, now?: Date): {
47
+ app_name: string;
48
+ first_name: string;
49
+ today: string;
50
+ weekday: string;
51
+ };
52
+ //# sourceMappingURL=variables.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"variables.d.ts","sourceRoot":"","sources":["../src/variables.ts"],"names":[],"mappings":"AACA,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAChG,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;AAChE,sHAAsH;AACtH,MAAM,MAAM,cAAc,GAAG;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,SAAS,CAAA;CAAE,CAAC;AAC1E,uHAAuH;AACvH,MAAM,MAAM,aAAa,GAAG;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzI,wEAAwE;AACxE,eAAO,MAAM,kBAAkB,yDAA0D,CAAC;AAC1F,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAmO,CAAC;AAIxR,qEAAqE;AACrE,wBAAgB,YAAY,CAAC,MAAM,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,cAAc,CAAC,EAAE,cAAc,CAAA;CAAE,GAAG,YAAY,CAO5J;AAED,8GAA8G;AAC9G,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,UAKzC;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,UAG1D;AAED,8GAA8G;AAC9G,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,GAAE,SAAkB,UAShE;AAED,8DAA8D;AAC9D,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,UAGvC;AAED,yIAAyI;AACzI,wBAAgB,aAAa,CAAC,MAAM,EAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAAC,cAAc,CAAC,EAAE,cAAc,CAAA;CAAE,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,GAAE,MAAM,EAAO,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAa5O;AAED,yCAAyC;AACzC,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,OAAa;;;;;EAO1G"}
@@ -0,0 +1,82 @@
1
+ /** Always available in copy; question answers can't use these names. */
2
+ export const BUILT_IN_VARIABLES = ['app_name', 'first_name', 'today', 'weekday'];
3
+ export const CURRENCY_SYMBOLS = { USD: '$', EUR: '€', GBP: '£', JPY: '¥', CNY: '¥', INR: '₹', BRL: 'R$', MXN: 'MX$', CAD: 'CA$', AUD: 'A$', CHF: 'CHF ', SEK: 'kr ', NOK: 'kr ', DKK: 'kr ', KRW: '₩', ZAR: 'R ', TRY: '₺', PLN: 'zł ', SGD: 'S$', NZD: 'NZ$' };
4
+ const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
5
+ const WEEKDAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
6
+ /** The variable type a question produces, from its answer format. */
7
+ export function variableType(screen) {
8
+ if (screen.answerType === 'singleChoice')
9
+ return screen.multiple ? 'list' : 'choice';
10
+ const format = screen.input?.format;
11
+ if (format === 'number' || format === 'wheel')
12
+ return screen.variableFormat?.currency ? 'money' : 'number';
13
+ if (format === 'date')
14
+ return 'date';
15
+ if (format === 'country')
16
+ return 'country';
17
+ return 'text';
18
+ }
19
+ /** 3000 → "3,000"; 70.5 → "70.5". Done by hand so it reads the same on every device and JavaScript engine. */
20
+ export function formatNumber(value) {
21
+ const fixed = Math.round(value * 100) / 100;
22
+ const [whole, fraction] = String(Math.abs(fixed)).split('.');
23
+ const grouped = whole.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
24
+ return `${fixed < 0 ? '-' : ''}${grouped}${fraction ? `.${fraction}` : ''}`;
25
+ }
26
+ export function formatMoney(value, currency) {
27
+ const symbol = CURRENCY_SYMBOLS[currency.toUpperCase()] ?? `${currency.toUpperCase()} `;
28
+ return `${symbol}${formatNumber(value)}`;
29
+ }
30
+ /** "1995-01-04" → "January 4, 1995" (long), "Jan 4, 1995" (medium), "Jan 4" (short), "1/4/1995" (numeric). */
31
+ export function formatDate(iso, style = 'long') {
32
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso);
33
+ if (!match)
34
+ return iso;
35
+ const [year, month, day] = [Number(match[1]), Number(match[2]), Number(match[3])];
36
+ const name = MONTHS[month - 1] ?? '';
37
+ if (style === 'numeric')
38
+ return `${month}/${day}/${year}`;
39
+ if (style === 'short')
40
+ return `${name.slice(0, 3)} ${day}`;
41
+ if (style === 'medium')
42
+ return `${name.slice(0, 3)} ${day}, ${year}`;
43
+ return `${name} ${day}, ${year}`;
44
+ }
45
+ /** ["Dairy", "Nuts", "Gluten"] → "Dairy, Nuts and Gluten". */
46
+ export function joinList(items) {
47
+ if (items.length <= 1)
48
+ return items[0] ?? '';
49
+ return `${items.slice(0, -1).join(', ')} and ${items[items.length - 1]}`;
50
+ }
51
+ /** A question's answer as a typed variable. `display` from a number wheel or field carries the unit the person chose (e.g. "154 lb"). */
52
+ export function variableValue(screen, answer, picks = [], display) {
53
+ const type = variableType(screen);
54
+ if (type === 'list')
55
+ return picks.length ? { type, value: picks, display: joinList(picks) } : null;
56
+ if (!answer.trim())
57
+ return null;
58
+ if (type === 'number' || type === 'money') {
59
+ const number = Number(answer.replace(',', '.'));
60
+ if (!Number.isFinite(number))
61
+ return { type: 'text', value: answer, display: answer };
62
+ if (type === 'money') {
63
+ const currency = (screen.variableFormat?.currency ?? 'USD').toUpperCase();
64
+ return { type, value: number, currency, display: formatMoney(number, currency) };
65
+ }
66
+ const unit = screen.input?.unit || undefined;
67
+ return { type, value: number, unit, display: display || `${formatNumber(number)}${unit ? ` ${unit}` : ''}` };
68
+ }
69
+ if (type === 'date')
70
+ return { type, value: answer, display: formatDate(answer, screen.variableFormat?.dateStyle) };
71
+ return { type, value: answer, display: answer };
72
+ }
73
+ /** Values for the built-in variables. */
74
+ export function builtInValues(appName, values, now = new Date()) {
75
+ return {
76
+ app_name: appName ?? '',
77
+ first_name: (values.name ?? '').trim().split(/\s+/)[0] ?? '',
78
+ today: `${MONTHS[now.getMonth()]} ${now.getDate()}`,
79
+ weekday: WEEKDAYS[now.getDay()],
80
+ };
81
+ }
82
+ //# sourceMappingURL=variables.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"variables.js","sourceRoot":"","sources":["../src/variables.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,72 @@
1
1
  {
2
2
  "name": "@onboarding-yak/react-native",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.6.0",
4
+ "description": "Render remotely configured OnboardingYak onboarding flows in React Native apps.",
5
+ "homepage": "https://onboardingyak.com",
6
+ "keywords": ["react-native", "expo", "onboarding", "remote-config", "a-b-testing"],
7
+ "main": "lib/index.js",
8
+ "types": "lib/index.d.ts",
9
+ "files": ["lib", "src", "README.md", "LICENSE"],
10
+ "sideEffects": false,
11
+ "scripts": {
12
+ "build": "rm -rf lib && tsc -p tsconfig.build.json",
13
+ "typecheck": "tsc -p tsconfig.build.json --noEmit",
14
+ "prepublishOnly": "npm run build"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "devDependencies": {
20
+ "@react-native-async-storage/async-storage": "2.2.0",
21
+ "@types/react": "~19.3.0",
22
+ "react": "19.3.0",
23
+ "react-native": "0.88.0",
24
+ "react-native-safe-area-context": "~5.10.1",
25
+ "typescript": "~6.0.3"
26
+ },
27
+ "peerDependencies": {
28
+ "@react-native-async-storage/async-storage": ">=1.21.0",
29
+ "expo-audio": "*",
30
+ "expo-camera": "*",
31
+ "expo-contacts": "*",
32
+ "expo-haptics": "*",
33
+ "expo-local-authentication": "*",
34
+ "expo-location": "*",
35
+ "expo-media-library": "*",
36
+ "expo-store-review": "*",
37
+ "expo-tracking-transparency": "*",
38
+ "react": ">=18.0.0",
39
+ "react-native": ">=0.73.0",
40
+ "react-native-safe-area-context": ">=4.0.0"
41
+ },
42
+ "peerDependenciesMeta": {
43
+ "expo-audio": {
44
+ "optional": true
45
+ },
46
+ "expo-camera": {
47
+ "optional": true
48
+ },
49
+ "expo-contacts": {
50
+ "optional": true
51
+ },
52
+ "expo-haptics": {
53
+ "optional": true
54
+ },
55
+ "expo-local-authentication": {
56
+ "optional": true
57
+ },
58
+ "expo-location": {
59
+ "optional": true
60
+ },
61
+ "expo-media-library": {
62
+ "optional": true
63
+ },
64
+ "expo-store-review": {
65
+ "optional": true
66
+ },
67
+ "expo-tracking-transparency": {
68
+ "optional": true
69
+ }
70
+ },
71
+ "license": "MIT"
72
+ }
@@ -0,0 +1,41 @@
1
+ // Mirrors lib/flow-schema.ts so the SDK derives the same dark palette as the studio preview.
2
+ export const DARK_COLOR_KEYS = ['background', 'text', 'muted', 'primary', 'cardBackground', 'overlayColor'] as const;
3
+ export type DarkColorKey = (typeof DARK_COLOR_KEYS)[number];
4
+ export type DarkColors = Partial<Record<DarkColorKey, string>>;
5
+
6
+ function channels(hex: string) { return [1, 3, 5].map((index) => Number.parseInt(hex.slice(index, index + 2), 16) / 255); }
7
+ function luminanceOf(hex: string) {
8
+ const [r, g, b] = channels(hex).map((part) => part <= 0.03928 ? part / 12.92 : ((part + 0.055) / 1.055) ** 2.4);
9
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
10
+ }
11
+ function hslOf(hex: string) {
12
+ const [r, g, b] = channels(hex);
13
+ const max = Math.max(r, g, b); const min = Math.min(r, g, b); const l = (max + min) / 2; const d = max - min;
14
+ if (!d) return { h: 0, s: 0, l };
15
+ const s = d / (1 - Math.abs(2 * l - 1));
16
+ const h = max === r ? ((g - b) / d) % 6 : max === g ? (b - r) / d + 2 : (r - g) / d + 4;
17
+ return { h: (h * 60 + 360) % 360, s, l };
18
+ }
19
+ function hexOf(h: number, s: number, l: number) {
20
+ const c = (1 - Math.abs(2 * l - 1)) * s; const x = c * (1 - Math.abs(((h / 60) % 2) - 1)); const m = l - c / 2;
21
+ const [r, g, b] = h < 60 ? [c, x, 0] : h < 120 ? [x, c, 0] : h < 180 ? [0, c, x] : h < 240 ? [0, x, c] : h < 300 ? [x, 0, c] : [c, 0, x];
22
+ return `#${[r, g, b].map((part) => Math.round((part + m) * 255).toString(16).padStart(2, '0')).join('')}`;
23
+ }
24
+
25
+ /** Dark-mode colors for a screen: saved overrides first, otherwise a dark version of the light palette that keeps its hue. */
26
+ export function darkColors(light: Record<DarkColorKey, string>, overrides?: DarkColors): Record<DarkColorKey, string> {
27
+ const valid = (value: unknown) => typeof value === 'string' && /^#[\da-f]{6}$/i.test(value);
28
+ const pick = (key: DarkColorKey, fallback: string) => valid(overrides?.[key]) ? overrides![key]! : fallback;
29
+ if (luminanceOf(light.background) < 0.18) return Object.fromEntries(DARK_COLOR_KEYS.map((key) => [key, pick(key, light[key])])) as Record<DarkColorKey, string>;
30
+ const tint = hslOf(light.background).s > 0.06 ? hslOf(light.background).h : hslOf(light.primary).h;
31
+ const primary = hslOf(light.primary);
32
+ const derived: Record<DarkColorKey, string> = {
33
+ background: hexOf(tint, 0.14, 0.08),
34
+ cardBackground: hexOf(tint, 0.12, 0.14),
35
+ text: '#f1f3f2',
36
+ muted: hexOf(tint, 0.08, 0.7),
37
+ primary: luminanceOf(light.primary) < 0.03 ? '#f1f3f2' : primary.l < 0.62 ? hexOf(primary.h, Math.min(0.75, primary.s + 0.08), 0.64) : light.primary,
38
+ overlayColor: light.overlayColor,
39
+ };
40
+ return Object.fromEntries(DARK_COLOR_KEYS.map((key) => [key, pick(key, derived[key])])) as Record<DarkColorKey, string>;
41
+ }
@@ -0,0 +1,28 @@
1
+ // ISO 3166-1 alpha-2 codes with English names, used by the country picker in the SDK and the studio.
2
+ export const COUNTRIES: readonly (readonly [code: string, name: string])[] = [
3
+ ['AF', 'Afghanistan'], ['AL', 'Albania'], ['DZ', 'Algeria'], ['AD', 'Andorra'], ['AO', 'Angola'], ['AG', 'Antigua and Barbuda'], ['AR', 'Argentina'], ['AM', 'Armenia'], ['AU', 'Australia'], ['AT', 'Austria'],
4
+ ['AZ', 'Azerbaijan'], ['BS', 'Bahamas'], ['BH', 'Bahrain'], ['BD', 'Bangladesh'], ['BB', 'Barbados'], ['BY', 'Belarus'], ['BE', 'Belgium'], ['BZ', 'Belize'], ['BJ', 'Benin'], ['BT', 'Bhutan'],
5
+ ['BO', 'Bolivia'], ['BA', 'Bosnia and Herzegovina'], ['BW', 'Botswana'], ['BR', 'Brazil'], ['BN', 'Brunei'], ['BG', 'Bulgaria'], ['BF', 'Burkina Faso'], ['BI', 'Burundi'], ['CV', 'Cabo Verde'], ['KH', 'Cambodia'],
6
+ ['CM', 'Cameroon'], ['CA', 'Canada'], ['CF', 'Central African Republic'], ['TD', 'Chad'], ['CL', 'Chile'], ['CN', 'China'], ['CO', 'Colombia'], ['KM', 'Comoros'], ['CG', 'Congo'], ['CD', 'Congo (DRC)'],
7
+ ['CR', 'Costa Rica'], ['CI', 'Côte d’Ivoire'], ['HR', 'Croatia'], ['CU', 'Cuba'], ['CY', 'Cyprus'], ['CZ', 'Czechia'], ['DK', 'Denmark'], ['DJ', 'Djibouti'], ['DM', 'Dominica'], ['DO', 'Dominican Republic'],
8
+ ['EC', 'Ecuador'], ['EG', 'Egypt'], ['SV', 'El Salvador'], ['GQ', 'Equatorial Guinea'], ['ER', 'Eritrea'], ['EE', 'Estonia'], ['SZ', 'Eswatini'], ['ET', 'Ethiopia'], ['FJ', 'Fiji'], ['FI', 'Finland'],
9
+ ['FR', 'France'], ['GA', 'Gabon'], ['GM', 'Gambia'], ['GE', 'Georgia'], ['DE', 'Germany'], ['GH', 'Ghana'], ['GR', 'Greece'], ['GD', 'Grenada'], ['GT', 'Guatemala'], ['GN', 'Guinea'],
10
+ ['GW', 'Guinea-Bissau'], ['GY', 'Guyana'], ['HT', 'Haiti'], ['HN', 'Honduras'], ['HK', 'Hong Kong'], ['HU', 'Hungary'], ['IS', 'Iceland'], ['IN', 'India'], ['ID', 'Indonesia'], ['IR', 'Iran'],
11
+ ['IQ', 'Iraq'], ['IE', 'Ireland'], ['IL', 'Israel'], ['IT', 'Italy'], ['JM', 'Jamaica'], ['JP', 'Japan'], ['JO', 'Jordan'], ['KZ', 'Kazakhstan'], ['KE', 'Kenya'], ['KI', 'Kiribati'],
12
+ ['XK', 'Kosovo'], ['KW', 'Kuwait'], ['KG', 'Kyrgyzstan'], ['LA', 'Laos'], ['LV', 'Latvia'], ['LB', 'Lebanon'], ['LS', 'Lesotho'], ['LR', 'Liberia'], ['LY', 'Libya'], ['LI', 'Liechtenstein'],
13
+ ['LT', 'Lithuania'], ['LU', 'Luxembourg'], ['MO', 'Macao'], ['MG', 'Madagascar'], ['MW', 'Malawi'], ['MY', 'Malaysia'], ['MV', 'Maldives'], ['ML', 'Mali'], ['MT', 'Malta'], ['MH', 'Marshall Islands'],
14
+ ['MR', 'Mauritania'], ['MU', 'Mauritius'], ['MX', 'Mexico'], ['FM', 'Micronesia'], ['MD', 'Moldova'], ['MC', 'Monaco'], ['MN', 'Mongolia'], ['ME', 'Montenegro'], ['MA', 'Morocco'], ['MZ', 'Mozambique'],
15
+ ['MM', 'Myanmar'], ['NA', 'Namibia'], ['NR', 'Nauru'], ['NP', 'Nepal'], ['NL', 'Netherlands'], ['NZ', 'New Zealand'], ['NI', 'Nicaragua'], ['NE', 'Niger'], ['NG', 'Nigeria'], ['KP', 'North Korea'],
16
+ ['MK', 'North Macedonia'], ['NO', 'Norway'], ['OM', 'Oman'], ['PK', 'Pakistan'], ['PW', 'Palau'], ['PS', 'Palestine'], ['PA', 'Panama'], ['PG', 'Papua New Guinea'], ['PY', 'Paraguay'], ['PE', 'Peru'],
17
+ ['PH', 'Philippines'], ['PL', 'Poland'], ['PT', 'Portugal'], ['PR', 'Puerto Rico'], ['QA', 'Qatar'], ['RO', 'Romania'], ['RU', 'Russia'], ['RW', 'Rwanda'], ['KN', 'Saint Kitts and Nevis'], ['LC', 'Saint Lucia'],
18
+ ['VC', 'Saint Vincent and the Grenadines'], ['WS', 'Samoa'], ['SM', 'San Marino'], ['ST', 'São Tomé and Príncipe'], ['SA', 'Saudi Arabia'], ['SN', 'Senegal'], ['RS', 'Serbia'], ['SC', 'Seychelles'], ['SL', 'Sierra Leone'], ['SG', 'Singapore'],
19
+ ['SK', 'Slovakia'], ['SI', 'Slovenia'], ['SB', 'Solomon Islands'], ['SO', 'Somalia'], ['ZA', 'South Africa'], ['KR', 'South Korea'], ['SS', 'South Sudan'], ['ES', 'Spain'], ['LK', 'Sri Lanka'], ['SD', 'Sudan'],
20
+ ['SR', 'Suriname'], ['SE', 'Sweden'], ['CH', 'Switzerland'], ['SY', 'Syria'], ['TW', 'Taiwan'], ['TJ', 'Tajikistan'], ['TZ', 'Tanzania'], ['TH', 'Thailand'], ['TL', 'Timor-Leste'], ['TG', 'Togo'],
21
+ ['TO', 'Tonga'], ['TT', 'Trinidad and Tobago'], ['TN', 'Tunisia'], ['TR', 'Türkiye'], ['TM', 'Turkmenistan'], ['TV', 'Tuvalu'], ['UG', 'Uganda'], ['UA', 'Ukraine'], ['AE', 'United Arab Emirates'], ['GB', 'United Kingdom'],
22
+ ['US', 'United States'], ['UY', 'Uruguay'], ['UZ', 'Uzbekistan'], ['VU', 'Vanuatu'], ['VA', 'Vatican City'], ['VE', 'Venezuela'], ['VN', 'Vietnam'], ['YE', 'Yemen'], ['ZM', 'Zambia'], ['ZW', 'Zimbabwe'],
23
+ ];
24
+
25
+ /** Flag emoji built from regional indicator letters, so no image assets are needed. */
26
+ export function flagOf(code: string) {
27
+ return String.fromCodePoint(...[...code.toUpperCase()].map((letter) => 0x1f1a5 + letter.charCodeAt(0)));
28
+ }
@@ -0,0 +1,47 @@
1
+ // Mirrors lib/flow-schema.ts so a session gets the same version in the app as the studio's results expect.
2
+ /** Another version of a screen for an A/B test. Only the fields it sets differ from the original (version A). */
3
+ export type ScreenVariant = { id: string; name: string; weight: number; title?: string; subtitle?: string; button?: string; illustration?: string };
4
+ /** An A/B test on one screen: the original keeps `controlWeight` of traffic, each variant its own weight. */
5
+ export type ScreenExperiment = { controlWeight: number; variants: ScreenVariant[] };
6
+ export const CONTROL_VARIANT = 'a';
7
+
8
+ /** FNV-1a: a small, stable hash so the same session always lands in the same version. */
9
+ function hashOf(text: string) {
10
+ let hash = 0x811c9dc5;
11
+ for (let index = 0; index < text.length; index += 1) { hash ^= text.charCodeAt(index); hash = Math.imul(hash, 0x01000193); }
12
+ return hash >>> 0;
13
+ }
14
+
15
+ /** The version a session sees for a screen: 'a' for the original, otherwise a variant id. */
16
+ export function pickVariant(screen: { id: string; experiment?: ScreenExperiment }, sessionId: string) {
17
+ const experiment = screen.experiment;
18
+ if (!experiment?.variants?.length) return CONTROL_VARIANT;
19
+ const weights = [{ id: CONTROL_VARIANT, weight: Math.max(0, experiment.controlWeight) }, ...experiment.variants.map((variant) => ({ id: variant.id, weight: Math.max(0, variant.weight) }))];
20
+ const total = weights.reduce((sum, item) => sum + item.weight, 0);
21
+ if (total <= 0) return CONTROL_VARIANT;
22
+ let slot = hashOf(`${sessionId}:${screen.id}`) % total;
23
+ for (const item of weights) { if (slot < item.weight) return item.id; slot -= item.weight; }
24
+ return CONTROL_VARIANT;
25
+ }
26
+
27
+ /** The screen as a given version shows it. */
28
+ export function applyVariant<T extends { title: string; subtitle: string; button: string; illustration?: string; experiment?: ScreenExperiment }>(screen: T, variantId: string): T {
29
+ const variant = screen.experiment?.variants.find((item) => item.id === variantId);
30
+ if (!variant) return screen;
31
+ return {
32
+ ...screen,
33
+ title: variant.title ?? screen.title,
34
+ subtitle: variant.subtitle ?? screen.subtitle,
35
+ button: variant.button ?? screen.button,
36
+ illustration: variant.illustration ?? screen.illustration,
37
+ };
38
+ }
39
+
40
+ /** A whole-onboarding A/B test; the flow's own screens are version A. */
41
+ export type FlowTest<Screen = unknown> = { id: string; name: string; controlWeight: number; variants: { id: string; name: string; weight: number; screens: Screen[] }[] };
42
+
43
+ /** The flow-test version an install sees: 'a' for the original, otherwise a variant id. Same rule as lib/flow-schema.ts. */
44
+ export function pickFlowVersion(test: FlowTest | undefined, installId: string) {
45
+ if (!test?.variants?.length) return CONTROL_VARIANT;
46
+ return pickVariant({ id: `flow-test:${test.id}`, experiment: { controlWeight: test.controlWeight, variants: test.variants.map(({ id, name, weight }) => ({ id, name, weight })) } }, installId);
47
+ }