@revenuecat/purchases-ui-js 4.12.4 → 4.12.5

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.
@@ -7,6 +7,7 @@
7
7
  mapBorder,
8
8
  mapBorderRadius,
9
9
  mapColor,
10
+ mapFillBounds,
10
11
  mapShadow,
11
12
  mapSize,
12
13
  mapSpacing,
@@ -38,6 +39,7 @@
38
39
  css({
39
40
  width: mapSize(size.width),
40
41
  height: mapSize(size.height),
42
+ ...mapFillBounds(size),
41
43
  margin: mapSpacing(margin),
42
44
  padding: mapSpacing(padding),
43
45
  "background-color": mapColor(colorMode, background?.color),
@@ -4,9 +4,11 @@
4
4
  import { getLocalizationContext } from "../../stores/localization";
5
5
  import type { ImageProps } from "../../types/components/image";
6
6
  import {
7
+ clampFillSize,
7
8
  css,
8
9
  mapColor,
9
10
  mapColorMode,
11
+ mapFillBounds,
10
12
  mapFitMode,
11
13
  mapSize,
12
14
  mapSpacing,
@@ -86,7 +88,10 @@
86
88
  if (size.height.type === "fixed") {
87
89
  return size.height.value;
88
90
  }
89
- return Math.round(wrapperWidth * (imageSize.height / imageSize.width));
91
+ const aspectHeight = Math.round(
92
+ wrapperWidth * (imageSize.height / imageSize.width),
93
+ );
94
+ return clampFillSize(aspectHeight, size.height);
90
95
  });
91
96
 
92
97
  const style = $derived(
@@ -102,6 +107,7 @@
102
107
  ...(size.width.type === "fixed"
103
108
  ? {}
104
109
  : { "min-width": 0, "min-height": 0 }),
110
+ ...mapFillBounds(size),
105
111
  }),
106
112
  );
107
113
 
@@ -16,6 +16,7 @@
16
16
  css,
17
17
  mapBorder,
18
18
  mapBorderRadius,
19
+ mapFillBounds,
19
20
  mapShadow,
20
21
  mapSize,
21
22
  mapSpacing,
@@ -221,6 +222,7 @@
221
222
  "flex-direction": "column",
222
223
  width: mapSize(size.width),
223
224
  ...(heightOnContainer ? { height: mapSize(size.height) } : {}),
225
+ ...mapFillBounds(size),
224
226
  margin: mapSpacing(margin),
225
227
  }),
226
228
  );
@@ -5,7 +5,9 @@
5
5
  import { setLoaderContext } from "../../stores/loader";
6
6
  import type { LoaderAnimationProps } from "../../types/components/loader-animation";
7
7
  import { defineMeta } from "@storybook/addon-svelte-csf";
8
+ import type { ComponentProps } from "svelte";
8
9
  import { writable } from "svelte/store";
10
+ import { expect } from "storybook/test";
9
11
  import type { DecoratorFunction, Renderer } from "storybook/internal/csf";
10
12
 
