@revenuecat/purchases-ui-js 4.8.1 → 4.8.3
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/countdown/Countdown.svelte +9 -18
- package/dist/components/countdown/countdown-utils.d.ts +2 -2
- package/dist/components/countdown/countdown-utils.js +4 -6
- package/dist/components/countdown/shared-ticker.d.ts +6 -0
- package/dist/components/countdown/shared-ticker.js +20 -0
- package/dist/components/input-text/InputText.svelte +30 -7
- package/dist/components/input-text/InputTextTestWrapper.svelte +53 -0
- package/dist/components/input-text/InputTextTestWrapper.svelte.d.ts +8 -0
- package/dist/components/paywall/Paywall.svelte +2 -1
- package/package.json +1 -1
|
@@ -5,24 +5,26 @@
|
|
|
5
5
|
setVariablesContext,
|
|
6
6
|
} from "../../stores/variables";
|
|
7
7
|
import type { CountdownProps } from "../../types/components/countdown";
|
|
8
|
-
import { derived,
|
|
8
|
+
import { derived, toStore } from "svelte/store";
|
|
9
9
|
import {
|
|
10
10
|
calculateCountdownValues,
|
|
11
11
|
hasCountdownEnded,
|
|
12
12
|
} from "./countdown-utils";
|
|
13
|
+
import { sharedNow } from "./shared-ticker";
|
|
13
14
|
|
|
14
15
|
const props: CountdownProps = $props();
|
|
15
16
|
const { countdown_stack, end_stack } = props;
|
|
16
17
|
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
// All countdowns read one shared ticker so they stay in lockstep.
|
|
19
|
+
const countdownValues = $derived(
|
|
20
|
+
calculateCountdownValues(props.style.date, $sharedNow),
|
|
21
|
+
);
|
|
22
|
+
const isEnded = $derived(hasCountdownEnded(props.style.date, $sharedNow));
|
|
20
23
|
|
|
21
|
-
//
|
|
24
|
+
// Expose countdown variables to child Text components via context.
|
|
22
25
|
const fallbackVariables = getVariablesContext();
|
|
23
|
-
const countdownValuesStore = writable(countdownValues);
|
|
24
26
|
const variables = derived(
|
|
25
|
-
[fallbackVariables,
|
|
27
|
+
[fallbackVariables, toStore(() => countdownValues)],
|
|
26
28
|
([fallback, countdown]) => ({
|
|
27
29
|
...fallback,
|
|
28
30
|
...countdown,
|
|
@@ -30,17 +32,6 @@
|
|
|
30
32
|
);
|
|
31
33
|
setVariablesContext(variables);
|
|
32
34
|
|
|
33
|
-
// Setup interval to update countdown every second
|
|
34
|
-
$effect(() => {
|
|
35
|
-
const interval = setInterval(() => {
|
|
36
|
-
countdownValues = calculateCountdownValues(props.style.date);
|
|
37
|
-
isEnded = hasCountdownEnded(props.style.date);
|
|
38
|
-
countdownValuesStore.set(countdownValues);
|
|
39
|
-
}, 1000);
|
|
40
|
-
|
|
41
|
-
return () => clearInterval(interval);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
35
|
// Determine which stack to show
|
|
45
36
|
const activeStack = $derived.by(() => {
|
|
46
37
|
// If countdown has ended and end_stack exists, show end_stack
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* - count_minutes_with_zero / count_minutes_without_zero
|
|
7
7
|
* - count_seconds_with_zero / count_seconds_without_zero
|
|
8
8
|
*/
|
|
9
|
-
export declare function calculateCountdownValues(targetDate: string): Record<string, string>;
|
|
9
|
+
export declare function calculateCountdownValues(targetDate: string, nowMs?: number): Record<string, string>;
|
|
10
10
|
/**
|
|
11
11
|
* Checks if a countdown has ended (target date is in the past)
|
|
12
12
|
*/
|
|
13
|
-
export declare function hasCountdownEnded(targetDate: string): boolean;
|
|
13
|
+
export declare function hasCountdownEnded(targetDate: string, nowMs?: number): boolean;
|
|
@@ -18,10 +18,9 @@ function formatWithoutZero(value) {
|
|
|
18
18
|
* - count_minutes_with_zero / count_minutes_without_zero
|
|
19
19
|
* - count_seconds_with_zero / count_seconds_without_zero
|
|
20
20
|
*/
|
|
21
|
-
export function calculateCountdownValues(targetDate) {
|
|
22
|
-
const now = new Date();
|
|
21
|
+
export function calculateCountdownValues(targetDate, nowMs = Date.now()) {
|
|
23
22
|
const target = new Date(targetDate);
|
|
24
|
-
const diffInMs = target.getTime() -
|
|
23
|
+
const diffInMs = target.getTime() - nowMs;
|
|
25
24
|
// If past target date, return all zeros
|
|
26
25
|
if (diffInMs <= 0) {
|
|
27
26
|
return {
|
|
@@ -59,8 +58,7 @@ export function calculateCountdownValues(targetDate) {
|
|
|
59
58
|
/**
|
|
60
59
|
* Checks if a countdown has ended (target date is in the past)
|
|
61
60
|
*/
|
|
62
|
-
export function hasCountdownEnded(targetDate) {
|
|
63
|
-
const now = new Date();
|
|
61
|
+
export function hasCountdownEnded(targetDate, nowMs = Date.now()) {
|
|
64
62
|
const target = new Date(targetDate);
|
|
65
|
-
return target.getTime() <=
|
|
63
|
+
return target.getTime() <= nowMs;
|
|
66
64
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wall-clock now (epoch ms) shared by every countdown so they tick in lockstep.
|
|
3
|
+
* The interval runs only while at least one countdown is subscribed: Svelte's
|
|
4
|
+
* `readable` starts on the first subscriber and stops on the last.
|
|
5
|
+
*/
|
|
6
|
+
export declare const sharedNow: import("svelte/store").Readable<number>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { readable } from "svelte/store";
|
|
2
|
+
/**
|
|
3
|
+
* Wall-clock now (epoch ms) shared by every countdown so they tick in lockstep.
|
|
4
|
+
* The interval runs only while at least one countdown is subscribed: Svelte's
|
|
5
|
+
* `readable` starts on the first subscriber and stops on the last.
|
|
6
|
+
*/
|
|
7
|
+
export const sharedNow = readable(Date.now(), (set) => {
|
|
8
|
+
set(Date.now());
|
|
9
|
+
// Align the first tick to the whole second, then tick every second.
|
|
10
|
+
let interval;
|
|
11
|
+
const boundaryTimeout = setTimeout(() => {
|
|
12
|
+
set(Date.now());
|
|
13
|
+
interval = setInterval(() => set(Date.now()), 1000);
|
|
14
|
+
}, 1000 - (Date.now() % 1000));
|
|
15
|
+
return () => {
|
|
16
|
+
clearTimeout(boundaryTimeout);
|
|
17
|
+
if (interval !== undefined)
|
|
18
|
+
clearInterval(interval);
|
|
19
|
+
};
|
|
20
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getColorModeContext } from "../../stores/color-mode";
|
|
3
|
+
import { getInputValidationContext } from "../../stores/inputValidation";
|
|
3
4
|
import { getLocalizationContext } from "../../stores/localization";
|
|
4
5
|
import { getOptionalPackageInfoContext } from "../../stores/packageInfo";
|
|
5
6
|
import { getPaywallContext } from "../../stores/paywall";
|
|
@@ -35,6 +36,7 @@
|
|
|
35
36
|
|
|
36
37
|
let focused = $state(false);
|
|
37
38
|
let error = $state(false);
|
|
39
|
+
let valid = $state(!props.required);
|
|
38
40
|
|
|
39
41
|
const {
|
|
40
42
|
placeholder_lid,
|
|
@@ -69,12 +71,7 @@
|
|
|
69
71
|
};
|
|
70
72
|
});
|
|
71
73
|
|
|
72
|
-
function
|
|
73
|
-
error = !input.validity.valid;
|
|
74
|
-
if (error) {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
74
|
+
function commitValue(input: HTMLInputElement) {
|
|
78
75
|
const value = input.value.trim();
|
|
79
76
|
if (reserved_attribute) {
|
|
80
77
|
if (!onReservedAttributeChanged) {
|
|
@@ -87,17 +84,35 @@
|
|
|
87
84
|
}
|
|
88
85
|
}
|
|
89
86
|
|
|
87
|
+
function handleValueChange(input: HTMLInputElement) {
|
|
88
|
+
error = !input.validity.valid;
|
|
89
|
+
if (error) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
commitValue(input);
|
|
93
|
+
}
|
|
94
|
+
|
|
90
95
|
const oninput: FormEventHandler<HTMLInputElement> = (event) => {
|
|
96
|
+
const input = event.currentTarget;
|
|
97
|
+
valid = input.validity.valid;
|
|
98
|
+
// Keep the propagated value in lockstep with `valid` so Continue can
|
|
99
|
+
// never unlock before the workflow has received the current value.
|
|
100
|
+
if (valid) {
|
|
101
|
+
commitValue(input);
|
|
102
|
+
}
|
|
91
103
|
if (error) {
|
|
92
|
-
handleValueChange(
|
|
104
|
+
handleValueChange(input);
|
|
93
105
|
}
|
|
94
106
|
};
|
|
95
107
|
|
|
96
108
|
const onblur: FormEventHandler<HTMLInputElement> = (event) => {
|
|
97
109
|
focused = false;
|
|
110
|
+
valid = event.currentTarget.validity.valid;
|
|
98
111
|
handleValueChange(event.currentTarget);
|
|
99
112
|
};
|
|
100
113
|
|
|
114
|
+
const validationContext = getInputValidationContext();
|
|
115
|
+
|
|
101
116
|
const getColorMode = getColorModeContext();
|
|
102
117
|
const colorMode = $derived(getColorMode());
|
|
103
118
|
const {
|
|
@@ -201,6 +216,14 @@
|
|
|
201
216
|
props.visible,
|
|
202
217
|
),
|
|
203
218
|
);
|
|
219
|
+
|
|
220
|
+
if (validationContext) {
|
|
221
|
+
$effect(() => {
|
|
222
|
+
// A hidden field can't be filled in, so it shouldn't be able to block
|
|
223
|
+
// Continue for the rest of the screen.
|
|
224
|
+
validationContext.updateSatisfaction(!isVisible || valid);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
204
227
|
</script>
|
|
205
228
|
|
|
206
229
|
{#if isVisible}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { setColorModeContext } from "../../stores/color-mode";
|
|
3
|
+
import {
|
|
4
|
+
createInputValidationContext,
|
|
5
|
+
setInputValidationContext,
|
|
6
|
+
} from "../../stores/inputValidation";
|
|
7
|
+
import { setLocalizationContext } from "../../stores/localization";
|
|
8
|
+
import { setPackageInfoContext } from "../../stores/packageInfo";
|
|
9
|
+
import { setPaywallContext } from "../../stores/paywall";
|
|
10
|
+
import { setVariablesContext } from "../../stores/variables";
|
|
11
|
+
import { readable, writable } from "svelte/store";
|
|
12
|
+
import InputText from "./InputText.svelte";
|
|
13
|
+
import type { InputTextProps } from "../../types/components/input-text";
|
|
14
|
+
|
|
15
|
+
interface Props extends InputTextProps {
|
|
16
|
+
onInputChanged?: (fieldId: string, value: string) => void;
|
|
17
|
+
onSatisfactionChange?: (satisfied: boolean) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { onInputChanged, onSatisfactionChange, ...inputTextProps }: Props =
|
|
21
|
+
$props();
|
|
22
|
+
|
|
23
|
+
const validationContext = createInputValidationContext();
|
|
24
|
+
setInputValidationContext(validationContext);
|
|
25
|
+
if (onSatisfactionChange) {
|
|
26
|
+
validationContext.isSatisfied.subscribe(onSatisfactionChange);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
setColorModeContext();
|
|
30
|
+
setLocalizationContext(() => ({
|
|
31
|
+
defaultLocale: "en_US",
|
|
32
|
+
localizations: {
|
|
33
|
+
en_US: {},
|
|
34
|
+
},
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
setPaywallContext({
|
|
38
|
+
selectedPackageId: writable(undefined),
|
|
39
|
+
variablesPerPackage: readable(undefined),
|
|
40
|
+
baseVariables: readable(undefined),
|
|
41
|
+
infoPerPackage: readable(undefined),
|
|
42
|
+
onPurchase: () => {},
|
|
43
|
+
emitComponentInteraction: () => {},
|
|
44
|
+
onButtonAction: () => {},
|
|
45
|
+
onInputChanged,
|
|
46
|
+
uiConfig: { app: { fonts: {} } } as never,
|
|
47
|
+
hideBackButtons: false,
|
|
48
|
+
});
|
|
49
|
+
setPackageInfoContext(readable(undefined));
|
|
50
|
+
setVariablesContext(readable({}));
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<InputText {...inputTextProps} />
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { InputTextProps } from "../../types/components/input-text";
|
|
2
|
+
interface Props extends InputTextProps {
|
|
3
|
+
onInputChanged?: (fieldId: string, value: string) => void;
|
|
4
|
+
onSatisfactionChange?: (satisfied: boolean) => void;
|
|
5
|
+
}
|
|
6
|
+
declare const InputTextTestWrapper: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type InputTextTestWrapper = ReturnType<typeof InputTextTestWrapper>;
|
|
8
|
+
export default InputTextTestWrapper;
|
|
@@ -445,10 +445,11 @@
|
|
|
445
445
|
box-sizing: border-box;
|
|
446
446
|
margin: 0;
|
|
447
447
|
padding: 0;
|
|
448
|
-
font-family:
|
|
448
|
+
font-family: inherit;
|
|
449
449
|
}
|
|
450
450
|
|
|
451
451
|
.paywall {
|
|
452
|
+
font-family: sans-serif;
|
|
452
453
|
line-height: 1.5;
|
|
453
454
|
-webkit-font-smoothing: antialiased;
|
|
454
455
|
}
|