@revenuecat/purchases-ui-js 4.1.0 → 4.2.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/components/carousel/Carousel.stories.svelte +29 -0
- package/dist/components/carousel/Carousel.svelte +74 -8
- package/dist/components/paywall/BackgroundVideoSurface.svelte +162 -0
- package/dist/components/paywall/BackgroundVideoSurface.svelte.d.ts +12 -0
- package/dist/components/paywall/Paywall.stories.svelte +133 -0
- package/dist/components/paywall/Sheet.svelte +66 -3
- package/dist/components/paywall/ViewportBackdrop.svelte +40 -9
- package/dist/components/paywall/fixtures/sheet-video-stacking-paywall.d.ts +6 -0
- package/dist/components/paywall/fixtures/sheet-video-stacking-paywall.js +42 -0
- package/dist/components/stack/Stack.stories.svelte +74 -0
- package/dist/components/stack/Stack.svelte +58 -3
- package/dist/components/tabs/Tabs.stories.svelte +27 -0
- package/dist/components/tabs/Tabs.svelte +69 -4
- package/dist/components/tabs/tabs-video-stacking-story-args.d.ts +3 -0
- package/dist/components/tabs/tabs-video-stacking-story-args.js +131 -0
- package/dist/components/video/Video.svelte +32 -56
- package/dist/stories/video-background-story-fixture.d.ts +3 -0
- package/dist/stories/video-background-story-fixture.js +36 -0
- package/dist/types/background.d.ts +9 -1
- package/dist/ui/molecules/button.svelte +2 -2
- package/dist/utils/background-utils.d.ts +30 -1
- package/dist/utils/background-utils.js +53 -5
- package/dist/utils/match-media-compat.d.ts +4 -0
- package/dist/utils/match-media-compat.js +11 -0
- package/dist/utils/video-background-host.d.ts +31 -0
- package/dist/utils/video-background-host.js +25 -0
- package/dist/utils/video-inline-playback.d.ts +25 -0
- package/dist/utils/video-inline-playback.js +78 -0
- package/dist/web-components/index.css +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { PaywallData } from "../../../types/paywall";
|
|
2
|
+
/**
|
|
3
|
+
* Minimal variant of {@link SHEET_PAYWALL} with a sheet-level video background for
|
|
4
|
+
* Storybook z-order regression checks (`rc-sheet-video-bg`).
|
|
5
|
+
*/
|
|
6
|
+
export declare const SHEET_PAYWALL_VIDEO_STACKING: PaywallData;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { STORY_BACKGROUND_VIDEO_FILL } from "../../../stories/video-background-story-fixture";
|
|
2
|
+
import { SHEET_PAYWALL } from "./sheet-paywall";
|
|
3
|
+
/**
|
|
4
|
+
* Minimal variant of {@link SHEET_PAYWALL} with a sheet-level video background for
|
|
5
|
+
* Storybook z-order regression checks (`rc-sheet-video-bg`).
|
|
6
|
+
*/
|
|
7
|
+
export const SHEET_PAYWALL_VIDEO_STACKING = (() => {
|
|
8
|
+
const data = structuredClone(SHEET_PAYWALL);
|
|
9
|
+
data.id = "sheet_paywall_video_stacking";
|
|
10
|
+
data.components_localizations ??= {};
|
|
11
|
+
data.components_localizations.en_US ??= {};
|
|
12
|
+
Object.assign(data.components_localizations.en_US, {
|
|
13
|
+
"5GWqi3MXpt": "Content above tinted video",
|
|
14
|
+
E6CK5Xd1cE: "Open sheet · video backdrop",
|
|
15
|
+
});
|
|
16
|
+
const first = data.components_config.base.stack.components[0];
|
|
17
|
+
if (!first || first.type !== "button") {
|
|
18
|
+
return data;
|
|
19
|
+
}
|
|
20
|
+
const { action } = first;
|
|
21
|
+
if (!action ||
|
|
22
|
+
action.type !== "navigate_to" ||
|
|
23
|
+
action.destination !== "sheet") {
|
|
24
|
+
return data;
|
|
25
|
+
}
|
|
26
|
+
const { sheet } = action;
|
|
27
|
+
if (!sheet) {
|
|
28
|
+
return data;
|
|
29
|
+
}
|
|
30
|
+
sheet.background = STORY_BACKGROUND_VIDEO_FILL;
|
|
31
|
+
sheet.stack.background = null;
|
|
32
|
+
const headline = sheet.stack.components[0];
|
|
33
|
+
if (headline?.type === "text") {
|
|
34
|
+
headline.horizontal_alignment = "center";
|
|
35
|
+
headline.color = {
|
|
36
|
+
light: { type: "hex", value: "#ffffff" },
|
|
37
|
+
};
|
|
38
|
+
headline.font_size = "heading_m";
|
|
39
|
+
headline.font_weight = "semibold";
|
|
40
|
+
}
|
|
41
|
+
return data;
|
|
42
|
+
})();
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { localizationDecorator } from "../../stories/localization-decorator";
|
|
7
7
|
import type { StackProps } from "../../types/components/stack";
|
|
8
8
|
import type { Component } from "../../types/component";
|
|
9
|
+
import { STORY_BACKGROUND_VIDEO_FILL } from "../../stories/video-background-story-fixture";
|
|
9
10
|
import { DEFAULT_TEXT_COLOR } from "../../utils/constants";
|
|
10
11
|
import { defineMeta } from "@storybook/addon-svelte-csf";
|
|
11
12
|
|
|
@@ -153,6 +154,79 @@
|
|
|
153
154
|
}}
|
|
154
155
|
/>
|
|
155
156
|
|
|
157
|
+
<Story
|
|
158
|
+
name="Surface — video background"
|
|
159
|
+
parameters={{
|
|
160
|
+
chromatic: { disableSnapshot: true },
|
|
161
|
+
docs: {
|
|
162
|
+
description: {
|
|
163
|
+
story:
|
|
164
|
+
"**Z-order:** `BackgroundVideoSurface` (`z-index: 1`), `::before` tint (`z-index: 2`), siblings (`z-index: 3`). Content and controls should remain sharp above the tint; motion is disabled in Chromatic.",
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
}}
|
|
168
|
+
args={{
|
|
169
|
+
size: {
|
|
170
|
+
width: { type: "fixed", value: 380 },
|
|
171
|
+
height: { type: "fixed", value: 260 },
|
|
172
|
+
},
|
|
173
|
+
shape: {
|
|
174
|
+
type: "rectangle",
|
|
175
|
+
corners: {
|
|
176
|
+
top_leading: 20,
|
|
177
|
+
top_trailing: 20,
|
|
178
|
+
bottom_leading: 20,
|
|
179
|
+
bottom_trailing: 20,
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
background: STORY_BACKGROUND_VIDEO_FILL,
|
|
183
|
+
components: [
|
|
184
|
+
{
|
|
185
|
+
type: "text",
|
|
186
|
+
size: {
|
|
187
|
+
width: { type: "fit" },
|
|
188
|
+
height: { type: "fit" },
|
|
189
|
+
},
|
|
190
|
+
horizontal_alignment: "center",
|
|
191
|
+
name: "Item 1",
|
|
192
|
+
id: "vbg-1",
|
|
193
|
+
text_lid: "id1",
|
|
194
|
+
color: {
|
|
195
|
+
light: { type: "hex", value: "#ffffff" },
|
|
196
|
+
},
|
|
197
|
+
font_name: null,
|
|
198
|
+
font_size: "heading_s",
|
|
199
|
+
font_weight: "bold",
|
|
200
|
+
background_color: {
|
|
201
|
+
dark: { type: "alias", value: "transparent" },
|
|
202
|
+
light: { type: "alias", value: "transparent" },
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
type: "text",
|
|
207
|
+
size: {
|
|
208
|
+
width: { type: "fill" },
|
|
209
|
+
height: { type: "fit" },
|
|
210
|
+
},
|
|
211
|
+
horizontal_alignment: "center",
|
|
212
|
+
name: "Item 2",
|
|
213
|
+
id: "vbg-2",
|
|
214
|
+
text_lid: "id2",
|
|
215
|
+
color: {
|
|
216
|
+
light: { type: "hex", value: "#e2e8f0" },
|
|
217
|
+
},
|
|
218
|
+
font_name: null,
|
|
219
|
+
font_size: "body_m",
|
|
220
|
+
font_weight: "regular",
|
|
221
|
+
background_color: {
|
|
222
|
+
dark: { type: "alias", value: "transparent" },
|
|
223
|
+
light: { type: "alias", value: "transparent" },
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
] as unknown as TextNodeProps[],
|
|
227
|
+
}}
|
|
228
|
+
/>
|
|
229
|
+
|
|
156
230
|
<Story
|
|
157
231
|
name="Z Layer"
|
|
158
232
|
args={{
|
|
@@ -6,8 +6,16 @@
|
|
|
6
6
|
import { getPaywallContext } from "../../stores/paywall";
|
|
7
7
|
import { getSelectedStateContext } from "../../stores/selected";
|
|
8
8
|
import { getOptionalVariablesContext } from "../../stores/variables";
|
|
9
|
+
import BackgroundVideoSurface from "../paywall/BackgroundVideoSurface.svelte";
|
|
9
10
|
import type { StackProps } from "../../types/components/stack";
|
|
10
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
mapBackground,
|
|
13
|
+
resolveBackgroundVideoForSurface,
|
|
14
|
+
} from "../../utils/background-utils";
|
|
15
|
+
import {
|
|
16
|
+
rcVideoBackgroundHostClass,
|
|
17
|
+
videoBackgroundHostStyles,
|
|
18
|
+
} from "../../utils/video-background-host";
|
|
11
19
|
import {
|
|
12
20
|
css,
|
|
13
21
|
mapBorder,
|
|
@@ -66,6 +74,15 @@
|
|
|
66
74
|
const getColorMode = getColorModeContext();
|
|
67
75
|
const colorMode = $derived(getColorMode());
|
|
68
76
|
|
|
77
|
+
const hasVideoBg = $derived(background?.type === "video");
|
|
78
|
+
const videoSurface = $derived.by(() => {
|
|
79
|
+
if (!hasVideoBg) return null;
|
|
80
|
+
const bg = background;
|
|
81
|
+
if (!bg || bg.type !== "video") return null;
|
|
82
|
+
return resolveBackgroundVideoForSurface(colorMode, bg);
|
|
83
|
+
});
|
|
84
|
+
const borderRadiusCss = $derived(mapBorderRadius(shape));
|
|
85
|
+
|
|
69
86
|
const stackStyle = $derived(
|
|
70
87
|
css({
|
|
71
88
|
display: "flex",
|
|
@@ -78,8 +95,12 @@
|
|
|
78
95
|
padding: mapSpacing(padding),
|
|
79
96
|
...mapBackground(colorMode, background_color, background),
|
|
80
97
|
...mapBorder(colorMode, border),
|
|
81
|
-
"border-radius":
|
|
98
|
+
"border-radius": borderRadiusCss,
|
|
82
99
|
"box-shadow": mapShadow(colorMode, shadow),
|
|
100
|
+
...videoBackgroundHostStyles({
|
|
101
|
+
hasVideoBg,
|
|
102
|
+
clipOverflow: borderRadiusCss !== "0",
|
|
103
|
+
}),
|
|
83
104
|
// Read from props proxy so $derived re-evaluates when parent updates style
|
|
84
105
|
...props.style,
|
|
85
106
|
}),
|
|
@@ -124,9 +145,28 @@
|
|
|
124
145
|
role={onclick !== undefined ? "button" : undefined}
|
|
125
146
|
{onclick}
|
|
126
147
|
style={stackStyle}
|
|
127
|
-
class={[
|
|
148
|
+
class={[
|
|
149
|
+
"stack",
|
|
150
|
+
"rc-gradient-border",
|
|
151
|
+
classProp,
|
|
152
|
+
hasVideoBg ? rcVideoBackgroundHostClass("stack") : "",
|
|
153
|
+
]
|
|
154
|
+
.filter(Boolean)
|
|
155
|
+
.join(" ")}
|
|
128
156
|
data-testid={testId}
|
|
129
157
|
>
|
|
158
|
+
{#if videoSurface}
|
|
159
|
+
<BackgroundVideoSurface
|
|
160
|
+
url={videoSurface.url}
|
|
161
|
+
urlLowRes={videoSurface.url_low_res}
|
|
162
|
+
objectFit={videoSurface.fit}
|
|
163
|
+
objectPosition={videoSurface.position}
|
|
164
|
+
poster={videoSurface.posterWebp}
|
|
165
|
+
mute={videoSurface.mute}
|
|
166
|
+
loop={videoSurface.loop}
|
|
167
|
+
/>
|
|
168
|
+
{/if}
|
|
169
|
+
|
|
130
170
|
{#if badge}
|
|
131
171
|
<div style={badgeStyle}>
|
|
132
172
|
<Node nodeData={badge.stack} />
|
|
@@ -173,4 +213,19 @@
|
|
|
173
213
|
background: var(--overlay);
|
|
174
214
|
}
|
|
175
215
|
}
|
|
216
|
+
|
|
217
|
+
.stack.rc-stack-video-bg::before {
|
|
218
|
+
z-index: 2;
|
|
219
|
+
pointer-events: none;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.stack.rc-stack-video-bg > :global(.rc-bg-video) {
|
|
223
|
+
z-index: 1;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/* Keep flex children interactive and above tinted overlay (::before z-index). */
|
|
227
|
+
.stack.rc-stack-video-bg > :global(:not(.rc-bg-video)) {
|
|
228
|
+
position: relative;
|
|
229
|
+
z-index: 3;
|
|
230
|
+
}
|
|
176
231
|
</style>
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { variablesDecorator } from "../../stories/variables-decorator";
|
|
6
6
|
import { defineMeta } from "@storybook/addon-svelte-csf";
|
|
7
7
|
import { VARIABLES } from "../paywall/fixtures/variables";
|
|
8
|
+
import { TABS_VIDEO_STACKING_STORY_ARGS } from "./tabs-video-stacking-story-args";
|
|
8
9
|
|
|
9
10
|
const defaultLocale = "en_US";
|
|
10
11
|
|
|
@@ -1428,3 +1429,29 @@
|
|
|
1428
1429
|
type: "tabs",
|
|
1429
1430
|
}}
|
|
1430
1431
|
/>
|
|
1432
|
+
|
|
1433
|
+
<Story
|
|
1434
|
+
name="Surface — video background (stacking / z-order)"
|
|
1435
|
+
parameters={{
|
|
1436
|
+
chromatic: { disableSnapshot: true },
|
|
1437
|
+
docs: {
|
|
1438
|
+
description: {
|
|
1439
|
+
story:
|
|
1440
|
+
"**Z-order:** video layer → tinted `::before` → tab content clip. Rounded corners should clip the backdrop; Chromatic skips motion snapshots.",
|
|
1441
|
+
},
|
|
1442
|
+
},
|
|
1443
|
+
}}
|
|
1444
|
+
decorators={[
|
|
1445
|
+
componentDecorator(),
|
|
1446
|
+
localizationDecorator({
|
|
1447
|
+
defaultLocale,
|
|
1448
|
+
localizations: {
|
|
1449
|
+
[defaultLocale]: {
|
|
1450
|
+
"t-a-body": "Tab A · content above video",
|
|
1451
|
+
"t-b-body": "Tab B · content above video",
|
|
1452
|
+
},
|
|
1453
|
+
},
|
|
1454
|
+
}),
|
|
1455
|
+
]}
|
|
1456
|
+
args={TABS_VIDEO_STACKING_STORY_ARGS}
|
|
1457
|
+
/>
|
|
@@ -4,8 +4,16 @@
|
|
|
4
4
|
import { getPaywallContext } from "../../stores/paywall";
|
|
5
5
|
import { getSelectedStateContext } from "../../stores/selected";
|
|
6
6
|
import { getOptionalVariablesContext } from "../../stores/variables";
|
|
7
|
+
import BackgroundVideoSurface from "../paywall/BackgroundVideoSurface.svelte";
|
|
7
8
|
import type { TabsProps } from "../../types/components/tabs";
|
|
8
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
mapBackground,
|
|
11
|
+
resolveBackgroundVideoForSurface,
|
|
12
|
+
} from "../../utils/background-utils";
|
|
13
|
+
import {
|
|
14
|
+
rcVideoBackgroundHostClass,
|
|
15
|
+
videoBackgroundHostStyles,
|
|
16
|
+
} from "../../utils/video-background-host";
|
|
9
17
|
import {
|
|
10
18
|
css,
|
|
11
19
|
mapBorder,
|
|
@@ -40,15 +48,30 @@
|
|
|
40
48
|
const getColorMode = getColorModeContext();
|
|
41
49
|
const colorMode = $derived(getColorMode());
|
|
42
50
|
|
|
51
|
+
const hasVideoBg = $derived(background?.type === "video");
|
|
52
|
+
const videoSurface = $derived.by(() => {
|
|
53
|
+
if (!hasVideoBg) return null;
|
|
54
|
+
const bg = background;
|
|
55
|
+
if (!bg || bg.type !== "video") return null;
|
|
56
|
+
return resolveBackgroundVideoForSurface(colorMode, bg);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const tabsBorderRadiusCss = $derived(mapBorderRadius(shape));
|
|
60
|
+
|
|
43
61
|
const styles = $derived(
|
|
44
62
|
css({
|
|
63
|
+
position: "relative",
|
|
45
64
|
width: mapSize(size.width),
|
|
46
65
|
height: mapSize(size.height),
|
|
47
66
|
margin: mapSpacing(margin),
|
|
48
67
|
padding: mapSpacing(padding),
|
|
68
|
+
...videoBackgroundHostStyles({
|
|
69
|
+
hasVideoBg,
|
|
70
|
+
clipOverflow: tabsBorderRadiusCss !== "0",
|
|
71
|
+
}),
|
|
49
72
|
...mapBackground(colorMode, null, background),
|
|
50
73
|
...mapBorder(colorMode, border),
|
|
51
|
-
"border-radius":
|
|
74
|
+
"border-radius": tabsBorderRadiusCss,
|
|
52
75
|
"box-shadow": mapShadow(colorMode, shadow),
|
|
53
76
|
}),
|
|
54
77
|
);
|
|
@@ -117,9 +140,51 @@
|
|
|
117
140
|
</script>
|
|
118
141
|
|
|
119
142
|
{#if isVisible}
|
|
120
|
-
<div
|
|
143
|
+
<div
|
|
144
|
+
class={[
|
|
145
|
+
"rc-gradient-border",
|
|
146
|
+
hasVideoBg ? rcVideoBackgroundHostClass("tabs") : "",
|
|
147
|
+
]
|
|
148
|
+
.filter(Boolean)
|
|
149
|
+
.join(" ")}
|
|
150
|
+
style={styles}
|
|
151
|
+
>
|
|
152
|
+
{#if videoSurface}
|
|
153
|
+
<BackgroundVideoSurface
|
|
154
|
+
url={videoSurface.url}
|
|
155
|
+
urlLowRes={videoSurface.url_low_res}
|
|
156
|
+
objectFit={videoSurface.fit}
|
|
157
|
+
objectPosition={videoSurface.position}
|
|
158
|
+
poster={videoSurface.posterWebp}
|
|
159
|
+
mute={videoSurface.mute}
|
|
160
|
+
loop={videoSurface.loop}
|
|
161
|
+
/>
|
|
162
|
+
{/if}
|
|
121
163
|
{#if $tab !== undefined}
|
|
122
|
-
<Stack
|
|
164
|
+
<Stack
|
|
165
|
+
{...$tab.stack}
|
|
166
|
+
class={hasVideoBg ? "rc-tabs-stack-front" : undefined}
|
|
167
|
+
/>
|
|
123
168
|
{/if}
|
|
124
169
|
</div>
|
|
125
170
|
{/if}
|
|
171
|
+
|
|
172
|
+
<style>
|
|
173
|
+
.rc-tabs-video-bg::before {
|
|
174
|
+
content: "";
|
|
175
|
+
position: absolute;
|
|
176
|
+
inset: 0;
|
|
177
|
+
background: var(--overlay);
|
|
178
|
+
pointer-events: none;
|
|
179
|
+
z-index: 2;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.rc-tabs-video-bg > :global(.rc-bg-video) {
|
|
183
|
+
z-index: 1;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.rc-tabs-video-bg > :global(.rc-tabs-stack-front) {
|
|
187
|
+
position: relative;
|
|
188
|
+
z-index: 3;
|
|
189
|
+
}
|
|
190
|
+
</style>
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { STORY_BACKGROUND_VIDEO_FILL } from "../../stories/video-background-story-fixture";
|
|
2
|
+
const spacer = { bottom: 0, leading: 0, top: 0, trailing: 0 };
|
|
3
|
+
const simpleTextStack = ({ tid, textLid, }) => ({
|
|
4
|
+
type: "stack",
|
|
5
|
+
id: `${tid}-inner`,
|
|
6
|
+
name: "",
|
|
7
|
+
background_color: null,
|
|
8
|
+
background: null,
|
|
9
|
+
badge: null,
|
|
10
|
+
border: null,
|
|
11
|
+
shadow: null,
|
|
12
|
+
spacing: 0,
|
|
13
|
+
dimension: {
|
|
14
|
+
alignment: "center",
|
|
15
|
+
distribution: "center",
|
|
16
|
+
type: "vertical",
|
|
17
|
+
},
|
|
18
|
+
shape: null,
|
|
19
|
+
margin: spacer,
|
|
20
|
+
padding: {
|
|
21
|
+
bottom: 24,
|
|
22
|
+
leading: 24,
|
|
23
|
+
top: 24,
|
|
24
|
+
trailing: 24,
|
|
25
|
+
},
|
|
26
|
+
size: {
|
|
27
|
+
width: { type: "fill" },
|
|
28
|
+
height: { type: "fit" },
|
|
29
|
+
},
|
|
30
|
+
components: [
|
|
31
|
+
{
|
|
32
|
+
type: "text",
|
|
33
|
+
id: `${tid}-t`,
|
|
34
|
+
name: "",
|
|
35
|
+
text_lid: textLid,
|
|
36
|
+
background_color: null,
|
|
37
|
+
color: {
|
|
38
|
+
light: { type: "hex", value: "#ffffff" },
|
|
39
|
+
},
|
|
40
|
+
font_name: null,
|
|
41
|
+
font_size: "heading_m",
|
|
42
|
+
font_weight: "semibold",
|
|
43
|
+
horizontal_alignment: "center",
|
|
44
|
+
margin: spacer,
|
|
45
|
+
padding: spacer,
|
|
46
|
+
size: {
|
|
47
|
+
width: { type: "fit" },
|
|
48
|
+
height: { type: "fit" },
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
});
|
|
53
|
+
const tabToggle = {
|
|
54
|
+
type: "tab_control_toggle",
|
|
55
|
+
id: "vbg-toggle",
|
|
56
|
+
name: "",
|
|
57
|
+
default_value: false,
|
|
58
|
+
thumb_color_on: {
|
|
59
|
+
light: { type: "hex", value: "#ffffffff" },
|
|
60
|
+
},
|
|
61
|
+
thumb_color_off: {
|
|
62
|
+
light: { type: "hex", value: "#ccccccff" },
|
|
63
|
+
},
|
|
64
|
+
track_color_on: {
|
|
65
|
+
light: { type: "hex", value: "#4488ffff" },
|
|
66
|
+
},
|
|
67
|
+
track_color_off: {
|
|
68
|
+
light: { type: "hex", value: "#00000044" },
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
function tab(contentId, bodyLid) {
|
|
72
|
+
return {
|
|
73
|
+
type: "tab",
|
|
74
|
+
id: contentId,
|
|
75
|
+
name: "",
|
|
76
|
+
stack: simpleTextStack({ tid: contentId, textLid: bodyLid }),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/** Minimal Tabs tree for Storybook layering checks (`rc-tabs-video-bg`). */
|
|
80
|
+
export const TABS_VIDEO_STACKING_STORY_ARGS = {
|
|
81
|
+
type: "tabs",
|
|
82
|
+
id: "tabs-video-z",
|
|
83
|
+
name: "Tabs · video layering",
|
|
84
|
+
size: {
|
|
85
|
+
width: { type: "fixed", value: 400 },
|
|
86
|
+
height: { type: "fixed", value: 300 },
|
|
87
|
+
},
|
|
88
|
+
padding: { bottom: 12, leading: 12, top: 12, trailing: 12 },
|
|
89
|
+
margin: spacer,
|
|
90
|
+
background: STORY_BACKGROUND_VIDEO_FILL,
|
|
91
|
+
shape: {
|
|
92
|
+
corners: {
|
|
93
|
+
bottom_leading: 20,
|
|
94
|
+
bottom_trailing: 20,
|
|
95
|
+
top_leading: 20,
|
|
96
|
+
top_trailing: 20,
|
|
97
|
+
},
|
|
98
|
+
type: "rectangle",
|
|
99
|
+
},
|
|
100
|
+
border: null,
|
|
101
|
+
shadow: null,
|
|
102
|
+
default_tab_id: "tab-a",
|
|
103
|
+
control: {
|
|
104
|
+
type: "toggle",
|
|
105
|
+
stack: {
|
|
106
|
+
type: "stack",
|
|
107
|
+
id: "vbg-toggle-stack",
|
|
108
|
+
name: "",
|
|
109
|
+
background_color: null,
|
|
110
|
+
background: null,
|
|
111
|
+
badge: null,
|
|
112
|
+
border: null,
|
|
113
|
+
shadow: null,
|
|
114
|
+
spacing: 16,
|
|
115
|
+
dimension: {
|
|
116
|
+
alignment: "center",
|
|
117
|
+
distribution: "center",
|
|
118
|
+
type: "horizontal",
|
|
119
|
+
},
|
|
120
|
+
shape: null,
|
|
121
|
+
margin: { bottom: 8, leading: 0, top: 0, trailing: 0 },
|
|
122
|
+
padding: spacer,
|
|
123
|
+
size: {
|
|
124
|
+
width: { type: "fill" },
|
|
125
|
+
height: { type: "fit" },
|
|
126
|
+
},
|
|
127
|
+
components: [tabToggle],
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
tabs: [tab("tab-a", "t-a-body"), tab("tab-b", "t-b-body")],
|
|
131
|
+
};
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
evaluateVisibilityConditions,
|
|
21
21
|
getActiveStateProps,
|
|
22
22
|
} from "../../utils/style-utils";
|
|
23
|
+
import {
|
|
24
|
+
applyInlineVideoPlaybackFlags,
|
|
25
|
+
subscribeDecorativeVideoAutoplayAttempts,
|
|
26
|
+
} from "../../utils/video-inline-playback";
|
|
23
27
|
import ClipPath from "../image/ClipPath.svelte";
|
|
24
28
|
import Overlay from "../image/Overlay.svelte";
|
|
25
29
|
|
|
@@ -208,11 +212,9 @@
|
|
|
208
212
|
const shouldShowFallback = $derived(hasVideoError || !video);
|
|
209
213
|
|
|
210
214
|
// Autoplay: native `autoplay` + programmatic `play()`. Unmuted autoplay is often blocked; we retry
|
|
211
|
-
// once with muted playback (`playbackMutedOverride`) then try to restore sound
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
// `complete` (`load` won't fire)—extra microtask retries `tryPlay` for muted→unmuted paths.
|
|
215
|
-
// Pair with `translateZ(0)` + rc-workflows `screen-slide-fade`.
|
|
215
|
+
// once with muted playback (`playbackMutedOverride`) then try to restore sound (WebKit quirks).
|
|
216
|
+
// Bootstrap timing is shared via `subscribeDecorativeVideoAutoplayAttempts` (+ microtasks / `load`).
|
|
217
|
+
// Pair `translateZ(0)` on `<video>` with rc-workflows `screen-slide-fade` where needed.
|
|
216
218
|
$effect(() => {
|
|
217
219
|
if (!auto_play) {
|
|
218
220
|
untrack(() => {
|
|
@@ -243,10 +245,12 @@
|
|
|
243
245
|
|
|
244
246
|
let cancelled = false;
|
|
245
247
|
|
|
246
|
-
/** Keep
|
|
248
|
+
/** Keep muted / playsInline aligned with `mute_audio`, `playbackMutedOverride`, and the template. */
|
|
247
249
|
const applyVideoPlaybackFlags = () => {
|
|
248
|
-
el
|
|
249
|
-
|
|
250
|
+
applyInlineVideoPlaybackFlags(el, {
|
|
251
|
+
preferMuted: mute_audio,
|
|
252
|
+
mutedPlaybackOverride: playbackMutedOverride,
|
|
253
|
+
});
|
|
250
254
|
};
|
|
251
255
|
|
|
252
256
|
let playingUnmuteListener: (() => void) | null = null;
|
|
@@ -271,16 +275,25 @@
|
|
|
271
275
|
const playPromise = el.play();
|
|
272
276
|
if (playPromise !== undefined) {
|
|
273
277
|
playPromise.catch(() => {
|
|
278
|
+
if (cancelled || !el.isConnected) return;
|
|
274
279
|
if (!mute_audio && !playbackMutedOverride) {
|
|
275
280
|
untrack(() => {
|
|
276
281
|
playbackMutedOverride = true;
|
|
277
282
|
});
|
|
278
|
-
|
|
283
|
+
applyInlineVideoPlaybackFlags(el, {
|
|
284
|
+
preferMuted: mute_audio,
|
|
285
|
+
mutedPlaybackOverride: true,
|
|
286
|
+
});
|
|
279
287
|
|
|
280
288
|
/** Unmuted `play()` after a forced-muted start sometimes only works tied to playback (WebKit). */
|
|
281
289
|
let unmuteAfterMutedStartHandled = false;
|
|
282
290
|
const tryRestoreAudioAfterMutedAutoplay = () => {
|
|
283
|
-
if (
|
|
291
|
+
if (
|
|
292
|
+
unmuteAfterMutedStartHandled ||
|
|
293
|
+
mute_audio ||
|
|
294
|
+
cancelled ||
|
|
295
|
+
!el.isConnected
|
|
296
|
+
)
|
|
284
297
|
return;
|
|
285
298
|
unmuteAfterMutedStartHandled = true;
|
|
286
299
|
queueMicrotask(() => {
|
|
@@ -293,6 +306,7 @@
|
|
|
293
306
|
const p3 = el.play();
|
|
294
307
|
if (p3 === undefined) return;
|
|
295
308
|
p3.catch(() => {
|
|
309
|
+
if (cancelled || !el.isConnected) return;
|
|
296
310
|
untrack(() => {
|
|
297
311
|
playbackMutedOverride = true;
|
|
298
312
|
});
|
|
@@ -303,6 +317,7 @@
|
|
|
303
317
|
};
|
|
304
318
|
|
|
305
319
|
const onPlayingAfterMutedBootstrap = () => {
|
|
320
|
+
if (cancelled || !el.isConnected) return;
|
|
306
321
|
playingUnmuteListener = null;
|
|
307
322
|
tryRestoreAudioAfterMutedAutoplay();
|
|
308
323
|
};
|
|
@@ -339,17 +354,6 @@
|
|
|
339
354
|
}
|
|
340
355
|
};
|
|
341
356
|
|
|
342
|
-
const onMediaReady = () => {
|
|
343
|
-
tryPlay();
|
|
344
|
-
};
|
|
345
|
-
|
|
346
|
-
if (el.readyState < HTMLMediaElement.HAVE_CURRENT_DATA) {
|
|
347
|
-
el.addEventListener("loadeddata", onMediaReady);
|
|
348
|
-
el.addEventListener("canplay", onMediaReady);
|
|
349
|
-
} else {
|
|
350
|
-
tryPlay();
|
|
351
|
-
}
|
|
352
|
-
|
|
353
357
|
/**
|
|
354
358
|
* Once per `video.url`: muted `play()` nudge helps iOS decode before `HAVE_CURRENT_DATA`.
|
|
355
359
|
* When `mute_audio` is false, `load()` reloads the media so poster/metadata can attach; unmuted
|
|
@@ -368,49 +372,21 @@
|
|
|
368
372
|
}
|
|
369
373
|
}
|
|
370
374
|
};
|
|
375
|
+
|
|
371
376
|
if (needsBootstrap && el.readyState < HTMLMediaElement.HAVE_CURRENT_DATA) {
|
|
372
377
|
nudgePlayback();
|
|
373
378
|
}
|
|
374
379
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
tryPlay();
|
|
381
|
-
}
|
|
382
|
-
});
|
|
383
|
-
requestAnimationFrame(() => {
|
|
384
|
-
if (cancelled) return;
|
|
385
|
-
if (el.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
|
|
386
|
-
tryPlay();
|
|
387
|
-
}
|
|
388
|
-
});
|
|
389
|
-
};
|
|
390
|
-
|
|
391
|
-
deferTryPlayWithReadyGate();
|
|
392
|
-
|
|
393
|
-
const onWindowLoad = () => {
|
|
394
|
-
tryPlay();
|
|
395
|
-
};
|
|
396
|
-
if (typeof document !== "undefined" && typeof window !== "undefined") {
|
|
397
|
-
if (document.readyState === "complete") {
|
|
398
|
-
queueMicrotask(() => {
|
|
399
|
-
if (!cancelled) tryPlay();
|
|
400
|
-
});
|
|
401
|
-
} else {
|
|
402
|
-
window.addEventListener("load", onWindowLoad);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
380
|
+
const unsubAutoplay = subscribeDecorativeVideoAutoplayAttempts(
|
|
381
|
+
el,
|
|
382
|
+
tryPlay,
|
|
383
|
+
() => cancelled,
|
|
384
|
+
);
|
|
405
385
|
|
|
406
386
|
return () => {
|
|
407
387
|
cancelled = true;
|
|
408
388
|
clearPlayingUnmuteListener();
|
|
409
|
-
|
|
410
|
-
el.removeEventListener("canplay", onMediaReady);
|
|
411
|
-
if (typeof window !== "undefined") {
|
|
412
|
-
window.removeEventListener("load", onWindowLoad);
|
|
413
|
-
}
|
|
389
|
+
unsubAutoplay();
|
|
414
390
|
};
|
|
415
391
|
});
|
|
416
392
|
|