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