| 1 | import { createContext, useContext, useEffect, useMemo, useState } from 'react' |
| 2 | |
| 3 | interface FontFaceContextInterface { |
| 4 | styles: readonly string[] |
| 5 | setStyles: (styles: string[] | ((styles: string[]) => string[])) => void |
| 6 | } |
| 7 | |
| 8 | const FontFaceContext = createContext<FontFaceContextInterface>({ |
| 9 | styles: [], |
| 10 | setStyles: () => { |
| 11 | throw new Error('Required wrapping by <FontFaceProvider>.') |
| 12 | }, |
| 13 | }) |
| 14 | |
| 15 | export const useFontFace = (fonts: string | string[]) => { |
| 16 | const fnts = useMemo(() => ([] as string[]).concat(fonts), [fonts]) |
| 17 | const { setStyles } = useContext(FontFaceContext) |
| 18 | |
| 19 | useEffect(() => { |
| 20 | setStyles((styles) => [...styles, ...fnts]) |
| 21 | |
| 22 | return () => { |
| 23 | const _fnts = [...fnts] |
| 24 | |
| 25 | setStyles((styles) => |
| 26 | styles.filter((existingStyle) => { |
| 27 | const fntIdx = _fnts.indexOf(existingStyle) |
| 28 | if (fntIdx >= 0) _fnts.splice(fntIdx, 1) |
| 29 | |
| 30 | return fntIdx !== -1 |
| 31 | }) |
| 32 | ) |
| 33 | } |
| 34 | }, [fnts, setStyles]) |
| 35 | } |
| 36 | |
| 37 | // eslint-disable-next-line @typescript-eslint/ban-types |
| 38 | export const FontFaceProvider: React.FC<React.PropsWithChildren<{}>> = ({ |
| 39 | children, |
| 40 | }) => { |
| 41 | const [styles, setStyles] = useState<string[]>([]) |
| 42 | |
| 43 | return ( |
| 44 | <FontFaceContext.Provider value={{ styles, setStyles }}> |
| 45 | {children} |
| 46 | </FontFaceContext.Provider> |
| 47 | ) |
| 48 | } |
| 49 | |
| 50 | export const FontFaceRenderer = () => { |
| 51 | const { styles } = useContext(FontFaceContext) |
| 52 | |
| 53 | return ( |
| 54 | <> |
| 55 | {[...new Set(styles)] |
| 56 | .filter((style) => !!style) |
| 57 | .map((style) => ( |
| 58 | <style data-type="marp-font-face" key={style}> |
| 59 | {style} |
| 60 | </style> |
| 61 | ))} |
| 62 | </> |
| 63 | ) |
| 64 | } |
| 65 |