|
1 | | -import React, { useEffect, useMemo, useRef, useState } from "react" |
2 | | -import { Helmet } from "react-helmet" |
3 | | -import { kebabCase } from "../utils" |
4 | | - |
5 | | -declare var document: { fonts: any } |
| 1 | +import React from "react" |
| 2 | +import { hookOptions, useFontListener } from "../hooks" |
6 | 3 |
|
7 | 4 | interface Props { |
8 | | - fontNames: string[] |
9 | | - interval: number |
10 | | - timeout: number |
| 5 | + options: hookOptions |
11 | 6 | } |
12 | 7 |
|
13 | | -export const FontListener: React.FC<Props> = ({ |
14 | | - fontNames, |
15 | | - interval, |
16 | | - timeout, |
17 | | -}) => { |
18 | | - const [hasLoaded, setHasLoaded] = useState<Boolean>(false) |
19 | | - const [loadedFonts, setLoadedFonts] = useState<string[]>([]) |
20 | | - const [intervalId, setIntervalId] = useState<number>(-1) |
21 | | - const attempts = useRef<number>(Math.floor(timeout / interval)) |
22 | | - |
23 | | - const pendingFonts = useMemo( |
24 | | - () => fontNames.filter(fontName => !loadedFonts.includes(fontName)), |
25 | | - [loadedFonts, fontNames] |
26 | | - ) |
27 | | - |
28 | | - const loadedClassname = useMemo(getLoadedFontClassNames, [loadedFonts]) |
29 | | - |
30 | | - const apiAvailable = "fonts" in document |
31 | | - |
32 | | - useEffect(() => { |
33 | | - if (!apiAvailable) { |
34 | | - handleApiError("Font loading API not available") |
35 | | - return |
36 | | - } |
37 | | - |
38 | | - if (apiAvailable && !hasLoaded && intervalId < 0) { |
39 | | - const id = window.setInterval(isFontLoaded, interval) |
40 | | - setIntervalId(id) |
41 | | - } |
42 | | - }, [hasLoaded, intervalId, apiAvailable]) |
43 | | - |
44 | | - useEffect(() => { |
45 | | - if (hasLoaded && intervalId > 0) { |
46 | | - clearInterval(intervalId) |
47 | | - } |
48 | | - }, [hasLoaded, intervalId]) |
49 | | - |
50 | | - return ( |
51 | | - <Helmet> |
52 | | - <body className={loadedClassname} /> |
53 | | - </Helmet> |
54 | | - ) |
55 | | - |
56 | | - function getLoadedFontClassNames() { |
57 | | - return Boolean(loadedFonts.length) |
58 | | - ? loadedFonts |
59 | | - .map(fontName => `wf-${kebabCase(fontName)}--loaded`) |
60 | | - .join(" ") |
61 | | - : "" |
62 | | - } |
63 | | - |
64 | | - function errorFallback() { |
65 | | - setHasLoaded(true) |
66 | | - setLoadedFonts(fontNames) |
67 | | - } |
68 | | - |
69 | | - function handleApiError(error) { |
70 | | - console.info(`document.fonts API error: ${error}`) |
71 | | - console.info(`Replacing fonts instantly. FOUT handling failed due.`) |
72 | | - errorFallback() |
73 | | - } |
74 | | - |
75 | | - function isFontLoaded() { |
76 | | - const loaded = [] |
77 | | - attempts.current = attempts.current - 1 |
78 | | - |
79 | | - if (attempts.current < 0) { |
80 | | - handleApiError("Interval timeout reached, maybe due to slow connection.") |
81 | | - } |
82 | | - |
83 | | - const fontsLoading = pendingFonts.map(fontName => { |
84 | | - let hasLoaded = false |
85 | | - try { |
86 | | - hasLoaded = document.fonts.check(`12px '${fontName}'`) |
87 | | - } catch (error) { |
88 | | - handleApiError(error) |
89 | | - return |
90 | | - } |
91 | | - |
92 | | - if (hasLoaded) loaded.push(fontName) |
93 | | - return hasLoaded |
94 | | - }) |
95 | | - |
96 | | - const allFontsLoaded = fontsLoading.every(font => font) |
97 | | - |
98 | | - if (Boolean(loaded.length)) { |
99 | | - setLoadedFonts(loaded) |
100 | | - } |
| 8 | +export const FontListener: React.FC<Props> = ({ children, options }) => { |
| 9 | + useFontListener(options) |
101 | 10 |
|
102 | | - if (allFontsLoaded) { |
103 | | - setHasLoaded(true) |
104 | | - } |
105 | | - } |
| 11 | + return <>{children}</> |
106 | 12 | } |
0 commit comments