@flamingo-stack/openframe-frontend-core 0.0.177 → 0.0.178
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/{chunk-6LDN3CIY.js → chunk-AAX27BCR.js} +189 -348
- package/dist/chunk-AAX27BCR.js.map +1 -0
- package/dist/{chunk-WX7PT5C7.cjs → chunk-ALW3D72O.cjs} +61 -2
- package/dist/chunk-ALW3D72O.cjs.map +1 -0
- package/dist/{chunk-KB2N44BY.js → chunk-FMWHOUFE.js} +61 -2
- package/dist/chunk-FMWHOUFE.js.map +1 -0
- package/dist/{chunk-C6ZMI4UB.cjs → chunk-L4T24AN4.cjs} +113 -272
- package/dist/chunk-L4T24AN4.cjs.map +1 -0
- package/dist/components/features/index.cjs +3 -5
- package/dist/components/features/index.cjs.map +1 -1
- package/dist/components/features/index.js +2 -4
- package/dist/components/features/video-player.d.ts +17 -20
- package/dist/components/features/video-player.d.ts.map +1 -1
- package/dist/components/features/youtube-embed.d.ts +18 -4
- package/dist/components/features/youtube-embed.d.ts.map +1 -1
- package/dist/components/index.cjs +3 -5
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +2 -4
- package/dist/components/navigation/index.cjs +3 -3
- package/dist/components/navigation/index.js +2 -2
- package/dist/components/ui/index.cjs +3 -3
- package/dist/components/ui/index.js +2 -2
- package/dist/hooks/index.cjs +4 -2
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +3 -1
- package/dist/hooks/use-near-viewport.d.ts +42 -0
- package/dist/hooks/use-near-viewport.d.ts.map +1 -0
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -4
- package/package.json +1 -1
- package/src/components/features/video-player.tsx +39 -176
- package/src/components/features/youtube-embed.tsx +107 -224
- package/src/hooks/index.ts +3 -0
- package/src/hooks/use-near-viewport.ts +118 -0
- package/dist/chunk-6LDN3CIY.js.map +0 -1
- package/dist/chunk-C6ZMI4UB.cjs.map +0 -1
- package/dist/chunk-KB2N44BY.js.map +0 -1
- package/dist/chunk-WX7PT5C7.cjs.map +0 -1
- package/src/components/features/__tests__/video-player.test.tsx +0 -142
package/src/hooks/index.ts
CHANGED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useNearViewport — module-level shared IntersectionObserver in hook form.
|
|
3
|
+
*
|
|
4
|
+
* Single IO instance per `rootMargin` value, shared across every component
|
|
5
|
+
* that mounts the hook. Reduces overhead vs. one IO per component on
|
|
6
|
+
* grid/list pages where many subscribers observe the viewport with the same
|
|
7
|
+
* margin. Promoted from the inline singleton at
|
|
8
|
+
* `multi-platform-hub/components/shared/video-bites-display.tsx:21-43`,
|
|
9
|
+
* which is the only IO pattern in either repo today.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* ```tsx
|
|
13
|
+
* function MyCard() {
|
|
14
|
+
* const { ref, isNear } = useNearViewport('500px');
|
|
15
|
+
* return <div ref={ref}>{isNear ? <HeavyChild /> : <Placeholder />}</div>;
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* StrictMode safety: cleanup uses an identity check on the registered
|
|
20
|
+
* callback so React's dev double-mount (mount → cleanup → re-mount) does
|
|
21
|
+
* not drop the second mount's freshly-set subscription. The IO callback
|
|
22
|
+
* also checks `subscribers.get(target)` before invoking so a fire that
|
|
23
|
+
* races with unmount cannot crash on a torn-down component.
|
|
24
|
+
*
|
|
25
|
+
* The hook fires once — on first intersection it sets `isNear=true` and
|
|
26
|
+
* unobserves the element. Callers that need re-observation should
|
|
27
|
+
* unmount and remount (or fork the hook for two-way behavior).
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { useEffect, useRef, useState, useCallback } from 'react';
|
|
31
|
+
|
|
32
|
+
// Per-rootMargin IO map. Multiple call sites with different margins each
|
|
33
|
+
// get their own singleton observer.
|
|
34
|
+
const observers = new Map<string, IntersectionObserver>();
|
|
35
|
+
const subscribers = new WeakMap<Element, () => void>();
|
|
36
|
+
|
|
37
|
+
function getObserverFor(rootMargin: string): IntersectionObserver {
|
|
38
|
+
const existing = observers.get(rootMargin);
|
|
39
|
+
if (existing) return existing;
|
|
40
|
+
|
|
41
|
+
const io = new IntersectionObserver(
|
|
42
|
+
(entries) => {
|
|
43
|
+
entries.forEach((entry) => {
|
|
44
|
+
if (!entry.isIntersecting) return;
|
|
45
|
+
// Race-safe: re-read the callback at fire time. A late IO firing
|
|
46
|
+
// after cleanup must not invoke a stale callback.
|
|
47
|
+
const cb = subscribers.get(entry.target);
|
|
48
|
+
if (cb) {
|
|
49
|
+
cb();
|
|
50
|
+
io.unobserve(entry.target);
|
|
51
|
+
subscribers.delete(entry.target);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
},
|
|
55
|
+
{ rootMargin }
|
|
56
|
+
);
|
|
57
|
+
observers.set(rootMargin, io);
|
|
58
|
+
return io;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface UseNearViewportResult<T extends Element = HTMLElement> {
|
|
62
|
+
/** Ref to attach to the element you want to gate on visibility. */
|
|
63
|
+
ref: (node: T | null) => void;
|
|
64
|
+
/** Flips to `true` once the element enters within `rootMargin` of the viewport. Never flips back. */
|
|
65
|
+
isNear: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @param rootMargin Margin around the viewport (CSS-style string).
|
|
70
|
+
* '500px' = element starts mounting 500px before scroll-in.
|
|
71
|
+
* '1000px' = a full viewport's worth of lookahead.
|
|
72
|
+
* '0px' = strict on-screen detection.
|
|
73
|
+
*/
|
|
74
|
+
export function useNearViewport<T extends Element = HTMLElement>(
|
|
75
|
+
rootMargin: string = '500px'
|
|
76
|
+
): UseNearViewportResult<T> {
|
|
77
|
+
const [isNear, setIsNear] = useState(false);
|
|
78
|
+
const elRef = useRef<T | null>(null);
|
|
79
|
+
|
|
80
|
+
// Subscribe/unsubscribe on element change.
|
|
81
|
+
const ref = useCallback(
|
|
82
|
+
(node: T | null) => {
|
|
83
|
+
const prev = elRef.current;
|
|
84
|
+
|
|
85
|
+
// Unsubscribe previous, if any. Identity-check the callback so a
|
|
86
|
+
// StrictMode re-mount that has already re-registered keeps its sub.
|
|
87
|
+
if (prev) {
|
|
88
|
+
const stillOurs = subscribers.get(prev);
|
|
89
|
+
if (stillOurs) {
|
|
90
|
+
subscribers.delete(prev);
|
|
91
|
+
observers.get(rootMargin)?.unobserve(prev);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
elRef.current = node;
|
|
96
|
+
if (!node) return;
|
|
97
|
+
|
|
98
|
+
const cb = () => setIsNear(true);
|
|
99
|
+
subscribers.set(node, cb);
|
|
100
|
+
getObserverFor(rootMargin).observe(node);
|
|
101
|
+
},
|
|
102
|
+
[rootMargin]
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// Unsubscribe on unmount. Identity check guards the StrictMode race.
|
|
106
|
+
useEffect(() => {
|
|
107
|
+
return () => {
|
|
108
|
+
const el = elRef.current;
|
|
109
|
+
if (!el) return;
|
|
110
|
+
if (subscribers.get(el)) {
|
|
111
|
+
subscribers.delete(el);
|
|
112
|
+
observers.get(rootMargin)?.unobserve(el);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}, [rootMargin]);
|
|
116
|
+
|
|
117
|
+
return { ref, isNear };
|
|
118
|
+
}
|