@12-apps/ui 6.44.1 → 6.44.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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  LazyImage
3
- } from "../../../../chunk-QWFAEUFV.js";
3
+ } from "../../../../chunk-EO6LCGGQ.js";
4
4
  import "../../../../chunk-2TSGRQ2J.js";
5
5
  import "../../../../chunk-724ZONOL.js";
6
6
  import "../../../../chunk-BEGTLELZ.js";
@@ -8,6 +8,12 @@ import "../../../../chunk-E2QIKJX2.js";
8
8
  import "../../../../chunk-EMPVMZAB.js";
9
9
  import "../../../../chunk-7WDPC46N.js";
10
10
  import "../../../../chunk-TYJR4GOF.js";
11
+ import "../../../../chunk-M47ZOUGF.js";
12
+ import "../../../../chunk-7FDU3FIN.js";
13
+ import "../../../../chunk-EPEJMTCQ.js";
14
+ import "../../../../chunk-GDN2LUTZ.js";
15
+ import "../../../../chunk-O6MJWHU5.js";
16
+ import "../../../../chunk-26X4CZVR.js";
11
17
  import "../../../../chunk-FQ6UW7IY.js";
12
18
  import "../../../../chunk-6JAIFIPW.js";
13
19
  import "../../../../chunk-N3GAFIXG.js";
@@ -91,7 +91,7 @@ import "../../../chunk-S5UY5U73.js";
91
91
  import "../../../chunk-5ZFO7ROE.js";
92
92
  import {
93
93
  LazyImage
94
- } from "../../../chunk-QWFAEUFV.js";
94
+ } from "../../../chunk-EO6LCGGQ.js";
95
95
  import "../../../chunk-2TSGRQ2J.js";
96
96
  import "../../../chunk-724ZONOL.js";
97
97
  import "../../../chunk-UM35GGCS.js";
@@ -505,7 +505,7 @@ import "../../chunk-S5UY5U73.js";
505
505
  import "../../chunk-5ZFO7ROE.js";
506
506
  import {
507
507
  LazyImage
508
- } from "../../chunk-QWFAEUFV.js";
508
+ } from "../../chunk-EO6LCGGQ.js";
509
509
  import "../../chunk-2TSGRQ2J.js";
510
510
  import "../../chunk-724ZONOL.js";
511
511
  import "../../chunk-UM35GGCS.js";
