@revenuecat/purchases-ui-js 4.8.7 → 4.8.9

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.
@@ -10,7 +10,8 @@
10
10
  PackageSelectionSheetInteractionData,
11
11
  } from "../../types/paywall-component-interaction";
12
12
  import { getActiveStateProps } from "../../utils/style-utils";
13
- import { readable } from "svelte/store";
13
+ import { fromStore, readable } from "svelte/store";
14
+ import { isWorkflowButtonInputGated } from "./button-input-gating";
14
15
 
15
16
  const props: ButtonProps = $props();
16
17
  type SheetAction = Extract<
@@ -35,13 +36,8 @@
35
36
  const validationContext = getInputValidationContext();
36
37
  const { getLocalizedString } = getLocalizationContext();
37
38
 
38
- // Reactive store for input satisfaction (defaults to true if no validation context)
39
- const inputSatisfied = validationContext?.isSatisfied ?? readable(true);
40
-
41
- // Button is disabled when it's a workflow (continue) or complete_workflow action and inputs aren't satisfied
42
- const isDisabled = $derived(
43
- (action.type === "workflow" || action.type === "complete_workflow") &&
44
- !$inputSatisfied,
39
+ const inputSatisfied = fromStore(
40
+ validationContext?.isSatisfied ?? readable(true),
45
41
  );
46
42
 
47
43
  const getButtonInteractionData = (): ButtonInteractionData => {
@@ -142,7 +138,15 @@
142
138
  });
143
139
 
144
140
  const onclick = () => {
145
- if (isDisabled) return;
141
+ if (
142
+ isWorkflowButtonInputGated(
143
+ action.type,
144
+ props.require_valid_inputs,
145
+ inputSatisfied.current,
146
+ )
147
+ ) {
148
+ return;
149
+ }
146
150
  // For "workflow" actions, pass the component's own id so Workflow.svelte
147
151
  // can look it up in the step's triggers array (component ID → action ID).
148
152
  const actionId = action.type === "workflow" ? props.id : undefined;
@@ -177,14 +181,20 @@
177
181
  return true;
178
182
  }
179
183
  });
180
-
181
- const style = $derived(
182
- isDisabled
183
- ? { opacity: "0.4", "pointer-events": "none", cursor: "not-allowed" }
184
- : undefined,
185
- );
186
184
  </script>
187
185
 
