feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,2 @@
// @generated by expo-module-scripts
module.exports = require('expo-module-scripts/eslintrc.base.js');

View File

@@ -0,0 +1,294 @@
# Changelog
## Unpublished
### 🛠 Breaking changes
### 🎉 New features
### 🐛 Bug fixes
### 💡 Others
## 12.0.10 — 2024-09-03
### 🐛 Bug fixes
- Fix support for macOS. ([#31307](https://github.com/expo/expo/pull/31307) by [@gabrieldonadel](https://github.com/gabrieldonadel))
## 12.0.9 — 2024-07-16
### 🐛 Bug fixes
- On `iOS`, fix issues where fonts were removed when the app is backgrounded. ([#30400](https://github.com/expo/expo/pull/30400) by [@alanjhughes](https://github.com/alanjhughes))
## 12.0.8 — 2024-07-11
### 🎉 New features
- Allow woff and woff2 format on iOS with the config plugin. ([#30220](https://github.com/expo/expo/pull/30220) by [@titozzz](https://github/Titozzz))
## 12.0.7 — 2024-06-06
### 🐛 Bug fixes
- On `iOS`, store the font `postscriptName` instead of `fullName` which is what `iOS` will use to register the font. ([#29502](https://github.com/expo/expo/pull/29502) by [@alanjhughes](https://github.com/alanjhughes))
## 12.0.6 — 2024-05-29
### 🐛 Bug fixes
- [iOS] Fix font registration failing when font was in use. ([#28989](https://github.com/expo/expo/pull/28989) by [@aleqsio](https://github.com/aleqsio))
## 12.0.5 — 2024-05-10
### 🐛 Bug fixes
- Fixed some vector icons not rendering correctly. ([#28747](https://github.com/expo/expo/pull/28747) by [@tsapeta](https://github.com/tsapeta))
## 12.0.4 — 2024-04-24
_This version does not introduce any user-facing changes._
## 12.0.3 — 2024-04-24
- Fix font name lookup when name is not the same as family. ([#28407](https://github.com/expo/expo/pull/28407) by [@brentvatne](https://github.com/brentvatne))
## 12.0.2 — 2024-04-23
_This version does not introduce any user-facing changes._
## 12.0.1 — 2024-04-22
### 💡 Others
- Stopped scoping font family names in Expo Go on iOS. ([#28344](https://github.com/expo/expo/pull/28344) by [@tsapeta](https://github.com/tsapeta))
## 12.0.0 — 2024-04-18
### 🎉 New features
- [iOS] The native module has been simplified and rewritten to Swift. ([#26380](https://github.com/expo/expo/pull/26380) by [@tsapeta](https://github.com/tsapeta))
### 💡 Others
- drop unused web `name` property. ([#27437](https://github.com/expo/expo/pull/27437) by [@EvanBacon](https://github.com/EvanBacon))
- Remove most of Constants.appOwnership. ([#26313](https://github.com/expo/expo/pull/26313) by [@wschurman](https://github.com/wschurman))
- Removed deprecated backward compatible Gradle settings. ([#28083](https://github.com/expo/expo/pull/28083) by [@kudo](https://github.com/kudo))
## 11.10.3 - 2024-02-16
### 🐛 Bug fixes
- Only include supported font files when using the plugin. ([#27002](https://github.com/expo/expo/pull/27002) by [@alanjhughes](https://github.com/alanjhughes))
## 11.10.2 - 2024-01-18
_This version does not introduce any user-facing changes._
## 11.10.1 - 2024-01-10
### 🎉 New features
- Added support for macOS platform. ([#26242](https://github.com/expo/expo/pull/26242) by [@tsapeta](https://github.com/tsapeta))
## 11.10.0 — 2023-12-12
### 🎉 New features
- Added custom native fonts support to `Font.isLoaded()`. ([#25770](https://github.com/expo/expo/pull/25770) by [@kudo](https://github.com/kudo))
## 11.9.0 — 2023-11-14
### 🛠 Breaking changes
- Bumped iOS deployment target to 13.4. ([#25063](https://github.com/expo/expo/pull/25063) by [@gabrieldonadel](https://github.com/gabrieldonadel))
- On `Android` bump `compileSdkVersion` and `targetSdkVersion` to `34`. ([#24708](https://github.com/expo/expo/pull/24708) by [@alanjhughes](https://github.com/alanjhughes))
### 🐛 Bug fixes
- Handle the case where no argument is passed to the plugin. ([#25138](https://github.com/expo/expo/pull/25138) by [@alanjhughes](https://github.com/alanjhughes))
## 11.8.0 — 2023-10-17
### 🛠 Breaking changes
- Dropped support for Android SDK 21 and 22. ([#24201](https://github.com/expo/expo/pull/24201) by [@behenate](https://github.com/behenate))
### 🎉 New features
- Added config plugin to allow fonts to be linked at build time. ([#24772](https://github.com/expo/expo/pull/24772) by [@alanjhughes](https://github.com/alanjhughes))
- Remove `unimodule.json` in favour of `expo-module.config.json`. ([#25099](https://github.com/expo/expo/pull/25099) by [@reichhartd](https://github.com/reichhartd))
## 11.7.0 — 2023-09-15
### 🎉 New features
- Added support for Apple tvOS. ([#24329](https://github.com/expo/expo/pull/24329) by [@douglowder](https://github.com/douglowder))
### 💡 Others
- Updated types for server functions. ([#23911](https://github.com/expo/expo/pull/23911) by [@EvanBacon](https://github.com/EvanBacon))
## 11.6.0 — 2023-09-04
### 🎉 New features
- Added support for React Native 0.73. ([#24018](https://github.com/expo/expo/pull/24018) by [@kudo](https://github.com/kudo))
- Add static font extraction support with `expo-router`. ([#24027](https://github.com/expo/expo/pull/24027) by [@EvanBacon](https://github.com/EvanBacon))
### 💡 Others
- Migrated `FontLoaderModule` to use Expo Modules API. ([#24015](https://github.com/expo/expo/pull/24015) by [@lukmccall](https://github.com/lukmccall))
## 11.5.1 — 2023-08-02
### 💡 Others
- Change unloaded font error to a warning. ([#23788](https://github.com/expo/expo/pull/23788) by [@EvanBacon](https://github.com/EvanBacon))
## 11.5.0 — 2023-07-28
### 🐛 Bug fixes
- Gracefully catch exceptions during font loading on web with `fontfaceobserver`. ([#22954](https://github.com/expo/expo/pull/22954) by [@bradjones1](https://github.com/bradjones1))
## 11.4.0 — 2023-06-21
_This version does not introduce any user-facing changes._
## 11.3.0 — 2023-06-13
### 🐛 Bug fixes
- Fixed Android build warnings for Gradle version 8. ([#22537](https://github.com/expo/expo/pull/22537), [#22609](https://github.com/expo/expo/pull/22609) by [@kudo](https://github.com/kudo))
## 11.2.0 — 2023-05-08
### 🐛 Bug fixes
- Fix require cycle on web. ([#21593](https://github.com/expo/expo/pull/21593) by [@EvanBacon](https://github.com/EvanBacon))
## 11.1.1 — 2023-02-09
_This version does not introduce any user-facing changes._
## 11.1.0 — 2023-02-03
### 💡 Others
- On Android bump `compileSdkVersion` and `targetSdkVersion` to `33`. ([#20721](https://github.com/expo/expo/pull/20721) by [@lukmccall](https://github.com/lukmccall))
## 11.0.1 — 2022-10-28
_This version does not introduce any user-facing changes._
## 11.0.0 — 2022-10-06
### 🛠 Breaking changes
- Bumped iOS deployment target to 13.0 and deprecated support for iOS 12. ([#18873](https://github.com/expo/expo/pull/18873) by [@tsapeta](https://github.com/tsapeta))
### 🎉 New features
- Added support for Metro web. ([#19234](https://github.com/expo/expo/pull/19234) by [@EvanBacon](https://github.com/EvanBacon))
## 10.2.0 — 2022-07-07
_This version does not introduce any user-facing changes._
## 10.1.0 — 2022-04-18
### ⚠️ Notices
- On Android bump `compileSdkVersion` to `31`, `targetSdkVersion` to `31` and `Java` version to `11`. ([#16941](https://github.com/expo/expo/pull/16941) by [@bbarthec](https://github.com/bbarthec))
## 10.0.5 - 2022-02-01
### 🐛 Bug fixes
- Fix `Plugin with id 'maven' not found` build error from Android Gradle 7. ([#16080](https://github.com/expo/expo/pull/16080) by [@kudo](https://github.com/kudo))
## 10.0.4 — 2021-11-17
_This version does not introduce any user-facing changes._
## 10.0.1 — 2021-10-01
_This version does not introduce any user-facing changes._
## 10.0.0 — 2021-09-28
### 🛠 Breaking changes
- Dropped support for iOS 11.0 ([#14383](https://github.com/expo/expo/pull/14383) by [@cruzach](https://github.com/cruzach))
### 🐛 Bug fixes
- Fix building errors from use_frameworks! in Podfile. ([#14523](https://github.com/expo/expo/pull/14523) by [@kudo](https://github.com/kudo))
## 9.3.0 — 2021-09-08
### 💡 Others
- Rewrite android code to Kotlin ([#13956](https://github.com/expo/expo/pull/13956) by [@kkafar](https://github.com/kkafar))
## 9.2.0 — 2021-06-16
### 🐛 Bug fixes
- Enable kotlin in all modules. ([#12716](https://github.com/expo/expo/pull/12716) by [@wschurman](https://github.com/wschurman))
### 💡 Others
- Migrated `unimodules-font-interface` and `unimodules-constants-interface` to `expo-modules-core`. ([#12949](https://github.com/expo/expo/pull/12949), [#12876](https://github.com/expo/expo/pull/12876) by [@tsapeta](https://github.com/tsapeta))
- Build Android code using Java 8 to fix Android instrumented test build error. ([#12939](https://github.com/expo/expo/pull/12939) by [@kudo](https://github.com/kudo))
## 9.1.0 — 2021-03-10
### 🎉 New features
- Updated Android build configuration to target Android 11 (added support for Android SDK 30). ([#11647](https://github.com/expo/expo/pull/11647) by [@bbarthec](https://github.com/bbarthec))
### 🐛 Bug fixes
- Remove peerDependencies and unimodulePeerDependencies from Expo modules. ([#11980](https://github.com/expo/expo/pull/11980) by [@brentvatne](https://github.com/brentvatne))
## 9.0.0 — 2021-01-15
### 🛠 Breaking changes
- Dropped support for iOS 10.0 ([#11344](https://github.com/expo/expo/pull/11344) by [@tsapeta](https://github.com/tsapeta))
### 🐛 Bug fixes
- Remove Expo.AppLoading reference in error. ([#11204](https://github.com/expo/expo/pull/11204) by [@brentvatne](https://github.com/brentvatne))
- Removed `fbjs` dependency ([#11396](https://github.com/expo/expo/pull/11396) by [@cruzach](https://github.com/cruzach))
## 8.4.0 — 2020-11-17
_This version does not introduce any user-facing changes._
## 8.3.0 — 2020-08-18
_This version does not introduce any user-facing changes._
## 8.2.2 — 2020-07-27
### 🐛 Bug fixes
- Fixed fonts not being loaded in Internet Explorer. ([#8652](https://github.com/expo/expo/pull/8652) by [@d4rky-pl](https://github.com/d4rky-pl))
## 8.2.1 — 2020-05-29
_This version does not introduce any user-facing changes._
## 8.2.0 — 2020-05-27
_This version does not introduce any user-facing changes._
## 8.1.1 - 4/07/2020
### 🐛 Bug fixes
- Fixed timeout on Firefox [#7420](https://github.com/expo/expo/pull/7420)

View File

@@ -0,0 +1,41 @@
<p>
<a href="https://docs.expo.dev/versions/latest/sdk/font/">
<img
src="../../.github/resources/expo-font.svg"
alt="expo-font"
height="64" />
</a>
</p>
Load fonts at runtime and use them in React Native components.
# API documentation
- [Documentation for the main branch](https://github.com/expo/expo/blob/main/docs/pages/versions/unversioned/sdk/font.mdx)
- [Documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/font/)
# Installation in managed Expo projects
For [managed](https://docs.expo.dev/archive/managed-vs-bare/) Expo projects, please follow the installation instructions in the [API documentation for the latest stable release](https://docs.expo.dev/versions/latest/sdk/font/).
# Installation in bare React Native projects
For bare React Native projects, you must ensure that you have [installed and configured the `expo` package](https://docs.expo.dev/bare/installing-expo-modules/) before continuing.
### Add the package to your npm dependencies
```
npx expo install expo-font
```
### Configure for iOS
Run `npx pod-install` after installing the npm package.
### Configure for Android
No additional set up necessary.
# Contributing
Contributions are very welcome! Please refer to guidelines described in the [contributing guide](https://github.com/expo/expo#contributing).

View File

@@ -0,0 +1,19 @@
apply plugin: 'com.android.library'
group = 'host.exp.exponent'
version = '12.0.10'
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
apply from: expoModulesCorePlugin
applyKotlinExpoModulesCorePlugin()
useCoreDependencies()
useDefaultAndroidSdkVersions()
useExpoPublishing()
android {
namespace "expo.modules.font"
defaultConfig {
versionCode 29
versionName "12.0.10"
}
}

View File

@@ -0,0 +1,3 @@
<manifest>
</manifest>

View File

@@ -0,0 +1,74 @@
// Copyright 2015-present 650 Industries. All rights reserved.
package expo.modules.font
import android.content.Context
import android.graphics.Typeface
import android.net.Uri
import expo.modules.interfaces.font.FontManagerInterface
import expo.modules.kotlin.exception.CodedException
import expo.modules.kotlin.exception.Exceptions
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
import java.io.File
private const val ASSET_SCHEME = "asset://"
private class FontManagerInterfaceNotFoundException :
CodedException("FontManagerInterface not found")
private class FileNotFoundException(uri: String) :
CodedException("File '$uri' doesn't exist")
open class FontLoaderModule : Module() {
private val context: Context
get() = appContext.reactContext ?: throw Exceptions.ReactContextLost()
@Suppress("MemberVisibilityCanBePrivate")
open val prefix = ""
override fun definition() = ModuleDefinition {
Name("ExpoFontLoader")
Constants("customNativeFonts" to queryCustomNativeFonts())
AsyncFunction("loadAsync") { fontFamilyName: String, localUri: String ->
val context = appContext.reactContext ?: throw Exceptions.ReactContextLost()
// TODO(nikki): make sure path is in experience's scope
val typeface: Typeface = if (localUri.startsWith(ASSET_SCHEME)) {
Typeface.createFromAsset(
context.assets, // Also remove the leading slash.
localUri.substring(ASSET_SCHEME.length + 1)
)
} else {
val file = Uri.parse(localUri).path?.let { File(it) }
?: throw FileNotFoundException(localUri)
Typeface.createFromFile(file)
}
val fontManager = appContext.legacyModule<FontManagerInterface>()
?: throw FontManagerInterfaceNotFoundException()
fontManager.setTypeface(prefix + fontFamilyName, Typeface.NORMAL, typeface)
}
}
/**
* Queries custom native font names from the assets.
* Alignment with React Native's implementation at:
* https://github.com/facebook/react-native/blob/363ee484b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/assets/ReactFontManager.java#L146-L161
*/
private fun queryCustomNativeFonts(): List<String> {
val assetManager = context.assets
val fontFileRegex = Regex("^(.+?)(_bold|_italic|_bold_italic)?\\.(ttf|otf)$")
return assetManager.list("fonts/")
?.mapNotNull { fileName ->
fontFileRegex.find(fileName)?.groupValues?.get(1)
}
?.filter { it.isNotBlank() }
.orEmpty()
}
}

View File

@@ -0,0 +1 @@
module.exports = require('./plugin/build/withFonts');

View File

@@ -0,0 +1,3 @@
declare const _default: any;
export default _default;
//# sourceMappingURL=ExpoFontLoader.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoFontLoader.d.ts","sourceRoot":"","sources":["../src/ExpoFontLoader.ts"],"names":[],"mappings":";AACA,wBAAqD"}

View File

@@ -0,0 +1,3 @@
import { requireNativeModule } from 'expo-modules-core';
export default requireNativeModule('ExpoFontLoader');
//# sourceMappingURL=ExpoFontLoader.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoFontLoader.js","sourceRoot":"","sources":["../src/ExpoFontLoader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,eAAe,mBAAmB,CAAC,gBAAgB,CAAC,CAAC","sourcesContent":["import { requireNativeModule } from 'expo-modules-core';\nexport default requireNativeModule('ExpoFontLoader');\n"]}

View File

@@ -0,0 +1,13 @@
import { UnloadFontOptions } from './Font';
import { FontResource } from './Font.types';
declare const _default: {
unloadAllAsync(): Promise<void>;
unloadAsync(fontFamilyName: string, options?: UnloadFontOptions): Promise<void>;
getServerResources(): string[];
resetServerContext(): void;
isLoaded(fontFamilyName: string, resource?: UnloadFontOptions): boolean;
loadAsync(fontFamilyName: string, resource: FontResource): Promise<void>;
};
export default _default;
export declare function _createWebFontTemplate(fontFamily: string, resource: FontResource): string;
//# sourceMappingURL=ExpoFontLoader.web.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ExpoFontLoader.web.d.ts","sourceRoot":"","sources":["../src/ExpoFontLoader.web.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAC3C,OAAO,EAAe,YAAY,EAAE,MAAM,cAAc,CAAC;;sBAiF/B,QAAQ,IAAI,CAAC;gCASH,MAAM,YAAY,iBAAiB,GAAG,QAAQ,IAAI,CAAC;0BAS/D,MAAM,EAAE;;6BAqBL,MAAM,aAAY,iBAAiB,GAAQ,OAAO;8BAUjD,MAAM,YAAY,YAAY,GAAG,QAAQ,IAAI,CAAC;;AAlD1E,wBAmFE;AAeF,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,GAAG,MAAM,CAIzF"}

View File

@@ -0,0 +1,174 @@
import { CodedError, Platform } from 'expo-modules-core';
import FontObserver from 'fontfaceobserver';
import { FontDisplay } from './Font.types';
function getFontFaceStyleSheet() {
if (!Platform.isDOMAvailable) {
return null;
}
const styleSheet = getStyleElement();
return styleSheet.sheet ? styleSheet.sheet : null;
}
function getFontFaceRules() {
const sheet = getFontFaceStyleSheet();
if (sheet) {
// @ts-ignore: rule iterator
const rules = [...sheet.cssRules];
const items = [];
for (let i = 0; i < rules.length; i++) {
const rule = rules[i];
if (rule instanceof CSSFontFaceRule) {
items.push({ rule, index: i });
}
}
return items;
}
return [];
}
function getFontFaceRulesMatchingResource(fontFamilyName, options) {
const rules = getFontFaceRules();
return rules.filter(({ rule }) => {
return (rule.style.fontFamily === fontFamilyName &&
(options && options.display ? options.display === rule.style.fontDisplay : true));
});
}
const serverContext = new Set();
function getHeadElements() {
const entries = [...serverContext.entries()];
if (!entries.length) {
return [];
}
const css = entries.map(([{ css }]) => css).join('\n');
const links = entries.map(([{ resourceId }]) => resourceId);
// TODO: Maybe return nothing if no fonts were loaded.
return [
{
$$type: 'style',
children: css,
id: ID,
type: 'text/css',
},
...links.map((resourceId) => ({
$$type: 'link',
rel: 'preload',
href: resourceId,
as: 'font',
crossorigin: '',
})),
];
}
export default {
async unloadAllAsync() {
if (!Platform.isDOMAvailable)
return;
const element = document.getElementById(ID);
if (element && element instanceof HTMLStyleElement) {
document.removeChild(element);
}
},
async unloadAsync(fontFamilyName, options) {
const sheet = getFontFaceStyleSheet();
if (!sheet)
return;
const items = getFontFaceRulesMatchingResource(fontFamilyName, options);
for (const item of items) {
sheet.deleteRule(item.index);
}
},
getServerResources() {
const elements = getHeadElements();
return elements
.map((element) => {
switch (element.$$type) {
case 'style':
return `<style id="${element.id}" type="${element.type}">${element.children}</style>`;
case 'link':
return `<link rel="${element.rel}" href="${element.href}" as="${element.as}" crossorigin="${element.crossorigin}" />`;
default:
return '';
}
})
.filter(Boolean);
},
resetServerContext() {
serverContext.clear();
},
isLoaded(fontFamilyName, resource = {}) {
if (typeof window === 'undefined') {
return !![...serverContext.values()].find((asset) => {
return asset.name === fontFamilyName;
});
}
return getFontFaceRulesMatchingResource(fontFamilyName, resource)?.length > 0;
},
// NOTE(EvanBacon): No async keyword! This cannot return a promise in Node environments.
loadAsync(fontFamilyName, resource) {
if (typeof window === 'undefined') {
serverContext.add({
name: fontFamilyName,
css: _createWebFontTemplate(fontFamilyName, resource),
// @ts-expect-error: typeof string
resourceId: resource.uri,
});
return Promise.resolve();
}
const canInjectStyle = document.head && typeof document.head.appendChild === 'function';
if (!canInjectStyle) {
throw new CodedError('ERR_WEB_ENVIRONMENT', `The browser's \`document.head\` element doesn't support injecting fonts.`);
}
const style = getStyleElement();
document.head.appendChild(style);
const res = getFontFaceRulesMatchingResource(fontFamilyName, resource);
if (!res.length) {
_createWebStyle(fontFamilyName, resource);
}
if (!isFontLoadingListenerSupported()) {
return Promise.resolve();
}
return new FontObserver(fontFamilyName, { display: resource.display }).load(null, 6000);
},
};
const ID = 'expo-generated-fonts';
function getStyleElement() {
const element = document.getElementById(ID);
if (element && element instanceof HTMLStyleElement) {
return element;
}
const styleElement = document.createElement('style');
styleElement.id = ID;
styleElement.type = 'text/css';
return styleElement;
}
export function _createWebFontTemplate(fontFamily, resource) {
return `@font-face{font-family:${fontFamily};src:url(${resource.uri});font-display:${resource.display || FontDisplay.AUTO}}`;
}
function _createWebStyle(fontFamily, resource) {
const fontStyle = _createWebFontTemplate(fontFamily, resource);
const styleElement = getStyleElement();
// @ts-ignore: TypeScript does not define HTMLStyleElement::styleSheet. This is just for IE and
// possibly can be removed if it's unnecessary on IE 11.
if (styleElement.styleSheet) {
const styleElementIE = styleElement;
styleElementIE.styleSheet.cssText = styleElementIE.styleSheet.cssText
? styleElementIE.styleSheet.cssText + fontStyle
: fontStyle;
}
else {
const textNode = document.createTextNode(fontStyle);
styleElement.appendChild(textNode);
}
return styleElement;
}
function isFontLoadingListenerSupported() {
const { userAgent } = window.navigator;
// WebKit is broken https://github.com/bramstein/fontfaceobserver/issues/95
const isIOS = !!userAgent.match(/iPad|iPhone/i);
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
// Edge is broken https://github.com/bramstein/fontfaceobserver/issues/109#issuecomment-333356795
const isEdge = userAgent.includes('Edge');
// Internet Explorer
const isIE = userAgent.includes('Trident');
// Firefox
const isFirefox = userAgent.includes('Firefox');
return !isSafari && !isIOS && !isEdge && !isIE && !isFirefox;
}
//# sourceMappingURL=ExpoFontLoader.web.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,52 @@
import { FontDisplay, FontSource, FontResource, UnloadFontOptions } from './Font.types';
/**
* Used to transform font family names to the scoped name. This does not need to
* be called in standalone or bare apps but it will return unscoped font family
* names if it is called in those contexts.
*
* @param fontFamily Name of font to process.
* @returns Returns a name processed for use with the [current workflow](https://docs.expo.dev/archive/managed-vs-bare/).
*/
export declare function processFontFamily(fontFamily: string | null): string | null;
/**
* Synchronously detect if the font for `fontFamily` has finished loading.
*
* @param fontFamily The name used to load the `FontResource`.
* @return Returns `true` if the font has fully loaded.
*/
export declare function isLoaded(fontFamily: string): boolean;
/**
* Synchronously detect if the font for `fontFamily` is still being loaded.
*
* @param fontFamily The name used to load the `FontResource`.
* @returns Returns `true` if the font is still loading.
*/
export declare function isLoading(fontFamily: string): boolean;
/**
* Highly efficient method for loading fonts from static or remote resources which can then be used
* with the platform's native text elements. In the browser this generates a `@font-face` block in
* a shared style sheet for fonts. No CSS is needed to use this method.
*
* @param fontFamilyOrFontMap string or map of values that can be used as the [`fontFamily`](https://reactnative.dev/docs/text#style)
* style prop with React Native Text elements.
* @param source the font asset that should be loaded into the `fontFamily` namespace.
*
* @return Returns a promise that fulfils when the font has loaded. Often you may want to wrap the
* method in a `try/catch/finally` to ensure the app continues if the font fails to load.
*/
export declare function loadAsync(fontFamilyOrFontMap: string | Record<string, FontSource>, source?: FontSource): Promise<void>;
/**
* Unloads all the custom fonts. This is used for testing.
*/
export declare function unloadAllAsync(): Promise<void>;
/**
* Unload custom fonts matching the `fontFamily`s and display values provided.
* Because fonts are automatically unloaded on every platform this is mostly used for testing.
*
* @param fontFamilyOrFontMap The name or names of the custom fonts that will be unloaded.
* @param options When `fontFamilyOrFontMap` is a string, this should be the font source used to load
* the custom font originally.
*/
export declare function unloadAsync(fontFamilyOrFontMap: string | Record<string, UnloadFontOptions>, options?: UnloadFontOptions): Promise<void>;
export { FontDisplay, FontSource, FontResource, UnloadFontOptions };
//# sourceMappingURL=Font.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"Font.d.ts","sourceRoot":"","sources":["../src/Font.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAYxF;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAoB1E;AAGD;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAKpD;AAGD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAErD;AAGD;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CACvB,mBAAmB,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,EACxD,MAAM,CAAC,EAAE,UAAU,GAClB,OAAO,CAAC,IAAI,CAAC,CAkCf;AAwCD;;GAEG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC,CAiBpD;AAGD;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAC/B,mBAAmB,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,EAC/D,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,IAAI,CAAC,CAkBf;AA0BD,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,iBAAiB,EAAE,CAAC"}

View File

@@ -0,0 +1,176 @@
import { CodedError, Platform, UnavailabilityError } from 'expo-modules-core';
import ExpoFontLoader from './ExpoFontLoader';
import { FontDisplay } from './Font.types';
import { getAssetForSource, loadSingleFontAsync, fontFamilyNeedsScoping, getNativeFontName, } from './FontLoader';
import { loaded, loadPromises } from './memory';
import { registerStaticFont } from './server';
// @needsAudit
// note(brentvatne): at some point we may want to warn if this is called outside of a managed app.
/**
* Used to transform font family names to the scoped name. This does not need to
* be called in standalone or bare apps but it will return unscoped font family
* names if it is called in those contexts.
*
* @param fontFamily Name of font to process.
* @returns Returns a name processed for use with the [current workflow](https://docs.expo.dev/archive/managed-vs-bare/).
*/
export function processFontFamily(fontFamily) {
if (!fontFamily || !fontFamilyNeedsScoping(fontFamily)) {
return fontFamily;
}
if (!isLoaded(fontFamily)) {
if (__DEV__) {
if (isLoading(fontFamily)) {
console.warn(`You started loading the font "${fontFamily}", but used it before it finished loading. You need to wait for Font.loadAsync to complete before using the font.`);
}
else {
console.warn(`fontFamily "${fontFamily}" is not a system font and has not been loaded through expo-font.`);
}
}
}
return `ExpoFont-${getNativeFontName(fontFamily)}`;
}
// @needsAudit
/**
* Synchronously detect if the font for `fontFamily` has finished loading.
*
* @param fontFamily The name used to load the `FontResource`.
* @return Returns `true` if the font has fully loaded.
*/
export function isLoaded(fontFamily) {
if (Platform.OS === 'web') {
return fontFamily in loaded || !!ExpoFontLoader.isLoaded(fontFamily);
}
return fontFamily in loaded || ExpoFontLoader.customNativeFonts?.includes(fontFamily);
}
// @needsAudit
/**
* Synchronously detect if the font for `fontFamily` is still being loaded.
*
* @param fontFamily The name used to load the `FontResource`.
* @returns Returns `true` if the font is still loading.
*/
export function isLoading(fontFamily) {
return fontFamily in loadPromises;
}
// @needsAudit
/**
* Highly efficient method for loading fonts from static or remote resources which can then be used
* with the platform's native text elements. In the browser this generates a `@font-face` block in
* a shared style sheet for fonts. No CSS is needed to use this method.
*
* @param fontFamilyOrFontMap string or map of values that can be used as the [`fontFamily`](https://reactnative.dev/docs/text#style)
* style prop with React Native Text elements.
* @param source the font asset that should be loaded into the `fontFamily` namespace.
*
* @return Returns a promise that fulfils when the font has loaded. Often you may want to wrap the
* method in a `try/catch/finally` to ensure the app continues if the font fails to load.
*/
export function loadAsync(fontFamilyOrFontMap, source) {
// NOTE(EvanBacon): Static render pass on web must be synchronous to collect all fonts.
// Because of this, `loadAsync` doesn't use the `async` keyword and deviates from the
// standard Expo SDK style guide.
const isServer = Platform.OS === 'web' && typeof window === 'undefined';
if (typeof fontFamilyOrFontMap === 'object') {
if (source) {
return Promise.reject(new CodedError(`ERR_FONT_API`, `No fontFamily can be used for the provided source: ${source}. The second argument of \`loadAsync()\` can only be used with a \`string\` value as the first argument.`));
}
const fontMap = fontFamilyOrFontMap;
const names = Object.keys(fontMap);
if (isServer) {
names.map((name) => registerStaticFont(name, fontMap[name]));
return Promise.resolve();
}
return Promise.all(names.map((name) => loadFontInNamespaceAsync(name, fontMap[name]))).then(() => { });
}
if (isServer) {
registerStaticFont(fontFamilyOrFontMap, source);
return Promise.resolve();
}
return loadFontInNamespaceAsync(fontFamilyOrFontMap, source);
}
async function loadFontInNamespaceAsync(fontFamily, source) {
if (!source) {
throw new CodedError(`ERR_FONT_SOURCE`, `Cannot load null or undefined font source: { "${fontFamily}": ${source} }. Expected asset of type \`FontSource\` for fontFamily of name: "${fontFamily}"`);
}
if (loaded[fontFamily]) {
return;
}
if (loadPromises.hasOwnProperty(fontFamily)) {
return loadPromises[fontFamily];
}
// Important: we want all callers that concurrently try to load the same font to await the same
// promise. If we're here, we haven't created the promise yet. To ensure we create only one
// promise in the program, we need to create the promise synchronously without yielding the event
// loop from this point.
const asset = getAssetForSource(source);
loadPromises[fontFamily] = (async () => {
try {
await loadSingleFontAsync(fontFamily, asset);
loaded[fontFamily] = true;
}
finally {
delete loadPromises[fontFamily];
}
})();
await loadPromises[fontFamily];
}
// @needsAudit
/**
* Unloads all the custom fonts. This is used for testing.
*/
export async function unloadAllAsync() {
if (!ExpoFontLoader.unloadAllAsync) {
throw new UnavailabilityError('expo-font', 'unloadAllAsync');
}
if (Object.keys(loadPromises).length) {
throw new CodedError(`ERR_UNLOAD`, `Cannot unload fonts while they're still loading: ${Object.keys(loadPromises).join(', ')}`);
}
for (const fontFamily of Object.keys(loaded)) {
delete loaded[fontFamily];
}
await ExpoFontLoader.unloadAllAsync();
}
// @needsAudit
/**
* Unload custom fonts matching the `fontFamily`s and display values provided.
* Because fonts are automatically unloaded on every platform this is mostly used for testing.
*
* @param fontFamilyOrFontMap The name or names of the custom fonts that will be unloaded.
* @param options When `fontFamilyOrFontMap` is a string, this should be the font source used to load
* the custom font originally.
*/
export async function unloadAsync(fontFamilyOrFontMap, options) {
if (!ExpoFontLoader.unloadAsync) {
throw new UnavailabilityError('expo-font', 'unloadAsync');
}
if (typeof fontFamilyOrFontMap === 'object') {
if (options) {
throw new CodedError(`ERR_FONT_API`, `No fontFamily can be used for the provided options: ${options}. The second argument of \`unloadAsync()\` can only be used with a \`string\` value as the first argument.`);
}
const fontMap = fontFamilyOrFontMap;
const names = Object.keys(fontMap);
await Promise.all(names.map((name) => unloadFontInNamespaceAsync(name, fontMap[name])));
return;
}
return await unloadFontInNamespaceAsync(fontFamilyOrFontMap, options);
}
async function unloadFontInNamespaceAsync(fontFamily, options) {
if (!loaded[fontFamily]) {
return;
}
else {
delete loaded[fontFamily];
}
// Important: we want all callers that concurrently try to load the same font to await the same
// promise. If we're here, we haven't created the promise yet. To ensure we create only one
// promise in the program, we need to create the promise synchronously without yielding the event
// loop from this point.
const nativeFontName = getNativeFontName(fontFamily);
if (!nativeFontName) {
throw new CodedError(`ERR_FONT_FAMILY`, `Cannot unload an empty name`);
}
await ExpoFontLoader.unloadAsync(nativeFontName, options);
}
export { FontDisplay };
//# sourceMappingURL=Font.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,65 @@
import { Asset } from 'expo-asset';
/**
* The different types of assets you can provide to the [`loadAsync()`](#loadasyncfontfamilyorfontmap-source) function.
* A font source can be a URI, a module ID, or an Expo Asset.
*/
export type FontSource = string | number | Asset | FontResource;
/**
* An object used to dictate the resource that is loaded into the provided font namespace when used
* with [`loadAsync`](#loadasyncfontfamilyorfontmap-source).
*/
export type FontResource = {
uri?: string | number;
/**
* Sets the [`font-display`](#fontdisplay) property for a given typeface in the browser.
* @platform web
*/
display?: FontDisplay;
default?: string;
};
/**
* Sets the [font-display](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display)
* for a given typeface. The default font value on web is `FontDisplay.AUTO`.
* Even though setting the `fontDisplay` does nothing on native platforms, the default behavior
* emulates `FontDisplay.SWAP` on flagship devices like iOS, Samsung, Pixel, etc. Default
* functionality varies on One Plus devices. In the browser this value is set in the generated
* `@font-face` CSS block and not as a style property meaning you cannot dynamically change this
* value based on the element it's used in.
* @platform web
*/
export declare enum FontDisplay {
/**
* __(Default)__ The font display strategy is defined by the user agent or platform.
* This generally defaults to the text being invisible until the font is loaded.
* Good for buttons or banners that require a specific treatment.
*/
AUTO = "auto",
/**
* Fallback text is rendered immediately with a default font while the desired font is loaded.
* This is good for making the content appear to load instantly and is usually preferred.
*/
SWAP = "swap",
/**
* The text will be invisible until the font has loaded. If the font fails to load then nothing
* will appear - it's best to turn this off when debugging missing text.
*/
BLOCK = "block",
/**
* Splits the behavior between `SWAP` and `BLOCK`.
* There will be a [100ms timeout](https://developers.google.com/web/updates/2016/02/font-display?hl=en)
* where the text with a custom font is invisible, after that the text will either swap to the
* styled text or it'll show the unstyled text and continue to load the custom font. This is good
* for buttons that need a custom font but should also be quickly available to screen-readers.
*/
FALLBACK = "fallback",
/**
* This works almost identically to `FALLBACK`, the only difference is that the browser will
* decide to load the font based on slow connection speed or critical resource demand.
*/
OPTIONAL = "optional"
}
/**
* Object used to query fonts for unloading.
*/
export type UnloadFontOptions = Pick<FontResource, 'display'>;
//# sourceMappingURL=Font.types.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"Font.types.d.ts","sourceRoot":"","sources":["../src/Font.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,YAAY,CAAC;AAGhE;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB;;;OAGG;IACH,OAAO,CAAC,EAAE,WAAW,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAGF;;;;;;;;;GASG;AACH,oBAAY,WAAW;IACrB;;;;OAIG;IACH,IAAI,SAAS;IACb;;;OAGG;IACH,IAAI,SAAS;IACb;;;OAGG;IACH,KAAK,UAAU;IACf;;;;;;OAMG;IACH,QAAQ,aAAa;IACrB;;;OAGG;IACH,QAAQ,aAAa;CACtB;AAGD;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC"}

View File

@@ -0,0 +1,44 @@
// @needsAudit
/**
* Sets the [font-display](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display)
* for a given typeface. The default font value on web is `FontDisplay.AUTO`.
* Even though setting the `fontDisplay` does nothing on native platforms, the default behavior
* emulates `FontDisplay.SWAP` on flagship devices like iOS, Samsung, Pixel, etc. Default
* functionality varies on One Plus devices. In the browser this value is set in the generated
* `@font-face` CSS block and not as a style property meaning you cannot dynamically change this
* value based on the element it's used in.
* @platform web
*/
export var FontDisplay;
(function (FontDisplay) {
/**
* __(Default)__ The font display strategy is defined by the user agent or platform.
* This generally defaults to the text being invisible until the font is loaded.
* Good for buttons or banners that require a specific treatment.
*/
FontDisplay["AUTO"] = "auto";
/**
* Fallback text is rendered immediately with a default font while the desired font is loaded.
* This is good for making the content appear to load instantly and is usually preferred.
*/
FontDisplay["SWAP"] = "swap";
/**
* The text will be invisible until the font has loaded. If the font fails to load then nothing
* will appear - it's best to turn this off when debugging missing text.
*/
FontDisplay["BLOCK"] = "block";
/**
* Splits the behavior between `SWAP` and `BLOCK`.
* There will be a [100ms timeout](https://developers.google.com/web/updates/2016/02/font-display?hl=en)
* where the text with a custom font is invisible, after that the text will either swap to the
* styled text or it'll show the unstyled text and continue to load the custom font. This is good
* for buttons that need a custom font but should also be quickly available to screen-readers.
*/
FontDisplay["FALLBACK"] = "fallback";
/**
* This works almost identically to `FALLBACK`, the only difference is that the browser will
* decide to load the font based on slow connection speed or critical resource demand.
*/
FontDisplay["OPTIONAL"] = "optional";
})(FontDisplay || (FontDisplay = {}));
//# sourceMappingURL=Font.types.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"Font.types.js","sourceRoot":"","sources":["../src/Font.types.ts"],"names":[],"mappings":"AAwBA,cAAc;AACd;;;;;;;;;GASG;AACH,MAAM,CAAN,IAAY,WA8BX;AA9BD,WAAY,WAAW;IACrB;;;;OAIG;IACH,4BAAa,CAAA;IACb;;;OAGG;IACH,4BAAa,CAAA;IACb;;;OAGG;IACH,8BAAe,CAAA;IACf;;;;;;OAMG;IACH,oCAAqB,CAAA;IACrB;;;OAGG;IACH,oCAAqB,CAAA;AACvB,CAAC,EA9BW,WAAW,KAAX,WAAW,QA8BtB","sourcesContent":["import { Asset } from 'expo-asset';\n\n// @needsAudit\n/**\n * The different types of assets you can provide to the [`loadAsync()`](#loadasyncfontfamilyorfontmap-source) function.\n * A font source can be a URI, a module ID, or an Expo Asset.\n */\nexport type FontSource = string | number | Asset | FontResource;\n\n// @needsAudit\n/**\n * An object used to dictate the resource that is loaded into the provided font namespace when used\n * with [`loadAsync`](#loadasyncfontfamilyorfontmap-source).\n */\nexport type FontResource = {\n uri?: string | number;\n /**\n * Sets the [`font-display`](#fontdisplay) property for a given typeface in the browser.\n * @platform web\n */\n display?: FontDisplay;\n default?: string;\n};\n\n// @needsAudit\n/**\n * Sets the [font-display](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display)\n * for a given typeface. The default font value on web is `FontDisplay.AUTO`.\n * Even though setting the `fontDisplay` does nothing on native platforms, the default behavior\n * emulates `FontDisplay.SWAP` on flagship devices like iOS, Samsung, Pixel, etc. Default\n * functionality varies on One Plus devices. In the browser this value is set in the generated\n * `@font-face` CSS block and not as a style property meaning you cannot dynamically change this\n * value based on the element it's used in.\n * @platform web\n */\nexport enum FontDisplay {\n /**\n * __(Default)__ The font display strategy is defined by the user agent or platform.\n * This generally defaults to the text being invisible until the font is loaded.\n * Good for buttons or banners that require a specific treatment.\n */\n AUTO = 'auto',\n /**\n * Fallback text is rendered immediately with a default font while the desired font is loaded.\n * This is good for making the content appear to load instantly and is usually preferred.\n */\n SWAP = 'swap',\n /**\n * The text will be invisible until the font has loaded. If the font fails to load then nothing\n * will appear - it's best to turn this off when debugging missing text.\n */\n BLOCK = 'block',\n /**\n * Splits the behavior between `SWAP` and `BLOCK`.\n * There will be a [100ms timeout](https://developers.google.com/web/updates/2016/02/font-display?hl=en)\n * where the text with a custom font is invisible, after that the text will either swap to the\n * styled text or it'll show the unstyled text and continue to load the custom font. This is good\n * for buttons that need a custom font but should also be quickly available to screen-readers.\n */\n FALLBACK = 'fallback',\n /**\n * This works almost identically to `FALLBACK`, the only difference is that the browser will\n * decide to load the font based on slow connection speed or critical resource demand.\n */\n OPTIONAL = 'optional',\n}\n\n// @needsAudit\n/**\n * Object used to query fonts for unloading.\n */\nexport type UnloadFontOptions = Pick<FontResource, 'display'>;\n"]}

View File

@@ -0,0 +1,20 @@
import { FontSource } from './Font.types';
/**
* ```ts
* const [loaded, error] = useFonts({ ... });
* ```
* Load a map of fonts with [`loadAsync`](#loadasyncfontfamilyorfontmap-source). This returns a `boolean` if the fonts are
* loaded and ready to use. It also returns an error if something went wrong, to use in development.
*
* > Note, the fonts are not "reloaded" when you dynamically change the font map.
*
* @param map A map of `fontFamily`s to [`FontSource`](#fontsource)s. After loading the font you can
* use the key in the `fontFamily` style prop of a `Text` element.
*
* @return
* - __loaded__ (`boolean`) - A boolean to detect if the font for `fontFamily` has finished
* loading.
* - __error__ (`Error | null`) - An error encountered when loading the fonts.
*/
export declare const useFonts: (map: string | Record<string, FontSource>) => [boolean, Error | null];
//# sourceMappingURL=FontHooks.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"FontHooks.d.ts","sourceRoot":"","sources":["../src/FontHooks.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAiC1C;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,KAAK,CAAC,OAAO,EAAE,KAAK,GAAG,IAAI,CACzB,CAAC"}

View File

@@ -0,0 +1,47 @@
import { useEffect, useState } from 'react';
import { loadAsync, isLoaded } from './Font';
function isMapLoaded(map) {
if (typeof map === 'string') {
return isLoaded(map);
}
else {
return Object.keys(map).every((fontFamily) => isLoaded(fontFamily));
}
}
function useRuntimeFonts(map) {
const [loaded, setLoaded] = useState(
// For web rehydration, we need to check if the fonts are already loaded during the static render.
// Native will also benefit from this optimization.
isMapLoaded(map));
const [error, setError] = useState(null);
useEffect(() => {
loadAsync(map)
.then(() => setLoaded(true))
.catch(setError);
}, []);
return [loaded, error];
}
function useStaticFonts(map) {
loadAsync(map);
return [true, null];
}
// @needsAudit
/**
* ```ts
* const [loaded, error] = useFonts({ ... });
* ```
* Load a map of fonts with [`loadAsync`](#loadasyncfontfamilyorfontmap-source). This returns a `boolean` if the fonts are
* loaded and ready to use. It also returns an error if something went wrong, to use in development.
*
* > Note, the fonts are not "reloaded" when you dynamically change the font map.
*
* @param map A map of `fontFamily`s to [`FontSource`](#fontsource)s. After loading the font you can
* use the key in the `fontFamily` style prop of a `Text` element.
*
* @return
* - __loaded__ (`boolean`) - A boolean to detect if the font for `fontFamily` has finished
* loading.
* - __error__ (`Error | null`) - An error encountered when loading the fonts.
*/
export const useFonts = typeof window === 'undefined' ? useStaticFonts : useRuntimeFonts;
//# sourceMappingURL=FontHooks.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"FontHooks.js","sourceRoot":"","sources":["../src/FontHooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AAE5C,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAG7C,SAAS,WAAW,CAAC,GAAwC;IAC3D,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE;QAC3B,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC;KACtB;SAAM;QACL,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,UAAU,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC;KACrE;AACH,CAAC;AAED,SAAS,eAAe,CAAC,GAAwC;IAC/D,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ;IAClC,kGAAkG;IAClG,mDAAmD;IACnD,WAAW,CAAC,GAAG,CAAC,CACjB,CAAC;IACF,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAe,IAAI,CAAC,CAAC;IAEvD,SAAS,CAAC,GAAG,EAAE;QACb,SAAS,CAAC,GAAG,CAAC;aACX,IAAI,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;aAC3B,KAAK,CAAC,QAAQ,CAAC,CAAC;IACrB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AACzB,CAAC;AAED,SAAS,cAAc,CAAC,GAAwC;IAC9D,SAAS,CAAC,GAAG,CAAC,CAAC;IACf,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AACtB,CAAC;AAED,cAAc;AACd;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,QAAQ,GACnB,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,eAAe,CAAC","sourcesContent":["import { useEffect, useState } from 'react';\n\nimport { loadAsync, isLoaded } from './Font';\nimport { FontSource } from './Font.types';\n\nfunction isMapLoaded(map: string | Record<string, FontSource>) {\n if (typeof map === 'string') {\n return isLoaded(map);\n } else {\n return Object.keys(map).every((fontFamily) => isLoaded(fontFamily));\n }\n}\n\nfunction useRuntimeFonts(map: string | Record<string, FontSource>): [boolean, Error | null] {\n const [loaded, setLoaded] = useState(\n // For web rehydration, we need to check if the fonts are already loaded during the static render.\n // Native will also benefit from this optimization.\n isMapLoaded(map)\n );\n const [error, setError] = useState<Error | null>(null);\n\n useEffect(() => {\n loadAsync(map)\n .then(() => setLoaded(true))\n .catch(setError);\n }, []);\n\n return [loaded, error];\n}\n\nfunction useStaticFonts(map: string | Record<string, FontSource>): [boolean, Error | null] {\n loadAsync(map);\n return [true, null];\n}\n\n// @needsAudit\n/**\n * ```ts\n * const [loaded, error] = useFonts({ ... });\n * ```\n * Load a map of fonts with [`loadAsync`](#loadasyncfontfamilyorfontmap-source). This returns a `boolean` if the fonts are\n * loaded and ready to use. It also returns an error if something went wrong, to use in development.\n *\n * > Note, the fonts are not \"reloaded\" when you dynamically change the font map.\n *\n * @param map A map of `fontFamily`s to [`FontSource`](#fontsource)s. After loading the font you can\n * use the key in the `fontFamily` style prop of a `Text` element.\n *\n * @return\n * - __loaded__ (`boolean`) - A boolean to detect if the font for `fontFamily` has finished\n * loading.\n * - __error__ (`Error | null`) - An error encountered when loading the fonts.\n */\nexport const useFonts: (map: string | Record<string, FontSource>) => [boolean, Error | null] =\n typeof window === 'undefined' ? useStaticFonts : useRuntimeFonts;\n"]}

View File

@@ -0,0 +1,7 @@
import { Asset } from 'expo-asset';
import { FontResource, FontSource } from './Font.types';
export declare function fontFamilyNeedsScoping(name: string): boolean;
export declare function getAssetForSource(source: FontSource): Asset | FontResource;
export declare function loadSingleFontAsync(name: string, input: Asset | FontResource): Promise<void>;
export declare function getNativeFontName(name: string): string;
//# sourceMappingURL=FontLoader.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"FontLoader.d.ts","sourceRoot":"","sources":["../src/FontLoader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAKnC,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAIxD,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAQ5D;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,KAAK,GAAG,YAAY,CAiB1E;AAED,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,KAAK,GAAG,YAAY,GAC1B,OAAO,CAAC,IAAI,CAAC,CAcf;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMtD"}

View File

@@ -0,0 +1,50 @@
import { Asset } from 'expo-asset';
import Constants from 'expo-constants';
import { CodedError, Platform } from 'expo-modules-core';
import ExpoFontLoader from './ExpoFontLoader';
const isInExpoGo = Constants.appOwnership === 'expo';
export function fontFamilyNeedsScoping(name) {
return (isInExpoGo &&
Platform.OS !== 'ios' &&
!Constants.systemFonts.includes(name) &&
name !== 'System' &&
!name.includes(Constants.sessionId));
}
export function getAssetForSource(source) {
if (source instanceof Asset) {
return source;
}
if (typeof source === 'string') {
return Asset.fromURI(source);
}
else if (typeof source === 'number') {
return Asset.fromModule(source);
}
else if (typeof source === 'object' && typeof source.uri !== 'undefined') {
return getAssetForSource(source.uri);
}
// @ts-ignore Error: Type 'string' is not assignable to type 'Asset'
// We can't have a string here, we would have thrown an error if !isWeb
// or returned Asset.fromModule if isWeb.
return source;
}
export async function loadSingleFontAsync(name, input) {
const asset = input;
if (!asset.downloadAsync) {
throw new CodedError(`ERR_FONT_SOURCE`, '`loadSingleFontAsync` expected resource of type `Asset` from expo-asset on native');
}
await asset.downloadAsync();
if (!asset.downloaded) {
throw new CodedError(`ERR_DOWNLOAD`, `Failed to download asset for font "${name}"`);
}
await ExpoFontLoader.loadAsync(getNativeFontName(name), asset.localUri);
}
export function getNativeFontName(name) {
if (fontFamilyNeedsScoping(name)) {
return `${Constants.sessionId}-${name}`;
}
else {
return name;
}
}
//# sourceMappingURL=FontLoader.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"FontLoader.js","sourceRoot":"","sources":["../src/FontLoader.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,SAAS,MAAM,gBAAgB,CAAC;AACvC,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAEzD,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAG9C,MAAM,UAAU,GAAG,SAAS,CAAC,YAAY,KAAK,MAAM,CAAC;AAErD,MAAM,UAAU,sBAAsB,CAAC,IAAY;IACjD,OAAO,CACL,UAAU;QACV,QAAQ,CAAC,EAAE,KAAK,KAAK;QACrB,CAAC,SAAS,CAAC,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC;QACrC,IAAI,KAAK,QAAQ;QACjB,CAAC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC,CACpC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAAkB;IAClD,IAAI,MAAM,YAAY,KAAK,EAAE;QAC3B,OAAO,MAAM,CAAC;KACf;IAED,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE;QAC9B,OAAO,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;KAC9B;SAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE;QACrC,OAAO,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;KACjC;SAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,WAAW,EAAE;QAC1E,OAAO,iBAAiB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;KACtC;IAED,oEAAoE;IACpE,uEAAuE;IACvE,yCAAyC;IACzC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,IAAY,EACZ,KAA2B;IAE3B,MAAM,KAAK,GAAG,KAAc,CAAC;IAC7B,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE;QACxB,MAAM,IAAI,UAAU,CAClB,iBAAiB,EACjB,mFAAmF,CACpF,CAAC;KACH;IAED,MAAM,KAAK,CAAC,aAAa,EAAE,CAAC;IAC5B,IAAI,CAAC,KAAK,CAAC,UAAU,EAAE;QACrB,MAAM,IAAI,UAAU,CAAC,cAAc,EAAE,sCAAsC,IAAI,GAAG,CAAC,CAAC;KACrF;IACD,MAAM,cAAc,CAAC,SAAS,CAAC,iBAAiB,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;AAC1E,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,IAAI,sBAAsB,CAAC,IAAI,CAAC,EAAE;QAChC,OAAO,GAAG,SAAS,CAAC,SAAS,IAAI,IAAI,EAAE,CAAC;KACzC;SAAM;QACL,OAAO,IAAI,CAAC;KACb;AACH,CAAC","sourcesContent":["import { Asset } from 'expo-asset';\nimport Constants from 'expo-constants';\nimport { CodedError, Platform } from 'expo-modules-core';\n\nimport ExpoFontLoader from './ExpoFontLoader';\nimport { FontResource, FontSource } from './Font.types';\n\nconst isInExpoGo = Constants.appOwnership === 'expo';\n\nexport function fontFamilyNeedsScoping(name: string): boolean {\n return (\n isInExpoGo &&\n Platform.OS !== 'ios' &&\n !Constants.systemFonts.includes(name) &&\n name !== 'System' &&\n !name.includes(Constants.sessionId)\n );\n}\n\nexport function getAssetForSource(source: FontSource): Asset | FontResource {\n if (source instanceof Asset) {\n return source;\n }\n\n if (typeof source === 'string') {\n return Asset.fromURI(source);\n } else if (typeof source === 'number') {\n return Asset.fromModule(source);\n } else if (typeof source === 'object' && typeof source.uri !== 'undefined') {\n return getAssetForSource(source.uri);\n }\n\n // @ts-ignore Error: Type 'string' is not assignable to type 'Asset'\n // We can't have a string here, we would have thrown an error if !isWeb\n // or returned Asset.fromModule if isWeb.\n return source;\n}\n\nexport async function loadSingleFontAsync(\n name: string,\n input: Asset | FontResource\n): Promise<void> {\n const asset = input as Asset;\n if (!asset.downloadAsync) {\n throw new CodedError(\n `ERR_FONT_SOURCE`,\n '`loadSingleFontAsync` expected resource of type `Asset` from expo-asset on native'\n );\n }\n\n await asset.downloadAsync();\n if (!asset.downloaded) {\n throw new CodedError(`ERR_DOWNLOAD`, `Failed to download asset for font \"${name}\"`);\n }\n await ExpoFontLoader.loadAsync(getNativeFontName(name), asset.localUri);\n}\n\nexport function getNativeFontName(name: string): string {\n if (fontFamilyNeedsScoping(name)) {\n return `${Constants.sessionId}-${name}`;\n } else {\n return name;\n }\n}\n"]}

View File

@@ -0,0 +1,7 @@
import { Asset } from 'expo-asset';
import { FontResource, FontSource } from './Font.types';
export declare function fontFamilyNeedsScoping(name: string): boolean;
export declare function getAssetForSource(source: FontSource): Asset | FontResource;
export declare function loadSingleFontAsync(name: string, input: Asset | FontResource): Promise<void>;
export declare function getNativeFontName(name: string): string;
//# sourceMappingURL=FontLoader.web.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"FontLoader.web.d.ts","sourceRoot":"","sources":["../src/FontLoader.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAInC,OAAO,EAAE,YAAY,EAAE,UAAU,EAAe,MAAM,cAAc,CAAC;AAiBrE,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,UAAU,GAAG,KAAK,GAAG,YAAY,CAY1E;AAYD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAY5F;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtD"}

View File

@@ -0,0 +1,56 @@
import { Asset } from 'expo-asset';
import { CodedError } from 'expo-modules-core';
import ExpoFontLoader from './ExpoFontLoader';
import { FontDisplay } from './Font.types';
function uriFromFontSource(asset) {
if (typeof asset === 'string') {
return asset || null;
}
else if (typeof asset === 'object') {
return asset.uri || asset.localUri || asset.default || null;
}
else if (typeof asset === 'number') {
return uriFromFontSource(Asset.fromModule(asset));
}
return null;
}
function displayFromFontSource(asset) {
return asset.display || FontDisplay.AUTO;
}
export function fontFamilyNeedsScoping(name) {
return false;
}
export function getAssetForSource(source) {
const uri = uriFromFontSource(source);
const display = displayFromFontSource(source);
if (!uri || typeof uri !== 'string') {
throwInvalidSourceError(uri);
}
return {
uri: uri,
display,
};
}
function throwInvalidSourceError(source) {
let type = typeof source;
if (type === 'object')
type = JSON.stringify(source, null, 2);
throw new CodedError(`ERR_FONT_SOURCE`, `Expected font asset of type \`string | FontResource | Asset\` instead got: ${type}`);
}
// NOTE(EvanBacon): No async keyword!
export function loadSingleFontAsync(name, input) {
if (typeof input !== 'object' || typeof input.uri !== 'string' || input.downloadAsync) {
throwInvalidSourceError(input);
}
try {
return ExpoFontLoader.loadAsync(name, input);
}
catch {
// No-op.
}
return Promise.resolve();
}
export function getNativeFontName(name) {
return name;
}
//# sourceMappingURL=FontLoader.web.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"FontLoader.web.js","sourceRoot":"","sources":["../src/FontLoader.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAA4B,WAAW,EAAE,MAAM,cAAc,CAAC;AAErE,SAAS,iBAAiB,CAAC,KAAU;IACnC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;QAC7B,OAAO,KAAK,IAAI,IAAI,CAAC;KACtB;SAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;QACpC,OAAO,KAAK,CAAC,GAAG,IAAI,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,OAAO,IAAI,IAAI,CAAC;KAC7D;SAAM,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;QACpC,OAAO,iBAAiB,CAAC,KAAK,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;KACnD;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,qBAAqB,CAAC,KAAU;IACvC,OAAO,KAAK,CAAC,OAAO,IAAI,WAAW,CAAC,IAAI,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,IAAY;IACjD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAAkB;IAClD,MAAM,GAAG,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAE9C,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE;QACnC,uBAAuB,CAAC,GAAG,CAAC,CAAC;KAC9B;IAED,OAAO;QACL,GAAG,EAAE,GAAI;QACT,OAAO;KACR,CAAC;AACJ,CAAC;AAED,SAAS,uBAAuB,CAAC,MAAW;IAC1C,IAAI,IAAI,GAAW,OAAO,MAAM,CAAC;IACjC,IAAI,IAAI,KAAK,QAAQ;QAAE,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAC9D,MAAM,IAAI,UAAU,CAClB,iBAAiB,EACjB,8EAA8E,IAAI,EAAE,CACrF,CAAC;AACJ,CAAC;AAED,qCAAqC;AACrC,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,KAA2B;IAC3E,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ,IAAK,KAAa,CAAC,aAAa,EAAE;QAC9F,uBAAuB,CAAC,KAAK,CAAC,CAAC;KAChC;IAED,IAAI;QACF,OAAO,cAAc,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;KAC9C;IAAC,MAAM;QACN,SAAS;KACV;IAED,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;AAC3B,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["import { Asset } from 'expo-asset';\nimport { CodedError } from 'expo-modules-core';\n\nimport ExpoFontLoader from './ExpoFontLoader';\nimport { FontResource, FontSource, FontDisplay } from './Font.types';\n\nfunction uriFromFontSource(asset: any): string | null {\n if (typeof asset === 'string') {\n return asset || null;\n } else if (typeof asset === 'object') {\n return asset.uri || asset.localUri || asset.default || null;\n } else if (typeof asset === 'number') {\n return uriFromFontSource(Asset.fromModule(asset));\n }\n return null;\n}\n\nfunction displayFromFontSource(asset: any): FontDisplay | undefined {\n return asset.display || FontDisplay.AUTO;\n}\n\nexport function fontFamilyNeedsScoping(name: string): boolean {\n return false;\n}\n\nexport function getAssetForSource(source: FontSource): Asset | FontResource {\n const uri = uriFromFontSource(source);\n const display = displayFromFontSource(source);\n\n if (!uri || typeof uri !== 'string') {\n throwInvalidSourceError(uri);\n }\n\n return {\n uri: uri!,\n display,\n };\n}\n\nfunction throwInvalidSourceError(source: any): never {\n let type: string = typeof source;\n if (type === 'object') type = JSON.stringify(source, null, 2);\n throw new CodedError(\n `ERR_FONT_SOURCE`,\n `Expected font asset of type \\`string | FontResource | Asset\\` instead got: ${type}`\n );\n}\n\n// NOTE(EvanBacon): No async keyword!\nexport function loadSingleFontAsync(name: string, input: Asset | FontResource): Promise<void> {\n if (typeof input !== 'object' || typeof input.uri !== 'string' || (input as any).downloadAsync) {\n throwInvalidSourceError(input);\n }\n\n try {\n return ExpoFontLoader.loadAsync(name, input);\n } catch {\n // No-op.\n }\n\n return Promise.resolve();\n}\n\nexport function getNativeFontName(name: string): string {\n return name;\n}\n"]}

View File

@@ -0,0 +1,3 @@
export * from './Font';
export { useFonts } from './FontHooks';
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC"}

View File

@@ -0,0 +1,3 @@
export * from './Font';
export { useFonts } from './FontHooks';
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC","sourcesContent":["export * from './Font';\nexport { useFonts } from './FontHooks';\n"]}

View File

@@ -0,0 +1,7 @@
export declare const loaded: {
[name: string]: boolean;
};
export declare const loadPromises: {
[name: string]: Promise<void>;
};
//# sourceMappingURL=memory.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../src/memory.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,MAAM,EAAE;IAAE,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;CAAO,CAAC;AACtD,eAAO,MAAM,YAAY,EAAE;IAAE,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAAO,CAAC"}

View File

@@ -0,0 +1,3 @@
export const loaded = {};
export const loadPromises = {};
//# sourceMappingURL=memory.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../src/memory.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,MAAM,GAAgC,EAAE,CAAC;AACtD,MAAM,CAAC,MAAM,YAAY,GAAsC,EAAE,CAAC","sourcesContent":["export const loaded: { [name: string]: boolean } = {};\nexport const loadPromises: { [name: string]: Promise<void> } = {};\n"]}

View File

@@ -0,0 +1,13 @@
import { FontSource } from './Font.types';
/**
* @returns the server resources that should be statically extracted.
* @private
*/
export declare function getServerResources(): string[];
/**
* @returns clear the server resources from the global scope.
* @private
*/
export declare function resetServerContext(): any;
export declare function registerStaticFont(fontFamily: string, source?: FontSource | null): void;
//# sourceMappingURL=server.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,EAAE,CAE7C;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,QAEjC;AAED,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,IAAI,QAWhF"}

View File

@@ -0,0 +1,26 @@
import { CodedError } from 'expo-modules-core';
import ExpoFontLoader from './ExpoFontLoader';
import { getAssetForSource, loadSingleFontAsync } from './FontLoader';
/**
* @returns the server resources that should be statically extracted.
* @private
*/
export function getServerResources() {
return ExpoFontLoader.getServerResources();
}
/**
* @returns clear the server resources from the global scope.
* @private
*/
export function resetServerContext() {
return ExpoFontLoader.resetServerContext();
}
export function registerStaticFont(fontFamily, source) {
// MUST BE A SYNC FUNCTION!
if (!source) {
throw new CodedError(`ERR_FONT_SOURCE`, `Cannot load null or undefined font source: { "${fontFamily}": ${source} }. Expected asset of type \`FontSource\` for fontFamily of name: "${fontFamily}"`);
}
const asset = getAssetForSource(source);
loadSingleFontAsync(fontFamily, asset);
}
//# sourceMappingURL=server.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE/C,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAE9C,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEtE;;;GAGG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO,cAAc,CAAC,kBAAkB,EAAE,CAAC;AAC7C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB;IAChC,OAAO,cAAc,CAAC,kBAAkB,EAAE,CAAC;AAC7C,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,UAAkB,EAAE,MAA0B;IAC/E,2BAA2B;IAC3B,IAAI,CAAC,MAAM,EAAE;QACX,MAAM,IAAI,UAAU,CAClB,iBAAiB,EACjB,iDAAiD,UAAU,MAAM,MAAM,sEAAsE,UAAU,GAAG,CAC3J,CAAC;KACH;IACD,MAAM,KAAK,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAExC,mBAAmB,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;AACzC,CAAC","sourcesContent":["import { CodedError } from 'expo-modules-core';\n\nimport ExpoFontLoader from './ExpoFontLoader';\nimport { FontSource } from './Font.types';\nimport { getAssetForSource, loadSingleFontAsync } from './FontLoader';\n\n/**\n * @returns the server resources that should be statically extracted.\n * @private\n */\nexport function getServerResources(): string[] {\n return ExpoFontLoader.getServerResources();\n}\n\n/**\n * @returns clear the server resources from the global scope.\n * @private\n */\nexport function resetServerContext() {\n return ExpoFontLoader.resetServerContext();\n}\n\nexport function registerStaticFont(fontFamily: string, source?: FontSource | null) {\n // MUST BE A SYNC FUNCTION!\n if (!source) {\n throw new CodedError(\n `ERR_FONT_SOURCE`,\n `Cannot load null or undefined font source: { \"${fontFamily}\": ${source} }. Expected asset of type \\`FontSource\\` for fontFamily of name: \"${fontFamily}\"`\n );\n }\n const asset = getAssetForSource(source);\n\n loadSingleFontAsync(fontFamily, asset);\n}\n"]}

View File

@@ -0,0 +1,9 @@
{
"platforms": ["apple", "android", "web"],
"apple": {
"modules": ["FontLoaderModule"]
},
"android": {
"modules": ["expo.modules.font.FontLoaderModule"]
}
}

View File

@@ -0,0 +1,30 @@
require 'json'
package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json')))
Pod::Spec.new do |s|
s.name = 'ExpoFont'
s.version = package['version']
s.summary = package['description']
s.description = package['description']
s.license = package['license']
s.author = package['author']
s.homepage = package['homepage']
s.platforms = {
:ios => '13.4',
:osx => '10.15',
:tvos => '13.4'
}
s.swift_version = '5.4'
s.source = { git: 'https://github.com/expo/expo.git' }
s.static_framework = true
s.dependency 'ExpoModulesCore'
# Swift/Objective-C compatibility
s.pod_target_xcconfig = {
'DEFINES_MODULE' => 'YES'
}
s.source_files = "**/*.{h,m,swift}"
end

View File

@@ -0,0 +1,25 @@
import ExpoModulesCore
internal final class FontFileNotFoundException: GenericException<String> {
override var reason: String {
"Font file '\(param)' doesn't exist"
}
}
internal final class FontCreationFailedException: GenericException<String> {
override var reason: String {
"Could not create font from loaded data for '\(param)'"
}
}
internal final class FontRegistrationFailedException: GenericException<CFError> {
override var reason: String {
"Registering '\(param)' font failed with message: '\(param.localizedDescription)'"
}
}
internal final class UnregisteringFontFailedException: GenericException<CFError> {
override var reason: String {
"Unregistering '\(param)' font failed with message: '\(param.localizedDescription)'"
}
}

View File

@@ -0,0 +1,61 @@
import ExpoModulesCore
/**
A registry of font family aliases mapped to their real font family names.
*/
private var fontFamilyAliases = [String: String]()
/**
A flag that is set to `true` when the ``UIFont.fontNames(forFamilyName:)`` is already swizzled.
*/
private var hasSwizzled = false
/**
Manages the font family aliases and swizzles the `UIFont` class.
*/
internal struct FontFamilyAliasManager {
/**
Whether the given alias has already been set.
*/
internal static func hasAlias(_ familyNameAlias: String) -> Bool {
return fontFamilyAliases[familyNameAlias] != nil
}
/**
Sets the alias for the given family name.
If the alias has already been set, its family name will be overriden.
*/
internal static func setAlias(_ familyNameAlias: String, forFont font: String) {
maybeSwizzleUIFont()
fontFamilyAliases[familyNameAlias] = font
}
/**
Returns the family name for the given alias or `nil` when it's not set yet.
*/
internal static func familyName(forAlias familyNameAlias: String) -> String? {
return fontFamilyAliases[familyNameAlias]
}
}
/**
Swizzles ``UIFont.fontNames(forFamilyName:)`` to support font family aliases.
This is necessary because the user provides a custom family name that is then used in stylesheets,
however the font usually has a different name encoded in the binary, thus the system may use a different name.
*/
private func maybeSwizzleUIFont() {
if hasSwizzled {
return
}
#if !os(macOS)
let originalFontNamesMethod = class_getClassMethod(UIFont.self, #selector(UIFont.fontNames(forFamilyName:)))
let newFontNamesMethod = class_getClassMethod(UIFont.self, #selector(UIFont._expo_fontNames(forFamilyName:)))
if let originalFontNamesMethod, let newFontNamesMethod {
method_exchangeImplementations(originalFontNamesMethod, newFontNamesMethod)
} else {
log.error("expo-font is unable to swizzle `UIFont.fontNames(forFamilyName:)`")
}
#endif
hasSwizzled = true
}

View File

@@ -0,0 +1,32 @@
import ExpoModulesCore
public final class FontLoaderModule: Module {
public func definition() -> ModuleDefinition {
Name("ExpoFontLoader")
Property("customNativeFonts") {
return queryCustomNativeFonts()
}
AsyncFunction("loadAsync") { (fontFamilyAlias: String, localUri: URL) in
let fontUrl = localUri as CFURL
// If the font was already registered, unregister it first. Otherwise CTFontManagerRegisterFontsForURL
// would fail because of a duplicated font name when the app reloads or someone wants to override a font.
if FontFamilyAliasManager.familyName(forAlias: fontFamilyAlias) != nil {
guard try unregisterFont(url: fontUrl) else {
return
}
}
// Register the font
try registerFont(fontUrl)
// Create a font object from the given URL
let font = try loadFont(fromUrl: fontUrl, alias: fontFamilyAlias)
if let postScriptName = font.postScriptName as? String {
FontFamilyAliasManager.setAlias(fontFamilyAlias, forFont: postScriptName)
}
}
}
}

View File

@@ -0,0 +1,90 @@
import CoreGraphics
/**
* Queries custom native font names from the Info.plist `UIAppFonts`.
*/
internal func queryCustomNativeFonts() -> [String] {
// [0] Read from main bundle's Info.plist
guard let fontFilePaths = Bundle.main.object(forInfoDictionaryKey: "UIAppFonts") as? [String] else {
return []
}
// [1] Get font family names for each font file
let fontFamilies: [[String]] = fontFilePaths.compactMap { fontFilePath in
guard let fontUrl = Bundle.main.url(forResource: fontFilePath, withExtension: nil) as? URL else {
return []
}
guard let fontDescriptors = CTFontManagerCreateFontDescriptorsFromURL(fontUrl as CFURL) as? [CTFontDescriptor] else {
return []
}
return fontDescriptors.compactMap { descriptor in
return CTFontDescriptorCopyAttribute(descriptor, kCTFontFamilyNameAttribute) as? String
}
}
// [2] Retrieve font names by family names
return fontFamilies.flatMap { fontFamilyNames in
return fontFamilyNames.flatMap { fontFamilyName in
#if os(iOS) || os(tvOS)
return UIFont.fontNames(forFamilyName: fontFamilyName)
#elseif os(macOS)
return NSFontManager.shared.availableMembers(ofFontFamily: fontFamilyName)?.compactMap { $0[0] as? String } ?? []
#endif
}
}
}
/**
Loads the font from the given url and returns it as ``CGFont``.
*/
internal func loadFont(fromUrl url: CFURL, alias: String) throws -> CGFont {
guard let provider = CGDataProvider(url: url),
let cgFont = CGFont(provider) else {
throw FontCreationFailedException(alias)
}
return cgFont
}
/**
Registers the given font to make it discoverable through font descriptor matching.
*/
internal func registerFont(_ fontUrl: CFURL) throws {
var error: Unmanaged<CFError>?
if !CTFontManagerRegisterFontsForURL(fontUrl, .process, &error), let error = error?.takeRetainedValue() {
let fontError = CTFontManagerError(rawValue: CFErrorGetCode(error))
switch fontError {
case .alreadyRegistered, .duplicatedName:
// Ignore the error if:
// - this exact font instance was already registered or
// - another instance already registered with the same name (assuming it's most likely the same font anyway)
return
default:
throw FontRegistrationFailedException(error)
}
}
}
/**
Unregisters the given font, so the app will no longer be able to render it.
Returns a boolean indicating if the font is successfully unregistered after this function completes.
*/
internal func unregisterFont(url: CFURL) throws -> Bool {
var error: Unmanaged<CFError>?
if !CTFontManagerUnregisterFontsForURL(url, .process, &error), let error = error?.takeRetainedValue() {
if let ctFontManagerError = CTFontManagerError(rawValue: CFErrorGetCode(error as CFError)) {
switch ctFontManagerError {
case .systemRequired, .inUse:
return false
case .notRegistered:
return true
default:
throw UnregisteringFontFailedException(error)
}
}
}
return true
}

View File

@@ -0,0 +1,29 @@
#if !os(macOS)
/**
An extension to ``UIFont`` that adds a custom implementation of `fontNames(forFamilyName:)` that supports aliasing font families.
*/
public extension UIFont {
/**
Returns an array of font names for the specified family name or its alias.
*/
@objc
static dynamic func _expo_fontNames(forFamilyName familyName: String) -> [String] {
// Get font names from the original function.
let fontNames = UIFont._expo_fontNames(forFamilyName: familyName)
// If no font names were found, let's try with the alias.
if fontNames.isEmpty, let postScriptName = FontFamilyAliasManager.familyName(forAlias: familyName) {
let fontNames = UIFont._expo_fontNames(forFamilyName: postScriptName)
// If we still don't find any font names, we can assume it was not a family name but a font name.
// In that case we can safely return the original font name.
if fontNames.isEmpty {
return [postScriptName]
}
return fontNames
}
return fontNames
}
}
#endif

View File

@@ -0,0 +1,46 @@
{
"name": "expo-font",
"version": "12.0.10",
"description": "Load fonts at runtime and use them in React Native components.",
"main": "build/index.js",
"types": "build/index.d.ts",
"sideEffects": false,
"scripts": {
"build": "expo-module build",
"clean": "expo-module clean",
"lint": "expo-module lint",
"test": "expo-module test",
"prepare": "expo-module prepare",
"prepublishOnly": "expo-module prepublishOnly",
"expo-module": "expo-module"
},
"keywords": [
"react-native",
"expo",
"font"
],
"repository": {
"type": "git",
"url": "https://github.com/expo/expo.git",
"directory": "packages/expo-font"
},
"bugs": {
"url": "https://github.com/expo/expo/issues"
},
"author": "650 Industries, Inc.",
"license": "MIT",
"homepage": "https://docs.expo.dev/versions/latest/sdk/font/",
"jest": {
"preset": "expo-module-scripts"
},
"dependencies": {
"fontfaceobserver": "^2.1.0"
},
"devDependencies": {
"expo-module-scripts": "^3.0.0"
},
"peerDependencies": {
"expo": "*"
},
"gitHead": "de377512cb1707aee70e78cc8fd875d2ba9c3ab4"
}

View File

@@ -0,0 +1 @@
export declare function resolveFontPaths(fonts: string[], projectRoot: string): Promise<string[]>;

View File

@@ -0,0 +1,23 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveFontPaths = void 0;
const promises_1 = __importDefault(require("fs/promises"));
const path_1 = __importDefault(require("path"));
async function resolveFontPaths(fonts, projectRoot) {
const promises = fonts.map(async (p) => {
const resolvedPath = path_1.default.resolve(projectRoot, p);
const stat = await promises_1.default.stat(resolvedPath);
if (stat.isDirectory()) {
const dir = await promises_1.default.readdir(resolvedPath);
return dir.map((file) => path_1.default.join(resolvedPath, file));
}
return [resolvedPath];
});
return (await Promise.all(promises))
.flat()
.filter((p) => p.endsWith('.ttf') || p.endsWith('.otf') || p.endsWith('.woff') || p.endsWith('.woff2'));
}
exports.resolveFontPaths = resolveFontPaths;

View File

@@ -0,0 +1,12 @@
import { ConfigPlugin } from 'expo/config-plugins';
export type FontProps = {
fonts?: string[];
android?: {
fonts?: string[];
};
ios?: {
fonts?: string[];
};
};
declare const _default: ConfigPlugin<FontProps>;
export default _default;

View File

@@ -0,0 +1,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const config_plugins_1 = require("expo/config-plugins");
const withFontsAndroid_1 = require("./withFontsAndroid");
const withFontsIos_1 = require("./withFontsIos");
const pkg = require('expo-font/package.json');
const withFonts = (config, props) => {
if (!props) {
return config;
}
const iosFonts = [...(props.fonts ?? []), ...(props.ios?.fonts ?? [])];
if (iosFonts.length > 0) {
config = (0, withFontsIos_1.withFontsIos)(config, iosFonts);
}
const androidFonts = [...(props.fonts ?? []), ...(props.android?.fonts ?? [])];
if (androidFonts.length > 0) {
config = (0, withFontsAndroid_1.withFontsAndroid)(config, androidFonts);
}
return config;
};
exports.default = (0, config_plugins_1.createRunOncePlugin)(withFonts, pkg.name, pkg.version);

View File

@@ -0,0 +1,2 @@
import { ConfigPlugin } from 'expo/config-plugins';
export declare const withFontsAndroid: ConfigPlugin<string[]>;

View File

@@ -0,0 +1,28 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.withFontsAndroid = void 0;
const config_plugins_1 = require("expo/config-plugins");
const promises_1 = __importDefault(require("fs/promises"));
const path_1 = __importDefault(require("path"));
const utils_1 = require("./utils");
const withFontsAndroid = (config, fonts) => {
return (0, config_plugins_1.withDangerousMod)(config, [
'android',
async (config) => {
const resolvedFonts = await (0, utils_1.resolveFontPaths)(fonts, config.modRequest.projectRoot);
await Promise.all(resolvedFonts.map(async (asset) => {
const fontsDir = path_1.default.join(config.modRequest.platformProjectRoot, 'app/src/main/assets/fonts');
await promises_1.default.mkdir(fontsDir, { recursive: true });
const output = path_1.default.join(fontsDir, path_1.default.basename(asset));
if (output.endsWith('.ttf') || output.endsWith('.otf')) {
await promises_1.default.copyFile(asset, output);
}
}));
return config;
},
]);
};
exports.withFontsAndroid = withFontsAndroid;

View File

@@ -0,0 +1,2 @@
import { ConfigPlugin } from 'expo/config-plugins';
export declare const withFontsIos: ConfigPlugin<string[]>;

View File

@@ -0,0 +1,54 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.withFontsIos = void 0;
const config_plugins_1 = require("expo/config-plugins");
const path_1 = __importDefault(require("path"));
const utils_1 = require("./utils");
const withFontsIos = (config, fonts) => {
config = addFontsToTarget(config, fonts);
config = addFontsToPlist(config, fonts);
return config;
};
exports.withFontsIos = withFontsIos;
function addFontsToTarget(config, fonts) {
return (0, config_plugins_1.withXcodeProject)(config, async (config) => {
const resolvedFonts = await (0, utils_1.resolveFontPaths)(fonts, config.modRequest.projectRoot);
const project = config.modResults;
const platformProjectRoot = config.modRequest.platformProjectRoot;
config_plugins_1.IOSConfig.XcodeUtils.ensureGroupRecursively(project, 'Resources');
addResourceFile(project, platformProjectRoot, resolvedFonts);
return config;
});
}
function addFontsToPlist(config, fonts) {
return (0, config_plugins_1.withInfoPlist)(config, async (config) => {
const resolvedFonts = await (0, utils_1.resolveFontPaths)(fonts, config.modRequest.projectRoot);
const existingFonts = getUIAppFonts(config.modResults);
const fontList = resolvedFonts.map((font) => path_1.default.basename(font)) ?? [];
const allFonts = [...existingFonts, ...fontList];
config.modResults.UIAppFonts = Array.from(new Set(allFonts));
return config;
});
}
function addResourceFile(project, platformRoot, f) {
for (const font of f) {
const fontPath = path_1.default.relative(platformRoot, font);
config_plugins_1.IOSConfig.XcodeUtils.addResourceFileToGroup({
filepath: fontPath,
groupName: 'Resources',
project,
isBuildFile: true,
verbose: true,
});
}
}
function getUIAppFonts(infoPlist) {
const fonts = infoPlist['UIAppFonts'];
if (fonts != null && Array.isArray(fonts) && fonts.every((font) => typeof font === 'string')) {
return fonts;
}
return [];
}

View File

@@ -0,0 +1,20 @@
import fs from 'fs/promises';
import path from 'path';
export async function resolveFontPaths(fonts: string[], projectRoot: string) {
const promises = fonts.map(async (p) => {
const resolvedPath = path.resolve(projectRoot, p);
const stat = await fs.stat(resolvedPath);
if (stat.isDirectory()) {
const dir = await fs.readdir(resolvedPath);
return dir.map((file) => path.join(resolvedPath, file));
}
return [resolvedPath];
});
return (await Promise.all(promises))
.flat()
.filter(
(p) => p.endsWith('.ttf') || p.endsWith('.otf') || p.endsWith('.woff') || p.endsWith('.woff2')
);
}

View File

@@ -0,0 +1,38 @@
import { ConfigPlugin, createRunOncePlugin } from 'expo/config-plugins';
import { withFontsAndroid } from './withFontsAndroid';
import { withFontsIos } from './withFontsIos';
const pkg = require('expo-font/package.json');
export type FontProps = {
fonts?: string[];
android?: {
fonts?: string[];
};
ios?: {
fonts?: string[];
};
};
const withFonts: ConfigPlugin<FontProps> = (config, props) => {
if (!props) {
return config;
}
const iosFonts = [...(props.fonts ?? []), ...(props.ios?.fonts ?? [])];
if (iosFonts.length > 0) {
config = withFontsIos(config, iosFonts);
}
const androidFonts = [...(props.fonts ?? []), ...(props.android?.fonts ?? [])];
if (androidFonts.length > 0) {
config = withFontsAndroid(config, androidFonts);
}
return config;
};
export default createRunOncePlugin(withFonts, pkg.name, pkg.version);

View File

@@ -0,0 +1,28 @@
import { ConfigPlugin, withDangerousMod } from 'expo/config-plugins';
import fs from 'fs/promises';
import path from 'path';
import { resolveFontPaths } from './utils';
export const withFontsAndroid: ConfigPlugin<string[]> = (config, fonts) => {
return withDangerousMod(config, [
'android',
async (config) => {
const resolvedFonts = await resolveFontPaths(fonts, config.modRequest.projectRoot);
await Promise.all(
resolvedFonts.map(async (asset) => {
const fontsDir = path.join(
config.modRequest.platformProjectRoot,
'app/src/main/assets/fonts'
);
await fs.mkdir(fontsDir, { recursive: true });
const output = path.join(fontsDir, path.basename(asset));
if (output.endsWith('.ttf') || output.endsWith('.otf')) {
await fs.copyFile(asset, output);
}
})
);
return config;
},
]);
};

View File

@@ -0,0 +1,63 @@
import { ExpoConfig } from '@expo/config-types';
import {
ConfigPlugin,
IOSConfig,
InfoPlist,
XcodeProject,
withInfoPlist,
withXcodeProject,
} from 'expo/config-plugins';
import path from 'path';
import { resolveFontPaths } from './utils';
export const withFontsIos: ConfigPlugin<string[]> = (config, fonts) => {
config = addFontsToTarget(config, fonts);
config = addFontsToPlist(config, fonts);
return config;
};
function addFontsToTarget(config: ExpoConfig, fonts: string[]) {
return withXcodeProject(config, async (config) => {
const resolvedFonts = await resolveFontPaths(fonts, config.modRequest.projectRoot);
const project = config.modResults;
const platformProjectRoot = config.modRequest.platformProjectRoot;
IOSConfig.XcodeUtils.ensureGroupRecursively(project, 'Resources');
addResourceFile(project, platformProjectRoot, resolvedFonts);
return config;
});
}
function addFontsToPlist(config: ExpoConfig, fonts: string[]) {
return withInfoPlist(config, async (config) => {
const resolvedFonts = await resolveFontPaths(fonts, config.modRequest.projectRoot);
const existingFonts = getUIAppFonts(config.modResults);
const fontList = resolvedFonts.map((font) => path.basename(font)) ?? [];
const allFonts = [...existingFonts, ...fontList];
config.modResults.UIAppFonts = Array.from(new Set(allFonts));
return config;
});
}
function addResourceFile(project: XcodeProject, platformRoot: string, f: string[]) {
for (const font of f) {
const fontPath = path.relative(platformRoot, font);
IOSConfig.XcodeUtils.addResourceFileToGroup({
filepath: fontPath,
groupName: 'Resources',
project,
isBuildFile: true,
verbose: true,
});
}
}
function getUIAppFonts(infoPlist: InfoPlist): string[] {
const fonts = infoPlist['UIAppFonts'];
if (fonts != null && Array.isArray(fonts) && fonts.every((font) => typeof font === 'string')) {
return fonts as string[];
}
return [];
}

View File

@@ -0,0 +1,9 @@
{
"extends": "expo-module-scripts/tsconfig.plugin",
"compilerOptions": {
"outDir": "build",
"rootDir": "src"
},
"include": ["./src"],
"exclude": ["**/__mocks__/*", "**/__tests__/*"]
}

View File

@@ -0,0 +1,2 @@
import { requireNativeModule } from 'expo-modules-core';
export default requireNativeModule('ExpoFontLoader');

View File

@@ -0,0 +1,219 @@
import { CodedError, Platform } from 'expo-modules-core';
import FontObserver from 'fontfaceobserver';
import { UnloadFontOptions } from './Font';
import { FontDisplay, FontResource } from './Font.types';
function getFontFaceStyleSheet(): CSSStyleSheet | null {
if (!Platform.isDOMAvailable) {
return null;
}
const styleSheet = getStyleElement();
return styleSheet.sheet ? (styleSheet.sheet as CSSStyleSheet) : null;
}
type RuleItem = { rule: CSSFontFaceRule; index: number };
function getFontFaceRules(): RuleItem[] {
const sheet = getFontFaceStyleSheet();
if (sheet) {
// @ts-ignore: rule iterator
const rules = [...sheet.cssRules];
const items: RuleItem[] = [];
for (let i = 0; i < rules.length; i++) {
const rule = rules[i];
if (rule instanceof CSSFontFaceRule) {
items.push({ rule, index: i });
}
}
return items;
}
return [];
}
function getFontFaceRulesMatchingResource(
fontFamilyName: string,
options?: UnloadFontOptions
): RuleItem[] {
const rules = getFontFaceRules();
return rules.filter(({ rule }) => {
return (
rule.style.fontFamily === fontFamilyName &&
(options && options.display ? options.display === (rule.style as any).fontDisplay : true)
);
});
}
const serverContext: Set<{ name: string; css: string; resourceId: string }> = new Set();
function getHeadElements(): {
$$type: string;
rel?: string;
href?: string;
as?: string;
crossorigin?: string;
children?: string;
id?: string;
type?: string;
}[] {
const entries = [...serverContext.entries()];
if (!entries.length) {
return [];
}
const css = entries.map(([{ css }]) => css).join('\n');
const links = entries.map(([{ resourceId }]) => resourceId);
// TODO: Maybe return nothing if no fonts were loaded.
return [
{
$$type: 'style',
children: css,
id: ID,
type: 'text/css',
},
...links.map((resourceId) => ({
$$type: 'link',
rel: 'preload',
href: resourceId,
as: 'font',
crossorigin: '',
})),
];
}
export default {
async unloadAllAsync(): Promise<void> {
if (!Platform.isDOMAvailable) return;
const element = document.getElementById(ID);
if (element && element instanceof HTMLStyleElement) {
document.removeChild(element);
}
},
async unloadAsync(fontFamilyName: string, options?: UnloadFontOptions): Promise<void> {
const sheet = getFontFaceStyleSheet();
if (!sheet) return;
const items = getFontFaceRulesMatchingResource(fontFamilyName, options);
for (const item of items) {
sheet.deleteRule(item.index);
}
},
getServerResources(): string[] {
const elements = getHeadElements();
return elements
.map((element) => {
switch (element.$$type) {
case 'style':
return `<style id="${element.id}" type="${element.type}">${element.children}</style>`;
case 'link':
return `<link rel="${element.rel}" href="${element.href}" as="${element.as}" crossorigin="${element.crossorigin}" />`;
default:
return '';
}
})
.filter(Boolean);
},
resetServerContext() {
serverContext.clear();
},
isLoaded(fontFamilyName: string, resource: UnloadFontOptions = {}): boolean {
if (typeof window === 'undefined') {
return !![...serverContext.values()].find((asset) => {
return asset.name === fontFamilyName;
});
}
return getFontFaceRulesMatchingResource(fontFamilyName, resource)?.length > 0;
},
// NOTE(EvanBacon): No async keyword! This cannot return a promise in Node environments.
loadAsync(fontFamilyName: string, resource: FontResource): Promise<void> {
if (typeof window === 'undefined') {
serverContext.add({
name: fontFamilyName,
css: _createWebFontTemplate(fontFamilyName, resource),
// @ts-expect-error: typeof string
resourceId: resource.uri!,
});
return Promise.resolve();
}
const canInjectStyle = document.head && typeof document.head.appendChild === 'function';
if (!canInjectStyle) {
throw new CodedError(
'ERR_WEB_ENVIRONMENT',
`The browser's \`document.head\` element doesn't support injecting fonts.`
);
}
const style = getStyleElement();
document.head!.appendChild(style);
const res = getFontFaceRulesMatchingResource(fontFamilyName, resource);
if (!res.length) {
_createWebStyle(fontFamilyName, resource);
}
if (!isFontLoadingListenerSupported()) {
return Promise.resolve();
}
return new FontObserver(fontFamilyName, { display: resource.display }).load(null, 6000);
},
};
const ID = 'expo-generated-fonts';
function getStyleElement(): HTMLStyleElement {
const element = document.getElementById(ID);
if (element && element instanceof HTMLStyleElement) {
return element;
}
const styleElement = document.createElement('style');
styleElement.id = ID;
styleElement.type = 'text/css';
return styleElement;
}
export function _createWebFontTemplate(fontFamily: string, resource: FontResource): string {
return `@font-face{font-family:${fontFamily};src:url(${resource.uri});font-display:${
resource.display || FontDisplay.AUTO
}}`;
}
function _createWebStyle(fontFamily: string, resource: FontResource): HTMLStyleElement {
const fontStyle = _createWebFontTemplate(fontFamily, resource);
const styleElement = getStyleElement();
// @ts-ignore: TypeScript does not define HTMLStyleElement::styleSheet. This is just for IE and
// possibly can be removed if it's unnecessary on IE 11.
if (styleElement.styleSheet) {
const styleElementIE = styleElement as any;
styleElementIE.styleSheet.cssText = styleElementIE.styleSheet.cssText
? styleElementIE.styleSheet.cssText + fontStyle
: fontStyle;
} else {
const textNode = document.createTextNode(fontStyle);
styleElement.appendChild(textNode);
}
return styleElement;
}
function isFontLoadingListenerSupported(): boolean {
const { userAgent } = window.navigator;
// WebKit is broken https://github.com/bramstein/fontfaceobserver/issues/95
const isIOS = !!userAgent.match(/iPad|iPhone/i);
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
// Edge is broken https://github.com/bramstein/fontfaceobserver/issues/109#issuecomment-333356795
const isEdge = userAgent.includes('Edge');
// Internet Explorer
const isIE = userAgent.includes('Trident');
// Firefox
const isFirefox = userAgent.includes('Firefox');
return !isSafari && !isIOS && !isEdge && !isIE && !isFirefox;
}

View File

@@ -0,0 +1,239 @@
import { CodedError, Platform, UnavailabilityError } from 'expo-modules-core';
import ExpoFontLoader from './ExpoFontLoader';
import { FontDisplay, FontSource, FontResource, UnloadFontOptions } from './Font.types';
import {
getAssetForSource,
loadSingleFontAsync,
fontFamilyNeedsScoping,
getNativeFontName,
} from './FontLoader';
import { loaded, loadPromises } from './memory';
import { registerStaticFont } from './server';
// @needsAudit
// note(brentvatne): at some point we may want to warn if this is called outside of a managed app.
/**
* Used to transform font family names to the scoped name. This does not need to
* be called in standalone or bare apps but it will return unscoped font family
* names if it is called in those contexts.
*
* @param fontFamily Name of font to process.
* @returns Returns a name processed for use with the [current workflow](https://docs.expo.dev/archive/managed-vs-bare/).
*/
export function processFontFamily(fontFamily: string | null): string | null {
if (!fontFamily || !fontFamilyNeedsScoping(fontFamily)) {
return fontFamily;
}
if (!isLoaded(fontFamily)) {
if (__DEV__) {
if (isLoading(fontFamily)) {
console.warn(
`You started loading the font "${fontFamily}", but used it before it finished loading. You need to wait for Font.loadAsync to complete before using the font.`
);
} else {
console.warn(
`fontFamily "${fontFamily}" is not a system font and has not been loaded through expo-font.`
);
}
}
}
return `ExpoFont-${getNativeFontName(fontFamily)}`;
}
// @needsAudit
/**
* Synchronously detect if the font for `fontFamily` has finished loading.
*
* @param fontFamily The name used to load the `FontResource`.
* @return Returns `true` if the font has fully loaded.
*/
export function isLoaded(fontFamily: string): boolean {
if (Platform.OS === 'web') {
return fontFamily in loaded || !!ExpoFontLoader.isLoaded(fontFamily);
}
return fontFamily in loaded || ExpoFontLoader.customNativeFonts?.includes(fontFamily);
}
// @needsAudit
/**
* Synchronously detect if the font for `fontFamily` is still being loaded.
*
* @param fontFamily The name used to load the `FontResource`.
* @returns Returns `true` if the font is still loading.
*/
export function isLoading(fontFamily: string): boolean {
return fontFamily in loadPromises;
}
// @needsAudit
/**
* Highly efficient method for loading fonts from static or remote resources which can then be used
* with the platform's native text elements. In the browser this generates a `@font-face` block in
* a shared style sheet for fonts. No CSS is needed to use this method.
*
* @param fontFamilyOrFontMap string or map of values that can be used as the [`fontFamily`](https://reactnative.dev/docs/text#style)
* style prop with React Native Text elements.
* @param source the font asset that should be loaded into the `fontFamily` namespace.
*
* @return Returns a promise that fulfils when the font has loaded. Often you may want to wrap the
* method in a `try/catch/finally` to ensure the app continues if the font fails to load.
*/
export function loadAsync(
fontFamilyOrFontMap: string | Record<string, FontSource>,
source?: FontSource
): Promise<void> {
// NOTE(EvanBacon): Static render pass on web must be synchronous to collect all fonts.
// Because of this, `loadAsync` doesn't use the `async` keyword and deviates from the
// standard Expo SDK style guide.
const isServer = Platform.OS === 'web' && typeof window === 'undefined';
if (typeof fontFamilyOrFontMap === 'object') {
if (source) {
return Promise.reject(
new CodedError(
`ERR_FONT_API`,
`No fontFamily can be used for the provided source: ${source}. The second argument of \`loadAsync()\` can only be used with a \`string\` value as the first argument.`
)
);
}
const fontMap = fontFamilyOrFontMap;
const names = Object.keys(fontMap);
if (isServer) {
names.map((name) => registerStaticFont(name, fontMap[name]));
return Promise.resolve();
}
return Promise.all(names.map((name) => loadFontInNamespaceAsync(name, fontMap[name]))).then(
() => {}
);
}
if (isServer) {
registerStaticFont(fontFamilyOrFontMap, source);
return Promise.resolve();
}
return loadFontInNamespaceAsync(fontFamilyOrFontMap, source);
}
async function loadFontInNamespaceAsync(
fontFamily: string,
source?: FontSource | null
): Promise<void> {
if (!source) {
throw new CodedError(
`ERR_FONT_SOURCE`,
`Cannot load null or undefined font source: { "${fontFamily}": ${source} }. Expected asset of type \`FontSource\` for fontFamily of name: "${fontFamily}"`
);
}
if (loaded[fontFamily]) {
return;
}
if (loadPromises.hasOwnProperty(fontFamily)) {
return loadPromises[fontFamily];
}
// Important: we want all callers that concurrently try to load the same font to await the same
// promise. If we're here, we haven't created the promise yet. To ensure we create only one
// promise in the program, we need to create the promise synchronously without yielding the event
// loop from this point.
const asset = getAssetForSource(source);
loadPromises[fontFamily] = (async () => {
try {
await loadSingleFontAsync(fontFamily, asset);
loaded[fontFamily] = true;
} finally {
delete loadPromises[fontFamily];
}
})();
await loadPromises[fontFamily];
}
// @needsAudit
/**
* Unloads all the custom fonts. This is used for testing.
*/
export async function unloadAllAsync(): Promise<void> {
if (!ExpoFontLoader.unloadAllAsync) {
throw new UnavailabilityError('expo-font', 'unloadAllAsync');
}
if (Object.keys(loadPromises).length) {
throw new CodedError(
`ERR_UNLOAD`,
`Cannot unload fonts while they're still loading: ${Object.keys(loadPromises).join(', ')}`
);
}
for (const fontFamily of Object.keys(loaded)) {
delete loaded[fontFamily];
}
await ExpoFontLoader.unloadAllAsync();
}
// @needsAudit
/**
* Unload custom fonts matching the `fontFamily`s and display values provided.
* Because fonts are automatically unloaded on every platform this is mostly used for testing.
*
* @param fontFamilyOrFontMap The name or names of the custom fonts that will be unloaded.
* @param options When `fontFamilyOrFontMap` is a string, this should be the font source used to load
* the custom font originally.
*/
export async function unloadAsync(
fontFamilyOrFontMap: string | Record<string, UnloadFontOptions>,
options?: UnloadFontOptions
): Promise<void> {
if (!ExpoFontLoader.unloadAsync) {
throw new UnavailabilityError('expo-font', 'unloadAsync');
}
if (typeof fontFamilyOrFontMap === 'object') {
if (options) {
throw new CodedError(
`ERR_FONT_API`,
`No fontFamily can be used for the provided options: ${options}. The second argument of \`unloadAsync()\` can only be used with a \`string\` value as the first argument.`
);
}
const fontMap = fontFamilyOrFontMap;
const names = Object.keys(fontMap);
await Promise.all(names.map((name) => unloadFontInNamespaceAsync(name, fontMap[name])));
return;
}
return await unloadFontInNamespaceAsync(fontFamilyOrFontMap, options);
}
async function unloadFontInNamespaceAsync(
fontFamily: string,
options?: UnloadFontOptions | null
): Promise<void> {
if (!loaded[fontFamily]) {
return;
} else {
delete loaded[fontFamily];
}
// Important: we want all callers that concurrently try to load the same font to await the same
// promise. If we're here, we haven't created the promise yet. To ensure we create only one
// promise in the program, we need to create the promise synchronously without yielding the event
// loop from this point.
const nativeFontName = getNativeFontName(fontFamily);
if (!nativeFontName) {
throw new CodedError(`ERR_FONT_FAMILY`, `Cannot unload an empty name`);
}
await ExpoFontLoader.unloadAsync(nativeFontName, options);
}
export { FontDisplay, FontSource, FontResource, UnloadFontOptions };

View File

@@ -0,0 +1,72 @@
import { Asset } from 'expo-asset';
// @needsAudit
/**
* The different types of assets you can provide to the [`loadAsync()`](#loadasyncfontfamilyorfontmap-source) function.
* A font source can be a URI, a module ID, or an Expo Asset.
*/
export type FontSource = string | number | Asset | FontResource;
// @needsAudit
/**
* An object used to dictate the resource that is loaded into the provided font namespace when used
* with [`loadAsync`](#loadasyncfontfamilyorfontmap-source).
*/
export type FontResource = {
uri?: string | number;
/**
* Sets the [`font-display`](#fontdisplay) property for a given typeface in the browser.
* @platform web
*/
display?: FontDisplay;
default?: string;
};
// @needsAudit
/**
* Sets the [font-display](https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display)
* for a given typeface. The default font value on web is `FontDisplay.AUTO`.
* Even though setting the `fontDisplay` does nothing on native platforms, the default behavior
* emulates `FontDisplay.SWAP` on flagship devices like iOS, Samsung, Pixel, etc. Default
* functionality varies on One Plus devices. In the browser this value is set in the generated
* `@font-face` CSS block and not as a style property meaning you cannot dynamically change this
* value based on the element it's used in.
* @platform web
*/
export enum FontDisplay {
/**
* __(Default)__ The font display strategy is defined by the user agent or platform.
* This generally defaults to the text being invisible until the font is loaded.
* Good for buttons or banners that require a specific treatment.
*/
AUTO = 'auto',
/**
* Fallback text is rendered immediately with a default font while the desired font is loaded.
* This is good for making the content appear to load instantly and is usually preferred.
*/
SWAP = 'swap',
/**
* The text will be invisible until the font has loaded. If the font fails to load then nothing
* will appear - it's best to turn this off when debugging missing text.
*/
BLOCK = 'block',
/**
* Splits the behavior between `SWAP` and `BLOCK`.
* There will be a [100ms timeout](https://developers.google.com/web/updates/2016/02/font-display?hl=en)
* where the text with a custom font is invisible, after that the text will either swap to the
* styled text or it'll show the unstyled text and continue to load the custom font. This is good
* for buttons that need a custom font but should also be quickly available to screen-readers.
*/
FALLBACK = 'fallback',
/**
* This works almost identically to `FALLBACK`, the only difference is that the browser will
* decide to load the font based on slow connection speed or critical resource demand.
*/
OPTIONAL = 'optional',
}
// @needsAudit
/**
* Object used to query fonts for unloading.
*/
export type UnloadFontOptions = Pick<FontResource, 'display'>;

View File

@@ -0,0 +1,55 @@
import { useEffect, useState } from 'react';
import { loadAsync, isLoaded } from './Font';
import { FontSource } from './Font.types';
function isMapLoaded(map: string | Record<string, FontSource>) {
if (typeof map === 'string') {
return isLoaded(map);
} else {
return Object.keys(map).every((fontFamily) => isLoaded(fontFamily));
}
}
function useRuntimeFonts(map: string | Record<string, FontSource>): [boolean, Error | null] {
const [loaded, setLoaded] = useState(
// For web rehydration, we need to check if the fonts are already loaded during the static render.
// Native will also benefit from this optimization.
isMapLoaded(map)
);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
loadAsync(map)
.then(() => setLoaded(true))
.catch(setError);
}, []);
return [loaded, error];
}
function useStaticFonts(map: string | Record<string, FontSource>): [boolean, Error | null] {
loadAsync(map);
return [true, null];
}
// @needsAudit
/**
* ```ts
* const [loaded, error] = useFonts({ ... });
* ```
* Load a map of fonts with [`loadAsync`](#loadasyncfontfamilyorfontmap-source). This returns a `boolean` if the fonts are
* loaded and ready to use. It also returns an error if something went wrong, to use in development.
*
* > Note, the fonts are not "reloaded" when you dynamically change the font map.
*
* @param map A map of `fontFamily`s to [`FontSource`](#fontsource)s. After loading the font you can
* use the key in the `fontFamily` style prop of a `Text` element.
*
* @return
* - __loaded__ (`boolean`) - A boolean to detect if the font for `fontFamily` has finished
* loading.
* - __error__ (`Error | null`) - An error encountered when loading the fonts.
*/
export const useFonts: (map: string | Record<string, FontSource>) => [boolean, Error | null] =
typeof window === 'undefined' ? useStaticFonts : useRuntimeFonts;

View File

@@ -0,0 +1,64 @@
import { Asset } from 'expo-asset';
import Constants from 'expo-constants';
import { CodedError, Platform } from 'expo-modules-core';
import ExpoFontLoader from './ExpoFontLoader';
import { FontResource, FontSource } from './Font.types';
const isInExpoGo = Constants.appOwnership === 'expo';
export function fontFamilyNeedsScoping(name: string): boolean {
return (
isInExpoGo &&
Platform.OS !== 'ios' &&
!Constants.systemFonts.includes(name) &&
name !== 'System' &&
!name.includes(Constants.sessionId)
);
}
export function getAssetForSource(source: FontSource): Asset | FontResource {
if (source instanceof Asset) {
return source;
}
if (typeof source === 'string') {
return Asset.fromURI(source);
} else if (typeof source === 'number') {
return Asset.fromModule(source);
} else if (typeof source === 'object' && typeof source.uri !== 'undefined') {
return getAssetForSource(source.uri);
}
// @ts-ignore Error: Type 'string' is not assignable to type 'Asset'
// We can't have a string here, we would have thrown an error if !isWeb
// or returned Asset.fromModule if isWeb.
return source;
}
export async function loadSingleFontAsync(
name: string,
input: Asset | FontResource
): Promise<void> {
const asset = input as Asset;
if (!asset.downloadAsync) {
throw new CodedError(
`ERR_FONT_SOURCE`,
'`loadSingleFontAsync` expected resource of type `Asset` from expo-asset on native'
);
}
await asset.downloadAsync();
if (!asset.downloaded) {
throw new CodedError(`ERR_DOWNLOAD`, `Failed to download asset for font "${name}"`);
}
await ExpoFontLoader.loadAsync(getNativeFontName(name), asset.localUri);
}
export function getNativeFontName(name: string): string {
if (fontFamilyNeedsScoping(name)) {
return `${Constants.sessionId}-${name}`;
} else {
return name;
}
}

View File

@@ -0,0 +1,66 @@
import { Asset } from 'expo-asset';
import { CodedError } from 'expo-modules-core';
import ExpoFontLoader from './ExpoFontLoader';
import { FontResource, FontSource, FontDisplay } from './Font.types';
function uriFromFontSource(asset: any): string | null {
if (typeof asset === 'string') {
return asset || null;
} else if (typeof asset === 'object') {
return asset.uri || asset.localUri || asset.default || null;
} else if (typeof asset === 'number') {
return uriFromFontSource(Asset.fromModule(asset));
}
return null;
}
function displayFromFontSource(asset: any): FontDisplay | undefined {
return asset.display || FontDisplay.AUTO;
}
export function fontFamilyNeedsScoping(name: string): boolean {
return false;
}
export function getAssetForSource(source: FontSource): Asset | FontResource {
const uri = uriFromFontSource(source);
const display = displayFromFontSource(source);
if (!uri || typeof uri !== 'string') {
throwInvalidSourceError(uri);
}
return {
uri: uri!,
display,
};
}
function throwInvalidSourceError(source: any): never {
let type: string = typeof source;
if (type === 'object') type = JSON.stringify(source, null, 2);
throw new CodedError(
`ERR_FONT_SOURCE`,
`Expected font asset of type \`string | FontResource | Asset\` instead got: ${type}`
);
}
// NOTE(EvanBacon): No async keyword!
export function loadSingleFontAsync(name: string, input: Asset | FontResource): Promise<void> {
if (typeof input !== 'object' || typeof input.uri !== 'string' || (input as any).downloadAsync) {
throwInvalidSourceError(input);
}
try {
return ExpoFontLoader.loadAsync(name, input);
} catch {
// No-op.
}
return Promise.resolve();
}
export function getNativeFontName(name: string): string {
return name;
}

View File

@@ -0,0 +1,2 @@
export * from './Font';
export { useFonts } from './FontHooks';

View File

@@ -0,0 +1,2 @@
export const loaded: { [name: string]: boolean } = {};
export const loadPromises: { [name: string]: Promise<void> } = {};

View File

@@ -0,0 +1,34 @@
import { CodedError } from 'expo-modules-core';
import ExpoFontLoader from './ExpoFontLoader';
import { FontSource } from './Font.types';
import { getAssetForSource, loadSingleFontAsync } from './FontLoader';
/**
* @returns the server resources that should be statically extracted.
* @private
*/
export function getServerResources(): string[] {
return ExpoFontLoader.getServerResources();
}
/**
* @returns clear the server resources from the global scope.
* @private
*/
export function resetServerContext() {
return ExpoFontLoader.resetServerContext();
}
export function registerStaticFont(fontFamily: string, source?: FontSource | null) {
// MUST BE A SYNC FUNCTION!
if (!source) {
throw new CodedError(
`ERR_FONT_SOURCE`,
`Cannot load null or undefined font source: { "${fontFamily}": ${source} }. Expected asset of type \`FontSource\` for fontFamily of name: "${fontFamily}"`
);
}
const asset = getAssetForSource(source);
loadSingleFontAsync(fontFamily, asset);
}

View File

@@ -0,0 +1,9 @@
// @generated by expo-module-scripts
{
"extends": "expo-module-scripts/tsconfig.base",
"compilerOptions": {
"outDir": "./build"
},
"include": ["./src"],
"exclude": ["**/__mocks__/*", "**/__tests__/*"]
}