11
13
  /**
@@ -64,6 +66,12 @@
64
66
  });
65
67
  </script>
66
68
 
69
+ {#snippet linearClampTemplate(props: ComponentProps<typeof LoaderAnimation>)}
70
+ <div class="linear-clamp-stage">
71
+ <LoaderAnimation {...props} />
72
+ </div>
73
+ {/snippet}
74
+
67
75
  <Story
68
76
  name="Circular — In progress"
69
77
  decorators={[staticLoaderDecorator(0.7)]}
@@ -94,3 +102,39 @@
94
102
  decorators={[staticLoaderDecorator(1)]}
95
103
  args={linearArgs}
96
104
  />
105
+
106
+ <Story
107
+ name="Linear — Clamped fill height"
108
+ decorators={[staticLoaderDecorator(0.5)]}
109
+ template={linearClampTemplate}
110
+ play={async ({ canvasElement }) => {
111
+ const loader = canvasElement.querySelector(
112
+ ".rc-loader-animation--linear",
113
+ );
114
+ await expect(loader?.getBoundingClientRect().height).toBe(48);
115
+ }}
116
+ args={{
117
+ ...linearArgs,
118
+ track: {
119
+ ...linearArgs.track,
120
+ size: {
121
+ width: { type: "fill", min: 200, max: 320 },
122
+ height: { type: "fill", min: 24, max: 48 },
123
+ },
124
+ },
125
+ fill: {
126
+ ...linearArgs.fill,
127
+ size: {
128
+ width: { type: "fill", min: 200, max: 320 },
129
+ height: { type: "fill", min: 24, max: 48 },
130
+ },
131
+ },
132
+ }}
133
+ />
134
+
135
+ <style>
136
+ .linear-clamp-stage {
137
+ width: 400px;
138
+ height: 80px;
139
+ }
140
+ </style>
@@ -20,6 +20,7 @@
20
20
  mapColor,
21
21
  mapColorInfo,
22
22
  mapColorMode,
23
+ mapFillBounds,
23
24
  mapShadow,
24
25
  mapSize,
25
26
  mapSpacing,
@@ -71,8 +72,12 @@
71
72
  const pillWidth = (layer: LinearAnimationLayer) =>
72
73
  layer.size.width.type === "fill" ? "100%" : mapSize(layer.size.width);
73
74
 
75
+ const pillHeight = (layer: LinearAnimationLayer) =>
76
+ layer.size.height.type === "fill" ? "100%" : mapSize(layer.size.height);
77
+
74
78
  const pillLayerStyle = (layer: LinearAnimationLayer) => ({
75
- height: mapSize(layer.size.height),
79
+ height: pillHeight(layer),
80
+ ...mapFillBounds({ height: layer.size.height }),
76
81
  "border-radius": mapBorderRadius(layer.shape ?? null),
77
82
  background: mapColor(colorMode, layer.color),
78
83
  ...mapBorder(colorMode, layer.border ?? null),
@@ -119,6 +124,7 @@
119
124
  "aspect-ratio": "1",
120
125
  "flex-shrink": "0",
121
126
  width: diameterWidth(actualProps.track),
127
+ ...mapFillBounds({ width: actualProps.track.diameter }),
122
128
  margin: mapSpacing(actualProps.margin ?? null),
123
129
  padding: mapSpacing(actualProps.padding ?? null),
124
130
  })}
@@ -169,6 +175,11 @@
169
175
  display: "block",
170
176
  "flex-shrink": "0",
171
177
  width: pillWidth(actualProps.track),
178
+ ...(actualProps.track.size.height.type === "fill"
179
+ ? { height: "100%" }
180
+ : {}),
181
+ ...mapFillBounds({ width: actualProps.track.size.width }),
182
+ ...mapFillBounds({ height: actualProps.track.size.height }),
172
183
  margin: mapSpacing(actualProps.margin ?? null),
173
184
  padding: mapSpacing(actualProps.padding ?? null),
174
185
  })}
@@ -1,10 +1,8 @@
1
1
  <script lang="ts">
2
2
  import {
3
3
  mapDimension,
4
- mapFillHeight,
5
- mapFixedHeightBounds,
6
- mapFixedWidthBounds,
7
4
  mapLayerAlignment,
5
+ mapStackSize,
8
6
  } from "../stack/stack-utils";
9
7
  import { setStackDimensionContext } from "../stack/stack-dimension-context";
10
8
  import BackgroundVideoSurface from "../paywall/BackgroundVideoSurface.svelte";
@@ -21,7 +19,6 @@
21
19
  mapBorder,
22
20
  mapBorderRadius,
23
21
  mapShadow,
24
- mapSize,
25
22
  mapSpacing,
26
23
  px,
27
24
  } from "../../utils/base-utils";
@@ -59,18 +56,11 @@
59
56
  });
60
57
 
61
58
  const borderRadiusCss = $derived(mapBorderRadius(actualProps.shape));
62
- const heightFill = $derived(actualProps.size.height.type === "fill");
63
-
64
59
  const containerStyle = $derived(
65
60
  css({
66
61
  display: "flex",
67
62
  position: "relative",
68
- width: mapSize(actualProps.size.width),
69
- ...(heightFill
70
- ? mapFillHeight(getParentStackDimension?.())
71
- : { height: mapSize(actualProps.size.height) }),
72
- ...mapFixedWidthBounds(actualProps.size.width),
73
- ...mapFixedHeightBounds(actualProps.size.height),
63
+ ...mapStackSize(actualProps.size, getParentStackDimension?.()),
74
64
  ...mapDimension(actualProps.dimension),
75
65
  gap: px(actualProps.spacing),
76
66
  margin: mapSpacing(actualProps.margin),
@@ -4,7 +4,12 @@
4
4
  import { useConditionContext } from "../../stores/condition-context.svelte";
5
5
  import { getRequiredLoaderContext } from "../../stores/loader";
6
6
  import type { LoaderProgressComponentsProps } from "../../types/components/loader-progress-components";
7
- import { css, mapSize, mapSpacing } from "../../utils/base-utils";
7
+ import {
8
+ css,
9
+ mapFillBounds,
10
+ mapSize,
11
+ mapSpacing,
12
+ } from "../../utils/base-utils";
8
13
  import { resolveOverrideProperties } from "../../utils/style-utils";
9
14
 
10
15
  const props: LoaderProgressComponentsProps = $props();
@@ -49,6 +54,7 @@
49
54
  "justify-content": "center",
50
55
  width: mapSize(actualProps.size.width),
51
56
  height: mapSize(actualProps.size.height),
57
+ ...mapFillBounds(actualProps.size),
52
58
  padding: mapSpacing(actualProps.padding),
53
59
  margin: mapSpacing(actualProps.margin),
54
60
  }),
@@ -12,7 +12,7 @@
12
12
  rcVideoBackgroundHostClass,
13
13
  videoBackgroundHostStyles,
14
14
  } from "../../utils/video-background-host";
15
- import { css, mapSize } from "../../utils/base-utils";
15
+ import { css, mapFillBounds, mapSize } from "../../utils/base-utils";
16
16
  import { resolveOverrideProperties } from "../../utils/style-utils";
17
17
  import { onMount } from "svelte";
18
18
  import Stack from "../stack/Stack.svelte";
@@ -48,6 +48,7 @@
48
48
  const sheetStyle = $derived(
49
49
  css({
50
50
  height: mapSize(size.height),
51
+ ...mapFillBounds({ height: size.height }),
51
52
  ...videoBackgroundHostStyles({
52
53
  hasVideoBg,
53
54
  clipOverflow: true,
@@ -4,6 +4,7 @@
4
4
  css,
5
5
  mapBorder,
6
6
  mapBorderRadius,
7
+ mapFillBounds,
7
8
  mapShadow,
8
9
  mapSize,
9
10
  mapSpacing,
@@ -51,6 +52,7 @@
51
52
  position: "relative",
52
53
  width: mapSize(size.width),
53
54
  height: mapSize(size.height),
55
+ ...mapFillBounds(size),
54
56
  ...mapDimension(dimension),
55
57
  gap: px(spacing),
56
58
  margin: mapSpacing({
@@ -10,6 +10,8 @@
10
10
  import { STORY_BACKGROUND_VIDEO_FILL } from "../../stories/video-background-story-fixture";
11
11
  import { DEFAULT_TEXT_COLOR } from "../../utils/constants";
12
12
  import { defineMeta } from "@storybook/addon-svelte-csf";
13
+ import type { ComponentProps } from "svelte";
14
+ import { expect } from "storybook/test";
13
15
 
14
16
  const defaultLocale = Object.keys(localizations)[0];
15
17
 
@@ -198,6 +200,23 @@
198
200
  });
199
201
  </script>
200
202
 
203
+ {#snippet fillClampTemplate(props: ComponentProps<typeof Stack>)}
204
+ <div class="fill-clamp-layouts">
205
+ <Stack {...props} testId="vertical-fill-clamp" />
206
+ <Stack
207
+ {...props}
208
+ id="horizontal-fill-clamp"
209
+ name="Horizontal fill clamp"
210
+ testId="horizontal-fill-clamp"
211
+ dimension={{
212
+ type: "horizontal",
213
+ alignment: "center",
214
+ distribution: "start",
215
+ }}
216
+ />
217
+ </div>
218
+ {/snippet}
219
+
201
220
  <Story name="Vertical Stack" />
202
221
 
203
222
  <Story
@@ -211,6 +230,56 @@
211
230
  }}
212
231
  />
213
232
 
233
+ <Story
234
+ name="Fill min/max clamps"
235
+ template={fillClampTemplate}
236
+ play={async ({ canvasElement }) => {
237
+ for (const testId of ["vertical-fill-clamp", "horizontal-fill-clamp"]) {
238
+ const child = canvasElement.querySelector(
239
+ `[data-testid="${testId}"] > .stack`,
240
+ );
241
+ const bounds = child?.getBoundingClientRect();
242
+ await expect(bounds?.width).toBe(220);
243
+ await expect(bounds?.height).toBe(140);
244
+ }
245
+ }}
246
+ args={{
247
+ size: {
248
+ width: { type: "fixed", value: 360 },
249
+ height: { type: "fixed", value: 240 },
250
+ },
251
+ components: [
252
+ {
253
+ type: "stack",
254
+ id: "clamped-child",
255
+ name: "Clamped child",
256
+ components: [],
257
+ size: {
258
+ width: { type: "fill", min: 120, max: 220 },
259
+ height: { type: "fill", min: 80, max: 140 },
260
+ },
261
+ dimension: {
262
+ type: "vertical",
263
+ alignment: "center",
264
+ distribution: "center",
265
+ },
266
+ spacing: 0,
267
+ margin: { top: 0, trailing: 0, bottom: 0, leading: 0 },
268
+ padding: { top: 0, trailing: 0, bottom: 0, leading: 0 },
269
+ background: {
270
+ type: "color",
271
+ value: { light: { type: "hex", value: "#6C63FF" } },
272
+ },
273
+ background_color: null,
274
+ border: null,
275
+ shape: null,
276
+ shadow: null,
277
+ badge: null,
278
+ },
279
+ ],
280
+ }}
281
+ />
282
+
214
283
  <Story
215
284
  name="Surface — video background"
216
285
  parameters={{
@@ -284,6 +353,14 @@
284
353
  }}
285
354
  />
286
355
 
356
+ <style>
357
+ .fill-clamp-layouts {
358
+ display: flex;
359
+ flex-wrap: wrap;
360
+ gap: 24px;
361
+ }
362
+ </style>
363
+
287
364
  <Story
288
365
  name="Z Layer"
289
366
  args={{
@@ -17,7 +17,6 @@
17
17
  mapBorder,
18
18
  mapBorderRadius,
19
19
  mapShadow,
20
- mapSize,
21
20
  mapSpacing,
22
21
  px,
23
22
  type CSS,
@@ -27,10 +26,8 @@
27
26
  import {
28
27
  mapBadge,
29
28
  mapDimension,
30
- mapFillHeight,
31
- mapFixedHeightBounds,
32
- mapFixedWidthBounds,
33
29
  mapLayerAlignment,
30
+ mapStackSize,
34
31
  } from "./stack-utils";
35
32
  import { setStackDimensionContext } from "./stack-dimension-context";
36
33
 
@@ -85,20 +82,11 @@
85
82
  });
86
83
  const borderRadiusCss = $derived(mapBorderRadius(shape));
87
84
 
88
- const heightFill = $derived(size.height.type === "fill");
89
-
90
85
  const stackStyle = $derived(
91
86
  css({
92
87
  display: "flex",
93
88
  position: "relative",
94
- width: mapSize(size.width),
95
- // Fill height: definite height + growth in columns; stretch in rows
96
- // (see mapFillHeight). Lets nested fill children (e.g. web_view) resolve.
97
- ...(heightFill
98
- ? mapFillHeight(getParentStackDimension?.())
99
- : { height: mapSize(size.height) }),
100
- ...mapFixedWidthBounds(size.width),
101
- ...mapFixedHeightBounds(size.height),
89
+ ...mapStackSize(size, getParentStackDimension?.()),
102
90
  ...mapDimension(dimension),
103
91
  gap: px(spacing),
104
92
  margin: mapSpacing(margin),
@@ -1,10 +1,12 @@
1
- import type { Size } from "../../types";
1
+ import type { Size, SizeType } from "../../types";
2
2
  import type { Dimension, ZAlignment } from "../../types/alignment";
3
3
  import type { Badge } from "../../types/components/stack";
4
4
  import { type CSS } from "../../utils/base-utils";
5
5
  export declare function mapFixedWidthBounds(width: Size): CSS;
6
6
  export declare function mapFixedHeightBounds(height: Size): CSS;
7
7
  export declare function mapFillHeight(parentDimension?: Dimension["type"]): CSS;
8
+ /** Fill clamps must follow the flex min-size reset so authored bounds win. */
9
+ export declare function mapStackSize(size: SizeType, parentDimension?: Dimension["type"]): CSS;
8
10
  export declare function mapDimension(dimension: Dimension): Record<string, string>;
