@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.
- package/dist/_internal/components/data-display/LazyImage/LazyImage.js +7 -1
- package/dist/_internal/components/data-display/index.js +1 -1
- package/dist/_internal/components/index.js +1 -1
- package/dist/{chunk-QWFAEUFV.js → chunk-EO6LCGGQ.js} +40 -16
- package/dist/chunk-EO6LCGGQ.js.map +1 -0
- package/dist/data-display/LazyImage.js +7 -1
- package/dist/types/components/data-display/LazyImage/LazyImage.d.ts.map +1 -1
- package/dist/types/components/data-display/LazyImage/LazyImage.types.d.ts +13 -0
- package/dist/types/components/data-display/LazyImage/LazyImage.types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/data-display/LazyImage/LazyImage.md +23 -1
- package/src/components/data-display/LazyImage/LazyImage.tsx +80 -15
- package/src/components/data-display/LazyImage/LazyImage.types.ts +13 -0
- package/dist/chunk-QWFAEUFV.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
LazyImage
|
|
3
|
-
} from "../../../../chunk-
|
|
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-
|
|
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-
|
|
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
|
|
71
|
-
height
|
|
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`)
|
|
152
|
-
//
|
|
153
|
-
//
|
|
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:
|
|
207
|
-
height:
|
|
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-
|
|
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-
|
|
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;
|
|
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":"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
|
|
1
|
+
{"version":3,"file":"LazyImage.types.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/LazyImage/LazyImage.types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -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={
|
|
125
|
-
height={
|
|
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`)
|
|
232
|
-
//
|
|
233
|
-
//
|
|
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:
|
|
294
|
-
height:
|
|
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"]}
|