@@ -6,6 +6,9 @@ import {
6
6
  import {
7
7
  Skeleton
8
8
  } from "./chunk-BEGTLELZ.js";
9
+ import {
10
+ fieldHeight
11
+ } from "./chunk-M47ZOUGF.js";
9
12
  import {
10
13
  sheen
11
14
  } from "./chunk-FQ6UW7IY.js";
@@ -61,14 +64,41 @@ var sxLength = (theme, value) => isFraction(value) ? `${value * 100}%` : styleLe
61
64
  var innerLength = (theme, value) => isFraction(value) ? "100%" : styleLength(theme, value);
62
65
  var orDefault = (value, fallback) => value === void 0 || value === "" ? fallback : value;
63
66
  var emptyToUnset = (value) => value === "" ? void 0 : value;
67
+ var isRelativeLength = (value) => isFraction(value) || typeof value === "string" && value.trim().endsWith("%");
68
+ var placeholderAxis = (theme, width, height, resolve) => {
69
+ const w = emptyToUnset(width);
70
+ const h = height === "auto" ? void 0 : emptyToUnset(height);
71
+ if (w === void 0 && h === void 0) {
72
+ const field = fieldHeight(theme);
73
+ return { width: field, height: field };
74
+ }
75
+ if (h === void 0) {
76
+ if (isRelativeLength(w)) return { width: resolve(theme, w), height: "auto", aspectRatio: "1 / 1" };
77
+ const resolved = resolve(theme, w);
78
+ return { width: resolved, height: resolved };
79
+ }
80
+ if (w === void 0) {
81
+ if (isRelativeLength(h)) return { width: "auto", height: resolve(theme, h), aspectRatio: "1 / 1" };
82
+ const resolved = resolve(theme, h);
83
+ return { width: resolved, height: resolved };
84
+ }
85
+ return { width: resolve(theme, w), height: resolve(theme, h) };
86
+ };
87
+ var containerAxisFor = (theme, props, hasError) => {
88
+ const { width, height, fallback } = props;
89
+ const needsPlaceholderSize = hasError && Boolean(fallback) && typeof fallback !== "string";
90
+ return needsPlaceholderSize ? placeholderAxis(theme, width, height, sxLength) : { width: sxLength(theme, orDefault(width, "auto")), height: sxLength(theme, orDefault(height, "auto")) };
91
+ };
64
92
  var SkeletonIndicator = ({ props }) => {
65
93
  const theme = useTheme();
94
+ const { width, height, aspectRatio } = placeholderAxis(theme, props.width, props.height, innerLength);
66
95
  return /* @__PURE__ */ jsx(
67
96
  Skeleton,
68
97
  {
69
98
  variant: props.skeletonProps.variant ?? "rectangular",
70
- width: innerLength(theme, orDefault(props.width, "100%")),
71
- height: innerLength(theme, orDefault(props.height, "auto")),
99
+ width,
100
+ height,
101
+ style: aspectRatio ? { aspectRatio } : void 0,
72
102
  animation: props.skeletonProps.animation ?? "pulse",
73
103
  intensity: props.skeletonProps.intensity,
74
104
  borderRadius: styleLength(theme, props.borderRadius),
@@ -148,17 +178,9 @@ var ErrorFallback = ({ props, metrics }) => {
148
178
  FallbackContainer,
149
179
  {
150
180
  sx: {
151
- // FUT-2774 #4 (see `emptyToUnset`). Does NOT change the confirmed
152
- // height collapse — `ImageContainer`'s own height was already `'auto'`
153
- // either way; see `LazyImage.test.stories.tsx` for that separate,
154
- // unresolved defect. `orDefault(height, 'auto')` is safe to share with
155
- // `SkeletonIndicator` (unlike width's `'100%'`, deliberately NOT
156
- // shared — see `emptyToUnset`): a PERCENTAGE height against an
157
- // auto-height containing block computes to `auto` by spec regardless,
158
- // so whether `sx.height` is omitted (falling through to
159
- // `FallbackContainer`'s own `height: '100%'`) or set to the literal
160
- // `'auto'` here, an unset-height container gives the same result
161
- // either way — nothing for this fallback to protect against.
181
+ // FUT-2774 #4 (see `emptyToUnset`); the collapse is fixed by
182
+ // `LazyImage` giving `ImageContainer` ITSELF a size now
183
+ // (`containerAxisFor`) — this `position: absolute` box just fills it.
162
184
  width: innerLength(theme, emptyToUnset(width)),
163
185
  height: innerLength(theme, orDefault(height, "auto")),
164
186
  borderRadius: styleLength(theme, borderRadius)
@@ -197,14 +219,16 @@ var LazyImage = React.memo(function LazyImage2(rawProps) {
197
219
  objectPosition: props.objectPosition,
198
220
  borderRadius: styleLength(theme, borderRadius)
199
221
  };
222
+ const containerAxis = containerAxisFor(theme, props, state.hasError);
200
223
  return /* @__PURE__ */ jsxs(
201
224
  ImageContainer,
202
225
  {
203
226
  ref: containerRef,
204
227
  className: props.className,
205
228
  sx: {
206
- width: sxLength(theme, orDefault(width, "auto")),
207
- height: sxLength(theme, orDefault(height, "auto")),
229
+ width: containerAxis.width,
230
+ height: containerAxis.height,
231
+ aspectRatio: containerAxis.aspectRatio,
208
232
  // The same px length the image gets (`metrics`): in `sx` a bare number
209
233
  // would be a multiple of `shape.borderRadius`, and this box clips (FUT-2656).
210
234
  borderRadius: metrics.borderRadius
@@ -250,4 +274,4 @@ LazyImage.displayName = "LazyImage";
250
274
  export {
251
275
  LazyImage
252
276
  };
253
- //# sourceMappingURL=chunk-QWFAEUFV.js.map
277
+ //# sourceMappingURL=chunk-EO6LCGGQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/data-display/LazyImage/LazyImage.tsx"],"sourcesContent":["import Box from '@mui/material/Box/index.js';\nimport CircularProgress from '@mui/material/CircularProgress/index.js';\nimport { styled, useTheme, type Theme } from '@mui/material/styles/index.js';\nimport React from 'react';\nimport { Skeleton } from '../../layout/Skeleton';\nimport type { ResolvedLazyImageProps } from './LazyImage.hooks';\nimport { imgPassThrough, resolveLazyImageProps, useLazyImage } from './LazyImage.hooks';\nimport type { LazyImageProps } from './LazyImage.types';\nimport { sheen } from '../../../tokens/ink';\nimport { rem } from '../../../tokens/relative';\nimport { fieldHeight } from '../../../tokens/field-height';\n\nconst ImageContainer = styled(Box)(() => ({\n position: 'relative',\n display: 'inline-block',\n overflow: 'hidden',\n}));\n\nconst StyledImage = styled('img')<{\n fadeIn?: boolean;\n fadeInDuration?: number;\n isLoaded?: boolean;\n}>(({ fadeIn, fadeInDuration = 300, isLoaded }) => ({\n display: 'block',\n maxWidth: '100%',\n height: 'auto',\n opacity: fadeIn ? (isLoaded ? 1 : 0) : 1,\n transition: fadeIn ? `opacity ${fadeInDuration}ms ease-in-out` : 'none',\n}));\n\nconst SpinnerOverlay = styled(Box)(({ theme }) => ({\n position: 'absolute',\n top: '50%',\n left: '50%',\n transform: 'translate(-50%, -50%)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: sheen(theme, 0.8),\n borderRadius: '50%',\n padding: theme.spacing(1),\n}));\n\nconst FallbackContainer = styled(Box)(({ theme }) => ({\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: theme.palette.action.disabledBackground,\n color: theme.palette.text.secondary,\n width: '100%',\n height: '100%',\n position: 'absolute',\n top: 0,\n left: 0,\n}));\n\ntype Length = number | string | undefined;\n\n/** A length prop as React `style` and MUI's `Skeleton` read it: a number is design px. */\nconst styleLength = (theme: Theme, value: Length): string | undefined =>\n typeof value === 'number' ? rem(theme, value) : value;\n\n/**\n * A width or height above 0 and up to 1 is a fraction of the container's parent, as in `sx`.\n * A negative number is no fraction: it stays a (negative, so invalid) length.\n */\nconst isFraction = (value: Length): value is number =>\n typeof value === 'number' && value > 0 && value <= 1;\n\n/**\n * A length prop as `sx` reads it on a width or height: a number of 1 or less is\n * a fraction of the parent, and that stays so; any other number is design px.\n */\nconst sxLength = (theme: Theme, value: Length): string | undefined =>\n isFraction(value) ? `${value * 100}%` : styleLength(theme, value);\n\n/**\n * A width or height for what is drawn inside the container. The container alone\n * takes a fraction, so everything in it fills it: a percentage here is measured\n * against the container and would stack (FUT-2666). Otherwise the same as `styleLength`.\n */\nconst innerLength = (theme: Theme, value: Length): string | undefined =>\n isFraction(value) ? '100%' : styleLength(theme, value);\n\n/**\n * A width or height with its default. Only an unset or empty size takes it: `0`\n * is a size, which `||` read as unset (FUT-2669), and `sx` reads `''` as `0%`.\n */\nconst orDefault = (value: Length, fallback: number | string): number | string =>\n value === undefined || value === '' ? fallback : value;\n\n/**\n * A width/height as `metrics` and the `ReactNode` `ErrorFallback` read it\n * (FUT-2774 #4): an explicit empty string is normalized to \"as if unset\"\n * rather than mapped to a component-wide default. Unlike `SkeletonIndicator`\n * (a content-less box with nothing else to size by, hence its own deliberate\n * `'100%'`/`'auto'` fallback), these draw the real image or a `ReactNode`\n * fallback — both have their own intrinsic size, so leaving an unset width\n * unset lets it size naturally (`brand-link.tsx`'s logo relies on exactly\n * this: an unset width capped by its own `sx.maxWidth`).\n */\nconst emptyToUnset = (value: Length): Length => (value === '' ? undefined : value);\n\n/** A length, already resolved to CSS — {@link innerLength} or {@link sxLength}. */\ntype LengthResolver = (theme: Theme, value: Length) => string | undefined;\n\n/**\n * Whether a length is a PERCENTAGE (a numeric {@link isFraction} or a `%`\n * string) — copying it onto the OTHER axis re-creates this ticket's own\n * collapse either way (width%/height% measure different boxes; Chromium-confirmed).\n */\nconst isRelativeLength = (value: Length): boolean =>\n isFraction(value) || (typeof value === 'string' && value.trim().endsWith('%'));\n\n/** A resolved axis pair; `aspectRatio` is set when the OTHER axis comes from CSS, not a length. */\ntype AxisPair = { width: string | undefined; height: string | undefined; aspectRatio?: string };\n\n/**\n * The unset-axis DEFAULT for a PLACEHOLDER only (skeleton, `ReactNode`\n * fallback) — never the real image/`metrics` (an unset width sizes it\n * naturally; `brand-link.tsx`'s logo relies on that). Decision (FUT-2805): an\n * unset axis borrows the set one (a square); neither set, both take the\n * theme's field height; both set stays unchanged. `resolve` is `innerLength`\n * (skeleton) or `sxLength` (the container). A RELATIVE single axis\n * ({@link isRelativeLength}) cannot borrow the same way: it keeps its value\n * and squares up via `aspectRatio: '1 / 1'`, the other axis explicitly\n * `'auto'` (never `undefined` — `Skeleton`'s own default, a fixed `40`,\n * would silently win otherwise).\n */\nconst placeholderAxis = (theme: Theme, width: Length, height: Length, resolve: LengthResolver): AxisPair => {\n const w = emptyToUnset(width);\n // `height` always arrives defaulted to the literal `'auto'` when unset\n // (`LAZY_IMAGE_DEFAULTS`) — indistinguishable from an explicit `\"auto\"`,\n // and no distinction is needed: both mean \"no fixed height requested\".\n const h = height === 'auto' ? undefined : emptyToUnset(height);\n\n if (w === undefined && h === undefined) {\n const field = fieldHeight(theme);\n return { width: field, height: field };\n }\n if (h === undefined) {\n if (isRelativeLength(w)) return { width: resolve(theme, w), height: 'auto', aspectRatio: '1 / 1' };\n const resolved = resolve(theme, w);\n return { width: resolved, height: resolved };\n }\n if (w === undefined) {\n if (isRelativeLength(h)) return { width: 'auto', height: resolve(theme, h), aspectRatio: '1 / 1' };\n const resolved = resolve(theme, h);\n return { width: resolved, height: resolved };\n }\n return { width: resolve(theme, w), height: resolve(theme, h) };\n};\n\n/**\n * `ImageContainer`'s own axis: the ordinary `orDefault(...,'auto')` in every\n * state, except a ReactNode `fallback` while showing — the one case where\n * the container is the only thing that CAN fix its own collapse\n * (`FallbackContainer` is `position: absolute`, never feeding shrink-to-fit).\n */\nconst containerAxisFor = (theme: Theme, props: ResolvedLazyImageProps, hasError: boolean): AxisPair => {\n const { width, height, fallback } = props;\n const needsPlaceholderSize = hasError && Boolean(fallback) && typeof fallback !== 'string';\n return needsPlaceholderSize\n ? placeholderAxis(theme, width, height, sxLength)\n : { width: sxLength(theme, orDefault(width, 'auto')), height: sxLength(theme, orDefault(height, 'auto')) };\n};\n\n/** The box the image occupies, as CSS, shared by the real image and every stand-in for it. */\ninterface BoxMetrics {\n width?: string;\n height?: string;\n objectFit: NonNullable<LazyImageProps['objectFit']>;\n objectPosition: string;\n borderRadius?: string;\n}\n\ninterface IndicatorProps {\n props: ResolvedLazyImageProps;\n metrics: BoxMetrics;\n}\n\nconst SkeletonIndicator: React.FC<IndicatorProps> = ({ props }) => {\n const theme = useTheme();\n // FUT-2805: replaces the old literal `'100%'`/`'auto'` fallbacks, which\n // collapsed to 0 in a shrink-to-fit `ImageContainer` with nothing to\n // resolve against.\n const { width, height, aspectRatio } = placeholderAxis(theme, props.width, props.height, innerLength);\n return (\n <Skeleton\n // FUT-2774 #5: `skeletonProps.variant` was typed but never read — the\n // variant was hard-coded regardless of what a caller passed.\n variant={props.skeletonProps.variant ?? 'rectangular'}\n width={width}\n height={height}\n style={aspectRatio ? { aspectRatio } : undefined}\n // FUT-2774 #5: `||` reads a caller's `false` the same as \"unset\", so\n // `skeletonProps.animation={false}` could never turn the animation off.\n animation={props.skeletonProps.animation ?? 'pulse'}\n intensity={props.skeletonProps.intensity}\n borderRadius={styleLength(theme, props.borderRadius)}\n data-testid={`${props['data-testid']}-skeleton`}\n />\n );\n};\n\nconst SpinnerIndicator: React.FC<IndicatorProps> = ({ props }) => {\n const theme = useTheme();\n const { spinnerProps } = props;\n\n return (\n <SpinnerOverlay>\n <CircularProgress\n // FUT-2774 #5: `||` read `spinnerProps.size`/`thickness` of `0` the\n // same as unset — the same class of bug FUT-2669 fixed for\n // `width`/`height`, missed here.\n size={styleLength(theme, spinnerProps.size ?? 40)}\n thickness={spinnerProps.thickness ?? 4}\n sx={{ color: spinnerProps.color || theme.palette.primary.main }}\n data-testid={`${props['data-testid']}-spinner`}\n />\n </SpinnerOverlay>\n );\n};\n\n/**\n * The placeholder sits in the box's flow and gives the box its size: the box's\n * own when one is set, the placeholder's when not. The real image is drawn over\n * it (`OVER_PLACEHOLDER`) until it has faded in. Its load and error are not the\n * image's, so neither reaches the caller; a failing one just retires.\n */\nconst PlaceholderIndicator: React.FC<IndicatorProps & { onError: () => void }> = ({\n props,\n metrics,\n onError,\n}) => {\n if (!props.placeholder) return null;\n\n return (\n <StyledImage\n src={props.placeholder}\n alt={`${props.alt} (loading)`}\n style={metrics}\n decoding={props.decoding}\n loading={props.lazy ? 'lazy' : props.loading}\n onError={onError}\n data-testid={`${props['data-testid']}-placeholder`}\n />\n );\n};\n\nconst LoadingIndicator: React.FC<\n IndicatorProps & { kind: NonNullable<LazyImageProps['loadingState']>; onPlaceholderError: () => void }\n> = ({ kind, onPlaceholderError, ...rest }) => {\n switch (kind) {\n case 'skeleton':\n return <SkeletonIndicator {...rest} />;\n case 'spinner':\n return <SpinnerIndicator {...rest} />;\n case 'placeholder':\n return <PlaceholderIndicator {...rest} onError={onPlaceholderError} />;\n default:\n return null;\n }\n};\n\n/**\n * The real image while the placeholder is up: laid over it and cropped by\n * `objectFit`, so the box never grows to hold both. It takes the flow back\n * when the placeholder goes.\n */\nconst OVER_PLACEHOLDER: React.CSSProperties = {\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n};\n\nconst ErrorFallback: React.FC<IndicatorProps> = ({ props, metrics }) => {\n const theme = useTheme();\n const { alt, fallback, width, height, borderRadius } = props;\n const testId = props['data-testid'];\n\n if (!fallback) return null;\n\n if (typeof fallback === 'string') {\n return (\n <StyledImage\n src={fallback}\n alt={`${alt} (fallback)`}\n style={metrics}\n fadeIn={false}\n isLoaded={true}\n data-testid={`${testId}-fallback`}\n />\n );\n }\n\n return (\n <FallbackContainer\n sx={{\n // FUT-2774 #4 (see `emptyToUnset`); the collapse is fixed by\n // `LazyImage` giving `ImageContainer` ITSELF a size now\n // (`containerAxisFor`) — this `position: absolute` box just fills it.\n width: innerLength(theme, emptyToUnset(width)),\n height: innerLength(theme, orDefault(height, 'auto')),\n borderRadius: styleLength(theme, borderRadius),\n }}\n data-testid={`${testId}-fallback`}\n >\n {fallback}\n </FallbackContainer>\n );\n};\n\n/**\n * LazyImage component with enhanced features for optimized image loading\n * Supports lazy loading, placeholders, error handling, and various loading states\n */\nexport const LazyImage = React.memo<LazyImageProps>(function LazyImage(rawProps) {\n const props = resolveLazyImageProps(rawProps);\n const theme = useTheme();\n const {\n state,\n containerRef,\n effectiveLoadingState,\n handleImageLoad,\n handleImageError,\n handlePlaceholderError,\n showImage,\n showLoading,\n showPlaceholder,\n } = useLazyImage(props);\n\n const { width, height, borderRadius, alt } = props;\n const testId = props['data-testid'];\n const metrics: BoxMetrics = {\n // FUT-2774 #4 (see `emptyToUnset`): `metrics` also sizes the REAL `<img>`,\n // where an unset width means \"the image's own intrinsic size\" —\n // `brand-link.tsx`'s logo relies on exactly that. `height`'s `'auto'`\n // fallback has no such live case to protect: a percentage height against\n // an auto-height containing block computes to `auto` regardless, so\n // sharing `SkeletonIndicator`'s fallback changes nothing observable.\n width: innerLength(theme, emptyToUnset(width)),\n height: innerLength(theme, orDefault(height, 'auto')),\n objectFit: props.objectFit,\n objectPosition: props.objectPosition,\n borderRadius: styleLength(theme, borderRadius),\n };\n\n const containerAxis = containerAxisFor(theme, props, state.hasError);\n\n return (\n <ImageContainer\n ref={containerRef}\n className={props.className}\n sx={{\n width: containerAxis.width,\n height: containerAxis.height,\n aspectRatio: containerAxis.aspectRatio,\n // The same px length the image gets (`metrics`): in `sx` a bare number\n // would be a multiple of `shape.borderRadius`, and this box clips (FUT-2656).\n borderRadius: metrics.borderRadius,\n }}\n data-testid={testId}\n >\n {showLoading && (\n <LoadingIndicator\n kind={effectiveLoadingState}\n props={props}\n metrics={metrics}\n onPlaceholderError={handlePlaceholderError}\n />\n )}\n\n {showImage && (\n <StyledImage\n src={state.currentSrc || undefined}\n alt={alt}\n onLoad={handleImageLoad}\n onError={handleImageError}\n fadeIn={props.fadeIn}\n fadeInDuration={props.fadeInDuration}\n isLoaded={!state.isLoading}\n decoding={props.decoding}\n loading={props.lazy ? 'lazy' : props.loading}\n style={{ ...metrics, ...props.sx, ...(showPlaceholder && OVER_PLACEHOLDER) }}\n aria-label={props['aria-label'] || alt}\n aria-describedby={props['aria-describedby']}\n role={props.role}\n data-testid={`${testId}-img`}\n {...imgPassThrough(props)}\n />\n )}\n\n {state.hasError && <ErrorFallback props={props} metrics={metrics} />}\n </ImageContainer>\n );\n});\n\nLazyImage.displayName = 'LazyImage';\n"],"mappings":"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","names":["LazyImage"]}
@@ -1,7 +1,7 @@
1
1
  import "../chunk-5ZFO7ROE.js";
2
2
  import {
3
3
  LazyImage
4
- } from "../chunk-QWFAEUFV.js";
4
+ } from "../chunk-EO6LCGGQ.js";
5
5
  import "../chunk-2TSGRQ2J.js";
6
6
  import "../chunk-724ZONOL.js";
7
7
  import "../chunk-BEGTLELZ.js";
@@ -9,6 +9,12 @@ import "../chunk-E2QIKJX2.js";
9
9
  import "../chunk-EMPVMZAB.js";
10
10
  import "../chunk-7WDPC46N.js";
11
11
  import "../chunk-TYJR4GOF.js";
12
+ import "../chunk-M47ZOUGF.js";
13
+ import "../chunk-7FDU3FIN.js";
14
+ import "../chunk-EPEJMTCQ.js";
15
+ import "../chunk-GDN2LUTZ.js";
16
+ import "../chunk-O6MJWHU5.js";
17
+ import "../chunk-26X4CZVR.js";
12
18
  import "../chunk-FQ6UW7IY.js";
13
19
  import "../chunk-6JAIFIPW.js";
14
20
  import "../chunk-N3GAFIXG.js";
@@ -1 +1 @@
1
- {"version":3,"file":"LazyImage.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/LazyImage/LazyImage.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAqPxD;;;GAGG;AACH,eAAO,MAAM,SAAS,4CA4EpB,CAAC"}
1
+ {"version":3,"file":"LazyImage.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/LazyImage/LazyImage.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAmTxD;;;GAGG;AACH,eAAO,MAAM,SAAS,4CA+EpB,CAAC"}
@@ -16,6 +16,15 @@ export interface LazyImageProps extends React.ImgHTMLAttributes<HTMLImageElement
16
16
  * container's parent, as in `sx`: `0.5` is `50%`. The container takes it and
17
17
  * the image, its skeleton and its fallback fill the container.
18
18
  * A string is any CSS length and passes through untouched.
19
+ *
20
+ * Left unset, the real (loaded) image sizes itself naturally — but the
21
+ * loading skeleton and a `ReactNode` `fallback` never leave BOTH axes
22
+ * unresolved: an unset axis borrows the one that IS set (a square
23
+ * placeholder) when it is a DEFINITE length; a RELATIVE one (a fraction, or
24
+ * a `%` string) squares up through CSS `aspectRatio` instead, since copying
25
+ * a percentage onto the other axis would measure against the wrong box.
26
+ * With neither set, both take the theme's field height (FUT-2805; see
27
+ * `LazyImage.md`'s own note for the exact rule).
19
28
  */
20
29
  width?: number | string;
21
30
  /**
@@ -25,6 +34,10 @@ export interface LazyImageProps extends React.ImgHTMLAttributes<HTMLImageElement
25
34
  * the image, its skeleton and its fallback fill the container. A fractional
26
35
  * height takes effect only when the parent has a definite height.
27
36
  * A string is any CSS length and passes through untouched.
37
+ *
38
+ * Left unset, the real (loaded) image sizes itself naturally (`'auto'`) —
39
+ * the loading skeleton and a `ReactNode` `fallback` follow `width`'s own
40
+ * note instead (FUT-2805).
28
41
  */
29
42
  height?: number | string;
30
43
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"LazyImage.types.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/LazyImage/LazyImage.types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"LazyImage.types.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/LazyImage/LazyImage.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,YAAY,CAAC;AACtF,MAAM,MAAM,qBAAqB,GAAG,UAAU,GAAG,SAAS,GAAG,aAAa,GAAG,MAAM,CAAC;AAEpF,MAAM,WAAW,cAAe,SAAQ,KAAK,CAAC,iBAAiB,CAAC,gBAAgB,CAAC;IAC/E;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;;;;;;;;;;;;;;OAeG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAExB;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAEzB;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,SAAS,CAAC;IAEpC;;;;;;;;;;;OAWG;IACH,YAAY,CAAC,EAAE,qBAAqB,CAAC;IAErC;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB;;;OAGG;IACH,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAE/B;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAE/B;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IAEf;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAE9B;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;OAEG;IACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,cAAc,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;IAEjE;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,cAAc,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;IAElE;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IAEzB;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IAErC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAE3B;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,MAAM,CAAC;IAExC;;OAEG;IACH,aAAa,CAAC,EAAE;QACd,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;QACrC,OAAO,CAAC,EAAE,aAAa,GAAG,UAAU,CAAC;QACrC,SAAS,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;KACvC,CAAC;IAEF;;OAEG;IACH,YAAY,CAAC,EAAE;QACb,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QACvB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IAEF;;;;;;;OAOG;IACH,EAAE,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAEzB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;OAEG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAE5B;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@12-apps/ui",
3
- "version": "6.44.1",
3
+ "version": "6.44.3",
4
4
  "license": "MIT",
5
5
  "packageManager": "pnpm@10.34.5",
6
6
  "type": "module",
@@ -180,9 +180,31 @@ wrapping element, rounded to match the image:
180
180
  |------|------|---------|-------------|
181
181
  | `src` | `string` | - | **Required.** Image source URL |
182
182
  | `alt` | `string` | - | **Required.** Alternative text for accessibility |
183
- | `width` | `number \| string` | - | Image width. A number above 1 is design px; a number above 0 and up to 1 is a fraction of the parent, as in `sx` (`0.5` is `50%`); a string is any CSS length |
183
+ | `width` | `number \| string` | - | Image width. A number above 1 is design px; a number above 0 and up to 1 is a fraction of the parent, as in `sx` (`0.5` is `50%`); a string is any CSS length. Unset lets the real image size itself naturally — but the loading skeleton and a `ReactNode` `fallback` borrow the other axis when only it is set (a square), or the theme's field height when neither is (FUT-2805) |
184
184
  | `height` | `number \| string` | `'auto'` | Image height, read like `width`. A fractional height takes effect only when the parent has a definite height |
185
185
 
186
+ > **The placeholder's own default, when only one axis is set (FUT-2805).**
187
+ > The loading skeleton and a `ReactNode` `fallback` never leave both their own
188
+ > axes unresolved — never the real (loaded) image, which always sizes itself
189
+ > naturally from `width`/`height` exactly as documented above.
190
+ >
191
+ > - A **DEFINITE** set axis (a plain design-px number, or an absolute CSS
192
+ > length string) borrows straight onto the unset one: a square in real
193
+ > px/rem — e.g. `width={120}` alone gives the skeleton `120×120`.
194
+ > - A **RELATIVE** set axis (a fraction in `(0, 1]`, or a `%` string) cannot
195
+ > be copied onto the other one the same way — a width-percentage and a
196
+ > height-percentage measure against two different boxes, so copying the
197
+ > value would re-create the exact 0-height/0-width collapse this default
198
+ > exists to fix. Instead, the set axis keeps its own value and the box
199
+ > squares up through CSS `aspectRatio: '1 / 1'`, with the other axis left
200
+ > `auto` — e.g. `width="100%"` alone gives the skeleton `width: 100%;
201
+ > height: auto; aspect-ratio: 1 / 1`, a square exactly as wide as its
202
+ > (now non-collapsing) container.
203
+ > - With **neither** axis set, both take the theme's field height
204
+ > (`theme.fieldHeight`, through `fieldHeight()`/`rem()` — never a raw px).
205
+ > - With **both** set, nothing above applies: the placeholder renders exactly
206
+ > as its own explicit `width`/`height` say, unchanged.
207
+
186
208
  ### Loading Props
187
209
 
188
210
  | Prop | Type | Default | Description |
@@ -8,6 +8,7 @@ import { imgPassThrough, resolveLazyImageProps, useLazyImage } from './LazyImage
8
8
  import type { LazyImageProps } from './LazyImage.types';
9
9
  import { sheen } from '../../../tokens/ink';
10
10
  import { rem } from '../../../tokens/relative';
11
+ import { fieldHeight } from '../../../tokens/field-height';
11
12
 
12
13
  const ImageContainer = styled(Box)(() => ({
13
14
  position: 'relative',
@@ -100,6 +101,70 @@ const orDefault = (value: Length, fallback: number | string): number | string =>
100
101
  */
101
102
  const emptyToUnset = (value: Length): Length => (value === '' ? undefined : value);
102
103
 
104
+ /** A length, already resolved to CSS — {@link innerLength} or {@link sxLength}. */
105
+ type LengthResolver = (theme: Theme, value: Length) => string | undefined;
106
+
107
+ /**
108
+ * Whether a length is a PERCENTAGE (a numeric {@link isFraction} or a `%`
109
+ * string) — copying it onto the OTHER axis re-creates this ticket's own
110
+ * collapse either way (width%/height% measure different boxes; Chromium-confirmed).
111
+ */
112
+ const isRelativeLength = (value: Length): boolean =>
113
+ isFraction(value) || (typeof value === 'string' && value.trim().endsWith('%'));
114
+
115
+ /** A resolved axis pair; `aspectRatio` is set when the OTHER axis comes from CSS, not a length. */
116
+ type AxisPair = { width: string | undefined; height: string | undefined; aspectRatio?: string };
117
+
118
+ /**
119
+ * The unset-axis DEFAULT for a PLACEHOLDER only (skeleton, `ReactNode`
120
+ * fallback) — never the real image/`metrics` (an unset width sizes it
121
+ * naturally; `brand-link.tsx`'s logo relies on that). Decision (FUT-2805): an
122
+ * unset axis borrows the set one (a square); neither set, both take the
123
+ * theme's field height; both set stays unchanged. `resolve` is `innerLength`
124
+ * (skeleton) or `sxLength` (the container). A RELATIVE single axis
125
+ * ({@link isRelativeLength}) cannot borrow the same way: it keeps its value
126
+ * and squares up via `aspectRatio: '1 / 1'`, the other axis explicitly
127
+ * `'auto'` (never `undefined` — `Skeleton`'s own default, a fixed `40`,
128
+ * would silently win otherwise).
129
+ */
130
+ const placeholderAxis = (theme: Theme, width: Length, height: Length, resolve: LengthResolver): AxisPair => {
131
+ const w = emptyToUnset(width);
132
+ // `height` always arrives defaulted to the literal `'auto'` when unset
133
+ // (`LAZY_IMAGE_DEFAULTS`) — indistinguishable from an explicit `"auto"`,
134
+ // and no distinction is needed: both mean "no fixed height requested".
135
+ const h = height === 'auto' ? undefined : emptyToUnset(height);
136
+
137
+ if (w === undefined && h === undefined) {
138
+ const field = fieldHeight(theme);
139
+ return { width: field, height: field };
140
+ }
141
+ if (h === undefined) {
142
+ if (isRelativeLength(w)) return { width: resolve(theme, w), height: 'auto', aspectRatio: '1 / 1' };
143
+ const resolved = resolve(theme, w);
144
+ return { width: resolved, height: resolved };
145
+ }
146
+ if (w === undefined) {
147
+ if (isRelativeLength(h)) return { width: 'auto', height: resolve(theme, h), aspectRatio: '1 / 1' };
148
+ const resolved = resolve(theme, h);
149
+ return { width: resolved, height: resolved };
150
+ }
151
+ return { width: resolve(theme, w), height: resolve(theme, h) };
152
+ };
153
+
154
+ /**
155
+ * `ImageContainer`'s own axis: the ordinary `orDefault(...,'auto')` in every
156
+ * state, except a ReactNode `fallback` while showing — the one case where
157
+ * the container is the only thing that CAN fix its own collapse
158
+ * (`FallbackContainer` is `position: absolute`, never feeding shrink-to-fit).
159
+ */
160
+ const containerAxisFor = (theme: Theme, props: ResolvedLazyImageProps, hasError: boolean): AxisPair => {
161
+ const { width, height, fallback } = props;
162
+ const needsPlaceholderSize = hasError && Boolean(fallback) && typeof fallback !== 'string';
163
+ return needsPlaceholderSize
164
+ ? placeholderAxis(theme, width, height, sxLength)
165
+ : { width: sxLength(theme, orDefault(width, 'auto')), height: sxLength(theme, orDefault(height, 'auto')) };
166
+ };
167
+
103
168
  /** The box the image occupies, as CSS, shared by the real image and every stand-in for it. */
104
169
  interface BoxMetrics {
105
170
  width?: string;
@@ -116,13 +181,18 @@ interface IndicatorProps {
116
181
 
117
182
  const SkeletonIndicator: React.FC<IndicatorProps> = ({ props }) => {
118
183
  const theme = useTheme();
184
+ // FUT-2805: replaces the old literal `'100%'`/`'auto'` fallbacks, which
185
+ // collapsed to 0 in a shrink-to-fit `ImageContainer` with nothing to
186
+ // resolve against.
187
+ const { width, height, aspectRatio } = placeholderAxis(theme, props.width, props.height, innerLength);
119
188
  return (
120
189
  <Skeleton
121
190
  // FUT-2774 #5: `skeletonProps.variant` was typed but never read — the
122
191
  // variant was hard-coded regardless of what a caller passed.
123
192
  variant={props.skeletonProps.variant ?? 'rectangular'}
124
- width={innerLength(theme, orDefault(props.width, '100%'))}
125
- height={innerLength(theme, orDefault(props.height, 'auto'))}
193
+ width={width}
194
+ height={height}
195
+ style={aspectRatio ? { aspectRatio } : undefined}
126
196
  // FUT-2774 #5: `||` reads a caller's `false` the same as "unset", so
127
197
  // `skeletonProps.animation={false}` could never turn the animation off.
128
198
  animation={props.skeletonProps.animation ?? 'pulse'}
@@ -228,17 +298,9 @@ const ErrorFallback: React.FC<IndicatorProps> = ({ props, metrics }) => {
228
298
  return (
229
299
  <FallbackContainer
230
300
  sx={{
231
- // FUT-2774 #4 (see `emptyToUnset`). Does NOT change the confirmed
232
- // height collapse — `ImageContainer`'s own height was already `'auto'`
233
- // either way; see `LazyImage.test.stories.tsx` for that separate,
234
- // unresolved defect. `orDefault(height, 'auto')` is safe to share with
235
- // `SkeletonIndicator` (unlike width's `'100%'`, deliberately NOT
236
- // shared — see `emptyToUnset`): a PERCENTAGE height against an
237
- // auto-height containing block computes to `auto` by spec regardless,
238
- // so whether `sx.height` is omitted (falling through to
239
- // `FallbackContainer`'s own `height: '100%'`) or set to the literal
240
- // `'auto'` here, an unset-height container gives the same result
241
- // either way — nothing for this fallback to protect against.
301
+ // FUT-2774 #4 (see `emptyToUnset`); the collapse is fixed by
302
+ // `LazyImage` giving `ImageContainer` ITSELF a size now
303
+ // (`containerAxisFor`) — this `position: absolute` box just fills it.
242
304
  width: innerLength(theme, emptyToUnset(width)),
243
305
  height: innerLength(theme, orDefault(height, 'auto')),
244
306
  borderRadius: styleLength(theme, borderRadius),
@@ -285,13 +347,16 @@ export const LazyImage = React.memo<LazyImageProps>(function LazyImage(rawProps)
285
347
  borderRadius: styleLength(theme, borderRadius),
286
348
  };
287
349
 
350
+ const containerAxis = containerAxisFor(theme, props, state.hasError);
351
+
288
352
  return (
289
353
  <ImageContainer
290
354
  ref={containerRef}
291
355
  className={props.className}
292
356
  sx={{
293
- width: sxLength(theme, orDefault(width, 'auto')),
294
- height: sxLength(theme, orDefault(height, 'auto')),
357
+ width: containerAxis.width,
358
+ height: containerAxis.height,
359
+ aspectRatio: containerAxis.aspectRatio,
295
360
  // The same px length the image gets (`metrics`): in `sx` a bare number
296
361
  // would be a multiple of `shape.borderRadius`, and this box clips (FUT-2656).
297
362
  borderRadius: metrics.borderRadius,
@@ -20,6 +20,15 @@ export interface LazyImageProps extends React.ImgHTMLAttributes<HTMLImageElement
20
20
  * container's parent, as in `sx`: `0.5` is `50%`. The container takes it and
21
21
  * the image, its skeleton and its fallback fill the container.
22
22
  * A string is any CSS length and passes through untouched.
23
+ *
24
+ * Left unset, the real (loaded) image sizes itself naturally — but the
25
+ * loading skeleton and a `ReactNode` `fallback` never leave BOTH axes
26
+ * unresolved: an unset axis borrows the one that IS set (a square
27
+ * placeholder) when it is a DEFINITE length; a RELATIVE one (a fraction, or
28
+ * a `%` string) squares up through CSS `aspectRatio` instead, since copying
29
+ * a percentage onto the other axis would measure against the wrong box.
30
+ * With neither set, both take the theme's field height (FUT-2805; see
31
+ * `LazyImage.md`'s own note for the exact rule).
23
32
  */
24
33
  width?: number | string;
25
34
 
@@ -30,6 +39,10 @@ export interface LazyImageProps extends React.ImgHTMLAttributes<HTMLImageElement
30
39
  * the image, its skeleton and its fallback fill the container. A fractional
31
40
  * height takes effect only when the parent has a definite height.
32
41
  * A string is any CSS length and passes through untouched.
42
+ *
43
+ * Left unset, the real (loaded) image sizes itself naturally (`'auto'`) —
44
+ * the loading skeleton and a `ReactNode` `fallback` follow `width`'s own
45
+ * note instead (FUT-2805).
33
46
  */
34
47
  height?: number | string;
35
48
 
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/data-display/LazyImage/LazyImage.tsx"],"sourcesContent":["import Box from '@mui/material/Box/index.js';\nimport CircularProgress from '@mui/material/CircularProgress/index.js';\nimport { styled, useTheme, type Theme } from '@mui/material/styles/index.js';\nimport React from 'react';\nimport { Skeleton } from '../../layout/Skeleton';\nimport type { ResolvedLazyImageProps } from './LazyImage.hooks';\nimport { imgPassThrough, resolveLazyImageProps, useLazyImage } from './LazyImage.hooks';\nimport type { LazyImageProps } from './LazyImage.types';\nimport { sheen } from '../../../tokens/ink';\nimport { rem } from '../../../tokens/relative';\n\nconst ImageContainer = styled(Box)(() => ({\n position: 'relative',\n display: 'inline-block',\n overflow: 'hidden',\n}));\n\nconst StyledImage = styled('img')<{\n fadeIn?: boolean;\n fadeInDuration?: number;\n isLoaded?: boolean;\n}>(({ fadeIn, fadeInDuration = 300, isLoaded }) => ({\n display: 'block',\n maxWidth: '100%',\n height: 'auto',\n opacity: fadeIn ? (isLoaded ? 1 : 0) : 1,\n transition: fadeIn ? `opacity ${fadeInDuration}ms ease-in-out` : 'none',\n}));\n\nconst SpinnerOverlay = styled(Box)(({ theme }) => ({\n position: 'absolute',\n top: '50%',\n left: '50%',\n transform: 'translate(-50%, -50%)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: sheen(theme, 0.8),\n borderRadius: '50%',\n padding: theme.spacing(1),\n}));\n\nconst FallbackContainer = styled(Box)(({ theme }) => ({\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: theme.palette.action.disabledBackground,\n color: theme.palette.text.secondary,\n width: '100%',\n height: '100%',\n position: 'absolute',\n top: 0,\n left: 0,\n}));\n\ntype Length = number | string | undefined;\n\n/** A length prop as React `style` and MUI's `Skeleton` read it: a number is design px. */\nconst styleLength = (theme: Theme, value: Length): string | undefined =>\n typeof value === 'number' ? rem(theme, value) : value;\n\n/**\n * A width or height above 0 and up to 1 is a fraction of the container's parent, as in `sx`.\n * A negative number is no fraction: it stays a (negative, so invalid) length.\n */\nconst isFraction = (value: Length): value is number =>\n typeof value === 'number' && value > 0 && value <= 1;\n\n/**\n * A length prop as `sx` reads it on a width or height: a number of 1 or less is\n * a fraction of the parent, and that stays so; any other number is design px.\n */\nconst sxLength = (theme: Theme, value: Length): string | undefined =>\n isFraction(value) ? `${value * 100}%` : styleLength(theme, value);\n\n/**\n * A width or height for what is drawn inside the container. The container alone\n * takes a fraction, so everything in it fills it: a percentage here is measured\n * against the container and would stack (FUT-2666). Otherwise the same as `styleLength`.\n */\nconst innerLength = (theme: Theme, value: Length): string | undefined =>\n isFraction(value) ? '100%' : styleLength(theme, value);\n\n/**\n * A width or height with its default. Only an unset or empty size takes it: `0`\n * is a size, which `||` read as unset (FUT-2669), and `sx` reads `''` as `0%`.\n */\nconst orDefault = (value: Length, fallback: number | string): number | string =>\n value === undefined || value === '' ? fallback : value;\n\n/**\n * A width/height as `metrics` and the `ReactNode` `ErrorFallback` read it\n * (FUT-2774 #4): an explicit empty string is normalized to \"as if unset\"\n * rather than mapped to a component-wide default. Unlike `SkeletonIndicator`\n * (a content-less box with nothing else to size by, hence its own deliberate\n * `'100%'`/`'auto'` fallback), these draw the real image or a `ReactNode`\n * fallback — both have their own intrinsic size, so leaving an unset width\n * unset lets it size naturally (`brand-link.tsx`'s logo relies on exactly\n * this: an unset width capped by its own `sx.maxWidth`).\n */\nconst emptyToUnset = (value: Length): Length => (value === '' ? undefined : value);\n\n/** The box the image occupies, as CSS, shared by the real image and every stand-in for it. */\ninterface BoxMetrics {\n width?: string;\n height?: string;\n objectFit: NonNullable<LazyImageProps['objectFit']>;\n objectPosition: string;\n borderRadius?: string;\n}\n\ninterface IndicatorProps {\n props: ResolvedLazyImageProps;\n metrics: BoxMetrics;\n}\n\nconst SkeletonIndicator: React.FC<IndicatorProps> = ({ props }) => {\n const theme = useTheme();\n return (\n <Skeleton\n // FUT-2774 #5: `skeletonProps.variant` was typed but never read — the\n // variant was hard-coded regardless of what a caller passed.\n variant={props.skeletonProps.variant ?? 'rectangular'}\n width={innerLength(theme, orDefault(props.width, '100%'))}\n height={innerLength(theme, orDefault(props.height, 'auto'))}\n // FUT-2774 #5: `||` reads a caller's `false` the same as \"unset\", so\n // `skeletonProps.animation={false}` could never turn the animation off.\n animation={props.skeletonProps.animation ?? 'pulse'}\n intensity={props.skeletonProps.intensity}\n borderRadius={styleLength(theme, props.borderRadius)}\n data-testid={`${props['data-testid']}-skeleton`}\n />\n );\n};\n\nconst SpinnerIndicator: React.FC<IndicatorProps> = ({ props }) => {\n const theme = useTheme();\n const { spinnerProps } = props;\n\n return (\n <SpinnerOverlay>\n <CircularProgress\n // FUT-2774 #5: `||` read `spinnerProps.size`/`thickness` of `0` the\n // same as unset — the same class of bug FUT-2669 fixed for\n // `width`/`height`, missed here.\n size={styleLength(theme, spinnerProps.size ?? 40)}\n thickness={spinnerProps.thickness ?? 4}\n sx={{ color: spinnerProps.color || theme.palette.primary.main }}\n data-testid={`${props['data-testid']}-spinner`}\n />\n </SpinnerOverlay>\n );\n};\n\n/**\n * The placeholder sits in the box's flow and gives the box its size: the box's\n * own when one is set, the placeholder's when not. The real image is drawn over\n * it (`OVER_PLACEHOLDER`) until it has faded in. Its load and error are not the\n * image's, so neither reaches the caller; a failing one just retires.\n */\nconst PlaceholderIndicator: React.FC<IndicatorProps & { onError: () => void }> = ({\n props,\n metrics,\n onError,\n}) => {\n if (!props.placeholder) return null;\n\n return (\n <StyledImage\n src={props.placeholder}\n alt={`${props.alt} (loading)`}\n style={metrics}\n decoding={props.decoding}\n loading={props.lazy ? 'lazy' : props.loading}\n onError={onError}\n data-testid={`${props['data-testid']}-placeholder`}\n />\n );\n};\n\nconst LoadingIndicator: React.FC<\n IndicatorProps & { kind: NonNullable<LazyImageProps['loadingState']>; onPlaceholderError: () => void }\n> = ({ kind, onPlaceholderError, ...rest }) => {\n switch (kind) {\n case 'skeleton':\n return <SkeletonIndicator {...rest} />;\n case 'spinner':\n return <SpinnerIndicator {...rest} />;\n case 'placeholder':\n return <PlaceholderIndicator {...rest} onError={onPlaceholderError} />;\n default:\n return null;\n }\n};\n\n/**\n * The real image while the placeholder is up: laid over it and cropped by\n * `objectFit`, so the box never grows to hold both. It takes the flow back\n * when the placeholder goes.\n */\nconst OVER_PLACEHOLDER: React.CSSProperties = {\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n};\n\nconst ErrorFallback: React.FC<IndicatorProps> = ({ props, metrics }) => {\n const theme = useTheme();\n const { alt, fallback, width, height, borderRadius } = props;\n const testId = props['data-testid'];\n\n if (!fallback) return null;\n\n if (typeof fallback === 'string') {\n return (\n <StyledImage\n src={fallback}\n alt={`${alt} (fallback)`}\n style={metrics}\n fadeIn={false}\n isLoaded={true}\n data-testid={`${testId}-fallback`}\n />\n );\n }\n\n return (\n <FallbackContainer\n sx={{\n // FUT-2774 #4 (see `emptyToUnset`). Does NOT change the confirmed\n // height collapse — `ImageContainer`'s own height was already `'auto'`\n // either way; see `LazyImage.test.stories.tsx` for that separate,\n // unresolved defect. `orDefault(height, 'auto')` is safe to share with\n // `SkeletonIndicator` (unlike width's `'100%'`, deliberately NOT\n // shared — see `emptyToUnset`): a PERCENTAGE height against an\n // auto-height containing block computes to `auto` by spec regardless,\n // so whether `sx.height` is omitted (falling through to\n // `FallbackContainer`'s own `height: '100%'`) or set to the literal\n // `'auto'` here, an unset-height container gives the same result\n // either way — nothing for this fallback to protect against.\n width: innerLength(theme, emptyToUnset(width)),\n height: innerLength(theme, orDefault(height, 'auto')),\n borderRadius: styleLength(theme, borderRadius),\n }}\n data-testid={`${testId}-fallback`}\n >\n {fallback}\n </FallbackContainer>\n );\n};\n\n/**\n * LazyImage component with enhanced features for optimized image loading\n * Supports lazy loading, placeholders, error handling, and various loading states\n */\nexport const LazyImage = React.memo<LazyImageProps>(function LazyImage(rawProps) {\n const props = resolveLazyImageProps(rawProps);\n const theme = useTheme();\n const {\n state,\n containerRef,\n effectiveLoadingState,\n handleImageLoad,\n handleImageError,\n handlePlaceholderError,\n showImage,\n showLoading,\n showPlaceholder,\n } = useLazyImage(props);\n\n const { width, height, borderRadius, alt } = props;\n const testId = props['data-testid'];\n const metrics: BoxMetrics = {\n // FUT-2774 #4 (see `emptyToUnset`): `metrics` also sizes the REAL `<img>`,\n // where an unset width means \"the image's own intrinsic size\" —\n // `brand-link.tsx`'s logo relies on exactly that. `height`'s `'auto'`\n // fallback has no such live case to protect: a percentage height against\n // an auto-height containing block computes to `auto` regardless, so\n // sharing `SkeletonIndicator`'s fallback changes nothing observable.\n width: innerLength(theme, emptyToUnset(width)),\n height: innerLength(theme, orDefault(height, 'auto')),\n objectFit: props.objectFit,\n objectPosition: props.objectPosition,\n borderRadius: styleLength(theme, borderRadius),\n };\n\n return (\n <ImageContainer\n ref={containerRef}\n className={props.className}\n sx={{\n width: sxLength(theme, orDefault(width, 'auto')),\n height: sxLength(theme, orDefault(height, 'auto')),\n // The same px length the image gets (`metrics`): in `sx` a bare number\n // would be a multiple of `shape.borderRadius`, and this box clips (FUT-2656).\n borderRadius: metrics.borderRadius,\n }}\n data-testid={testId}\n >\n {showLoading && (\n <LoadingIndicator\n kind={effectiveLoadingState}\n props={props}\n metrics={metrics}\n onPlaceholderError={handlePlaceholderError}\n />\n )}\n\n {showImage && (\n <StyledImage\n src={state.currentSrc || undefined}\n alt={alt}\n onLoad={handleImageLoad}\n onError={handleImageError}\n fadeIn={props.fadeIn}\n fadeInDuration={props.fadeInDuration}\n isLoaded={!state.isLoading}\n decoding={props.decoding}\n loading={props.lazy ? 'lazy' : props.loading}\n style={{ ...metrics, ...props.sx, ...(showPlaceholder && OVER_PLACEHOLDER) }}\n aria-label={props['aria-label'] || alt}\n aria-describedby={props['aria-describedby']}\n role={props.role}\n data-testid={`${testId}-img`}\n {...imgPassThrough(props)}\n />\n )}\n\n {state.hasError && <ErrorFallback props={props} metrics={metrics} />}\n </ImageContainer>\n );\n});\n\nLazyImage.displayName = 'LazyImage';\n"],"mappings":"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","names":["LazyImage"]}