9
11
  type JustifyAlign = {
10
12
  justifyContent: string;
@@ -1,4 +1,4 @@
1
- import { px } from "../../utils/base-utils";
1
+ import { mapFillBounds, mapSize, px } from "../../utils/base-utils";
2
2
  const ALIGNMENT_MAP = Object.freeze({
3
3
  leading: "flex-start",
4
4
  top: "flex-start",
@@ -50,6 +50,18 @@ export function mapFillHeight(parentDimension) {
50
50
  : {}),
51
51
  };
52
52
  }
53
+ /** Fill clamps must follow the flex min-size reset so authored bounds win. */
54
+ export function mapStackSize(size, parentDimension) {
55
+ return {
56
+ width: mapSize(size.width),
57
+ ...(size.height.type === "fill"
58
+ ? mapFillHeight(parentDimension)
59
+ : { height: mapSize(size.height) }),
60
+ ...mapFixedWidthBounds(size.width),
61
+ ...mapFixedHeightBounds(size.height),
62
+ ...mapFillBounds(size),
63
+ };
64
+ }
53
65
  export function mapDimension(dimension) {
54
66
  if (dimension.type === "zlayer") {
55
67
  return {};
@@ -15,6 +15,7 @@
15
15
  css,
16
16
  mapBorder,
17
17
  mapBorderRadius,
18
+ mapFillBounds,
18
19
  mapShadow,
19
20
  mapSize,
20
21
  mapSpacing,
@@ -64,6 +65,7 @@
64
65
  position: "relative",
65
66
  width: mapSize(size.width),
66
67
  height: mapSize(size.height),
68
+ ...mapFillBounds(size),
67
69
  margin: mapSpacing(margin),
68
70
  padding: mapSpacing(padding),
69
71
  ...videoBackgroundHostStyles({
@@ -1,6 +1,6 @@
1
1
  import { FontSizes, FontWeights, TextAlignments, } from "../../types";
2
2
  import { mapBackground } from "../../utils/background-utils";
3
- import { css, mapColorInfo, mapColorMode, mapSize, mapSpacing, } from "../../utils/base-utils";
3
+ import { css, mapColorInfo, mapColorMode, mapFillBounds, mapSize, mapSpacing, } from "../../utils/base-utils";
4
4
  import { DEFAULT_TEXT_COLOR } from "../../utils/constants";
5
5
  import { getScopedFontFamily, isFontRCFMManaged } from "../../utils/font-utils";
6
6
  export const defaultColor = {
@@ -81,6 +81,7 @@ export function getTextWrapperInlineStyles(colorMode, _restProps, size, backgrou
81
81
  position: "relative",
82
82
  width: mapSize(size.width),
83
83
  height: mapSize(size.height),
84
+ ...mapFillBounds(size),
84
85
  ...mapBackground(colorMode, background_color, null),
85
86
  });
86
87
  }
@@ -1,6 +1,8 @@
1
1
  import type { TimelineProps } from "../../types/components/timeline";
2
2
  import type { ColorMode } from "../../types";
3
3
  export declare function getTimelineInlineStyles(props: TimelineProps): {
4
+ padding: string;
5
+ margin: string;
4
6
  display: string;
5
7
  "grid-template-columns": string;
6
8
  "column-gap": string;
@@ -9,8 +11,6 @@ export declare function getTimelineInlineStyles(props: TimelineProps): {
9
11
  flex: string;
10
12
  width: string;
11
13
  height: string;
12
- padding: string;
13
- margin: string;
14
14
  };
15
15
  export declare function getTimelineItemContentInlineStyles(_colorMode: ColorMode, timelineSpacing: {
16
16
  text_spacing: number;
@@ -1,4 +1,4 @@
1
- import { mapSize, mapSpacing } from "../../utils/base-utils";
1
+ import { mapFillBounds, mapSize, mapSpacing } from "../../utils/base-utils";
2
2
  export function getTimelineInlineStyles(props) {
3
3
  return {
4
4
  display: "grid",
@@ -9,6 +9,7 @@ export function getTimelineInlineStyles(props) {
9
9
  flex: "0 1 auto",
10
10
  width: mapSize(props.size.width),
11
11
  height: mapSize(props.size.height),
12
+ ...mapFillBounds(props.size),
12
13
  padding: mapSpacing(props.padding),
13
14
  margin: mapSpacing(props.margin),
14
15
  };
@@ -5,10 +5,12 @@
5
5
  import type { VideoProps } from "../../types/components/video";
6
6
  import type { VideoFiles, ImageFiles } from "../../types/media";
7
7
  import {
8
+ clampFillSize,
8
9
  css,
9
10
  mapBorderRadius,
10
11
  mapColor,
11
12
  mapColorMode,
13
+ mapFillBounds,
12
14
  mapFitMode,
13
15
  mapSize,
14
16
  mapSpacing,
@@ -145,9 +147,12 @@
145
147
  const h = videoSize.height;
146
148
  if (!Number.isFinite(w) || !Number.isFinite(h) || w <= 0 || h <= 0) {
147
149
  // Avoid 0-height box and NaN before metadata (`NaN <= 0` is false); 16:9 placeholder until dimensions resolve.
148
- return wrapperWidth > 0 ? Math.round(wrapperWidth * (9 / 16)) : 200;
150
+ const placeholderHeight =
151
+ wrapperWidth > 0 ? Math.round(wrapperWidth * (9 / 16)) : 200;
152
+ return clampFillSize(placeholderHeight, size.height);
149
153
  }
150
- return Math.round(wrapperWidth * (h / w));
154
+ const aspectHeight = Math.round(wrapperWidth * (h / w));
155
+ return clampFillSize(aspectHeight, size.height);
151
156
  });
152
157
 
153
158
  const style = $derived(
@@ -161,6 +166,7 @@
161
166
  ...(size.width.type === "fixed"
162
167
  ? {}
163
168
  : { "min-width": 0, "min-height": 0 }),
169
+ ...mapFillBounds(size),
164
170
  }),
165
171
  );
166
172
 
@@ -4,7 +4,13 @@
4
4
  import { getColorModeContext } from "../../stores/color-mode";
5
5
  import { getPaywallContext } from "../../stores/paywall";
6
6
  import type { WalletButtonProps } from "../../types/components/wallet-button";
7
- import { css, mapSize, mapSpacing, px } from "../../utils/base-utils";
7
+ import {
8
+ css,
9
+ mapFillBounds,
10
+ mapSize,
11
+ mapSpacing,
12
+ px,
13
+ } from "../../utils/base-utils";
8
14
  import { DEFAULT_SPACING } from "../../utils/constants";
9
15
  import { onDestroy } from "svelte";
10
16
 
@@ -62,6 +68,7 @@
62
68
  position: "relative",
63
69
  width: mapSize(size.width),
64
70
  height: mapSize(size.height),
71
+ ...mapFillBounds(size),
65
72
  ...mapDimension(dimension),
66
73
  gap: px(spacing),
67
74
  margin: mapSpacing({
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { onMount } from "svelte";
3
3
  import type { WebViewProps } from "../../types/components/web-view";
4
- import { css, mapSize, px } from "../../utils/base-utils";
4
+ import { css, mapFillBounds, mapSize, px } from "../../utils/base-utils";
5
5
  import { getStackDimensionContext } from "../stack/stack-dimension-context";
6
6
  import { mapFillHeight } from "../stack/stack-utils";
7
7
  import { isSameOriginAsHost, sanitizeWebViewUrl } from "./web-view-utils";
@@ -126,6 +126,7 @@
126
126
  width.type === "fixed" || widthFit || heightFit ? 0 : 1,
127
127
  }),
128
128
  "line-height": 0,
129
+ ...mapFillBounds(props.size),
129
130
  // No transition: the iframe is height:100% of this box, so animating the
130
131
  // resize would flash the iframe's own scrollbar mid-change.
131
132
  });
package/dist/types.d.ts CHANGED
@@ -57,6 +57,9 @@ type FitSize = {
57
57
  type FillSize = {
58
58
  type: "fill";
59
59
  value?: null;
60
+ /** Optional px clamps on the filled axis. */
61
+ min?: number | null;
62
+ max?: number | null;
60
63
  };
61
64
  export type Size = FixedSize | RelativeSize | FitSize | FillSize;
62
65
  export type SizeType = {
@@ -1,4 +1,4 @@
1
- import type { BorderType, CircleShape, ColorMode, ConcaveShape, ConvexShape, PillShape, RectangleShape, ShadowType, Size, Spacing } from "../types";
1
+ import type { BorderType, CircleShape, ColorMode, ConcaveShape, ConvexShape, PillShape, RectangleShape, ShadowType, Size, SizeType, Spacing } from "../types";
2
2
  import type { ColorGradientInfo, ColorGradientScheme, ColorScheme } from "../types/colors";
3
3
  import type { FitMode } from "../types/media";
4
4
  export type CSS = Record<string, string | number>;
@@ -9,6 +9,10 @@ export declare function mapColorMode<T>(colorMode: ColorMode, value: {
9
9
  dark?: T | null;
10
10
  }): T;
11
11
  export declare function mapSize(size: Size): string;
12
+ /** Spread after flex min-size resets so authored fill bounds win. */
13
+ export declare function mapFillBounds(size: Partial<SizeType>): CSS;
14
+ /** Apply fill bounds to a layout-derived size before the browser measures it. */
15
+ export declare function clampFillSize(value: number, size: Size): number;
12
16
  export declare function mapSpacing(spacing?: Spacing | null): string;
13
17
  export declare function mapColorInfo(info: ColorGradientInfo): string;
14
18
  export declare function mapColor(colorMode: ColorMode, scheme: ColorScheme | ColorGradientScheme | null | undefined): string;
@@ -33,6 +33,30 @@ export function mapSize(size) {
33
33
  }
34
34
  }
35
35
  }
36
+ function mapAxisFillBounds(size, axis) {
37
+ if (size.type !== "fill") {
38
+ return {};
39
+ }
40
+ return {
41
+ ...(size.min != null ? { [`min-${axis}`]: px(size.min) } : {}),
42
+ ...(size.max != null ? { [`max-${axis}`]: px(size.max) } : {}),
43
+ };
44
+ }
45
+ /** Spread after flex min-size resets so authored fill bounds win. */
46
+ export function mapFillBounds(size) {
47
+ return {
48
+ ...(size.width ? mapAxisFillBounds(size.width, "width") : {}),
49
+ ...(size.height ? mapAxisFillBounds(size.height, "height") : {}),
50
+ };
51
+ }
52
+ /** Apply fill bounds to a layout-derived size before the browser measures it. */
53
+ export function clampFillSize(value, size) {
54
+ if (size.type !== "fill") {
55
+ return value;
56
+ }
57
+ const maxClamped = size.max == null ? value : Math.min(value, size.max);
58
+ return size.min == null ? maxClamped : Math.max(maxClamped, size.min);
59
+ }
36
60
  export function mapSpacing(spacing) {
37
61
  const { top = 0, bottom = 0, leading = 0, trailing = 0, } = spacing ?? DEFAULT_SPACING;
38
62
  return mapTuple(top, trailing, bottom, leading);
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.12.4",
5
+ "version": "4.12.5",
6
6
  "author": {
7
7
  "name": "RevenueCat, Inc."
8
8
  },