@wix/web5-core 1.56.0 → 1.57.0
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/cjs/client/wixAuthFetch.js +2 -2
- package/dist/cjs/client/wixAuthFetch.js.map +1 -1
- package/dist/cjs/component/componentDefinitions/HeroEntitySectionDefinition.js +20 -15
- package/dist/cjs/component/componentDefinitions/HeroEntitySectionDefinition.js.map +1 -1
- package/dist/cjs/component/componentDefinitions/HeroSectionDefinition.js +12 -4
- package/dist/cjs/component/componentDefinitions/HeroSectionDefinition.js.map +1 -1
- package/dist/cjs/component/componentDefinitions/listItemsSectionDefinition.js +24 -29
- package/dist/cjs/component/componentDefinitions/listItemsSectionDefinition.js.map +1 -1
- package/dist/cjs/component/componentParser.js +8 -1
- package/dist/cjs/component/componentParser.js.map +1 -1
- package/dist/cjs/component/types.js.map +1 -1
- package/dist/cjs/components/placement/PlacementResponseRenderer.css +0 -4
- package/dist/cjs/components/placement/PlacementResponseRenderer.js +11 -33
- package/dist/cjs/components/placement/PlacementResponseRenderer.js.map +1 -1
- package/dist/cjs/components/placement/buildPlacementDependencies.js.map +1 -1
- package/dist/cjs/components/ui/OptimizedImage.js +59 -14
- package/dist/cjs/components/ui/OptimizedImage.js.map +1 -1
- package/dist/cjs/components/ui/SectionSkeleton.css +19 -0
- package/dist/cjs/components/ui/SectionSkeleton.js +8 -4
- package/dist/cjs/components/ui/SectionSkeleton.js.map +1 -1
- package/dist/cjs/components/ui/UserQuery.css +72 -0
- package/dist/cjs/components/ui/UserQuery.js +131 -37
- package/dist/cjs/components/ui/UserQuery.js.map +1 -1
- package/dist/cjs/context/UserQueryContext.js +34 -0
- package/dist/cjs/context/UserQueryContext.js.map +1 -0
- package/dist/cjs/entity/defaultExtractor.js +9 -2
- package/dist/cjs/entity/defaultExtractor.js.map +1 -1
- package/dist/cjs/entity/fetchEntityListData.js +13 -26
- package/dist/cjs/entity/fetchEntityListData.js.map +1 -1
- package/dist/cjs/entity/index.js +5 -1
- package/dist/cjs/entity/index.js.map +1 -1
- package/dist/cjs/entity/listEntityItems.js +90 -0
- package/dist/cjs/entity/listEntityItems.js.map +1 -0
- package/dist/cjs/hostScope.js +63 -0
- package/dist/cjs/hostScope.js.map +1 -0
- package/dist/cjs/index.js +38 -13
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/patternValidator/validator.test.js +61 -61
- package/dist/cjs/patternValidator/validator.test.js.map +1 -1
- package/dist/cjs/registry/ComponentRegistry.js +22 -2
- package/dist/cjs/registry/ComponentRegistry.js.map +1 -1
- package/dist/cjs/registry/index.js +1 -3
- package/dist/cjs/registry/index.js.map +1 -1
- package/dist/cjs/registry/types.js.map +1 -1
- package/dist/cjs/styles/base-utilities.css +35 -0
- package/dist/cjs/styles/components.css +1 -0
- package/dist/cjs/styles/host-reset.css +81 -0
- package/dist/cjs/styles/tailwind-theme.css +6 -6
- package/dist/cjs/utils/backendEnvironment.js +78 -0
- package/dist/cjs/utils/backendEnvironment.js.map +1 -0
- package/dist/cjs/utils/entityLinkParser.js +3 -1
- package/dist/cjs/utils/entityLinkParser.js.map +1 -1
- package/dist/cjs/utils/imageBackdrop.js +269 -0
- package/dist/cjs/utils/imageBackdrop.js.map +1 -0
- package/dist/cjs/utils/intentExtractor.js +20 -9
- package/dist/cjs/utils/intentExtractor.js.map +1 -1
- package/dist/cjs/utils/productBackHandoff.js +106 -0
- package/dist/cjs/utils/productBackHandoff.js.map +1 -0
- package/dist/cjs/utils/refreshPrompts.js +72 -0
- package/dist/cjs/utils/refreshPrompts.js.map +1 -0
- package/dist/esm/client/wixAuthFetch.js +2 -2
- package/dist/esm/client/wixAuthFetch.js.map +1 -1
- package/dist/esm/component/componentDefinitions/HeroEntitySectionDefinition.js +20 -15
- package/dist/esm/component/componentDefinitions/HeroEntitySectionDefinition.js.map +1 -1
- package/dist/esm/component/componentDefinitions/HeroSectionDefinition.js +12 -4
- package/dist/esm/component/componentDefinitions/HeroSectionDefinition.js.map +1 -1
- package/dist/esm/component/componentDefinitions/listItemsSectionDefinition.js +25 -30
- package/dist/esm/component/componentDefinitions/listItemsSectionDefinition.js.map +1 -1
- package/dist/esm/component/componentParser.js +8 -1
- package/dist/esm/component/componentParser.js.map +1 -1
- package/dist/esm/component/types.js.map +1 -1
- package/dist/esm/components/placement/PlacementResponseRenderer.css +0 -4
- package/dist/esm/components/placement/PlacementResponseRenderer.js +1 -16
- package/dist/esm/components/placement/PlacementResponseRenderer.js.map +1 -1
- package/dist/esm/components/placement/buildPlacementDependencies.js.map +1 -1
- package/dist/esm/components/ui/OptimizedImage.js +50 -5
- package/dist/esm/components/ui/OptimizedImage.js.map +1 -1
- package/dist/esm/components/ui/SectionSkeleton.css +19 -0
- package/dist/esm/components/ui/SectionSkeleton.js +5 -1
- package/dist/esm/components/ui/SectionSkeleton.js.map +1 -1
- package/dist/esm/components/ui/UserQuery.css +72 -0
- package/dist/esm/components/ui/UserQuery.js +52 -6
- package/dist/esm/components/ui/UserQuery.js.map +1 -1
- package/dist/esm/context/UserQueryContext.js +21 -0
- package/dist/esm/context/UserQueryContext.js.map +1 -0
- package/dist/esm/entity/defaultExtractor.js +9 -2
- package/dist/esm/entity/defaultExtractor.js.map +1 -1
- package/dist/esm/entity/fetchEntityListData.js +14 -26
- package/dist/esm/entity/fetchEntityListData.js.map +1 -1
- package/dist/esm/entity/index.js +1 -0
- package/dist/esm/entity/index.js.map +1 -1
- package/dist/esm/entity/listEntityItems.js +85 -0
- package/dist/esm/entity/listEntityItems.js.map +1 -0
- package/dist/esm/hostScope.js +59 -0
- package/dist/esm/hostScope.js.map +1 -0
- package/dist/esm/index.js +13 -4
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/patternValidator/validator.test.js +61 -61
- package/dist/esm/patternValidator/validator.test.js.map +1 -1
- package/dist/esm/registry/ComponentRegistry.js +22 -2
- package/dist/esm/registry/ComponentRegistry.js.map +1 -1
- package/dist/esm/registry/index.js +0 -1
- package/dist/esm/registry/index.js.map +1 -1
- package/dist/esm/registry/types.js.map +1 -1
- package/dist/esm/styles/base-utilities.css +35 -0
- package/dist/esm/styles/components.css +1 -0
- package/dist/esm/styles/host-reset.css +81 -0
- package/dist/esm/styles/tailwind-theme.css +6 -6
- package/dist/esm/utils/backendEnvironment.js +71 -0
- package/dist/esm/utils/backendEnvironment.js.map +1 -0
- package/dist/esm/utils/entityLinkParser.js +3 -1
- package/dist/esm/utils/entityLinkParser.js.map +1 -1
- package/dist/esm/utils/imageBackdrop.js +262 -0
- package/dist/esm/utils/imageBackdrop.js.map +1 -0
- package/dist/esm/utils/intentExtractor.js +21 -12
- package/dist/esm/utils/intentExtractor.js.map +1 -1
- package/dist/esm/utils/productBackHandoff.js +100 -0
- package/dist/esm/utils/productBackHandoff.js.map +1 -0
- package/dist/esm/utils/refreshPrompts.js +67 -0
- package/dist/esm/utils/refreshPrompts.js.map +1 -0
- package/dist/types/component/componentDefinitions/HeroEntitySectionDefinition.d.ts.map +1 -1
- package/dist/types/component/componentDefinitions/HeroSectionDefinition.d.ts.map +1 -1
- package/dist/types/component/componentDefinitions/listItemsSectionDefinition.d.ts +2 -2
- package/dist/types/component/componentDefinitions/listItemsSectionDefinition.d.ts.map +1 -1
- package/dist/types/component/componentParser.d.ts.map +1 -1
- package/dist/types/component/types.d.ts +1 -5
- package/dist/types/component/types.d.ts.map +1 -1
- package/dist/types/components/placement/PlacementResponseRenderer.d.ts +1 -1
- package/dist/types/components/placement/PlacementResponseRenderer.d.ts.map +1 -1
- package/dist/types/components/placement/buildPlacementDependencies.d.ts +1 -2
- package/dist/types/components/placement/buildPlacementDependencies.d.ts.map +1 -1
- package/dist/types/components/ui/OptimizedImage.d.ts +9 -0
- package/dist/types/components/ui/OptimizedImage.d.ts.map +1 -1
- package/dist/types/components/ui/SectionSkeleton.d.ts.map +1 -1
- package/dist/types/components/ui/UserQuery.d.ts +21 -0
- package/dist/types/components/ui/UserQuery.d.ts.map +1 -1
- package/dist/types/context/UserQueryContext.d.ts +12 -0
- package/dist/types/context/UserQueryContext.d.ts.map +1 -0
- package/dist/types/entity/defaultExtractor.d.ts.map +1 -1
- package/dist/types/entity/fetchEntityListData.d.ts +0 -2
- package/dist/types/entity/fetchEntityListData.d.ts.map +1 -1
- package/dist/types/entity/index.d.ts +1 -0
- package/dist/types/entity/index.d.ts.map +1 -1
- package/dist/types/entity/listEntityItems.d.ts +40 -0
- package/dist/types/entity/listEntityItems.d.ts.map +1 -0
- package/dist/types/hostScope.d.ts +83 -0
- package/dist/types/hostScope.d.ts.map +1 -0
- package/dist/types/index.d.ts +11 -7
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/registry/ComponentRegistry.d.ts +8 -0
- package/dist/types/registry/ComponentRegistry.d.ts.map +1 -1
- package/dist/types/registry/index.d.ts +0 -1
- package/dist/types/registry/index.d.ts.map +1 -1
- package/dist/types/registry/types.d.ts +5 -6
- package/dist/types/registry/types.d.ts.map +1 -1
- package/dist/types/utils/backendEnvironment.d.ts +30 -0
- package/dist/types/utils/backendEnvironment.d.ts.map +1 -0
- package/dist/types/utils/entityLinkParser.d.ts.map +1 -1
- package/dist/types/utils/imageBackdrop.d.ts +102 -0
- package/dist/types/utils/imageBackdrop.d.ts.map +1 -0
- package/dist/types/utils/intentExtractor.d.ts +21 -4
- package/dist/types/utils/intentExtractor.d.ts.map +1 -1
- package/dist/types/utils/productBackHandoff.d.ts +43 -0
- package/dist/types/utils/productBackHandoff.d.ts.map +1 -0
- package/dist/types/utils/refreshPrompts.d.ts +32 -0
- package/dist/types/utils/refreshPrompts.d.ts.map +1 -0
- package/package.json +4 -3
- package/src/components/placement/PlacementResponseRenderer.css +265 -0
- package/src/components/ui/Disclaimer.css +35 -0
- package/src/components/ui/FeedbackBar.css +267 -0
- package/src/components/ui/PlacementLoader.css +101 -0
- package/src/components/ui/PromptEntryEmptyState.css +172 -0
- package/src/components/ui/SearchSection.css +538 -0
- package/src/components/ui/SectionSkeleton.css +128 -0
- package/src/components/ui/UserQuery.css +72 -0
- package/src/styles/base-utilities.css +35 -0
- package/src/styles/components.css +1 -0
- package/src/styles/host-reset.css +81 -0
- package/src/styles/tailwind-theme.css +6 -6
- package/dist/cjs/registry/Intent.js +0 -19
- package/dist/cjs/registry/Intent.js.map +0 -1
- package/dist/cjs/utils/conversationApi.js +0 -52
- package/dist/cjs/utils/conversationApi.js.map +0 -1
- package/dist/cjs/utils/conversationMode.js +0 -23
- package/dist/cjs/utils/conversationMode.js.map +0 -1
- package/dist/esm/registry/Intent.js +0 -15
- package/dist/esm/registry/Intent.js.map +0 -1
- package/dist/esm/utils/conversationApi.js +0 -45
- package/dist/esm/utils/conversationApi.js.map +0 -1
- package/dist/esm/utils/conversationMode.js +0 -18
- package/dist/esm/utils/conversationMode.js.map +0 -1
- package/dist/types/registry/Intent.d.ts +0 -12
- package/dist/types/registry/Intent.d.ts.map +0 -1
- package/dist/types/utils/conversationApi.d.ts +0 -26
- package/dist/types/utils/conversationApi.d.ts.map +0 -1
- package/dist/types/utils/conversationMode.d.ts +0 -16
- package/dist/types/utils/conversationMode.d.ts.map +0 -1
|
@@ -84,21 +84,6 @@ const StarIcon = () => /*#__PURE__*/React.createElement('svg', {
|
|
|
84
84
|
d: 'M8 0 L9.6 6.4 L16 8 L9.6 9.6 L8 16 L6.4 9.6 L0 8 L6.4 6.4 Z',
|
|
85
85
|
fill: 'currentColor'
|
|
86
86
|
}));
|
|
87
|
-
const ChevronIcon = () => /*#__PURE__*/React.createElement('svg', {
|
|
88
|
-
className: 'w5-placement__show-more-icon',
|
|
89
|
-
viewBox: '0 0 16 16',
|
|
90
|
-
width: 14,
|
|
91
|
-
height: 14,
|
|
92
|
-
'aria-hidden': true,
|
|
93
|
-
focusable: false
|
|
94
|
-
}, /*#__PURE__*/React.createElement('path', {
|
|
95
|
-
d: 'M4 6 L8 10 L12 6',
|
|
96
|
-
fill: 'none',
|
|
97
|
-
stroke: 'currentColor',
|
|
98
|
-
strokeWidth: 1.6,
|
|
99
|
-
strokeLinecap: 'round',
|
|
100
|
-
strokeLinejoin: 'round'
|
|
101
|
-
}));
|
|
102
87
|
export const PlacementResponseRenderer = _ref2 => {
|
|
103
88
|
let {
|
|
104
89
|
sections,
|
|
@@ -168,6 +153,6 @@ export const PlacementResponseRenderer = _ref2 => {
|
|
|
168
153
|
className: "w5-placement__show-more",
|
|
169
154
|
href: showMoreHref,
|
|
170
155
|
"aria-label": "See the full answer on the AI page"
|
|
171
|
-
}, /*#__PURE__*/React.createElement("span", null, showMoreLabel ?? '
|
|
156
|
+
}, /*#__PURE__*/React.createElement("span", null, showMoreLabel ?? 'Continue in Form AI'))));
|
|
172
157
|
};
|
|
173
158
|
//# sourceMappingURL=PlacementResponseRenderer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["React","Fragment","PlacementSmoothHeight","_ref","children","innerRef","useRef","height","setHeight","useState","useEffect","el","current","ResizeObserver","observer","offsetHeight","observe","disconnect","createElement","className","style","undefined","ref","StarIcon","viewBox","width","focusable","d","fill","ChevronIcon","stroke","strokeWidth","strokeLinecap","strokeLinejoin","PlacementResponseRenderer","_ref2","sections","prompt","eyebrow","query","variant","streaming","clamp","clampHeight","showMoreHref","showMoreLabel","rootRef","overflowing","setOverflowing","measure","scrollHeight","clientHeight","rootClass","filter","Boolean","join","sectionNodes","map","section","i","key","id","sectionType","component","showMore","maxHeight","href"],"sources":["../../../../src/components/placement/PlacementResponseRenderer.tsx"],"sourcesContent":["/**\n * PlacementResponseRenderer (DL #088 D3.2, DL #097 visual pass).\n *\n * Renders the AI response inside a placement slot. Takes already-parsed\n * sections from the caller's parser and renders the placement chrome\n * (FormAI eyebrow + optional user-query chip) above them.\n *\n * The renderer deliberately does NOT call the parser itself — that's the\n * caller's job (the placement bundle wires the parser of its choice).\n *\n * No post-parse filtering: hero / next-steps exclusion is the **prompt's**\n * job (the LLM is instructed not to emit them), and there's no\n * `maxComponents` cap. Placement-specific designs for a section type (e.g. a\n * tailored rich-text fallback) belong as `{ placement: true }` registry\n * variants in the client package — `resolveSection(type, { placement: true })`\n * picks them automatically.\n *\n * Visual chrome (Figma 128:5251 framing / 128:5258 conversation):\n * - `variant` drives the card; both variants show the highlight border.\n * - `eyebrow` renders the FormAI star + label (chrome, opt-in).\n * - `query` renders the user's question as a chip (conversation only).\n * Styles live in `./PlacementResponseRenderer.css` (injected with the client UMD).\n */\nimport React, { Fragment, type FC, type ReactNode } from 'react';\nimport type { PlacementVariant } from '../../types/placement';\n\n/**\n * Minimal section shape the renderer needs. Designed to match\n * `web50-server-ui`'s `PageSection` shape (with a pre-instantiated `component`)\n * so the renderer works against today's parser output without modification.\n */\nexport interface PlacementSection {\n /** Stable id for React key. Falls back to `sectionType + index` if absent. */\n id?: string;\n /** Section type key (e.g., 'narrative', 'list-items', 'pros-cons'). */\n sectionType: string;\n /** Pre-instantiated React element from the parser. */\n component: ReactNode;\n}\n\n/**\n * Placement visual variant → `w5-placement--<variant>` class. Defined in\n * `../../types/placement` (DL #106 D1: free-form string, no longer a closed\n * `'informational' | 'conversation'` union) and re-exported here for back-compat\n * with existing `from '.../PlacementResponseRenderer'` imports.\n */\nexport type { PlacementVariant };\n\nexport interface PlacementResponseRendererProps {\n /** Already-parsed sections from `parseMarkdownToComponents`. */\n sections: readonly PlacementSection[];\n /** Deprecated plain-text prompt header (kept for back-compat). */\n prompt?: string;\n /**\n * Eyebrow label shown with the accent star at the top (e.g. \"FormAI\").\n * Chrome — opt-in; omit to render no eyebrow.\n */\n eyebrow?: string;\n /**\n * The user's question, rendered as a chip above the answer (conversation\n * variant, DL #097). Omit for the informational variant.\n */\n query?: string;\n /** Visual variant — both add the persistent gradient highlight border. */\n variant?: PlacementVariant;\n /**\n * Clamp the answer body to a max height with a fade-out and a \"Show more\"\n * affordance (DL #113) — Google-AI-Overview style. Opt-in; used by the\n * `searchResults` placement. The body is capped at `clampHeight` (or the\n * `--w5-placement-clamp-height` token); the fade + button appear only once\n * the content overflows and a `showMoreHref` is supplied.\n */\n clamp?: boolean;\n /** Clamp height in px (DL #113). Overrides `--w5-placement-clamp-height`. */\n clampHeight?: number;\n /**\n * Destination for \"Show more\" (DL #113) — the main Web5 page seeded with the\n * visitor's search terms (`<mainPage>?initialQuestion=<q>`). When absent the\n * button is not rendered (clamp + fade still apply).\n */\n showMoreHref?: string;\n /** Label for the \"Show more\" button (DL #113). Default `'Show more'`. */\n showMoreLabel?: string;\n /**\n * True while the answer is still streaming in. The highlight border stays\n * solid for the whole render and only starts its spin once this flips to\n * false (answer settled); it remains visible afterwards.\n */\n streaming?: boolean;\n /** Optional extra className for the placement root. */\n className?: string;\n}\n\n/**\n * Smooths out step-wise height changes in the placement slot: the\n * loader→content swap and each streamed section appending. `height: auto`\n * changes aren't CSS-animatable, so we measure the content's natural height\n * with a ResizeObserver and transition an explicit `height` on the wrapper\n * (see `.w5-placement__body` in the CSS). Falls back to natural auto-height\n * when ResizeObserver is unavailable (SSR, old jsdom).\n *\n * Mounted by the placement stream app (embed-placement) AROUND the\n * loader/content swap — not inside this renderer — so the swap itself\n * animates too.\n */\nexport const PlacementSmoothHeight: FC<{ children: ReactNode }> = ({\n children,\n}) => {\n const innerRef = React.useRef<HTMLDivElement | null>(null);\n const [height, setHeight] = React.useState<number | null>(null);\n\n React.useEffect(() => {\n const el = innerRef.current;\n if (!el || typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver(() => {\n setHeight(el.offsetHeight);\n });\n observer.observe(el);\n return () => observer.disconnect();\n }, []);\n\n return (\n <div\n className=\"w5-placement__body\"\n style={height != null ? { height } : undefined}\n >\n {/* flow-root (in CSS) keeps child margins inside the measured box. */}\n <div ref={innerRef} className=\"w5-placement__body-inner\">\n {children}\n </div>\n </div>\n );\n};\n\nconst StarIcon: FC = () =>\n React.createElement(\n 'svg',\n {\n className: 'w5-placement__eyebrow-icon',\n viewBox: '0 0 16 16',\n width: 13,\n height: 13,\n 'aria-hidden': true,\n focusable: false,\n },\n React.createElement('path', {\n d: 'M8 0 L9.6 6.4 L16 8 L9.6 9.6 L8 16 L6.4 9.6 L0 8 L6.4 6.4 Z',\n fill: 'currentColor',\n }),\n );\n\nconst ChevronIcon: FC = () =>\n React.createElement(\n 'svg',\n {\n className: 'w5-placement__show-more-icon',\n viewBox: '0 0 16 16',\n width: 14,\n height: 14,\n 'aria-hidden': true,\n focusable: false,\n },\n React.createElement('path', {\n d: 'M4 6 L8 10 L12 6',\n fill: 'none',\n stroke: 'currentColor',\n strokeWidth: 1.6,\n strokeLinecap: 'round',\n strokeLinejoin: 'round',\n }),\n );\n\nexport const PlacementResponseRenderer: FC<PlacementResponseRendererProps> = ({\n sections,\n prompt,\n eyebrow,\n query,\n variant,\n streaming,\n className,\n clamp,\n clampHeight,\n showMoreHref,\n showMoreLabel,\n}) => {\n // Overflow detection for the clamp (DL #113). The clamp caps the *root* card\n // (not a wrapper) so the answer sections stay direct children of the\n // placement root — clients lay them out with `:has(> …)` direct-child grids\n // (e.g. feature-com's search-overview two-column layout), which a wrapper\n // would break. With `max-height` + `overflow: hidden` the root's scrollHeight\n // is the full content and clientHeight is the capped box; the fade + \"Show\n // more\" only appear when the content actually overflows (a fade under a short\n // answer reads as broken). Re-measure on every section change (streaming) and\n // on container resize.\n const rootRef = React.useRef<HTMLElement | null>(null);\n const [overflowing, setOverflowing] = React.useState(false);\n\n React.useEffect(() => {\n if (!clamp) {\n setOverflowing(false);\n return;\n }\n const el = rootRef.current;\n if (!el) return;\n const measure = (): void =>\n setOverflowing(el.scrollHeight > el.clientHeight + 1);\n measure();\n if (typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver(measure);\n observer.observe(el);\n return () => observer.disconnect();\n }, [clamp, clampHeight, sections]);\n\n const rootClass = [\n 'w5-placement',\n variant ? `w5-placement--${variant}` : '',\n streaming ? 'w5-placement--streaming' : '',\n clamp ? 'w5-placement--clamped' : '',\n className ?? '',\n ]\n .filter(Boolean)\n .join(' ');\n\n const sectionNodes = sections.map((section, i) => (\n <Fragment key={section.id ?? `${section.sectionType}-${i}`}>\n {section.component}\n </Fragment>\n ));\n\n // The fade + button overlay the bottom of the clamped card. The button only\n // appears once the content actually overflows and a destination is supplied.\n const showMore = !!clamp && overflowing && !!showMoreHref;\n\n return (\n <section\n ref={rootRef}\n className={rootClass}\n data-w5-placement-renderer=\"\"\n style={\n clamp && clampHeight != null\n ? { maxHeight: `${clampHeight}px` }\n : undefined\n }\n >\n {eyebrow && (\n <div className=\"w5-placement__eyebrow\">\n <StarIcon />\n <span className=\"w5-placement__eyebrow-label\">{eyebrow}</span>\n </div>\n )}\n {query && <p className=\"w5-placement__query\">{query}</p>}\n {prompt && <header className=\"w5-placement__prompt\">{prompt}</header>}\n {sectionNodes}\n {/* Fade + \"Show more\" overlay the bottom of the clamped card. Both are\n absolutely positioned (out of flow), so they don't disturb the\n client's grid placement of the sections, and are clipped by the\n root's `overflow: hidden`. */}\n {showMore && (\n <>\n <div className=\"w5-placement__fade\" aria-hidden=\"true\" />\n <a\n className=\"w5-placement__show-more\"\n href={showMoreHref}\n aria-label=\"See the full answer on the AI page\"\n >\n <span>{showMoreLabel ?? 'Show more'}</span>\n <ChevronIcon />\n </a>\n </>\n )}\n </section>\n );\n};\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["React","Fragment","PlacementSmoothHeight","_ref","children","innerRef","useRef","height","setHeight","useState","useEffect","el","current","ResizeObserver","observer","offsetHeight","observe","disconnect","createElement","className","style","undefined","ref","StarIcon","viewBox","width","focusable","d","fill","PlacementResponseRenderer","_ref2","sections","prompt","eyebrow","query","variant","streaming","clamp","clampHeight","showMoreHref","showMoreLabel","rootRef","overflowing","setOverflowing","measure","scrollHeight","clientHeight","rootClass","filter","Boolean","join","sectionNodes","map","section","i","key","id","sectionType","component","showMore","maxHeight","href"],"sources":["../../../../src/components/placement/PlacementResponseRenderer.tsx"],"sourcesContent":["/**\n * PlacementResponseRenderer (DL #088 D3.2, DL #097 visual pass).\n *\n * Renders the AI response inside a placement slot. Takes already-parsed\n * sections from the caller's parser and renders the placement chrome\n * (FormAI eyebrow + optional user-query chip) above them.\n *\n * The renderer deliberately does NOT call the parser itself — that's the\n * caller's job (the placement bundle wires the parser of its choice).\n *\n * No post-parse filtering: hero / next-steps exclusion is the **prompt's**\n * job (the LLM is instructed not to emit them), and there's no\n * `maxComponents` cap. Placement-specific designs for a section type (e.g. a\n * tailored rich-text fallback) belong as `{ placement: true }` registry\n * variants in the client package — `resolveSection(type, { placement: true })`\n * picks them automatically.\n *\n * Visual chrome (Figma 128:5251 framing / 128:5258 conversation):\n * - `variant` drives the card; both variants show the highlight border.\n * - `eyebrow` renders the FormAI star + label (chrome, opt-in).\n * - `query` renders the user's question as a chip (conversation only).\n * Styles live in `./PlacementResponseRenderer.css` (injected with the client UMD).\n */\nimport React, { Fragment, type FC, type ReactNode } from 'react';\nimport type { PlacementVariant } from '../../types/placement';\n\n/**\n * Minimal section shape the renderer needs. Designed to match\n * `web50-server-ui`'s `PageSection` shape (with a pre-instantiated `component`)\n * so the renderer works against today's parser output without modification.\n */\nexport interface PlacementSection {\n /** Stable id for React key. Falls back to `sectionType + index` if absent. */\n id?: string;\n /** Section type key (e.g., 'narrative', 'list-items', 'pros-cons'). */\n sectionType: string;\n /** Pre-instantiated React element from the parser. */\n component: ReactNode;\n}\n\n/**\n * Placement visual variant → `w5-placement--<variant>` class. Defined in\n * `../../types/placement` (DL #106 D1: free-form string, no longer a closed\n * `'informational' | 'conversation'` union) and re-exported here for back-compat\n * with existing `from '.../PlacementResponseRenderer'` imports.\n */\nexport type { PlacementVariant };\n\nexport interface PlacementResponseRendererProps {\n /** Already-parsed sections from `parseMarkdownToComponents`. */\n sections: readonly PlacementSection[];\n /** Deprecated plain-text prompt header (kept for back-compat). */\n prompt?: string;\n /**\n * Eyebrow label shown with the accent star at the top (e.g. \"FormAI\").\n * Chrome — opt-in; omit to render no eyebrow.\n */\n eyebrow?: string;\n /**\n * The user's question, rendered as a chip above the answer (conversation\n * variant, DL #097). Omit for the informational variant.\n */\n query?: string;\n /** Visual variant — both add the persistent gradient highlight border. */\n variant?: PlacementVariant;\n /**\n * Clamp the answer body to a max height with a fade-out and a \"Show more\"\n * affordance (DL #113) — Google-AI-Overview style. Opt-in; used by the\n * `searchResults` placement. The body is capped at `clampHeight` (or the\n * `--w5-placement-clamp-height` token); the fade + button appear only once\n * the content overflows and a `showMoreHref` is supplied.\n */\n clamp?: boolean;\n /** Clamp height in px (DL #113). Overrides `--w5-placement-clamp-height`. */\n clampHeight?: number;\n /**\n * Destination for \"Show more\" (DL #113) — the main Web5 page seeded with the\n * visitor's search terms (`<mainPage>?initialQuestion=<q>`). When absent the\n * button is not rendered (clamp + fade still apply).\n */\n showMoreHref?: string;\n /** Label for the \"Show more\" button (DL #113). Default `'Continue in Form AI'`. */\n showMoreLabel?: string;\n /**\n * True while the answer is still streaming in. The highlight border stays\n * solid for the whole render and only starts its spin once this flips to\n * false (answer settled); it remains visible afterwards.\n */\n streaming?: boolean;\n /** Optional extra className for the placement root. */\n className?: string;\n}\n\n/**\n * Smooths out step-wise height changes in the placement slot: the\n * loader→content swap and each streamed section appending. `height: auto`\n * changes aren't CSS-animatable, so we measure the content's natural height\n * with a ResizeObserver and transition an explicit `height` on the wrapper\n * (see `.w5-placement__body` in the CSS). Falls back to natural auto-height\n * when ResizeObserver is unavailable (SSR, old jsdom).\n *\n * Mounted by the placement stream app (embed-placement) AROUND the\n * loader/content swap — not inside this renderer — so the swap itself\n * animates too.\n */\nexport const PlacementSmoothHeight: FC<{ children: ReactNode }> = ({\n children,\n}) => {\n const innerRef = React.useRef<HTMLDivElement | null>(null);\n const [height, setHeight] = React.useState<number | null>(null);\n\n React.useEffect(() => {\n const el = innerRef.current;\n if (!el || typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver(() => {\n setHeight(el.offsetHeight);\n });\n observer.observe(el);\n return () => observer.disconnect();\n }, []);\n\n return (\n <div\n className=\"w5-placement__body\"\n style={height != null ? { height } : undefined}\n >\n {/* flow-root (in CSS) keeps child margins inside the measured box. */}\n <div ref={innerRef} className=\"w5-placement__body-inner\">\n {children}\n </div>\n </div>\n );\n};\n\nconst StarIcon: FC = () =>\n React.createElement(\n 'svg',\n {\n className: 'w5-placement__eyebrow-icon',\n viewBox: '0 0 16 16',\n width: 13,\n height: 13,\n 'aria-hidden': true,\n focusable: false,\n },\n React.createElement('path', {\n d: 'M8 0 L9.6 6.4 L16 8 L9.6 9.6 L8 16 L6.4 9.6 L0 8 L6.4 6.4 Z',\n fill: 'currentColor',\n }),\n );\n\nexport const PlacementResponseRenderer: FC<PlacementResponseRendererProps> = ({\n sections,\n prompt,\n eyebrow,\n query,\n variant,\n streaming,\n className,\n clamp,\n clampHeight,\n showMoreHref,\n showMoreLabel,\n}) => {\n // Overflow detection for the clamp (DL #113). The clamp caps the *root* card\n // (not a wrapper) so the answer sections stay direct children of the\n // placement root — clients lay them out with `:has(> …)` direct-child grids\n // (e.g. feature-com's search-overview two-column layout), which a wrapper\n // would break. With `max-height` + `overflow: hidden` the root's scrollHeight\n // is the full content and clientHeight is the capped box; the fade + \"Show\n // more\" only appear when the content actually overflows (a fade under a short\n // answer reads as broken). Re-measure on every section change (streaming) and\n // on container resize.\n const rootRef = React.useRef<HTMLElement | null>(null);\n const [overflowing, setOverflowing] = React.useState(false);\n\n React.useEffect(() => {\n if (!clamp) {\n setOverflowing(false);\n return;\n }\n const el = rootRef.current;\n if (!el) return;\n const measure = (): void =>\n setOverflowing(el.scrollHeight > el.clientHeight + 1);\n measure();\n if (typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver(measure);\n observer.observe(el);\n return () => observer.disconnect();\n }, [clamp, clampHeight, sections]);\n\n const rootClass = [\n 'w5-placement',\n variant ? `w5-placement--${variant}` : '',\n streaming ? 'w5-placement--streaming' : '',\n clamp ? 'w5-placement--clamped' : '',\n className ?? '',\n ]\n .filter(Boolean)\n .join(' ');\n\n const sectionNodes = sections.map((section, i) => (\n <Fragment key={section.id ?? `${section.sectionType}-${i}`}>\n {section.component}\n </Fragment>\n ));\n\n // The fade + button overlay the bottom of the clamped card. The button only\n // appears once the content actually overflows and a destination is supplied.\n const showMore = !!clamp && overflowing && !!showMoreHref;\n\n return (\n <section\n ref={rootRef}\n className={rootClass}\n data-w5-placement-renderer=\"\"\n style={\n clamp && clampHeight != null\n ? { maxHeight: `${clampHeight}px` }\n : undefined\n }\n >\n {eyebrow && (\n <div className=\"w5-placement__eyebrow\">\n <StarIcon />\n <span className=\"w5-placement__eyebrow-label\">{eyebrow}</span>\n </div>\n )}\n {query && <p className=\"w5-placement__query\">{query}</p>}\n {prompt && <header className=\"w5-placement__prompt\">{prompt}</header>}\n {sectionNodes}\n {/* Fade + \"Show more\" overlay the bottom of the clamped card. Both are\n absolutely positioned (out of flow), so they don't disturb the\n client's grid placement of the sections, and are clipped by the\n root's `overflow: hidden`. */}\n {showMore && (\n <>\n <div className=\"w5-placement__fade\" aria-hidden=\"true\" />\n <a\n className=\"w5-placement__show-more\"\n href={showMoreHref}\n aria-label=\"See the full answer on the AI page\"\n >\n <span>{showMoreLabel ?? 'Continue in Form AI'}</span>\n </a>\n </>\n )}\n </section>\n );\n};\n"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAOA,KAAK,IAAIC,QAAQ,QAAiC,OAAO;;AAGhE;AACA;AACA;AACA;AACA;;AAUA;AACA;AACA;AACA;AACA;AACA;;AAgDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,qBAAkD,GAAGC,IAAA,IAE5D;EAAA,IAF6D;IACjEC;EACF,CAAC,GAAAD,IAAA;EACC,MAAME,QAAQ,GAAGL,KAAK,CAACM,MAAM,CAAwB,IAAI,CAAC;EAC1D,MAAM,CAACC,MAAM,EAAEC,SAAS,CAAC,GAAGR,KAAK,CAACS,QAAQ,CAAgB,IAAI,CAAC;EAE/DT,KAAK,CAACU,SAAS,CAAC,MAAM;IACpB,MAAMC,EAAE,GAAGN,QAAQ,CAACO,OAAO;IAC3B,IAAI,CAACD,EAAE,IAAI,OAAOE,cAAc,KAAK,WAAW,EAAE;IAClD,MAAMC,QAAQ,GAAG,IAAID,cAAc,CAAC,MAAM;MACxCL,SAAS,CAACG,EAAE,CAACI,YAAY,CAAC;IAC5B,CAAC,CAAC;IACFD,QAAQ,CAACE,OAAO,CAACL,EAAE,CAAC;IACpB,OAAO,MAAMG,QAAQ,CAACG,UAAU,CAAC,CAAC;EACpC,CAAC,EAAE,EAAE,CAAC;EAEN,oBACEjB,KAAA,CAAAkB,aAAA;IACEC,SAAS,EAAC,oBAAoB;IAC9BC,KAAK,EAAEb,MAAM,IAAI,IAAI,GAAG;MAAEA;IAAO,CAAC,GAAGc;EAAU,gBAG/CrB,KAAA,CAAAkB,aAAA;IAAKI,GAAG,EAAEjB,QAAS;IAACc,SAAS,EAAC;EAA0B,GACrDf,QACE,CACF,CAAC;AAEV,CAAC;AAED,MAAMmB,QAAY,GAAGA,CAAA,kBACnBvB,KAAK,CAACkB,aAAa,CACjB,KAAK,EACL;EACEC,SAAS,EAAE,4BAA4B;EACvCK,OAAO,EAAE,WAAW;EACpBC,KAAK,EAAE,EAAE;EACTlB,MAAM,EAAE,EAAE;EACV,aAAa,EAAE,IAAI;EACnBmB,SAAS,EAAE;AACb,CAAC,eACD1B,KAAK,CAACkB,aAAa,CAAC,MAAM,EAAE;EAC1BS,CAAC,EAAE,6DAA6D;EAChEC,IAAI,EAAE;AACR,CAAC,CACH,CAAC;AAEH,OAAO,MAAMC,yBAA6D,GAAGC,KAAA,IAYvE;EAAA,IAZwE;IAC5EC,QAAQ;IACRC,MAAM;IACNC,OAAO;IACPC,KAAK;IACLC,OAAO;IACPC,SAAS;IACTjB,SAAS;IACTkB,KAAK;IACLC,WAAW;IACXC,YAAY;IACZC;EACF,CAAC,GAAAV,KAAA;EACC;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,MAAMW,OAAO,GAAGzC,KAAK,CAACM,MAAM,CAAqB,IAAI,CAAC;EACtD,MAAM,CAACoC,WAAW,EAAEC,cAAc,CAAC,GAAG3C,KAAK,CAACS,QAAQ,CAAC,KAAK,CAAC;EAE3DT,KAAK,CAACU,SAAS,CAAC,MAAM;IACpB,IAAI,CAAC2B,KAAK,EAAE;MACVM,cAAc,CAAC,KAAK,CAAC;MACrB;IACF;IACA,MAAMhC,EAAE,GAAG8B,OAAO,CAAC7B,OAAO;IAC1B,IAAI,CAACD,EAAE,EAAE;IACT,MAAMiC,OAAO,GAAGA,CAAA,KACdD,cAAc,CAAChC,EAAE,CAACkC,YAAY,GAAGlC,EAAE,CAACmC,YAAY,GAAG,CAAC,CAAC;IACvDF,OAAO,CAAC,CAAC;IACT,IAAI,OAAO/B,cAAc,KAAK,WAAW,EAAE;IAC3C,MAAMC,QAAQ,GAAG,IAAID,cAAc,CAAC+B,OAAO,CAAC;IAC5C9B,QAAQ,CAACE,OAAO,CAACL,EAAE,CAAC;IACpB,OAAO,MAAMG,QAAQ,CAACG,UAAU,CAAC,CAAC;EACpC,CAAC,EAAE,CAACoB,KAAK,EAAEC,WAAW,EAAEP,QAAQ,CAAC,CAAC;EAElC,MAAMgB,SAAS,GAAG,CAChB,cAAc,EACdZ,OAAO,GAAG,iBAAiBA,OAAO,EAAE,GAAG,EAAE,EACzCC,SAAS,GAAG,yBAAyB,GAAG,EAAE,EAC1CC,KAAK,GAAG,uBAAuB,GAAG,EAAE,EACpClB,SAAS,IAAI,EAAE,CAChB,CACE6B,MAAM,CAACC,OAAO,CAAC,CACfC,IAAI,CAAC,GAAG,CAAC;EAEZ,MAAMC,YAAY,GAAGpB,QAAQ,CAACqB,GAAG,CAAC,CAACC,OAAO,EAAEC,CAAC,kBAC3CtD,KAAA,CAAAkB,aAAA,CAACjB,QAAQ;IAACsD,GAAG,EAAEF,OAAO,CAACG,EAAE,IAAI,GAAGH,OAAO,CAACI,WAAW,IAAIH,CAAC;EAAG,GACxDD,OAAO,CAACK,SACD,CACX,CAAC;;EAEF;EACA;EACA,MAAMC,QAAQ,GAAG,CAAC,CAACtB,KAAK,IAAIK,WAAW,IAAI,CAAC,CAACH,YAAY;EAEzD,oBACEvC,KAAA,CAAAkB,aAAA;IACEI,GAAG,EAAEmB,OAAQ;IACbtB,SAAS,EAAE4B,SAAU;IACrB,8BAA2B,EAAE;IAC7B3B,KAAK,EACHiB,KAAK,IAAIC,WAAW,IAAI,IAAI,GACxB;MAAEsB,SAAS,EAAE,GAAGtB,WAAW;IAAK,CAAC,GACjCjB;EACL,GAEAY,OAAO,iBACNjC,KAAA,CAAAkB,aAAA;IAAKC,SAAS,EAAC;EAAuB,gBACpCnB,KAAA,CAAAkB,aAAA,CAACK,QAAQ,MAAE,CAAC,eACZvB,KAAA,CAAAkB,aAAA;IAAMC,SAAS,EAAC;EAA6B,GAAEc,OAAc,CAC1D,CACN,EACAC,KAAK,iBAAIlC,KAAA,CAAAkB,aAAA;IAAGC,SAAS,EAAC;EAAqB,GAAEe,KAAS,CAAC,EACvDF,MAAM,iBAAIhC,KAAA,CAAAkB,aAAA;IAAQC,SAAS,EAAC;EAAsB,GAAEa,MAAe,CAAC,EACpEmB,YAAY,EAKZQ,QAAQ,iBACP3D,KAAA,CAAAkB,aAAA,CAAAlB,KAAA,CAAAC,QAAA,qBACED,KAAA,CAAAkB,aAAA;IAAKC,SAAS,EAAC,oBAAoB;IAAC,eAAY;EAAM,CAAE,CAAC,eACzDnB,KAAA,CAAAkB,aAAA;IACEC,SAAS,EAAC,yBAAyB;IACnC0C,IAAI,EAAEtB,YAAa;IACnB,cAAW;EAAoC,gBAE/CvC,KAAA,CAAAkB,aAAA,eAAOsB,aAAa,IAAI,qBAA4B,CACnD,CACH,CAEG,CAAC;AAEd,CAAC","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["useEffect","useMemo","useState","transformToSolutionEntityData","transformToBlogPostEntityData","transformToGenericEntityData","fetchEntityListData","usePlacementPayload","FROM_PARAM","WEB5_ID_PARAM","forwardWeb5Id","url","web5Id","window","startsWith","target","URL","location","href","origin","searchParams","get","set","isAbsolute","test","toString","pathname","search","hash","makeWeb5LinkApi","opts","fullAppUrl","decorate","getForwardWeb5Id","navigateFull","originalUrl","encodeURIComponent","assign","handleLinkClick","e","preventDefault","sendWeb5Message","navigateToUrl","transformUrl","resolveUrl","isExternalUrl","isActionRegistered","executeAction","undefined","makeConversationApi","sendMessage","message","trackingOptions","onSubmit","loadConversationByTriggerId","STUB_MARKDOWN_UTILS","parseMarkdownToAst","extractLinks","extractText","REAL_ENTITY_TRANSFORMS","buildPlacementDependencies","web5LinkApi","conversationApi","entityFetcher","built","useWeb5Link","useConversation","useDebugImageContext","useResolveGenericEntityData","entities","options","entityConfig","idsToFetch","filter","data","map","entityId","idsKey","join","apiData","setApiData","Map","apiSettled","setApiSettled","length","cancelled","fetchImpl","then","prev","size","next","k","v","catch","enrichedEntities","entity","raw","typeConfig","entityTypes","entityType","payloadType","urlMatchProperty","transform","loading","entityTransforms","markdownUtils","overrides"],"sources":["../../../../src/components/placement/buildPlacementDependencies.ts"],"sourcesContent":["/**\n * buildPlacementDependencies (DL #088 D3.4).\n *\n * Produces a `ComponentDependencies` value tailored to the placement surface.\n * The placement-bundled section components consume the same DI hooks the full\n * app does (`useWeb5Link`, `useConversation`, `useMarkdownUtils`, …) but\n * placement needs **different impls** for some of them:\n *\n * - `useWeb5Link` — `web5://` links always redirect to full Web5 (D12).\n * - `useConversation.sendMessage` — calls into the placement bundle's submit\n * pathway (D11/D13) via the `onSubmit` callback.\n * - Everything else — safe defaults (no-ops, passthroughs, or stubs that\n * throw with a clear message if accidentally called by a section that\n * isn't placement-ready).\n *\n * Per OQ10, the exact set of fields that need real impls vs no-ops depends on\n * which section components the loaded client UMD actually registers in\n * placement. v1 ships safe stubs; we tighten them per-section as integration\n * surfaces gaps.\n *\n * The caller can override any field via `opts.overrides`.\n */\nimport { useEffect, useMemo, useState } from 'react';\nimport type {\n ComponentDependencies,\n Web5LinkApi,\n ConversationApi,\n MarkdownUtils,\n EntityTransforms,\n SendMessageOptions,\n} from '../../types/dependencies';\nimport {\n transformToSolutionEntityData,\n transformToBlogPostEntityData,\n transformToGenericEntityData,\n} from '../../entity/defaultExtractor';\nimport { fetchEntityListData } from '../../entity/fetchEntityListData';\nimport { usePlacementPayload } from './PlacementPayloadContext';\nimport type { ApiPayloadItem, EntityTypeConfig } from '../../types/entity';\n\nexport interface BuildPlacementDependenciesOptions {\n /**\n * URL the placement navigates to when a `web5://` link is clicked or\n * `sendWeb5Message` is called. Defaults to `/web5`. The original URL is\n * passed as a query param so the full app can pick up where placement left off.\n */\n fullAppUrl?: string;\n /**\n * Called when a section component invokes `useConversation().sendMessage(...)`.\n * Wired by the placement bundle to its D10 submit pathway.\n */\n onSubmit?: (\n message: string,\n options?: SendMessageOptions,\n ) => Promise<void> | void;\n /**\n * Auth-aware `fetch` for the items API\n * (`/wix-assistant-web5/v1/items?id=…`). Same Wix-OAuth strategy the\n * placement's conversation fetcher uses — the host page can't make\n * anonymous cross-origin calls to wixapis.com (no\n * Access-Control-Allow-Credentials on the response), so the placement\n * bundle creates a Wix-auth fetcher in `main.ts` and supplies it here.\n * When omitted, the resolver skips the API step and only enriches\n * from the streaming `answer.payload` (DL #099 PR 4).\n */\n entityFetcher?: typeof fetch;\n /**\n * Returns the `web5_id` value to forward onto same-origin navigable URLs the\n * placement's section components resolve (entity cards, links) — the\n * placement's own response trigger id (optionally `<triggerId>;<threadId>`,\n * DL #110), falling back to the page's `?web5_id=`. Read at resolve/navigate\n * time so a value captured mid-stream is used. Mirrors the main app, which\n * forwards its current conversation's trigger id (see web50-server-ui\n * `useWeb5Link.appendWeb5Id`). Carrying it forward keeps the destination\n * page's placement (e.g. `pdpFraming`) warm so it renders. Omit to forward\n * nothing.\n */\n getForwardWeb5Id?: () => string | undefined;\n /**\n * Optional overrides for any of the auto-built deps. Merged shallowly on top\n * of the defaults — pass `useWeb5Link: customHook` to take full control.\n */\n overrides?: Partial<ComponentDependencies>;\n}\n\nconst FROM_PARAM = 'from';\nconst WEB5_ID_PARAM = 'web5_id';\n\n/**\n * Append `web5Id` as `?web5_id=` onto an outbound navigable URL so the\n * destination page's placement renders warm too. Skips `web5://` / `mailto:` /\n * hash- or query-only / cross-origin URLs, preserves the original\n * relative/absolute shape, and is idempotent.\n */\nfunction forwardWeb5Id(url: string, web5Id: string): string {\n if (typeof window === 'undefined' || !url || !web5Id) return url;\n if (\n url.startsWith('web5://') ||\n url.startsWith('mailto:') ||\n url.startsWith('#') ||\n url.startsWith('?')\n ) {\n return url;\n }\n let target: URL;\n try {\n target = new URL(url, window.location.href);\n } catch {\n return url;\n }\n // Never leak the id cross-site — only decorate same-origin destinations.\n if (target.origin !== window.location.origin) return url;\n if (target.searchParams.get(WEB5_ID_PARAM) === web5Id) return url;\n target.searchParams.set(WEB5_ID_PARAM, web5Id);\n const isAbsolute =\n /^[a-zA-Z][a-zA-Z\\d+\\-.]*:/.test(url) || url.startsWith('//');\n return isAbsolute\n ? target.toString()\n : target.pathname + target.search + target.hash;\n}\n\nfunction makeWeb5LinkApi(opts: BuildPlacementDependenciesOptions): Web5LinkApi {\n const fullAppUrl = opts.fullAppUrl ?? '/web5';\n const decorate = <T extends string | undefined>(url: T): T => {\n const web5Id = opts.getForwardWeb5Id?.();\n return web5Id && url ? (forwardWeb5Id(url, web5Id) as T) : url;\n };\n\n const navigateFull = (originalUrl: string): void => {\n if (typeof window === 'undefined') return;\n const target = `${fullAppUrl}?${FROM_PARAM}=${encodeURIComponent(originalUrl)}`;\n window.location.assign(target);\n };\n\n return {\n handleLinkClick: (e, url) => {\n if (!url) return;\n if (url.startsWith('web5://')) {\n // D12: web5:// links from placement always redirect to full Web5.\n e.preventDefault();\n navigateFull(url);\n }\n // Other URLs use normal browser navigation (the <a href> default). The\n // href the section already rendered came through `resolveUrl`, so it\n // already carries `?web5_id=` when forwarding is enabled.\n },\n sendWeb5Message: (url) => navigateFull(url),\n navigateToUrl: (url) => {\n if (typeof window !== 'undefined') {\n window.location.assign(decorate(url));\n }\n },\n transformUrl: () => null,\n resolveUrl: (url) => decorate(url),\n isExternalUrl: () => false,\n isActionRegistered: () => false,\n executeAction: async () => undefined,\n };\n}\n\nfunction makeConversationApi(\n opts: BuildPlacementDependenciesOptions,\n): ConversationApi {\n return {\n sendMessage: async (message, trackingOptions) => {\n await opts.onSubmit?.(message, trackingOptions);\n },\n loadConversationByTriggerId: async () => {\n // No-op for v1 — placement is bound to a single trigger (its own).\n // Browsing other triggers means leaving placement; see D6 history.\n },\n };\n}\n\nconst STUB_MARKDOWN_UTILS: MarkdownUtils = {\n parseMarkdownToAst: () => ({}),\n extractLinks: () => [],\n extractText: () => '',\n};\n\n/**\n * Real entity transforms (DL #099 PR 4). Pure payload-row → entity-data\n * mappers — no React, no host environment — so the placement can wire\n * them in directly. Section components in placement-ready client UMDs\n * pull these via `useEntityTransforms()` and pass them as the\n * `options.transform` to `useResolveGenericEntityData`.\n *\n * Before DL #099 these were throwing stubs because the placement had\n * no payload to feed them. Now the BE's `answer.payload` rides through\n * `PlacementPayloadProvider` and the resolver actually runs.\n */\nconst REAL_ENTITY_TRANSFORMS: EntityTransforms = {\n transformToSolutionEntityData,\n transformToBlogPostEntityData,\n transformToGenericEntityData,\n};\n\nexport function buildPlacementDependencies(\n opts: BuildPlacementDependenciesOptions = {},\n): ComponentDependencies {\n const web5LinkApi = makeWeb5LinkApi(opts);\n const conversationApi = makeConversationApi(opts);\n const entityFetcher = opts.entityFetcher;\n\n const built: ComponentDependencies = {\n useWeb5Link: () => web5LinkApi,\n useConversation: () => conversationApi,\n useDebugImageContext: () => false,\n // Real payload-lookup resolver (DL #099 PR 4). Reads the accumulated\n // streaming `answer.payload` and the client UMD's `entityConfig` from\n // `PlacementPayloadProvider`; if either is missing we pass entities\n // through unchanged. No async fallback — the placement has no\n // entity-list API; sections render what the payload provided or nothing.\n useResolveGenericEntityData: (entities, options) => {\n const { entityConfig } = usePlacementPayload();\n\n // The placement resolves entity data exclusively via the items API\n // — mirroring the main app's `useEntityListData`\n // (web50-server-ui/src/hooks/useEntityListData.ts). The streaming\n // `answer.payload` is intentionally ignored (DL #099 PR 4 follow-up):\n // its shape varies per query and partial coverage caused empty cards.\n const idsToFetch = useMemo(\n () => entities.filter((e) => !e.data).map((e) => e.entityId),\n [entities],\n );\n const idsKey = idsToFetch.join(',');\n\n const [apiData, setApiData] = useState<\n Map<string, ApiPayloadItem>\n >(() => new Map());\n const [apiSettled, setApiSettled] = useState(false);\n\n useEffect(() => {\n if (!entityFetcher || idsToFetch.length === 0) {\n setApiSettled(true);\n return;\n }\n let cancelled = false;\n setApiSettled(false);\n fetchEntityListData(idsToFetch, { fetchImpl: entityFetcher })\n .then((map) => {\n if (cancelled) return;\n setApiData((prev) => {\n if (map.size === 0) return prev;\n const next = new Map(prev);\n for (const [k, v] of map) next.set(k, v);\n return next;\n });\n setApiSettled(true);\n })\n .catch(() => {\n if (!cancelled) setApiSettled(true);\n });\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [idsKey]);\n\n // Merge API rows. Per-type config looked up via entityConfig; falls\n // back to a synthetic config if the client UMD didn't register a row\n // for this entityType (rare).\n const enrichedEntities = useMemo(() => {\n if (apiData.size === 0) return entities;\n return entities.map((entity) => {\n if (entity.data) return entity;\n const raw = apiData.get(entity.entityId);\n if (!raw) return entity;\n const typeConfig: EntityTypeConfig =\n entityConfig?.entityTypes[entity.entityType] ?? {\n payloadType: entity.entityType,\n urlMatchProperty: 'url',\n };\n return { ...entity, data: options.transform(raw, typeConfig) };\n });\n }, [entities, apiData, entityConfig]);\n\n return { enrichedEntities, loading: !apiSettled };\n },\n entityTransforms: REAL_ENTITY_TRANSFORMS,\n markdownUtils: STUB_MARKDOWN_UTILS,\n };\n\n return { ...built, ...(opts.overrides ?? {}) };\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["useEffect","useMemo","useState","transformToSolutionEntityData","transformToBlogPostEntityData","transformToGenericEntityData","fetchEntityListData","usePlacementPayload","FROM_PARAM","WEB5_ID_PARAM","forwardWeb5Id","url","web5Id","window","startsWith","target","URL","location","href","origin","searchParams","get","set","isAbsolute","test","toString","pathname","search","hash","makeWeb5LinkApi","opts","fullAppUrl","decorate","getForwardWeb5Id","navigateFull","originalUrl","encodeURIComponent","assign","handleLinkClick","e","preventDefault","sendWeb5Message","navigateToUrl","transformUrl","resolveUrl","isExternalUrl","isActionRegistered","executeAction","undefined","makeConversationApi","sendMessage","message","trackingOptions","onSubmit","loadConversationByTriggerId","STUB_MARKDOWN_UTILS","parseMarkdownToAst","extractLinks","extractText","REAL_ENTITY_TRANSFORMS","buildPlacementDependencies","web5LinkApi","conversationApi","entityFetcher","built","useWeb5Link","useConversation","useDebugImageContext","useResolveGenericEntityData","entities","options","entityConfig","idsToFetch","filter","data","map","entityId","idsKey","join","apiData","setApiData","Map","apiSettled","setApiSettled","length","cancelled","fetchImpl","then","prev","size","next","k","v","catch","enrichedEntities","entity","raw","typeConfig","entityTypes","entityType","payloadType","urlMatchProperty","transform","loading","entityTransforms","markdownUtils","overrides"],"sources":["../../../../src/components/placement/buildPlacementDependencies.ts"],"sourcesContent":["/**\n * buildPlacementDependencies (DL #088 D3.4).\n *\n * Produces a `ComponentDependencies` value tailored to the placement surface.\n * The placement-bundled section components consume the same DI hooks the full\n * app does (`useWeb5Link`, `useConversation`, `useMarkdownUtils`, …) but\n * placement needs **different impls** for some of them:\n *\n * - `useWeb5Link` — `web5://` links always redirect to full Web5 (D12).\n * - `useConversation.sendMessage` — calls into the placement bundle's submit\n * pathway (D11/D13) via the `onSubmit` callback.\n * - Everything else — safe defaults (no-ops, passthroughs, or stubs that\n * throw with a clear message if accidentally called by a section that\n * isn't placement-ready).\n *\n * Per OQ10, the exact set of fields that need real impls vs no-ops depends on\n * which section components the loaded client UMD actually registers in\n * placement. v1 ships safe stubs; we tighten them per-section as integration\n * surfaces gaps.\n *\n * The caller can override any field via `opts.overrides`.\n */\nimport { useEffect, useMemo, useState } from 'react';\nimport type {\n ComponentDependencies,\n Web5LinkApi,\n ConversationApi,\n MarkdownUtils,\n EntityTransforms,\n SendMessageOptions,\n} from '../../types/dependencies';\nimport {\n transformToSolutionEntityData,\n transformToBlogPostEntityData,\n transformToGenericEntityData,\n} from '../../entity/defaultExtractor';\nimport { fetchEntityListData } from '../../entity/fetchEntityListData';\nimport { usePlacementPayload } from './PlacementPayloadContext';\nimport type { ApiPayloadItem, EntityTypeConfig } from '../../types/entity';\n\nexport interface BuildPlacementDependenciesOptions {\n /**\n * URL the placement navigates to when a `web5://` link is clicked or\n * `sendWeb5Message` is called. Defaults to `/web5`. The original URL is\n * passed as a query param so the full app can pick up where placement left off.\n */\n fullAppUrl?: string;\n /**\n * Called when a section component invokes `useConversation().sendMessage(...)`.\n * Wired by the placement bundle to its D10 submit pathway.\n */\n onSubmit?: (\n message: string,\n options?: SendMessageOptions,\n ) => Promise<void> | void;\n /**\n * Auth-aware `fetch` for the entity `list-items` API. Same Wix-OAuth strategy the\n * placement's conversation fetcher uses — the host page can't make\n * anonymous cross-origin calls to wixapis.com (no\n * Access-Control-Allow-Credentials on the response), so the placement\n * bundle creates a Wix-auth fetcher in `main.ts` and supplies it here.\n * When omitted, the resolver skips the API step and only enriches\n * from the streaming `answer.payload` (DL #099 PR 4).\n */\n entityFetcher?: typeof fetch;\n /**\n * Returns the `web5_id` value to forward onto same-origin navigable URLs the\n * placement's section components resolve (entity cards, links) — the\n * placement's own response trigger id (optionally `<triggerId>;<threadId>`,\n * DL #110), falling back to the page's `?web5_id=`. Read at resolve/navigate\n * time so a value captured mid-stream is used. Mirrors the main app, which\n * forwards its current conversation's trigger id (see web50-server-ui\n * `useWeb5Link.appendWeb5Id`). Carrying it forward keeps the destination\n * page's placement (e.g. `pdpFraming`) warm so it renders. Omit to forward\n * nothing.\n */\n getForwardWeb5Id?: () => string | undefined;\n /**\n * Optional overrides for any of the auto-built deps. Merged shallowly on top\n * of the defaults — pass `useWeb5Link: customHook` to take full control.\n */\n overrides?: Partial<ComponentDependencies>;\n}\n\nconst FROM_PARAM = 'from';\nconst WEB5_ID_PARAM = 'web5_id';\n\n/**\n * Append `web5Id` as `?web5_id=` onto an outbound navigable URL so the\n * destination page's placement renders warm too. Skips `web5://` / `mailto:` /\n * hash- or query-only / cross-origin URLs, preserves the original\n * relative/absolute shape, and is idempotent.\n */\nfunction forwardWeb5Id(url: string, web5Id: string): string {\n if (typeof window === 'undefined' || !url || !web5Id) return url;\n if (\n url.startsWith('web5://') ||\n url.startsWith('mailto:') ||\n url.startsWith('#') ||\n url.startsWith('?')\n ) {\n return url;\n }\n let target: URL;\n try {\n target = new URL(url, window.location.href);\n } catch {\n return url;\n }\n // Never leak the id cross-site — only decorate same-origin destinations.\n if (target.origin !== window.location.origin) return url;\n if (target.searchParams.get(WEB5_ID_PARAM) === web5Id) return url;\n target.searchParams.set(WEB5_ID_PARAM, web5Id);\n const isAbsolute =\n /^[a-zA-Z][a-zA-Z\\d+\\-.]*:/.test(url) || url.startsWith('//');\n return isAbsolute\n ? target.toString()\n : target.pathname + target.search + target.hash;\n}\n\nfunction makeWeb5LinkApi(opts: BuildPlacementDependenciesOptions): Web5LinkApi {\n const fullAppUrl = opts.fullAppUrl ?? '/web5';\n const decorate = <T extends string | undefined>(url: T): T => {\n const web5Id = opts.getForwardWeb5Id?.();\n return web5Id && url ? (forwardWeb5Id(url, web5Id) as T) : url;\n };\n\n const navigateFull = (originalUrl: string): void => {\n if (typeof window === 'undefined') return;\n const target = `${fullAppUrl}?${FROM_PARAM}=${encodeURIComponent(originalUrl)}`;\n window.location.assign(target);\n };\n\n return {\n handleLinkClick: (e, url) => {\n if (!url) return;\n if (url.startsWith('web5://')) {\n // D12: web5:// links from placement always redirect to full Web5.\n e.preventDefault();\n navigateFull(url);\n }\n // Other URLs use normal browser navigation (the <a href> default). The\n // href the section already rendered came through `resolveUrl`, so it\n // already carries `?web5_id=` when forwarding is enabled.\n },\n sendWeb5Message: (url) => navigateFull(url),\n navigateToUrl: (url) => {\n if (typeof window !== 'undefined') {\n window.location.assign(decorate(url));\n }\n },\n transformUrl: () => null,\n resolveUrl: (url) => decorate(url),\n isExternalUrl: () => false,\n isActionRegistered: () => false,\n executeAction: async () => undefined,\n };\n}\n\nfunction makeConversationApi(\n opts: BuildPlacementDependenciesOptions,\n): ConversationApi {\n return {\n sendMessage: async (message, trackingOptions) => {\n await opts.onSubmit?.(message, trackingOptions);\n },\n loadConversationByTriggerId: async () => {\n // No-op for v1 — placement is bound to a single trigger (its own).\n // Browsing other triggers means leaving placement; see D6 history.\n },\n };\n}\n\nconst STUB_MARKDOWN_UTILS: MarkdownUtils = {\n parseMarkdownToAst: () => ({}),\n extractLinks: () => [],\n extractText: () => '',\n};\n\n/**\n * Real entity transforms (DL #099 PR 4). Pure payload-row → entity-data\n * mappers — no React, no host environment — so the placement can wire\n * them in directly. Section components in placement-ready client UMDs\n * pull these via `useEntityTransforms()` and pass them as the\n * `options.transform` to `useResolveGenericEntityData`.\n *\n * Before DL #099 these were throwing stubs because the placement had\n * no payload to feed them. Now the BE's `answer.payload` rides through\n * `PlacementPayloadProvider` and the resolver actually runs.\n */\nconst REAL_ENTITY_TRANSFORMS: EntityTransforms = {\n transformToSolutionEntityData,\n transformToBlogPostEntityData,\n transformToGenericEntityData,\n};\n\nexport function buildPlacementDependencies(\n opts: BuildPlacementDependenciesOptions = {},\n): ComponentDependencies {\n const web5LinkApi = makeWeb5LinkApi(opts);\n const conversationApi = makeConversationApi(opts);\n const entityFetcher = opts.entityFetcher;\n\n const built: ComponentDependencies = {\n useWeb5Link: () => web5LinkApi,\n useConversation: () => conversationApi,\n useDebugImageContext: () => false,\n // Real payload-lookup resolver (DL #099 PR 4). Reads the accumulated\n // streaming `answer.payload` and the client UMD's `entityConfig` from\n // `PlacementPayloadProvider`; if either is missing we pass entities\n // through unchanged. No async fallback — the placement has no\n // entity-list API; sections render what the payload provided or nothing.\n useResolveGenericEntityData: (entities, options) => {\n const { entityConfig } = usePlacementPayload();\n\n // The placement resolves entity data exclusively via the items API\n // — mirroring the main app's `useEntityListData`\n // (web50-server-ui/src/hooks/useEntityListData.ts). The streaming\n // `answer.payload` is intentionally ignored (DL #099 PR 4 follow-up):\n // its shape varies per query and partial coverage caused empty cards.\n const idsToFetch = useMemo(\n () => entities.filter((e) => !e.data).map((e) => e.entityId),\n [entities],\n );\n const idsKey = idsToFetch.join(',');\n\n const [apiData, setApiData] = useState<\n Map<string, ApiPayloadItem>\n >(() => new Map());\n const [apiSettled, setApiSettled] = useState(false);\n\n useEffect(() => {\n if (!entityFetcher || idsToFetch.length === 0) {\n setApiSettled(true);\n return;\n }\n let cancelled = false;\n setApiSettled(false);\n fetchEntityListData(idsToFetch, { fetchImpl: entityFetcher })\n .then((map) => {\n if (cancelled) return;\n setApiData((prev) => {\n if (map.size === 0) return prev;\n const next = new Map(prev);\n for (const [k, v] of map) next.set(k, v);\n return next;\n });\n setApiSettled(true);\n })\n .catch(() => {\n if (!cancelled) setApiSettled(true);\n });\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [idsKey]);\n\n // Merge API rows. Per-type config looked up via entityConfig; falls\n // back to a synthetic config if the client UMD didn't register a row\n // for this entityType (rare).\n const enrichedEntities = useMemo(() => {\n if (apiData.size === 0) return entities;\n return entities.map((entity) => {\n if (entity.data) return entity;\n const raw = apiData.get(entity.entityId);\n if (!raw) return entity;\n const typeConfig: EntityTypeConfig =\n entityConfig?.entityTypes[entity.entityType] ?? {\n payloadType: entity.entityType,\n urlMatchProperty: 'url',\n };\n return { ...entity, data: options.transform(raw, typeConfig) };\n });\n }, [entities, apiData, entityConfig]);\n\n return { enrichedEntities, loading: !apiSettled };\n },\n entityTransforms: REAL_ENTITY_TRANSFORMS,\n markdownUtils: STUB_MARKDOWN_UTILS,\n };\n\n return { ...built, ...(opts.overrides ?? {}) };\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React, { useState, useRef, useEffect, memo, useMemo } from 'react';
|
|
2
2
|
import { getResizedImageUrl, normalizeImageUrl } from '../../utils/image-utils.js';
|
|
3
|
+
import { loadBackdropAnalysis } from '../../utils/imageBackdrop.js';
|
|
3
4
|
import { isWeb5ImageUrl } from '../../types/link-types.js';
|
|
4
5
|
import { useComponentDependencies } from '../../context/ComponentDependenciesContext.js';
|
|
5
6
|
import { useResolvedImageSources } from '../../hooks/useResolvedImageSources.js';
|
|
@@ -21,6 +22,7 @@ export const OptimizedImage = /*#__PURE__*/memo(props => {
|
|
|
21
22
|
imageFromText = false,
|
|
22
23
|
disableResize = false,
|
|
23
24
|
imageMode = 'fit',
|
|
25
|
+
adaptiveBackdrop = false,
|
|
24
26
|
fallbackOnLowRes,
|
|
25
27
|
lowResThreshold = 550
|
|
26
28
|
} = props;
|
|
@@ -36,6 +38,8 @@ export const OptimizedImage = /*#__PURE__*/memo(props => {
|
|
|
36
38
|
const [isError, setIsError] = useState(false);
|
|
37
39
|
const [imageUrl, setImageUrl] = useState('');
|
|
38
40
|
const [isFallback, setIsFallback] = useState(false);
|
|
41
|
+
// null = adaptive analysis pending (only meaningful when adaptiveBackdrop).
|
|
42
|
+
const [backdrop, setBackdrop] = useState(null);
|
|
39
43
|
const isDebugImageEnabled = deps.useDebugImageContext();
|
|
40
44
|
const [dimensions, setDimensions] = useState(null);
|
|
41
45
|
const containerRef = useRef(null);
|
|
@@ -106,6 +110,41 @@ export const OptimizedImage = /*#__PURE__*/memo(props => {
|
|
|
106
110
|
const isLowResResolvedAsset = hasResolvedSourceDimensions && (resolvedSource.width < lowResThreshold || resolvedSource.height < lowResThreshold);
|
|
107
111
|
const cachedFallbackUrl = useMemo(() => sourceCacheKey ? lowResFallbackCache.get(sourceCacheKey) ?? '' : '', [sourceCacheKey]);
|
|
108
112
|
const showDynamicBadge = isContextualImage && isDebugImageEnabled;
|
|
113
|
+
|
|
114
|
+
// Adaptive fit: probe the resolved image's edges, then map to fit/object-fit.
|
|
115
|
+
// Until analysis resolves (`backdrop === null`) the displayed URL is held back
|
|
116
|
+
// so we never fetch with the wrong mode and flash a re-crop. On a backdrop we
|
|
117
|
+
// render `contain` (whole subject visible) and paint the slot its colour; on a
|
|
118
|
+
// busy scene we `cover`. Failures resolve to `hasBackdrop: false` → `cover`.
|
|
119
|
+
const adaptiveActive = adaptiveBackdrop && layout !== 'intrinsic';
|
|
120
|
+
const adaptiveResolved = !adaptiveActive || backdrop !== null;
|
|
121
|
+
const effectiveObjectFit = adaptiveActive ? backdrop != null && backdrop.hasBackdrop ? 'contain' : 'cover' : objectFit;
|
|
122
|
+
const effectiveImageMode = adaptiveActive ? backdrop != null && backdrop.hasBackdrop ? 'fit' : 'fill' : imageMode;
|
|
123
|
+
const adaptiveBackgroundColor = adaptiveActive && backdrop != null && backdrop.hasBackdrop ? backdrop.backgroundColor : undefined;
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if (!adaptiveActive) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
setBackdrop(null);
|
|
129
|
+
if (!effectiveFilename || isFallback || cachedFallbackUrl || isLowResResolvedAsset && fallbackOnLowRes) {
|
|
130
|
+
// No analyzable primary source → behave like a busy scene (cover).
|
|
131
|
+
setBackdrop({
|
|
132
|
+
hasBackdrop: false
|
|
133
|
+
});
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const token = {
|
|
137
|
+
aborted: false
|
|
138
|
+
};
|
|
139
|
+
loadBackdropAnalysis(effectiveFilename, token).then(result => {
|
|
140
|
+
if (!token.aborted) {
|
|
141
|
+
setBackdrop(result);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
return () => {
|
|
145
|
+
token.aborted = true;
|
|
146
|
+
};
|
|
147
|
+
}, [adaptiveActive, effectiveFilename, isFallback, cachedFallbackUrl, isLowResResolvedAsset, fallbackOnLowRes]);
|
|
109
148
|
useEffect(() => {
|
|
110
149
|
setIsLoaded(false);
|
|
111
150
|
setIsError(false);
|
|
@@ -210,6 +249,11 @@ export const OptimizedImage = /*#__PURE__*/memo(props => {
|
|
|
210
249
|
if (disableResize) {
|
|
211
250
|
return;
|
|
212
251
|
}
|
|
252
|
+
// Hold the fetch until adaptive analysis picks fit vs fill, so we request
|
|
253
|
+
// the right CDN mode once instead of fetching twice and re-cropping.
|
|
254
|
+
if (!adaptiveResolved) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
213
257
|
if (!dimensions || !effectiveFilename || isFallback || cachedFallbackUrl || isLowResResolvedAsset && fallbackOnLowRes) {
|
|
214
258
|
return;
|
|
215
259
|
}
|
|
@@ -233,10 +277,10 @@ export const OptimizedImage = /*#__PURE__*/memo(props => {
|
|
|
233
277
|
const dpr = window.devicePixelRatio ?? 1;
|
|
234
278
|
const optimizedUrl = getResizedImageUrl(imageFilename, dimensions.width * dpr, dimensions.height * dpr, {
|
|
235
279
|
quality,
|
|
236
|
-
imageMode
|
|
280
|
+
imageMode: effectiveImageMode
|
|
237
281
|
});
|
|
238
282
|
setImageUrl(optimizedUrl);
|
|
239
|
-
}, [dimensions, cachedFallbackUrl, effectiveFilename, isFallback, isLowResResolvedAsset, layout, disableResize, quality,
|
|
283
|
+
}, [dimensions, cachedFallbackUrl, effectiveFilename, isFallback, isLowResResolvedAsset, layout, disableResize, quality, effectiveImageMode, adaptiveResolved]);
|
|
240
284
|
const handleImageLoad = e => {
|
|
241
285
|
const img = e.currentTarget;
|
|
242
286
|
if (fallbackOnLowRes && !isFallback && !hasResolvedSourceDimensions && (img.naturalWidth < lowResThreshold || img.naturalHeight < lowResThreshold)) {
|
|
@@ -294,7 +338,8 @@ export const OptimizedImage = /*#__PURE__*/memo(props => {
|
|
|
294
338
|
ref: containerRef,
|
|
295
339
|
className: ['block', layout === 'intrinsic' ? '' : 'relative overflow-hidden', showDynamicBadge ? 'relative' : '', className].filter(Boolean).join(' '),
|
|
296
340
|
style: layout === 'intrinsic' ? undefined : {
|
|
297
|
-
minHeight: '1px'
|
|
341
|
+
minHeight: '1px',
|
|
342
|
+
backgroundColor: adaptiveBackgroundColor
|
|
298
343
|
},
|
|
299
344
|
onClick: clickable ? onClick : undefined,
|
|
300
345
|
onKeyDown: clickable ? handleKeyDown : undefined,
|
|
@@ -331,7 +376,7 @@ export const OptimizedImage = /*#__PURE__*/memo(props => {
|
|
|
331
376
|
}, "Failed to load image"))), imageUrl && !isError && /*#__PURE__*/React.createElement("img", {
|
|
332
377
|
src: imageUrl,
|
|
333
378
|
alt: alt,
|
|
334
|
-
className: `${layout === 'intrinsic' ? 'w-full h-auto' : 'absolute inset-0 w-full h-full'} transition-opacity duration-300 ${isLoaded ? 'opacity-100' : 'opacity-0'} ${
|
|
379
|
+
className: `${layout === 'intrinsic' ? 'w-full h-auto' : 'absolute inset-0 w-full h-full'} transition-opacity duration-300 ${isLoaded ? 'opacity-100' : 'opacity-0'} ${effectiveObjectFit === 'contain' ? 'object-contain' : effectiveObjectFit === 'cover' ? 'object-cover' : effectiveObjectFit === 'fill' ? 'object-fill' : effectiveObjectFit === 'none' ? 'object-none' : effectiveObjectFit === 'scale-down' ? 'object-scale-down' : ''}`,
|
|
335
380
|
style: layout === 'intrinsic' ? {
|
|
336
381
|
width: '100%',
|
|
337
382
|
height: 'auto',
|
|
@@ -342,7 +387,7 @@ export const OptimizedImage = /*#__PURE__*/memo(props => {
|
|
|
342
387
|
inset: 0,
|
|
343
388
|
width: '100%',
|
|
344
389
|
height: '100%',
|
|
345
|
-
objectFit,
|
|
390
|
+
objectFit: effectiveObjectFit,
|
|
346
391
|
maxWidth: '100%',
|
|
347
392
|
display: 'block'
|
|
348
393
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["React","useState","useRef","useEffect","memo","useMemo","getResizedImageUrl","normalizeImageUrl","isWeb5ImageUrl","useComponentDependencies","useResolvedImageSources","lowResFallbackCache","Map","OptimizedImage","props","alt","className","quality","format","progressive","placeholderColor","objectFit","layout","onLoad","onError","onClick","clickable","imageFromText","disableResize","imageMode","fallbackOnLowRes","lowResThreshold","isResolvedMode","unresolvedSource","filename","preResolvedUrl","resolvedUrl","filter","undefined","sourceIdentifier","deps","isLoaded","setIsLoaded","isError","setIsError","imageUrl","setImageUrl","isFallback","setIsFallback","isDebugImageEnabled","useDebugImageContext","dimensions","setDimensions","containerRef","observerRef","isContextualImage","contextualImageKey","rawKey","slice","length","decodeURIComponent","trim","resolutionInputs","src","images","resolvedSources","loading","isResolutionLoading","resolvedSource","logContextualImage","url","contextualTerm","imageEventType","isImageResolved","isFallbackImage","contextualImageFallback","fallbacks","fallbackImages","Math","floor","random","effectiveFilename","sourceCacheKey","hasResolvedSourceDimensions","width","height","isLowResResolvedAsset","cachedFallbackUrl","get","showDynamicBadge","set","container","current","updateDimensions","rect","getBoundingClientRect","ceil","window","ResizeObserver","observe","handleResize","addEventListener","disconnect","removeEventListener","normalizedUrl","isExternalNonWixUrl","startsWith","includes","imageFilename","match","replace","dpr","devicePixelRatio","optimizedUrl","handleImageLoad","e","img","currentTarget","naturalWidth","naturalHeight","originalImageUrl","currentSrc","console","warn","fallbackImageUrl","handleImageError","error","source","Error","isTransparentPlaceholder","placeholderStyles","background","animation","backgroundSize","handleKeyDown","key","preventDefault","createElement","Fragment","ref","Boolean","join","style","minHeight","onKeyDown","tabIndex","role","fill","stroke","viewBox","focusable","strokeLinecap","strokeLinejoin","strokeWidth","d","maxWidth","display","position","inset"],"sources":["../../../../src/components/ui/OptimizedImage.tsx"],"sourcesContent":["import React, { useState, useRef, useEffect, memo, useMemo } from 'react';\nimport { getResizedImageUrl, normalizeImageUrl } from '../../utils/image-utils';\nimport { isWeb5ImageUrl } from '../../types/link-types';\nimport { useComponentDependencies } from '../../context/ComponentDependenciesContext';\nimport type { ImageSearchFilterString } from '../../image/imageSearchFilterTypes';\nimport { useResolvedImageSources } from '../../hooks/useResolvedImageSources';\n\nconst lowResFallbackCache = new Map<string, string>();\n\ninterface OptimizedImageBaseProps {\n /** Accessible alternative text for the rendered image. */\n alt: string;\n /** Additional CSS classes applied to the outer image container. */\n className?: string;\n /** Preferred output quality for resized Wix media URLs. */\n quality?: number;\n /** Requested output format for future image delivery customization. */\n format?: 'auto' | 'webp' | 'jpg' | 'png';\n /** Enables progressive loading behavior when supported by the delivery layer. */\n progressive?: boolean;\n /** Placeholder background color shown before the image loads. */\n placeholderColor?: string;\n /** CSS object-fit behavior applied to the rendered `<img>`. */\n objectFit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';\n /** Layout strategy for the image container. */\n layout?: 'fill' | 'intrinsic';\n /** Called after the final rendered image successfully loads. */\n onLoad?: () => void;\n /** Called when the final rendered image fails to load. */\n onError?: (error: Error) => void;\n /** Click handler for interactive image surfaces. */\n onClick?: () => void;\n /** Makes the image container keyboard-focusable and clickable. */\n clickable?: boolean;\n /** Marks the source as originating from text-driven image generation for BI logging. */\n imageFromText?: boolean;\n /** Skips Wix fitted delivery URL rewriting and uses the normalized original URL directly. */\n disableResize?: boolean;\n /** CDN resize mode. 'fill' crops to exact container dimensions (sharp at slot size).\n * 'fit' (default) preserves aspect ratio. */\n imageMode?: 'fit' | 'fill';\n /** Full URL to a fallback image used when the primary image is considered low resolution. */\n fallbackOnLowRes?: string;\n /** Minimum acceptable width or height before low-resolution fallback is triggered. */\n lowResThreshold?: number;\n}\n\ninterface OptimizedImageWithFilenameProps {\n /** Image source to render. Supports full URLs and `web5://image/...` contextual image tokens. */\n filename: string;\n /** Optional contextual search filter used only when resolving a `web5://image/...` source. */\n filter?: ImageSearchFilterString;\n /** Mutually exclusive with `filename`; provide this only when the image URL is already resolved. */\n resolvedUrl?: never;\n}\n\ninterface OptimizedImageWithResolvedUrlProps {\n /** Already-resolved image URL. When provided, OptimizedImage skips all source resolution. */\n resolvedUrl: string;\n /** Mutually exclusive with `resolvedUrl`; use `filename` when the source still needs resolving. */\n filename?: never;\n /** Not applicable for already-resolved image URLs. */\n filter?: never;\n}\n\nexport type OptimizedImageProps = OptimizedImageBaseProps &\n (OptimizedImageWithFilenameProps | OptimizedImageWithResolvedUrlProps);\n\nexport const OptimizedImage: React.FC<OptimizedImageProps> = memo((props) => {\n const {\n alt,\n className = '',\n quality = 85,\n format = 'auto',\n progressive = true,\n placeholderColor = '#f3f4f6',\n objectFit = 'contain',\n layout = 'fill',\n onLoad,\n onError,\n onClick,\n clickable = false,\n imageFromText = false,\n disableResize = false,\n imageMode = 'fit',\n fallbackOnLowRes,\n lowResThreshold = 550,\n } = props;\n void format;\n void progressive;\n\n const isResolvedMode = 'resolvedUrl' in props;\n const unresolvedSource = 'filename' in props ? props.filename ?? '' : '';\n const preResolvedUrl = 'resolvedUrl' in props ? props.resolvedUrl : '';\n const filter = 'filename' in props ? props.filter : undefined;\n const sourceIdentifier = isResolvedMode ? preResolvedUrl : unresolvedSource;\n\n const deps = useComponentDependencies();\n const [isLoaded, setIsLoaded] = useState(false);\n const [isError, setIsError] = useState(false);\n const [imageUrl, setImageUrl] = useState<string>('');\n const [isFallback, setIsFallback] = useState(false);\n const isDebugImageEnabled = deps.useDebugImageContext();\n const [dimensions, setDimensions] = useState<{\n width: number;\n height: number;\n } | null>(null);\n\n const containerRef = useRef<HTMLDivElement>(null);\n const observerRef = useRef<ResizeObserver | null>(null);\n\n const isContextualImage = useMemo(\n () =>\n !isResolvedMode &&\n !!(unresolvedSource && isWeb5ImageUrl(unresolvedSource)),\n [isResolvedMode, unresolvedSource],\n );\n\n const contextualImageKey = useMemo(() => {\n if (!isContextualImage) {\n return '';\n }\n const rawKey = unresolvedSource.slice('web5://image/'.length);\n try {\n return decodeURIComponent(rawKey).trim();\n } catch {\n return rawKey.trim();\n }\n }, [isContextualImage, unresolvedSource]);\n const resolutionInputs = useMemo(\n () =>\n isResolvedMode || !unresolvedSource\n ? []\n : [\n {\n src: unresolvedSource,\n filter,\n },\n ],\n [filter, isResolvedMode, unresolvedSource],\n );\n const { images: resolvedSources, loading: isResolutionLoading } =\n useResolvedImageSources(resolutionInputs);\n const resolvedSource = useMemo(\n () => (!isResolvedMode ? resolvedSources?.[0] ?? null : null),\n [isResolvedMode, resolvedSources],\n );\n\n useEffect(() => {\n if (\n !isContextualImage ||\n isResolvedMode ||\n isResolutionLoading ||\n resolvedSources === null\n ) {\n return;\n }\n\n deps.logContextualImage?.({\n imageUrl: resolvedSource?.url ?? '',\n contextualTerm: contextualImageKey,\n imageEventType: imageFromText\n ? 'web5_image_link_from_text'\n : 'web5_image_link',\n isImageResolved: !!resolvedSource?.url,\n isFallbackImage: false,\n });\n }, [\n contextualImageKey,\n deps,\n imageFromText,\n isContextualImage,\n isResolvedMode,\n isResolutionLoading,\n resolvedSource?.url,\n resolvedSources,\n ]);\n\n // When imageFromText contextual image resolution returns empty, use a random fallback\n const contextualImageFallback = useMemo(() => {\n if (\n !imageFromText ||\n !isContextualImage ||\n isResolutionLoading ||\n resolvedSources === null ||\n resolvedSource?.url\n ) {\n return '';\n }\n const fallbacks = deps.fallbackImages;\n if (!fallbacks || fallbacks.length === 0) {\n return '';\n }\n return fallbacks[Math.floor(Math.random() * fallbacks.length)];\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n imageFromText,\n isContextualImage,\n isResolutionLoading,\n resolvedSources,\n resolvedSource?.url,\n deps.fallbackImages,\n ]);\n\n // Log when a fallback image is used for an unresolved contextual image\n useEffect(() => {\n if (!contextualImageFallback) {\n return;\n }\n deps.logContextualImage?.({\n imageUrl: contextualImageFallback,\n contextualTerm: contextualImageKey,\n imageEventType: 'web5_image_fallback',\n isImageResolved: false,\n isFallbackImage: true,\n });\n }, [contextualImageFallback, contextualImageKey, deps]);\n\n const effectiveFilename = isResolvedMode\n ? preResolvedUrl\n : resolvedSource?.url || contextualImageFallback;\n const sourceCacheKey = useMemo(\n () =>\n effectiveFilename ? normalizeImageUrl(effectiveFilename).trim() : '',\n [effectiveFilename],\n );\n const hasResolvedSourceDimensions =\n typeof resolvedSource?.width === 'number' &&\n typeof resolvedSource?.height === 'number';\n const isLowResResolvedAsset =\n hasResolvedSourceDimensions &&\n (resolvedSource.width! < lowResThreshold ||\n resolvedSource.height! < lowResThreshold);\n const cachedFallbackUrl = useMemo(\n () => (sourceCacheKey ? lowResFallbackCache.get(sourceCacheKey) ?? '' : ''),\n [sourceCacheKey],\n );\n const showDynamicBadge = isContextualImage && isDebugImageEnabled;\n\n useEffect(() => {\n setIsLoaded(false);\n setIsError(false);\n setIsFallback(false);\n setImageUrl('');\n }, [sourceCacheKey, fallbackOnLowRes]);\n\n useEffect(() => {\n if (!sourceCacheKey || !fallbackOnLowRes) {\n return;\n }\n\n if (isLowResResolvedAsset) {\n lowResFallbackCache.set(sourceCacheKey, fallbackOnLowRes);\n setIsFallback(true);\n setImageUrl(fallbackOnLowRes);\n return;\n }\n\n if (!cachedFallbackUrl) {\n return;\n }\n\n setIsFallback(true);\n setImageUrl(cachedFallbackUrl);\n }, [\n cachedFallbackUrl,\n fallbackOnLowRes,\n isLowResResolvedAsset,\n sourceCacheKey,\n ]);\n\n // For fill layout, measure container so we can request a resized Wix image.\n useEffect(() => {\n if (layout === 'intrinsic' || disableResize) {\n return;\n }\n const container = containerRef.current;\n if (!container) {\n return;\n }\n\n const updateDimensions = () => {\n const rect = container.getBoundingClientRect();\n if (rect.width > 0 && rect.height > 0) {\n setDimensions({\n width: Math.ceil(rect.width),\n height: Math.ceil(rect.height),\n });\n }\n };\n\n updateDimensions();\n\n if ('ResizeObserver' in window) {\n observerRef.current = new ResizeObserver(() => {\n updateDimensions();\n });\n observerRef.current.observe(container);\n }\n\n const handleResize = () => updateDimensions();\n window.addEventListener('resize', handleResize);\n\n return () => {\n if (observerRef.current) {\n observerRef.current.disconnect();\n }\n window.removeEventListener('resize', handleResize);\n };\n }, [disableResize, layout]);\n\n // Intrinsic layout: use normalized URL without resize logic.\n useEffect(() => {\n if (layout !== 'intrinsic') {\n return;\n }\n if (\n !effectiveFilename ||\n isFallback ||\n cachedFallbackUrl ||\n (isLowResResolvedAsset && fallbackOnLowRes)\n ) {\n return;\n }\n\n const normalizedUrl = normalizeImageUrl(effectiveFilename);\n setImageUrl(normalizedUrl);\n }, [\n cachedFallbackUrl,\n effectiveFilename,\n isFallback,\n isLowResResolvedAsset,\n layout,\n ]);\n\n // External non-Wix URLs skip resizing; use them immediately.\n useEffect(() => {\n if (layout === 'intrinsic') {\n return;\n }\n if (\n !effectiveFilename ||\n isFallback ||\n cachedFallbackUrl ||\n (isLowResResolvedAsset && fallbackOnLowRes)\n ) {\n return;\n }\n\n const normalizedUrl = normalizeImageUrl(effectiveFilename);\n const isExternalNonWixUrl =\n (normalizedUrl.startsWith('http://') ||\n normalizedUrl.startsWith('https://')) &&\n !normalizedUrl.includes('static.wixstatic.com');\n\n if (isExternalNonWixUrl) {\n setImageUrl(normalizedUrl);\n }\n }, [\n cachedFallbackUrl,\n effectiveFilename,\n isFallback,\n isLowResResolvedAsset,\n layout,\n ]);\n\n // Explicit opt-out: use original normalized URL even in fill layouts.\n useEffect(() => {\n if (layout === 'intrinsic') {\n return;\n }\n if (\n !disableResize ||\n !effectiveFilename ||\n isFallback ||\n cachedFallbackUrl ||\n (isLowResResolvedAsset && fallbackOnLowRes)\n ) {\n return;\n }\n\n setImageUrl(normalizeImageUrl(effectiveFilename));\n }, [\n cachedFallbackUrl,\n disableResize,\n effectiveFilename,\n isFallback,\n isLowResResolvedAsset,\n layout,\n ]);\n\n // Wix/local URLs: wait for dimensions, then build a resized URL.\n useEffect(() => {\n if (layout === 'intrinsic') {\n return;\n }\n if (disableResize) {\n return;\n }\n if (\n !dimensions ||\n !effectiveFilename ||\n isFallback ||\n cachedFallbackUrl ||\n (isLowResResolvedAsset && fallbackOnLowRes)\n ) {\n return;\n }\n\n const normalizedUrl = normalizeImageUrl(effectiveFilename);\n const isExternalNonWixUrl =\n (normalizedUrl.startsWith('http://') ||\n normalizedUrl.startsWith('https://')) &&\n !normalizedUrl.includes('static.wixstatic.com');\n\n if (isExternalNonWixUrl) {\n return;\n }\n\n let imageFilename = effectiveFilename;\n if (normalizedUrl.includes('static.wixstatic.com')) {\n const match = normalizedUrl.match(/media\\/([^/?]+)/);\n if (match && match[1]) {\n imageFilename = match[1];\n } else {\n setImageUrl(normalizedUrl);\n return;\n }\n } else if (\n normalizedUrl.startsWith('https://static.wixstatic.com/media/')\n ) {\n imageFilename = normalizedUrl.replace(\n 'https://static.wixstatic.com/media/',\n '',\n );\n }\n\n const dpr = window.devicePixelRatio ?? 1;\n const optimizedUrl = getResizedImageUrl(\n imageFilename,\n dimensions.width * dpr,\n dimensions.height * dpr,\n { quality, imageMode },\n );\n setImageUrl(optimizedUrl);\n }, [\n dimensions,\n cachedFallbackUrl,\n effectiveFilename,\n isFallback,\n isLowResResolvedAsset,\n layout,\n disableResize,\n quality,\n imageMode,\n ]);\n\n const handleImageLoad = (e: React.SyntheticEvent<HTMLImageElement>) => {\n const img = e.currentTarget;\n\n if (\n fallbackOnLowRes &&\n !isFallback &&\n !hasResolvedSourceDimensions &&\n (img.naturalWidth < lowResThreshold ||\n img.naturalHeight < lowResThreshold)\n ) {\n const originalImageUrl =\n img.currentSrc || imageUrl || effectiveFilename || sourceIdentifier;\n\n if (sourceCacheKey) {\n lowResFallbackCache.set(sourceCacheKey, fallbackOnLowRes);\n }\n\n setIsFallback(true);\n setImageUrl(fallbackOnLowRes);\n setIsLoaded(false);\n\n console.warn('OptimizedImage applied a low-res fallback image.', {\n originalImageUrl,\n fallbackImageUrl: fallbackOnLowRes,\n naturalWidth: img.naturalWidth,\n naturalHeight: img.naturalHeight,\n lowResThreshold,\n });\n\n deps.logContextualImage?.({\n imageUrl: fallbackOnLowRes,\n contextualTerm: contextualImageKey,\n imageEventType: 'web5_image_fallback',\n isImageResolved: true,\n isFallbackImage: true,\n });\n return;\n }\n\n setIsLoaded(true);\n onLoad?.();\n };\n\n const handleImageError = () => {\n setIsError(true);\n console.error('Failed to load image:', {\n source: sourceIdentifier,\n imageUrl,\n alt,\n });\n onError?.(new Error(`Failed to load image: ${sourceIdentifier}`));\n };\n\n const isTransparentPlaceholder = placeholderColor === 'transparent';\n const placeholderStyles = isTransparentPlaceholder\n ? {\n background: 'transparent',\n animation: 'none',\n }\n : {\n background: `linear-gradient(90deg, ${placeholderColor} 0%, #e5e7eb 50%, ${placeholderColor} 100%)`,\n backgroundSize: '200% 100%',\n animation: 'shimmer 2s infinite ease-in-out',\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (onClick && (e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault();\n onClick();\n }\n };\n\n return (\n <>\n <span\n ref={containerRef}\n className={[\n 'block',\n layout === 'intrinsic' ? '' : 'relative overflow-hidden',\n showDynamicBadge ? 'relative' : '',\n className,\n ]\n .filter(Boolean)\n .join(' ')}\n style={layout === 'intrinsic' ? undefined : { minHeight: '1px' }}\n onClick={clickable ? onClick : undefined}\n onKeyDown={clickable ? handleKeyDown : undefined}\n tabIndex={clickable ? 0 : undefined}\n role={clickable ? 'button' : undefined}\n aria-label={clickable ? alt : undefined}\n >\n {showDynamicBadge && (\n <span className=\"absolute left-2 top-2 z-10 rounded bg-blue-600/90 px-2 py-1 text-[10px] font-semibold tracking-wide text-white shadow block\">\n {contextualImageKey}\n </span>\n )}\n {!isLoaded && !isError && sourceIdentifier && (\n <span\n className=\"absolute inset-0 rounded block\"\n style={placeholderStyles}\n role=\"status\"\n aria-label=\"Loading image\"\n />\n )}\n\n {isError && (\n <span\n className=\"absolute inset-0 flex items-center justify-center bg-gray-100 text-gray-500 rounded block\"\n role=\"alert\"\n aria-live=\"assertive\"\n >\n <span className=\"text-center p-4 block\">\n <svg\n className=\"w-8 h-8 mx-auto mb-2 text-gray-400\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z\"\n />\n </svg>\n <span className=\"text-xs block\">Failed to load image</span>\n </span>\n </span>\n )}\n\n {imageUrl && !isError && (\n <img\n src={imageUrl}\n alt={alt}\n className={`${\n layout === 'intrinsic'\n ? 'w-full h-auto'\n : 'absolute inset-0 w-full h-full'\n } transition-opacity duration-300 ${\n isLoaded ? 'opacity-100' : 'opacity-0'\n } ${\n objectFit === 'contain'\n ? 'object-contain'\n : objectFit === 'cover'\n ? 'object-cover'\n : objectFit === 'fill'\n ? 'object-fill'\n : objectFit === 'none'\n ? 'object-none'\n : objectFit === 'scale-down'\n ? 'object-scale-down'\n : ''\n }`}\n style={\n layout === 'intrinsic'\n ? {\n width: '100%',\n height: 'auto',\n maxWidth: '100%',\n display: 'block',\n }\n : {\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n objectFit,\n maxWidth: '100%',\n display: 'block',\n }\n }\n onLoad={handleImageLoad}\n onError={handleImageError}\n loading={imageUrl.startsWith('data:') ? 'eager' : 'lazy'}\n />\n )}\n </span>\n </>\n );\n});\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["React","useState","useRef","useEffect","memo","useMemo","getResizedImageUrl","normalizeImageUrl","loadBackdropAnalysis","isWeb5ImageUrl","useComponentDependencies","useResolvedImageSources","lowResFallbackCache","Map","OptimizedImage","props","alt","className","quality","format","progressive","placeholderColor","objectFit","layout","onLoad","onError","onClick","clickable","imageFromText","disableResize","imageMode","adaptiveBackdrop","fallbackOnLowRes","lowResThreshold","isResolvedMode","unresolvedSource","filename","preResolvedUrl","resolvedUrl","filter","undefined","sourceIdentifier","deps","isLoaded","setIsLoaded","isError","setIsError","imageUrl","setImageUrl","isFallback","setIsFallback","backdrop","setBackdrop","isDebugImageEnabled","useDebugImageContext","dimensions","setDimensions","containerRef","observerRef","isContextualImage","contextualImageKey","rawKey","slice","length","decodeURIComponent","trim","resolutionInputs","src","images","resolvedSources","loading","isResolutionLoading","resolvedSource","logContextualImage","url","contextualTerm","imageEventType","isImageResolved","isFallbackImage","contextualImageFallback","fallbacks","fallbackImages","Math","floor","random","effectiveFilename","sourceCacheKey","hasResolvedSourceDimensions","width","height","isLowResResolvedAsset","cachedFallbackUrl","get","showDynamicBadge","adaptiveActive","adaptiveResolved","effectiveObjectFit","hasBackdrop","effectiveImageMode","adaptiveBackgroundColor","backgroundColor","token","aborted","then","result","set","container","current","updateDimensions","rect","getBoundingClientRect","ceil","window","ResizeObserver","observe","handleResize","addEventListener","disconnect","removeEventListener","normalizedUrl","isExternalNonWixUrl","startsWith","includes","imageFilename","match","replace","dpr","devicePixelRatio","optimizedUrl","handleImageLoad","e","img","currentTarget","naturalWidth","naturalHeight","originalImageUrl","currentSrc","console","warn","fallbackImageUrl","handleImageError","error","source","Error","isTransparentPlaceholder","placeholderStyles","background","animation","backgroundSize","handleKeyDown","key","preventDefault","createElement","Fragment","ref","Boolean","join","style","minHeight","onKeyDown","tabIndex","role","fill","stroke","viewBox","focusable","strokeLinecap","strokeLinejoin","strokeWidth","d","maxWidth","display","position","inset"],"sources":["../../../../src/components/ui/OptimizedImage.tsx"],"sourcesContent":["import React, { useState, useRef, useEffect, memo, useMemo } from 'react';\nimport { getResizedImageUrl, normalizeImageUrl } from '../../utils/image-utils';\nimport {\n loadBackdropAnalysis,\n type BackdropAnalysis,\n} from '../../utils/imageBackdrop';\nimport { isWeb5ImageUrl } from '../../types/link-types';\nimport { useComponentDependencies } from '../../context/ComponentDependenciesContext';\nimport type { ImageSearchFilterString } from '../../image/imageSearchFilterTypes';\nimport { useResolvedImageSources } from '../../hooks/useResolvedImageSources';\n\nconst lowResFallbackCache = new Map<string, string>();\n\ninterface OptimizedImageBaseProps {\n /** Accessible alternative text for the rendered image. */\n alt: string;\n /** Additional CSS classes applied to the outer image container. */\n className?: string;\n /** Preferred output quality for resized Wix media URLs. */\n quality?: number;\n /** Requested output format for future image delivery customization. */\n format?: 'auto' | 'webp' | 'jpg' | 'png';\n /** Enables progressive loading behavior when supported by the delivery layer. */\n progressive?: boolean;\n /** Placeholder background color shown before the image loads. */\n placeholderColor?: string;\n /** CSS object-fit behavior applied to the rendered `<img>`. */\n objectFit?: 'contain' | 'cover' | 'fill' | 'none' | 'scale-down';\n /** Layout strategy for the image container. */\n layout?: 'fill' | 'intrinsic';\n /** Called after the final rendered image successfully loads. */\n onLoad?: () => void;\n /** Called when the final rendered image fails to load. */\n onError?: (error: Error) => void;\n /** Click handler for interactive image surfaces. */\n onClick?: () => void;\n /** Makes the image container keyboard-focusable and clickable. */\n clickable?: boolean;\n /** Marks the source as originating from text-driven image generation for BI logging. */\n imageFromText?: boolean;\n /** Skips Wix fitted delivery URL rewriting and uses the normalized original URL directly. */\n disableResize?: boolean;\n /** CDN resize mode. 'fill' crops to exact container dimensions (sharp at slot size).\n * 'fit' (default) preserves aspect ratio. */\n imageMode?: 'fit' | 'fill';\n /**\n * Auto-pick `objectFit`/`imageMode` per image by sniffing its edges on a\n * canvas (see `imageBackdrop.ts`). Images shot on a seamless backdrop are\n * rendered `contain` with the slot painted that backdrop colour (the subject\n * is never cropped, the letterbox is invisible); full-bleed scenes fall back\n * to `cover`. Overrides `objectFit`/`imageMode` once analysis resolves; until\n * then, and on any probe/CORS failure, the passed `objectFit`/`imageMode`\n * apply. No effect for `layout=\"intrinsic\"`. */\n adaptiveBackdrop?: boolean;\n /** Full URL to a fallback image used when the primary image is considered low resolution. */\n fallbackOnLowRes?: string;\n /** Minimum acceptable width or height before low-resolution fallback is triggered. */\n lowResThreshold?: number;\n}\n\ninterface OptimizedImageWithFilenameProps {\n /** Image source to render. Supports full URLs and `web5://image/...` contextual image tokens. */\n filename: string;\n /** Optional contextual search filter used only when resolving a `web5://image/...` source. */\n filter?: ImageSearchFilterString;\n /** Mutually exclusive with `filename`; provide this only when the image URL is already resolved. */\n resolvedUrl?: never;\n}\n\ninterface OptimizedImageWithResolvedUrlProps {\n /** Already-resolved image URL. When provided, OptimizedImage skips all source resolution. */\n resolvedUrl: string;\n /** Mutually exclusive with `resolvedUrl`; use `filename` when the source still needs resolving. */\n filename?: never;\n /** Not applicable for already-resolved image URLs. */\n filter?: never;\n}\n\nexport type OptimizedImageProps = OptimizedImageBaseProps &\n (OptimizedImageWithFilenameProps | OptimizedImageWithResolvedUrlProps);\n\nexport const OptimizedImage: React.FC<OptimizedImageProps> = memo((props) => {\n const {\n alt,\n className = '',\n quality = 85,\n format = 'auto',\n progressive = true,\n placeholderColor = '#f3f4f6',\n objectFit = 'contain',\n layout = 'fill',\n onLoad,\n onError,\n onClick,\n clickable = false,\n imageFromText = false,\n disableResize = false,\n imageMode = 'fit',\n adaptiveBackdrop = false,\n fallbackOnLowRes,\n lowResThreshold = 550,\n } = props;\n void format;\n void progressive;\n\n const isResolvedMode = 'resolvedUrl' in props;\n const unresolvedSource = 'filename' in props ? props.filename ?? '' : '';\n const preResolvedUrl = 'resolvedUrl' in props ? props.resolvedUrl : '';\n const filter = 'filename' in props ? props.filter : undefined;\n const sourceIdentifier = isResolvedMode ? preResolvedUrl : unresolvedSource;\n\n const deps = useComponentDependencies();\n const [isLoaded, setIsLoaded] = useState(false);\n const [isError, setIsError] = useState(false);\n const [imageUrl, setImageUrl] = useState<string>('');\n const [isFallback, setIsFallback] = useState(false);\n // null = adaptive analysis pending (only meaningful when adaptiveBackdrop).\n const [backdrop, setBackdrop] = useState<BackdropAnalysis | null>(null);\n const isDebugImageEnabled = deps.useDebugImageContext();\n const [dimensions, setDimensions] = useState<{\n width: number;\n height: number;\n } | null>(null);\n\n const containerRef = useRef<HTMLDivElement>(null);\n const observerRef = useRef<ResizeObserver | null>(null);\n\n const isContextualImage = useMemo(\n () =>\n !isResolvedMode &&\n !!(unresolvedSource && isWeb5ImageUrl(unresolvedSource)),\n [isResolvedMode, unresolvedSource],\n );\n\n const contextualImageKey = useMemo(() => {\n if (!isContextualImage) {\n return '';\n }\n const rawKey = unresolvedSource.slice('web5://image/'.length);\n try {\n return decodeURIComponent(rawKey).trim();\n } catch {\n return rawKey.trim();\n }\n }, [isContextualImage, unresolvedSource]);\n const resolutionInputs = useMemo(\n () =>\n isResolvedMode || !unresolvedSource\n ? []\n : [\n {\n src: unresolvedSource,\n filter,\n },\n ],\n [filter, isResolvedMode, unresolvedSource],\n );\n const { images: resolvedSources, loading: isResolutionLoading } =\n useResolvedImageSources(resolutionInputs);\n const resolvedSource = useMemo(\n () => (!isResolvedMode ? resolvedSources?.[0] ?? null : null),\n [isResolvedMode, resolvedSources],\n );\n\n useEffect(() => {\n if (\n !isContextualImage ||\n isResolvedMode ||\n isResolutionLoading ||\n resolvedSources === null\n ) {\n return;\n }\n\n deps.logContextualImage?.({\n imageUrl: resolvedSource?.url ?? '',\n contextualTerm: contextualImageKey,\n imageEventType: imageFromText\n ? 'web5_image_link_from_text'\n : 'web5_image_link',\n isImageResolved: !!resolvedSource?.url,\n isFallbackImage: false,\n });\n }, [\n contextualImageKey,\n deps,\n imageFromText,\n isContextualImage,\n isResolvedMode,\n isResolutionLoading,\n resolvedSource?.url,\n resolvedSources,\n ]);\n\n // When imageFromText contextual image resolution returns empty, use a random fallback\n const contextualImageFallback = useMemo(() => {\n if (\n !imageFromText ||\n !isContextualImage ||\n isResolutionLoading ||\n resolvedSources === null ||\n resolvedSource?.url\n ) {\n return '';\n }\n const fallbacks = deps.fallbackImages;\n if (!fallbacks || fallbacks.length === 0) {\n return '';\n }\n return fallbacks[Math.floor(Math.random() * fallbacks.length)];\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n imageFromText,\n isContextualImage,\n isResolutionLoading,\n resolvedSources,\n resolvedSource?.url,\n deps.fallbackImages,\n ]);\n\n // Log when a fallback image is used for an unresolved contextual image\n useEffect(() => {\n if (!contextualImageFallback) {\n return;\n }\n deps.logContextualImage?.({\n imageUrl: contextualImageFallback,\n contextualTerm: contextualImageKey,\n imageEventType: 'web5_image_fallback',\n isImageResolved: false,\n isFallbackImage: true,\n });\n }, [contextualImageFallback, contextualImageKey, deps]);\n\n const effectiveFilename = isResolvedMode\n ? preResolvedUrl\n : resolvedSource?.url || contextualImageFallback;\n const sourceCacheKey = useMemo(\n () =>\n effectiveFilename ? normalizeImageUrl(effectiveFilename).trim() : '',\n [effectiveFilename],\n );\n const hasResolvedSourceDimensions =\n typeof resolvedSource?.width === 'number' &&\n typeof resolvedSource?.height === 'number';\n const isLowResResolvedAsset =\n hasResolvedSourceDimensions &&\n (resolvedSource.width! < lowResThreshold ||\n resolvedSource.height! < lowResThreshold);\n const cachedFallbackUrl = useMemo(\n () => (sourceCacheKey ? lowResFallbackCache.get(sourceCacheKey) ?? '' : ''),\n [sourceCacheKey],\n );\n const showDynamicBadge = isContextualImage && isDebugImageEnabled;\n\n // Adaptive fit: probe the resolved image's edges, then map to fit/object-fit.\n // Until analysis resolves (`backdrop === null`) the displayed URL is held back\n // so we never fetch with the wrong mode and flash a re-crop. On a backdrop we\n // render `contain` (whole subject visible) and paint the slot its colour; on a\n // busy scene we `cover`. Failures resolve to `hasBackdrop: false` → `cover`.\n const adaptiveActive = adaptiveBackdrop && layout !== 'intrinsic';\n const adaptiveResolved = !adaptiveActive || backdrop !== null;\n const effectiveObjectFit = adaptiveActive\n ? backdrop?.hasBackdrop\n ? 'contain'\n : 'cover'\n : objectFit;\n const effectiveImageMode = adaptiveActive\n ? backdrop?.hasBackdrop\n ? 'fit'\n : 'fill'\n : imageMode;\n const adaptiveBackgroundColor =\n adaptiveActive && backdrop?.hasBackdrop\n ? backdrop.backgroundColor\n : undefined;\n\n useEffect(() => {\n if (!adaptiveActive) {\n return;\n }\n setBackdrop(null);\n if (\n !effectiveFilename ||\n isFallback ||\n cachedFallbackUrl ||\n (isLowResResolvedAsset && fallbackOnLowRes)\n ) {\n // No analyzable primary source → behave like a busy scene (cover).\n setBackdrop({ hasBackdrop: false });\n return;\n }\n const token = { aborted: false };\n loadBackdropAnalysis(effectiveFilename, token).then((result) => {\n if (!token.aborted) {\n setBackdrop(result);\n }\n });\n return () => {\n token.aborted = true;\n };\n }, [\n adaptiveActive,\n effectiveFilename,\n isFallback,\n cachedFallbackUrl,\n isLowResResolvedAsset,\n fallbackOnLowRes,\n ]);\n\n useEffect(() => {\n setIsLoaded(false);\n setIsError(false);\n setIsFallback(false);\n setImageUrl('');\n }, [sourceCacheKey, fallbackOnLowRes]);\n\n useEffect(() => {\n if (!sourceCacheKey || !fallbackOnLowRes) {\n return;\n }\n\n if (isLowResResolvedAsset) {\n lowResFallbackCache.set(sourceCacheKey, fallbackOnLowRes);\n setIsFallback(true);\n setImageUrl(fallbackOnLowRes);\n return;\n }\n\n if (!cachedFallbackUrl) {\n return;\n }\n\n setIsFallback(true);\n setImageUrl(cachedFallbackUrl);\n }, [\n cachedFallbackUrl,\n fallbackOnLowRes,\n isLowResResolvedAsset,\n sourceCacheKey,\n ]);\n\n // For fill layout, measure container so we can request a resized Wix image.\n useEffect(() => {\n if (layout === 'intrinsic' || disableResize) {\n return;\n }\n const container = containerRef.current;\n if (!container) {\n return;\n }\n\n const updateDimensions = () => {\n const rect = container.getBoundingClientRect();\n if (rect.width > 0 && rect.height > 0) {\n setDimensions({\n width: Math.ceil(rect.width),\n height: Math.ceil(rect.height),\n });\n }\n };\n\n updateDimensions();\n\n if ('ResizeObserver' in window) {\n observerRef.current = new ResizeObserver(() => {\n updateDimensions();\n });\n observerRef.current.observe(container);\n }\n\n const handleResize = () => updateDimensions();\n window.addEventListener('resize', handleResize);\n\n return () => {\n if (observerRef.current) {\n observerRef.current.disconnect();\n }\n window.removeEventListener('resize', handleResize);\n };\n }, [disableResize, layout]);\n\n // Intrinsic layout: use normalized URL without resize logic.\n useEffect(() => {\n if (layout !== 'intrinsic') {\n return;\n }\n if (\n !effectiveFilename ||\n isFallback ||\n cachedFallbackUrl ||\n (isLowResResolvedAsset && fallbackOnLowRes)\n ) {\n return;\n }\n\n const normalizedUrl = normalizeImageUrl(effectiveFilename);\n setImageUrl(normalizedUrl);\n }, [\n cachedFallbackUrl,\n effectiveFilename,\n isFallback,\n isLowResResolvedAsset,\n layout,\n ]);\n\n // External non-Wix URLs skip resizing; use them immediately.\n useEffect(() => {\n if (layout === 'intrinsic') {\n return;\n }\n if (\n !effectiveFilename ||\n isFallback ||\n cachedFallbackUrl ||\n (isLowResResolvedAsset && fallbackOnLowRes)\n ) {\n return;\n }\n\n const normalizedUrl = normalizeImageUrl(effectiveFilename);\n const isExternalNonWixUrl =\n (normalizedUrl.startsWith('http://') ||\n normalizedUrl.startsWith('https://')) &&\n !normalizedUrl.includes('static.wixstatic.com');\n\n if (isExternalNonWixUrl) {\n setImageUrl(normalizedUrl);\n }\n }, [\n cachedFallbackUrl,\n effectiveFilename,\n isFallback,\n isLowResResolvedAsset,\n layout,\n ]);\n\n // Explicit opt-out: use original normalized URL even in fill layouts.\n useEffect(() => {\n if (layout === 'intrinsic') {\n return;\n }\n if (\n !disableResize ||\n !effectiveFilename ||\n isFallback ||\n cachedFallbackUrl ||\n (isLowResResolvedAsset && fallbackOnLowRes)\n ) {\n return;\n }\n\n setImageUrl(normalizeImageUrl(effectiveFilename));\n }, [\n cachedFallbackUrl,\n disableResize,\n effectiveFilename,\n isFallback,\n isLowResResolvedAsset,\n layout,\n ]);\n\n // Wix/local URLs: wait for dimensions, then build a resized URL.\n useEffect(() => {\n if (layout === 'intrinsic') {\n return;\n }\n if (disableResize) {\n return;\n }\n // Hold the fetch until adaptive analysis picks fit vs fill, so we request\n // the right CDN mode once instead of fetching twice and re-cropping.\n if (!adaptiveResolved) {\n return;\n }\n if (\n !dimensions ||\n !effectiveFilename ||\n isFallback ||\n cachedFallbackUrl ||\n (isLowResResolvedAsset && fallbackOnLowRes)\n ) {\n return;\n }\n\n const normalizedUrl = normalizeImageUrl(effectiveFilename);\n const isExternalNonWixUrl =\n (normalizedUrl.startsWith('http://') ||\n normalizedUrl.startsWith('https://')) &&\n !normalizedUrl.includes('static.wixstatic.com');\n\n if (isExternalNonWixUrl) {\n return;\n }\n\n let imageFilename = effectiveFilename;\n if (normalizedUrl.includes('static.wixstatic.com')) {\n const match = normalizedUrl.match(/media\\/([^/?]+)/);\n if (match && match[1]) {\n imageFilename = match[1];\n } else {\n setImageUrl(normalizedUrl);\n return;\n }\n } else if (\n normalizedUrl.startsWith('https://static.wixstatic.com/media/')\n ) {\n imageFilename = normalizedUrl.replace(\n 'https://static.wixstatic.com/media/',\n '',\n );\n }\n\n const dpr = window.devicePixelRatio ?? 1;\n const optimizedUrl = getResizedImageUrl(\n imageFilename,\n dimensions.width * dpr,\n dimensions.height * dpr,\n { quality, imageMode: effectiveImageMode },\n );\n setImageUrl(optimizedUrl);\n }, [\n dimensions,\n cachedFallbackUrl,\n effectiveFilename,\n isFallback,\n isLowResResolvedAsset,\n layout,\n disableResize,\n quality,\n effectiveImageMode,\n adaptiveResolved,\n ]);\n\n const handleImageLoad = (e: React.SyntheticEvent<HTMLImageElement>) => {\n const img = e.currentTarget;\n\n if (\n fallbackOnLowRes &&\n !isFallback &&\n !hasResolvedSourceDimensions &&\n (img.naturalWidth < lowResThreshold ||\n img.naturalHeight < lowResThreshold)\n ) {\n const originalImageUrl =\n img.currentSrc || imageUrl || effectiveFilename || sourceIdentifier;\n\n if (sourceCacheKey) {\n lowResFallbackCache.set(sourceCacheKey, fallbackOnLowRes);\n }\n\n setIsFallback(true);\n setImageUrl(fallbackOnLowRes);\n setIsLoaded(false);\n\n console.warn('OptimizedImage applied a low-res fallback image.', {\n originalImageUrl,\n fallbackImageUrl: fallbackOnLowRes,\n naturalWidth: img.naturalWidth,\n naturalHeight: img.naturalHeight,\n lowResThreshold,\n });\n\n deps.logContextualImage?.({\n imageUrl: fallbackOnLowRes,\n contextualTerm: contextualImageKey,\n imageEventType: 'web5_image_fallback',\n isImageResolved: true,\n isFallbackImage: true,\n });\n return;\n }\n\n setIsLoaded(true);\n onLoad?.();\n };\n\n const handleImageError = () => {\n setIsError(true);\n console.error('Failed to load image:', {\n source: sourceIdentifier,\n imageUrl,\n alt,\n });\n onError?.(new Error(`Failed to load image: ${sourceIdentifier}`));\n };\n\n const isTransparentPlaceholder = placeholderColor === 'transparent';\n const placeholderStyles = isTransparentPlaceholder\n ? {\n background: 'transparent',\n animation: 'none',\n }\n : {\n background: `linear-gradient(90deg, ${placeholderColor} 0%, #e5e7eb 50%, ${placeholderColor} 100%)`,\n backgroundSize: '200% 100%',\n animation: 'shimmer 2s infinite ease-in-out',\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (onClick && (e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault();\n onClick();\n }\n };\n\n return (\n <>\n <span\n ref={containerRef}\n className={[\n 'block',\n layout === 'intrinsic' ? '' : 'relative overflow-hidden',\n showDynamicBadge ? 'relative' : '',\n className,\n ]\n .filter(Boolean)\n .join(' ')}\n style={\n layout === 'intrinsic'\n ? undefined\n : { minHeight: '1px', backgroundColor: adaptiveBackgroundColor }\n }\n onClick={clickable ? onClick : undefined}\n onKeyDown={clickable ? handleKeyDown : undefined}\n tabIndex={clickable ? 0 : undefined}\n role={clickable ? 'button' : undefined}\n aria-label={clickable ? alt : undefined}\n >\n {showDynamicBadge && (\n <span className=\"absolute left-2 top-2 z-10 rounded bg-blue-600/90 px-2 py-1 text-[10px] font-semibold tracking-wide text-white shadow block\">\n {contextualImageKey}\n </span>\n )}\n {!isLoaded && !isError && sourceIdentifier && (\n <span\n className=\"absolute inset-0 rounded block\"\n style={placeholderStyles}\n role=\"status\"\n aria-label=\"Loading image\"\n />\n )}\n\n {isError && (\n <span\n className=\"absolute inset-0 flex items-center justify-center bg-gray-100 text-gray-500 rounded block\"\n role=\"alert\"\n aria-live=\"assertive\"\n >\n <span className=\"text-center p-4 block\">\n <svg\n className=\"w-8 h-8 mx-auto mb-2 text-gray-400\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z\"\n />\n </svg>\n <span className=\"text-xs block\">Failed to load image</span>\n </span>\n </span>\n )}\n\n {imageUrl && !isError && (\n <img\n src={imageUrl}\n alt={alt}\n className={`${\n layout === 'intrinsic'\n ? 'w-full h-auto'\n : 'absolute inset-0 w-full h-full'\n } transition-opacity duration-300 ${\n isLoaded ? 'opacity-100' : 'opacity-0'\n } ${\n effectiveObjectFit === 'contain'\n ? 'object-contain'\n : effectiveObjectFit === 'cover'\n ? 'object-cover'\n : effectiveObjectFit === 'fill'\n ? 'object-fill'\n : effectiveObjectFit === 'none'\n ? 'object-none'\n : effectiveObjectFit === 'scale-down'\n ? 'object-scale-down'\n : ''\n }`}\n style={\n layout === 'intrinsic'\n ? {\n width: '100%',\n height: 'auto',\n maxWidth: '100%',\n display: 'block',\n }\n : {\n position: 'absolute',\n inset: 0,\n width: '100%',\n height: '100%',\n objectFit: effectiveObjectFit,\n maxWidth: '100%',\n display: 'block',\n }\n }\n onLoad={handleImageLoad}\n onError={handleImageError}\n loading={imageUrl.startsWith('data:') ? 'eager' : 'lazy'}\n />\n )}\n </span>\n </>\n );\n});\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -9,6 +9,25 @@
|
|
|
9
9
|
pointer-events: none;
|
|
10
10
|
user-select: none;
|
|
11
11
|
padding-block: 4rem;
|
|
12
|
+
/* `min-height` (per-shape, below) includes the block padding so the reserved
|
|
13
|
+
space matches the eventual section regardless of the consuming app's reset. */
|
|
14
|
+
box-sizing: border-box;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/* Per-section-type height reservation (DL #114 C) — approximate the eventual
|
|
18
|
+
section so the skeleton→content swap causes near-zero layout shift below it.
|
|
19
|
+
Erring slightly tall is preferable (content settles up, not down). */
|
|
20
|
+
.w5-skeleton--cards {
|
|
21
|
+
min-height: 28rem;
|
|
22
|
+
}
|
|
23
|
+
.w5-skeleton--tiles {
|
|
24
|
+
min-height: 14rem;
|
|
25
|
+
}
|
|
26
|
+
.w5-skeleton--rows {
|
|
27
|
+
min-height: 18rem;
|
|
28
|
+
}
|
|
29
|
+
.w5-skeleton--lines {
|
|
30
|
+
min-height: 10rem;
|
|
12
31
|
}
|
|
13
32
|
|
|
14
33
|
@media (min-width: 64rem) {
|
|
@@ -86,7 +86,11 @@ export const SectionSkeleton = _ref => {
|
|
|
86
86
|
const Shape = SHAPES[shape];
|
|
87
87
|
const label = (semantic || sectionType || 'content').replace(/[-_]/g, ' ');
|
|
88
88
|
return /*#__PURE__*/React.createElement("section", {
|
|
89
|
-
|
|
89
|
+
// `w5-skeleton--<shape>` reserves a realistic min-height per section type so
|
|
90
|
+
// the skeleton→content swap doesn't reflow the sections below it; the
|
|
91
|
+
// `w5-skeleton-expand` class unfolds it from the top when it first appears
|
|
92
|
+
// (both DL #114 C; the expand rule lives in base-utilities.css).
|
|
93
|
+
className: `w5-skeleton w5-skeleton--${shape} w5-skeleton-expand ${className}`.trim(),
|
|
90
94
|
role: "status",
|
|
91
95
|
"aria-busy": "true",
|
|
92
96
|
"aria-label": `Loading ${label} section`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["React","shapeFor","sectionType","semantic","key","toLowerCase","test","Cards","createElement","className","map","i","Tiles","Rows","LINE_WIDTHS","Lines","w","SHAPES","cards","tiles","rows","lines","SectionSkeleton","_ref","shape","Shape","label","replace","trim","role"],"sources":["../../../../src/components/ui/SectionSkeleton.tsx"],"sourcesContent":["import React, { FC } from 'react';\nimport './SectionSkeleton.css';\n\n/**\n * Default status-driven placeholder for a V3 section that's been declared (its\n * `<!-- section ... -->` remark arrived in the layout pass) but whose body\n * hasn't finished streaming yet (DL #106). Lives in core so every surface has a\n * sensible default; client packages can override via\n * `uiSlots.SectionSkeletonComponent` and the app resolves the override at\n * render time (see web50-server-ui `SectionSkeletonSlot`).\n *\n * Unlike real sections it does NOT go through the ComponentRegistry — the\n * registry maps parsed content nodes to components, and a skeleton has no\n * content node. The consumption layer hand-builds a `PageSection` whose\n * `component` is this element.\n *\n * Shape is chosen from the section's `semantic`/`sectionType` so the placeholder\n * roughly matches what will replace it (cards for item grids, tiles for KPIs,\n * rows for tables, lines for prose) — minimizing reflow on the skeleton→content\n * swap.\n */\nexport interface SectionSkeletonProps {\n sectionType?: string;\n semantic?: string;\n className?: string;\n}\n\ntype SkeletonShape = 'cards' | 'tiles' | 'rows' | 'lines';\n\nconst shapeFor = (sectionType = '', semantic = ''): SkeletonShape => {\n const key = `${semantic} ${sectionType}`.toLowerCase();\n if (/\\bkpi\\b|metric/.test(key)) {\n return 'tiles';\n }\n if (/comparison|table/.test(key)) {\n return 'rows';\n }\n if (/item|feature_list|gallery|card/.test(key)) {\n return 'cards';\n }\n return 'lines';\n};\n\nconst Cards: FC = () => (\n <div className=\"w5-skeleton__grid\">\n {[0, 1, 2, 3].map((i) => (\n <div key={i} className=\"w5-skeleton__card\">\n <span className=\"w5-skeleton__bar w5-skeleton__media\" />\n <span className=\"w5-skeleton__bar w5-skeleton__line w5-skeleton__line--md\" />\n <span className=\"w5-skeleton__bar w5-skeleton__line w5-skeleton__line--sm\" />\n </div>\n ))}\n </div>\n);\n\nconst Tiles: FC = () => (\n <div className=\"w5-skeleton__grid\">\n {[0, 1, 2, 3].map((i) => (\n <span key={i} className=\"w5-skeleton__bar w5-skeleton__tile\" />\n ))}\n </div>\n);\n\nconst Rows: FC = () => (\n <div className=\"w5-skeleton__lines\">\n {[0, 1, 2, 3, 4].map((i) => (\n <span key={i} className=\"w5-skeleton__bar w5-skeleton__row\" />\n ))}\n </div>\n);\n\nconst LINE_WIDTHS = ['lg', 'md', 'lg', 'md', 'sm'] as const;\nconst Lines: FC = () => (\n <div className=\"w5-skeleton__lines\">\n {LINE_WIDTHS.map((w, i) => (\n <span\n key={i}\n className={`w5-skeleton__bar w5-skeleton__line w5-skeleton__line--${w}`}\n />\n ))}\n </div>\n);\n\nconst SHAPES: Record<SkeletonShape, FC> = {\n cards: Cards,\n tiles: Tiles,\n rows: Rows,\n lines: Lines,\n};\n\nexport const SectionSkeleton: FC<SectionSkeletonProps> = ({\n sectionType,\n semantic,\n className = '',\n}) => {\n const shape = shapeFor(sectionType, semantic);\n const Shape = SHAPES[shape];\n const label = (semantic || sectionType || 'content').replace(/[-_]/g, ' ');\n\n return (\n <section\n className={`w5-skeleton ${className}`.trim()}\n role=\"status\"\n aria-busy=\"true\"\n aria-label={`Loading ${label} section`}\n >\n <div className=\"w5-skeleton__inner\">\n <span\n className=\"w5-skeleton__bar w5-skeleton__title\"\n aria-hidden=\"true\"\n />\n <Shape />\n </div>\n </section>\n );\n};\n"],"mappings":"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
|
|
1
|
+
{"version":3,"names":["React","shapeFor","sectionType","semantic","key","toLowerCase","test","Cards","createElement","className","map","i","Tiles","Rows","LINE_WIDTHS","Lines","w","SHAPES","cards","tiles","rows","lines","SectionSkeleton","_ref","shape","Shape","label","replace","trim","role"],"sources":["../../../../src/components/ui/SectionSkeleton.tsx"],"sourcesContent":["import React, { FC } from 'react';\nimport './SectionSkeleton.css';\n\n/**\n * Default status-driven placeholder for a V3 section that's been declared (its\n * `<!-- section ... -->` remark arrived in the layout pass) but whose body\n * hasn't finished streaming yet (DL #106). Lives in core so every surface has a\n * sensible default; client packages can override via\n * `uiSlots.SectionSkeletonComponent` and the app resolves the override at\n * render time (see web50-server-ui `SectionSkeletonSlot`).\n *\n * Unlike real sections it does NOT go through the ComponentRegistry — the\n * registry maps parsed content nodes to components, and a skeleton has no\n * content node. The consumption layer hand-builds a `PageSection` whose\n * `component` is this element.\n *\n * Shape is chosen from the section's `semantic`/`sectionType` so the placeholder\n * roughly matches what will replace it (cards for item grids, tiles for KPIs,\n * rows for tables, lines for prose) — minimizing reflow on the skeleton→content\n * swap.\n */\nexport interface SectionSkeletonProps {\n sectionType?: string;\n semantic?: string;\n className?: string;\n}\n\ntype SkeletonShape = 'cards' | 'tiles' | 'rows' | 'lines';\n\nconst shapeFor = (sectionType = '', semantic = ''): SkeletonShape => {\n const key = `${semantic} ${sectionType}`.toLowerCase();\n if (/\\bkpi\\b|metric/.test(key)) {\n return 'tiles';\n }\n if (/comparison|table/.test(key)) {\n return 'rows';\n }\n if (/item|feature_list|gallery|card/.test(key)) {\n return 'cards';\n }\n return 'lines';\n};\n\nconst Cards: FC = () => (\n <div className=\"w5-skeleton__grid\">\n {[0, 1, 2, 3].map((i) => (\n <div key={i} className=\"w5-skeleton__card\">\n <span className=\"w5-skeleton__bar w5-skeleton__media\" />\n <span className=\"w5-skeleton__bar w5-skeleton__line w5-skeleton__line--md\" />\n <span className=\"w5-skeleton__bar w5-skeleton__line w5-skeleton__line--sm\" />\n </div>\n ))}\n </div>\n);\n\nconst Tiles: FC = () => (\n <div className=\"w5-skeleton__grid\">\n {[0, 1, 2, 3].map((i) => (\n <span key={i} className=\"w5-skeleton__bar w5-skeleton__tile\" />\n ))}\n </div>\n);\n\nconst Rows: FC = () => (\n <div className=\"w5-skeleton__lines\">\n {[0, 1, 2, 3, 4].map((i) => (\n <span key={i} className=\"w5-skeleton__bar w5-skeleton__row\" />\n ))}\n </div>\n);\n\nconst LINE_WIDTHS = ['lg', 'md', 'lg', 'md', 'sm'] as const;\nconst Lines: FC = () => (\n <div className=\"w5-skeleton__lines\">\n {LINE_WIDTHS.map((w, i) => (\n <span\n key={i}\n className={`w5-skeleton__bar w5-skeleton__line w5-skeleton__line--${w}`}\n />\n ))}\n </div>\n);\n\nconst SHAPES: Record<SkeletonShape, FC> = {\n cards: Cards,\n tiles: Tiles,\n rows: Rows,\n lines: Lines,\n};\n\nexport const SectionSkeleton: FC<SectionSkeletonProps> = ({\n sectionType,\n semantic,\n className = '',\n}) => {\n const shape = shapeFor(sectionType, semantic);\n const Shape = SHAPES[shape];\n const label = (semantic || sectionType || 'content').replace(/[-_]/g, ' ');\n\n return (\n <section\n // `w5-skeleton--<shape>` reserves a realistic min-height per section type so\n // the skeleton→content swap doesn't reflow the sections below it; the\n // `w5-skeleton-expand` class unfolds it from the top when it first appears\n // (both DL #114 C; the expand rule lives in base-utilities.css).\n className={`w5-skeleton w5-skeleton--${shape} w5-skeleton-expand ${className}`.trim()}\n role=\"status\"\n aria-busy=\"true\"\n aria-label={`Loading ${label} section`}\n >\n <div className=\"w5-skeleton__inner\">\n <span\n className=\"w5-skeleton__bar w5-skeleton__title\"\n aria-hidden=\"true\"\n />\n <Shape />\n </div>\n </section>\n );\n};\n"],"mappings":"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","ignoreList":[]}
|