react-native-revnix 1.4.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/LICENSE +21 -0
- package/README.md +766 -0
- package/dist/blocks/background.d.ts +101 -0
- package/dist/blocks/background.js +165 -0
- package/dist/blocks/color.d.ts +88 -0
- package/dist/blocks/color.js +329 -0
- package/dist/blocks/locale.d.ts +42 -0
- package/dist/blocks/locale.js +263 -0
- package/dist/blocks/selection.d.ts +49 -0
- package/dist/blocks/selection.js +109 -0
- package/dist/blocks/style.d.ts +93 -0
- package/dist/blocks/style.js +365 -0
- package/dist/blocks/tags.d.ts +12 -0
- package/dist/blocks/tags.js +121 -0
- package/dist/blocks/types.d.ts +301 -0
- package/dist/blocks/types.js +44 -0
- package/dist/client.d.ts +402 -0
- package/dist/client.js +1653 -0
- package/dist/device.d.ts +29 -0
- package/dist/device.js +137 -0
- package/dist/errors.d.ts +39 -0
- package/dist/errors.js +68 -0
- package/dist/esm/blocks/background.js +160 -0
- package/dist/esm/blocks/color.js +318 -0
- package/dist/esm/blocks/locale.js +255 -0
- package/dist/esm/blocks/selection.js +100 -0
- package/dist/esm/blocks/style.js +356 -0
- package/dist/esm/blocks/tags.js +117 -0
- package/dist/esm/blocks/types.js +40 -0
- package/dist/esm/client.js +1649 -0
- package/dist/esm/device.js +130 -0
- package/dist/esm/errors.js +59 -0
- package/dist/esm/iap.js +101 -0
- package/dist/esm/implicit.js +63 -0
- package/dist/esm/index.js +29 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/storage.js +78 -0
- package/dist/esm/types.js +4 -0
- package/dist/esm/ui/RevnixBlockPaywall.js +706 -0
- package/dist/esm/ui/RevnixPaywall.js +890 -0
- package/dist/esm/ui/index.js +6 -0
- package/dist/esm/version.js +4 -0
- package/dist/iap.d.ts +20 -0
- package/dist/iap.js +104 -0
- package/dist/implicit.d.ts +69 -0
- package/dist/implicit.js +67 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +50 -0
- package/dist/storage.d.ts +14 -0
- package/dist/storage.js +85 -0
- package/dist/types.d.ts +463 -0
- package/dist/types.js +5 -0
- package/dist/ui/RevnixBlockPaywall.d.ts +96 -0
- package/dist/ui/RevnixBlockPaywall.js +742 -0
- package/dist/ui/RevnixPaywall.d.ts +148 -0
- package/dist/ui/RevnixPaywall.js +927 -0
- package/dist/ui/index.d.ts +7 -0
- package/dist/ui/index.js +25 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +7 -0
- package/package.json +79 -0
- package/src/blocks/background.ts +232 -0
- package/src/blocks/color.ts +352 -0
- package/src/blocks/locale.ts +271 -0
- package/src/blocks/paywall-background-wire.json +12 -0
- package/src/blocks/paywall-selection-wire.json +149 -0
- package/src/blocks/selection.ts +161 -0
- package/src/blocks/style.ts +411 -0
- package/src/blocks/tags.ts +125 -0
- package/src/blocks/types.ts +352 -0
- package/src/client.ts +1993 -0
- package/src/device.ts +146 -0
- package/src/errors.ts +69 -0
- package/src/iap.ts +137 -0
- package/src/implicit.ts +129 -0
- package/src/index.ts +82 -0
- package/src/storage.ts +92 -0
- package/src/types.ts +528 -0
- package/src/ui/RevnixBlockPaywall.tsx +1423 -0
- package/src/ui/RevnixPaywall.tsx +1542 -0
- package/src/ui/index.ts +43 -0
- package/src/version.ts +4 -0
- package/ui.d.ts +1 -0
- package/ui.js +5 -0
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/** Per-block visual style. Everything optional — a block renders sensibly
|
|
2
|
+
* with no style at all. Sizes are px numbers; colors are any CSS color, or
|
|
3
|
+
* a palette token (see `resolveColor`). */
|
|
4
|
+
import type { PaywallBackground } from "./background.js";
|
|
5
|
+
export type { BackgroundFit, BackgroundImage, BackgroundLayers, BackgroundOverlay, PaywallBackground, PaywallBackgroundSpec, } from "./background.js";
|
|
6
|
+
export interface BlockStyle {
|
|
7
|
+
fill?: string;
|
|
8
|
+
/** Sizing for an image or repeating-gradient `fill` (CSS background-size).
|
|
9
|
+
* RN paints `fill` as a flat colour, so tiled washes do not survive the
|
|
10
|
+
* trip and this is ignored. */
|
|
11
|
+
fillSize?: string;
|
|
12
|
+
textColor?: string;
|
|
13
|
+
/** 0–100, like the dashboard's opacity inputs. */
|
|
14
|
+
opacity?: number;
|
|
15
|
+
borderColor?: string;
|
|
16
|
+
borderWidth?: number;
|
|
17
|
+
/** Per-side rules, as a CSS border shorthand ("1px solid @text/12"). */
|
|
18
|
+
borderTop?: string;
|
|
19
|
+
borderRight?: string;
|
|
20
|
+
borderBottom?: string;
|
|
21
|
+
borderLeft?: string;
|
|
22
|
+
radius?: number;
|
|
23
|
+
padding?: number;
|
|
24
|
+
paddingX?: number;
|
|
25
|
+
paddingY?: number;
|
|
26
|
+
/** Per-side padding wins over the shorthands above. */
|
|
27
|
+
paddingTop?: number;
|
|
28
|
+
paddingRight?: number;
|
|
29
|
+
paddingBottom?: number;
|
|
30
|
+
paddingLeft?: number;
|
|
31
|
+
/** px numbers, or "auto" — the designs centre and bottom-pin with it. */
|
|
32
|
+
margin?: number;
|
|
33
|
+
marginTop?: number | "auto";
|
|
34
|
+
marginRight?: number | "auto";
|
|
35
|
+
marginBottom?: number | "auto";
|
|
36
|
+
marginLeft?: number | "auto";
|
|
37
|
+
fontSize?: number;
|
|
38
|
+
fontWeight?: number;
|
|
39
|
+
/** A design font family name (e.g. "Newsreader"). Renders only when the
|
|
40
|
+
* host app has that font registered — see `fontFamilyMap`. */
|
|
41
|
+
fontFamily?: string;
|
|
42
|
+
fontStyle?: "normal" | "italic";
|
|
43
|
+
align?: "left" | "center" | "right";
|
|
44
|
+
/** In em, like CSS. Converted to points against the block's font size. */
|
|
45
|
+
letterSpacing?: number;
|
|
46
|
+
/** Unitless multiplier, like CSS. Converted to points. */
|
|
47
|
+
lineHeight?: number;
|
|
48
|
+
textTransform?: "none" | "uppercase";
|
|
49
|
+
decoration?: "none" | "underline" | "line-through";
|
|
50
|
+
/** Line-breaking preference. No RN equivalent — ignored. */
|
|
51
|
+
textWrap?: "balance" | "pretty";
|
|
52
|
+
/** Keep the copy on one line. */
|
|
53
|
+
nowrap?: boolean;
|
|
54
|
+
/** Gap between a container's children. */
|
|
55
|
+
gap?: number;
|
|
56
|
+
/** px number, or a percentage string ("78%") for proportional boxes. */
|
|
57
|
+
height?: number | string;
|
|
58
|
+
minHeight?: number;
|
|
59
|
+
/** px number, or a percentage string ("50%"). */
|
|
60
|
+
width?: number | string;
|
|
61
|
+
maxWidth?: number | string;
|
|
62
|
+
/** Width-to-height ratio; "16/9" strings are parsed. */
|
|
63
|
+
aspectRatio?: number | string;
|
|
64
|
+
/** flex-grow inside a row/column container. */
|
|
65
|
+
flex?: number;
|
|
66
|
+
/** flex-shrink; 0 stops a row item from being squashed. */
|
|
67
|
+
shrink?: number;
|
|
68
|
+
/** flex-basis in px — carousel cards and side rails are sized by basis,
|
|
69
|
+
* and collapse to nothing without it. */
|
|
70
|
+
basis?: number;
|
|
71
|
+
/** Let a row wrap onto more lines. */
|
|
72
|
+
wrap?: boolean;
|
|
73
|
+
/** Main-axis distribution for row/column containers. */
|
|
74
|
+
justify?: "start" | "center" | "end" | "between";
|
|
75
|
+
/** Cross-axis alignment for row/column containers. */
|
|
76
|
+
items?: "start" | "center" | "end" | "baseline" | "stretch";
|
|
77
|
+
/** Cross-axis alignment for THIS block, overriding the container's
|
|
78
|
+
* `items` — how a design opts one chip out of a stretched row. */
|
|
79
|
+
selfAlign?: "start" | "center" | "end" | "baseline" | "stretch";
|
|
80
|
+
/** Raw CSS box-shadow. Approximated by a soft RN shadow. */
|
|
81
|
+
shadow?: string;
|
|
82
|
+
/** backdrop-filter blur radius in px (glass cards). RN has no backdrop
|
|
83
|
+
* filter; the translucent `fill` carries the effect on its own. */
|
|
84
|
+
blur?: number;
|
|
85
|
+
/** Raw CSS filter. No RN equivalent — ignored. */
|
|
86
|
+
filter?: string;
|
|
87
|
+
/** Raw CSS clip-path (starbursts, ticket notches). No RN equivalent —
|
|
88
|
+
* ignored, so the shape renders as its uncut box. */
|
|
89
|
+
clipPath?: string;
|
|
90
|
+
rotate?: number;
|
|
91
|
+
/** CSS `translate` ("-50% 0"), which the designs pair with a 50% offset to
|
|
92
|
+
* centre a pinned badge. RN transforms take points, not percentages, so a
|
|
93
|
+
* percentage translate is ignored; a px one is applied. */
|
|
94
|
+
translate?: string;
|
|
95
|
+
/** Placement inside a `stack` container. `inset` fills the stack; the
|
|
96
|
+
* individual offsets pin an edge. Ignored outside a stack. */
|
|
97
|
+
inset?: boolean;
|
|
98
|
+
/** px numbers, or percentage strings ("50%") for proportional pinning. */
|
|
99
|
+
top?: number | string;
|
|
100
|
+
right?: number | string;
|
|
101
|
+
bottom?: number | string;
|
|
102
|
+
left?: number | string;
|
|
103
|
+
zIndex?: number;
|
|
104
|
+
overflow?: "hidden" | "visible";
|
|
105
|
+
}
|
|
106
|
+
/** What tapping a block does. Omitted means the block is decoration.
|
|
107
|
+
*
|
|
108
|
+
* A FIELD on the existing block types rather than a new block type, so an
|
|
109
|
+
* SDK older than this one drops it and still renders the element exactly as
|
|
110
|
+
* it does today — a design carrying a close chip degrades to inert rather
|
|
111
|
+
* than losing the chip, which is what a new block type would have cost. */
|
|
112
|
+
/** REV-262: draw a block only while its plan card is (or is not) the selected
|
|
113
|
+
* one. Ignored outside a package card — a root block is always drawn. */
|
|
114
|
+
export type BlockVisibility = "selected" | "unselected";
|
|
115
|
+
export type BlockAction = "close";
|
|
116
|
+
export interface TextBlock {
|
|
117
|
+
id: string;
|
|
118
|
+
type: "text";
|
|
119
|
+
text: string;
|
|
120
|
+
/** Tapping this block dismisses the paywall. See BlockAction. */
|
|
121
|
+
action?: BlockAction;
|
|
122
|
+
style?: BlockStyle;
|
|
123
|
+
/** REV-262: merged over `style`, field by field, while the block sits in a
|
|
124
|
+
* selected plan card (a pinned or repeated card whose package is the
|
|
125
|
+
* selected one, or any descendant of such a card). */
|
|
126
|
+
selectedStyle?: BlockStyle;
|
|
127
|
+
visibility?: BlockVisibility;
|
|
128
|
+
}
|
|
129
|
+
export interface ImageBlock {
|
|
130
|
+
id: string;
|
|
131
|
+
type: "image";
|
|
132
|
+
/** Empty falls back to the config's hero image, then to a blank slot. */
|
|
133
|
+
url?: string;
|
|
134
|
+
shape?: "rect" | "circle";
|
|
135
|
+
fit?: "cover" | "contain";
|
|
136
|
+
placeholder?: string;
|
|
137
|
+
/** Tapping this block dismisses the paywall. See BlockAction. */
|
|
138
|
+
action?: BlockAction;
|
|
139
|
+
style?: BlockStyle;
|
|
140
|
+
/** REV-262: merged over `style`, field by field, while the block sits in a
|
|
141
|
+
* selected plan card (a pinned or repeated card whose package is the
|
|
142
|
+
* selected one, or any descendant of such a card). */
|
|
143
|
+
selectedStyle?: BlockStyle;
|
|
144
|
+
visibility?: BlockVisibility;
|
|
145
|
+
}
|
|
146
|
+
export interface ListBlock {
|
|
147
|
+
id: string;
|
|
148
|
+
type: "list";
|
|
149
|
+
items: Array<{
|
|
150
|
+
icon?: string;
|
|
151
|
+
title: string;
|
|
152
|
+
description?: string;
|
|
153
|
+
}>;
|
|
154
|
+
/** Icon color; defaults to the screen accent. */
|
|
155
|
+
iconColor?: string;
|
|
156
|
+
style?: BlockStyle;
|
|
157
|
+
/** REV-262: merged over `style`, field by field, while the block sits in a
|
|
158
|
+
* selected plan card (a pinned or repeated card whose package is the
|
|
159
|
+
* selected one, or any descendant of such a card). */
|
|
160
|
+
selectedStyle?: BlockStyle;
|
|
161
|
+
visibility?: BlockVisibility;
|
|
162
|
+
}
|
|
163
|
+
/** Renders the attached offering's packages as selectable cards. */
|
|
164
|
+
export interface ProductsBlock {
|
|
165
|
+
id: string;
|
|
166
|
+
type: "products";
|
|
167
|
+
direction?: "column" | "row";
|
|
168
|
+
titleTpl?: string;
|
|
169
|
+
priceTpl?: string;
|
|
170
|
+
highlightSub?: string;
|
|
171
|
+
badgeText?: string;
|
|
172
|
+
cardStyle?: BlockStyle;
|
|
173
|
+
highlightStyle?: BlockStyle;
|
|
174
|
+
style?: BlockStyle;
|
|
175
|
+
/** REV-262: merged over `style`, field by field, while the block sits in a
|
|
176
|
+
* selected plan card (a pinned or repeated card whose package is the
|
|
177
|
+
* selected one, or any descendant of such a card). */
|
|
178
|
+
selectedStyle?: BlockStyle;
|
|
179
|
+
visibility?: BlockVisibility;
|
|
180
|
+
}
|
|
181
|
+
export interface ButtonBlock {
|
|
182
|
+
id: string;
|
|
183
|
+
type: "button";
|
|
184
|
+
label: string;
|
|
185
|
+
/** `close` turns this button into a dismiss ("Not now") instead of the
|
|
186
|
+
* purchase CTA, which is what a button means by default. */
|
|
187
|
+
action?: BlockAction;
|
|
188
|
+
style?: BlockStyle;
|
|
189
|
+
/** REV-262: merged over `style`, field by field, while the block sits in a
|
|
190
|
+
* selected plan card (a pinned or repeated card whose package is the
|
|
191
|
+
* selected one, or any descendant of such a card). */
|
|
192
|
+
selectedStyle?: BlockStyle;
|
|
193
|
+
visibility?: BlockVisibility;
|
|
194
|
+
}
|
|
195
|
+
export interface LinksBlock {
|
|
196
|
+
id: string;
|
|
197
|
+
type: "links";
|
|
198
|
+
showRestore?: boolean;
|
|
199
|
+
showTerms?: boolean;
|
|
200
|
+
showPrivacy?: boolean;
|
|
201
|
+
termsUrl?: string;
|
|
202
|
+
privacyUrl?: string;
|
|
203
|
+
style?: BlockStyle;
|
|
204
|
+
/** REV-262: merged over `style`, field by field, while the block sits in a
|
|
205
|
+
* selected plan card (a pinned or repeated card whose package is the
|
|
206
|
+
* selected one, or any descendant of such a card). */
|
|
207
|
+
selectedStyle?: BlockStyle;
|
|
208
|
+
visibility?: BlockVisibility;
|
|
209
|
+
}
|
|
210
|
+
export interface LineBlock {
|
|
211
|
+
id: string;
|
|
212
|
+
type: "line";
|
|
213
|
+
style?: BlockStyle;
|
|
214
|
+
/** REV-262: merged over `style`, field by field, while the block sits in a
|
|
215
|
+
* selected plan card (a pinned or repeated card whose package is the
|
|
216
|
+
* selected one, or any descendant of such a card). */
|
|
217
|
+
selectedStyle?: BlockStyle;
|
|
218
|
+
visibility?: BlockVisibility;
|
|
219
|
+
}
|
|
220
|
+
export interface SpacerBlock {
|
|
221
|
+
id: string;
|
|
222
|
+
type: "spacer";
|
|
223
|
+
/** Grows to push what follows to the bottom. */
|
|
224
|
+
flex?: boolean;
|
|
225
|
+
style?: BlockStyle;
|
|
226
|
+
/** REV-262: merged over `style`, field by field, while the block sits in a
|
|
227
|
+
* selected plan card (a pinned or repeated card whose package is the
|
|
228
|
+
* selected one, or any descendant of such a card). */
|
|
229
|
+
selectedStyle?: BlockStyle;
|
|
230
|
+
visibility?: BlockVisibility;
|
|
231
|
+
}
|
|
232
|
+
/** The one container block. `layout` picks how children are placed: column /
|
|
233
|
+
* row are flex lines, `stack` layers them (children position with
|
|
234
|
+
* style.inset or the edge offsets), and `grid` is an N-column grid. */
|
|
235
|
+
export interface CardBlock {
|
|
236
|
+
id: string;
|
|
237
|
+
type: "card";
|
|
238
|
+
layout?: "column" | "row" | "stack" | "grid";
|
|
239
|
+
/** Renders this container once per package in the attached offering. Its
|
|
240
|
+
* subtree reads the package through tag variables. */
|
|
241
|
+
repeat?: "packages";
|
|
242
|
+
/** "This card describes package N of the offering". A card whose index the
|
|
243
|
+
* offering does not reach is hidden. */
|
|
244
|
+
packageIndex?: number;
|
|
245
|
+
/** grid only; defaults to 2. */
|
|
246
|
+
columns?: number;
|
|
247
|
+
/** grid only — a CSS track list ("1fr 60px 66px"). */
|
|
248
|
+
gridColumns?: string;
|
|
249
|
+
children: PaywallBlock[];
|
|
250
|
+
style?: BlockStyle;
|
|
251
|
+
/** REV-262: merged over `style`, field by field, while the block sits in a
|
|
252
|
+
* selected plan card (a pinned or repeated card whose package is the
|
|
253
|
+
* selected one, or any descendant of such a card). */
|
|
254
|
+
selectedStyle?: BlockStyle;
|
|
255
|
+
visibility?: BlockVisibility;
|
|
256
|
+
}
|
|
257
|
+
export type PaywallBlock = TextBlock | ImageBlock | ListBlock | ProductsBlock | ButtonBlock | LinksBlock | LineBlock | SpacerBlock | CardBlock;
|
|
258
|
+
/** The published document: screen palette plus the block tree. */
|
|
259
|
+
export interface PaywallBlockDoc {
|
|
260
|
+
version: number;
|
|
261
|
+
/** "canvas" designs are authored against a fixed device screen and scale as
|
|
262
|
+
* a whole; "flow" designs lay out in a scrolling column. */
|
|
263
|
+
layout?: "canvas" | "flow";
|
|
264
|
+
/** Any CSS color or gradient string — or the layered form with a photo
|
|
265
|
+
* and scrim (see ./background.ts). */
|
|
266
|
+
background: PaywallBackground;
|
|
267
|
+
textColor: string;
|
|
268
|
+
accent: string;
|
|
269
|
+
accentInk: string;
|
|
270
|
+
fontFamily?: string;
|
|
271
|
+
blocks: PaywallBlock[];
|
|
272
|
+
/** REV-271: the language the blocks' own copy is written in. Absent = "en".
|
|
273
|
+
* Never a key in a stored doc's `locales` — the tree IS that language's
|
|
274
|
+
* version. A localized copy names the language it was localized to. */
|
|
275
|
+
defaultLocale?: string;
|
|
276
|
+
/** REV-271: translations, keyed by BCP-47 tag then by block string key
|
|
277
|
+
* (`<blockId>.text`, `<blockId>.items.0.title`, …). Only words differ per
|
|
278
|
+
* language; styles, layout and ids are shared. `RevnixBlockPaywall`
|
|
279
|
+
* applies the right one — see ./locale.ts. */
|
|
280
|
+
locales?: Record<string, Record<string, string>>;
|
|
281
|
+
}
|
|
282
|
+
/** The device screen `canvas` designs are authored against. */
|
|
283
|
+
export declare const DESIGN_CANVAS: {
|
|
284
|
+
readonly width: 393;
|
|
285
|
+
readonly height: 852;
|
|
286
|
+
};
|
|
287
|
+
/** Does this tree author a dismiss affordance that is CERTAIN to render?
|
|
288
|
+
*
|
|
289
|
+
* The renderer draws its own close button only when this is false, so a
|
|
290
|
+
* design written before close existed becomes dismissible without being
|
|
291
|
+
* edited, and a design that DOES author a close chip never shows two. The
|
|
292
|
+
* same predicate is implemented in every Revnix SDK — keep them identical.
|
|
293
|
+
*
|
|
294
|
+
* Conditional containers are deliberately not searched: a `repeat` card
|
|
295
|
+
* renders once per package (none, when the offering is empty) and a
|
|
296
|
+
* `packageIndex` card is hidden when the offering does not reach that index,
|
|
297
|
+
* so a close authored inside one MIGHT not appear. Counting it would
|
|
298
|
+
* suppress the fallback and leave the customer with no way out — the exact
|
|
299
|
+
* bug this feature exists to fix. Two close buttons is merely ugly, so the
|
|
300
|
+
* tie breaks toward always having one. */
|
|
301
|
+
export declare function hasCloseAction(blocks: PaywallBlock[]): boolean;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// The paywall block model — a portable description of a designed paywall.
|
|
3
|
+
//
|
|
4
|
+
// A block paywall is a TREE of styled elements: the screen owns a background
|
|
5
|
+
// and a list of blocks; every block carries its own style. The dashboard's
|
|
6
|
+
// builder authors this tree and publishes it on the paywall config as
|
|
7
|
+
// `blocks`, where it takes precedence over the classic `template` layouts.
|
|
8
|
+
//
|
|
9
|
+
// This file mirrors revnix-app's src/lib/paywall-blocks/types.ts one-for-one.
|
|
10
|
+
// The two must stay in lockstep: the dashboard preview and this SDK renderer
|
|
11
|
+
// are two interpreters of the SAME document, and a field only one side knows
|
|
12
|
+
// is a design that ships looking different from the design that was approved.
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.DESIGN_CANVAS = void 0;
|
|
15
|
+
exports.hasCloseAction = hasCloseAction;
|
|
16
|
+
/** The device screen `canvas` designs are authored against. */
|
|
17
|
+
exports.DESIGN_CANVAS = { width: 393, height: 852 };
|
|
18
|
+
/** Does this tree author a dismiss affordance that is CERTAIN to render?
|
|
19
|
+
*
|
|
20
|
+
* The renderer draws its own close button only when this is false, so a
|
|
21
|
+
* design written before close existed becomes dismissible without being
|
|
22
|
+
* edited, and a design that DOES author a close chip never shows two. The
|
|
23
|
+
* same predicate is implemented in every Revnix SDK — keep them identical.
|
|
24
|
+
*
|
|
25
|
+
* Conditional containers are deliberately not searched: a `repeat` card
|
|
26
|
+
* renders once per package (none, when the offering is empty) and a
|
|
27
|
+
* `packageIndex` card is hidden when the offering does not reach that index,
|
|
28
|
+
* so a close authored inside one MIGHT not appear. Counting it would
|
|
29
|
+
* suppress the fallback and leave the customer with no way out — the exact
|
|
30
|
+
* bug this feature exists to fix. Two close buttons is merely ugly, so the
|
|
31
|
+
* tie breaks toward always having one. */
|
|
32
|
+
function hasCloseAction(blocks) {
|
|
33
|
+
return blocks.some((b) =>
|
|
34
|
+
// A close the design only draws in one selection state (`visibility`)
|
|
35
|
+
// must not suppress the fallback chip — the other state would have none.
|
|
36
|
+
b.visibility !== undefined
|
|
37
|
+
? false
|
|
38
|
+
: b.type === "card"
|
|
39
|
+
? b.repeat === undefined &&
|
|
40
|
+
b.packageIndex === undefined &&
|
|
41
|
+
hasCloseAction(b.children)
|
|
42
|
+
: (b.type === "text" || b.type === "image" || b.type === "button") &&
|
|
43
|
+
b.action === "close");
|
|
44
|
+
}
|