@revenuecat/purchases-ui-js 2.0.5 → 2.1.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/button/ButtonNode.stories.svelte +20 -0
- package/dist/components/button/ButtonNode.svelte +12 -4
- package/dist/components/paywall/Node.svelte +4 -0
- package/dist/components/paywall/Paywall.svelte +11 -5
- package/dist/components/paywall/Paywall.svelte.d.ts +1 -0
- package/dist/components/stack/Stack.svelte +6 -3
- package/dist/components/stack/Stack.svelte.d.ts +2 -0
- package/dist/components/tabs/TabControlToggle.svelte +103 -0
- package/dist/components/tabs/TabControlToggle.svelte.d.ts +4 -0
- package/dist/components/tabs/Tabs.stories.svelte +439 -0
- package/dist/components/tabs/Tabs.svelte +12 -5
- package/dist/components/tabs/tabs-context.d.ts +1 -0
- package/dist/components/text/TextNode.stories.svelte +106 -0
- package/dist/components/text/TextNode.svelte +5 -3
- package/dist/components/text/text-utils.d.ts +5 -5
- package/dist/components/text/text-utils.js +34 -19
- package/dist/components/timeline/TimelineItem.svelte +4 -8
- package/dist/components/video/Video.stories.svelte +267 -0
- package/dist/components/video/Video.stories.svelte.d.ts +19 -0
- package/dist/components/video/Video.svelte +248 -0
- package/dist/components/video/Video.svelte.d.ts +4 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/stores/paywall.d.ts +1 -1
- package/dist/stories/paywall-decorator.js +1 -1
- package/dist/types/base.d.ts +1 -0
- package/dist/types/component.d.ts +3 -2
- package/dist/types/components/button.d.ts +7 -1
- package/dist/types/components/tabs.d.ts +10 -0
- package/dist/types/components/text.d.ts +2 -2
- package/dist/types/components/video.d.ts +35 -0
- package/dist/types/components/video.js +1 -0
- package/dist/types/media.d.ts +17 -4
- package/dist/types/ui-config.d.ts +1 -1
- package/dist/utils/base-utils.d.ts +2 -1
- package/dist/utils/base-utils.js +1 -1
- package/dist/utils/style-utils.d.ts +0 -30
- package/dist/utils/style-utils.js +0 -66
- package/package.json +1 -1
|
@@ -1,38 +1,53 @@
|
|
|
1
|
+
import { FontSizes, FontWeights, TextAlignments, } from "../../types";
|
|
1
2
|
import { mapBackground } from "../../utils/background-utils";
|
|
2
|
-
import { css,
|
|
3
|
+
import { css, mapColorInfo, mapColorMode, mapSize, mapSpacing, } from "../../utils/base-utils";
|
|
3
4
|
import { DEFAULT_TEXT_COLOR } from "../../utils/constants";
|
|
4
|
-
import {
|
|
5
|
+
import { getScopedFontFamily, isFontRCFMManaged } from "../../utils/font-utils";
|
|
5
6
|
export const defaultColor = {
|
|
6
7
|
light: { type: "hex", value: DEFAULT_TEXT_COLOR },
|
|
7
8
|
};
|
|
9
|
+
export function mapTextColor(colorMode, scheme) {
|
|
10
|
+
const info = mapColorMode(colorMode, scheme);
|
|
11
|
+
const color = mapColorInfo(info);
|
|
12
|
+
switch (info.type) {
|
|
13
|
+
case "alias":
|
|
14
|
+
case "hex":
|
|
15
|
+
return { color };
|
|
16
|
+
case "linear":
|
|
17
|
+
case "radial":
|
|
18
|
+
return {
|
|
19
|
+
color: "transparent",
|
|
20
|
+
background: color,
|
|
21
|
+
"-webkit-background-clip": "text",
|
|
22
|
+
"background-clip": "text",
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
}
|
|
8
26
|
/**
|
|
9
27
|
* Generates comprehensive styles for text components by combining text, component and size styles
|
|
10
28
|
* @param props - Text component properties including font, color, background, spacing etc.
|
|
11
29
|
* @returns Object containing text inline styles and the appropriate HTML tag to render
|
|
12
30
|
*/
|
|
13
|
-
export const getTextComponentStyles = (colorMode, props) => {
|
|
14
|
-
const { color = defaultColor,
|
|
15
|
-
const
|
|
16
|
-
const
|
|
31
|
+
export const getTextComponentStyles = (colorMode, props, fonts) => {
|
|
32
|
+
const { font_name, font_size, font_weight, font_weight_int, horizontal_alignment, color = defaultColor, padding, margin, size, } = props;
|
|
33
|
+
const font = fonts[font_name ?? ""];
|
|
34
|
+
const fontFamily = font?.web?.family;
|
|
35
|
+
return css({
|
|
17
36
|
display: "block",
|
|
18
37
|
width: mapSize(size.width),
|
|
19
38
|
height: mapSize(size.height),
|
|
20
39
|
margin: mapSpacing(margin),
|
|
21
40
|
padding: mapSpacing(padding),
|
|
22
|
-
...
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
"font-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"-
|
|
29
|
-
|
|
30
|
-
|
|
41
|
+
...mapTextColor(colorMode, color),
|
|
42
|
+
"text-align": TextAlignments[horizontal_alignment] || TextAlignments.leading,
|
|
43
|
+
"font-weight": font_weight_int ?? FontWeights[font_weight] ?? FontWeights.regular,
|
|
44
|
+
"font-size": Number.isInteger(Number(font_size))
|
|
45
|
+
? `${font_size}px`
|
|
46
|
+
: FontSizes[font_size] || FontSizes.body_m,
|
|
47
|
+
"font-family": isFontRCFMManaged(font_name ?? "")
|
|
48
|
+
? getScopedFontFamily(fontFamily ?? "")
|
|
49
|
+
: "sans-serif",
|
|
31
50
|
});
|
|
32
|
-
return {
|
|
33
|
-
textStyles: style,
|
|
34
|
-
tagToRender: "span",
|
|
35
|
-
};
|
|
36
51
|
};
|
|
37
52
|
export function getTextWrapperInlineStyles(colorMode, _restProps, size, background_color) {
|
|
38
53
|
return css({
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getTimelineItemContentInlineStyles } from "./timeline-utils";
|
|
3
|
-
import type { TimelineItemProps } from "../../types/components/timeline";
|
|
4
3
|
import { getColorModeContext } from "../../stores/color-mode";
|
|
5
|
-
import { getColor, getActiveStateProps } from "../../utils/style-utils";
|
|
6
4
|
import { getSelectedStateContext } from "../../stores/selected";
|
|
5
|
+
import type { TimelineItemProps } from "../../types/components/timeline";
|
|
6
|
+
import { css, mapColor } from "../../utils/base-utils";
|
|
7
|
+
import { getActiveStateProps } from "../../utils/style-utils";
|
|
7
8
|
import Icon from "../icon/Icon.svelte";
|
|
8
|
-
import { css } from "../../utils/base-utils";
|
|
9
9
|
import TextNode from "../text/TextNode.svelte";
|
|
10
10
|
|
|
11
11
|
interface Props extends TimelineItemProps {
|
|
@@ -64,11 +64,7 @@
|
|
|
64
64
|
const connectorBarStyles = $derived.by(() => {
|
|
65
65
|
if (!connector) return "";
|
|
66
66
|
|
|
67
|
-
const background =
|
|
68
|
-
colorMap: connector.color,
|
|
69
|
-
colorMode,
|
|
70
|
-
fallback: "#000000",
|
|
71
|
-
});
|
|
67
|
+
const background = mapColor(colorMode, connector.color);
|
|
72
68
|
|
|
73
69
|
return css({
|
|
74
70
|
width: "100%",
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
import Video from "./Video.svelte";
|
|
3
|
+
import { componentDecorator } from "../../stories/component-decorator";
|
|
4
|
+
import { defineMeta } from "@storybook/addon-svelte-csf";
|
|
5
|
+
|
|
6
|
+
const { Story } = defineMeta({
|
|
7
|
+
title: "Components/Video",
|
|
8
|
+
component: Video,
|
|
9
|
+
decorators: [componentDecorator()],
|
|
10
|
+
parameters: {
|
|
11
|
+
chromatic: { disableSnapshot: true },
|
|
12
|
+
},
|
|
13
|
+
args: {
|
|
14
|
+
type: "video",
|
|
15
|
+
id: "video",
|
|
16
|
+
name: "Video",
|
|
17
|
+
fit_mode: "fill",
|
|
18
|
+
auto_play: true,
|
|
19
|
+
loop: true,
|
|
20
|
+
mute_audio: true,
|
|
21
|
+
show_controls: true,
|
|
22
|
+
size: {
|
|
23
|
+
width: { type: "fill" },
|
|
24
|
+
height: { type: "fill" },
|
|
25
|
+
},
|
|
26
|
+
source: {
|
|
27
|
+
dark: null,
|
|
28
|
+
light: {
|
|
29
|
+
width: 1920,
|
|
30
|
+
height: 1080,
|
|
31
|
+
url: "https://videos.pexels.com/video-files/10288594/10288594-hd_1920_1080_25fps.mp4",
|
|
32
|
+
url_low_res:
|
|
33
|
+
"https://videos.pexels.com/video-files/10288594/10288594-hd_1920_1080_25fps.mp4",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
</script>
|
|
39
|
+
|
|
40
|
+
<Story
|
|
41
|
+
name="Rectangle"
|
|
42
|
+
args={{
|
|
43
|
+
mask_shape: {
|
|
44
|
+
type: "rectangle",
|
|
45
|
+
corners: {
|
|
46
|
+
top_leading: 0,
|
|
47
|
+
top_trailing: 0,
|
|
48
|
+
bottom_leading: 0,
|
|
49
|
+
bottom_trailing: 0,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
}}
|
|
53
|
+
/>
|
|
54
|
+
|
|
55
|
+
<Story
|
|
56
|
+
name="Rounded Rectangle"
|
|
57
|
+
args={{
|
|
58
|
+
mask_shape: {
|
|
59
|
+
type: "rectangle",
|
|
60
|
+
corners: {
|
|
61
|
+
top_leading: 32,
|
|
62
|
+
top_trailing: 32,
|
|
63
|
+
bottom_leading: 32,
|
|
64
|
+
bottom_trailing: 32,
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
}}
|
|
68
|
+
/>
|
|
69
|
+
|
|
70
|
+
<Story
|
|
71
|
+
name="Circle"
|
|
72
|
+
args={{
|
|
73
|
+
mask_shape: { type: "circle" },
|
|
74
|
+
}}
|
|
75
|
+
/>
|
|
76
|
+
|
|
77
|
+
<Story
|
|
78
|
+
name="Concave"
|
|
79
|
+
args={{
|
|
80
|
+
mask_shape: { type: "concave" },
|
|
81
|
+
}}
|
|
82
|
+
/>
|
|
83
|
+
|
|
84
|
+
<Story
|
|
85
|
+
name="Convex"
|
|
86
|
+
args={{
|
|
87
|
+
mask_shape: { type: "convex" },
|
|
88
|
+
}}
|
|
89
|
+
/>
|
|
90
|
+
|
|
91
|
+
<Story
|
|
92
|
+
name="With Controls"
|
|
93
|
+
args={{
|
|
94
|
+
show_controls: true,
|
|
95
|
+
auto_play: false,
|
|
96
|
+
mute_audio: false,
|
|
97
|
+
}}
|
|
98
|
+
/>
|
|
99
|
+
|
|
100
|
+
<Story
|
|
101
|
+
name="Autoplay Muted"
|
|
102
|
+
args={{
|
|
103
|
+
show_controls: false,
|
|
104
|
+
auto_play: true,
|
|
105
|
+
mute_audio: true,
|
|
106
|
+
loop: true,
|
|
107
|
+
}}
|
|
108
|
+
/>
|
|
109
|
+
|
|
110
|
+
<Story
|
|
111
|
+
name="With Fallback Image"
|
|
112
|
+
args={{
|
|
113
|
+
source: null,
|
|
114
|
+
fallback_source: {
|
|
115
|
+
dark: null,
|
|
116
|
+
light: {
|
|
117
|
+
width: 1920,
|
|
118
|
+
height: 1080,
|
|
119
|
+
original:
|
|
120
|
+
"https://placehold.co/1920x1080/blue/white?text=Video+Not+Available",
|
|
121
|
+
heic: "https://placehold.co/1920x1080/blue/white?text=Video+Not+Available",
|
|
122
|
+
heic_low_res:
|
|
123
|
+
"https://placehold.co/1920x1080/blue/white?text=Video+Not+Available",
|
|
124
|
+
webp: "https://placehold.co/1920x1080/blue/white?text=Video+Not+Available",
|
|
125
|
+
webp_low_res:
|
|
126
|
+
"https://placehold.co/1920x1080/blue/white?text=Video+Not+Available",
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
}}
|
|
130
|
+
/>
|
|
131
|
+
|
|
132
|
+
<Story
|
|
133
|
+
name="Overlay Radial Gradient"
|
|
134
|
+
args={{
|
|
135
|
+
color_overlay: {
|
|
136
|
+
light: {
|
|
137
|
+
type: "radial",
|
|
138
|
+
points: [
|
|
139
|
+
{
|
|
140
|
+
percent: 0,
|
|
141
|
+
color: "#020024ff",
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
percent: 100,
|
|
145
|
+
color: "#00d4ff00",
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
}}
|
|
151
|
+
/>
|
|
152
|
+
|
|
153
|
+
<Story
|
|
154
|
+
name="Overlay Linear Gradient"
|
|
155
|
+
args={{
|
|
156
|
+
color_overlay: {
|
|
157
|
+
light: {
|
|
158
|
+
degrees: 180,
|
|
159
|
+
points: [
|
|
160
|
+
{
|
|
161
|
+
percent: 0,
|
|
162
|
+
color: "#020024ff",
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
percent: 100,
|
|
166
|
+
color: "#00d4ff00",
|
|
167
|
+
},
|
|
168
|
+
],
|
|
169
|
+
type: "linear",
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
}}
|
|
173
|
+
/>
|
|
174
|
+
|
|
175
|
+
<Story
|
|
176
|
+
name="Overlay Solid"
|
|
177
|
+
args={{
|
|
178
|
+
color_overlay: {
|
|
179
|
+
light: {
|
|
180
|
+
type: "hex",
|
|
181
|
+
value: "#e7c00069",
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
}}
|
|
185
|
+
/>
|
|
186
|
+
|
|
187
|
+
<Story
|
|
188
|
+
name="Complex"
|
|
189
|
+
args={{
|
|
190
|
+
size: {
|
|
191
|
+
width: { type: "fixed", value: 400 },
|
|
192
|
+
height: { type: "fixed", value: 300 },
|
|
193
|
+
},
|
|
194
|
+
mask_shape: {
|
|
195
|
+
type: "rectangle",
|
|
196
|
+
corners: {
|
|
197
|
+
top_leading: 0,
|
|
198
|
+
top_trailing: 16,
|
|
199
|
+
bottom_trailing: 32,
|
|
200
|
+
bottom_leading: 64,
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
fit_mode: "fill",
|
|
204
|
+
padding: { top: 0, bottom: 0, leading: 0, trailing: 0 },
|
|
205
|
+
margin: { top: 0, bottom: 0, leading: 0, trailing: 0 },
|
|
206
|
+
color_overlay: {
|
|
207
|
+
light: {
|
|
208
|
+
type: "linear",
|
|
209
|
+
degrees: 180,
|
|
210
|
+
points: [
|
|
211
|
+
{ percent: 0, color: "#ff000080" },
|
|
212
|
+
{ percent: 100, color: "#ff00ff00" },
|
|
213
|
+
],
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
border: {
|
|
217
|
+
width: 3,
|
|
218
|
+
color: {
|
|
219
|
+
light: { type: "hex", value: "#00ff0080" },
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
shadow: {
|
|
223
|
+
x: 0,
|
|
224
|
+
y: 0,
|
|
225
|
+
radius: 16,
|
|
226
|
+
color: {
|
|
227
|
+
light: { type: "hex", value: "#0000ff80" },
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
show_controls: true,
|
|
231
|
+
auto_play: false,
|
|
232
|
+
mute_audio: false,
|
|
233
|
+
loop: false,
|
|
234
|
+
}}
|
|
235
|
+
/>
|
|
236
|
+
|
|
237
|
+
<Story
|
|
238
|
+
name="Fixed Size"
|
|
239
|
+
args={{
|
|
240
|
+
size: {
|
|
241
|
+
width: { type: "fixed", value: 320 },
|
|
242
|
+
height: { type: "fixed", value: 240 },
|
|
243
|
+
},
|
|
244
|
+
}}
|
|
245
|
+
/>
|
|
246
|
+
|
|
247
|
+
<Story
|
|
248
|
+
name="Fit Mode - Contain"
|
|
249
|
+
args={{
|
|
250
|
+
fit_mode: "fit",
|
|
251
|
+
size: {
|
|
252
|
+
width: { type: "fixed", value: 400 },
|
|
253
|
+
height: { type: "fixed", value: 300 },
|
|
254
|
+
},
|
|
255
|
+
}}
|
|
256
|
+
/>
|
|
257
|
+
|
|
258
|
+
<Story
|
|
259
|
+
name="Fit Mode - Cover"
|
|
260
|
+
args={{
|
|
261
|
+
fit_mode: "fill",
|
|
262
|
+
size: {
|
|
263
|
+
width: { type: "fixed", value: 400 },
|
|
264
|
+
height: { type: "fixed", value: 300 },
|
|
265
|
+
},
|
|
266
|
+
}}
|
|
267
|
+
/>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import Video from "./Video.svelte";
|
|
2
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
3
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
4
|
+
$$bindings?: Bindings;
|
|
5
|
+
} & Exports;
|
|
6
|
+
(internal: unknown, props: {
|
|
7
|
+
$$events?: Events;
|
|
8
|
+
$$slots?: Slots;
|
|
9
|
+
}): Exports & {
|
|
10
|
+
$set?: any;
|
|
11
|
+
$on?: any;
|
|
12
|
+
};
|
|
13
|
+
z_$$bindings?: Bindings;
|
|
14
|
+
}
|
|
15
|
+
declare const Video: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
|
|
16
|
+
[evt: string]: CustomEvent<any>;
|
|
17
|
+
}, {}, {}, string>;
|
|
18
|
+
type Video = InstanceType<typeof Video>;
|
|
19
|
+
export default Video;
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getColorModeContext } from "../../stores/color-mode";
|
|
3
|
+
import { getSelectedStateContext } from "../../stores/selected";
|
|
4
|
+
import type { VideoProps } from "../../types/components/video";
|
|
5
|
+
import type { VideoFiles, ImageFiles } from "../../types/media";
|
|
6
|
+
import {
|
|
7
|
+
css,
|
|
8
|
+
mapColor,
|
|
9
|
+
mapColorMode,
|
|
10
|
+
mapFitMode,
|
|
11
|
+
mapSize,
|
|
12
|
+
mapSpacing,
|
|
13
|
+
} from "../../utils/base-utils";
|
|
14
|
+
import { getActiveStateProps } from "../../utils/style-utils";
|
|
15
|
+
import ClipPath from "../image/ClipPath.svelte";
|
|
16
|
+
import Overlay from "../image/Overlay.svelte";
|
|
17
|
+
|
|
18
|
+
const props: VideoProps = $props();
|
|
19
|
+
|
|
20
|
+
const selectedState = getSelectedStateContext();
|
|
21
|
+
const {
|
|
22
|
+
id,
|
|
23
|
+
source,
|
|
24
|
+
fallback_source,
|
|
25
|
+
size,
|
|
26
|
+
mask_shape,
|
|
27
|
+
fit_mode,
|
|
28
|
+
padding,
|
|
29
|
+
margin,
|
|
30
|
+
color_overlay,
|
|
31
|
+
border,
|
|
32
|
+
shadow,
|
|
33
|
+
auto_play,
|
|
34
|
+
loop,
|
|
35
|
+
mute_audio,
|
|
36
|
+
show_controls,
|
|
37
|
+
} = $derived.by(() => {
|
|
38
|
+
return {
|
|
39
|
+
...props,
|
|
40
|
+
...getActiveStateProps($selectedState, props.overrides),
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const getColorMode = getColorModeContext();
|
|
45
|
+
const colorMode = $derived(getColorMode());
|
|
46
|
+
const video: VideoFiles | null = $derived(
|
|
47
|
+
source ? (mapColorMode(colorMode, source as any) as VideoFiles) : null,
|
|
48
|
+
);
|
|
49
|
+
const fallbackImage: ImageFiles | null = $derived(
|
|
50
|
+
fallback_source
|
|
51
|
+
? (mapColorMode(colorMode, fallback_source as any) as ImageFiles)
|
|
52
|
+
: null,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
let wrapperWidth = $state(0);
|
|
56
|
+
let videoSize = $state({
|
|
57
|
+
height: 0,
|
|
58
|
+
width: 0,
|
|
59
|
+
});
|
|
60
|
+
let videoElement = $state<HTMLVideoElement | null>(null);
|
|
61
|
+
let hasVideoError = $state(false);
|
|
62
|
+
|
|
63
|
+
// Load video or fallback image metadata to get dimensions
|
|
64
|
+
$effect(() => {
|
|
65
|
+
// If we have a video, load its dimensions
|
|
66
|
+
if (video) {
|
|
67
|
+
if (video.width && video.height) {
|
|
68
|
+
videoSize = {
|
|
69
|
+
height: video.height,
|
|
70
|
+
width: video.width,
|
|
71
|
+
};
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// If dimensions aren't provided, try to load them from the video element
|
|
76
|
+
if (videoElement) {
|
|
77
|
+
const onLoadedMetadata = () => {
|
|
78
|
+
videoSize = {
|
|
79
|
+
height: videoElement?.videoHeight ?? 0,
|
|
80
|
+
width: videoElement?.videoWidth ?? 0,
|
|
81
|
+
};
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const onError = () => {
|
|
85
|
+
hasVideoError = true;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
videoElement.addEventListener("loadedmetadata", onLoadedMetadata);
|
|
89
|
+
videoElement.addEventListener("error", onError);
|
|
90
|
+
|
|
91
|
+
return () => {
|
|
92
|
+
videoElement?.removeEventListener("loadedmetadata", onLoadedMetadata);
|
|
93
|
+
videoElement?.removeEventListener("error", onError);
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// If we don't have a video but have a fallback image, use its dimensions
|
|
98
|
+
else if (fallbackImage) {
|
|
99
|
+
if (fallbackImage.width && fallbackImage.height) {
|
|
100
|
+
videoSize = {
|
|
101
|
+
height: fallbackImage.height,
|
|
102
|
+
width: fallbackImage.width,
|
|
103
|
+
};
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// If dimensions aren't provided, load them from the image
|
|
108
|
+
const img = new window.Image();
|
|
109
|
+
img.src = fallbackImage.original;
|
|
110
|
+
|
|
111
|
+
const onImageLoad = () => {
|
|
112
|
+
videoSize = {
|
|
113
|
+
height: img.height,
|
|
114
|
+
width: img.width,
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
img.addEventListener("load", onImageLoad);
|
|
119
|
+
return () => {
|
|
120
|
+
img.removeEventListener("load", onImageLoad);
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const height = $derived.by(() => {
|
|
126
|
+
if (size.height.type === "fixed") {
|
|
127
|
+
return size.height.value;
|
|
128
|
+
}
|
|
129
|
+
return Math.round(wrapperWidth * (videoSize.height / videoSize.width));
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const style = $derived(
|
|
133
|
+
css({
|
|
134
|
+
width: mapSize(size.width),
|
|
135
|
+
height: `${height}px`,
|
|
136
|
+
margin: mapSpacing(margin),
|
|
137
|
+
padding: mapSpacing(padding),
|
|
138
|
+
"line-height": 0,
|
|
139
|
+
"flex-shrink": size.width.type === "fixed" ? 0 : 1,
|
|
140
|
+
}),
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
let svgRect = $state<DOMRect | null>(null);
|
|
144
|
+
|
|
145
|
+
const [svgWidth, svgHeight] = $derived.by(() => {
|
|
146
|
+
return [svgRect?.width ?? 0, svgRect?.height ?? 0];
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const viewBox = $derived.by(() => {
|
|
150
|
+
return `0 0 ${svgWidth} ${svgHeight}`;
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const svgStyle = $derived.by(() => {
|
|
154
|
+
if (!shadow?.color) {
|
|
155
|
+
return "";
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const { x, y, radius, color } = shadow;
|
|
159
|
+
const shadowColor = mapColor(colorMode, color);
|
|
160
|
+
return `filter: drop-shadow(${x}px ${y}px ${radius}px ${shadowColor})`;
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
const overlay = $derived(
|
|
164
|
+
color_overlay && mapColorMode(colorMode, color_overlay),
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
const shouldShowFallback = $derived(hasVideoError || !video);
|
|
168
|
+
</script>
|
|
169
|
+
|
|
170
|
+
<div {style} bind:clientWidth={wrapperWidth}>
|
|
171
|
+
<svg
|
|
172
|
+
bind:contentRect={svgRect}
|
|
173
|
+
width="100%"
|
|
174
|
+
height="100%"
|
|
175
|
+
{viewBox}
|
|
176
|
+
style={svgStyle}
|
|
177
|
+
>
|
|
178
|
+
<defs>
|
|
179
|
+
<clipPath id={`${id}-path`}>
|
|
180
|
+
<ClipPath shape={mask_shape} width={svgWidth} height={svgHeight} />
|
|
181
|
+
</clipPath>
|
|
182
|
+
|
|
183
|
+
<g id={`${id}-border`}>
|
|
184
|
+
<ClipPath shape={mask_shape} width={svgWidth} height={svgHeight} />
|
|
185
|
+
</g>
|
|
186
|
+
|
|
187
|
+
<Overlay id={`${id}-overlay`} {overlay} />
|
|
188
|
+
</defs>
|
|
189
|
+
|
|
190
|
+
{#if border && border.width > 0}
|
|
191
|
+
<use href={`#${id}-border`} fill="transparent" />
|
|
192
|
+
{/if}
|
|
193
|
+
|
|
194
|
+
<g clip-path={`url(#${id}-path)`}>
|
|
195
|
+
<foreignObject x="0" y="0" width="100%" height="100%">
|
|
196
|
+
{#if shouldShowFallback && fallbackImage}
|
|
197
|
+
<img
|
|
198
|
+
src={fallbackImage.original}
|
|
199
|
+
style="object-fit:{mapFitMode(fit_mode)}"
|
|
200
|
+
alt=""
|
|
201
|
+
/>
|
|
202
|
+
{:else if video}
|
|
203
|
+
<video
|
|
204
|
+
bind:this={videoElement}
|
|
205
|
+
src={video.url}
|
|
206
|
+
style="object-fit:{mapFitMode(fit_mode)}"
|
|
207
|
+
autoplay={auto_play}
|
|
208
|
+
{loop}
|
|
209
|
+
muted={mute_audio}
|
|
210
|
+
controls={show_controls}
|
|
211
|
+
playsinline
|
|
212
|
+
>
|
|
213
|
+
<track kind="captions" />
|
|
214
|
+
</video>
|
|
215
|
+
{/if}
|
|
216
|
+
</foreignObject>
|
|
217
|
+
|
|
218
|
+
{#if overlay}
|
|
219
|
+
<rect
|
|
220
|
+
x="0"
|
|
221
|
+
y="0"
|
|
222
|
+
height="100%"
|
|
223
|
+
width="100%"
|
|
224
|
+
fill={`url(#${id}-overlay)`}
|
|
225
|
+
/>
|
|
226
|
+
{/if}
|
|
227
|
+
|
|
228
|
+
{#if border && border.width > 0}
|
|
229
|
+
<use
|
|
230
|
+
href={`#${id}-border`}
|
|
231
|
+
fill="none"
|
|
232
|
+
stroke={mapColor(colorMode, border.color)}
|
|
233
|
+
stroke-width={border.width}
|
|
234
|
+
/>
|
|
235
|
+
{/if}
|
|
236
|
+
</g>
|
|
237
|
+
</svg>
|
|
238
|
+
</div>
|
|
239
|
+
|
|
240
|
+
<style>
|
|
241
|
+
video,
|
|
242
|
+
img {
|
|
243
|
+
width: 100%;
|
|
244
|
+
height: 100%;
|
|
245
|
+
object-fit: contain;
|
|
246
|
+
object-position: center;
|
|
247
|
+
}
|
|
248
|
+
</style>
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export { default as PurchaseButton } from "./components/purchase-button/Purchase
|
|
|
7
7
|
export { default as Stack } from "./components/stack/Stack.svelte";
|
|
8
8
|
export { default as Text } from "./components/text/Text.svelte";
|
|
9
9
|
export { default as Timeline } from "./components/timeline/Timeline.svelte";
|
|
10
|
+
export { default as Video } from "./components/video/Video.svelte";
|
|
10
11
|
export * from "./types";
|
|
11
12
|
export { type PaywallData } from "./types/paywall";
|
|
12
13
|
export { type UIConfig } from "./types/ui-config";
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ export { default as PurchaseButton } from "./components/purchase-button/Purchase
|
|
|
8
8
|
export { default as Stack } from "./components/stack/Stack.svelte";
|
|
9
9
|
export { default as Text } from "./components/text/Text.svelte";
|
|
10
10
|
export { default as Timeline } from "./components/timeline/Timeline.svelte";
|
|
11
|
+
export { default as Video } from "./components/video/Video.svelte";
|
|
11
12
|
export * from "./types";
|
|
12
13
|
export {} from "./types/paywall";
|
|
13
14
|
export {} from "./types/ui-config";
|
package/dist/stores/paywall.d.ts
CHANGED
|
@@ -6,7 +6,7 @@ type PaywallContext = Readonly<{
|
|
|
6
6
|
selectedPackageId: Writable<string | undefined>;
|
|
7
7
|
variablesPerPackage: Readable<Record<string, VariableDictionary> | undefined>;
|
|
8
8
|
onPurchase: () => void;
|
|
9
|
-
onButtonAction: (action: Action) => void;
|
|
9
|
+
onButtonAction: (action: Action, actionId?: string) => void;
|
|
10
10
|
uiConfig: UIConfig;
|
|
11
11
|
}>;
|
|
12
12
|
export declare function setPaywallContext(context: PaywallContext): void;
|
|
@@ -17,7 +17,7 @@ export function paywallDecorator() {
|
|
|
17
17
|
selectedPackageId,
|
|
18
18
|
variablesPerPackage: readable(undefined),
|
|
19
19
|
onPurchase: () => window.alert("Purchase clicked"),
|
|
20
|
-
onButtonAction: (action) => window.alert(`Button clicked: ${JSON.stringify(action, undefined, 2)}`),
|
|
20
|
+
onButtonAction: (action, actionId) => window.alert(`Button clicked: ${JSON.stringify({ action, actionId }, undefined, 2)}`),
|
|
21
21
|
uiConfig: emptyUiConfig,
|
|
22
22
|
});
|
|
23
23
|
return Story();
|