@wix/web5-core 1.63.27 → 1.63.28
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.
|
@@ -43,6 +43,19 @@ var _jsxFileName = "/home/builduser/work/c2b6ca0b3fa984ee/packages/web5-core/dis
|
|
|
43
43
|
*/
|
|
44
44
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
45
45
|
const ImageSlotContext = /*#__PURE__*/(0, _react.createContext)(null);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Module-level so `read` returns a STABLE reference for a slot nobody has
|
|
49
|
+
* published yet. `useImageSlot` reads this store through
|
|
50
|
+
* `useSyncExternalStore`, which compares snapshots with `Object.is` on every
|
|
51
|
+
* render — a fresh `{ status: 'pending' }` per call reads as "the store
|
|
52
|
+
* changed", and React re-renders until it throws "Maximum update depth
|
|
53
|
+
* exceeded". Every slot is pending from mount until its batch answers, so a
|
|
54
|
+
* literal here takes down every section that declares one.
|
|
55
|
+
*/
|
|
56
|
+
const PENDING = {
|
|
57
|
+
status: 'pending'
|
|
58
|
+
};
|
|
46
59
|
/**
|
|
47
60
|
* Mounted once per section by the host's section wrapper, so every section in
|
|
48
61
|
* every client package gets a collector without opting in.
|
|
@@ -160,9 +173,7 @@ const ImageSlotProvider = ({
|
|
|
160
173
|
});
|
|
161
174
|
}
|
|
162
175
|
}, [flush]);
|
|
163
|
-
const read = (0, _react.useCallback)(slotId => statesRef.current[slotId] ??
|
|
164
|
-
status: 'pending'
|
|
165
|
-
}, []);
|
|
176
|
+
const read = (0, _react.useCallback)(slotId => statesRef.current[slotId] ?? PENDING, []);
|
|
166
177
|
const subscribe = (0, _react.useCallback)((slotId, onChange) => {
|
|
167
178
|
let set = listenersRef.current.get(slotId);
|
|
168
179
|
if (!set) {
|
|
@@ -192,7 +203,7 @@ const ImageSlotProvider = ({
|
|
|
192
203
|
__self: void 0,
|
|
193
204
|
__source: {
|
|
194
205
|
fileName: _jsxFileName,
|
|
195
|
-
lineNumber:
|
|
206
|
+
lineNumber: 246,
|
|
196
207
|
columnNumber: 5
|
|
197
208
|
}
|
|
198
209
|
}, children);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_react","_interopRequireWildcard","require","_ComponentDependenciesContext","_diagnosticTypes","_jsxFileName","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","ImageSlotContext","createContext","ImageSlotProvider","sectionId","children","deps","useOptionalComponentDependencies","resolve","resolveImageSet","statesRef","useRef","listenersRef","Map","pendingRef","sentRef","Set","flushScheduled","aliveRef","useEffect","current","publish","useCallback","next","changed","id","state","entries","push","_listenersRef$current","forEach","fn","markUnavailable","batch","fromEntries","map","s","status","flush","values","filter","clear","length","add","res","bySlotId","slots","slotId","req","got","imageUrl","slot","err","reportDiagnostic","DIAGNOSTIC_TYPES","IMAGE_RESOLUTION_FAILED","Error","message","String","declare","queueMicrotask","read","subscribe","onChange","_set","delete","size","api","useMemo","createElement","Provider","value","__self","__source","fileName","lineNumber","columnNumber","exports","useImageSlotCollector","useContext"],"sources":["../../../src/context/ImageSlotContext.tsx"],"sourcesContent":["/**\n * The section-scoped image slot collector.\n *\n * A section declares the holes in its layout; it never makes a request. This\n * gathers every declaration made inside one section's subtree, fires ONE\n * `ResolveImageSet` through the injected port, and hands each declaration its\n * answer back by `slotId`. Sections stay ignorant of each other — no section\n * imports another, no ordering rules, no exclusion lists threaded through\n * props — and the network still sees the joint solve that distinctness depends\n * on.\n *\n * WHY THE BOUNDARY IS THE SECTION\n * -------------------------------\n * The resolver solves a maximum-weight one-to-one assignment across everything\n * in one request, so distinctness and palette cohesion are properties of that\n * solve and stop at its edge. Drawing the boundary at the page would buy\n * cross-section distinctness at the cost of making every section's pictures\n * wait on the slowest declaration on the page; drawing it at the section keeps\n * each section independent and accepts that two sections may land on the same\n * photograph. The most visible case — four identical photos in one grid — is\n * still prevented for free, because those slots share a request.\n *\n * WHY THERE IS NO READINESS POLICY\n * --------------------------------\n * There is nothing to wait for. A section component is never constructed from\n * partial markdown: a declared-but-unstreamed section is a `PageSection` with\n * `isSkeleton: true` and `component: null`, and the real element is built only\n * once its body has finished streaming. So a section's props are final the\n * moment it exists, and the flush is simply \"after this section's first render\n * commit\" — one effect, not a scheduler.\n *\n * The same fact bounds the win honestly: because only a *mounted* section\n * declares, no section can ask for a picture before its own body has streamed,\n * including a background whose subject needed nothing from that body.\n */\nimport React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n} from 'react';\nimport { useOptionalComponentDependencies } from './ComponentDependenciesContext';\nimport { DIAGNOSTIC_TYPES } from '../component/diagnosticTypes';\nimport type {\n ImageSlotRequest,\n SlotState,\n} from '../image/imageSlotTypes';\n\nexport interface ImageSlotCollector {\n /** Register a slot for this batch. Idempotent per content-derived id. */\n declare: (slot: ImageSlotRequest) => void;\n /** Current state for one slot. Not reactive alone — pair with `subscribe`. */\n read: (slotId: string) => SlotState;\n /** Notify only when THIS slot changes. Returns an unsubscribe. */\n subscribe: (slotId: string, onChange: () => void) => () => void;\n}\n\nconst ImageSlotContext = createContext<ImageSlotCollector | null>(null);\n\nexport interface ImageSlotProviderProps {\n /** Identifies the section in diagnostics. */\n sectionId: string;\n children: React.ReactNode;\n}\n\n/**\n * Mounted once per section by the host's section wrapper, so every section in\n * every client package gets a collector without opting in.\n */\nexport const ImageSlotProvider: React.FC<ImageSlotProviderProps> = ({\n sectionId,\n children,\n}) => {\n // Optional on purpose: this is mounted by the host around EVERY section, so\n // it sits above trees that may have no dependencies configured at all (a\n // layout test, a story). Throwing there would make the image feature's\n // provider a global requirement for rendering any section.\n const deps = useOptionalComponentDependencies();\n const resolve = deps?.resolveImageSet;\n\n /**\n * Slot state lives in a ref, NOT in React state, and the context value never\n * changes. Holding it in state put it inside the provider's value, so every\n * resolution produced a new value and re-rendered every consumer in the\n * section — free at one slot, twelve re-renders per resolution in a\n * twelve-card grid, which is exactly the shape this boundary exists to serve.\n * Consumers now subscribe to their own id and nothing else.\n */\n const statesRef = useRef<Record<string, SlotState>>({});\n const listenersRef = useRef<Map<string, Set<() => void>>>(new Map());\n\n // Declarations arriving during this commit. A ref, not state: collecting a\n // declaration must not itself cause a render, or every hook that declares\n // would re-render every sibling before the batch is even sent.\n const pendingRef = useRef<Map<string, ImageSlotRequest>>(new Map());\n // Ids already sent. A re-render re-declares the same content-derived id, and\n // that must be a no-op rather than a second request.\n const sentRef = useRef<Set<string>>(new Set());\n const flushScheduled = useRef(false);\n const aliveRef = useRef(true);\n\n useEffect(() => {\n aliveRef.current = true;\n return () => {\n aliveRef.current = false;\n };\n }, []);\n\n const publish = useCallback((next: Record<string, SlotState>) => {\n const changed: string[] = [];\n for (const [id, state] of Object.entries(next)) {\n if (statesRef.current[id] !== state) {\n statesRef.current[id] = state;\n changed.push(id);\n }\n }\n for (const id of changed) {\n listenersRef.current.get(id)?.forEach((fn) => fn());\n }\n }, []);\n\n const markUnavailable = useCallback(\n (batch: ImageSlotRequest[]) => {\n if (!aliveRef.current) {\n return;\n }\n publish(\n Object.fromEntries(\n batch.map((s) => [s.id, { status: 'unavailable' } as SlotState]),\n ),\n );\n },\n [publish],\n );\n\n const flush = useCallback(async () => {\n flushScheduled.current = false;\n\n const batch = [...pendingRef.current.values()].filter(\n (s) => !sentRef.current.has(s.id),\n );\n pendingRef.current.clear();\n if (batch.length === 0) {\n return;\n }\n batch.forEach((s) => sentRef.current.add(s.id));\n\n // No port means the host has not injected a resolver. Fail the batch closed\n // rather than silently leaving slots pending forever.\n if (!resolve) {\n markUnavailable(batch);\n return;\n }\n\n try {\n const res = await resolve(batch);\n if (!aliveRef.current) {\n return;\n }\n const bySlotId = new Map(res.slots.map((s) => [s.slotId, s]));\n const next: Record<string, SlotState> = {};\n for (const req of batch) {\n const got = bySlotId.get(req.id);\n // A slot the resolver could not fill comes back with a null url. That\n // is `unavailable` to a layout, not a resolved picture.\n next[req.id] =\n got && got.imageUrl\n ? { status: 'resolved', slot: got }\n : { status: 'unavailable' };\n }\n publish(next);\n } catch (err) {\n // Failure is contained to this section: one bad request costs this\n // section its pictures and nothing else on the page.\n deps?.reportDiagnostic?.(\n DIAGNOSTIC_TYPES.IMAGE_RESOLUTION_FAILED,\n sectionId,\n `ResolveImageSet failed for ${batch.length} slot(s): ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n markUnavailable(batch);\n }\n }, [resolve, deps, sectionId, publish, markUnavailable]);\n\n const declare = useCallback(\n (slot: ImageSlotRequest) => {\n if (sentRef.current.has(slot.id)) {\n return;\n }\n pendingRef.current.set(slot.id, slot);\n // Flush on a microtask, so every declaration made during this commit —\n // the background, the cards, an inline image — lands in the same batch.\n if (!flushScheduled.current) {\n flushScheduled.current = true;\n queueMicrotask(() => {\n void flush();\n });\n }\n },\n [flush],\n );\n\n const read = useCallback(\n (slotId: string): SlotState =>\n statesRef.current[slotId] ?? { status: 'pending' },\n [],\n );\n\n const subscribe = useCallback((slotId: string, onChange: () => void) => {\n let set = listenersRef.current.get(slotId);\n if (!set) {\n set = new Set();\n listenersRef.current.set(slotId, set);\n }\n set.add(onChange);\n return () => {\n set?.delete(onChange);\n if (set && set.size === 0) {\n listenersRef.current.delete(slotId);\n }\n };\n }, []);\n\n // Stable for the provider's lifetime: every member is a `useCallback` with no\n // reactive deps, so mounting this around a section costs its consumers\n // nothing after first render.\n const api = useMemo<ImageSlotCollector>(\n () => ({ declare, read, subscribe }),\n [declare, read, subscribe],\n );\n\n return (\n <ImageSlotContext.Provider value={api}>{children}</ImageSlotContext.Provider>\n );\n};\n\n/**\n * Null when no provider is mounted above — a host that has not adopted the\n * section wrapper's collector. `useImageSlot` handles that by resolving each\n * slot on its own, so a section still gets its picture and only loses batching\n * with its siblings.\n */\nexport const useImageSlotCollector = (): ImageSlotCollector | null =>\n useContext(ImageSlotContext);\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["_react","_interopRequireWildcard","require","_ComponentDependenciesContext","_diagnosticTypes","_jsxFileName","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","ImageSlotContext","createContext","PENDING","status","ImageSlotProvider","sectionId","children","deps","useOptionalComponentDependencies","resolve","resolveImageSet","statesRef","useRef","listenersRef","Map","pendingRef","sentRef","Set","flushScheduled","aliveRef","useEffect","current","publish","useCallback","next","changed","id","state","entries","push","_listenersRef$current","forEach","fn","markUnavailable","batch","fromEntries","map","s","flush","values","filter","clear","length","add","res","bySlotId","slots","slotId","req","got","imageUrl","slot","err","reportDiagnostic","DIAGNOSTIC_TYPES","IMAGE_RESOLUTION_FAILED","Error","message","String","declare","queueMicrotask","read","subscribe","onChange","_set","delete","size","api","useMemo","createElement","Provider","value","__self","__source","fileName","lineNumber","columnNumber","exports","useImageSlotCollector","useContext"],"sources":["../../../src/context/ImageSlotContext.tsx"],"sourcesContent":["/**\n * The section-scoped image slot collector.\n *\n * A section declares the holes in its layout; it never makes a request. This\n * gathers every declaration made inside one section's subtree, fires ONE\n * `ResolveImageSet` through the injected port, and hands each declaration its\n * answer back by `slotId`. Sections stay ignorant of each other — no section\n * imports another, no ordering rules, no exclusion lists threaded through\n * props — and the network still sees the joint solve that distinctness depends\n * on.\n *\n * WHY THE BOUNDARY IS THE SECTION\n * -------------------------------\n * The resolver solves a maximum-weight one-to-one assignment across everything\n * in one request, so distinctness and palette cohesion are properties of that\n * solve and stop at its edge. Drawing the boundary at the page would buy\n * cross-section distinctness at the cost of making every section's pictures\n * wait on the slowest declaration on the page; drawing it at the section keeps\n * each section independent and accepts that two sections may land on the same\n * photograph. The most visible case — four identical photos in one grid — is\n * still prevented for free, because those slots share a request.\n *\n * WHY THERE IS NO READINESS POLICY\n * --------------------------------\n * There is nothing to wait for. A section component is never constructed from\n * partial markdown: a declared-but-unstreamed section is a `PageSection` with\n * `isSkeleton: true` and `component: null`, and the real element is built only\n * once its body has finished streaming. So a section's props are final the\n * moment it exists, and the flush is simply \"after this section's first render\n * commit\" — one effect, not a scheduler.\n *\n * The same fact bounds the win honestly: because only a *mounted* section\n * declares, no section can ask for a picture before its own body has streamed,\n * including a background whose subject needed nothing from that body.\n */\nimport React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n} from 'react';\nimport { useOptionalComponentDependencies } from './ComponentDependenciesContext';\nimport { DIAGNOSTIC_TYPES } from '../component/diagnosticTypes';\nimport type {\n ImageSlotRequest,\n SlotState,\n} from '../image/imageSlotTypes';\n\nexport interface ImageSlotCollector {\n /** Register a slot for this batch. Idempotent per content-derived id. */\n declare: (slot: ImageSlotRequest) => void;\n /** Current state for one slot. Not reactive alone — pair with `subscribe`. */\n read: (slotId: string) => SlotState;\n /** Notify only when THIS slot changes. Returns an unsubscribe. */\n subscribe: (slotId: string, onChange: () => void) => () => void;\n}\n\nconst ImageSlotContext = createContext<ImageSlotCollector | null>(null);\n\n/**\n * Module-level so `read` returns a STABLE reference for a slot nobody has\n * published yet. `useImageSlot` reads this store through\n * `useSyncExternalStore`, which compares snapshots with `Object.is` on every\n * render — a fresh `{ status: 'pending' }` per call reads as \"the store\n * changed\", and React re-renders until it throws \"Maximum update depth\n * exceeded\". Every slot is pending from mount until its batch answers, so a\n * literal here takes down every section that declares one.\n */\nconst PENDING: SlotState = { status: 'pending' };\n\nexport interface ImageSlotProviderProps {\n /** Identifies the section in diagnostics. */\n sectionId: string;\n children: React.ReactNode;\n}\n\n/**\n * Mounted once per section by the host's section wrapper, so every section in\n * every client package gets a collector without opting in.\n */\nexport const ImageSlotProvider: React.FC<ImageSlotProviderProps> = ({\n sectionId,\n children,\n}) => {\n // Optional on purpose: this is mounted by the host around EVERY section, so\n // it sits above trees that may have no dependencies configured at all (a\n // layout test, a story). Throwing there would make the image feature's\n // provider a global requirement for rendering any section.\n const deps = useOptionalComponentDependencies();\n const resolve = deps?.resolveImageSet;\n\n /**\n * Slot state lives in a ref, NOT in React state, and the context value never\n * changes. Holding it in state put it inside the provider's value, so every\n * resolution produced a new value and re-rendered every consumer in the\n * section — free at one slot, twelve re-renders per resolution in a\n * twelve-card grid, which is exactly the shape this boundary exists to serve.\n * Consumers now subscribe to their own id and nothing else.\n */\n const statesRef = useRef<Record<string, SlotState>>({});\n const listenersRef = useRef<Map<string, Set<() => void>>>(new Map());\n\n // Declarations arriving during this commit. A ref, not state: collecting a\n // declaration must not itself cause a render, or every hook that declares\n // would re-render every sibling before the batch is even sent.\n const pendingRef = useRef<Map<string, ImageSlotRequest>>(new Map());\n // Ids already sent. A re-render re-declares the same content-derived id, and\n // that must be a no-op rather than a second request.\n const sentRef = useRef<Set<string>>(new Set());\n const flushScheduled = useRef(false);\n const aliveRef = useRef(true);\n\n useEffect(() => {\n aliveRef.current = true;\n return () => {\n aliveRef.current = false;\n };\n }, []);\n\n const publish = useCallback((next: Record<string, SlotState>) => {\n const changed: string[] = [];\n for (const [id, state] of Object.entries(next)) {\n if (statesRef.current[id] !== state) {\n statesRef.current[id] = state;\n changed.push(id);\n }\n }\n for (const id of changed) {\n listenersRef.current.get(id)?.forEach((fn) => fn());\n }\n }, []);\n\n const markUnavailable = useCallback(\n (batch: ImageSlotRequest[]) => {\n if (!aliveRef.current) {\n return;\n }\n publish(\n Object.fromEntries(\n batch.map((s) => [s.id, { status: 'unavailable' } as SlotState]),\n ),\n );\n },\n [publish],\n );\n\n const flush = useCallback(async () => {\n flushScheduled.current = false;\n\n const batch = [...pendingRef.current.values()].filter(\n (s) => !sentRef.current.has(s.id),\n );\n pendingRef.current.clear();\n if (batch.length === 0) {\n return;\n }\n batch.forEach((s) => sentRef.current.add(s.id));\n\n // No port means the host has not injected a resolver. Fail the batch closed\n // rather than silently leaving slots pending forever.\n if (!resolve) {\n markUnavailable(batch);\n return;\n }\n\n try {\n const res = await resolve(batch);\n if (!aliveRef.current) {\n return;\n }\n const bySlotId = new Map(res.slots.map((s) => [s.slotId, s]));\n const next: Record<string, SlotState> = {};\n for (const req of batch) {\n const got = bySlotId.get(req.id);\n // A slot the resolver could not fill comes back with a null url. That\n // is `unavailable` to a layout, not a resolved picture.\n next[req.id] =\n got && got.imageUrl\n ? { status: 'resolved', slot: got }\n : { status: 'unavailable' };\n }\n publish(next);\n } catch (err) {\n // Failure is contained to this section: one bad request costs this\n // section its pictures and nothing else on the page.\n deps?.reportDiagnostic?.(\n DIAGNOSTIC_TYPES.IMAGE_RESOLUTION_FAILED,\n sectionId,\n `ResolveImageSet failed for ${batch.length} slot(s): ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n markUnavailable(batch);\n }\n }, [resolve, deps, sectionId, publish, markUnavailable]);\n\n const declare = useCallback(\n (slot: ImageSlotRequest) => {\n if (sentRef.current.has(slot.id)) {\n return;\n }\n pendingRef.current.set(slot.id, slot);\n // Flush on a microtask, so every declaration made during this commit —\n // the background, the cards, an inline image — lands in the same batch.\n if (!flushScheduled.current) {\n flushScheduled.current = true;\n queueMicrotask(() => {\n void flush();\n });\n }\n },\n [flush],\n );\n\n const read = useCallback(\n (slotId: string): SlotState => statesRef.current[slotId] ?? PENDING,\n [],\n );\n\n const subscribe = useCallback((slotId: string, onChange: () => void) => {\n let set = listenersRef.current.get(slotId);\n if (!set) {\n set = new Set();\n listenersRef.current.set(slotId, set);\n }\n set.add(onChange);\n return () => {\n set?.delete(onChange);\n if (set && set.size === 0) {\n listenersRef.current.delete(slotId);\n }\n };\n }, []);\n\n // Stable for the provider's lifetime: every member is a `useCallback` with no\n // reactive deps, so mounting this around a section costs its consumers\n // nothing after first render.\n const api = useMemo<ImageSlotCollector>(\n () => ({ declare, read, subscribe }),\n [declare, read, subscribe],\n );\n\n return (\n <ImageSlotContext.Provider value={api}>{children}</ImageSlotContext.Provider>\n );\n};\n\n/**\n * Null when no provider is mounted above — a host that has not adopted the\n * section wrapper's collector. `useImageSlot` handles that by resolving each\n * slot on its own, so a section still gets its picture and only loses batching\n * with its siblings.\n */\nexport const useImageSlotCollector = (): ImageSlotCollector | null =>\n useContext(ImageSlotContext);\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -37,6 +37,19 @@ import React, { createContext, useCallback, useContext, useEffect, useMemo, useR
|
|
|
37
37
|
import { useOptionalComponentDependencies } from './ComponentDependenciesContext.js';
|
|
38
38
|
import { DIAGNOSTIC_TYPES } from '../component/diagnosticTypes.js';
|
|
39
39
|
const ImageSlotContext = /*#__PURE__*/createContext(null);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Module-level so `read` returns a STABLE reference for a slot nobody has
|
|
43
|
+
* published yet. `useImageSlot` reads this store through
|
|
44
|
+
* `useSyncExternalStore`, which compares snapshots with `Object.is` on every
|
|
45
|
+
* render — a fresh `{ status: 'pending' }` per call reads as "the store
|
|
46
|
+
* changed", and React re-renders until it throws "Maximum update depth
|
|
47
|
+
* exceeded". Every slot is pending from mount until its batch answers, so a
|
|
48
|
+
* literal here takes down every section that declares one.
|
|
49
|
+
*/
|
|
50
|
+
const PENDING = {
|
|
51
|
+
status: 'pending'
|
|
52
|
+
};
|
|
40
53
|
/**
|
|
41
54
|
* Mounted once per section by the host's section wrapper, so every section in
|
|
42
55
|
* every client package gets a collector without opting in.
|
|
@@ -155,9 +168,7 @@ export const ImageSlotProvider = _ref => {
|
|
|
155
168
|
});
|
|
156
169
|
}
|
|
157
170
|
}, [flush]);
|
|
158
|
-
const read = useCallback(slotId => statesRef.current[slotId] ??
|
|
159
|
-
status: 'pending'
|
|
160
|
-
}, []);
|
|
171
|
+
const read = useCallback(slotId => statesRef.current[slotId] ?? PENDING, []);
|
|
161
172
|
const subscribe = useCallback((slotId, onChange) => {
|
|
162
173
|
let set = listenersRef.current.get(slotId);
|
|
163
174
|
if (!set) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["React","createContext","useCallback","useContext","useEffect","useMemo","useRef","useOptionalComponentDependencies","DIAGNOSTIC_TYPES","ImageSlotContext","ImageSlotProvider","_ref","sectionId","children","deps","resolve","resolveImageSet","statesRef","listenersRef","Map","pendingRef","sentRef","Set","flushScheduled","aliveRef","current","publish","next","changed","id","state","Object","entries","push","_listenersRef$current","get","forEach","fn","markUnavailable","batch","fromEntries","map","s","status","flush","values","filter","has","clear","length","add","res","bySlotId","slots","slotId","req","got","imageUrl","slot","err","reportDiagnostic","IMAGE_RESOLUTION_FAILED","Error","message","String","declare","set","queueMicrotask","read","subscribe","onChange","_set","delete","size","api","createElement","Provider","value","useImageSlotCollector"],"sources":["../../../src/context/ImageSlotContext.tsx"],"sourcesContent":["/**\n * The section-scoped image slot collector.\n *\n * A section declares the holes in its layout; it never makes a request. This\n * gathers every declaration made inside one section's subtree, fires ONE\n * `ResolveImageSet` through the injected port, and hands each declaration its\n * answer back by `slotId`. Sections stay ignorant of each other — no section\n * imports another, no ordering rules, no exclusion lists threaded through\n * props — and the network still sees the joint solve that distinctness depends\n * on.\n *\n * WHY THE BOUNDARY IS THE SECTION\n * -------------------------------\n * The resolver solves a maximum-weight one-to-one assignment across everything\n * in one request, so distinctness and palette cohesion are properties of that\n * solve and stop at its edge. Drawing the boundary at the page would buy\n * cross-section distinctness at the cost of making every section's pictures\n * wait on the slowest declaration on the page; drawing it at the section keeps\n * each section independent and accepts that two sections may land on the same\n * photograph. The most visible case — four identical photos in one grid — is\n * still prevented for free, because those slots share a request.\n *\n * WHY THERE IS NO READINESS POLICY\n * --------------------------------\n * There is nothing to wait for. A section component is never constructed from\n * partial markdown: a declared-but-unstreamed section is a `PageSection` with\n * `isSkeleton: true` and `component: null`, and the real element is built only\n * once its body has finished streaming. So a section's props are final the\n * moment it exists, and the flush is simply \"after this section's first render\n * commit\" — one effect, not a scheduler.\n *\n * The same fact bounds the win honestly: because only a *mounted* section\n * declares, no section can ask for a picture before its own body has streamed,\n * including a background whose subject needed nothing from that body.\n */\nimport React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n} from 'react';\nimport { useOptionalComponentDependencies } from './ComponentDependenciesContext';\nimport { DIAGNOSTIC_TYPES } from '../component/diagnosticTypes';\nimport type {\n ImageSlotRequest,\n SlotState,\n} from '../image/imageSlotTypes';\n\nexport interface ImageSlotCollector {\n /** Register a slot for this batch. Idempotent per content-derived id. */\n declare: (slot: ImageSlotRequest) => void;\n /** Current state for one slot. Not reactive alone — pair with `subscribe`. */\n read: (slotId: string) => SlotState;\n /** Notify only when THIS slot changes. Returns an unsubscribe. */\n subscribe: (slotId: string, onChange: () => void) => () => void;\n}\n\nconst ImageSlotContext = createContext<ImageSlotCollector | null>(null);\n\nexport interface ImageSlotProviderProps {\n /** Identifies the section in diagnostics. */\n sectionId: string;\n children: React.ReactNode;\n}\n\n/**\n * Mounted once per section by the host's section wrapper, so every section in\n * every client package gets a collector without opting in.\n */\nexport const ImageSlotProvider: React.FC<ImageSlotProviderProps> = ({\n sectionId,\n children,\n}) => {\n // Optional on purpose: this is mounted by the host around EVERY section, so\n // it sits above trees that may have no dependencies configured at all (a\n // layout test, a story). Throwing there would make the image feature's\n // provider a global requirement for rendering any section.\n const deps = useOptionalComponentDependencies();\n const resolve = deps?.resolveImageSet;\n\n /**\n * Slot state lives in a ref, NOT in React state, and the context value never\n * changes. Holding it in state put it inside the provider's value, so every\n * resolution produced a new value and re-rendered every consumer in the\n * section — free at one slot, twelve re-renders per resolution in a\n * twelve-card grid, which is exactly the shape this boundary exists to serve.\n * Consumers now subscribe to their own id and nothing else.\n */\n const statesRef = useRef<Record<string, SlotState>>({});\n const listenersRef = useRef<Map<string, Set<() => void>>>(new Map());\n\n // Declarations arriving during this commit. A ref, not state: collecting a\n // declaration must not itself cause a render, or every hook that declares\n // would re-render every sibling before the batch is even sent.\n const pendingRef = useRef<Map<string, ImageSlotRequest>>(new Map());\n // Ids already sent. A re-render re-declares the same content-derived id, and\n // that must be a no-op rather than a second request.\n const sentRef = useRef<Set<string>>(new Set());\n const flushScheduled = useRef(false);\n const aliveRef = useRef(true);\n\n useEffect(() => {\n aliveRef.current = true;\n return () => {\n aliveRef.current = false;\n };\n }, []);\n\n const publish = useCallback((next: Record<string, SlotState>) => {\n const changed: string[] = [];\n for (const [id, state] of Object.entries(next)) {\n if (statesRef.current[id] !== state) {\n statesRef.current[id] = state;\n changed.push(id);\n }\n }\n for (const id of changed) {\n listenersRef.current.get(id)?.forEach((fn) => fn());\n }\n }, []);\n\n const markUnavailable = useCallback(\n (batch: ImageSlotRequest[]) => {\n if (!aliveRef.current) {\n return;\n }\n publish(\n Object.fromEntries(\n batch.map((s) => [s.id, { status: 'unavailable' } as SlotState]),\n ),\n );\n },\n [publish],\n );\n\n const flush = useCallback(async () => {\n flushScheduled.current = false;\n\n const batch = [...pendingRef.current.values()].filter(\n (s) => !sentRef.current.has(s.id),\n );\n pendingRef.current.clear();\n if (batch.length === 0) {\n return;\n }\n batch.forEach((s) => sentRef.current.add(s.id));\n\n // No port means the host has not injected a resolver. Fail the batch closed\n // rather than silently leaving slots pending forever.\n if (!resolve) {\n markUnavailable(batch);\n return;\n }\n\n try {\n const res = await resolve(batch);\n if (!aliveRef.current) {\n return;\n }\n const bySlotId = new Map(res.slots.map((s) => [s.slotId, s]));\n const next: Record<string, SlotState> = {};\n for (const req of batch) {\n const got = bySlotId.get(req.id);\n // A slot the resolver could not fill comes back with a null url. That\n // is `unavailable` to a layout, not a resolved picture.\n next[req.id] =\n got && got.imageUrl\n ? { status: 'resolved', slot: got }\n : { status: 'unavailable' };\n }\n publish(next);\n } catch (err) {\n // Failure is contained to this section: one bad request costs this\n // section its pictures and nothing else on the page.\n deps?.reportDiagnostic?.(\n DIAGNOSTIC_TYPES.IMAGE_RESOLUTION_FAILED,\n sectionId,\n `ResolveImageSet failed for ${batch.length} slot(s): ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n markUnavailable(batch);\n }\n }, [resolve, deps, sectionId, publish, markUnavailable]);\n\n const declare = useCallback(\n (slot: ImageSlotRequest) => {\n if (sentRef.current.has(slot.id)) {\n return;\n }\n pendingRef.current.set(slot.id, slot);\n // Flush on a microtask, so every declaration made during this commit —\n // the background, the cards, an inline image — lands in the same batch.\n if (!flushScheduled.current) {\n flushScheduled.current = true;\n queueMicrotask(() => {\n void flush();\n });\n }\n },\n [flush],\n );\n\n const read = useCallback(\n (slotId: string): SlotState =>\n statesRef.current[slotId] ?? { status: 'pending' },\n [],\n );\n\n const subscribe = useCallback((slotId: string, onChange: () => void) => {\n let set = listenersRef.current.get(slotId);\n if (!set) {\n set = new Set();\n listenersRef.current.set(slotId, set);\n }\n set.add(onChange);\n return () => {\n set?.delete(onChange);\n if (set && set.size === 0) {\n listenersRef.current.delete(slotId);\n }\n };\n }, []);\n\n // Stable for the provider's lifetime: every member is a `useCallback` with no\n // reactive deps, so mounting this around a section costs its consumers\n // nothing after first render.\n const api = useMemo<ImageSlotCollector>(\n () => ({ declare, read, subscribe }),\n [declare, read, subscribe],\n );\n\n return (\n <ImageSlotContext.Provider value={api}>{children}</ImageSlotContext.Provider>\n );\n};\n\n/**\n * Null when no provider is mounted above — a host that has not adopted the\n * section wrapper's collector. `useImageSlot` handles that by resolving each\n * slot on its own, so a section still gets its picture and only loses batching\n * with its siblings.\n */\nexport const useImageSlotCollector = (): ImageSlotCollector | null =>\n useContext(ImageSlotContext);\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["React","createContext","useCallback","useContext","useEffect","useMemo","useRef","useOptionalComponentDependencies","DIAGNOSTIC_TYPES","ImageSlotContext","PENDING","status","ImageSlotProvider","_ref","sectionId","children","deps","resolve","resolveImageSet","statesRef","listenersRef","Map","pendingRef","sentRef","Set","flushScheduled","aliveRef","current","publish","next","changed","id","state","Object","entries","push","_listenersRef$current","get","forEach","fn","markUnavailable","batch","fromEntries","map","s","flush","values","filter","has","clear","length","add","res","bySlotId","slots","slotId","req","got","imageUrl","slot","err","reportDiagnostic","IMAGE_RESOLUTION_FAILED","Error","message","String","declare","set","queueMicrotask","read","subscribe","onChange","_set","delete","size","api","createElement","Provider","value","useImageSlotCollector"],"sources":["../../../src/context/ImageSlotContext.tsx"],"sourcesContent":["/**\n * The section-scoped image slot collector.\n *\n * A section declares the holes in its layout; it never makes a request. This\n * gathers every declaration made inside one section's subtree, fires ONE\n * `ResolveImageSet` through the injected port, and hands each declaration its\n * answer back by `slotId`. Sections stay ignorant of each other — no section\n * imports another, no ordering rules, no exclusion lists threaded through\n * props — and the network still sees the joint solve that distinctness depends\n * on.\n *\n * WHY THE BOUNDARY IS THE SECTION\n * -------------------------------\n * The resolver solves a maximum-weight one-to-one assignment across everything\n * in one request, so distinctness and palette cohesion are properties of that\n * solve and stop at its edge. Drawing the boundary at the page would buy\n * cross-section distinctness at the cost of making every section's pictures\n * wait on the slowest declaration on the page; drawing it at the section keeps\n * each section independent and accepts that two sections may land on the same\n * photograph. The most visible case — four identical photos in one grid — is\n * still prevented for free, because those slots share a request.\n *\n * WHY THERE IS NO READINESS POLICY\n * --------------------------------\n * There is nothing to wait for. A section component is never constructed from\n * partial markdown: a declared-but-unstreamed section is a `PageSection` with\n * `isSkeleton: true` and `component: null`, and the real element is built only\n * once its body has finished streaming. So a section's props are final the\n * moment it exists, and the flush is simply \"after this section's first render\n * commit\" — one effect, not a scheduler.\n *\n * The same fact bounds the win honestly: because only a *mounted* section\n * declares, no section can ask for a picture before its own body has streamed,\n * including a background whose subject needed nothing from that body.\n */\nimport React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n} from 'react';\nimport { useOptionalComponentDependencies } from './ComponentDependenciesContext';\nimport { DIAGNOSTIC_TYPES } from '../component/diagnosticTypes';\nimport type {\n ImageSlotRequest,\n SlotState,\n} from '../image/imageSlotTypes';\n\nexport interface ImageSlotCollector {\n /** Register a slot for this batch. Idempotent per content-derived id. */\n declare: (slot: ImageSlotRequest) => void;\n /** Current state for one slot. Not reactive alone — pair with `subscribe`. */\n read: (slotId: string) => SlotState;\n /** Notify only when THIS slot changes. Returns an unsubscribe. */\n subscribe: (slotId: string, onChange: () => void) => () => void;\n}\n\nconst ImageSlotContext = createContext<ImageSlotCollector | null>(null);\n\n/**\n * Module-level so `read` returns a STABLE reference for a slot nobody has\n * published yet. `useImageSlot` reads this store through\n * `useSyncExternalStore`, which compares snapshots with `Object.is` on every\n * render — a fresh `{ status: 'pending' }` per call reads as \"the store\n * changed\", and React re-renders until it throws \"Maximum update depth\n * exceeded\". Every slot is pending from mount until its batch answers, so a\n * literal here takes down every section that declares one.\n */\nconst PENDING: SlotState = { status: 'pending' };\n\nexport interface ImageSlotProviderProps {\n /** Identifies the section in diagnostics. */\n sectionId: string;\n children: React.ReactNode;\n}\n\n/**\n * Mounted once per section by the host's section wrapper, so every section in\n * every client package gets a collector without opting in.\n */\nexport const ImageSlotProvider: React.FC<ImageSlotProviderProps> = ({\n sectionId,\n children,\n}) => {\n // Optional on purpose: this is mounted by the host around EVERY section, so\n // it sits above trees that may have no dependencies configured at all (a\n // layout test, a story). Throwing there would make the image feature's\n // provider a global requirement for rendering any section.\n const deps = useOptionalComponentDependencies();\n const resolve = deps?.resolveImageSet;\n\n /**\n * Slot state lives in a ref, NOT in React state, and the context value never\n * changes. Holding it in state put it inside the provider's value, so every\n * resolution produced a new value and re-rendered every consumer in the\n * section — free at one slot, twelve re-renders per resolution in a\n * twelve-card grid, which is exactly the shape this boundary exists to serve.\n * Consumers now subscribe to their own id and nothing else.\n */\n const statesRef = useRef<Record<string, SlotState>>({});\n const listenersRef = useRef<Map<string, Set<() => void>>>(new Map());\n\n // Declarations arriving during this commit. A ref, not state: collecting a\n // declaration must not itself cause a render, or every hook that declares\n // would re-render every sibling before the batch is even sent.\n const pendingRef = useRef<Map<string, ImageSlotRequest>>(new Map());\n // Ids already sent. A re-render re-declares the same content-derived id, and\n // that must be a no-op rather than a second request.\n const sentRef = useRef<Set<string>>(new Set());\n const flushScheduled = useRef(false);\n const aliveRef = useRef(true);\n\n useEffect(() => {\n aliveRef.current = true;\n return () => {\n aliveRef.current = false;\n };\n }, []);\n\n const publish = useCallback((next: Record<string, SlotState>) => {\n const changed: string[] = [];\n for (const [id, state] of Object.entries(next)) {\n if (statesRef.current[id] !== state) {\n statesRef.current[id] = state;\n changed.push(id);\n }\n }\n for (const id of changed) {\n listenersRef.current.get(id)?.forEach((fn) => fn());\n }\n }, []);\n\n const markUnavailable = useCallback(\n (batch: ImageSlotRequest[]) => {\n if (!aliveRef.current) {\n return;\n }\n publish(\n Object.fromEntries(\n batch.map((s) => [s.id, { status: 'unavailable' } as SlotState]),\n ),\n );\n },\n [publish],\n );\n\n const flush = useCallback(async () => {\n flushScheduled.current = false;\n\n const batch = [...pendingRef.current.values()].filter(\n (s) => !sentRef.current.has(s.id),\n );\n pendingRef.current.clear();\n if (batch.length === 0) {\n return;\n }\n batch.forEach((s) => sentRef.current.add(s.id));\n\n // No port means the host has not injected a resolver. Fail the batch closed\n // rather than silently leaving slots pending forever.\n if (!resolve) {\n markUnavailable(batch);\n return;\n }\n\n try {\n const res = await resolve(batch);\n if (!aliveRef.current) {\n return;\n }\n const bySlotId = new Map(res.slots.map((s) => [s.slotId, s]));\n const next: Record<string, SlotState> = {};\n for (const req of batch) {\n const got = bySlotId.get(req.id);\n // A slot the resolver could not fill comes back with a null url. That\n // is `unavailable` to a layout, not a resolved picture.\n next[req.id] =\n got && got.imageUrl\n ? { status: 'resolved', slot: got }\n : { status: 'unavailable' };\n }\n publish(next);\n } catch (err) {\n // Failure is contained to this section: one bad request costs this\n // section its pictures and nothing else on the page.\n deps?.reportDiagnostic?.(\n DIAGNOSTIC_TYPES.IMAGE_RESOLUTION_FAILED,\n sectionId,\n `ResolveImageSet failed for ${batch.length} slot(s): ${\n err instanceof Error ? err.message : String(err)\n }`,\n );\n markUnavailable(batch);\n }\n }, [resolve, deps, sectionId, publish, markUnavailable]);\n\n const declare = useCallback(\n (slot: ImageSlotRequest) => {\n if (sentRef.current.has(slot.id)) {\n return;\n }\n pendingRef.current.set(slot.id, slot);\n // Flush on a microtask, so every declaration made during this commit —\n // the background, the cards, an inline image — lands in the same batch.\n if (!flushScheduled.current) {\n flushScheduled.current = true;\n queueMicrotask(() => {\n void flush();\n });\n }\n },\n [flush],\n );\n\n const read = useCallback(\n (slotId: string): SlotState => statesRef.current[slotId] ?? PENDING,\n [],\n );\n\n const subscribe = useCallback((slotId: string, onChange: () => void) => {\n let set = listenersRef.current.get(slotId);\n if (!set) {\n set = new Set();\n listenersRef.current.set(slotId, set);\n }\n set.add(onChange);\n return () => {\n set?.delete(onChange);\n if (set && set.size === 0) {\n listenersRef.current.delete(slotId);\n }\n };\n }, []);\n\n // Stable for the provider's lifetime: every member is a `useCallback` with no\n // reactive deps, so mounting this around a section costs its consumers\n // nothing after first render.\n const api = useMemo<ImageSlotCollector>(\n () => ({ declare, read, subscribe }),\n [declare, read, subscribe],\n );\n\n return (\n <ImageSlotContext.Provider value={api}>{children}</ImageSlotContext.Provider>\n );\n};\n\n/**\n * Null when no provider is mounted above — a host that has not adopted the\n * section wrapper's collector. `useImageSlot` handles that by resolving each\n * slot on its own, so a section still gets its picture and only loses batching\n * with its siblings.\n */\nexport const useImageSlotCollector = (): ImageSlotCollector | null =>\n useContext(ImageSlotContext);\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;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAOA,KAAK,IACVC,aAAa,EACbC,WAAW,EACXC,UAAU,EACVC,SAAS,EACTC,OAAO,EACPC,MAAM,QACD,OAAO;AACd,SAASC,gCAAgC,QAAQ,gCAAgC;AACjF,SAASC,gBAAgB,QAAQ,8BAA8B;AAe/D,MAAMC,gBAAgB,gBAAGR,aAAa,CAA4B,IAAI,CAAC;;AAEvE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,MAAMS,OAAkB,GAAG;EAAEC,MAAM,EAAE;AAAU,CAAC;AAQhD;AACA;AACA;AACA;AACA,OAAO,MAAMC,iBAAmD,GAAGC,IAAA,IAG7D;EAAA,IAH8D;IAClEC,SAAS;IACTC;EACF,CAAC,GAAAF,IAAA;EACC;EACA;EACA;EACA;EACA,MAAMG,IAAI,GAAGT,gCAAgC,CAAC,CAAC;EAC/C,MAAMU,OAAO,GAAGD,IAAI,oBAAJA,IAAI,CAAEE,eAAe;;EAErC;AACF;AACA;AACA;AACA;AACA;AACA;AACA;EACE,MAAMC,SAAS,GAAGb,MAAM,CAA4B,CAAC,CAAC,CAAC;EACvD,MAAMc,YAAY,GAAGd,MAAM,CAA+B,IAAIe,GAAG,CAAC,CAAC,CAAC;;EAEpE;EACA;EACA;EACA,MAAMC,UAAU,GAAGhB,MAAM,CAAgC,IAAIe,GAAG,CAAC,CAAC,CAAC;EACnE;EACA;EACA,MAAME,OAAO,GAAGjB,MAAM,CAAc,IAAIkB,GAAG,CAAC,CAAC,CAAC;EAC9C,MAAMC,cAAc,GAAGnB,MAAM,CAAC,KAAK,CAAC;EACpC,MAAMoB,QAAQ,GAAGpB,MAAM,CAAC,IAAI,CAAC;EAE7BF,SAAS,CAAC,MAAM;IACdsB,QAAQ,CAACC,OAAO,GAAG,IAAI;IACvB,OAAO,MAAM;MACXD,QAAQ,CAACC,OAAO,GAAG,KAAK;IAC1B,CAAC;EACH,CAAC,EAAE,EAAE,CAAC;EAEN,MAAMC,OAAO,GAAG1B,WAAW,CAAE2B,IAA+B,IAAK;IAC/D,MAAMC,OAAiB,GAAG,EAAE;IAC5B,KAAK,MAAM,CAACC,EAAE,EAAEC,KAAK,CAAC,IAAIC,MAAM,CAACC,OAAO,CAACL,IAAI,CAAC,EAAE;MAC9C,IAAIV,SAAS,CAACQ,OAAO,CAACI,EAAE,CAAC,KAAKC,KAAK,EAAE;QACnCb,SAAS,CAACQ,OAAO,CAACI,EAAE,CAAC,GAAGC,KAAK;QAC7BF,OAAO,CAACK,IAAI,CAACJ,EAAE,CAAC;MAClB;IACF;IACA,KAAK,MAAMA,EAAE,IAAID,OAAO,EAAE;MAAA,IAAAM,qBAAA;MACxB,CAAAA,qBAAA,GAAAhB,YAAY,CAACO,OAAO,CAACU,GAAG,CAACN,EAAE,CAAC,aAA5BK,qBAAA,CAA8BE,OAAO,CAAEC,EAAE,IAAKA,EAAE,CAAC,CAAC,CAAC;IACrD;EACF,CAAC,EAAE,EAAE,CAAC;EAEN,MAAMC,eAAe,GAAGtC,WAAW,CAChCuC,KAAyB,IAAK;IAC7B,IAAI,CAACf,QAAQ,CAACC,OAAO,EAAE;MACrB;IACF;IACAC,OAAO,CACLK,MAAM,CAACS,WAAW,CAChBD,KAAK,CAACE,GAAG,CAAEC,CAAC,IAAK,CAACA,CAAC,CAACb,EAAE,EAAE;MAAEpB,MAAM,EAAE;IAAc,CAAC,CAAc,CACjE,CACF,CAAC;EACH,CAAC,EACD,CAACiB,OAAO,CACV,CAAC;EAED,MAAMiB,KAAK,GAAG3C,WAAW,CAAC,YAAY;IACpCuB,cAAc,CAACE,OAAO,GAAG,KAAK;IAE9B,MAAMc,KAAK,GAAG,CAAC,GAAGnB,UAAU,CAACK,OAAO,CAACmB,MAAM,CAAC,CAAC,CAAC,CAACC,MAAM,CAClDH,CAAC,IAAK,CAACrB,OAAO,CAACI,OAAO,CAACqB,GAAG,CAACJ,CAAC,CAACb,EAAE,CAClC,CAAC;IACDT,UAAU,CAACK,OAAO,CAACsB,KAAK,CAAC,CAAC;IAC1B,IAAIR,KAAK,CAACS,MAAM,KAAK,CAAC,EAAE;MACtB;IACF;IACAT,KAAK,CAACH,OAAO,CAAEM,CAAC,IAAKrB,OAAO,CAACI,OAAO,CAACwB,GAAG,CAACP,CAAC,CAACb,EAAE,CAAC,CAAC;;IAE/C;IACA;IACA,IAAI,CAACd,OAAO,EAAE;MACZuB,eAAe,CAACC,KAAK,CAAC;MACtB;IACF;IAEA,IAAI;MACF,MAAMW,GAAG,GAAG,MAAMnC,OAAO,CAACwB,KAAK,CAAC;MAChC,IAAI,CAACf,QAAQ,CAACC,OAAO,EAAE;QACrB;MACF;MACA,MAAM0B,QAAQ,GAAG,IAAIhC,GAAG,CAAC+B,GAAG,CAACE,KAAK,CAACX,GAAG,CAAEC,CAAC,IAAK,CAACA,CAAC,CAACW,MAAM,EAAEX,CAAC,CAAC,CAAC,CAAC;MAC7D,MAAMf,IAA+B,GAAG,CAAC,CAAC;MAC1C,KAAK,MAAM2B,GAAG,IAAIf,KAAK,EAAE;QACvB,MAAMgB,GAAG,GAAGJ,QAAQ,CAAChB,GAAG,CAACmB,GAAG,CAACzB,EAAE,CAAC;QAChC;QACA;QACAF,IAAI,CAAC2B,GAAG,CAACzB,EAAE,CAAC,GACV0B,GAAG,IAAIA,GAAG,CAACC,QAAQ,GACf;UAAE/C,MAAM,EAAE,UAAU;UAAEgD,IAAI,EAAEF;QAAI,CAAC,GACjC;UAAE9C,MAAM,EAAE;QAAc,CAAC;MACjC;MACAiB,OAAO,CAACC,IAAI,CAAC;IACf,CAAC,CAAC,OAAO+B,GAAG,EAAE;MACZ;MACA;MACA5C,IAAI,YAAJA,IAAI,CAAE6C,gBAAgB,YAAtB7C,IAAI,CAAE6C,gBAAgB,CACpBrD,gBAAgB,CAACsD,uBAAuB,EACxChD,SAAS,EACT,8BAA8B2B,KAAK,CAACS,MAAM,aACxCU,GAAG,YAAYG,KAAK,GAAGH,GAAG,CAACI,OAAO,GAAGC,MAAM,CAACL,GAAG,CAAC,EAEpD,CAAC;MACDpB,eAAe,CAACC,KAAK,CAAC;IACxB;EACF,CAAC,EAAE,CAACxB,OAAO,EAAED,IAAI,EAAEF,SAAS,EAAEc,OAAO,EAAEY,eAAe,CAAC,CAAC;EAExD,MAAM0B,OAAO,GAAGhE,WAAW,CACxByD,IAAsB,IAAK;IAC1B,IAAIpC,OAAO,CAACI,OAAO,CAACqB,GAAG,CAACW,IAAI,CAAC5B,EAAE,CAAC,EAAE;MAChC;IACF;IACAT,UAAU,CAACK,OAAO,CAACwC,GAAG,CAACR,IAAI,CAAC5B,EAAE,EAAE4B,IAAI,CAAC;IACrC;IACA;IACA,IAAI,CAAClC,cAAc,CAACE,OAAO,EAAE;MAC3BF,cAAc,CAACE,OAAO,GAAG,IAAI;MAC7ByC,cAAc,CAAC,MAAM;QACnB,KAAKvB,KAAK,CAAC,CAAC;MACd,CAAC,CAAC;IACJ;EACF,CAAC,EACD,CAACA,KAAK,CACR,CAAC;EAED,MAAMwB,IAAI,GAAGnE,WAAW,CACrBqD,MAAc,IAAgBpC,SAAS,CAACQ,OAAO,CAAC4B,MAAM,CAAC,IAAI7C,OAAO,EACnE,EACF,CAAC;EAED,MAAM4D,SAAS,GAAGpE,WAAW,CAAC,CAACqD,MAAc,EAAEgB,QAAoB,KAAK;IACtE,IAAIJ,GAAG,GAAG/C,YAAY,CAACO,OAAO,CAACU,GAAG,CAACkB,MAAM,CAAC;IAC1C,IAAI,CAACY,GAAG,EAAE;MACRA,GAAG,GAAG,IAAI3C,GAAG,CAAC,CAAC;MACfJ,YAAY,CAACO,OAAO,CAACwC,GAAG,CAACZ,MAAM,EAAEY,GAAG,CAAC;IACvC;IACAA,GAAG,CAAChB,GAAG,CAACoB,QAAQ,CAAC;IACjB,OAAO,MAAM;MAAA,IAAAC,IAAA;MACX,CAAAA,IAAA,GAAAL,GAAG,aAAHK,IAAA,CAAKC,MAAM,CAACF,QAAQ,CAAC;MACrB,IAAIJ,GAAG,IAAIA,GAAG,CAACO,IAAI,KAAK,CAAC,EAAE;QACzBtD,YAAY,CAACO,OAAO,CAAC8C,MAAM,CAAClB,MAAM,CAAC;MACrC;IACF,CAAC;EACH,CAAC,EAAE,EAAE,CAAC;;EAEN;EACA;EACA;EACA,MAAMoB,GAAG,GAAGtE,OAAO,CACjB,OAAO;IAAE6D,OAAO;IAAEG,IAAI;IAAEC;EAAU,CAAC,CAAC,EACpC,CAACJ,OAAO,EAAEG,IAAI,EAAEC,SAAS,CAC3B,CAAC;EAED,oBACEtE,KAAA,CAAA4E,aAAA,CAACnE,gBAAgB,CAACoE,QAAQ;IAACC,KAAK,EAAEH;EAAI,GAAE5D,QAAoC,CAAC;AAEjF,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMgE,qBAAqB,GAAGA,CAAA,KACnC5E,UAAU,CAACM,gBAAgB,CAAC","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ImageSlotContext.d.ts","sourceRoot":"","sources":["../../../src/context/ImageSlotContext.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,OAAO,KAON,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EACV,gBAAgB,EAChB,SAAS,EACV,MAAM,yBAAyB,CAAC;AAEjC,MAAM,WAAW,kBAAkB;IACjC,yEAAyE;IACzE,OAAO,EAAE,CAAC,IAAI,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC1C,8EAA8E;IAC9E,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,SAAS,CAAC;IACpC,kEAAkE;IAClE,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;CACjE;
|
|
1
|
+
{"version":3,"file":"ImageSlotContext.d.ts","sourceRoot":"","sources":["../../../src/context/ImageSlotContext.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,OAAO,KAON,MAAM,OAAO,CAAC;AAGf,OAAO,KAAK,EACV,gBAAgB,EAChB,SAAS,EACV,MAAM,yBAAyB,CAAC;AAEjC,MAAM,WAAW,kBAAkB;IACjC,yEAAyE;IACzE,OAAO,EAAE,CAAC,IAAI,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC1C,8EAA8E;IAC9E,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,SAAS,CAAC;IACpC,kEAAkE;IAClE,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;CACjE;AAeD,MAAM,WAAW,sBAAsB;IACrC,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,sBAAsB,CAqK9D,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,QAAO,kBAAkB,GAAG,IAChC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/web5-core",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "1.63.
|
|
4
|
+
"version": "1.63.28",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tsachis",
|
|
7
7
|
"email": "tsachis@wix.com"
|
|
@@ -100,5 +100,5 @@
|
|
|
100
100
|
"wallaby": {
|
|
101
101
|
"autoDetect": true
|
|
102
102
|
},
|
|
103
|
-
"falconPackageHash": "
|
|
103
|
+
"falconPackageHash": "b3550211ac3d2098f3873cb795d5501b74a37d235bb3c8aee1920419"
|
|
104
104
|
}
|