@12-apps/ui 6.39.6 → 6.39.8
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.hooks.js +1 -1
- package/dist/_internal/components/data-display/LazyImage/LazyImage.js +2 -2
- package/dist/_internal/components/data-display/index.js +2 -2
- package/dist/_internal/components/form/Autocomplete/Autocomplete.helpers.js +1 -1
- package/dist/_internal/components/form/Autocomplete/Autocomplete.js +5 -5
- package/dist/_internal/components/form/Autocomplete/Autocomplete.keys.js +2 -2
- package/dist/_internal/components/form/Autocomplete/Autocomplete.parts.js +2 -2
- package/dist/_internal/components/form/Autocomplete/useAutocomplete.js +3 -3
- package/dist/_internal/components/form/index.js +5 -5
- package/dist/_internal/components/index.js +7 -7
- package/dist/{chunk-LW2LQHIM.js → chunk-BVF2BSWV.js} +20 -9
- package/dist/chunk-BVF2BSWV.js.map +1 -0
- package/dist/{chunk-PH36XEB3.js → chunk-DQZCQV2C.js} +4 -4
- package/dist/{chunk-HCT5ELST.js → chunk-GMUYCN33.js} +47 -12
- package/dist/chunk-GMUYCN33.js.map +1 -0
- package/dist/{chunk-BN742L2M.js → chunk-H6AWKLOV.js} +13 -5
- package/dist/chunk-H6AWKLOV.js.map +1 -0
- package/dist/{chunk-362OGE5J.js → chunk-HBRKA67N.js} +3 -3
- package/dist/{chunk-XBHQT4BQ.js → chunk-JXS56EW5.js} +3 -3
- package/dist/{chunk-XBHQT4BQ.js.map → chunk-JXS56EW5.js.map} +1 -1
- package/dist/{chunk-JDFTIK4L.js → chunk-PELRSZFU.js} +2 -2
- package/dist/data-display/LazyImage.js +2 -2
- package/dist/form/Autocomplete.js +5 -5
- package/dist/types/components/data-display/LazyImage/LazyImage.d.ts.map +1 -1
- package/dist/types/components/data-display/LazyImage/LazyImage.hooks.d.ts +4 -1
- package/dist/types/components/data-display/LazyImage/LazyImage.hooks.d.ts.map +1 -1
- package/dist/types/components/data-display/LazyImage/LazyImage.types.d.ts +19 -3
- package/dist/types/components/data-display/LazyImage/LazyImage.types.d.ts.map +1 -1
- package/dist/types/components/form/Autocomplete/Autocomplete.helpers.d.ts +16 -3
- package/dist/types/components/form/Autocomplete/Autocomplete.helpers.d.ts.map +1 -1
- package/dist/types/components/form/Autocomplete/Autocomplete.parts.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/data-display/LazyImage/LazyImage.hooks.ts +78 -16
- package/src/components/data-display/LazyImage/LazyImage.md +23 -3
- package/src/components/data-display/LazyImage/LazyImage.tsx +31 -10
- package/src/components/data-display/LazyImage/LazyImage.types.ts +19 -3
- package/src/components/form/Autocomplete/Autocomplete.helpers.ts +23 -5
- package/src/components/form/Autocomplete/Autocomplete.parts.tsx +17 -3
- package/dist/chunk-BN742L2M.js.map +0 -1
- package/dist/chunk-HCT5ELST.js.map +0 -1
- package/dist/chunk-LW2LQHIM.js.map +0 -1
- /package/dist/{chunk-PH36XEB3.js.map → chunk-DQZCQV2C.js.map} +0 -0
- /package/dist/{chunk-362OGE5J.js.map → chunk-HBRKA67N.js.map} +0 -0
- /package/dist/{chunk-JDFTIK4L.js.map → chunk-PELRSZFU.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
LazyImage
|
|
3
|
-
} from "../../../../chunk-
|
|
4
|
-
import "../../../../chunk-
|
|
3
|
+
} from "../../../../chunk-BVF2BSWV.js";
|
|
4
|
+
import "../../../../chunk-GMUYCN33.js";
|
|
5
5
|
import "../../../../chunk-724ZONOL.js";
|
|
6
6
|
import "../../../../chunk-BEGTLELZ.js";
|
|
7
7
|
import "../../../../chunk-E2QIKJX2.js";
|
|
@@ -91,8 +91,8 @@ import "../../../chunk-S5UY5U73.js";
|
|
|
91
91
|
import "../../../chunk-5ZFO7ROE.js";
|
|
92
92
|
import {
|
|
93
93
|
LazyImage
|
|
94
|
-
} from "../../../chunk-
|
|
95
|
-
import "../../../chunk-
|
|
94
|
+
} from "../../../chunk-BVF2BSWV.js";
|
|
95
|
+
import "../../../chunk-GMUYCN33.js";
|
|
96
96
|
import "../../../chunk-724ZONOL.js";
|
|
97
97
|
import "../../../chunk-UM35GGCS.js";
|
|
98
98
|
import {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Autocomplete,
|
|
3
3
|
Autocomplete_default
|
|
4
|
-
} from "../../../../chunk-
|
|
5
|
-
import "../../../../chunk-
|
|
6
|
-
import "../../../../chunk-
|
|
7
|
-
import "../../../../chunk-
|
|
8
|
-
import "../../../../chunk-
|
|
4
|
+
} from "../../../../chunk-DQZCQV2C.js";
|
|
5
|
+
import "../../../../chunk-HBRKA67N.js";
|
|
6
|
+
import "../../../../chunk-H6AWKLOV.js";
|
|
7
|
+
import "../../../../chunk-PELRSZFU.js";
|
|
8
|
+
import "../../../../chunk-JXS56EW5.js";
|
|
9
9
|
import "../../../../chunk-CGO452DC.js";
|
|
10
10
|
import "../../../../chunk-GDN2LUTZ.js";
|
|
11
11
|
import "../../../../chunk-7FDU3FIN.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
handleAutocompleteKey
|
|
3
|
-
} from "../../../../chunk-
|
|
4
|
-
import "../../../../chunk-
|
|
3
|
+
} from "../../../../chunk-PELRSZFU.js";
|
|
4
|
+
import "../../../../chunk-JXS56EW5.js";
|
|
5
5
|
import "../../../../chunk-PZ5AY32C.js";
|
|
6
6
|
export {
|
|
7
7
|
handleAutocompleteKey
|
|
@@ -6,8 +6,8 @@ import {
|
|
|
6
6
|
NoResultsRow,
|
|
7
7
|
SuggestionListBox,
|
|
8
8
|
suggestionTypeIcon
|
|
9
|
-
} from "../../../../chunk-
|
|
10
|
-
import "../../../../chunk-
|
|
9
|
+
} from "../../../../chunk-H6AWKLOV.js";
|
|
10
|
+
import "../../../../chunk-JXS56EW5.js";
|
|
11
11
|
import "../../../../chunk-CGO452DC.js";
|
|
12
12
|
import "../../../../chunk-GDN2LUTZ.js";
|
|
13
13
|
import "../../../../chunk-7FDU3FIN.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
useAutocomplete
|
|
3
|
-
} from "../../../../chunk-
|
|
4
|
-
import "../../../../chunk-
|
|
5
|
-
import "../../../../chunk-
|
|
3
|
+
} from "../../../../chunk-HBRKA67N.js";
|
|
4
|
+
import "../../../../chunk-PELRSZFU.js";
|
|
5
|
+
import "../../../../chunk-JXS56EW5.js";
|
|
6
6
|
import "../../../../chunk-PZ5AY32C.js";
|
|
7
7
|
export {
|
|
8
8
|
useAutocomplete
|
|
@@ -139,11 +139,11 @@ import "../../../chunk-7VRCGWQL.js";
|
|
|
139
139
|
import "../../../chunk-2LXHUGYB.js";
|
|
140
140
|
import {
|
|
141
141
|
Autocomplete
|
|
142
|
-
} from "../../../chunk-
|
|
143
|
-
import "../../../chunk-
|
|
144
|
-
import "../../../chunk-
|
|
145
|
-
import "../../../chunk-
|
|
146
|
-
import "../../../chunk-
|
|
142
|
+
} from "../../../chunk-DQZCQV2C.js";
|
|
143
|
+
import "../../../chunk-HBRKA67N.js";
|
|
144
|
+
import "../../../chunk-H6AWKLOV.js";
|
|
145
|
+
import "../../../chunk-PELRSZFU.js";
|
|
146
|
+
import "../../../chunk-JXS56EW5.js";
|
|
147
147
|
import "../../../chunk-BO6S47KI.js";
|
|
148
148
|
import {
|
|
149
149
|
Calendar
|
|
@@ -345,11 +345,11 @@ import "../../chunk-7VRCGWQL.js";
|
|
|
345
345
|
import "../../chunk-2LXHUGYB.js";
|
|
346
346
|
import {
|
|
347
347
|
Autocomplete
|
|
348
|
-
} from "../../chunk-
|
|
349
|
-
import "../../chunk-
|
|
350
|
-
import "../../chunk-
|
|
351
|
-
import "../../chunk-
|
|
352
|
-
import "../../chunk-
|
|
348
|
+
} from "../../chunk-DQZCQV2C.js";
|
|
349
|
+
import "../../chunk-HBRKA67N.js";
|
|
350
|
+
import "../../chunk-H6AWKLOV.js";
|
|
351
|
+
import "../../chunk-PELRSZFU.js";
|
|
352
|
+
import "../../chunk-JXS56EW5.js";
|
|
353
353
|
import "../../chunk-BO6S47KI.js";
|
|
354
354
|
import {
|
|
355
355
|
Calendar
|
|
@@ -477,8 +477,8 @@ import "../../chunk-S5UY5U73.js";
|
|
|
477
477
|
import "../../chunk-5ZFO7ROE.js";
|
|
478
478
|
import {
|
|
479
479
|
LazyImage
|
|
480
|
-
} from "../../chunk-
|
|
481
|
-
import "../../chunk-
|
|
480
|
+
} from "../../chunk-BVF2BSWV.js";
|
|
481
|
+
import "../../chunk-GMUYCN33.js";
|
|
482
482
|
import "../../chunk-724ZONOL.js";
|
|
483
483
|
import "../../chunk-UM35GGCS.js";
|
|
484
484
|
import {
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
imgPassThrough,
|
|
3
3
|
resolveLazyImageProps,
|
|
4
4
|
useLazyImage
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-GMUYCN33.js";
|
|
6
6
|
import {
|
|
7
7
|
Skeleton
|
|
8
8
|
} from "./chunk-BEGTLELZ.js";
|
|
@@ -91,31 +91,40 @@ var SpinnerIndicator = ({ props }) => {
|
|
|
91
91
|
var PlaceholderIndicator = ({
|
|
92
92
|
props,
|
|
93
93
|
metrics,
|
|
94
|
-
|
|
94
|
+
onError
|
|
95
95
|
}) => {
|
|
96
|
-
if (!props.placeholder
|
|
96
|
+
if (!props.placeholder) return null;
|
|
97
97
|
return /* @__PURE__ */ jsx(
|
|
98
98
|
StyledImage,
|
|
99
99
|
{
|
|
100
100
|
src: props.placeholder,
|
|
101
101
|
alt: `${props.alt} (loading)`,
|
|
102
102
|
style: metrics,
|
|
103
|
+
decoding: props.decoding,
|
|
104
|
+
loading: props.lazy ? "lazy" : props.loading,
|
|
105
|
+
onError,
|
|
103
106
|
"data-testid": `${props["data-testid"]}-placeholder`
|
|
104
107
|
}
|
|
105
108
|
);
|
|
106
109
|
};
|
|
107
|
-
var LoadingIndicator = ({ kind,
|
|
110
|
+
var LoadingIndicator = ({ kind, onPlaceholderError, ...rest }) => {
|
|
108
111
|
switch (kind) {
|
|
109
112
|
case "skeleton":
|
|
110
113
|
return /* @__PURE__ */ jsx(SkeletonIndicator, { ...rest });
|
|
111
114
|
case "spinner":
|
|
112
115
|
return /* @__PURE__ */ jsx(SpinnerIndicator, { ...rest });
|
|
113
116
|
case "placeholder":
|
|
114
|
-
return /* @__PURE__ */ jsx(PlaceholderIndicator, { ...rest,
|
|
117
|
+
return /* @__PURE__ */ jsx(PlaceholderIndicator, { ...rest, onError: onPlaceholderError });
|
|
115
118
|
default:
|
|
116
119
|
return null;
|
|
117
120
|
}
|
|
118
121
|
};
|
|
122
|
+
var OVER_PLACEHOLDER = {
|
|
123
|
+
position: "absolute",
|
|
124
|
+
inset: 0,
|
|
125
|
+
width: "100%",
|
|
126
|
+
height: "100%"
|
|
127
|
+
};
|
|
119
128
|
var ErrorFallback = ({ props, metrics }) => {
|
|
120
129
|
const theme = useTheme();
|
|
121
130
|
const { alt, fallback, width, height, borderRadius } = props;
|
|
@@ -156,8 +165,10 @@ var LazyImage = React.memo(function LazyImage2(rawProps) {
|
|
|
156
165
|
effectiveLoadingState,
|
|
157
166
|
handleImageLoad,
|
|
158
167
|
handleImageError,
|
|
168
|
+
handlePlaceholderError,
|
|
159
169
|
showImage,
|
|
160
|
-
showLoading
|
|
170
|
+
showLoading,
|
|
171
|
+
showPlaceholder
|
|
161
172
|
} = useLazyImage(props);
|
|
162
173
|
const { width, height, borderRadius, alt } = props;
|
|
163
174
|
const testId = props["data-testid"];
|
|
@@ -188,7 +199,7 @@ var LazyImage = React.memo(function LazyImage2(rawProps) {
|
|
|
188
199
|
kind: effectiveLoadingState,
|
|
189
200
|
props,
|
|
190
201
|
metrics,
|
|
191
|
-
|
|
202
|
+
onPlaceholderError: handlePlaceholderError
|
|
192
203
|
}
|
|
193
204
|
),
|
|
194
205
|
showImage && /* @__PURE__ */ jsx(
|
|
@@ -203,7 +214,7 @@ var LazyImage = React.memo(function LazyImage2(rawProps) {
|
|
|
203
214
|
isLoaded: !state.isLoading,
|
|
204
215
|
decoding: props.decoding,
|
|
205
216
|
loading: props.lazy ? "lazy" : props.loading,
|
|
206
|
-
style: { ...metrics, ...props.sx },
|
|
217
|
+
style: { ...metrics, ...props.sx, ...showPlaceholder && OVER_PLACEHOLDER },
|
|
207
218
|
"aria-label": props["aria-label"] || alt,
|
|
208
219
|
"aria-describedby": props["aria-describedby"],
|
|
209
220
|
role: props.role,
|
|
@@ -221,4 +232,4 @@ LazyImage.displayName = "LazyImage";
|
|
|
221
232
|
export {
|
|
222
233
|
LazyImage
|
|
223
234
|
};
|
|
224
|
-
//# sourceMappingURL=chunk-
|
|
235
|
+
//# sourceMappingURL=chunk-BVF2BSWV.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';\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/** 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 variant=\"rectangular\"\n width={innerLength(theme, orDefault(props.width, '100%'))}\n height={innerLength(theme, orDefault(props.height, 'auto'))}\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 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 width: innerLength(theme, width),\n height: innerLength(theme, height),\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 width: innerLength(theme, width),\n height: innerLength(theme, height),\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"]}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
useAutocomplete
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-HBRKA67N.js";
|
|
4
4
|
import {
|
|
5
5
|
AutocompleteInput,
|
|
6
6
|
ChipList,
|
|
7
7
|
DefaultSuggestion,
|
|
8
8
|
SuggestionListBox
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-H6AWKLOV.js";
|
|
10
10
|
import {
|
|
11
11
|
defaultGetKey,
|
|
12
12
|
defaultGetLabel,
|
|
13
13
|
defaultGetSuggestionType
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-JXS56EW5.js";
|
|
15
15
|
|
|
16
16
|
// src/components/form/Autocomplete/Autocomplete.tsx
|
|
17
17
|
import Box from "@mui/material/Box/index.js";
|
|
@@ -131,4 +131,4 @@ export {
|
|
|
131
131
|
Autocomplete,
|
|
132
132
|
Autocomplete_default
|
|
133
133
|
};
|
|
134
|
-
//# sourceMappingURL=chunk-
|
|
134
|
+
//# sourceMappingURL=chunk-DQZCQV2C.js.map
|
|
@@ -93,7 +93,7 @@ var useVisibility = (props, containerRef, isVisible, onVisible) => {
|
|
|
93
93
|
};
|
|
94
94
|
}, [lazy, src, rootMargin, threshold, isVisible, onLoadStart, onVisible, containerRef]);
|
|
95
95
|
};
|
|
96
|
-
var
|
|
96
|
+
var useTimeoutRef = () => {
|
|
97
97
|
const ref = useRef(void 0);
|
|
98
98
|
useEffect(
|
|
99
99
|
() => () => {
|
|
@@ -105,19 +105,35 @@ var useRetryTimeout = () => {
|
|
|
105
105
|
);
|
|
106
106
|
return ref;
|
|
107
107
|
};
|
|
108
|
-
var
|
|
109
|
-
const {
|
|
110
|
-
const
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
const
|
|
108
|
+
var usePlaceholderPhase = (props, loadingState) => {
|
|
109
|
+
const { placeholder, fadeIn, fadeInDuration } = props;
|
|
110
|
+
const active = loadingState === "placeholder" && Boolean(placeholder);
|
|
111
|
+
const [retired, setRetired] = useState(false);
|
|
112
|
+
const fadeTimeoutRef = useTimeoutRef();
|
|
113
|
+
const retire = useCallback(() => setRetired(true), []);
|
|
114
|
+
const retireAfterFade = useCallback(() => {
|
|
115
|
+
if (!active || !fadeIn) {
|
|
116
|
+
setRetired(true);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
clearTimeout(fadeTimeoutRef.current);
|
|
120
|
+
fadeTimeoutRef.current = setTimeout(() => setRetired(true), fadeInDuration);
|
|
121
|
+
}, [active, fadeIn, fadeInDuration, fadeTimeoutRef]);
|
|
122
|
+
return {
|
|
123
|
+
showPlaceholder: active && !retired,
|
|
124
|
+
retirePlaceholder: retire,
|
|
125
|
+
retirePlaceholderAfterFade: retireAfterFade
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
var useImageSource = (props, containerRef) => {
|
|
129
|
+
const { src, lazy } = props;
|
|
114
130
|
const [state, setState] = useState({
|
|
115
131
|
isLoading: true,
|
|
116
132
|
hasError: false,
|
|
117
133
|
isVisible: !lazy,
|
|
118
134
|
// If not lazy, load immediately
|
|
119
135
|
retryCount: 0,
|
|
120
|
-
currentSrc: lazy ?
|
|
136
|
+
currentSrc: lazy ? null : src
|
|
121
137
|
});
|
|
122
138
|
const markVisible = useCallback(
|
|
123
139
|
() => setState((prev) => ({ ...prev, isVisible: true, currentSrc: src })),
|
|
@@ -130,17 +146,32 @@ var useLazyImage = (props) => {
|
|
|
130
146
|
}
|
|
131
147
|
setState((prev) => ({ ...prev, currentSrc: src, isLoading: true, hasError: false }));
|
|
132
148
|
}, [state.isVisible, src, state.currentSrc]);
|
|
149
|
+
return [state, setState];
|
|
150
|
+
};
|
|
151
|
+
var useLazyImage = (props) => {
|
|
152
|
+
const { src, loadingState, showSpinner } = props;
|
|
153
|
+
const { onLoad, onError, retryOnError, maxRetries, retryDelay } = props;
|
|
154
|
+
const containerRef = useRef(null);
|
|
155
|
+
const retryTimeoutRef = useTimeoutRef();
|
|
156
|
+
const [state, setState] = useImageSource(props, containerRef);
|
|
157
|
+
const effectiveLoadingState = showSpinner ? "spinner" : loadingState;
|
|
158
|
+
const { showPlaceholder, retirePlaceholder, retirePlaceholderAfterFade } = usePlaceholderPhase(
|
|
159
|
+
props,
|
|
160
|
+
effectiveLoadingState
|
|
161
|
+
);
|
|
133
162
|
const handleImageLoad = useCallback(
|
|
134
163
|
(event) => {
|
|
135
164
|
setState((prev) => ({ ...prev, isLoading: false, hasError: false, retryCount: 0 }));
|
|
165
|
+
retirePlaceholderAfterFade();
|
|
136
166
|
onLoad?.(event);
|
|
137
167
|
},
|
|
138
|
-
[onLoad]
|
|
168
|
+
[onLoad, retirePlaceholderAfterFade]
|
|
139
169
|
);
|
|
140
170
|
const handleImageError = useCallback(
|
|
141
171
|
(event) => {
|
|
142
172
|
if (!retryOnError || state.retryCount >= maxRetries) {
|
|
143
173
|
setState((prev) => ({ ...prev, isLoading: false, hasError: true }));
|
|
174
|
+
retirePlaceholder();
|
|
144
175
|
onError?.(event);
|
|
145
176
|
return;
|
|
146
177
|
}
|
|
@@ -153,7 +184,7 @@ var useLazyImage = (props) => {
|
|
|
153
184
|
}));
|
|
154
185
|
}, retryDelay);
|
|
155
186
|
},
|
|
156
|
-
[onError, retryOnError, state.retryCount, maxRetries, retryDelay, src]
|
|
187
|
+
[onError, retryOnError, state.retryCount, maxRetries, retryDelay, src, retirePlaceholder]
|
|
157
188
|
);
|
|
158
189
|
return {
|
|
159
190
|
state,
|
|
@@ -161,8 +192,12 @@ var useLazyImage = (props) => {
|
|
|
161
192
|
effectiveLoadingState,
|
|
162
193
|
handleImageLoad,
|
|
163
194
|
handleImageError,
|
|
195
|
+
handlePlaceholderError: retirePlaceholder,
|
|
164
196
|
showImage: Boolean(state.currentSrc) && !state.hasError,
|
|
165
|
-
|
|
197
|
+
// In placeholder mode the placeholder outlasts `isLoading`: it stays under
|
|
198
|
+
// the real image until the fade has ended.
|
|
199
|
+
showLoading: effectiveLoadingState === "placeholder" ? showPlaceholder : state.isLoading,
|
|
200
|
+
showPlaceholder
|
|
166
201
|
};
|
|
167
202
|
};
|
|
168
203
|
|
|
@@ -171,4 +206,4 @@ export {
|
|
|
171
206
|
imgPassThrough,
|
|
172
207
|
useLazyImage
|
|
173
208
|
};
|
|
174
|
-
//# sourceMappingURL=chunk-
|
|
209
|
+
//# sourceMappingURL=chunk-GMUYCN33.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/data-display/LazyImage/LazyImage.hooks.ts"],"sourcesContent":["import { useTheme } from '@mui/material/styles/index.js';\nimport React, { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { remPx } from '../../../tokens/relative';\n\nimport type { LazyImageProps, LazyImageState } from './LazyImage.types';\n\ntype LazyImageDefaultedKeys =\n | 'height'\n | 'loadingState'\n | 'objectFit'\n | 'objectPosition'\n | 'lazy'\n | 'threshold'\n | 'fadeIn'\n | 'fadeInDuration'\n | 'retryOnError'\n | 'maxRetries'\n | 'retryDelay'\n | 'decoding'\n | 'skeletonProps'\n | 'spinnerProps'\n | 'sx'\n | 'role';\n\nexport type ResolvedLazyImageProps = LazyImageProps &\n Required<Pick<LazyImageProps, LazyImageDefaultedKeys>>;\n\nconst LAZY_IMAGE_DEFAULTS: Pick<LazyImageProps, LazyImageDefaultedKeys> = {\n height: 'auto',\n loadingState: 'skeleton',\n objectFit: 'cover',\n objectPosition: 'center',\n lazy: true,\n threshold: 0,\n fadeIn: true,\n fadeInDuration: 300,\n retryOnError: false,\n maxRetries: 3,\n retryDelay: 1000,\n decoding: 'async',\n skeletonProps: {},\n spinnerProps: {},\n sx: {},\n role: 'img',\n};\n\n// Strips explicitly-undefined props before the merge, so `lazy={undefined}` still\n// falls back to the default exactly as a destructuring default would.\nconst definedProps = (props: LazyImageProps): Partial<LazyImageProps> =>\n Object.fromEntries(\n Object.entries(props).filter(([, value]) => value !== undefined),\n ) as Partial<LazyImageProps>;\n\nexport const resolveLazyImageProps = (props: LazyImageProps): ResolvedLazyImageProps =>\n ({ ...LAZY_IMAGE_DEFAULTS, ...definedProps(props) }) as ResolvedLazyImageProps;\n\n// Props this component consumes itself. Anything else the caller passed is an\n// ordinary <img> attribute and is forwarded untouched.\nconst OWN_PROPS = new Set([\n 'src', 'alt', 'width', 'height', 'placeholder', 'fallback', 'loadingState',\n 'showSpinner', 'objectFit', 'objectPosition', 'borderRadius', 'lazy', 'rootMargin',\n 'threshold', 'fadeIn', 'fadeInDuration', 'onLoad', 'onError', 'onLoadStart',\n 'retryOnError', 'maxRetries', 'retryDelay', 'decoding', 'loading', 'fetchPriority',\n 'skeletonProps', 'spinnerProps', 'sx', 'className', 'data-testid', 'aria-label',\n 'aria-describedby', 'role',\n]);\n\nexport const imgPassThrough = (props: ResolvedLazyImageProps): Record<string, unknown> =>\n Object.fromEntries(Object.entries(props).filter(([key]) => !OWN_PROPS.has(key)));\n\n/**\n * Flips `isVisible` the first time the container nears the viewport, then stops\n * observing. Does nothing when `lazy` is off — the caller wants the request now.\n */\nconst useVisibility = (\n props: ResolvedLazyImageProps,\n containerRef: React.RefObject<HTMLDivElement | null>,\n isVisible: boolean,\n onVisible: () => void,\n) => {\n const { lazy, src, threshold, onLoadStart } = props;\n const theme = useTheme();\n // `IntersectionObserver` takes px or %, never rem: the default reach is 100\n // design px, measured through the theme so it moves with the root.\n const rootMargin = props.rootMargin ?? `${remPx(theme, 100)}px`;\n\n useEffect(() => {\n if (!lazy || isVisible) {\n return;\n }\n\n const observer = new IntersectionObserver(\n (entries) => {\n entries.forEach((entry) => {\n if (entry.isIntersecting) {\n onVisible();\n onLoadStart?.();\n observer.unobserve(entry.target);\n }\n });\n },\n { rootMargin, threshold },\n );\n\n if (containerRef.current) {\n observer.observe(containerRef.current);\n }\n\n return () => {\n if (containerRef.current) {\n observer.unobserve(containerRef.current);\n }\n };\n }, [lazy, src, rootMargin, threshold, isVisible, onLoadStart, onVisible, containerRef]);\n};\n\n/** A pending timer has to be cancelled on unmount, or it sets state on a dead component. */\nconst useTimeoutRef = () => {\n const ref = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n\n useEffect(\n () => () => {\n if (ref.current) {\n clearTimeout(ref.current);\n }\n },\n [],\n );\n\n return ref;\n};\n\n/**\n * Whether the placeholder image is up. It is a loading state: only\n * `loadingState=\"placeholder\"` with a `placeholder` set draws it, from mount\n * until the real image settles — its `load` plus the fade, or its final error.\n * A placeholder that fails to load retires at once, as if none were set.\n */\nconst usePlaceholderPhase = (\n props: ResolvedLazyImageProps,\n loadingState: NonNullable<LazyImageProps['loadingState']>,\n) => {\n const { placeholder, fadeIn, fadeInDuration } = props;\n // Only this mode draws a placeholder. Otherwise the real image settling\n // retires the phase at once — batched into the load's own render, with no\n // timer — so a placeholder switched on afterwards never covers a loaded image.\n const active = loadingState === 'placeholder' && Boolean(placeholder);\n const [retired, setRetired] = useState(false);\n const fadeTimeoutRef = useTimeoutRef();\n\n const retire = useCallback(() => setRetired(true), []);\n\n // The real image fades in over the placeholder, so it goes when the fade ends.\n const retireAfterFade = useCallback(() => {\n if (!active || !fadeIn) {\n setRetired(true);\n return;\n }\n clearTimeout(fadeTimeoutRef.current);\n fadeTimeoutRef.current = setTimeout(() => setRetired(true), fadeInDuration);\n }, [active, fadeIn, fadeInDuration, fadeTimeoutRef]);\n\n return {\n showPlaceholder: active && !retired,\n retirePlaceholder: retire,\n retirePlaceholderAfterFade: retireAfterFade,\n };\n};\n\n/**\n * The real image's state, and when it is requested: at once when not lazy,\n * else the first time the container nears the viewport. The main <img> only\n * ever carries the real src — a placeholder is drawn by its own element, so its\n * load and error are never the image's.\n */\nconst useImageSource = (\n props: ResolvedLazyImageProps,\n containerRef: React.RefObject<HTMLDivElement | null>,\n) => {\n const { src, lazy } = props;\n const [state, setState] = useState<LazyImageState>({\n isLoading: true,\n hasError: false,\n isVisible: !lazy, // If not lazy, load immediately\n retryCount: 0,\n currentSrc: lazy ? null : src,\n });\n\n const markVisible = useCallback(\n () => setState((prev) => ({ ...prev, isVisible: true, currentSrc: src })),\n [src],\n );\n useVisibility(props, containerRef, state.isVisible, markVisible);\n\n // Load image when it becomes visible\n useEffect(() => {\n if (!state.isVisible || !src || state.currentSrc === src) {\n return;\n }\n\n setState((prev) => ({ ...prev, currentSrc: src, isLoading: true, hasError: false }));\n }, [state.isVisible, src, state.currentSrc]);\n\n return [state, setState] as const;\n};\n\n/**\n * Owns whether the real image has been requested yet and how it went: the\n * observer above, the load/error handlers, the retry timer, and how long the\n * placeholder stays up.\n */\nexport const useLazyImage = (props: ResolvedLazyImageProps) => {\n const { src, loadingState, showSpinner } = props;\n const { onLoad, onError, retryOnError, maxRetries, retryDelay } = props;\n\n const containerRef = useRef<HTMLDivElement>(null);\n const retryTimeoutRef = useTimeoutRef();\n const [state, setState] = useImageSource(props, containerRef);\n\n // Handle deprecated showSpinner prop\n const effectiveLoadingState = showSpinner ? 'spinner' : loadingState;\n const { showPlaceholder, retirePlaceholder, retirePlaceholderAfterFade } = usePlaceholderPhase(\n props,\n effectiveLoadingState,\n );\n\n const handleImageLoad = useCallback(\n (event: React.SyntheticEvent<HTMLImageElement>) => {\n setState((prev) => ({ ...prev, isLoading: false, hasError: false, retryCount: 0 }));\n retirePlaceholderAfterFade();\n onLoad?.(event);\n },\n [onLoad, retirePlaceholderAfterFade],\n );\n\n const handleImageError = useCallback(\n (event: React.SyntheticEvent<HTMLImageElement>) => {\n if (!retryOnError || state.retryCount >= maxRetries) {\n setState((prev) => ({ ...prev, isLoading: false, hasError: true }));\n retirePlaceholder();\n onError?.(event);\n return;\n }\n\n retryTimeoutRef.current = setTimeout(() => {\n setState((prev) => ({\n ...prev,\n retryCount: prev.retryCount + 1,\n currentSrc: `${src}?retry=${prev.retryCount + 1}`, // Force reload with cache buster\n }));\n }, retryDelay);\n },\n [onError, retryOnError, state.retryCount, maxRetries, retryDelay, src, retirePlaceholder],\n );\n\n return {\n state,\n containerRef,\n effectiveLoadingState,\n handleImageLoad,\n handleImageError,\n handlePlaceholderError: retirePlaceholder,\n showImage: Boolean(state.currentSrc) && !state.hasError,\n // In placeholder mode the placeholder outlasts `isLoading`: it stays under\n // the real image until the fade has ended.\n showLoading: effectiveLoadingState === 'placeholder' ? showPlaceholder : state.isLoading,\n showPlaceholder,\n };\n};\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
highlightLabel
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-JXS56EW5.js";
|
|
4
4
|
import {
|
|
5
5
|
fieldTextFieldStyles
|
|
6
6
|
} from "./chunk-CGO452DC.js";
|
|
@@ -23,6 +23,7 @@ import Popper from "@mui/material/Popper/index.js";
|
|
|
23
23
|
import TextField from "@mui/material/TextField/index.js";
|
|
24
24
|
import Typography from "@mui/material/Typography/index.js";
|
|
25
25
|
import { styled, useTheme } from "@mui/material/styles/index.js";
|
|
26
|
+
import React from "react";
|
|
26
27
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
27
28
|
var StyledPopper = styled(Popper)(({ theme }) => ({
|
|
28
29
|
zIndex: theme.zIndex.tooltip,
|
|
@@ -144,7 +145,7 @@ function DefaultSuggestion({
|
|
|
144
145
|
}) {
|
|
145
146
|
const label = getLabel(item);
|
|
146
147
|
const description = getDescription?.(item);
|
|
147
|
-
const
|
|
148
|
+
const segments = highlightLabel(label, state.query, matchMode);
|
|
148
149
|
return /* @__PURE__ */ jsxs(
|
|
149
150
|
Box,
|
|
150
151
|
{
|
|
@@ -153,7 +154,8 @@ function DefaultSuggestion({
|
|
|
153
154
|
alignItems: "center",
|
|
154
155
|
gap: 1,
|
|
155
156
|
width: "100%",
|
|
156
|
-
// Bold the matched
|
|
157
|
+
// Bold every run the query matched, in any match mode but fuzzy — not
|
|
158
|
+
// only a prefix, even under `startsWith` (not a yellow <mark> highlight).
|
|
157
159
|
"& mark": { backgroundColor: "transparent", color: "inherit", fontWeight: 700 }
|
|
158
160
|
},
|
|
159
161
|
children: [
|
|
@@ -161,7 +163,13 @@ function DefaultSuggestion({
|
|
|
161
163
|
/* @__PURE__ */ jsx(
|
|
162
164
|
ListItemText,
|
|
163
165
|
{
|
|
164
|
-
primary: /* @__PURE__ */ jsx("span", {
|
|
166
|
+
primary: /* @__PURE__ */ jsx("span", { children: segments.map(
|
|
167
|
+
(segment, index) => (
|
|
168
|
+
// The runs are derived from one label and never reorder, so the
|
|
169
|
+
// position is a stable key.
|
|
170
|
+
segment.match ? /* @__PURE__ */ jsx("mark", { children: segment.text }, index) : /* @__PURE__ */ jsx(React.Fragment, { children: segment.text }, index)
|
|
171
|
+
)
|
|
172
|
+
) }),
|
|
165
173
|
secondary: description
|
|
166
174
|
}
|
|
167
175
|
)
|
|
@@ -267,4 +275,4 @@ export {
|
|
|
267
275
|
NoResultsRow,
|
|
268
276
|
SuggestionListBox
|
|
269
277
|
};
|
|
270
|
-
//# sourceMappingURL=chunk-
|
|
278
|
+
//# sourceMappingURL=chunk-H6AWKLOV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/form/Autocomplete/Autocomplete.parts.tsx"],"sourcesContent":["import LinkIcon from '@mui/icons-material/Link';\nimport SearchIcon from '@mui/icons-material/Search';\nimport Box from '@mui/material/Box/index.js';\nimport Chip from '@mui/material/Chip/index.js';\nimport CircularProgress from '@mui/material/CircularProgress/index.js';\nimport List from '@mui/material/List/index.js';\nimport ListItem from '@mui/material/ListItem/index.js';\nimport ListItemText from '@mui/material/ListItemText/index.js';\nimport Paper from '@mui/material/Paper/index.js';\nimport Popper from '@mui/material/Popper/index.js';\nimport TextField from '@mui/material/TextField/index.js';\nimport Typography from '@mui/material/Typography/index.js';\nimport { styled, useTheme } from '@mui/material/styles/index.js';\nimport React from 'react';\n\nimport { highlightLabel, type MatchMode } from './Autocomplete.helpers';\nimport type { SuggestionItemState, SuggestionType } from './Autocomplete.types';\nimport type { AutocompleteCopy } from '../../../copy';\nimport { fieldTextFieldStyles } from '../../../tokens/field-height';\nimport { rem, sxRem } from '../../../tokens/relative';\n\nconst StyledPopper = styled(Popper)(({ theme }) => ({\n zIndex: theme.zIndex.tooltip,\n width: '100%',\n maxHeight: rem(theme, 300),\n overflow: 'auto',\n // Popper.js writes the computed position (and stamps `data-popper-placement`)\n // in a layout effect that can land AFTER the first paint. Until it does, the\n // element would paint once unpositioned — at the offset-parent origin, far\n // left and full width — then jump under the input, a visible per-keystroke\n // flash as the list re-renders. Hide that unpositioned first frame; it becomes\n // visible the instant popper assigns a placement.\n '&:not([data-popper-placement])': { visibility: 'hidden' },\n}));\n\nconst InlineSuggestionDisplay = styled('div')(({ theme }) => ({\n position: 'absolute',\n pointerEvents: 'none',\n left: rem(theme, 14), // Match TextField padding\n top: '50%',\n transform: 'translateY(-50%)',\n fontSize: 'inherit',\n fontFamily: 'inherit',\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n zIndex: 1,\n}));\n\nconst UserTextTwin = styled('span')({\n visibility: 'hidden',\n});\n\nconst GhostText = styled('span')(({ theme }) => ({\n color: theme.palette.text.secondary,\n opacity: 0.6,\n}));\n\nconst ChipContainer = styled(Box)(({ theme }) => ({\n display: 'flex',\n flexWrap: 'wrap',\n gap: theme.spacing(0.5),\n marginBottom: theme.spacing(1),\n}));\n\n/** Leading icon for a typed suggestion (search vs. link); null for untyped items. */\nexport function suggestionTypeIcon(type: SuggestionType | undefined): React.ReactNode {\n const sx = { color: 'text.secondary', flexShrink: 0 } as const;\n if (type === 'link') return <LinkIcon fontSize=\"small\" sx={sx} />;\n if (type === 'search') return <SearchIcon fontSize=\"small\" sx={sx} />;\n return null;\n}\n\n/** The inline ghost-text overlay: an invisible twin of the typed text + the grey remainder. */\nfunction GhostOverlay({ inputValue, ghost }: { inputValue: string; ghost: string }): React.JSX.Element {\n return (\n <InlineSuggestionDisplay>\n <UserTextTwin>{inputValue}</UserTextTwin>\n <GhostText>{ghost}</GhostText>\n </InlineSuggestionDisplay>\n );\n}\n\nexport interface AutocompleteInputProps {\n id: string;\n listId: string;\n inputRef: React.RefObject<HTMLInputElement | null>;\n inputValue: string;\n ghost: string;\n showGhost: boolean;\n isInputFocused: boolean;\n open: boolean;\n activeDescendant: string | undefined;\n isLoading: boolean;\n disabled: boolean;\n autoFocus: boolean;\n placeholder: string;\n startAdornment?: React.ReactNode;\n inputAriaLabel?: string;\n inputClassName?: string;\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;\n onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => void;\n onFocus: () => void;\n onBlur: () => void;\n onCompositionStart: () => void;\n onCompositionEnd: () => void;\n}\n\n/** The combobox text field plus its inline ghost-text overlay. */\nexport function AutocompleteInput(props: AutocompleteInputProps): React.JSX.Element {\n const theme = useTheme();\n const showOverlay = Boolean(props.ghost) && props.showGhost && props.isInputFocused;\n return (\n <Box sx={{ position: 'relative' }}>\n {showOverlay && <GhostOverlay inputValue={props.inputValue} ghost={props.ghost} />}\n <TextField\n ref={props.inputRef}\n fullWidth\n sx={fieldTextFieldStyles}\n value={props.inputValue}\n onChange={props.onChange}\n onKeyDown={props.onKeyDown}\n onFocus={props.onFocus}\n onBlur={props.onBlur}\n onCompositionStart={props.onCompositionStart}\n onCompositionEnd={props.onCompositionEnd}\n placeholder={props.placeholder}\n disabled={props.disabled}\n autoFocus={props.autoFocus}\n className={props.inputClassName}\n InputProps={{\n startAdornment: props.startAdornment,\n endAdornment: props.isLoading ? <CircularProgress size={rem(theme, 20)} /> : undefined,\n }}\n inputProps={{\n role: 'combobox',\n 'aria-expanded': props.open,\n 'aria-controls': props.open ? props.listId : undefined,\n 'aria-activedescendant': props.activeDescendant,\n 'aria-autocomplete': 'list',\n 'aria-label': props.inputAriaLabel,\n id: props.id,\n }}\n />\n </Box>\n );\n}\n\n/** Selected-item chips (multiple mode). */\nexport function ChipList<T>({\n items,\n getKey,\n getLabel,\n onRemove,\n chipClassName,\n}: {\n items: T[];\n getKey: (item: T) => string;\n getLabel: (item: T) => string;\n onRemove: (item: T) => void;\n chipClassName?: string;\n}): React.JSX.Element {\n return (\n <ChipContainer data-testid=\"selected-items-container\">\n {items.map((item, index) => (\n <Chip\n key={`${getKey(item)}-${index}`}\n label={getLabel(item)}\n onDelete={() => onRemove(item)}\n size=\"small\"\n className={chipClassName}\n data-testid={`selected-chip-${index}`}\n />\n ))}\n </ChipContainer>\n );\n}\n\n/** Default suggestion row: per-type icon + highlighted label + optional description. */\nexport function DefaultSuggestion<T>({\n item,\n state,\n getLabel,\n getDescription,\n getSuggestionType,\n matchMode,\n}: {\n item: T;\n state: SuggestionItemState;\n getLabel: (item: T) => string;\n getDescription?: (item: T) => string | undefined;\n getSuggestionType: (item: T) => SuggestionType | undefined;\n matchMode: MatchMode;\n}): React.JSX.Element {\n const label = getLabel(item);\n const description = getDescription?.(item);\n // Each run is a React text child, so the label is escaped however it reads.\n const segments = highlightLabel(label, state.query, matchMode);\n return (\n <Box\n sx={{\n display: 'flex',\n alignItems: 'center',\n gap: 1,\n width: '100%',\n // Bold every run the query matched, in any match mode but fuzzy — not\n // only a prefix, even under `startsWith` (not a yellow <mark> highlight).\n '& mark': { backgroundColor: 'transparent', color: 'inherit', fontWeight: 700 },\n }}\n >\n {suggestionTypeIcon(getSuggestionType(item))}\n <ListItemText\n primary={\n <span>\n {segments.map((segment, index) =>\n // The runs are derived from one label and never reorder, so the\n // position is a stable key.\n segment.match ? (\n <mark key={index}>{segment.text}</mark>\n ) : (\n <React.Fragment key={index}>{segment.text}</React.Fragment>\n ),\n )}\n </span>\n }\n secondary={description}\n />\n </Box>\n );\n}\n\n/** A \"Loading…\" row shown while async suggestions resolve. */\nexport function LoadingRow({ label }: { label: string }): React.JSX.Element {\n const theme = useTheme();\n return (\n <ListItem>\n <Box display=\"flex\" alignItems=\"center\" gap={1}>\n <CircularProgress size={rem(theme, 16)} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {label}\n </Typography>\n </Box>\n </ListItem>\n );\n}\n\n/** The empty \"No results found\" row. */\nexport function NoResultsRow({ label }: { label: string }): React.JSX.Element {\n return (\n <ListItem>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {label}\n </Typography>\n </ListItem>\n );\n}\n\n/** Keep the Popper the same width as its anchor input. */\nconst SAME_WIDTH_MODIFIER = {\n name: 'sameWidth',\n enabled: true,\n fn: ({ state }: { state: { elements: { reference?: unknown }; styles: { popper?: Record<string, string> }; rects: { reference: { width: number } } } }) => {\n if (state.elements.reference && state.styles.popper) {\n state.styles.popper.width = `${state.rects.reference.width}px`;\n }\n },\n phase: 'beforeWrite' as const,\n requires: ['computeStyles'],\n};\n\nexport interface SuggestionListBoxProps<T> {\n copy: AutocompleteCopy;\n open: boolean;\n anchorEl: HTMLElement | null;\n portal: boolean | { container?: HTMLElement };\n listRef: React.RefObject<HTMLUListElement | null>;\n listId: string;\n optionIdPrefix: string;\n items: T[];\n activeIndex: number;\n isLoading: boolean;\n showNoResults: boolean;\n getKey: (item: T) => string;\n onSelect: (item: T) => void;\n onHover: (index: number) => void;\n renderItem: (item: T, active: boolean) => React.ReactNode;\n listClassName?: string;\n itemClassName?: string;\n activeItemClassName?: string;\n}\n\n/** The suggestions dropdown: a same-width Popper with the option list + loading/empty rows. */\nexport function SuggestionListBox<T>({\n copy,\n open,\n anchorEl,\n portal,\n listRef,\n listId,\n optionIdPrefix,\n items,\n activeIndex,\n isLoading,\n showNoResults,\n getKey,\n onSelect,\n onHover,\n renderItem,\n listClassName,\n itemClassName,\n activeItemClassName,\n}: SuggestionListBoxProps<T>): React.JSX.Element {\n return (\n <StyledPopper\n open={open}\n anchorEl={anchorEl}\n placement=\"bottom-start\"\n disablePortal={!portal}\n modifiers={[SAME_WIDTH_MODIFIER]}\n >\n <Paper elevation={8} className={listClassName} data-testid=\"suggestions-dropdown\">\n <List\n ref={listRef}\n role=\"listbox\"\n id={listId}\n dense\n sx={{ maxHeight: sxRem(300), overflow: 'auto' }}\n data-testid=\"suggestions-list\"\n >\n {items.map((item, index) => (\n <ListItem\n key={getKey(item)}\n id={`${optionIdPrefix}-${index}`}\n role=\"option\"\n component=\"div\"\n sx={{\n cursor: 'pointer',\n backgroundColor: index === activeIndex ? 'action.hover' : 'transparent',\n '&:hover': { backgroundColor: 'action.hover' },\n }}\n aria-selected={index === activeIndex}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => onSelect(item)}\n onMouseEnter={() => onHover(index)}\n className={`${itemClassName} ${index === activeIndex ? activeItemClassName : ''}`}\n data-testid={`suggestion-item-${index}`}\n >\n {renderItem(item, index === activeIndex)}\n </ListItem>\n ))}\n {isLoading && <LoadingRow label={copy.loading} />}\n {showNoResults && <NoResultsRow label={copy.noResults} />}\n </List>\n </Paper>\n </StyledPopper>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
handleAutocompleteKey
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-PELRSZFU.js";
|
|
4
4
|
import {
|
|
5
5
|
defaultGetKey,
|
|
6
6
|
defaultGetLabel,
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
defaultOpenLink,
|
|
10
10
|
filterSuggestions,
|
|
11
11
|
firstGhostSuggestion
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-JXS56EW5.js";
|
|
13
13
|
|
|
14
14
|
// src/components/form/Autocomplete/useAutocomplete.ts
|
|
15
15
|
import { useEffect, useId, useRef, useState } from "react";
|
|
@@ -258,4 +258,4 @@ function useAutocomplete(p) {
|
|
|
258
258
|
export {
|
|
259
259
|
useAutocomplete
|
|
260
260
|
};
|
|
261
|
-
//# sourceMappingURL=chunk-
|
|
261
|
+
//# sourceMappingURL=chunk-HBRKA67N.js.map
|
|
@@ -67,9 +67,9 @@ function escapeRegExp(value) {
|
|
|
67
67
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
68
68
|
}
|
|
69
69
|
function highlightLabel(label, query, matchMode) {
|
|
70
|
-
if (!query || matchMode === "fuzzy") return label;
|
|
70
|
+
if (!query || matchMode === "fuzzy") return [{ text: label, match: false }];
|
|
71
71
|
const regex = new RegExp(`(${escapeRegExp(query)})`, "gi");
|
|
72
|
-
return label.
|
|
72
|
+
return label.split(regex).map((text, index) => ({ text, match: index % 2 === 1 })).filter((segment) => segment.text !== "");
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
export {
|
|
@@ -82,4 +82,4 @@ export {
|
|
|
82
82
|
firstGhostSuggestion,
|
|
83
83
|
highlightLabel
|
|
84
84
|
};
|
|
85
|
-
//# sourceMappingURL=chunk-
|
|
85
|
+
//# sourceMappingURL=chunk-JXS56EW5.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/form/Autocomplete/Autocomplete.helpers.ts"],"sourcesContent":["import type { SuggestionType } from './Autocomplete.types';\n\n/** Local filtering strategy. */\nexport type MatchMode = 'startsWith' | 'contains' | 'fuzzy';\n\n/** Default key extractor: id/key/value/label, else the item itself stringified. */\nexport const defaultGetKey = <T,>(item: T): string => {\n if (typeof item === 'string') return item;\n if (typeof item === 'object' && item !== null) {\n const obj = item as Record<string, unknown>;\n return String(obj.id ?? obj.key ?? obj.value ?? obj.label ?? obj);\n }\n return String(item);\n};\n\n/** Default label extractor: label/name/text, else the item itself stringified. */\nexport const defaultGetLabel = <T,>(item: T): string => {\n if (typeof item === 'string') return item;\n if (typeof item === 'object' && item !== null) {\n const obj = item as Record<string, unknown>;\n return String(obj.label ?? obj.name ?? obj.text ?? obj);\n }\n return String(item);\n};\n\n/** Default suggestion-type extractor: reads a `type: 'search' | 'link'` field. */\nexport const defaultGetSuggestionType = <T,>(item: T): SuggestionType | undefined => {\n if (typeof item === 'object' && item !== null) {\n const { type } = item as { type?: unknown };\n if (type === 'search' || type === 'link') return type;\n }\n return undefined;\n};\n\n/** Default URL extractor for `link` suggestions: reads a `url: string` field. */\nexport const defaultGetUrl = <T,>(item: T): string | undefined => {\n if (typeof item === 'object' && item !== null) {\n const { url } = item as { url?: unknown };\n if (typeof url === 'string') return url;\n }\n return undefined;\n};\n\n/** Default link opener: a new tab with `noopener,noreferrer`. SSR-guarded. */\nexport const defaultOpenLink = (url: string): void => {\n if (typeof window !== 'undefined') {\n window.open(url, '_blank', 'noopener,noreferrer');\n }\n};\n\n/** Fuzzy match: every char of `query` appears in `label`, in order. */\nfunction fuzzyMatch(label: string, query: string): boolean {\n let labelIndex = 0;\n for (let queryIndex = 0; queryIndex < query.length; queryIndex++) {\n const char = query[queryIndex];\n if (!char) continue;\n labelIndex = label.indexOf(char, labelIndex);\n if (labelIndex === -1) return false;\n labelIndex++;\n }\n return true;\n}\n\n/** True when `label` matches `query` under the given {@link MatchMode} (case-insensitive). */\nfunction matchesQuery(label: string, query: string, matchMode: MatchMode): boolean {\n const haystack = label.toLowerCase();\n const needle = query.toLowerCase();\n if (matchMode === 'startsWith') return haystack.startsWith(needle);\n if (matchMode === 'fuzzy') return fuzzyMatch(haystack, needle);\n return haystack.includes(needle);\n}\n\n/** Filter `suggestions` by `query` under `matchMode`; an empty query returns all. */\nexport function filterSuggestions<T>(\n suggestions: T[],\n query: string,\n matchMode: MatchMode,\n getLabel: (item: T) => string,\n): T[] {\n if (!query) return suggestions;\n return suggestions.filter((item) => matchesQuery(getLabel(item), query, matchMode));\n}\n\n/**\n * The first suggestion eligible for inline ghost completion: a non-`link` item\n * whose label starts with `inputValue` (case-insensitive) but isn't equal to it.\n * Shared by the ghost-text effect and ArrowRight completion.\n */\nexport function firstGhostSuggestion<T>(\n list: T[],\n inputValue: string,\n getLabel: (item: T) => string,\n getSuggestionType: (item: T) => SuggestionType | undefined,\n): T | undefined {\n const needle = inputValue.toLowerCase();\n return list.find((item) => {\n const label = getLabel(item).toLowerCase();\n return getSuggestionType(item) !== 'link' && label.startsWith(needle) && label !== needle;\n });\n}\n\n/** Escape a user query for safe use inside a `RegExp`. */\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n *
|
|
1
|
+
{"version":3,"sources":["../src/components/form/Autocomplete/Autocomplete.helpers.ts"],"sourcesContent":["import type { SuggestionType } from './Autocomplete.types';\n\n/** Local filtering strategy. */\nexport type MatchMode = 'startsWith' | 'contains' | 'fuzzy';\n\n/** Default key extractor: id/key/value/label, else the item itself stringified. */\nexport const defaultGetKey = <T,>(item: T): string => {\n if (typeof item === 'string') return item;\n if (typeof item === 'object' && item !== null) {\n const obj = item as Record<string, unknown>;\n return String(obj.id ?? obj.key ?? obj.value ?? obj.label ?? obj);\n }\n return String(item);\n};\n\n/** Default label extractor: label/name/text, else the item itself stringified. */\nexport const defaultGetLabel = <T,>(item: T): string => {\n if (typeof item === 'string') return item;\n if (typeof item === 'object' && item !== null) {\n const obj = item as Record<string, unknown>;\n return String(obj.label ?? obj.name ?? obj.text ?? obj);\n }\n return String(item);\n};\n\n/** Default suggestion-type extractor: reads a `type: 'search' | 'link'` field. */\nexport const defaultGetSuggestionType = <T,>(item: T): SuggestionType | undefined => {\n if (typeof item === 'object' && item !== null) {\n const { type } = item as { type?: unknown };\n if (type === 'search' || type === 'link') return type;\n }\n return undefined;\n};\n\n/** Default URL extractor for `link` suggestions: reads a `url: string` field. */\nexport const defaultGetUrl = <T,>(item: T): string | undefined => {\n if (typeof item === 'object' && item !== null) {\n const { url } = item as { url?: unknown };\n if (typeof url === 'string') return url;\n }\n return undefined;\n};\n\n/** Default link opener: a new tab with `noopener,noreferrer`. SSR-guarded. */\nexport const defaultOpenLink = (url: string): void => {\n if (typeof window !== 'undefined') {\n window.open(url, '_blank', 'noopener,noreferrer');\n }\n};\n\n/** Fuzzy match: every char of `query` appears in `label`, in order. */\nfunction fuzzyMatch(label: string, query: string): boolean {\n let labelIndex = 0;\n for (let queryIndex = 0; queryIndex < query.length; queryIndex++) {\n const char = query[queryIndex];\n if (!char) continue;\n labelIndex = label.indexOf(char, labelIndex);\n if (labelIndex === -1) return false;\n labelIndex++;\n }\n return true;\n}\n\n/** True when `label` matches `query` under the given {@link MatchMode} (case-insensitive). */\nfunction matchesQuery(label: string, query: string, matchMode: MatchMode): boolean {\n const haystack = label.toLowerCase();\n const needle = query.toLowerCase();\n if (matchMode === 'startsWith') return haystack.startsWith(needle);\n if (matchMode === 'fuzzy') return fuzzyMatch(haystack, needle);\n return haystack.includes(needle);\n}\n\n/** Filter `suggestions` by `query` under `matchMode`; an empty query returns all. */\nexport function filterSuggestions<T>(\n suggestions: T[],\n query: string,\n matchMode: MatchMode,\n getLabel: (item: T) => string,\n): T[] {\n if (!query) return suggestions;\n return suggestions.filter((item) => matchesQuery(getLabel(item), query, matchMode));\n}\n\n/**\n * The first suggestion eligible for inline ghost completion: a non-`link` item\n * whose label starts with `inputValue` (case-insensitive) but isn't equal to it.\n * Shared by the ghost-text effect and ArrowRight completion.\n */\nexport function firstGhostSuggestion<T>(\n list: T[],\n inputValue: string,\n getLabel: (item: T) => string,\n getSuggestionType: (item: T) => SuggestionType | undefined,\n): T | undefined {\n const needle = inputValue.toLowerCase();\n return list.find((item) => {\n const label = getLabel(item).toLowerCase();\n return getSuggestionType(item) !== 'link' && label.startsWith(needle) && label !== needle;\n });\n}\n\n/** Escape a user query for safe use inside a `RegExp`. */\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/** One run of a highlighted label: plain text, or text the query matched. */\nexport interface HighlightSegment {\n text: string;\n match: boolean;\n}\n\n/**\n * Split `label` into the runs the query matches and the runs between them, in\n * order, for the default suggestion renderer to wrap the matches in `<mark>`.\n * Every occurrence of the query matches, case-insensitively and literally (regex\n * metacharacters are escaped). Fuzzy mode (non-contiguous matches) and an empty\n * query yield the whole label as one unmatched run.\n *\n * Returns TEXT, never markup: a label is caller data (in a consumer, a user's\n * own display name), so the renderer must hand each run to React as a text\n * child, which escapes it. Building an HTML string here and injecting it is how\n * a label of `<img onerror=…>` once ran script in the host page.\n */\nexport function highlightLabel(label: string, query: string, matchMode: MatchMode): HighlightSegment[] {\n if (!query || matchMode === 'fuzzy') return [{ text: label, match: false }];\n // The capturing group makes `split` keep each match, at the odd indices.\n const regex = new RegExp(`(${escapeRegExp(query)})`, 'gi');\n return label\n .split(regex)\n .map((text, index) => ({ text, match: index % 2 === 1 }))\n .filter((segment) => segment.text !== '');\n}\n"],"mappings":";AAMO,IAAM,gBAAgB,CAAK,SAAoB;AACpD,MAAI,OAAO,SAAS,SAAU,QAAO;AACrC,MAAI,OAAO,SAAS,YAAY,SAAS,MAAM;AAC7C,UAAM,MAAM;AACZ,WAAO,OAAO,IAAI,MAAM,IAAI,OAAO,IAAI,SAAS,IAAI,SAAS,GAAG;AAAA,EAClE;AACA,SAAO,OAAO,IAAI;AACpB;AAGO,IAAM,kBAAkB,CAAK,SAAoB;AACtD,MAAI,OAAO,SAAS,SAAU,QAAO;AACrC,MAAI,OAAO,SAAS,YAAY,SAAS,MAAM;AAC7C,UAAM,MAAM;AACZ,WAAO,OAAO,IAAI,SAAS,IAAI,QAAQ,IAAI,QAAQ,GAAG;AAAA,EACxD;AACA,SAAO,OAAO,IAAI;AACpB;AAGO,IAAM,2BAA2B,CAAK,SAAwC;AACnF,MAAI,OAAO,SAAS,YAAY,SAAS,MAAM;AAC7C,UAAM,EAAE,KAAK,IAAI;AACjB,QAAI,SAAS,YAAY,SAAS,OAAQ,QAAO;AAAA,EACnD;AACA,SAAO;AACT;AAGO,IAAM,gBAAgB,CAAK,SAAgC;AAChE,MAAI,OAAO,SAAS,YAAY,SAAS,MAAM;AAC7C,UAAM,EAAE,IAAI,IAAI;AAChB,QAAI,OAAO,QAAQ,SAAU,QAAO;AAAA,EACtC;AACA,SAAO;AACT;AAGO,IAAM,kBAAkB,CAAC,QAAsB;AACpD,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO,KAAK,KAAK,UAAU,qBAAqB;AAAA,EAClD;AACF;AAGA,SAAS,WAAW,OAAe,OAAwB;AACzD,MAAI,aAAa;AACjB,WAAS,aAAa,GAAG,aAAa,MAAM,QAAQ,cAAc;AAChE,UAAM,OAAO,MAAM,UAAU;AAC7B,QAAI,CAAC,KAAM;AACX,iBAAa,MAAM,QAAQ,MAAM,UAAU;AAC3C,QAAI,eAAe,GAAI,QAAO;AAC9B;AAAA,EACF;AACA,SAAO;AACT;AAGA,SAAS,aAAa,OAAe,OAAe,WAA+B;AACjF,QAAM,WAAW,MAAM,YAAY;AACnC,QAAM,SAAS,MAAM,YAAY;AACjC,MAAI,cAAc,aAAc,QAAO,SAAS,WAAW,MAAM;AACjE,MAAI,cAAc,QAAS,QAAO,WAAW,UAAU,MAAM;AAC7D,SAAO,SAAS,SAAS,MAAM;AACjC;AAGO,SAAS,kBACd,aACA,OACA,WACA,UACK;AACL,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,YAAY,OAAO,CAAC,SAAS,aAAa,SAAS,IAAI,GAAG,OAAO,SAAS,CAAC;AACpF;AAOO,SAAS,qBACd,MACA,YACA,UACA,mBACe;AACf,QAAM,SAAS,WAAW,YAAY;AACtC,SAAO,KAAK,KAAK,CAAC,SAAS;AACzB,UAAM,QAAQ,SAAS,IAAI,EAAE,YAAY;AACzC,WAAO,kBAAkB,IAAI,MAAM,UAAU,MAAM,WAAW,MAAM,KAAK,UAAU;AAAA,EACrF,CAAC;AACH;AAGA,SAAS,aAAa,OAAuB;AAC3C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;AAoBO,SAAS,eAAe,OAAe,OAAe,WAA0C;AACrG,MAAI,CAAC,SAAS,cAAc,QAAS,QAAO,CAAC,EAAE,MAAM,OAAO,OAAO,MAAM,CAAC;AAE1E,QAAM,QAAQ,IAAI,OAAO,IAAI,aAAa,KAAK,CAAC,KAAK,IAAI;AACzD,SAAO,MACJ,MAAM,KAAK,EACX,IAAI,CAAC,MAAM,WAAW,EAAE,MAAM,OAAO,QAAQ,MAAM,EAAE,EAAE,EACvD,OAAO,CAAC,YAAY,QAAQ,SAAS,EAAE;AAC5C;","names":[]}
|