@revenuecat/purchases-ui-js 4.8.20 → 4.8.21

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.
@@ -1,12 +1,16 @@
1
1
  <script module lang="ts">
2
2
  import Image from "./Image.svelte";
3
3
  import { componentDecorator } from "../../stories/component-decorator";
4
+ import { localizationDecorator } from "../../stories/localization-decorator";
4
5
  import { defineMeta } from "@storybook/addon-svelte-csf";
5
6
 
6
7
  const { Story } = defineMeta({
7
8
  title: "Components/Image",
8
9
  component: Image,
9
- decorators: [componentDecorator()],
10
+ decorators: [
11
+ componentDecorator(),
12
+ localizationDecorator({ defaultLocale: "en_US", localizations: {} }),
13
+ ],
10
14
  args: {
11
15
  type: "image",
12
16
  id: "image",
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { getColorModeContext } from "../../stores/color-mode";
3
3
  import { useConditionContext } from "../../stores/condition-context.svelte";
4
+ import { getLocalizationContext } from "../../stores/localization";
4
5
  import type { ImageProps } from "../../types/components/image";
5
6
  import {
6
7
  css,
@@ -12,15 +13,19 @@
12
13
  } from "../../utils/base-utils";
13
14
  import { resolveOverrideProperties } from "../../utils/style-utils";
14
15
  import ClipPath from "./ClipPath.svelte";
16
+ import { resolveImageSource } from "./image-utils";
15
17
  import { viewBoxSize } from "./view-box-size";
16
18
  import Overlay from "./Overlay.svelte";
17
19
 
18
20
  const props: ImageProps = $props();
19
21
 
20
22
  const buildConditionContext = useConditionContext({ state: true });
23
+ const { getLocalizedImage } = getLocalizationContext();
24
+
25
+ const conditionContext = $derived(buildConditionContext());
26
+
21
27
  const {
22
28
  id,
23
- source,
24
29
  size,
25
30
  mask_shape,
26
31
  fit_mode,
@@ -33,10 +38,14 @@
33
38
  } = $derived.by(() => {
34
39
  return {
35
40
  ...props,
36
- ...resolveOverrideProperties(buildConditionContext(), props.overrides),
41
+ ...resolveOverrideProperties(conditionContext, props.overrides),
37
42
  };
38
43
  });
39
44
 
45
+ const source = $derived(
46
+ resolveImageSource(props, conditionContext, getLocalizedImage),
47
+ );
48
+
40
49
  const getColorMode = getColorModeContext();
41
50
  const colorMode = $derived(getColorMode());
42
51
  const image = $derived(mapColorMode(colorMode, source));
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { readable, writable } from "svelte/store";
3
3
  import { setColorModeContext } from "../../stores/color-mode";
4
+ import { setLocalizationContext } from "../../stores/localization";
4
5
  import { setPaywallContext } from "../../stores/paywall";
5
6
  import Image from "./Image.svelte";
6
7
  import type { ImageProps } from "../../types/components/image";
@@ -8,6 +9,10 @@
8
9
  const props: ImageProps = $props();
9
10
 
10
11
  setColorModeContext();
12
+ setLocalizationContext(() => ({
13
+ defaultLocale: "en_US",
14
+ localizations: {},
15
+ }));
11
16
  setPaywallContext({
12
17
  selectedPackageId: writable(undefined),
13
18
  variablesPerPackage: readable(undefined),
@@ -0,0 +1,9 @@
1
+ import type { ImageProps } from "../../types/components/image";
2
+ import type { ImageInfo } from "../../types/media";
3
+ import { type ConditionContext } from "../../utils/style-utils";
4
+ import type { GetLocalizedImage } from "../../stores/localization";
5
+ /**
6
+ * Resolves the ImageInfo to render for an image component, preferring
7
+ * a matching override but defaulting to the image source.
8
+ */
9
+ export declare function resolveImageSource(props: ImageProps, context: ConditionContext, getLocalizedImage: GetLocalizedImage): ImageInfo;
@@ -0,0 +1,20 @@
1
+ import { filterMatchingOverrides, } from "../../utils/style-utils";
2
+ /**
3
+ * Resolves the ImageInfo to render for an image component, preferring
4
+ * a matching override but defaulting to the image source.
5
+ */
6
+ export function resolveImageSource(props, context, getLocalizedImage) {
7
+ const resolveLocalizedSource = (source, overrideSourceLid) => overrideSourceLid
8
+ ? (getLocalizedImage(overrideSourceLid) ?? source)
9
+ : source;
10
+ let resolvedOverrideImage;
11
+ for (const override of filterMatchingOverrides(context, props.overrides)) {
12
+ const image = resolveLocalizedSource(override.properties.source, override.properties.override_source_lid);
13
+ if (image) {
14
+ resolvedOverrideImage = image;
15
+ }
16
+ }
17
+ return (resolvedOverrideImage ??
18
+ resolveLocalizedSource(props.source, props.override_source_lid) ??
19
+ props.source);
20
+ }
@@ -1,7 +1,10 @@
1
1
  import type { Localizations } from "../types/localization";
2
+ import type { ImageInfo } from "../types/media";
2
3
  type GetLocalizedString = (id?: string) => string | undefined;
4
+ export type GetLocalizedImage = (id?: string) => ImageInfo | undefined;
3
5
  interface LocalizationContext {
4
6
  getLocalizedString: GetLocalizedString;
7
+ getLocalizedImage: GetLocalizedImage;
5
8
  }
6
9
  export interface LocalizationContextProps {
7
10
  defaultLocale: string;
@@ -1,9 +1,9 @@
1
1
  import { getContext, setContext } from "svelte";
2
2
  const key = Symbol("localization");
3
3
  export function setLocalizationContext(props) {
4
- const getLocalizedString = (id) => {
4
+ const getLocalizedValue = (id) => {
5
5
  if (!id) {
6
- return "";
6
+ return undefined;
7
7
  }
8
8
  const { selectedLocale, defaultLocale, localizations } = props();
9
9
  const locale = selectedLocale ?? defaultLocale;
@@ -11,8 +11,20 @@ export function setLocalizationContext(props) {
11
11
  const fallback = localizations[defaultLocale]?.[id];
12
12
  return selected ?? fallback;
13
13
  };
14
+ const getLocalizedString = (id) => {
15
+ if (!id) {
16
+ return "";
17
+ }
18
+ const value = getLocalizedValue(id);
19
+ return typeof value === "string" ? value : undefined;
20
+ };
21
+ const getLocalizedImage = (id) => {
22
+ const value = getLocalizedValue(id);
23
+ return typeof value === "object" ? value : undefined;
24
+ };
14
25
  const context = {
15
26
  getLocalizedString,
27
+ getLocalizedImage,
16
28
  };
17
29
  setContext(key, context);
18
30
  return context;
@@ -1,5 +1,5 @@
1
1
  import type { ImageInfo } from "./media";
2
2
  export type LocalizationValue = string | ImageInfo;
3
- type Localization = Partial<Record<string, string>>;
3
+ type Localization = Partial<Record<string, LocalizationValue>>;
4
4
  export type Localizations = Partial<Record<string, Localization>>;
5
5
  export {};
@@ -29,6 +29,11 @@ export type ConditionContext = {
29
29
  focus?: boolean;
30
30
  error?: boolean;
31
31
  };
32
+ /**
33
+ * Returns the overrides (in array order) whose conditions all match (AND) the
34
+ * given context, without merging their properties.
35
+ */
36
+ export declare const filterMatchingOverrides: <T extends Component>(context: ConditionContext, overrides?: Overrides<T>) => Overrides<T>;
32
37
  /**
33
38
  * Merges the properties of every override whose conditions all match (AND),
34
39
  * applying them in override-array order (later overrides overwrite earlier ones).
@@ -115,6 +115,15 @@ export function collectPackageIdsInDisplayOrder({ stack, sticky_footer, }) {
115
115
  }
116
116
  return ids;
117
117
  }
118
+ /**
119
+ * Returns the overrides (in array order) whose conditions all match (AND) the
120
+ * given context, without merging their properties.
121
+ */
122
+ export const filterMatchingOverrides = (context, overrides) => {
123
+ if (!overrides)
124
+ return [];
125
+ return overrides.filter((override) => override.conditions.every((c) => conditionMatches(c, context)));
126
+ };
118
127
  /**
119
128
  * Merges the properties of every override whose conditions all match (AND),
120
129
  * applying them in override-array order (later overrides overwrite earlier ones).
@@ -128,20 +137,16 @@ export function collectPackageIdsInDisplayOrder({ stack, sticky_footer, }) {
128
137
  * (`actualProps.visible !== false`).
129
138
  */
130
139
  export const resolveOverrideProperties = (context, overrides) => {
131
- if (!overrides)
132
- return {};
133
140
  let merged = {};
134
- for (const override of overrides) {
135
- if (override.conditions.every((c) => conditionMatches(c, context))) {
136
- const properties = { ...override.properties };
137
- const canReveal = override.conditions.some((c) => c.type === RuleKey.StateCondition);
138
- // visible: null carries no intent and must not clobber an earlier false.
139
- if (properties.visible === null ||
140
- (properties.visible === true && !canReveal)) {
141
- delete properties.visible;
142
- }
143
- merged = { ...merged, ...properties };
141
+ for (const override of filterMatchingOverrides(context, overrides)) {
142
+ const properties = { ...override.properties };
143
+ const canReveal = override.conditions.some((c) => c.type === RuleKey.StateCondition);
144
+ // visible: null carries no intent and must not clobber an earlier false.
145
+ if (properties.visible === null ||
146
+ (properties.visible === true && !canReveal)) {
147
+ delete properties.visible;
144
148
  }
149
+ merged = { ...merged, ...properties };
145
150
  }
146
151
  return merged;
147
152
  };
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.20",
5
+ "version": "4.8.21",
6
6
  "author": {
7
7
  "name": "RevenueCat, Inc."
8
8
  },