@civitai/blocks-react 0.53.0 → 0.53.1
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/README.md
CHANGED
|
@@ -35,7 +35,9 @@ export function App() {
|
|
|
35
35
|
const rootRef = useRef<HTMLDivElement>(null);
|
|
36
36
|
useBlockResize(rootRef); // host fits the iframe to content
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
// No ref on the pre-init skeleton — useBlockResize observes the real root
|
|
39
|
+
// whenever it mounts, including on a later render.
|
|
40
|
+
if (!ready) return <div>Loading…</div>;
|
|
39
41
|
// `context` is a union keyed on slotId — narrow with the guard, not a cast.
|
|
40
42
|
if (!isModelSlotContext(context)) return <div ref={rootRef}>Wrong slot.</div>;
|
|
41
43
|
|
|
@@ -153,6 +155,12 @@ const rootRef = useRef<HTMLDivElement>(null);
|
|
|
153
155
|
useBlockResize(rootRef);
|
|
154
156
|
```
|
|
155
157
|
|
|
158
|
+
**The element may mount on a later render, and that is the normal case** — a
|
|
159
|
+
block renders a skeleton until `BLOCK_INIT` lands. The hook keys on the observed
|
|
160
|
+
*element*, so you do **not** need to pin the same `ref` to every branch of a
|
|
161
|
+
loading/ready conditional to keep the host resizing. Put it on the root you
|
|
162
|
+
actually want measured, in whichever branch renders it.
|
|
163
|
+
|
|
156
164
|
> Also set `iframe.minHeight` in your manifest to the block's *real* rendered
|
|
157
165
|
> height — a too-small minHeight makes the iframe seed short and grow-jump on
|
|
158
166
|
> `BLOCK_READY` (CLS). Measure it in the dev harness (gotcha #53).
|
|
@@ -1039,7 +1047,7 @@ import {
|
|
|
1039
1047
|
export function App() {
|
|
1040
1048
|
const { ready, theme } = useBlockContext();
|
|
1041
1049
|
const rootRef = useRef<HTMLDivElement>(null);
|
|
1042
|
-
if (!ready) return <div
|
|
1050
|
+
if (!ready) return <div>Loading…</div>;
|
|
1043
1051
|
|
|
1044
1052
|
return (
|
|
1045
1053
|
// GOTCHA #60 — theme your OWN root; that's what the pack reads.
|
|
@@ -9,11 +9,37 @@ import { type RefObject } from 'react';
|
|
|
9
9
|
* - Inline path: `InlineTransport.sendMessage` is a no-op (the host DOM
|
|
10
10
|
* reflows naturally), so the observer fires but no message goes out.
|
|
11
11
|
*
|
|
12
|
+
* 🔴 THE ELEMENT MAY MOUNT ON A LATER RENDER, AND THAT IS THE NORMAL CASE.
|
|
13
|
+
* Every block renders a skeleton until `BLOCK_INIT` lands, so on the first
|
|
14
|
+
* render there is nothing to observe. This hook therefore keys on the OBSERVED
|
|
15
|
+
* ELEMENT, not on the ref wrapper's identity — the same reasoning written down
|
|
16
|
+
* at `useBlockBreakpoint.ts`'s effect, with one mechanical difference that
|
|
17
|
+
* matters:
|
|
18
|
+
*
|
|
19
|
+
* `useBlockBreakpoint` can compare `ref.current` read DURING RENDER, because
|
|
20
|
+
* it re-renders its own caller and so always gets another render in which to
|
|
21
|
+
* notice. This hook re-renders nobody. React attaches a ref during COMMIT,
|
|
22
|
+
* i.e. AFTER the render that mounts it, so a `[ref.current]` dependency read
|
|
23
|
+
* during render is a render behind and — with no further render coming —
|
|
24
|
+
* never catches up. Measured: with `[ref.current]` as the dependency, a
|
|
25
|
+
* component that mounts its root on the second render still observes
|
|
26
|
+
* nothing.
|
|
27
|
+
*
|
|
28
|
+
* So the effect runs on every render (no dependency array) and does its own
|
|
29
|
+
* identity check against the element it is already observing. The check is a
|
|
30
|
+
* reference compare; the observer is torn down and rebuilt only when the
|
|
31
|
+
* element actually changes.
|
|
32
|
+
*
|
|
33
|
+
* Because of this, a block does NOT need to pin the same `ref` to every branch
|
|
34
|
+
* of a loading/ready conditional to keep the host resizing. That workaround was
|
|
35
|
+
* load-bearing before this fix and is not any more.
|
|
36
|
+
*
|
|
12
37
|
* @param ref - Ref to the block's root DOM element to observe.
|
|
13
38
|
*
|
|
14
39
|
* @example
|
|
15
40
|
* const rootRef = useRef<HTMLDivElement>(null);
|
|
16
41
|
* useBlockResize(rootRef); // host fits the iframe to content
|
|
42
|
+
* if (!ready) return <div>Loading…</div>; // no ref needed on this branch
|
|
17
43
|
* return <div ref={rootRef}>…</div>;
|
|
18
44
|
*/
|
|
19
45
|
export declare function useBlockResize(ref: RefObject<HTMLElement | null>): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBlockResize.d.ts","sourceRoot":"","sources":["../../src/hooks/useBlockResize.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"useBlockResize.d.ts","sourceRoot":"","sources":["../../src/hooks/useBlockResize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAI1D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,GAAG,IAAI,CAyCvE"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useEffect } from 'react';
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
2
|
import { getTransport } from '../internal/singleton.js';
|
|
3
3
|
/**
|
|
4
4
|
* Observes the referenced element's height and asks the host to resize on
|
|
@@ -10,20 +10,67 @@ import { getTransport } from '../internal/singleton.js';
|
|
|
10
10
|
* - Inline path: `InlineTransport.sendMessage` is a no-op (the host DOM
|
|
11
11
|
* reflows naturally), so the observer fires but no message goes out.
|
|
12
12
|
*
|
|
13
|
+
* 🔴 THE ELEMENT MAY MOUNT ON A LATER RENDER, AND THAT IS THE NORMAL CASE.
|
|
14
|
+
* Every block renders a skeleton until `BLOCK_INIT` lands, so on the first
|
|
15
|
+
* render there is nothing to observe. This hook therefore keys on the OBSERVED
|
|
16
|
+
* ELEMENT, not on the ref wrapper's identity — the same reasoning written down
|
|
17
|
+
* at `useBlockBreakpoint.ts`'s effect, with one mechanical difference that
|
|
18
|
+
* matters:
|
|
19
|
+
*
|
|
20
|
+
* `useBlockBreakpoint` can compare `ref.current` read DURING RENDER, because
|
|
21
|
+
* it re-renders its own caller and so always gets another render in which to
|
|
22
|
+
* notice. This hook re-renders nobody. React attaches a ref during COMMIT,
|
|
23
|
+
* i.e. AFTER the render that mounts it, so a `[ref.current]` dependency read
|
|
24
|
+
* during render is a render behind and — with no further render coming —
|
|
25
|
+
* never catches up. Measured: with `[ref.current]` as the dependency, a
|
|
26
|
+
* component that mounts its root on the second render still observes
|
|
27
|
+
* nothing.
|
|
28
|
+
*
|
|
29
|
+
* So the effect runs on every render (no dependency array) and does its own
|
|
30
|
+
* identity check against the element it is already observing. The check is a
|
|
31
|
+
* reference compare; the observer is torn down and rebuilt only when the
|
|
32
|
+
* element actually changes.
|
|
33
|
+
*
|
|
34
|
+
* Because of this, a block does NOT need to pin the same `ref` to every branch
|
|
35
|
+
* of a loading/ready conditional to keep the host resizing. That workaround was
|
|
36
|
+
* load-bearing before this fix and is not any more.
|
|
37
|
+
*
|
|
13
38
|
* @param ref - Ref to the block's root DOM element to observe.
|
|
14
39
|
*
|
|
15
40
|
* @example
|
|
16
41
|
* const rootRef = useRef<HTMLDivElement>(null);
|
|
17
42
|
* useBlockResize(rootRef); // host fits the iframe to content
|
|
43
|
+
* if (!ready) return <div>Loading…</div>; // no ref needed on this branch
|
|
18
44
|
* return <div ref={rootRef}>…</div>;
|
|
19
45
|
*/
|
|
20
46
|
export function useBlockResize(ref) {
|
|
47
|
+
/** The element the live observer is watching. `null` = watching nothing. */
|
|
48
|
+
const observedRef = useRef(null);
|
|
49
|
+
const observerRef = useRef(null);
|
|
50
|
+
// Unmount-only teardown. Declared FIRST so that on a remount (React
|
|
51
|
+
// StrictMode's deliberate double-invoke) its cleanup — which clears
|
|
52
|
+
// `observedRef` — runs before the observe effect's setup re-runs and finds a
|
|
53
|
+
// clean slate. React runs every cleanup before any setup.
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
return () => {
|
|
56
|
+
observerRef.current?.disconnect();
|
|
57
|
+
observerRef.current = null;
|
|
58
|
+
observedRef.current = null;
|
|
59
|
+
};
|
|
60
|
+
}, []);
|
|
61
|
+
// No dependency array on purpose — see the block comment above. The identity
|
|
62
|
+
// check below, not a dependency list, is what makes this cheap.
|
|
21
63
|
useEffect(() => {
|
|
64
|
+
if (typeof ResizeObserver === 'undefined')
|
|
65
|
+
return;
|
|
22
66
|
const el = ref.current;
|
|
67
|
+
if (el === observedRef.current)
|
|
68
|
+
return; // already observing exactly this
|
|
69
|
+
observerRef.current?.disconnect();
|
|
70
|
+
observerRef.current = null;
|
|
71
|
+
observedRef.current = el;
|
|
23
72
|
if (!el)
|
|
24
73
|
return;
|
|
25
|
-
if (typeof ResizeObserver === 'undefined')
|
|
26
|
-
return;
|
|
27
74
|
const transport = getTransport();
|
|
28
75
|
let lastHeight = -1;
|
|
29
76
|
const observer = new ResizeObserver((entries) => {
|
|
@@ -34,7 +81,7 @@ export function useBlockResize(ref) {
|
|
|
34
81
|
transport.sendMessage({ type: 'RESIZE_IFRAME', payload: { height } });
|
|
35
82
|
});
|
|
36
83
|
observer.observe(el);
|
|
37
|
-
|
|
38
|
-
}
|
|
84
|
+
observerRef.current = observer;
|
|
85
|
+
});
|
|
39
86
|
}
|
|
40
87
|
//# sourceMappingURL=useBlockResize.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useBlockResize.js","sourceRoot":"","sources":["../../src/hooks/useBlockResize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAkB,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"useBlockResize.js","sourceRoot":"","sources":["../../src/hooks/useBlockResize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,EAAkB,MAAM,OAAO,CAAC;AAE1D,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,MAAM,UAAU,cAAc,CAAC,GAAkC;IAC/D,4EAA4E;IAC5E,MAAM,WAAW,GAAG,MAAM,CAAqB,IAAI,CAAC,CAAC;IACrD,MAAM,WAAW,GAAG,MAAM,CAAwB,IAAI,CAAC,CAAC;IAExD,oEAAoE;IACpE,oEAAoE;IACpE,6EAA6E;IAC7E,0DAA0D;IAC1D,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,GAAG,EAAE;YACV,WAAW,CAAC,OAAO,EAAE,UAAU,EAAE,CAAC;YAClC,WAAW,CAAC,OAAO,GAAG,IAAI,CAAC;YAC3B,WAAW,CAAC,OAAO,GAAG,IAAI,CAAC;QAC7B,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,6EAA6E;IAC7E,gEAAgE;IAChE,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,OAAO,cAAc,KAAK,WAAW;YAAE,OAAO;QAElD,MAAM,EAAE,GAAG,GAAG,CAAC,OAAO,CAAC;QACvB,IAAI,EAAE,KAAK,WAAW,CAAC,OAAO;YAAE,OAAO,CAAC,iCAAiC;QAEzE,WAAW,CAAC,OAAO,EAAE,UAAU,EAAE,CAAC;QAClC,WAAW,CAAC,OAAO,GAAG,IAAI,CAAC;QAC3B,WAAW,CAAC,OAAO,GAAG,EAAE,CAAC;QACzB,IAAI,CAAC,EAAE;YAAE,OAAO;QAEhB,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;QACjC,IAAI,UAAU,GAAG,CAAC,CAAC,CAAC;QACpB,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,CAAC,OAAO,EAAE,EAAE;YAC9C,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,MAAM,IAAI,EAAE,CAAC,YAAY,CAAC,CAAC;YAC5E,IAAI,MAAM,KAAK,UAAU;gBAAE,OAAO;YAClC,UAAU,GAAG,MAAM,CAAC;YACpB,SAAS,CAAC,WAAW,CAAC,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;QACxE,CAAC,CAAC,CAAC;QACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;QACrB,WAAW,CAAC,OAAO,GAAG,QAAQ,CAAC;IACjC,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@civitai/blocks-react",
|
|
3
|
-
"version": "0.53.
|
|
3
|
+
"version": "0.53.1",
|
|
4
4
|
"description": "React hooks and iframe transport for Civitai Apps. Pairs with @civitai/app-sdk/blocks.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
"react-dom": "^19.0.0",
|
|
123
123
|
"typescript": "^5.9.2",
|
|
124
124
|
"vitest": "^4.1.11",
|
|
125
|
-
"@civitai/app-sdk": "^0.
|
|
125
|
+
"@civitai/app-sdk": "^0.46.0"
|
|
126
126
|
},
|
|
127
127
|
"publishConfig": {
|
|
128
128
|
"access": "public"
|