@pushwoosh/websdk-common 0.0.0 → 6.17.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/package.json +30 -5
- package/subscription-widget/SubscriptionWidgetReader.d.ts +23 -0
- package/subscription-widget/SubscriptionWidgetReader.js +307 -0
- package/subscription-widget/constants.d.ts +19 -0
- package/subscription-widget/constants.js +61 -0
- package/subscription-widget/helpers.d.ts +16 -0
- package/subscription-widget/helpers.js +110 -0
- package/subscription-widget/index.d.ts +4 -0
- package/subscription-widget/index.js +3 -0
- package/subscription-widget/types.d.ts +90 -0
- package/subscription-widget/types.js +1 -0
- package/web-popups/WebPopupReader.d.ts +4 -0
- package/web-popups/WebPopupReader.js +1394 -0
- package/web-popups/colorScheme.d.ts +2 -0
- package/web-popups/colorScheme.js +22 -0
- package/web-popups/constants.d.ts +71 -0
- package/web-popups/constants.js +216 -0
- package/web-popups/glyphs.d.ts +8 -0
- package/web-popups/glyphs.js +30 -0
- package/web-popups/helpers.d.ts +84 -0
- package/web-popups/helpers.js +687 -0
- package/web-popups/index.d.ts +9 -0
- package/web-popups/index.js +8 -0
- package/web-popups/layout.d.ts +39 -0
- package/web-popups/layout.js +290 -0
- package/web-popups/palette.d.ts +34 -0
- package/web-popups/palette.js +157 -0
- package/web-popups/templates.d.ts +3 -0
- package/web-popups/templates.js +125 -0
- package/web-popups/timer.d.ts +16 -0
- package/web-popups/timer.js +194 -0
- package/web-popups/types.d.ts +660 -0
- package/web-popups/types.js +10 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { WebPopupReader, WebPopupBox, WebPopupSlotView } from './WebPopupReader';
|
|
2
|
+
export { WEB_POPUP_TEMPLATES, findWebPopupTemplate } from './templates';
|
|
3
|
+
export { parseWebPopupJson, stringifyWebPopup, resolveWebPopupPages, resolveWebPopupPageSize, startPageOf, safeUrl, sanitizeWebPopupHtml, normalizeWebPopupColors, normalizeWebPopupValues, webPopupValuesMap, collectWebPopupFormCodes, webPopupExpired, normalizeWebPopupDark, WEB_POPUP_MAX_SIZE_PX, } from './helpers';
|
|
4
|
+
export { webPopupTimerLabels, webPopupTimerRemaining, webPopupTimeZoneSupported, webPopupWallClockMs, type WebPopupTimerLabels, } from './timer';
|
|
5
|
+
export { normalizeWebPopupLayout, substituteWebPopupLayoutValues, WEB_POPUP_VALUE_KEY_PATTERN, webPopupBackgroundImageStyle, webPopupContainerStyle, webPopupCssLength, webPopupPaddingValue, webPopupTracksValue, } from './layout';
|
|
6
|
+
export { WEB_POPUP_VERSION, WEB_POPUP_FONT_STACK, WEB_POPUP_TEXT_COLOR, WEB_POPUP_TEXT_SECONDARY_COLOR, WEB_POPUP_ACCENT_COLOR, WEB_POPUP_BACKGROUND_COLOR, WEB_POPUP_DEFAULT_COLORS, webPopupTextColor, WEB_POPUP_TEXT_VARIANT_STYLES, WEB_POPUP_BG_SECTION_FALLBACK, WEB_POPUP_TEXT_SLOT_PADDING, WEB_POPUP_BUTTON_SLOT_PADDING, webPopupSlotGap, webPopupSlotRole, type WebPopupSlotRole, WEB_POPUP_DEFAULT_WIDTH, WEB_POPUP_LAYOUT_MAX_DEPTH, WEB_POPUP_LAYOUT_MAX_NODES, webPopupButtonPillStyle, webPopupFormInputStyle, WEB_POPUP_FORM_INPUT_BORDER, WEB_POPUP_FORM_ERROR_COLOR, WEB_POPUP_FORM_ERROR_COLOR_DARK, WEB_POPUP_ON_ACCENT_COLOR, WEB_POPUP_FORM_DEFAULTS, WEB_POPUP_CHOICE_DEFAULTS, WEB_POPUP_MAX_CHOICE_OPTIONS, WEB_POPUP_RATING_DEFAULTS, WEB_POPUP_RETRY_ERROR, webPopupRatingScores, } from './constants';
|
|
7
|
+
export { resolveWebPopupBoxChrome, resolveWebPopupColors, autoDarkWebPopupColors, webPopupColorAlpha, webPopupDarkColors, webPopupDarkEnabled, webPopupEffectiveScheme, webPopupImageSrc, webPopupMixColor, webPopupShiftColor, } from './palette';
|
|
8
|
+
export { WEB_POPUP_GLYPH_NAMES, WEB_POPUP_GLYPH_PATHS, WEB_POPUP_GLYPH_SIZES, WEB_POPUP_THUMB_UP_PATH, } from './glyphs';
|
|
9
|
+
export { type WebPopupJson, type WebPopupKind, type WebPopupParams, type WebPopupColors, type WebPopupChrome, type WebPopupPageParams, type WebPopupResolvedPage, type WebPopupDark, type WebPopupResolvedColors, type WebPopupColorScheme, type WebPopupColorSchemePref, type WebPopupSlot, type WebPopupImage, type WebPopupVideo, type WebPopupPlacement, type WebPopupOverlay, type WebPopupAnimation, type WebPopupButtonAction, type WebPopupButtonVariant, type WebPopupTextVariant, type WebPopupTextAlign, type WebPopupVAnchor, type WebPopupHAnchor, type WebPopupReaderMode, type WebPopupReaderProps, type WebPopupLeafRenderers, type WebPopupBoxProps, type WebPopupLayoutNode, type WebPopupContainerNode, type WebPopupContentNode, type WebPopupImageNode, type WebPopupContainerType, type WebPopupContainerProps, type WebPopupContentZone, type WebPopupContentTone, type WebPopupCssLength, type WebPopupBoxSides, type WebPopupAlignItems, type WebPopupJustifyContent, type WebPopupValueParam, type WebPopupValueControl, type WebPopupLengthUnit, type WebPopupLengthRange, type WebPopupValueRef, type WebPopupTemplate, type WebPopupFormField, type WebPopupFormFieldKind, type WebPopupFormPrivacy, type WebPopupFormSubmitPayload, type WebPopupFormSubmitResult, type WebPopupFormSubmitHandler, type WebPopupTimerSlot, type WebPopupTimerExpireAction, type WebPopupTimerRemaining, type WebPopupChoiceSlot, type WebPopupChoiceOption, type WebPopupChoiceControl, type WebPopupChoiceSubmitPayload, type WebPopupChoiceSubmitHandler, type WebPopupRatingSlot, type WebPopupRatingScale, type WebPopupRatingSubmitMode, type WebPopupRatingSubmitPayload, type WebPopupRatingSubmitHandler, type WebPopupSubscribeResult, type WebPopupGlyphSlot, type WebPopupGlyphName, type WebPopupGlyphSize, type WebPopupSlotViewProps, } from './types';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { WebPopupReader, WebPopupBox, WebPopupSlotView } from './WebPopupReader';
|
|
2
|
+
export { WEB_POPUP_TEMPLATES, findWebPopupTemplate } from './templates';
|
|
3
|
+
export { parseWebPopupJson, stringifyWebPopup, resolveWebPopupPages, resolveWebPopupPageSize, startPageOf, safeUrl, sanitizeWebPopupHtml, normalizeWebPopupColors, normalizeWebPopupValues, webPopupValuesMap, collectWebPopupFormCodes, webPopupExpired, normalizeWebPopupDark, WEB_POPUP_MAX_SIZE_PX } from './helpers';
|
|
4
|
+
export { webPopupTimerLabels, webPopupTimerRemaining, webPopupTimeZoneSupported, webPopupWallClockMs } from './timer';
|
|
5
|
+
export { normalizeWebPopupLayout, substituteWebPopupLayoutValues, WEB_POPUP_VALUE_KEY_PATTERN, webPopupBackgroundImageStyle, webPopupContainerStyle, webPopupCssLength, webPopupPaddingValue, webPopupTracksValue } from './layout';
|
|
6
|
+
export { WEB_POPUP_VERSION, WEB_POPUP_FONT_STACK, WEB_POPUP_TEXT_COLOR, WEB_POPUP_TEXT_SECONDARY_COLOR, WEB_POPUP_ACCENT_COLOR, WEB_POPUP_BACKGROUND_COLOR, WEB_POPUP_DEFAULT_COLORS, webPopupTextColor, WEB_POPUP_TEXT_VARIANT_STYLES, WEB_POPUP_BG_SECTION_FALLBACK, WEB_POPUP_TEXT_SLOT_PADDING, WEB_POPUP_BUTTON_SLOT_PADDING, webPopupSlotGap, webPopupSlotRole, WEB_POPUP_DEFAULT_WIDTH, WEB_POPUP_LAYOUT_MAX_DEPTH, WEB_POPUP_LAYOUT_MAX_NODES, webPopupButtonPillStyle, webPopupFormInputStyle, WEB_POPUP_FORM_INPUT_BORDER, WEB_POPUP_FORM_ERROR_COLOR, WEB_POPUP_FORM_ERROR_COLOR_DARK, WEB_POPUP_ON_ACCENT_COLOR, WEB_POPUP_FORM_DEFAULTS, WEB_POPUP_CHOICE_DEFAULTS, WEB_POPUP_MAX_CHOICE_OPTIONS, WEB_POPUP_RATING_DEFAULTS, WEB_POPUP_RETRY_ERROR, webPopupRatingScores } from './constants';
|
|
7
|
+
export { resolveWebPopupBoxChrome, resolveWebPopupColors, autoDarkWebPopupColors, webPopupColorAlpha, webPopupDarkColors, webPopupDarkEnabled, webPopupEffectiveScheme, webPopupImageSrc, webPopupMixColor, webPopupShiftColor } from './palette';
|
|
8
|
+
export { WEB_POPUP_GLYPH_NAMES, WEB_POPUP_GLYPH_PATHS, WEB_POPUP_GLYPH_SIZES, WEB_POPUP_THUMB_UP_PATH } from './glyphs';
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type CSSProperties } from 'react';
|
|
2
|
+
import { type WebPopupBoxSides, type WebPopupContainerNode, type WebPopupCssLength, type WebPopupLayoutNode } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Validate an embedded layout tree from the stored json. Returns the
|
|
5
|
+
* normalized tree, or null when the value isn't a renderable tree at all —
|
|
6
|
+
* the page is then dropped by resolveWebPopupPages.
|
|
7
|
+
*/
|
|
8
|
+
export declare function normalizeWebPopupLayout(raw: unknown): WebPopupLayoutNode | null;
|
|
9
|
+
/** A page value key, as referenced from the tree by a `$key` token. */
|
|
10
|
+
export declare const WEB_POPUP_VALUE_KEY_PATTERN: RegExp;
|
|
11
|
+
/**
|
|
12
|
+
* Substitute a page's named values into its RAW layout tree — `$key` tokens
|
|
13
|
+
* in string props become the values, verbatim. Runs BEFORE
|
|
14
|
+
* normalizeWebPopupLayout (see resolveWebPopupPages), so a substituted value
|
|
15
|
+
* passes exactly the whitelists/clamps a literal one would; substitution is
|
|
16
|
+
* single-pass and normalizeWebPopupValues rejects values containing '$', so
|
|
17
|
+
* refs can't chain. Binding keys (`content`, `image`, `backgroundImage`) are
|
|
18
|
+
* never substituted — they already name params by design.
|
|
19
|
+
*/
|
|
20
|
+
export declare function substituteWebPopupLayoutValues(layout: unknown, values: Record<string, string>): unknown;
|
|
21
|
+
export declare function webPopupCssLength(value: WebPopupCssLength): string;
|
|
22
|
+
export declare function webPopupPaddingValue(padding: WebPopupCssLength | WebPopupBoxSides): string;
|
|
23
|
+
/** grid-template value for Columns/Rows: absent sizes = one 1fr per child. */
|
|
24
|
+
export declare function webPopupTracksValue(sizes: number | string | undefined, childCount: number): string;
|
|
25
|
+
export declare function webPopupScrimGradient(dim?: number): string | null;
|
|
26
|
+
/**
|
|
27
|
+
* The cover-background css of a container that draws an image behind its
|
|
28
|
+
* content. `dim` (0–1) is the image's scrim — a dark translucent layer
|
|
29
|
+
* painted as a gradient stacked over the url() in the same property, so it
|
|
30
|
+
* needs no extra element and rides along wherever the background is drawn.
|
|
31
|
+
*/
|
|
32
|
+
export declare function webPopupBackgroundImageStyle(src: string, dim?: number): CSSProperties;
|
|
33
|
+
/**
|
|
34
|
+
* The inline style of a container node — grid, mirroring kit-helpers'
|
|
35
|
+
* Vertical/Horizontal/Columns/Rows semantics. The background image is passed
|
|
36
|
+
* resolved (looked up in the page's images by the caller) so this stays a
|
|
37
|
+
* pure props → css mapping.
|
|
38
|
+
*/
|
|
39
|
+
export declare function webPopupContainerStyle(node: WebPopupContainerNode, backgroundSrc?: string, backgroundDim?: number): CSSProperties;
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { WEB_POPUP_LAYOUT_MAX_DEPTH, WEB_POPUP_LAYOUT_MAX_NODES } from './constants';
|
|
2
|
+
// Validation + css conversion for the embedded layout tree. The json is
|
|
3
|
+
// API-writable and its styles land inline on customers' pages, so every prop
|
|
4
|
+
// is normalized against a whitelist on parse: numbers are clamped, strings
|
|
5
|
+
// must match a strict css-length/track pattern, enums fall back to defaults,
|
|
6
|
+
// unknown props are dropped. Unknown node TYPES degrade instead of breaking:
|
|
7
|
+
// a node with children renders as a Vertical stack (a container from a NEWER
|
|
8
|
+
// editor keeps its content visible on an older engine build), a childless
|
|
9
|
+
// unknown node is dropped.
|
|
10
|
+
const CONTAINER_TYPES = ['Vertical', 'Horizontal', 'Columns', 'Rows'];
|
|
11
|
+
const ORDERS = ['normal', 'reverse'];
|
|
12
|
+
const ALIGN_ITEMS = ['start', 'center', 'end', 'stretch'];
|
|
13
|
+
const JUSTIFY_CONTENT = ['start', 'center', 'end', 'space-between'];
|
|
14
|
+
const TEXT_ALIGNS = ['left', 'center', 'right'];
|
|
15
|
+
const TONES = ['default', 'inverse'];
|
|
16
|
+
const MAX_LENGTH_PX = 2000;
|
|
17
|
+
// String lengths carry a unit — clamp each to its own sane ceiling, like the
|
|
18
|
+
// numeric branch: a wire value ("9999rem" scales off the HOST page's root
|
|
19
|
+
// font size) must not be able to blow the popup past the viewport.
|
|
20
|
+
const LENGTH_UNIT_MAX = {
|
|
21
|
+
px: MAX_LENGTH_PX,
|
|
22
|
+
'%': 100,
|
|
23
|
+
em: 125,
|
|
24
|
+
rem: 125
|
|
25
|
+
};
|
|
26
|
+
const LENGTH_PATTERN = /^(\d{1,4}(?:\.\d+)?)(px|%|em|rem)$/;
|
|
27
|
+
const TRACK_UNIT_MAX = {
|
|
28
|
+
px: MAX_LENGTH_PX,
|
|
29
|
+
'%': 100,
|
|
30
|
+
fr: 100
|
|
31
|
+
};
|
|
32
|
+
const TRACK_PATTERN = /^(\d{1,4}(?:\.\d+)?)(px|%|fr)$/;
|
|
33
|
+
const TRACK_KEYWORDS = ['auto', 'min-content', 'max-content'];
|
|
34
|
+
const MAX_TRACKS = 12;
|
|
35
|
+
function oneOf(value, allowed) {
|
|
36
|
+
return allowed.includes(value) ? value : undefined;
|
|
37
|
+
}
|
|
38
|
+
function cssLength(value) {
|
|
39
|
+
if (typeof value === 'number' && Number.isFinite(value)) {
|
|
40
|
+
return Math.min(Math.max(value, 0), MAX_LENGTH_PX);
|
|
41
|
+
}
|
|
42
|
+
if (typeof value !== 'string') return undefined;
|
|
43
|
+
const match = LENGTH_PATTERN.exec(value.trim());
|
|
44
|
+
if (!match) return undefined;
|
|
45
|
+
const [, amount, unit] = match;
|
|
46
|
+
return `${Math.min(parseFloat(amount), LENGTH_UNIT_MAX[unit])}${unit}`;
|
|
47
|
+
}
|
|
48
|
+
const BOX_SIDES = ['top', 'right', 'bottom', 'left'];
|
|
49
|
+
function boxPadding(value) {
|
|
50
|
+
const whole = cssLength(value);
|
|
51
|
+
if (whole !== undefined) return whole;
|
|
52
|
+
if (typeof value !== 'object' || value === null) return undefined;
|
|
53
|
+
const sides = {};
|
|
54
|
+
for (const side of BOX_SIDES) {
|
|
55
|
+
const sideValue = cssLength(value[side]);
|
|
56
|
+
if (sideValue !== undefined) sides[side] = sideValue;
|
|
57
|
+
}
|
|
58
|
+
return Object.keys(sides).length > 0 ? sides : undefined;
|
|
59
|
+
}
|
|
60
|
+
function trackSize(token) {
|
|
61
|
+
if (TRACK_KEYWORDS.includes(token)) return token;
|
|
62
|
+
const match = TRACK_PATTERN.exec(token);
|
|
63
|
+
if (!match) return undefined;
|
|
64
|
+
const [, amount, unit] = match;
|
|
65
|
+
return `${Math.min(parseFloat(amount), TRACK_UNIT_MAX[unit])}${unit}`;
|
|
66
|
+
}
|
|
67
|
+
function trackSizes(value) {
|
|
68
|
+
if (typeof value === 'number') {
|
|
69
|
+
return Number.isInteger(value) && value >= 1 && value <= MAX_TRACKS ? value : undefined;
|
|
70
|
+
}
|
|
71
|
+
if (typeof value !== 'string') return undefined;
|
|
72
|
+
const tokens = value.trim().split(/\s+/);
|
|
73
|
+
if (tokens.length < 1 || tokens.length > MAX_TRACKS) return undefined;
|
|
74
|
+
const clamped = tokens.map(trackSize);
|
|
75
|
+
return clamped.every(token => token !== undefined) ? clamped.join(' ') : undefined;
|
|
76
|
+
}
|
|
77
|
+
function nonEmptyString(value) {
|
|
78
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
79
|
+
}
|
|
80
|
+
function containerProps(raw) {
|
|
81
|
+
const props = {};
|
|
82
|
+
const gap = cssLength(raw.gap);
|
|
83
|
+
if (gap !== undefined) props.gap = gap;
|
|
84
|
+
const padding = boxPadding(raw.padding);
|
|
85
|
+
if (padding !== undefined) props.padding = padding;
|
|
86
|
+
const alignItems = oneOf(raw.alignItems, ALIGN_ITEMS);
|
|
87
|
+
if (alignItems) props.alignItems = alignItems;
|
|
88
|
+
const justifyContent = oneOf(raw.justifyContent, JUSTIFY_CONTENT);
|
|
89
|
+
if (justifyContent) props.justifyContent = justifyContent;
|
|
90
|
+
const alignContent = oneOf(raw.alignContent, JUSTIFY_CONTENT);
|
|
91
|
+
if (alignContent) props.alignContent = alignContent;
|
|
92
|
+
const backgroundImage = nonEmptyString(raw.backgroundImage);
|
|
93
|
+
if (backgroundImage) props.backgroundImage = backgroundImage;
|
|
94
|
+
const sizes = trackSizes(raw.sizes);
|
|
95
|
+
if (sizes !== undefined) props.sizes = sizes;
|
|
96
|
+
return props;
|
|
97
|
+
}
|
|
98
|
+
function normalizeNode(raw, depth, budget) {
|
|
99
|
+
if (depth > WEB_POPUP_LAYOUT_MAX_DEPTH || budget.nodes <= 0) return null;
|
|
100
|
+
if (typeof raw !== 'object' || raw === null) return null;
|
|
101
|
+
budget.nodes -= 1;
|
|
102
|
+
const node = raw;
|
|
103
|
+
const props = typeof node.props === 'object' && node.props !== null ? node.props : {};
|
|
104
|
+
if (node.type === 'Content') {
|
|
105
|
+
const content = nonEmptyString(props.content);
|
|
106
|
+
if (!content) return null;
|
|
107
|
+
const out = {
|
|
108
|
+
type: 'Content',
|
|
109
|
+
props: {
|
|
110
|
+
content
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
const align = oneOf(props.align, TEXT_ALIGNS);
|
|
114
|
+
if (align) out.props.align = align;
|
|
115
|
+
const tone = oneOf(props.tone, TONES);
|
|
116
|
+
if (tone && tone !== 'default') out.props.tone = tone;
|
|
117
|
+
return out;
|
|
118
|
+
}
|
|
119
|
+
if (node.type === 'Image') {
|
|
120
|
+
const image = nonEmptyString(props.image);
|
|
121
|
+
if (!image) return null;
|
|
122
|
+
const out = {
|
|
123
|
+
type: 'Image',
|
|
124
|
+
props: {
|
|
125
|
+
image
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
130
|
+
if (!Array.isArray(node.children)) return null;
|
|
131
|
+
const type = oneOf(node.type, CONTAINER_TYPES) ?? 'Vertical';
|
|
132
|
+
const children = [];
|
|
133
|
+
for (const child of node.children) {
|
|
134
|
+
const normalized = normalizeNode(child, depth + 1, budget);
|
|
135
|
+
if (normalized) children.push(normalized);
|
|
136
|
+
}
|
|
137
|
+
const out = containerProps(props);
|
|
138
|
+
// `order: 'reverse'` is applied right here and consumed: the normalized
|
|
139
|
+
// tree carries the children already reordered (and the sizes template
|
|
140
|
+
// reversed with them, so each child keeps its track) — the renderers and
|
|
141
|
+
// the editor canvas never see the prop. Caveat for hand-authored json: the
|
|
142
|
+
// "keeps its track" guarantee assumes one sizes token per child; with a
|
|
143
|
+
// mismatched count the reversed template pairs children with different
|
|
144
|
+
// tracks (grid itself would misalign such a template the same way).
|
|
145
|
+
if (oneOf(props.order, ORDERS) === 'reverse') {
|
|
146
|
+
children.reverse();
|
|
147
|
+
if (typeof out.sizes === 'string') out.sizes = out.sizes.split(' ').reverse().join(' ');
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
type,
|
|
151
|
+
props: out,
|
|
152
|
+
children
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Validate an embedded layout tree from the stored json. Returns the
|
|
157
|
+
* normalized tree, or null when the value isn't a renderable tree at all —
|
|
158
|
+
* the page is then dropped by resolveWebPopupPages.
|
|
159
|
+
*/
|
|
160
|
+
export function normalizeWebPopupLayout(raw) {
|
|
161
|
+
return normalizeNode(raw, 0, {
|
|
162
|
+
nodes: WEB_POPUP_LAYOUT_MAX_NODES
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
// --- value substitution ($refs) ---------------------------------------------
|
|
166
|
+
/** A page value key, as referenced from the tree by a `$key` token. */
|
|
167
|
+
export const WEB_POPUP_VALUE_KEY_PATTERN = /^[a-zA-Z][a-zA-Z0-9-]*$/;
|
|
168
|
+
const VALUE_REF_PATTERN = /^\$([a-zA-Z][a-zA-Z0-9-]*)$/;
|
|
169
|
+
// Every whitespace-separated token of the form $key is replaced; separators
|
|
170
|
+
// survive, so both a whole-string prop ('$stackGap') and a token inside a
|
|
171
|
+
// sizes template ('1fr $imageWidth') substitute with the same rule. An
|
|
172
|
+
// unknown key stays as-is and fails the prop whitelist downstream — the prop
|
|
173
|
+
// is dropped, same as any invalid literal.
|
|
174
|
+
function substituteString(value, values) {
|
|
175
|
+
if (!value.includes('$')) return value;
|
|
176
|
+
return value.split(/(\s+)/).map(token => {
|
|
177
|
+
const key = VALUE_REF_PATTERN.exec(token)?.[1];
|
|
178
|
+
// Own properties only: '$constructor' must stay an unknown ref, not
|
|
179
|
+
// resolve to an Object.prototype member.
|
|
180
|
+
return key !== undefined && Object.prototype.hasOwnProperty.call(values, key) ? values[key] : token;
|
|
181
|
+
}).join('');
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Substitute a page's named values into its RAW layout tree — `$key` tokens
|
|
185
|
+
* in string props become the values, verbatim. Runs BEFORE
|
|
186
|
+
* normalizeWebPopupLayout (see resolveWebPopupPages), so a substituted value
|
|
187
|
+
* passes exactly the whitelists/clamps a literal one would; substitution is
|
|
188
|
+
* single-pass and normalizeWebPopupValues rejects values containing '$', so
|
|
189
|
+
* refs can't chain. Binding keys (`content`, `image`, `backgroundImage`) are
|
|
190
|
+
* never substituted — they already name params by design.
|
|
191
|
+
*/
|
|
192
|
+
export function substituteWebPopupLayoutValues(layout, values) {
|
|
193
|
+
if (typeof layout !== 'object' || layout === null || Object.keys(values).length === 0) return layout;
|
|
194
|
+
return substituteNode(layout, values, 0);
|
|
195
|
+
}
|
|
196
|
+
const BINDING_PROPS = new Set(['content', 'image', 'backgroundImage']);
|
|
197
|
+
function substituteNode(raw, values, depth) {
|
|
198
|
+
if (depth > WEB_POPUP_LAYOUT_MAX_DEPTH || typeof raw !== 'object' || raw === null) return raw;
|
|
199
|
+
const node = raw;
|
|
200
|
+
const out = {
|
|
201
|
+
...node
|
|
202
|
+
};
|
|
203
|
+
if (typeof node.props === 'object' && node.props !== null) {
|
|
204
|
+
const props = {
|
|
205
|
+
...node.props
|
|
206
|
+
};
|
|
207
|
+
for (const [key, value] of Object.entries(props)) {
|
|
208
|
+
if (BINDING_PROPS.has(key)) continue;
|
|
209
|
+
if (typeof value === 'string') {
|
|
210
|
+
props[key] = substituteString(value, values);
|
|
211
|
+
} else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
212
|
+
// Per-side paddings: one level of string sides.
|
|
213
|
+
const sides = {
|
|
214
|
+
...value
|
|
215
|
+
};
|
|
216
|
+
for (const [side, sideValue] of Object.entries(sides)) {
|
|
217
|
+
if (typeof sideValue === 'string') sides[side] = substituteString(sideValue, values);
|
|
218
|
+
}
|
|
219
|
+
props[key] = sides;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
out.props = props;
|
|
223
|
+
}
|
|
224
|
+
if (Array.isArray(node.children)) {
|
|
225
|
+
out.children = node.children.map(child => substituteNode(child, values, depth + 1));
|
|
226
|
+
}
|
|
227
|
+
return out;
|
|
228
|
+
}
|
|
229
|
+
// --- css conversion (shared by the reader and the editor's canvas) ---------
|
|
230
|
+
export function webPopupCssLength(value) {
|
|
231
|
+
return typeof value === 'number' ? `${value}px` : value;
|
|
232
|
+
}
|
|
233
|
+
export function webPopupPaddingValue(padding) {
|
|
234
|
+
if (typeof padding === 'number' || typeof padding === 'string') return webPopupCssLength(padding);
|
|
235
|
+
const side = value => value === undefined ? '0' : webPopupCssLength(value);
|
|
236
|
+
return `${side(padding.top)} ${side(padding.right)} ${side(padding.bottom)} ${side(padding.left)}`;
|
|
237
|
+
}
|
|
238
|
+
/** grid-template value for Columns/Rows: absent sizes = one 1fr per child. */
|
|
239
|
+
export function webPopupTracksValue(sizes, childCount) {
|
|
240
|
+
if (typeof sizes === 'string') return sizes;
|
|
241
|
+
const count = typeof sizes === 'number' ? sizes : Math.max(childCount, 1);
|
|
242
|
+
return `repeat(${count}, 1fr)`;
|
|
243
|
+
}
|
|
244
|
+
// The scrim as a standalone gradient, for the layers that can't stack it in
|
|
245
|
+
// the same background property (a background VIDEO paints its own element).
|
|
246
|
+
export function webPopupScrimGradient(dim = 0) {
|
|
247
|
+
if (!(dim > 0)) return null;
|
|
248
|
+
const scrim = `rgba(16, 24, 40, ${Math.min(dim, 1)})`;
|
|
249
|
+
return `linear-gradient(${scrim}, ${scrim})`;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* The cover-background css of a container that draws an image behind its
|
|
253
|
+
* content. `dim` (0–1) is the image's scrim — a dark translucent layer
|
|
254
|
+
* painted as a gradient stacked over the url() in the same property, so it
|
|
255
|
+
* needs no extra element and rides along wherever the background is drawn.
|
|
256
|
+
*/
|
|
257
|
+
export function webPopupBackgroundImageStyle(src, dim = 0) {
|
|
258
|
+
// JSON.stringify quotes + escapes the url, so an src can't break out of
|
|
259
|
+
// the css function.
|
|
260
|
+
const image = `url(${JSON.stringify(src)})`;
|
|
261
|
+
const scrim = webPopupScrimGradient(dim);
|
|
262
|
+
return {
|
|
263
|
+
backgroundImage: scrim ? `${scrim}, ${image}` : image,
|
|
264
|
+
backgroundSize: 'cover',
|
|
265
|
+
backgroundPosition: 'center'
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* The inline style of a container node — grid, mirroring kit-helpers'
|
|
270
|
+
* Vertical/Horizontal/Columns/Rows semantics. The background image is passed
|
|
271
|
+
* resolved (looked up in the page's images by the caller) so this stays a
|
|
272
|
+
* pure props → css mapping.
|
|
273
|
+
*/
|
|
274
|
+
export function webPopupContainerStyle(node, backgroundSrc, backgroundDim = 0) {
|
|
275
|
+
const props = node.props ?? {};
|
|
276
|
+
const style = {
|
|
277
|
+
display: 'grid',
|
|
278
|
+
minWidth: 0
|
|
279
|
+
};
|
|
280
|
+
if (node.type === 'Horizontal') style.gridAutoFlow = 'column';
|
|
281
|
+
if (node.type === 'Columns') style.gridTemplateColumns = webPopupTracksValue(props.sizes, node.children.length);
|
|
282
|
+
if (node.type === 'Rows') style.gridTemplateRows = webPopupTracksValue(props.sizes, node.children.length);
|
|
283
|
+
if (props.gap !== undefined) style.gap = webPopupCssLength(props.gap);
|
|
284
|
+
if (props.padding !== undefined) style.padding = webPopupPaddingValue(props.padding);
|
|
285
|
+
if (props.alignItems) style.alignItems = props.alignItems;
|
|
286
|
+
if (props.justifyContent) style.justifyContent = props.justifyContent;
|
|
287
|
+
if (props.alignContent) style.alignContent = props.alignContent;
|
|
288
|
+
if (backgroundSrc) Object.assign(style, webPopupBackgroundImageStyle(backgroundSrc, backgroundDim));
|
|
289
|
+
return style;
|
|
290
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type WebPopupColorScheme, type WebPopupColors, type WebPopupDark, type WebPopupImage, type WebPopupOverlay, type WebPopupResolvedColors } from './types';
|
|
2
|
+
/** Hue and saturation survive; only lightness moves. Non-hex colors pass through. */
|
|
3
|
+
export declare function webPopupShiftColor(color: string, direction: 'fold' | 'lift'): string;
|
|
4
|
+
/** `ratio` of `over` blended into `base`; either color non-hex = `base` unchanged. */
|
|
5
|
+
export declare function webPopupMixColor(base: string, over: string, ratio: number): string;
|
|
6
|
+
/** `color` at `alpha` as rgba(); a non-hex color renders full-strength instead of being guessed at. */
|
|
7
|
+
export declare function webPopupColorAlpha(color: string, alpha: number): string;
|
|
8
|
+
export declare function autoDarkWebPopupColors(light: WebPopupColors): WebPopupColors;
|
|
9
|
+
/** The dark palette a popup renders with: authored roles over the autogenerated ones. */
|
|
10
|
+
export declare function webPopupDarkColors(light: WebPopupColors, dark?: WebPopupDark): WebPopupColors;
|
|
11
|
+
/** True when the popup opts into dark mode at all — the host's cue to watch the media query. */
|
|
12
|
+
export declare function webPopupDarkEnabled(chrome: {
|
|
13
|
+
dark?: WebPopupDark;
|
|
14
|
+
}): boolean;
|
|
15
|
+
/** The scheme a popup actually draws with: 'dark' only when it opts in. */
|
|
16
|
+
export declare function webPopupEffectiveScheme(chrome: {
|
|
17
|
+
dark?: WebPopupDark;
|
|
18
|
+
}, scheme?: WebPopupColorScheme): WebPopupColorScheme;
|
|
19
|
+
export declare function resolveWebPopupColors(chrome: {
|
|
20
|
+
colors: WebPopupColors;
|
|
21
|
+
dark?: WebPopupDark;
|
|
22
|
+
}, scheme?: WebPopupColorScheme): WebPopupResolvedColors;
|
|
23
|
+
/** The url drawn for `scheme`; a dark source only exists when authored. */
|
|
24
|
+
export declare function webPopupImageSrc(image: WebPopupImage, scheme?: WebPopupColorScheme): string;
|
|
25
|
+
/** The page dim and box shadow for the scheme actually drawn; the light ones
|
|
26
|
+
* stand in wherever the dark palette authored no override. */
|
|
27
|
+
export declare function resolveWebPopupBoxChrome(chrome: {
|
|
28
|
+
dark?: WebPopupDark;
|
|
29
|
+
overlay?: WebPopupOverlay;
|
|
30
|
+
boxShadow: string;
|
|
31
|
+
}, scheme?: WebPopupColorScheme): {
|
|
32
|
+
overlay?: WebPopupOverlay;
|
|
33
|
+
boxShadow: string;
|
|
34
|
+
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
// Scheme resolution for the popup palette: the authored light/dark roles plus
|
|
2
|
+
// the derived ones (input surface, borders, the label over an accent fill).
|
|
3
|
+
import { WEB_POPUP_DEFAULT_COLORS, WEB_POPUP_FORM_ERROR_COLOR, WEB_POPUP_FORM_ERROR_COLOR_DARK, WEB_POPUP_FORM_INPUT_BORDER, WEB_POPUP_ON_ACCENT_COLOR, WEB_POPUP_TEXT_COLOR, WEB_POPUP_TEXT_SECONDARY_COLOR } from './constants';
|
|
4
|
+
const HEX_RE = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
|
|
5
|
+
function parseHex(color) {
|
|
6
|
+
const hex = HEX_RE.exec(color.trim())?.[1];
|
|
7
|
+
if (!hex) return null;
|
|
8
|
+
const full = hex.length === 3 ? hex.split('').map(c => c + c).join('') : hex;
|
|
9
|
+
return [0, 2, 4].map(i => parseInt(full.slice(i, i + 2), 16));
|
|
10
|
+
}
|
|
11
|
+
function toHex([r, g, b]) {
|
|
12
|
+
return `#${[r, g, b].map(v => Math.max(0, Math.min(255, Math.round(v))).toString(16).padStart(2, '0')).join('')}`;
|
|
13
|
+
}
|
|
14
|
+
function rgbToHsl([r, g, b]) {
|
|
15
|
+
const [rn, gn, bn] = [r / 255, g / 255, b / 255];
|
|
16
|
+
const max = Math.max(rn, gn, bn);
|
|
17
|
+
const min = Math.min(rn, gn, bn);
|
|
18
|
+
const l = (max + min) / 2;
|
|
19
|
+
if (max === min) return [0, 0, l];
|
|
20
|
+
const d = max - min;
|
|
21
|
+
const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
22
|
+
let h;
|
|
23
|
+
if (max === rn) h = (gn - bn) / d + (gn < bn ? 6 : 0);else if (max === gn) h = (bn - rn) / d + 2;else h = (rn - gn) / d + 4;
|
|
24
|
+
return [h / 6, s, l];
|
|
25
|
+
}
|
|
26
|
+
function hueToChannel(p, q, t) {
|
|
27
|
+
let tn = t;
|
|
28
|
+
if (tn < 0) tn += 1;
|
|
29
|
+
if (tn > 1) tn -= 1;
|
|
30
|
+
if (tn < 1 / 6) return p + (q - p) * 6 * tn;
|
|
31
|
+
if (tn < 1 / 2) return q;
|
|
32
|
+
if (tn < 2 / 3) return p + (q - p) * (2 / 3 - tn) * 6;
|
|
33
|
+
return p;
|
|
34
|
+
}
|
|
35
|
+
function hslToRgb([h, s, l]) {
|
|
36
|
+
if (s === 0) {
|
|
37
|
+
const v = l * 255;
|
|
38
|
+
return [v, v, v];
|
|
39
|
+
}
|
|
40
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
41
|
+
const p = 2 * l - q;
|
|
42
|
+
return [hueToChannel(p, q, h + 1 / 3) * 255, hueToChannel(p, q, h) * 255, hueToChannel(p, q, h - 1 / 3) * 255];
|
|
43
|
+
}
|
|
44
|
+
// Mirror the light half onto [0.07, 0.5]: #FFFFFF lands on the dark canvas
|
|
45
|
+
// tone and near-whites stay apart instead of collapsing into one floor.
|
|
46
|
+
function foldLightness(l) {
|
|
47
|
+
return l > 0.5 ? 0.07 + (1 - l) * ((0.5 - 0.07) / 0.5) : l;
|
|
48
|
+
}
|
|
49
|
+
// Mirror the dark half onto [0.5, 0.93]: #000000 lands short of glaring white.
|
|
50
|
+
function liftLightness(l) {
|
|
51
|
+
return l < 0.5 ? 0.93 - l * ((0.93 - 0.5) / 0.5) : l;
|
|
52
|
+
}
|
|
53
|
+
/** Hue and saturation survive; only lightness moves. Non-hex colors pass through. */
|
|
54
|
+
export function webPopupShiftColor(color, direction) {
|
|
55
|
+
const rgb = parseHex(color);
|
|
56
|
+
if (!rgb) return color;
|
|
57
|
+
const [h, s, l] = rgbToHsl(rgb);
|
|
58
|
+
return toHex(hslToRgb([h, s, direction === 'fold' ? foldLightness(l) : liftLightness(l)]));
|
|
59
|
+
}
|
|
60
|
+
/** `ratio` of `over` blended into `base`; either color non-hex = `base` unchanged. */
|
|
61
|
+
export function webPopupMixColor(base, over, ratio) {
|
|
62
|
+
const a = parseHex(base);
|
|
63
|
+
const b = parseHex(over);
|
|
64
|
+
if (!a || !b) return base;
|
|
65
|
+
return toHex(a.map((v, i) => v + (b[i] - v) * ratio));
|
|
66
|
+
}
|
|
67
|
+
/** `color` at `alpha` as rgba(); a non-hex color renders full-strength instead of being guessed at. */
|
|
68
|
+
export function webPopupColorAlpha(color, alpha) {
|
|
69
|
+
const rgb = parseHex(color);
|
|
70
|
+
if (!rgb || alpha >= 1) return color;
|
|
71
|
+
const [r, g, b] = rgb;
|
|
72
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
73
|
+
}
|
|
74
|
+
function isLightColor(color) {
|
|
75
|
+
const rgb = parseHex(color);
|
|
76
|
+
if (!rgb) return false;
|
|
77
|
+
// Perceived luminance, not HSL lightness: #FFFF00 and #0000FF share l = 0.5.
|
|
78
|
+
const [r, g, b] = rgb;
|
|
79
|
+
return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.6;
|
|
80
|
+
}
|
|
81
|
+
// The dark counterpart of a light palette: text lifts, surfaces fold, the
|
|
82
|
+
// accent and the over-image inverse color stay as authored.
|
|
83
|
+
export function autoDarkWebPopupColors(light) {
|
|
84
|
+
return {
|
|
85
|
+
background: webPopupShiftColor(light.background, 'fold'),
|
|
86
|
+
accent: light.accent,
|
|
87
|
+
text: webPopupShiftColor(light.text, 'lift'),
|
|
88
|
+
textSecondary: webPopupShiftColor(light.textSecondary, 'lift'),
|
|
89
|
+
textInverse: light.textInverse
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// Light keeps the values the popup shipped with before dark mode existed
|
|
93
|
+
// (white cards, grey border, white label) — enabling dark must not restyle it.
|
|
94
|
+
function derivedColors(colors, scheme) {
|
|
95
|
+
if (scheme === 'light') {
|
|
96
|
+
return {
|
|
97
|
+
surface: WEB_POPUP_DEFAULT_COLORS.background,
|
|
98
|
+
surfaceText: WEB_POPUP_TEXT_COLOR,
|
|
99
|
+
border: WEB_POPUP_FORM_INPUT_BORDER,
|
|
100
|
+
onAccent: WEB_POPUP_ON_ACCENT_COLOR,
|
|
101
|
+
error: WEB_POPUP_FORM_ERROR_COLOR,
|
|
102
|
+
chromeIcon: WEB_POPUP_TEXT_SECONDARY_COLOR
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
surface: webPopupMixColor(colors.background, colors.text, 0.08),
|
|
107
|
+
surfaceText: colors.text,
|
|
108
|
+
border: webPopupMixColor(colors.background, colors.text, 0.24),
|
|
109
|
+
onAccent: isLightColor(colors.accent) ? WEB_POPUP_TEXT_COLOR : WEB_POPUP_ON_ACCENT_COLOR,
|
|
110
|
+
error: WEB_POPUP_FORM_ERROR_COLOR_DARK,
|
|
111
|
+
chromeIcon: colors.textSecondary
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/** The dark palette a popup renders with: authored roles over the autogenerated ones. */
|
|
115
|
+
export function webPopupDarkColors(light, dark) {
|
|
116
|
+
return {
|
|
117
|
+
...autoDarkWebPopupColors(light),
|
|
118
|
+
...(dark?.colors ?? {})
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/** True when the popup opts into dark mode at all — the host's cue to watch the media query. */
|
|
122
|
+
export function webPopupDarkEnabled(chrome) {
|
|
123
|
+
return chrome.dark?.enabled === true;
|
|
124
|
+
}
|
|
125
|
+
/** The scheme a popup actually draws with: 'dark' only when it opts in. */
|
|
126
|
+
export function webPopupEffectiveScheme(chrome, scheme = 'light') {
|
|
127
|
+
return scheme === 'dark' && webPopupDarkEnabled(chrome) ? 'dark' : 'light';
|
|
128
|
+
}
|
|
129
|
+
// Every color a renderer needs, for the scheme actually drawn. Asking for
|
|
130
|
+
// 'dark' on a popup without dark mode returns the light palette.
|
|
131
|
+
export function resolveWebPopupColors(chrome, scheme = 'light') {
|
|
132
|
+
const effective = webPopupEffectiveScheme(chrome, scheme);
|
|
133
|
+
const colors = effective === 'dark' ? webPopupDarkColors(chrome.colors, chrome.dark) : chrome.colors;
|
|
134
|
+
return {
|
|
135
|
+
...colors,
|
|
136
|
+
scheme: effective,
|
|
137
|
+
...derivedColors(colors, effective)
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/** The url drawn for `scheme`; a dark source only exists when authored. */
|
|
141
|
+
export function webPopupImageSrc(image, scheme = 'light') {
|
|
142
|
+
return scheme === 'dark' && image.srcDark ? image.srcDark : image.src;
|
|
143
|
+
}
|
|
144
|
+
/** The page dim and box shadow for the scheme actually drawn; the light ones
|
|
145
|
+
* stand in wherever the dark palette authored no override. */
|
|
146
|
+
export function resolveWebPopupBoxChrome(chrome, scheme = 'light') {
|
|
147
|
+
if (webPopupEffectiveScheme(chrome, scheme) === 'light') {
|
|
148
|
+
return {
|
|
149
|
+
overlay: chrome.overlay,
|
|
150
|
+
boxShadow: chrome.boxShadow
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
overlay: chrome.dark?.overlay ?? chrome.overlay,
|
|
155
|
+
boxShadow: chrome.dark?.boxShadow ?? chrome.boxShadow
|
|
156
|
+
};
|
|
157
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// The system layout templates — the small blessed set of arrangements the
|
|
2
|
+
// editor's kind catalog (smart-blocks) instantiates: a kind is a template
|
|
3
|
+
// name plus gallery data (label, chrome, demo content, value defaults); only
|
|
4
|
+
// a genuinely unique arrangement warrants a kind carrying its own tree. The
|
|
5
|
+
// trees reference `$key` value params (resolved per page at render, see
|
|
6
|
+
// substituteWebPopupLayoutValues) and bind the canonical keys — zone 'main',
|
|
7
|
+
// image 'image' — which the catalog may rebind when instantiating.
|
|
8
|
+
// EDITOR-ONLY, like the kind catalog: the display engine renders the tree
|
|
9
|
+
// embedded in each page and must not pull this module.
|
|
10
|
+
// The default padded box around a content zone (matches the editor canvas).
|
|
11
|
+
const CONTENT_PAD = {
|
|
12
|
+
top: 24,
|
|
13
|
+
right: 24,
|
|
14
|
+
bottom: 22,
|
|
15
|
+
left: 24
|
|
16
|
+
};
|
|
17
|
+
// The single content zone in a padded box — the whole `content` template and
|
|
18
|
+
// the text half of the image ones.
|
|
19
|
+
const contentStack = () => ({
|
|
20
|
+
type: 'Vertical',
|
|
21
|
+
props: {
|
|
22
|
+
padding: CONTENT_PAD
|
|
23
|
+
},
|
|
24
|
+
children: [{
|
|
25
|
+
type: 'Content',
|
|
26
|
+
props: {
|
|
27
|
+
content: 'main',
|
|
28
|
+
align: 'center'
|
|
29
|
+
}
|
|
30
|
+
}]
|
|
31
|
+
});
|
|
32
|
+
const imageWidthParam = () => ({
|
|
33
|
+
key: 'imageWidth',
|
|
34
|
+
label: 'Image width',
|
|
35
|
+
control: {
|
|
36
|
+
kind: 'percent',
|
|
37
|
+
min: 20,
|
|
38
|
+
max: 70
|
|
39
|
+
},
|
|
40
|
+
value: '45%'
|
|
41
|
+
});
|
|
42
|
+
const imageSideParam = () => ({
|
|
43
|
+
key: 'imageSide',
|
|
44
|
+
label: 'Image position',
|
|
45
|
+
control: {
|
|
46
|
+
kind: 'select',
|
|
47
|
+
options: [{
|
|
48
|
+
value: 'normal',
|
|
49
|
+
label: 'Right'
|
|
50
|
+
}, {
|
|
51
|
+
value: 'reverse',
|
|
52
|
+
label: 'Left'
|
|
53
|
+
}]
|
|
54
|
+
},
|
|
55
|
+
value: 'normal'
|
|
56
|
+
});
|
|
57
|
+
export const WEB_POPUP_TEMPLATES = [
|
|
58
|
+
// Just the content stack, no image.
|
|
59
|
+
{
|
|
60
|
+
name: 'content',
|
|
61
|
+
layout: contentStack(),
|
|
62
|
+
params: []
|
|
63
|
+
},
|
|
64
|
+
// Image on top, the content stack below.
|
|
65
|
+
{
|
|
66
|
+
name: 'vertical',
|
|
67
|
+
layout: {
|
|
68
|
+
type: 'Vertical',
|
|
69
|
+
children: [{
|
|
70
|
+
type: 'Image',
|
|
71
|
+
props: {
|
|
72
|
+
image: 'image'
|
|
73
|
+
}
|
|
74
|
+
}, contentStack()]
|
|
75
|
+
},
|
|
76
|
+
params: []
|
|
77
|
+
},
|
|
78
|
+
// Image beside the content; which side and how wide are the page's knobs.
|
|
79
|
+
{
|
|
80
|
+
name: 'horizontal',
|
|
81
|
+
layout: {
|
|
82
|
+
type: 'Columns',
|
|
83
|
+
props: {
|
|
84
|
+
sizes: '1fr $imageWidth',
|
|
85
|
+
alignItems: 'center',
|
|
86
|
+
order: '$imageSide'
|
|
87
|
+
},
|
|
88
|
+
children: [contentStack(), {
|
|
89
|
+
type: 'Image',
|
|
90
|
+
props: {
|
|
91
|
+
image: 'image'
|
|
92
|
+
}
|
|
93
|
+
}]
|
|
94
|
+
},
|
|
95
|
+
params: [imageWidthParam(), imageSideParam()]
|
|
96
|
+
},
|
|
97
|
+
// The content over a cover background image (an inverse-tone zone: the
|
|
98
|
+
// copy uses the palette's textInverse, the image gets the Dim scrim knob).
|
|
99
|
+
{
|
|
100
|
+
name: 'content-over-image',
|
|
101
|
+
layout: {
|
|
102
|
+
type: 'Vertical',
|
|
103
|
+
props: {
|
|
104
|
+
padding: {
|
|
105
|
+
top: 36,
|
|
106
|
+
right: 24,
|
|
107
|
+
bottom: 34,
|
|
108
|
+
left: 24
|
|
109
|
+
},
|
|
110
|
+
backgroundImage: 'image'
|
|
111
|
+
},
|
|
112
|
+
children: [{
|
|
113
|
+
type: 'Content',
|
|
114
|
+
props: {
|
|
115
|
+
content: 'main',
|
|
116
|
+
align: 'center',
|
|
117
|
+
tone: 'inverse'
|
|
118
|
+
}
|
|
119
|
+
}]
|
|
120
|
+
},
|
|
121
|
+
params: []
|
|
122
|
+
}];
|
|
123
|
+
export function findWebPopupTemplate(name) {
|
|
124
|
+
return WEB_POPUP_TEMPLATES.find(template => template.name === name);
|
|
125
|
+
}
|