@getrheo/flow-runtime 1.0.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/dist/agentPrompt/index.d.ts +72 -0
- package/dist/agentPrompt/index.js +739 -0
- package/dist/agentPrompt/index.js.map +1 -0
- package/dist/aiFlowGenerationMerge.d.ts +32 -0
- package/dist/aiFlowGenerationMerge.js +120 -0
- package/dist/aiFlowGenerationMerge.js.map +1 -0
- package/dist/animations.d.ts +110 -0
- package/dist/animations.js +312 -0
- package/dist/animations.js.map +1 -0
- package/dist/assignment.d.ts +7 -0
- package/dist/assignment.js +25 -0
- package/dist/assignment.js.map +1 -0
- package/dist/brandGradient.d.ts +57 -0
- package/dist/brandGradient.js +137 -0
- package/dist/brandGradient.js.map +1 -0
- package/dist/brandGradientManifestIssues.d.ts +11 -0
- package/dist/brandGradientManifestIssues.js +302 -0
- package/dist/brandGradientManifestIssues.js.map +1 -0
- package/dist/buildFlowPreview.d.ts +7 -0
- package/dist/buildFlowPreview.js +81 -0
- package/dist/buildFlowPreview.js.map +1 -0
- package/dist/buttonVariantChrome.d.ts +26 -0
- package/dist/buttonVariantChrome.js +59 -0
- package/dist/buttonVariantChrome.js.map +1 -0
- package/dist/checkboxGlyphStyle.d.ts +31 -0
- package/dist/checkboxGlyphStyle.js +241 -0
- package/dist/checkboxGlyphStyle.js.map +1 -0
- package/dist/choiceOptionSelection.d.ts +11 -0
- package/dist/choiceOptionSelection.js +120 -0
- package/dist/choiceOptionSelection.js.map +1 -0
- package/dist/colorAlpha.d.ts +8 -0
- package/dist/colorAlpha.js +48 -0
- package/dist/colorAlpha.js.map +1 -0
- package/dist/counterLayer.d.ts +42 -0
- package/dist/counterLayer.js +95 -0
- package/dist/counterLayer.js.map +1 -0
- package/dist/decisionEval.d.ts +27 -0
- package/dist/decisionEval.js +197 -0
- package/dist/decisionEval.js.map +1 -0
- package/dist/dropShadow.d.ts +26 -0
- package/dist/dropShadow.js +76 -0
- package/dist/dropShadow.js.map +1 -0
- package/dist/emailPasswordAuthValidation.d.ts +16 -0
- package/dist/emailPasswordAuthValidation.js +25 -0
- package/dist/emailPasswordAuthValidation.js.map +1 -0
- package/dist/flowBuilderRules.d.ts +15 -0
- package/dist/flowBuilderRules.js +368 -0
- package/dist/flowBuilderRules.js.map +1 -0
- package/dist/flowGraph.d.ts +19 -0
- package/dist/flowGraph.js +373 -0
- package/dist/flowGraph.js.map +1 -0
- package/dist/hyperlinkLabel.d.ts +19 -0
- package/dist/hyperlinkLabel.js +232 -0
- package/dist/hyperlinkLabel.js.map +1 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +4200 -0
- package/dist/index.js.map +1 -0
- package/dist/interpolateTemplate.d.ts +44 -0
- package/dist/interpolateTemplate.js +188 -0
- package/dist/interpolateTemplate.js.map +1 -0
- package/dist/layerRotate.d.ts +10 -0
- package/dist/layerRotate.js +9 -0
- package/dist/layerRotate.js.map +1 -0
- package/dist/layerTypography.d.ts +36 -0
- package/dist/layerTypography.js +68 -0
- package/dist/layerTypography.js.map +1 -0
- package/dist/layers.d.ts +69 -0
- package/dist/layers.js +257 -0
- package/dist/layers.js.map +1 -0
- package/dist/layout/index.d.ts +57 -0
- package/dist/layout/index.js +151 -0
- package/dist/layout/index.js.map +1 -0
- package/dist/manifestBillingSlice.d.ts +17 -0
- package/dist/manifestBillingSlice.js +102 -0
- package/dist/manifestBillingSlice.js.map +1 -0
- package/dist/prepareAiGeneratedScreen.d.ts +17 -0
- package/dist/prepareAiGeneratedScreen.js +99 -0
- package/dist/prepareAiGeneratedScreen.js.map +1 -0
- package/dist/publish-exports.json +166 -0
- package/dist/responsive/breakpoints.d.ts +34 -0
- package/dist/responsive/breakpoints.js +52 -0
- package/dist/responsive/breakpoints.js.map +1 -0
- package/dist/responsive/index.d.ts +8 -0
- package/dist/responsive/index.js +307 -0
- package/dist/responsive/index.js.map +1 -0
- package/dist/responsive/layerResolve.d.ts +43 -0
- package/dist/responsive/layerResolve.js +168 -0
- package/dist/responsive/layerResolve.js.map +1 -0
- package/dist/responsive/merge.d.ts +19 -0
- package/dist/responsive/merge.js +74 -0
- package/dist/responsive/merge.js.map +1 -0
- package/dist/responsive/previewSafeAreaInsets.d.ts +14 -0
- package/dist/responsive/previewSafeAreaInsets.js +24 -0
- package/dist/responsive/previewSafeAreaInsets.js.map +1 -0
- package/dist/responsive/screenContainerResolve.d.ts +11 -0
- package/dist/responsive/screenContainerResolve.js +122 -0
- package/dist/responsive/screenContainerResolve.js.map +1 -0
- package/dist/responsive/screenShellInsets.d.ts +11 -0
- package/dist/responsive/screenShellInsets.js +26 -0
- package/dist/responsive/screenShellInsets.js.map +1 -0
- package/dist/restingMotion.d.ts +167 -0
- package/dist/restingMotion.js +484 -0
- package/dist/restingMotion.js.map +1 -0
- package/dist/rheoAgentManifestMerge.d.ts +33 -0
- package/dist/rheoAgentManifestMerge.js +55 -0
- package/dist/rheoAgentManifestMerge.js.map +1 -0
- package/dist/scaleInputStyle.d.ts +35 -0
- package/dist/scaleInputStyle.js +77 -0
- package/dist/scaleInputStyle.js.map +1 -0
- package/dist/scaleValidation.d.ts +9 -0
- package/dist/scaleValidation.js +21 -0
- package/dist/scaleValidation.js.map +1 -0
- package/dist/stateMachine.d.ts +105 -0
- package/dist/stateMachine.js +674 -0
- package/dist/stateMachine.js.map +1 -0
- package/dist/stepResponse-BXgoZ7o-.d.ts +112 -0
- package/dist/textInputValidation.d.ts +14 -0
- package/dist/textInputValidation.js +46 -0
- package/dist/textInputValidation.js.map +1 -0
- package/dist/translationPlaceholders.d.ts +9 -0
- package/dist/translationPlaceholders.js +52 -0
- package/dist/translationPlaceholders.js.map +1 -0
- package/dist/validation.d.ts +31 -0
- package/dist/validation.js +233 -0
- package/dist/validation.js.map +1 -0
- package/package.json +242 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { FlowManifest } from '@getrheo/contracts/manifest';
|
|
2
|
+
import { LocalizedText } from '@getrheo/contracts/localized';
|
|
3
|
+
import { S as StepResponse } from './stepResponse-BXgoZ7o-.js';
|
|
4
|
+
import '@getrheo/contracts/layers';
|
|
5
|
+
import '@getrheo/contracts/externalSurfaces';
|
|
6
|
+
|
|
7
|
+
/** Runtime substitution for Text layer copy (after locale resolution). */
|
|
8
|
+
type InterpolationRuntimeContext = {
|
|
9
|
+
manifest: FlowManifest;
|
|
10
|
+
locale: string;
|
|
11
|
+
responses: Record<string, StepResponse>;
|
|
12
|
+
customProperties?: Record<string, string>;
|
|
13
|
+
};
|
|
14
|
+
/** Props-sized slice passed into renderers. */
|
|
15
|
+
type InterpolationContext = {
|
|
16
|
+
responses: Record<string, StepResponse>;
|
|
17
|
+
customProperties?: Record<string, string>;
|
|
18
|
+
/** When true, the flow has a prior step so back navigation is available. */
|
|
19
|
+
canGoBack?: boolean;
|
|
20
|
+
};
|
|
21
|
+
declare const extractLiquidTemplateBodies: (s: string) => string[];
|
|
22
|
+
type InterpolationExprAnalysis = {
|
|
23
|
+
kind: 'custom';
|
|
24
|
+
key: string;
|
|
25
|
+
} | {
|
|
26
|
+
kind: 'field';
|
|
27
|
+
fieldKey: string;
|
|
28
|
+
mode: 'label' | 'id';
|
|
29
|
+
} | {
|
|
30
|
+
kind: 'invalid';
|
|
31
|
+
};
|
|
32
|
+
/** Parse one `{{ … }}` inner (after stripping delimiters) for diagnostics / validation. */
|
|
33
|
+
declare const analyzeLiquidTemplateInner: (inner: string) => {
|
|
34
|
+
expr: InterpolationExprAnalysis;
|
|
35
|
+
defaultValue?: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Interpolate `{{ … }}` segments in a plain string (already localized).
|
|
39
|
+
* Supports `custom.key`, `field_key`, `field_key.id`, and `| default: …`.
|
|
40
|
+
*/
|
|
41
|
+
declare const interpolateTemplateString: (template: string, ctx: InterpolationRuntimeContext) => string;
|
|
42
|
+
declare const resolveAndInterpolateLocalizedText: (text: LocalizedText, ctx: InterpolationRuntimeContext) => string;
|
|
43
|
+
|
|
44
|
+
export { type InterpolationContext, type InterpolationExprAnalysis, type InterpolationRuntimeContext, analyzeLiquidTemplateInner, extractLiquidTemplateBodies, interpolateTemplateString, resolveAndInterpolateLocalizedText };
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { FIELD_KEY_RE } from '@getrheo/contracts/fields';
|
|
2
|
+
import { resolveLocalizedText } from '@getrheo/contracts/localized';
|
|
3
|
+
import { isInputLayer } from '@getrheo/contracts/layers';
|
|
4
|
+
|
|
5
|
+
// src/interpolateTemplate.ts
|
|
6
|
+
var walkLayers = (root, fn) => {
|
|
7
|
+
const visit = (l, depth) => {
|
|
8
|
+
fn(l, depth);
|
|
9
|
+
if (l.kind === "stack") l.children.forEach((c) => visit(c, depth + 1));
|
|
10
|
+
else if (l.kind === "carousel") l.slides.forEach((c) => visit(c, depth + 1));
|
|
11
|
+
else if (l.kind === "button") l.children.forEach((c) => visit(c, depth + 1));
|
|
12
|
+
else if (l.kind === "back_button") l.children.forEach((c) => visit(c, depth + 1));
|
|
13
|
+
else if (l.kind === "hyperlink") l.children.forEach((c) => visit(c, depth + 1));
|
|
14
|
+
else if (l.kind === "single_choice" || l.kind === "multiple_choice") {
|
|
15
|
+
l.children.forEach((c) => visit(c, depth + 1));
|
|
16
|
+
} else if (l.kind === "text_input" || l.kind === "scale_input") {
|
|
17
|
+
l.children?.forEach((c) => visit(c, depth + 1));
|
|
18
|
+
} else if (l.kind === "oauth_login") {
|
|
19
|
+
l.children.forEach((c) => visit(c, depth + 1));
|
|
20
|
+
} else if (l.kind === "oauth_provider" && l.variant === "custom") {
|
|
21
|
+
l.children.forEach((c) => visit(c, depth + 1));
|
|
22
|
+
} else if (l.kind === "email_password_auth") {
|
|
23
|
+
l.children.forEach((c) => visit(c, depth + 1));
|
|
24
|
+
} else if (l.kind === "email_password_field") {
|
|
25
|
+
l.children?.forEach((c) => visit(c, depth + 1));
|
|
26
|
+
} else if (l.kind === "email_password_submit") {
|
|
27
|
+
l.children.forEach((c) => visit(c, depth + 1));
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
visit(root, 0);
|
|
31
|
+
};
|
|
32
|
+
var walkScreen = (screen, fn) => {
|
|
33
|
+
if (screen.regions.header) walkLayers(screen.regions.header, fn);
|
|
34
|
+
walkLayers(screen.regions.body, fn);
|
|
35
|
+
if (screen.regions.footer) walkLayers(screen.regions.footer, fn);
|
|
36
|
+
};
|
|
37
|
+
var findInputLayer = (screen) => {
|
|
38
|
+
let found = null;
|
|
39
|
+
walkScreen(screen, (l) => {
|
|
40
|
+
if (!found && isInputLayer(l)) found = l;
|
|
41
|
+
});
|
|
42
|
+
return found;
|
|
43
|
+
};
|
|
44
|
+
var findOptionStackForChoice = (layer, optionId) => {
|
|
45
|
+
const binding = layer.optionBindings.find((b) => b.optionId === optionId);
|
|
46
|
+
if (!binding) return null;
|
|
47
|
+
const stack = layer.children.find((c) => c.id === binding.rootLayerId);
|
|
48
|
+
return stack ?? null;
|
|
49
|
+
};
|
|
50
|
+
var choiceOptionLabel = (layer, optionId, locale) => {
|
|
51
|
+
const stack = findOptionStackForChoice(layer, optionId);
|
|
52
|
+
if (!stack) return "";
|
|
53
|
+
let foundText = null;
|
|
54
|
+
walkLayers(stack, (l) => {
|
|
55
|
+
if (foundText) return;
|
|
56
|
+
if (l.kind === "text") foundText = l.text;
|
|
57
|
+
});
|
|
58
|
+
if (foundText) return resolveLocalizedText(foundText, locale);
|
|
59
|
+
return optionId;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// src/interpolateTemplate.ts
|
|
63
|
+
var extractLiquidTemplateBodies = (s) => {
|
|
64
|
+
const out = [];
|
|
65
|
+
let i = 0;
|
|
66
|
+
while (i < s.length) {
|
|
67
|
+
const open = s.indexOf("{{", i);
|
|
68
|
+
if (open === -1) break;
|
|
69
|
+
const close = s.indexOf("}}", open + 2);
|
|
70
|
+
if (close === -1) break;
|
|
71
|
+
out.push(s.slice(open + 2, close));
|
|
72
|
+
i = close + 2;
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
};
|
|
76
|
+
var FIELD_KEY_SOURCE = FIELD_KEY_RE.source.replace(/^\^|\$$/g, "");
|
|
77
|
+
var parseDefaultFilter = (inner) => {
|
|
78
|
+
const m = inner.match(/\s*\|\s*default\s*:/i);
|
|
79
|
+
if (!m || m.index === void 0) return { exprPart: inner.trim() };
|
|
80
|
+
const exprPart = inner.slice(0, m.index).trim();
|
|
81
|
+
let tail = inner.slice(m.index + m[0].length).trim();
|
|
82
|
+
if (tail.startsWith('"')) {
|
|
83
|
+
const end = tail.indexOf('"', 1);
|
|
84
|
+
if (end > 0) tail = tail.slice(1, end);
|
|
85
|
+
else tail = tail.slice(1);
|
|
86
|
+
}
|
|
87
|
+
return { exprPart, defaultValue: tail };
|
|
88
|
+
};
|
|
89
|
+
var parseExpression = (exprRaw) => {
|
|
90
|
+
const expr = exprRaw.trim();
|
|
91
|
+
const customM = expr.match(new RegExp(`^custom\\.(${FIELD_KEY_SOURCE})$`));
|
|
92
|
+
if (customM) return { kind: "custom", key: customM[1] };
|
|
93
|
+
const idM = expr.match(new RegExp(`^(${FIELD_KEY_SOURCE})\\.id$`));
|
|
94
|
+
if (idM) return { kind: "field", fieldKey: idM[1], mode: "id" };
|
|
95
|
+
const plainM = expr.match(new RegExp(`^(${FIELD_KEY_SOURCE})$`));
|
|
96
|
+
if (plainM) return { kind: "field", fieldKey: plainM[1], mode: "label" };
|
|
97
|
+
return { kind: "invalid" };
|
|
98
|
+
};
|
|
99
|
+
var analyzeLiquidTemplateInner = (inner) => {
|
|
100
|
+
const { exprPart, defaultValue } = parseDefaultFilter(inner);
|
|
101
|
+
const p = parseExpression(exprPart);
|
|
102
|
+
if (p.kind === "invalid") return { expr: { kind: "invalid" }, defaultValue };
|
|
103
|
+
if (p.kind === "custom") return { expr: { kind: "custom", key: p.key }, defaultValue };
|
|
104
|
+
return { expr: { kind: "field", fieldKey: p.fieldKey, mode: p.mode }, defaultValue };
|
|
105
|
+
};
|
|
106
|
+
var findInputOwner = (manifest, fieldKey) => {
|
|
107
|
+
for (const screen of manifest.screens) {
|
|
108
|
+
const input = findInputLayer(screen);
|
|
109
|
+
if (input && input.fieldKey === fieldKey) return { screen, input };
|
|
110
|
+
}
|
|
111
|
+
return null;
|
|
112
|
+
};
|
|
113
|
+
var choiceLabelFor = (manifest, fieldKey, choiceId, locale) => {
|
|
114
|
+
const owner = findInputOwner(manifest, fieldKey);
|
|
115
|
+
if (!owner) return "";
|
|
116
|
+
const { input } = owner;
|
|
117
|
+
if (input.kind !== "single_choice" && input.kind !== "multiple_choice") return "";
|
|
118
|
+
return choiceOptionLabel(input, choiceId, locale);
|
|
119
|
+
};
|
|
120
|
+
var responseToDisplayString = (manifest, fieldKey, response, mode, locale) => {
|
|
121
|
+
if (!response) return "";
|
|
122
|
+
switch (response.kind) {
|
|
123
|
+
case "text":
|
|
124
|
+
return mode === "id" ? "" : response.value;
|
|
125
|
+
case "scale":
|
|
126
|
+
return mode === "id" ? "" : String(response.value);
|
|
127
|
+
case "choice":
|
|
128
|
+
if (mode === "id") return response.choiceId;
|
|
129
|
+
return choiceLabelFor(manifest, fieldKey, response.choiceId, locale);
|
|
130
|
+
case "multiChoice":
|
|
131
|
+
if (mode === "id") return response.choiceIds.join(", ");
|
|
132
|
+
return response.choiceIds.map((id) => choiceLabelFor(manifest, fieldKey, id, locale)).filter(Boolean).join(", ");
|
|
133
|
+
case "checkbox":
|
|
134
|
+
return mode === "id" ? "" : response.value ? "true" : "false";
|
|
135
|
+
case "bypass_input":
|
|
136
|
+
return "";
|
|
137
|
+
default:
|
|
138
|
+
return "";
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
var evalParsed = (parsed, ctx) => {
|
|
142
|
+
if (parsed.kind === "invalid") return "";
|
|
143
|
+
if (parsed.kind === "custom") {
|
|
144
|
+
const v = ctx.customProperties?.[parsed.key];
|
|
145
|
+
return v === void 0 || v === "" ? "" : String(v);
|
|
146
|
+
}
|
|
147
|
+
const owner = findInputOwner(ctx.manifest, parsed.fieldKey);
|
|
148
|
+
const r = ctx.responses[parsed.fieldKey];
|
|
149
|
+
if (parsed.mode === "id" && owner && owner.input.kind !== "single_choice" && owner.input.kind !== "multiple_choice") {
|
|
150
|
+
return "";
|
|
151
|
+
}
|
|
152
|
+
return responseToDisplayString(ctx.manifest, parsed.fieldKey, r, parsed.mode, ctx.locale);
|
|
153
|
+
};
|
|
154
|
+
var interpolateTemplateString = (template, ctx) => {
|
|
155
|
+
let i = 0;
|
|
156
|
+
let out = "";
|
|
157
|
+
while (i < template.length) {
|
|
158
|
+
const open = template.indexOf("{{", i);
|
|
159
|
+
if (open === -1) {
|
|
160
|
+
out += template.slice(i);
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
out += template.slice(i, open);
|
|
164
|
+
const close = template.indexOf("}}", open + 2);
|
|
165
|
+
if (close === -1) {
|
|
166
|
+
out += template.slice(open);
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
const inner = template.slice(open + 2, close);
|
|
170
|
+
const { exprPart, defaultValue } = parseDefaultFilter(inner);
|
|
171
|
+
const parsed = parseExpression(exprPart);
|
|
172
|
+
let value = evalParsed(parsed, ctx);
|
|
173
|
+
if ((value === "" || value === void 0) && defaultValue !== void 0) {
|
|
174
|
+
value = defaultValue;
|
|
175
|
+
}
|
|
176
|
+
out += value;
|
|
177
|
+
i = close + 2;
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
};
|
|
181
|
+
var resolveAndInterpolateLocalizedText = (text, ctx) => {
|
|
182
|
+
const localized = resolveLocalizedText(text, ctx.locale);
|
|
183
|
+
return interpolateTemplateString(localized, ctx);
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export { analyzeLiquidTemplateInner, extractLiquidTemplateBodies, interpolateTemplateString, resolveAndInterpolateLocalizedText };
|
|
187
|
+
//# sourceMappingURL=interpolateTemplate.js.map
|
|
188
|
+
//# sourceMappingURL=interpolateTemplate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/layers.ts","../src/interpolateTemplate.ts"],"names":["resolveLocalizedText"],"mappings":"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","file":"interpolateTemplate.js","sourcesContent":["import type { Branding } from '@getrheo/contracts/dashboard';\nimport type { FlowManifest, Theme } from '@getrheo/contracts/manifest';\nimport type { Screen } from '@getrheo/contracts/screens';\nimport {\n brandGradientFromThemedColor,\n brandGradientNativeLinear,\n brandGradientSolidFallback,\n isStoredLinearGradientCss,\n nativeLinearFromAngleAndStops,\n parseLinearGradientCss,\n resolveBrandGradientToken,\n type BrandGradientNativeLinear,\n} from './brandGradient';\nimport type {\n InputLayer,\n Layer,\n MultipleChoiceLayer,\n ScaleInputLayer,\n SingleChoiceLayer,\n StackLayer,\n TextInputLayer,\n ThemedColor,\n} from '@getrheo/contracts/layers';\nimport type { LocalizedText } from '@getrheo/contracts/localized';\nimport { resolveLocalizedText } from '@getrheo/contracts/localized';\nimport { isInputLayer } from '@getrheo/contracts/layers';\n\n/** Walk a layer tree depth-first. */\nexport const walkLayers = (root: Layer, fn: (l: Layer, depth: number) => void): void => {\n const visit = (l: Layer, depth: number): void => {\n fn(l, depth);\n if (l.kind === 'stack') l.children.forEach((c) => visit(c, depth + 1));\n else if (l.kind === 'carousel') l.slides.forEach((c) => visit(c, depth + 1));\n else if (l.kind === 'button') l.children.forEach((c) => visit(c, depth + 1));\n else if (l.kind === 'back_button') l.children.forEach((c) => visit(c, depth + 1));\n else if (l.kind === 'hyperlink') l.children.forEach((c) => visit(c, depth + 1));\n else if (l.kind === 'single_choice' || l.kind === 'multiple_choice') {\n l.children.forEach((c) => visit(c, depth + 1));\n } else if (l.kind === 'text_input' || l.kind === 'scale_input') {\n l.children?.forEach((c) => visit(c, depth + 1));\n } else if (l.kind === 'oauth_login') {\n l.children.forEach((c) => visit(c, depth + 1));\n } else if (l.kind === 'oauth_provider' && l.variant === 'custom') {\n l.children.forEach((c) => visit(c, depth + 1));\n } else if (l.kind === 'email_password_auth') {\n l.children.forEach((c) => visit(c, depth + 1));\n } else if (l.kind === 'email_password_field') {\n l.children?.forEach((c) => visit(c, depth + 1));\n } else if (l.kind === 'email_password_submit') {\n l.children.forEach((c) => visit(c, depth + 1));\n }\n };\n visit(root, 0);\n};\n\n/** Walk every layer in a screen's regions (header → body → footer). */\nexport const walkScreen = (screen: Screen, fn: (l: Layer) => void): void => {\n if (screen.regions.header) walkLayers(screen.regions.header, fn);\n walkLayers(screen.regions.body, fn);\n if (screen.regions.footer) walkLayers(screen.regions.footer, fn);\n};\n\n/** Find the screen's lone input layer (if any). Schema enforces ≤1. */\nexport const findInputLayer = (screen: Screen): InputLayer | null => {\n let found: InputLayer | null = null;\n walkScreen(screen, (l) => {\n if (!found && isInputLayer(l)) found = l;\n });\n return found;\n};\n\n/** Input kinds that use a screen draft and require an explicit Continue to submit. */\nexport const findManualSubmitInputLayer = (\n screen: Screen,\n): MultipleChoiceLayer | TextInputLayer | ScaleInputLayer | null => {\n const input = findInputLayer(screen);\n if (!input) return null;\n if (input.kind === 'multiple_choice' || input.kind === 'text_input' || input.kind === 'scale_input') {\n return input;\n }\n return null;\n};\n\n/**\n * Whether the screen contains any Button layer that submits the screen\n * (i.e. `action.kind === 'continue'`). Used by input layers to decide\n * between auto-submit-on-tap (legacy behaviour for choice-only screens)\n * and writing into the screen-level draft for a Button to submit.\n */\nexport const screenHasContinueButton = (screen: Screen): boolean => {\n let found = false;\n walkScreen(screen, (l) => {\n if (l.kind === 'button' && l.action.kind === 'continue') found = true;\n });\n return found;\n};\n\n/**\n * Resolve a choice layer's option stack by stable optionId via its\n * binding. Returns null when the binding is missing or the bound child\n * was removed (manifest validation rejects that, but runtime stays safe).\n */\nexport const findOptionStackForChoice = (\n layer: SingleChoiceLayer | MultipleChoiceLayer,\n optionId: string,\n): StackLayer | null => {\n const binding = layer.optionBindings.find((b) => b.optionId === optionId);\n if (!binding) return null;\n const stack = layer.children.find((c) => c.id === binding.rootLayerId);\n return stack ?? null;\n};\n\n/**\n * Best-effort textual label for a choice option, used by interpolation\n * (e.g. `{{ goal }}` rendering the chosen option's label) and by editor\n * surfaces that show option rows. Walks the option's child subtree\n * depth-first and returns the first `text` layer's content; falls back\n * to the option's stable id when no text is present.\n */\nexport const choiceOptionLabel = (\n layer: SingleChoiceLayer | MultipleChoiceLayer,\n optionId: string,\n locale: string,\n): string => {\n const stack = findOptionStackForChoice(layer, optionId);\n if (!stack) return '';\n let foundText: LocalizedText | null = null;\n walkLayers(stack, (l) => {\n if (foundText) return;\n if (l.kind === 'text') foundText = l.text;\n });\n if (foundText) return resolveLocalizedText(foundText, locale);\n return optionId;\n};\n\n/** Find a layer in a screen by id, including nested children/slides. */\nexport const findLayerById = (screen: Screen, id: string): Layer | null => {\n let found: Layer | null = null;\n walkScreen(screen, (l) => {\n if (!found && l.id === id) found = l;\n });\n return found;\n};\n\n/** Collect all input fieldKeys across a manifest. */\nexport const collectFieldKeys = (manifest: FlowManifest): { fieldKey: string; screenId: string }[] => {\n const out: { fieldKey: string; screenId: string }[] = [];\n for (const screen of manifest.screens) {\n walkScreen(screen as unknown as Screen, (l) => {\n if (isInputLayer(l)) out.push({ fieldKey: l.fieldKey, screenId: screen.id });\n if (l.kind === 'checkbox') out.push({ fieldKey: l.fieldKey, screenId: screen.id });\n if (l.kind === 'email_password_auth') {\n out.push({ fieldKey: l.fieldKey, screenId: screen.id });\n }\n });\n }\n return out;\n};\n\n/**\n * Pick a snake_case field key starting from `base` that is not in `used`\n * (e.g. `text` → `text_2` → `text_3` when `text` is taken).\n */\nexport const nextUniqueFieldKey = (base: string, used: Iterable<string>): string => {\n const set = used instanceof Set ? used : new Set(used);\n if (!set.has(base)) return base;\n let n = 2;\n while (set.has(`${base}_${n}`)) n += 1;\n return `${base}_${n}`;\n};\n\n/**\n * Resolve a token reference like `$primary` to a literal value from `theme`.\n * Pass-through for non-token strings; returns undefined for `undefined`.\n */\nexport const resolveTokens = <T extends string | undefined>(\n theme: Theme | undefined,\n value: T,\n): T | string => {\n if (value === undefined) return value;\n if (typeof value !== 'string' || !value.startsWith('$')) return value;\n const key = value.slice(1) as keyof Theme;\n const literal = theme?.[key];\n if (typeof literal === 'string') return literal;\n return value;\n};\n\n/**\n * Resolve a layer color for the current appearance (`light` | `dark`).\n * Plain string uses `resolveTokens` for both modes (legacy). Object form\n * picks `light` / `dark` with fallback to the other key when one is omitted.\n */\nexport const resolveThemedColor = (\n theme: Theme | undefined,\n palette: 'light' | 'dark',\n value: ThemedColor | undefined,\n): string | undefined => {\n if (value === undefined) return undefined;\n if (typeof value === 'string') return resolveTokens(theme, value) as string;\n const raw = palette === 'dark' ? (value.dark ?? value.light) : (value.light ?? value.dark);\n if (raw === undefined) return undefined;\n return resolveTokens(theme, raw) as string;\n};\n\n/**\n * Resolve a themed value used for CSS `background` (or RN background fill).\n * Supports `$brandGradient:<uuid>` when branding presets are provided; other values match {@link resolveThemedColor}.\n */\nexport const resolveThemedBackground = (\n theme: Theme | undefined,\n branding: Branding | undefined,\n palette: 'light' | 'dark',\n value: ThemedColor | undefined,\n): string | undefined => {\n if (value === undefined) return undefined;\n if (typeof value === 'string') {\n if (value.startsWith('$brandGradient:')) {\n return resolveBrandGradientToken(branding, value);\n }\n return resolveTokens(theme, value) as string;\n }\n const raw = palette === 'dark' ? (value.dark ?? value.light) : (value.light ?? value.dark);\n if (raw === undefined) return undefined;\n if (raw.startsWith('$brandGradient:')) {\n return resolveBrandGradientToken(branding, raw);\n }\n return resolveTokens(theme, raw) as string;\n};\n\nexport const nativeBrandBackgroundFromThemedColor = (\n theme: Theme | undefined,\n branding: Branding | undefined,\n palette: 'light' | 'dark',\n value: ThemedColor | undefined,\n): { solid?: string; linear?: BrandGradientNativeLinear } => {\n const preset = brandGradientFromThemedColor(branding, palette, value);\n if (preset) {\n const lin = brandGradientNativeLinear(preset);\n if (lin) return { linear: lin };\n return { solid: brandGradientSolidFallback(preset) };\n }\n const bg = resolveThemedBackground(theme, branding, palette, value) as string | undefined;\n if (!bg) return {};\n const parsed = parseLinearGradientCss(bg);\n if (parsed) {\n return {\n linear: nativeLinearFromAngleAndStops(\n parsed.angleDeg,\n parsed.stops.map((s) => ({ color: s.color, offsetPct: s.offsetPct })),\n ),\n };\n }\n if (isStoredLinearGradientCss(bg)) {\n const first = bg.match(/#[0-9a-fA-F]{3,8}/);\n return { solid: first ? first[0] : '#808080' };\n }\n return { solid: bg };\n};\n","import { FIELD_KEY_RE } from '@getrheo/contracts/fields';\nimport type { FlowManifest } from '@getrheo/contracts/manifest';\nimport type { Screen } from '@getrheo/contracts/screens';\nimport type { LocalizedText } from '@getrheo/contracts/localized';\nimport { resolveLocalizedText } from '@getrheo/contracts/localized';\nimport { choiceOptionLabel, findInputLayer } from './layers';\nimport type { StepResponse } from './stateMachine';\n\n/** Runtime substitution for Text layer copy (after locale resolution). */\nexport type InterpolationRuntimeContext = {\n manifest: FlowManifest;\n locale: string;\n responses: Record<string, StepResponse>;\n customProperties?: Record<string, string>;\n};\n\n/** Props-sized slice passed into renderers. */\nexport type InterpolationContext = {\n responses: Record<string, StepResponse>;\n customProperties?: Record<string, string>;\n /** When true, the flow has a prior step so back navigation is available. */\n canGoBack?: boolean;\n};\n\nexport const extractLiquidTemplateBodies = (s: string): string[] => {\n const out: string[] = [];\n let i = 0;\n while (i < s.length) {\n const open = s.indexOf('{{', i);\n if (open === -1) break;\n const close = s.indexOf('}}', open + 2);\n if (close === -1) break;\n out.push(s.slice(open + 2, close));\n i = close + 2;\n }\n return out;\n};\n\nconst FIELD_KEY_SOURCE = FIELD_KEY_RE.source.replace(/^\\^|\\$$/g, '');\n\nconst parseDefaultFilter = (\n inner: string,\n): { exprPart: string; defaultValue?: string } => {\n const m = inner.match(/\\s*\\|\\s*default\\s*:/i);\n if (!m || m.index === undefined) return { exprPart: inner.trim() };\n\n const exprPart = inner.slice(0, m.index).trim();\n let tail = inner.slice(m.index + m[0].length).trim();\n if (tail.startsWith('\"')) {\n const end = tail.indexOf('\"', 1);\n if (end > 0) tail = tail.slice(1, end);\n else tail = tail.slice(1);\n }\n return { exprPart, defaultValue: tail };\n};\n\nconst parseExpression = (\n exprRaw: string,\n):\n | { kind: 'custom'; key: string }\n | { kind: 'field'; fieldKey: string; mode: 'label' | 'id' }\n | { kind: 'invalid' } => {\n const expr = exprRaw.trim();\n const customM = expr.match(new RegExp(`^custom\\\\.(${FIELD_KEY_SOURCE})$`));\n if (customM) return { kind: 'custom', key: customM[1]! };\n\n const idM = expr.match(new RegExp(`^(${FIELD_KEY_SOURCE})\\\\.id$`));\n if (idM) return { kind: 'field', fieldKey: idM[1]!, mode: 'id' };\n\n const plainM = expr.match(new RegExp(`^(${FIELD_KEY_SOURCE})$`));\n if (plainM) return { kind: 'field', fieldKey: plainM[1]!, mode: 'label' };\n\n return { kind: 'invalid' };\n};\n\nexport type InterpolationExprAnalysis =\n | { kind: 'custom'; key: string }\n | { kind: 'field'; fieldKey: string; mode: 'label' | 'id' }\n | { kind: 'invalid' };\n\n/** Parse one `{{ … }}` inner (after stripping delimiters) for diagnostics / validation. */\nexport const analyzeLiquidTemplateInner = (\n inner: string,\n): { expr: InterpolationExprAnalysis; defaultValue?: string } => {\n const { exprPart, defaultValue } = parseDefaultFilter(inner);\n const p = parseExpression(exprPart);\n if (p.kind === 'invalid') return { expr: { kind: 'invalid' }, defaultValue };\n if (p.kind === 'custom') return { expr: { kind: 'custom', key: p.key }, defaultValue };\n return { expr: { kind: 'field', fieldKey: p.fieldKey, mode: p.mode }, defaultValue };\n};\n\nconst findInputOwner = (\n manifest: FlowManifest,\n fieldKey: string,\n): { screen: Screen; input: NonNullable<ReturnType<typeof findInputLayer>> } | null => {\n for (const screen of manifest.screens) {\n const input = findInputLayer(screen as Screen);\n if (input && input.fieldKey === fieldKey) return { screen: screen as Screen, input };\n }\n return null;\n};\n\nconst choiceLabelFor = (\n manifest: FlowManifest,\n fieldKey: string,\n choiceId: string,\n locale: string,\n): string => {\n const owner = findInputOwner(manifest, fieldKey);\n if (!owner) return '';\n const { input } = owner;\n if (input.kind !== 'single_choice' && input.kind !== 'multiple_choice') return '';\n return choiceOptionLabel(input, choiceId, locale);\n};\n\nconst responseToDisplayString = (\n manifest: FlowManifest,\n fieldKey: string,\n response: StepResponse | undefined,\n mode: 'label' | 'id',\n locale: string,\n): string => {\n if (!response) return '';\n switch (response.kind) {\n case 'text':\n return mode === 'id' ? '' : response.value;\n case 'scale':\n return mode === 'id' ? '' : String(response.value);\n case 'choice':\n if (mode === 'id') return response.choiceId;\n return choiceLabelFor(manifest, fieldKey, response.choiceId, locale);\n case 'multiChoice':\n if (mode === 'id') return response.choiceIds.join(', ');\n return response.choiceIds\n .map((id) => choiceLabelFor(manifest, fieldKey, id, locale))\n .filter(Boolean)\n .join(', ');\n case 'checkbox':\n return mode === 'id' ? '' : response.value ? 'true' : 'false';\n case 'bypass_input':\n return '';\n default:\n return '';\n }\n};\n\nconst evalParsed = (\n parsed:\n | { kind: 'custom'; key: string }\n | { kind: 'field'; fieldKey: string; mode: 'label' | 'id' }\n | { kind: 'invalid' },\n ctx: InterpolationRuntimeContext,\n): string => {\n if (parsed.kind === 'invalid') return '';\n if (parsed.kind === 'custom') {\n const v = ctx.customProperties?.[parsed.key];\n return v === undefined || v === '' ? '' : String(v);\n }\n const owner = findInputOwner(ctx.manifest, parsed.fieldKey);\n const r = ctx.responses[parsed.fieldKey];\n if (parsed.mode === 'id' && owner && owner.input.kind !== 'single_choice' && owner.input.kind !== 'multiple_choice') {\n return '';\n }\n return responseToDisplayString(ctx.manifest, parsed.fieldKey, r, parsed.mode, ctx.locale);\n};\n\n/**\n * Interpolate `{{ … }}` segments in a plain string (already localized).\n * Supports `custom.key`, `field_key`, `field_key.id`, and `| default: …`.\n */\nexport const interpolateTemplateString = (\n template: string,\n ctx: InterpolationRuntimeContext,\n): string => {\n let i = 0;\n let out = '';\n while (i < template.length) {\n const open = template.indexOf('{{', i);\n if (open === -1) {\n out += template.slice(i);\n break;\n }\n out += template.slice(i, open);\n const close = template.indexOf('}}', open + 2);\n if (close === -1) {\n out += template.slice(open);\n break;\n }\n const inner = template.slice(open + 2, close);\n const { exprPart, defaultValue } = parseDefaultFilter(inner);\n const parsed = parseExpression(exprPart);\n let value = evalParsed(parsed, ctx);\n if ((value === '' || value === undefined) && defaultValue !== undefined) {\n value = defaultValue;\n }\n out += value;\n i = close + 2;\n }\n return out;\n};\n\nexport const resolveAndInterpolateLocalizedText = (\n text: LocalizedText,\n ctx: InterpolationRuntimeContext,\n): string => {\n const localized = resolveLocalizedText(text, ctx.locale);\n return interpolateTemplateString(localized, ctx);\n};\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Authored layer rotation (CSS `rotate`, RN `transform.rotate`, Swift `rotationEffect`). */
|
|
2
|
+
declare const LAYER_ROTATE_DEG_MIN = -360;
|
|
3
|
+
declare const LAYER_ROTATE_DEG_MAX = 360;
|
|
4
|
+
declare const layerRotateCssTransform: (deg: number | undefined) => string | undefined;
|
|
5
|
+
/** React Native `transform` entry for authored rotation. */
|
|
6
|
+
declare const layerRotateNativeTransform: (deg: number | undefined) => Array<{
|
|
7
|
+
rotate: string;
|
|
8
|
+
}> | undefined;
|
|
9
|
+
|
|
10
|
+
export { LAYER_ROTATE_DEG_MAX, LAYER_ROTATE_DEG_MIN, layerRotateCssTransform, layerRotateNativeTransform };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// src/layerRotate.ts
|
|
2
|
+
var LAYER_ROTATE_DEG_MIN = -360;
|
|
3
|
+
var LAYER_ROTATE_DEG_MAX = 360;
|
|
4
|
+
var layerRotateCssTransform = (deg) => deg === void 0 ? void 0 : `rotate(${deg}deg)`;
|
|
5
|
+
var layerRotateNativeTransform = (deg) => deg === void 0 ? void 0 : [{ rotate: `${deg}deg` }];
|
|
6
|
+
|
|
7
|
+
export { LAYER_ROTATE_DEG_MAX, LAYER_ROTATE_DEG_MIN, layerRotateCssTransform, layerRotateNativeTransform };
|
|
8
|
+
//# sourceMappingURL=layerRotate.js.map
|
|
9
|
+
//# sourceMappingURL=layerRotate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/layerRotate.ts"],"names":[],"mappings":";AACO,IAAM,oBAAA,GAAuB;AAC7B,IAAM,oBAAA,GAAuB;AAE7B,IAAM,0BAA0B,CAAC,GAAA,KACtC,QAAQ,MAAA,GAAY,MAAA,GAAY,UAAU,GAAG,CAAA,IAAA;AAGxC,IAAM,0BAAA,GAA6B,CACxC,GAAA,KAEA,GAAA,KAAQ,MAAA,GAAY,MAAA,GAAY,CAAC,EAAE,MAAA,EAAQ,CAAA,EAAG,GAAG,CAAA,GAAA,CAAA,EAAO","file":"layerRotate.js","sourcesContent":["/** Authored layer rotation (CSS `rotate`, RN `transform.rotate`, Swift `rotationEffect`). */\nexport const LAYER_ROTATE_DEG_MIN = -360;\nexport const LAYER_ROTATE_DEG_MAX = 360;\n\nexport const layerRotateCssTransform = (deg: number | undefined): string | undefined =>\n deg === undefined ? undefined : `rotate(${deg}deg)`;\n\n/** React Native `transform` entry for authored rotation. */\nexport const layerRotateNativeTransform = (\n deg: number | undefined,\n): Array<{ rotate: string }> | undefined =>\n deg === undefined ? undefined : [{ rotate: `${deg}deg` }];\n"]}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Branding } from '@getrheo/contracts/dashboard';
|
|
2
|
+
import { Theme } from '@getrheo/contracts/manifest';
|
|
3
|
+
|
|
4
|
+
/** Stored on {@link TextStyle.fontFamily} to force the document system stack (ignores `theme.fontFamily`). */
|
|
5
|
+
declare const TEXT_FONT_FAMILY_SYSTEM_UI = "__rheo_system_ui__";
|
|
6
|
+
/** Web sim / builder root stack when no `theme.fontFamily` is set (matches `LayerRenderer` container). */
|
|
7
|
+
declare const WEB_DOCUMENT_FONT_STACK = "-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif";
|
|
8
|
+
/**
|
|
9
|
+
* CSS `font-family` for the flow shell so `text` layers with no `fontFamily` inherit the theme body font.
|
|
10
|
+
*/
|
|
11
|
+
declare const resolveWebRootFontFamilyCss: (theme: Theme | undefined) => string | undefined;
|
|
12
|
+
/**
|
|
13
|
+
* CSS `font-family` for a text layer `div` (undefined layer value → inherit shell).
|
|
14
|
+
*/
|
|
15
|
+
declare const resolveWebTextFontFamilyCss: (layerFont: string | undefined) => string | undefined;
|
|
16
|
+
/**
|
|
17
|
+
* Inline `@font-face` rules for uploaded branding fonts (web / sim only).
|
|
18
|
+
*/
|
|
19
|
+
declare const brandingWebFontFacesCss: (branding: Branding | undefined) => string;
|
|
20
|
+
/**
|
|
21
|
+
* Expo / RN registration name for a single uploaded style row (see {@link buildBrandingFontLoadMap}).
|
|
22
|
+
*/
|
|
23
|
+
declare const nativeFontRegistrationNameForStyle: (styleId: string) => string;
|
|
24
|
+
/**
|
|
25
|
+
* Map of **native font registration name → font file URL** for every branding style that has a `url`.
|
|
26
|
+
* Pass the result to your host runtime’s font loader (e.g. `expo-font`’s `loadAsync`, a bare-RN native
|
|
27
|
+
* module, or pre-linked assets). Keys match {@link resolveNativeTextFontFamilyName} after fonts are registered.
|
|
28
|
+
*/
|
|
29
|
+
declare const buildBrandingFontLoadMap: (branding: Branding | null | undefined) => Record<string, string>;
|
|
30
|
+
/**
|
|
31
|
+
* Resolves the RN `fontFamily` string for a logical family name, preferring a loaded branding file
|
|
32
|
+
* that matches weight (and falls back to nearest uploaded style with a URL).
|
|
33
|
+
*/
|
|
34
|
+
declare const resolveNativeTextFontFamilyName: (branding: Branding | undefined, logicalFamily: string | undefined, fontWeight: number | undefined) => string | undefined;
|
|
35
|
+
|
|
36
|
+
export { TEXT_FONT_FAMILY_SYSTEM_UI, WEB_DOCUMENT_FONT_STACK, brandingWebFontFacesCss, buildBrandingFontLoadMap, nativeFontRegistrationNameForStyle, resolveNativeTextFontFamilyName, resolveWebRootFontFamilyCss, resolveWebTextFontFamilyCss };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// src/layerTypography.ts
|
|
2
|
+
var TEXT_FONT_FAMILY_SYSTEM_UI = "__rheo_system_ui__";
|
|
3
|
+
var WEB_DOCUMENT_FONT_STACK = '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif';
|
|
4
|
+
var quoteCssFamilyName = (name) => {
|
|
5
|
+
const t = name.trim();
|
|
6
|
+
if (!t) return "";
|
|
7
|
+
const safe = /^[-_a-zA-Z0-9]+$/.test(t);
|
|
8
|
+
return safe ? t : JSON.stringify(t);
|
|
9
|
+
};
|
|
10
|
+
var fontStackWithPrimary = (primary) => primary ? `${primary}, ${WEB_DOCUMENT_FONT_STACK}` : WEB_DOCUMENT_FONT_STACK;
|
|
11
|
+
var resolveWebRootFontFamilyCss = (theme) => {
|
|
12
|
+
const raw = theme?.fontFamily?.trim();
|
|
13
|
+
if (!raw) return void 0;
|
|
14
|
+
if (raw.includes(",")) return raw;
|
|
15
|
+
return fontStackWithPrimary(quoteCssFamilyName(raw));
|
|
16
|
+
};
|
|
17
|
+
var resolveWebTextFontFamilyCss = (layerFont) => {
|
|
18
|
+
if (layerFont === TEXT_FONT_FAMILY_SYSTEM_UI) return WEB_DOCUMENT_FONT_STACK;
|
|
19
|
+
const raw = layerFont?.trim();
|
|
20
|
+
if (!raw) return void 0;
|
|
21
|
+
if (raw.includes(",")) return raw;
|
|
22
|
+
return fontStackWithPrimary(quoteCssFamilyName(raw));
|
|
23
|
+
};
|
|
24
|
+
var brandingWebFontFacesCss = (branding) => {
|
|
25
|
+
if (!branding?.fontFamilies?.length) return "";
|
|
26
|
+
const blocks = [];
|
|
27
|
+
for (const fam of branding.fontFamilies) {
|
|
28
|
+
const famCss = quoteCssFamilyName(fam.name);
|
|
29
|
+
for (const st of fam.styles) {
|
|
30
|
+
if (!st.url?.trim()) continue;
|
|
31
|
+
const url = JSON.stringify(st.url);
|
|
32
|
+
blocks.push(
|
|
33
|
+
`@font-face{font-family:${famCss};src:url(${url});font-weight:${st.weight};font-style:${st.italic ? "italic" : "normal"};font-display:swap;}`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return blocks.join("");
|
|
38
|
+
};
|
|
39
|
+
var nativeFontRegistrationNameForStyle = (styleId) => `RheoFont__${styleId}`;
|
|
40
|
+
var buildBrandingFontLoadMap = (branding) => {
|
|
41
|
+
const map = {};
|
|
42
|
+
if (!branding?.fontFamilies?.length) return map;
|
|
43
|
+
for (const fam of branding.fontFamilies) {
|
|
44
|
+
for (const st of fam.styles) {
|
|
45
|
+
const u = st.url?.trim();
|
|
46
|
+
if (!u) continue;
|
|
47
|
+
map[nativeFontRegistrationNameForStyle(st.id)] = u;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return map;
|
|
51
|
+
};
|
|
52
|
+
var resolveNativeTextFontFamilyName = (branding, logicalFamily, fontWeight) => {
|
|
53
|
+
if (!logicalFamily?.trim()) return void 0;
|
|
54
|
+
if (logicalFamily === TEXT_FONT_FAMILY_SYSTEM_UI) return void 0;
|
|
55
|
+
const fam = branding?.fontFamilies.find((f) => f.name === logicalFamily);
|
|
56
|
+
if (!fam) return logicalFamily;
|
|
57
|
+
const w = fontWeight ?? 400;
|
|
58
|
+
const withUrl = fam.styles.filter((s) => !!s.url?.trim());
|
|
59
|
+
if (withUrl.length === 0) return logicalFamily;
|
|
60
|
+
const exact = withUrl.find((s) => s.weight === w && !s.italic);
|
|
61
|
+
const st = exact ?? withUrl.find((s) => s.weight === w) ?? withUrl[0];
|
|
62
|
+
if (!st) return logicalFamily;
|
|
63
|
+
return nativeFontRegistrationNameForStyle(st.id);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export { TEXT_FONT_FAMILY_SYSTEM_UI, WEB_DOCUMENT_FONT_STACK, brandingWebFontFacesCss, buildBrandingFontLoadMap, nativeFontRegistrationNameForStyle, resolveNativeTextFontFamilyName, resolveWebRootFontFamilyCss, resolveWebTextFontFamilyCss };
|
|
67
|
+
//# sourceMappingURL=layerTypography.js.map
|
|
68
|
+
//# sourceMappingURL=layerTypography.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/layerTypography.ts"],"names":[],"mappings":"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","file":"layerTypography.js","sourcesContent":["import type { Branding } from '@getrheo/contracts/dashboard';\nimport type { Theme } from '@getrheo/contracts/manifest';\n\n/** Stored on {@link TextStyle.fontFamily} to force the document system stack (ignores `theme.fontFamily`). */\nexport const TEXT_FONT_FAMILY_SYSTEM_UI = '__rheo_system_ui__';\n\n/** Web sim / builder root stack when no `theme.fontFamily` is set (matches `LayerRenderer` container). */\nexport const WEB_DOCUMENT_FONT_STACK =\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Helvetica, Arial, sans-serif';\n\nconst quoteCssFamilyName = (name: string): string => {\n const t = name.trim();\n if (!t) return '';\n const safe = /^[-_a-zA-Z0-9]+$/.test(t);\n return safe ? t : JSON.stringify(t);\n};\n\nconst fontStackWithPrimary = (primary: string | undefined): string =>\n primary ? `${primary}, ${WEB_DOCUMENT_FONT_STACK}` : WEB_DOCUMENT_FONT_STACK;\n\n/**\n * CSS `font-family` for the flow shell so `text` layers with no `fontFamily` inherit the theme body font.\n */\nexport const resolveWebRootFontFamilyCss = (theme: Theme | undefined): string | undefined => {\n const raw = theme?.fontFamily?.trim();\n if (!raw) return undefined;\n if (raw.includes(',')) return raw;\n return fontStackWithPrimary(quoteCssFamilyName(raw));\n};\n\n/**\n * CSS `font-family` for a text layer `div` (undefined layer value → inherit shell).\n */\nexport const resolveWebTextFontFamilyCss = (layerFont: string | undefined): string | undefined => {\n if (layerFont === TEXT_FONT_FAMILY_SYSTEM_UI) return WEB_DOCUMENT_FONT_STACK;\n const raw = layerFont?.trim();\n if (!raw) return undefined;\n if (raw.includes(',')) return raw;\n return fontStackWithPrimary(quoteCssFamilyName(raw));\n};\n\n/**\n * Inline `@font-face` rules for uploaded branding fonts (web / sim only).\n */\nexport const brandingWebFontFacesCss = (branding: Branding | undefined): string => {\n if (!branding?.fontFamilies?.length) return '';\n const blocks: string[] = [];\n for (const fam of branding.fontFamilies) {\n const famCss = quoteCssFamilyName(fam.name);\n for (const st of fam.styles) {\n if (!st.url?.trim()) continue;\n const url = JSON.stringify(st.url);\n blocks.push(\n `@font-face{font-family:${famCss};src:url(${url});font-weight:${st.weight};font-style:${st.italic ? 'italic' : 'normal'};font-display:swap;}`,\n );\n }\n }\n return blocks.join('');\n};\n\n/**\n * Expo / RN registration name for a single uploaded style row (see {@link buildBrandingFontLoadMap}).\n */\nexport const nativeFontRegistrationNameForStyle = (styleId: string): string => `RheoFont__${styleId}`;\n\n/**\n * Map of **native font registration name → font file URL** for every branding style that has a `url`.\n * Pass the result to your host runtime’s font loader (e.g. `expo-font`’s `loadAsync`, a bare-RN native\n * module, or pre-linked assets). Keys match {@link resolveNativeTextFontFamilyName} after fonts are registered.\n */\nexport const buildBrandingFontLoadMap = (\n branding: Branding | null | undefined,\n): Record<string, string> => {\n const map: Record<string, string> = {};\n if (!branding?.fontFamilies?.length) return map;\n for (const fam of branding.fontFamilies) {\n for (const st of fam.styles) {\n const u = st.url?.trim();\n if (!u) continue;\n map[nativeFontRegistrationNameForStyle(st.id)] = u;\n }\n }\n return map;\n};\n\n/**\n * Resolves the RN `fontFamily` string for a logical family name, preferring a loaded branding file\n * that matches weight (and falls back to nearest uploaded style with a URL).\n */\nexport const resolveNativeTextFontFamilyName = (\n branding: Branding | undefined,\n logicalFamily: string | undefined,\n fontWeight: number | undefined,\n): string | undefined => {\n if (!logicalFamily?.trim()) return undefined;\n if (logicalFamily === TEXT_FONT_FAMILY_SYSTEM_UI) return undefined;\n const fam = branding?.fontFamilies.find((f) => f.name === logicalFamily);\n if (!fam) return logicalFamily;\n const w = fontWeight ?? 400;\n const withUrl = fam.styles.filter((s) => !!s.url?.trim());\n if (withUrl.length === 0) return logicalFamily;\n const exact = withUrl.find((s) => s.weight === w && !s.italic);\n const st = exact ?? withUrl.find((s) => s.weight === w) ?? withUrl[0];\n if (!st) return logicalFamily;\n return nativeFontRegistrationNameForStyle(st.id);\n};\n"]}
|
package/dist/layers.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Branding } from '@getrheo/contracts/dashboard';
|
|
2
|
+
import { FlowManifest, Theme } from '@getrheo/contracts/manifest';
|
|
3
|
+
import { Screen } from '@getrheo/contracts/screens';
|
|
4
|
+
import { BrandGradientNativeLinear } from './brandGradient.js';
|
|
5
|
+
import { SingleChoiceLayer, MultipleChoiceLayer, InputLayer, Layer, TextInputLayer, ScaleInputLayer, StackLayer, ThemedColor } from '@getrheo/contracts/layers';
|
|
6
|
+
|
|
7
|
+
/** Walk a layer tree depth-first. */
|
|
8
|
+
declare const walkLayers: (root: Layer, fn: (l: Layer, depth: number) => void) => void;
|
|
9
|
+
/** Walk every layer in a screen's regions (header → body → footer). */
|
|
10
|
+
declare const walkScreen: (screen: Screen, fn: (l: Layer) => void) => void;
|
|
11
|
+
/** Find the screen's lone input layer (if any). Schema enforces ≤1. */
|
|
12
|
+
declare const findInputLayer: (screen: Screen) => InputLayer | null;
|
|
13
|
+
/** Input kinds that use a screen draft and require an explicit Continue to submit. */
|
|
14
|
+
declare const findManualSubmitInputLayer: (screen: Screen) => MultipleChoiceLayer | TextInputLayer | ScaleInputLayer | null;
|
|
15
|
+
/**
|
|
16
|
+
* Whether the screen contains any Button layer that submits the screen
|
|
17
|
+
* (i.e. `action.kind === 'continue'`). Used by input layers to decide
|
|
18
|
+
* between auto-submit-on-tap (legacy behaviour for choice-only screens)
|
|
19
|
+
* and writing into the screen-level draft for a Button to submit.
|
|
20
|
+
*/
|
|
21
|
+
declare const screenHasContinueButton: (screen: Screen) => boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Resolve a choice layer's option stack by stable optionId via its
|
|
24
|
+
* binding. Returns null when the binding is missing or the bound child
|
|
25
|
+
* was removed (manifest validation rejects that, but runtime stays safe).
|
|
26
|
+
*/
|
|
27
|
+
declare const findOptionStackForChoice: (layer: SingleChoiceLayer | MultipleChoiceLayer, optionId: string) => StackLayer | null;
|
|
28
|
+
/**
|
|
29
|
+
* Best-effort textual label for a choice option, used by interpolation
|
|
30
|
+
* (e.g. `{{ goal }}` rendering the chosen option's label) and by editor
|
|
31
|
+
* surfaces that show option rows. Walks the option's child subtree
|
|
32
|
+
* depth-first and returns the first `text` layer's content; falls back
|
|
33
|
+
* to the option's stable id when no text is present.
|
|
34
|
+
*/
|
|
35
|
+
declare const choiceOptionLabel: (layer: SingleChoiceLayer | MultipleChoiceLayer, optionId: string, locale: string) => string;
|
|
36
|
+
/** Find a layer in a screen by id, including nested children/slides. */
|
|
37
|
+
declare const findLayerById: (screen: Screen, id: string) => Layer | null;
|
|
38
|
+
/** Collect all input fieldKeys across a manifest. */
|
|
39
|
+
declare const collectFieldKeys: (manifest: FlowManifest) => {
|
|
40
|
+
fieldKey: string;
|
|
41
|
+
screenId: string;
|
|
42
|
+
}[];
|
|
43
|
+
/**
|
|
44
|
+
* Pick a snake_case field key starting from `base` that is not in `used`
|
|
45
|
+
* (e.g. `text` → `text_2` → `text_3` when `text` is taken).
|
|
46
|
+
*/
|
|
47
|
+
declare const nextUniqueFieldKey: (base: string, used: Iterable<string>) => string;
|
|
48
|
+
/**
|
|
49
|
+
* Resolve a token reference like `$primary` to a literal value from `theme`.
|
|
50
|
+
* Pass-through for non-token strings; returns undefined for `undefined`.
|
|
51
|
+
*/
|
|
52
|
+
declare const resolveTokens: <T extends string | undefined>(theme: Theme | undefined, value: T) => T | string;
|
|
53
|
+
/**
|
|
54
|
+
* Resolve a layer color for the current appearance (`light` | `dark`).
|
|
55
|
+
* Plain string uses `resolveTokens` for both modes (legacy). Object form
|
|
56
|
+
* picks `light` / `dark` with fallback to the other key when one is omitted.
|
|
57
|
+
*/
|
|
58
|
+
declare const resolveThemedColor: (theme: Theme | undefined, palette: "light" | "dark", value: ThemedColor | undefined) => string | undefined;
|
|
59
|
+
/**
|
|
60
|
+
* Resolve a themed value used for CSS `background` (or RN background fill).
|
|
61
|
+
* Supports `$brandGradient:<uuid>` when branding presets are provided; other values match {@link resolveThemedColor}.
|
|
62
|
+
*/
|
|
63
|
+
declare const resolveThemedBackground: (theme: Theme | undefined, branding: Branding | undefined, palette: "light" | "dark", value: ThemedColor | undefined) => string | undefined;
|
|
64
|
+
declare const nativeBrandBackgroundFromThemedColor: (theme: Theme | undefined, branding: Branding | undefined, palette: "light" | "dark", value: ThemedColor | undefined) => {
|
|
65
|
+
solid?: string;
|
|
66
|
+
linear?: BrandGradientNativeLinear;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export { choiceOptionLabel, collectFieldKeys, findInputLayer, findLayerById, findManualSubmitInputLayer, findOptionStackForChoice, nativeBrandBackgroundFromThemedColor, nextUniqueFieldKey, resolveThemedBackground, resolveThemedColor, resolveTokens, screenHasContinueButton, walkLayers, walkScreen };
|