188
186
  {#if visible}
189
- <Stack {...props.stack} {onclick} {style} testId={`button-${action.type}`} />
187
+ {@const isDisabled = isWorkflowButtonInputGated(
188
+ action.type,
189
+ props.require_valid_inputs,
190
+ inputSatisfied.current,
191
+ )}
192
+ <Stack
193
+ {...props.stack}
194
+ {onclick}
195
+ style={isDisabled
196
+ ? { opacity: "0.4", "pointer-events": "none", cursor: "not-allowed" }
197
+ : undefined}
198
+ testId={`button-${action.type}`}
199
+ />
190
200
  {/if}
@@ -2,6 +2,10 @@
2
2
  import { setLocalizationContext } from "../../stores/localization";
3
3
  import { setPackageInfoContext } from "../../stores/packageInfo";
4
4
  import { setPaywallContext } from "../../stores/paywall";
5
+ import {
6
+ createInputValidationContext,
7
+ setInputValidationContext,
8
+ } from "../../stores/inputValidation";
5
9
  import { setVariablesContext } from "../../stores/variables";
6
10
  import { writable, readable } from "svelte/store";
7
11
  import ButtonNode from "./ButtonNode.svelte";
@@ -9,9 +13,23 @@
9
13
 
10
14
  interface Props extends ButtonProps {
11
15
  hideBackButtons?: boolean;
16
+ /** When set, seeds the shared input validation context (default: satisfied). */
17
+ inputsSatisfied?: boolean;
18
+ onSatisfactionChange?: (satisfied: boolean) => void;
12
19
  }
13
20
 
14
- const { hideBackButtons = false, ...buttonProps }: Props = $props();
21
+ const {
22
+ hideBackButtons = false,
23
+ inputsSatisfied = true,
24
+ onSatisfactionChange,
25
+ ...buttonProps
26
+ }: Props = $props();
27
+
28
+ const validationContext = createInputValidationContext(inputsSatisfied);
29
+ setInputValidationContext(validationContext);
30
+ if (onSatisfactionChange) {
31
+ validationContext.isSatisfied.subscribe(onSatisfactionChange);
32
+ }
15
33
 
16
34
  setLocalizationContext(() => ({
17
35
  defaultLocale: "en_US",
@@ -1,6 +1,9 @@
1
1
  import type { ButtonProps } from "../../types/components/button";
2
2
  interface Props extends ButtonProps {
3
3
  hideBackButtons?: boolean;
4
+ /** When set, seeds the shared input validation context (default: satisfied). */
5
+ inputsSatisfied?: boolean;
6
+ onSatisfactionChange?: (satisfied: boolean) => void;
4
7
  }
5
8
  declare const ButtonNodeTestWrapper: import("svelte").Component<Props, {}, "">;
6
9
  type ButtonNodeTestWrapper = ReturnType<typeof ButtonNodeTestWrapper>;
@@ -0,0 +1,3 @@
1
+ import type { Action } from "../../types/components/button";
2
+ /** Whether a workflow / complete_workflow button should be disabled. */
3
+ export declare function isWorkflowButtonInputGated(actionType: Action["type"], requireValidInputs: boolean | undefined, inputsSatisfied: boolean): boolean;
@@ -0,0 +1,6 @@
1
+ /** Whether a workflow / complete_workflow button should be disabled. */
2
+ export function isWorkflowButtonInputGated(actionType, requireValidInputs, inputsSatisfied) {
3
+ return (requireValidInputs !== false &&
4
+ (actionType === "workflow" || actionType === "complete_workflow") &&
5
+ !inputsSatisfied);
6
+ }
@@ -22,7 +22,12 @@
22
22
  } from "../../utils/base-utils";
23
23
  import { getActiveStateProps } from "../../utils/style-utils";
24
24
  import { onMount } from "svelte";
25
- import { clamp, getTranslation, mapPageAlignment } from "./carousel-utils";
25
+ import {
26
+ clamp,
27
+ getSwipeTargetPage,
28
+ getTranslation,
29
+ mapPageAlignment,
30
+ } from "./carousel-utils";
26
31
  import CarouselPage from "./CarouselPage.svelte";
27
32
  import PageControl from "./PageControl.svelte";
28
33
 
@@ -220,19 +225,35 @@
220
225
 
221
226
  let startTranslation = $state(0);
222
227
 
228
+ let lastClientX = 0;
229
+
223
230
  function onpointerdown(event: PointerEvent) {
224
231
  const target = event.currentTarget as HTMLElement;
225
232
  target.setPointerCapture(event.pointerId);
226
233
  startTranslation = getTranslation(slider);
227
234
  gestureStartPage = currentPage;
235
+ lastClientX = event.clientX;
228
236
  }
229
237
 
230
238
  function onpointerup(event: PointerEvent) {
231
239
  const target = event.currentTarget as HTMLElement;
232
- target.releasePointerCapture(event.pointerId);
240
+ if (target.hasPointerCapture(event.pointerId)) {
241
+ target.releasePointerCapture(event.pointerId);
242
+ }
233
243
 
234
244
  const translation = getTranslation(slider);
235
- const page = getPageFromTranslation(translation);
245
+
246
+ // Advance at most one page from where the gesture started, based on how
247
+ // far the finger travelled relative to a page's width (see
248
+ // getSwipeTargetPage).
249
+ const page = getSwipeTargetPage({
250
+ startPage: gestureStartPage ?? getPageFromTranslation(translation),
251
+ dragDistance: translation - startTranslation,
252
+ pageWidth,
253
+ pageCount: pages.length,
254
+ loop: carouselPagingLoop,
255
+ });
256
+
236
257
  const destinationPage = normalizePageIndex(page);
237
258
 
238
259
  if (gestureStartPage !== null && gestureStartPage !== destinationPage) {
@@ -249,6 +270,7 @@
249
270
  }
250
271
 
251
272
  gestureStartPage = null;
273
+ currentPage = destinationPage;
252
274
  startTransition(false, 500);
253
275
  moveTo(page);
254
276
  }
@@ -259,11 +281,17 @@
259
281
  return;
260
282
  }
261
283
 
284
+ // Track movement from clientX instead of event.movementX: movementX is 0
285
+ // for touch-derived pointer events in Safari and device-pixel scaled in
286
+ // some Chrome configurations.
287
+ const deltaX = event.clientX - lastClientX;
288
+ lastClientX = event.clientX;
289
+
262
290
  const [min, max] = carouselPagingLoop
263
291
  ? [startTranslation - carouselWidth, startTranslation + carouselWidth]
264
292
  : [-(pages.length - 1) * pageWidth, 0];
265
293
  const translation = getTranslation(slider);
266
- const newTranslation = clamp(translation + event.movementX, min, max);
294
+ const newTranslation = clamp(translation + deltaX, min, max);
267
295
 
268
296
  const page = getPageFromTranslation(newTranslation);
269
297
  currentPage = (page + pages.length) % pages.length;
@@ -1,4 +1,35 @@
1
1
  import type { VerticalAlignment } from "../../types/alignment";
2
2
  export declare function mapPageAlignment(alignment: VerticalAlignment): string;
3
3
  export declare function clamp(value: number, min: number, max: number): number;
4
+ /**
5
+ * Advance one page when the drag passed this fraction of a page's width,
6
+ * matching the iOS SDK carousel. A shorter drag snaps back to the page the
7
+ * gesture started on.
8
+ */
9
+ export declare const PAGE_ADVANCE_THRESHOLD = 0.2;
10
+ /**
11
+ * Decides which page a swipe settles on when the pointer is released.
12
+ *
13
+ * Distance-based (not velocity-based), mirroring the iOS SDK's `handleDragEnd`:
14
+ * a drag past `PAGE_ADVANCE_THRESHOLD` of a page's width advances exactly one
15
+ * page in the swipe direction regardless of speed; a shorter drag stays put.
16
+ * Non-loop carousels clamp at their first and last page; loop carousels may
17
+ * return an index just outside `[0, pageCount)` so the caller can animate into
18
+ * a clone before normalizing.
19
+ *
20
+ * @param startPage Page the gesture started on.
21
+ * @param dragDistance Signed change in slider translation (negative = finger
22
+ * moved left / next page).
23
+ * @param pageWidth Width of a single page in px.
24
+ * @param pageCount Total number of real pages.
25
+ * @param loop Whether the carousel loops.
26
+ */
27
+ export declare function getSwipeTargetPage({ startPage, dragDistance, pageWidth, pageCount, loop, advanceThreshold, }: {
28
+ startPage: number;
29
+ dragDistance: number;
30
+ pageWidth: number;
31
+ pageCount: number;
32
+ loop: boolean;
33
+ advanceThreshold?: number;
34
+ }): number;
4
35
  export declare function getTranslation(element: HTMLElement | null): number;
@@ -11,6 +11,42 @@ export function mapPageAlignment(alignment) {
11
11
  export function clamp(value, min, max) {
12
12
  return Math.max(min, Math.min(value, max));
13
13
  }
14
+ /**
15
+ * Advance one page when the drag passed this fraction of a page's width,
16
+ * matching the iOS SDK carousel. A shorter drag snaps back to the page the
17
+ * gesture started on.
18
+ */
19
+ export const PAGE_ADVANCE_THRESHOLD = 0.2;
20
+ /**
21
+ * Decides which page a swipe settles on when the pointer is released.
22
+ *
23
+ * Distance-based (not velocity-based), mirroring the iOS SDK's `handleDragEnd`:
24
+ * a drag past `PAGE_ADVANCE_THRESHOLD` of a page's width advances exactly one
25
+ * page in the swipe direction regardless of speed; a shorter drag stays put.
26
+ * Non-loop carousels clamp at their first and last page; loop carousels may
27
+ * return an index just outside `[0, pageCount)` so the caller can animate into
28
+ * a clone before normalizing.
29
+ *
30
+ * @param startPage Page the gesture started on.
31
+ * @param dragDistance Signed change in slider translation (negative = finger
32
+ * moved left / next page).
33
+ * @param pageWidth Width of a single page in px.
34
+ * @param pageCount Total number of real pages.
35
+ * @param loop Whether the carousel loops.
36
+ */
37
+ export function getSwipeTargetPage({ startPage, dragDistance, pageWidth, pageCount, loop, advanceThreshold = PAGE_ADVANCE_THRESHOLD, }) {
38
+ const threshold = pageWidth * advanceThreshold;
39
+ let page = startPage;
40
+ if (dragDistance < -threshold) {
41
+ // Finger moved left, content shifts left: next page.
42
+ page += 1;
43
+ }
44
+ else if (dragDistance > threshold) {
45
+ // Finger moved right: previous page.
46
+ page -= 1;
47
+ }
48
+ return loop ? page : clamp(page, 0, pageCount - 1);
49
+ }
14
50
  export function getTranslation(element) {
15
51
  if (element === null) {
16
52
  return 0;
@@ -73,6 +73,12 @@
73
73
 
74
74
  function commitValue(input: HTMLInputElement) {
75
75
  const value = input.value.trim();
76
+ if (keyboard_type === "password") {
77
+ // Sensitive value: never emit through the persisted onInputChanged path.
78
+ // Hosts keep it in memory only (no storage, analytics, or attributes).
79
+ onSensitiveInputChanged?.(field_id, value);
80
+ return;
81
+ }
76
82
  if (reserved_attribute) {
77
83
  if (!onReservedAttributeChanged) {
78
84
  console.error("onReservedAttributeChanged is not set");
@@ -118,6 +124,7 @@
118
124
  const {
119
125
  uiConfig,
120
126
  onInputChanged,
127
+ onSensitiveInputChanged,
121
128
  onReservedAttributeChanged,
122
129
  selectedPackageId,
123
130
  } = getPaywallContext();
@@ -196,6 +203,8 @@
196
203
  return "number";
197
204
  case "email":
198
205
  return "email";
206
+ case "password":
207
+ return "password";
199
208
  case "tel":
200
209
  return "tel";
201
210
  case "text":
@@ -205,6 +214,12 @@
205
214
  }
206
215
  });
207
216
 
217
+ // `password` is not a valid `inputmode`; masking is handled by `type` above, so
218
+ // fall back to the default text keyboard for it.
219
+ const inputMode = $derived.by((): HTMLInputAttributes["inputmode"] =>
220
+ keyboard_type === "password" ? "text" : keyboard_type,
221
+ );
222
+
208
223
  const isVisible = $derived(
209
224
  evaluateVisibilityConditions(
210
225
  {
@@ -232,7 +247,7 @@
232
247
  {type}
233
248
  {placeholder}
234
249
  {required}
235
- inputmode={keyboard_type}
250
+ inputmode={inputMode}
236
251
  autocapitalize={capitalize}
237
252
  style={inputStyle}
238
253
  {oninput}
@@ -10,15 +10,28 @@
10
10
  import { setVariablesContext } from "../../stores/variables";
11
11
  import { readable, writable } from "svelte/store";
12
12
  import InputText from "./InputText.svelte";
13
- import type { InputTextProps } from "../../types/components/input-text";
13
+ import type {
14
+ InputTextProps,
15
+ ReservedAttribute,
16
+ } from "../../types/components/input-text";
14
17
 
15
18
  interface Props extends InputTextProps {
16
19
  onInputChanged?: (fieldId: string, value: string) => void;
20
+ onSensitiveInputChanged?: (fieldId: string, value: string) => void;
21
+ onReservedAttributeChanged?: (
22
+ reservedAttribute: ReservedAttribute,
23
+ value: string,
24
+ ) => void;
17
25
  onSatisfactionChange?: (satisfied: boolean) => void;
18
26
  }
19
27
 
20
- const { onInputChanged, onSatisfactionChange, ...inputTextProps }: Props =
21
- $props();
28
+ const {
29
+ onInputChanged,
30
+ onSensitiveInputChanged,
31
+ onReservedAttributeChanged,
32
+ onSatisfactionChange,
33
+ ...inputTextProps
34
+ }: Props = $props();
22
35
 
23
36
  const validationContext = createInputValidationContext();
24
37
  setInputValidationContext(validationContext);
@@ -43,6 +56,8 @@
43
56
  emitComponentInteraction: () => {},
44
57
  onButtonAction: () => {},
45
58
  onInputChanged,
59
+ onSensitiveInputChanged,
60
+ onReservedAttributeChanged,
46
61
  uiConfig: { app: { fonts: {} } } as never,
47
62
  hideBackButtons: false,
48
63
  });
@@ -1,6 +1,8 @@
1
- import type { InputTextProps } from "../../types/components/input-text";
1
+ import type { InputTextProps, ReservedAttribute } from "../../types/components/input-text";
2
2
  interface Props extends InputTextProps {
3
3
  onInputChanged?: (fieldId: string, value: string) => void;
4
+ onSensitiveInputChanged?: (fieldId: string, value: string) => void;
5
+ onReservedAttributeChanged?: (reservedAttribute: ReservedAttribute, value: string) => void;
4
6
  onSatisfactionChange?: (satisfied: boolean) => void;
5
7
  }
6
8
  declare const InputTextTestWrapper: import("svelte").Component<Props, {}, "">;
@@ -93,6 +93,11 @@
93
93
  value: string,
94
94
  actionId?: string,
95
95
  ) => void;
96
+ /**
97
+ * Called for sensitive inputs (keyboard_type: "password") instead of
98
+ * onInputChanged, so hosts can keep the value in memory only.
99
+ */
100
+ onSensitiveInputChanged?: (fieldId: string, value: string) => void;
96
101
  onReservedAttributeChanged?: (
97
102
  reservedAttribute: ReservedAttribute,
98
103
  value: string,
@@ -141,6 +146,7 @@
141
146
  uiConfig,
142
147
  walletButtonRender,
143
148
  onInputChanged,
149
+ onSensitiveInputChanged,
144
150
  onReservedAttributeChanged,
145
151
  hideBackButtons = false,
146
152
  maxContentWidth,
@@ -309,6 +315,7 @@
309
315
  onNavigateToUrl: onNavigateToUrlClicked,
310
316
  onButtonAction,
311
317
  onInputChanged,
318
+ onSensitiveInputChanged,
312
319
  onReservedAttributeChanged,
313
320
  walletButtonRender,
314
321
  uiConfig,
@@ -44,6 +44,11 @@ interface Props {
44
44
  hideBackButtons?: boolean;
45
45
  walletButtonRender?: WalletButtonRender;
46
46
  onInputChanged?: (fieldId: string, value: string, actionId?: string) => void;
47
+ /**
48
+ * Called for sensitive inputs (keyboard_type: "password") instead of
49
+ * onInputChanged, so hosts can keep the value in memory only.
50
+ */
51
+ onSensitiveInputChanged?: (fieldId: string, value: string) => void;
47
52
  onReservedAttributeChanged?: (reservedAttribute: ReservedAttribute, value: string) => void;
48
53
  maxContentWidth?: string;
49
54
  initialInputSelections?: InitialInputSelections;
@@ -32,6 +32,11 @@
32
32
  value: string,
33
33
  actionId?: string,
34
34
  ) => void;
35
+ /**
36
+ * Called for sensitive inputs (keyboard_type: "password") instead of
37
+ * onInputChanged, so hosts can keep the value in memory only.
38
+ */
39
+ onSensitiveInputChanged?: (fieldId: string, value: string) => void;
35
40
  onReservedAttributeChanged?: (
36
41
  reservedAttribute: ReservedAttribute,
37
42
  value: string,
@@ -61,6 +66,7 @@
61
66
  infoPerPackage,
62
67
  initialInputSelections = {},
63
68
  onInputChanged,
69
+ onSensitiveInputChanged,
64
70
  onReservedAttributeChanged,
65
71
  onCompleteWorkflowNavigate,
66
72
  onNavigateToUrlClicked,
@@ -100,6 +106,7 @@
100
106
  {onComponentInteraction}
101
107
  {onPurchaseClicked}
102
108
  {onInputChanged}
109
+ {onSensitiveInputChanged}
103
110
  {onReservedAttributeChanged}
104
111
  {walletButtonRender}
105
112
  {safeAreaFallbackColor}
@@ -23,6 +23,11 @@ interface Props {
23
23
  infoPerPackage?: Record<string, PackageInfo>;
24
24
  initialInputSelections?: InitialInputSelections;
25
25
  onInputChanged?: (fieldId: string, value: string, actionId?: string) => void;
26
+ /**
27
+ * Called for sensitive inputs (keyboard_type: "password") instead of
28
+ * onInputChanged, so hosts can keep the value in memory only.
29
+ */
30
+ onSensitiveInputChanged?: (fieldId: string, value: string) => void;
26
31
  onReservedAttributeChanged?: (reservedAttribute: ReservedAttribute, value: string) => void;
27
32
  onCompleteWorkflowNavigate?: (args: CompleteWorkflowNavigateArgs) => void | Promise<void>;
28
33
  onNavigateToUrlClicked?: (url: string) => void;
@@ -49,6 +49,11 @@
49
49
  value: string,
50
50
  actionId?: string,
51
51
  ) => void;
52
+ /**
53
+ * Called for sensitive inputs (keyboard_type: "password") instead of
54
+ * onInputChanged, so hosts can keep the value in memory only.
55
+ */
56
+ onSensitiveInputChanged?: (fieldId: string, value: string) => void;
52
57
  onReservedAttributeChanged?: (
53
58
  reservedAttribute: ReservedAttribute,
54
59
  value: string,
@@ -84,6 +89,7 @@
84
89
  onPurchaseClicked,
85
90
  onActionTriggered,
86
91
  onInputChanged,
92
+ onSensitiveInputChanged,
87
93
  onReservedAttributeChanged,
88
94
  onCompleteWorkflowNavigate,
89
95
  onNavigateToUrlClicked,
@@ -303,6 +309,7 @@
303
309
  {onPurchaseClicked}
304
310
  onActionTriggered={handleActionTriggered}
305
311
  {onInputChanged}
312
+ {onSensitiveInputChanged}
306
313
  {onReservedAttributeChanged}
307
314
  {onCompleteWorkflowNavigate}
308
315
  {onNavigateToUrlClicked}
@@ -21,6 +21,11 @@ interface Props {
21
21
  onPurchaseClicked?: (packageId: string, actionId: string) => void | Promise<void>;
22
22
  onActionTriggered?: (actionId: string) => void;
23
23
  onInputChanged?: (fieldId: string, value: string, actionId?: string) => void;
24
+ /**
25
+ * Called for sensitive inputs (keyboard_type: "password") instead of
26
+ * onInputChanged, so hosts can keep the value in memory only.
27
+ */
28
+ onSensitiveInputChanged?: (fieldId: string, value: string) => void;
24
29
  onReservedAttributeChanged?: (reservedAttribute: ReservedAttribute, value: string) => void;
25
30
  onCompleteWorkflowNavigate?: (args: CompleteWorkflowNavigateArgs) => void | Promise<void>;
26
31
  onNavigateToUrlClicked?: (url: string) => void;
@@ -8,7 +8,7 @@ interface InputValidationContext {
8
8
  isSatisfied: Readable<boolean>;
9
9
  updateSatisfaction: (satisfied: boolean) => void;
10
10
  }
11
- export declare function createInputValidationContext(): InputValidationContext;
11
+ export declare function createInputValidationContext(initiallySatisfied?: boolean): InputValidationContext;
12
12
  export declare function setInputValidationContext(context: InputValidationContext): void;
13
13
  export declare function getInputValidationContext(): InputValidationContext | undefined;
14
14
  export declare function setInitialInputSelectionsContext(selections: InitialInputSelections): void;
@@ -2,8 +2,8 @@ import { getContext, setContext } from "svelte";
2
2
  import { writable } from "svelte/store";
3
3
  const key = Symbol("inputValidation");
4
4
  const initialSelectionsKey = Symbol("initialInputSelections");
5
- export function createInputValidationContext() {
6
- const isSatisfied = writable(true);
5
+ export function createInputValidationContext(initiallySatisfied = true) {
6
+ const isSatisfied = writable(initiallySatisfied);
7
7
  const updateSatisfaction = (satisfied) => {
8
8
  isSatisfied.set(satisfied);
9
9
  };
@@ -22,6 +22,12 @@ type PaywallContext = Readonly<{
22
22
  emitComponentInteraction: (data: ComponentInteractionData) => void;
23
23
  onNavigateToUrl?: (url: string) => void;
24
24
  onInputChanged?: (fieldId: string, value: string, actionId?: string) => void;
25
+ /**
26
+ * Called for sensitive inputs (keyboard_type: "password") instead of
27
+ * onInputChanged, so hosts can keep the value in memory only and never
28
+ * persist, log, or forward it.
29
+ */
30
+ onSensitiveInputChanged?: (fieldId: string, value: string) => void;
25
31
  onReservedAttributeChanged?: (reservedAttribute: ReservedAttribute, value: string) => void;
26
32
  walletButtonRender?: WalletButtonRender;
27
33
  onWalletButtonReady?: (walletButtonAvailable?: boolean) => void;
@@ -63,5 +63,11 @@ export interface ButtonProps extends BaseComponent {
63
63
  stack: StackProps;
64
64
  transition?: null;
65
65
  overrides?: Overrides<ButtonProps>;
66
+ /**
67
+ * When false, workflow / complete_workflow buttons stay enabled even if
68
+ * required inputs on the screen are empty. Hosts (e.g. rc-workflows) may set
69
+ * this at runtime for auth cross-links; it is not persisted in paywall JSON.
70
+ */
71
+ require_valid_inputs?: boolean;
66
72
  }
67
73
  export {};
@@ -4,7 +4,7 @@ import type { BaseComponent } from "../base";
4
4
  import type { ColorGradientScheme } from "../colors";
5
5
  import type { Overrides } from "../overrides";
6
6
  export type InputTextCapitalizeType = "none" | "sentences" | "words" | "characters";
7
- export type InputTextKeyboardType = "decimal" | "email" | "numeric" | "tel" | "text" | "url";
7
+ export type InputTextKeyboardType = "decimal" | "email" | "numeric" | "password" | "tel" | "text" | "url";
8
8
  export type ReservedAttribute = "$email" | "$displayName" | "$phoneNumber";
9
9
  export interface InputTextProps extends BaseComponent {
10
10
  type: "input_text";
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@revenuecat/purchases-ui-js",
3
3
  "description": "Web components for Paywalls. Powered by RevenueCat",
4
4
  "private": false,
5
- "version": "4.8.7",
5
+ "version": "4.8.9",
6
6
  "author": {
7
7
  "name": "RevenueCat, Inc."
8
8
  },