@symbiote-native/font 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +210 -0
  3. package/build/angular/index.d.ts +2 -0
  4. package/build/angular/index.js +4 -0
  5. package/build/angular/services/fonts.service/index.d.ts +8 -0
  6. package/build/angular/services/fonts.service/index.js +64 -0
  7. package/build/core/font-loader.d.ts +4 -0
  8. package/build/core/font-loader.js +30 -0
  9. package/build/core/font-utils.d.ts +14 -0
  10. package/build/core/font-utils.js +16 -0
  11. package/build/core/font.d.ts +11 -0
  12. package/build/core/font.js +94 -0
  13. package/build/core/index.d.ts +5 -0
  14. package/build/core/index.js +3 -0
  15. package/build/core/memory.d.ts +6 -0
  16. package/build/core/memory.js +30 -0
  17. package/build/core/native-modules.d.ts +24 -0
  18. package/build/core/native-modules.js +7 -0
  19. package/build/core/types.d.ts +18 -0
  20. package/build/core/types.js +9 -0
  21. package/build/react/hooks/use-fonts/index.d.ts +2 -0
  22. package/build/react/hooks/use-fonts/index.js +28 -0
  23. package/build/react/index.d.ts +2 -0
  24. package/build/react/index.js +3 -0
  25. package/build/solid/index.d.ts +2 -0
  26. package/build/solid/index.js +5 -0
  27. package/build/solid/primitives/create-fonts.d.ts +6 -0
  28. package/build/solid/primitives/create-fonts.js +12 -0
  29. package/build/svelte/index.d.ts +2 -0
  30. package/build/svelte/index.js +5 -0
  31. package/build/svelte/runes/use-fonts.svelte.d.ts +5 -0
  32. package/build/svelte/runes/use-fonts.svelte.js +24 -0
  33. package/build/vue/composables/use-fonts/index.d.ts +6 -0
  34. package/build/vue/composables/use-fonts/index.js +18 -0
  35. package/build/vue/index.d.ts +2 -0
  36. package/build/vue/index.js +3 -0
  37. package/build-ngc/angular/index.d.ts +2 -0
  38. package/build-ngc/angular/index.js +5 -0
  39. package/build-ngc/angular/index.js.map +1 -0
  40. package/build-ngc/angular/services/fonts.service/index.d.ts +11 -0
  41. package/build-ngc/angular/services/fonts.service/index.js +24 -0
  42. package/build-ngc/angular/services/fonts.service/index.js.map +1 -0
  43. package/build-ngc/core/font-loader.d.ts +4 -0
  44. package/build-ngc/core/font-loader.js +31 -0
  45. package/build-ngc/core/font-loader.js.map +1 -0
  46. package/build-ngc/core/font-utils.d.ts +14 -0
  47. package/build-ngc/core/font-utils.js +17 -0
  48. package/build-ngc/core/font-utils.js.map +1 -0
  49. package/build-ngc/core/font.d.ts +11 -0
  50. package/build-ngc/core/font.js +95 -0
  51. package/build-ngc/core/font.js.map +1 -0
  52. package/build-ngc/core/index.d.ts +5 -0
  53. package/build-ngc/core/index.js +4 -0
  54. package/build-ngc/core/index.js.map +1 -0
  55. package/build-ngc/core/memory.d.ts +6 -0
  56. package/build-ngc/core/memory.js +31 -0
  57. package/build-ngc/core/memory.js.map +1 -0
  58. package/build-ngc/core/native-modules.d.ts +24 -0
  59. package/build-ngc/core/native-modules.js +8 -0
  60. package/build-ngc/core/native-modules.js.map +1 -0
  61. package/build-ngc/core/types.d.ts +18 -0
  62. package/build-ngc/core/types.js +10 -0
  63. package/build-ngc/core/types.js.map +1 -0
  64. package/native-link.json +17 -0
  65. package/package.json +151 -0
  66. package/src/angular/index.ts +4 -0
  67. package/src/angular/services/fonts.service/index.ts +21 -0
  68. package/src/core/font-loader.ts +44 -0
  69. package/src/core/font-utils.ts +39 -0
  70. package/src/core/font.ts +150 -0
  71. package/src/core/index.ts +18 -0
  72. package/src/core/memory.ts +39 -0
  73. package/src/core/native-modules.ts +45 -0
  74. package/src/core/types.ts +25 -0
  75. package/src/react/hooks/use-fonts/index.ts +36 -0
  76. package/src/react/index.ts +4 -0
  77. package/src/solid/index.ts +6 -0
  78. package/src/solid/primitives/create-fonts.ts +18 -0
  79. package/src/svelte/index.ts +6 -0
  80. package/src/svelte/runes/use-fonts.svelte.ts +30 -0
  81. package/src/svelte/svelte-ambient.d.ts +3 -0
  82. package/src/vue/composables/use-fonts/index.ts +24 -0
  83. package/src/vue/index.ts +4 -0
package/package.json ADDED
@@ -0,0 +1,151 @@
1
+ {
2
+ "name": "@symbiote-native/font",
3
+ "version": "0.1.0",
4
+ "description": "expo-font wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, Svelte, Solid, and Angular adapters. Runtime font loading/unloading by fontFamily, plus a text-to-image render utility.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/OneEyed1366/symbiote-native.git",
9
+ "directory": "packages/font"
10
+ },
11
+ "homepage": "https://github.com/OneEyed1366/symbiote-native/tree/master/packages/font#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/OneEyed1366/symbiote-native/issues"
14
+ },
15
+ "author": "Andrey Prokopenko <psevdoproger@gmail.com>",
16
+ "keywords": [
17
+ "react-native",
18
+ "symbiote-native",
19
+ "expo-font",
20
+ "react",
21
+ "vue",
22
+ "svelte",
23
+ "solid",
24
+ "angular",
25
+ "font",
26
+ "typography"
27
+ ],
28
+ "type": "module",
29
+ "main": "./build/core/index.js",
30
+ "module": "./build/core/index.js",
31
+ "types": "./build/core/index.d.ts",
32
+ "exports": {
33
+ ".": {
34
+ "types": "./build/core/index.d.ts",
35
+ "default": "./build/core/index.js"
36
+ },
37
+ "./vue": {
38
+ "types": "./build/vue/index.d.ts",
39
+ "default": "./build/vue/index.js"
40
+ },
41
+ "./react": {
42
+ "types": "./build/react/index.d.ts",
43
+ "default": "./build/react/index.js"
44
+ },
45
+ "./svelte": {
46
+ "types": "./build/svelte/index.d.ts",
47
+ "default": "./build/svelte/index.js"
48
+ },
49
+ "./solid": {
50
+ "types": "./build/solid/index.d.ts",
51
+ "default": "./build/solid/index.js"
52
+ },
53
+ "./angular": {
54
+ "types": "./build-ngc/angular/index.d.ts",
55
+ "react-native": "./build-ngc/angular/index.js",
56
+ "default": "./src/angular/index.ts"
57
+ }
58
+ },
59
+ "files": [
60
+ "src",
61
+ "build",
62
+ "build-ngc",
63
+ "native-link.json",
64
+ "!src/**/*.test.*",
65
+ "!src/**/*.spec.*",
66
+ "!src/**/*.detox.*"
67
+ ],
68
+ "publishConfig": {
69
+ "access": "public"
70
+ },
71
+ "dependencies": {
72
+ "expo-font": "57.0.4",
73
+ "expo-modules-core": "57.0.20",
74
+ "@symbiote-native/asset": "0.1.0"
75
+ },
76
+ "peerDependencies": {
77
+ "@angular/core": ">=20",
78
+ "@vue/runtime-core": "^3.5.13",
79
+ "react": ">=19.0.0",
80
+ "react-native": ">=0.86",
81
+ "solid-js": ">=1.9.0",
82
+ "svelte": ">=5.56.0",
83
+ "vue": ">=3.5.0",
84
+ "@symbiote-native/angular": "^3.1.3",
85
+ "@symbiote-native/react": "^3.1.0",
86
+ "@symbiote-native/solid": "^3.0.5",
87
+ "@symbiote-native/engine": "^1.4.0",
88
+ "@symbiote-native/vue": "^3.1.0",
89
+ "@symbiote-native/svelte": "^3.0.5"
90
+ },
91
+ "peerDependenciesMeta": {
92
+ "@symbiote-native/angular": {
93
+ "optional": true
94
+ },
95
+ "@symbiote-native/react": {
96
+ "optional": true
97
+ },
98
+ "@symbiote-native/solid": {
99
+ "optional": true
100
+ },
101
+ "@symbiote-native/svelte": {
102
+ "optional": true
103
+ },
104
+ "@symbiote-native/vue": {
105
+ "optional": true
106
+ },
107
+ "@angular/core": {
108
+ "optional": true
109
+ },
110
+ "@vue/runtime-core": {
111
+ "optional": true
112
+ },
113
+ "react": {
114
+ "optional": true
115
+ },
116
+ "solid-js": {
117
+ "optional": true
118
+ },
119
+ "svelte": {
120
+ "optional": true
121
+ },
122
+ "vue": {
123
+ "optional": true
124
+ }
125
+ },
126
+ "devDependencies": {
127
+ "@angular/compiler": "~22.0.8",
128
+ "@angular/compiler-cli": "~22.0.8",
129
+ "@angular/core": "~22.0.8",
130
+ "@types/node": "^26.0.0",
131
+ "@types/react": "^19.2.0",
132
+ "@vue/runtime-core": "^3.5.13",
133
+ "react": "19.2.3",
134
+ "solid-js": "^1.9.14",
135
+ "svelte": "^5.56.0",
136
+ "typescript": "~6.0.0",
137
+ "@symbiote-native/svelte": "3.0.5",
138
+ "@symbiote-native/angular": "3.1.3",
139
+ "@symbiote-native/react": "3.1.0",
140
+ "@symbiote-native/engine": "1.4.0",
141
+ "@symbiote-native/test-utils": "0.4.5",
142
+ "@symbiote-native/vue": "3.1.0",
143
+ "@symbiote-native/solid": "3.0.5"
144
+ },
145
+ "scripts": {
146
+ "typecheck": "tsc --build",
147
+ "clean": "rm -rf build-ngc",
148
+ "ng:build": "pnpm run clean && ngc -p tsconfig.angular.json",
149
+ "format": "prettier --write \"src/**/*.{ts,tsx}\""
150
+ }
151
+ }
@@ -0,0 +1,4 @@
1
+ // FontsService is the Angular-only lifecycle half; the loading/caching logic all lives in
2
+ // core, shared with React/Vue/Svelte/Solid.
3
+ export { FontsService } from './services/fonts.service';
4
+ export * from '../core';
@@ -0,0 +1,21 @@
1
+ import { Injectable, signal, type Signal } from '@angular/core';
2
+ import { isFontMapLoaded, loadAsync, type FontSource } from '../../../core';
3
+
4
+ // Angular twin of React's useFonts hook and Vue's useFonts composable — connect(map) returns
5
+ // a Signal pair, e.g. `readonly fonts = inject(FontsService).connect({ Inter: require('...') })`.
6
+ @Injectable({ providedIn: 'root' })
7
+ export class FontsService {
8
+ connect(map: string | Record<string, FontSource>): {
9
+ loaded: Signal<boolean>;
10
+ error: Signal<Error | null>;
11
+ } {
12
+ const loaded = signal(isFontMapLoaded(map));
13
+ const error = signal<Error | null>(null);
14
+
15
+ loadAsync(map)
16
+ .then(() => loaded.set(true))
17
+ .catch((err: Error) => error.set(err));
18
+
19
+ return { loaded: loaded.asReadonly(), error: error.asReadonly() };
20
+ }
21
+ }
@@ -0,0 +1,44 @@
1
+ // Ported from expo-font/src/FontLoader.ts (sdk-57), resolving through @symbiote-native/asset's
2
+ // Asset class (this repo's own expo-asset port) instead of importing expo-asset directly.
3
+ import { Asset } from '@symbiote-native/asset';
4
+ import { CodedError } from 'expo-modules-core';
5
+
6
+ import { expoFontLoader } from './native-modules';
7
+ import type { FontResource, FontSource } from './types';
8
+
9
+ export function getAssetForSource(source: FontSource): Asset | FontResource {
10
+ if (source instanceof Asset) {
11
+ return source;
12
+ }
13
+ if (typeof source === 'string') {
14
+ return Asset.fromURI(source);
15
+ }
16
+ if (typeof source === 'number') {
17
+ return Asset.fromModule(source);
18
+ }
19
+ if (typeof source === 'object' && typeof source.uri !== 'undefined') {
20
+ return getAssetForSource(source.uri);
21
+ }
22
+ return source;
23
+ }
24
+
25
+ export async function loadSingleFontAsync(
26
+ name: string,
27
+ input: Asset | FontResource,
28
+ ): Promise<void> {
29
+ if (!(input instanceof Asset)) {
30
+ throw new CodedError(
31
+ 'ERR_FONT_SOURCE',
32
+ '`loadSingleFontAsync` expected a resource of type `Asset` from @symbiote-native/asset',
33
+ );
34
+ }
35
+
36
+ await input.downloadAsync();
37
+ if (!input.downloaded || !input.localUri) {
38
+ throw new CodedError(
39
+ 'ERR_DOWNLOAD',
40
+ `Failed to download asset for font "${name}"`,
41
+ );
42
+ }
43
+ await expoFontLoader.loadAsync(name, input.localUri);
44
+ }
@@ -0,0 +1,39 @@
1
+ // Ported from expo-font/src/FontUtils.ts (sdk-57).
2
+ import { UnavailabilityError } from 'expo-modules-core';
3
+ import { processColor } from 'react-native';
4
+
5
+ import { expoFontUtils } from './native-modules';
6
+
7
+ export type IRenderToImageOptions = {
8
+ fontFamily?: string;
9
+ size?: number;
10
+ color?: string;
11
+ lineHeight?: number;
12
+ };
13
+
14
+ export type IRenderToImageResult = {
15
+ uri: string;
16
+ width: number;
17
+ height: number;
18
+ scale: number;
19
+ };
20
+
21
+ /** @platform android @platform ios */
22
+ export async function renderToImageAsync(
23
+ glyphs: string,
24
+ options?: IRenderToImageOptions,
25
+ ): Promise<IRenderToImageResult> {
26
+ if (!expoFontUtils) {
27
+ throw new UnavailabilityError(
28
+ 'expo-font',
29
+ 'ExpoFontUtils.renderToImageAsync',
30
+ );
31
+ }
32
+
33
+ return expoFontUtils.renderToImageAsync(glyphs, {
34
+ ...options,
35
+ color: options?.color
36
+ ? (processColor(options.color) ?? undefined)
37
+ : undefined,
38
+ });
39
+ }
@@ -0,0 +1,150 @@
1
+ // Ported from expo-font/src/Font.ts (sdk-57), native-only — drops the web/server branches
2
+ // (isLoaded's web fallback, loadAsync's server pre-render pass, registerStaticFont).
3
+ import { CodedError, UnavailabilityError } from 'expo-modules-core';
4
+
5
+ import { getAssetForSource, loadSingleFontAsync } from './font-loader';
6
+ import {
7
+ isLoadedNative,
8
+ loadPromises,
9
+ markLoaded,
10
+ purgeCache,
11
+ purgeFontFamilyFromCache,
12
+ } from './memory';
13
+ import { expoFontLoader } from './native-modules';
14
+ import type { FontSource, UnloadFontOptions } from './types';
15
+
16
+ export function isLoaded(fontFamily: string): boolean {
17
+ return isLoadedNative(fontFamily);
18
+ }
19
+
20
+ export function getLoadedFonts(): string[] {
21
+ return expoFontLoader.getLoadedFonts();
22
+ }
23
+
24
+ export function isLoading(fontFamily: string): boolean {
25
+ return fontFamily in loadPromises;
26
+ }
27
+
28
+ /** Shared seed check for every adapter's `useFonts`/`createFonts` lifecycle wrapper. */
29
+ export function isFontMapLoaded(
30
+ map: string | Record<string, FontSource>,
31
+ ): boolean {
32
+ if (typeof map === 'string') {
33
+ return isLoaded(map);
34
+ }
35
+ return Object.keys(map).every(fontFamily => isLoaded(fontFamily));
36
+ }
37
+
38
+ export function loadAsync(
39
+ fontFamilyOrFontMap: string | Record<string, FontSource>,
40
+ source?: FontSource,
41
+ ): Promise<void> {
42
+ if (typeof fontFamilyOrFontMap === 'object') {
43
+ if (source) {
44
+ return Promise.reject(
45
+ new CodedError(
46
+ 'ERR_FONT_API',
47
+ `No fontFamily can be used for the provided source: ${String(source)}. The second argument of loadAsync() can only be used with a string value as the first argument.`,
48
+ ),
49
+ );
50
+ }
51
+ const fontMap = fontFamilyOrFontMap;
52
+ const names = Object.keys(fontMap);
53
+ return Promise.all(
54
+ names.map(name => loadFontInNamespaceAsync(name, fontMap[name])),
55
+ ).then(() => {});
56
+ }
57
+
58
+ return loadFontInNamespaceAsync(fontFamilyOrFontMap, source);
59
+ }
60
+
61
+ async function loadFontInNamespaceAsync(
62
+ fontFamily: string,
63
+ source?: FontSource | null,
64
+ ): Promise<void> {
65
+ if (!source) {
66
+ throw new CodedError(
67
+ 'ERR_FONT_SOURCE',
68
+ `Cannot load null or undefined font source for fontFamily "${fontFamily}".`,
69
+ );
70
+ }
71
+
72
+ if (isLoaded(fontFamily)) {
73
+ return;
74
+ }
75
+
76
+ if (Object.hasOwn(loadPromises, fontFamily)) {
77
+ return loadPromises[fontFamily];
78
+ }
79
+
80
+ const asset = getAssetForSource(source);
81
+ loadPromises[fontFamily] = (async () => {
82
+ try {
83
+ await loadSingleFontAsync(fontFamily, asset);
84
+ markLoaded(fontFamily);
85
+ } finally {
86
+ delete loadPromises[fontFamily];
87
+ }
88
+ })();
89
+
90
+ await loadPromises[fontFamily];
91
+ }
92
+
93
+ /** Always throws on native — ExpoFontLoader has no unload method there, web-only upstream. */
94
+ export async function unloadAllAsync(): Promise<void> {
95
+ if (!expoFontLoader.unloadAllAsync) {
96
+ throw new UnavailabilityError('expo-font', 'unloadAllAsync');
97
+ }
98
+ if (Object.keys(loadPromises).length) {
99
+ throw new CodedError(
100
+ 'ERR_UNLOAD',
101
+ `Cannot unload fonts while they're still loading: ${Object.keys(loadPromises).join(', ')}`,
102
+ );
103
+ }
104
+ purgeCache();
105
+ await expoFontLoader.unloadAllAsync();
106
+ }
107
+
108
+ /** Always throws on native — ExpoFontLoader has no unload method there, web-only upstream. */
109
+ export async function unloadAsync(
110
+ fontFamilyOrFontMap: string | Record<string, UnloadFontOptions>,
111
+ options?: UnloadFontOptions,
112
+ ): Promise<void> {
113
+ if (!expoFontLoader.unloadAsync) {
114
+ throw new UnavailabilityError('expo-font', 'unloadAsync');
115
+ }
116
+ if (typeof fontFamilyOrFontMap === 'object') {
117
+ if (options) {
118
+ throw new CodedError(
119
+ 'ERR_FONT_API',
120
+ `No fontFamily can be used for the provided options: ${String(options)}. The second argument of unloadAsync() can only be used with a string value as the first argument.`,
121
+ );
122
+ }
123
+ const names = Object.keys(fontFamilyOrFontMap);
124
+ await Promise.all(
125
+ names.map(name =>
126
+ unloadFontInNamespaceAsync(name, fontFamilyOrFontMap[name]),
127
+ ),
128
+ );
129
+ return;
130
+ }
131
+ return unloadFontInNamespaceAsync(fontFamilyOrFontMap, options);
132
+ }
133
+
134
+ async function unloadFontInNamespaceAsync(
135
+ fontFamily: string,
136
+ options?: UnloadFontOptions,
137
+ ): Promise<void> {
138
+ if (!isLoaded(fontFamily)) {
139
+ return;
140
+ }
141
+ purgeFontFamilyFromCache(fontFamily);
142
+
143
+ if (!fontFamily) {
144
+ throw new CodedError('ERR_FONT_FAMILY', 'Cannot unload an empty name');
145
+ }
146
+ if (!expoFontLoader.unloadAsync) {
147
+ throw new UnavailabilityError('expo-font', 'unloadAsync');
148
+ }
149
+ await expoFontLoader.unloadAsync(fontFamily, options);
150
+ }
@@ -0,0 +1,18 @@
1
+ export {
2
+ isLoaded,
3
+ getLoadedFonts,
4
+ isLoading,
5
+ isFontMapLoaded,
6
+ loadAsync,
7
+ unloadAllAsync,
8
+ unloadAsync,
9
+ } from './font';
10
+ export { renderToImageAsync } from './font-utils';
11
+ export type { IRenderToImageOptions, IRenderToImageResult } from './font-utils';
12
+ export { FontDisplay } from './types';
13
+ export type {
14
+ FontResource,
15
+ FontSource,
16
+ UnloadFontOptions,
17
+ UseFontsResult,
18
+ } from './types';
@@ -0,0 +1,39 @@
1
+ // Ported verbatim from expo-font/src/memory.ts (sdk-57) — no web/server branch to drop, this
2
+ // module was already native-agnostic.
3
+ import { expoFontLoader } from './native-modules';
4
+
5
+ export const loadPromises: Record<string, Promise<void>> = {};
6
+
7
+ let cache: Record<string, boolean> = {};
8
+
9
+ export function markLoaded(fontFamily: string): void {
10
+ cache[fontFamily] = true;
11
+ }
12
+
13
+ export function isLoadedInCache(fontFamily: string): boolean {
14
+ return fontFamily in cache;
15
+ }
16
+
17
+ export function isLoadedNative(fontFamily: string): boolean {
18
+ if (isLoadedInCache(fontFamily)) {
19
+ return true;
20
+ }
21
+
22
+ const loadedNativeFonts = expoFontLoader.getLoadedFonts();
23
+ if (!loadedNativeFonts?.length) {
24
+ return false;
25
+ }
26
+
27
+ loadedNativeFonts.forEach(font => {
28
+ cache[font] = true;
29
+ });
30
+ return fontFamily in cache;
31
+ }
32
+
33
+ export function purgeFontFamilyFromCache(fontFamily: string): void {
34
+ delete cache[fontFamily];
35
+ }
36
+
37
+ export function purgeCache(): void {
38
+ cache = {};
39
+ }
@@ -0,0 +1,45 @@
1
+ import {
2
+ requireNativeModule,
3
+ requireOptionalNativeModule,
4
+ } from 'expo-modules-core';
5
+ import type { ProcessedColorValue } from 'react-native';
6
+
7
+ import type { UnloadFontOptions } from './types';
8
+
9
+ export type INativeFontLoaderModule = {
10
+ getLoadedFonts(): string[];
11
+ loadAsync(fontFamilyName: string, localUri: string): Promise<void>;
12
+ unloadAllAsync?(): Promise<void>;
13
+ unloadAsync?(
14
+ fontFamilyName: string,
15
+ options?: UnloadFontOptions,
16
+ ): Promise<void>;
17
+ };
18
+
19
+ export type INativeRenderToImageOptions = {
20
+ fontFamily?: string;
21
+ size?: number;
22
+ color?: ProcessedColorValue;
23
+ lineHeight?: number;
24
+ };
25
+
26
+ export type INativeFontUtilsModule = {
27
+ renderToImageAsync(
28
+ glyphs: string,
29
+ options?: INativeRenderToImageOptions,
30
+ ): Promise<{ uri: string; width: number; height: number; scale: number }>;
31
+ };
32
+
33
+ const EXPO_FONT_LOADER_MODULE_NAME = 'ExpoFontLoader';
34
+ const EXPO_FONT_UTILS_MODULE_NAME = 'ExpoFontUtils';
35
+
36
+ export const expoFontLoader = requireNativeModule<INativeFontLoaderModule>(
37
+ EXPO_FONT_LOADER_MODULE_NAME,
38
+ );
39
+
40
+ // Present on both iOS and Android upstream — still optional (matches upstream's own
41
+ // requireOptionalNativeModule) since only web ships without it.
42
+ export const expoFontUtils =
43
+ requireOptionalNativeModule<INativeFontUtilsModule>(
44
+ EXPO_FONT_UTILS_MODULE_NAME,
45
+ );
@@ -0,0 +1,25 @@
1
+ // Ported from expo-font/src/Font.types.ts (sdk-57) — full parity, including the `number`
2
+ // (require() module) and `Asset` instance source forms, both resolved via @symbiote-native/asset.
3
+ import type { Asset } from '@symbiote-native/asset';
4
+
5
+ /** @platform web — has no effect on native, kept for API-surface parity with upstream. */
6
+ export enum FontDisplay {
7
+ AUTO = 'auto',
8
+ SWAP = 'swap',
9
+ BLOCK = 'block',
10
+ FALLBACK = 'fallback',
11
+ OPTIONAL = 'optional',
12
+ }
13
+
14
+ export type FontResource = {
15
+ uri?: string | number;
16
+ display?: FontDisplay;
17
+ default?: string;
18
+ testString?: string;
19
+ };
20
+
21
+ export type FontSource = string | number | Asset | FontResource;
22
+
23
+ export type UnloadFontOptions = Pick<FontResource, 'display'>;
24
+
25
+ export type UseFontsResult = readonly [loaded: boolean, error: Error | null];
@@ -0,0 +1,36 @@
1
+ // React lifecycle wiring over the framework-agnostic core (core/font.ts). Loads once on mount
2
+ // (upstream's own contract: a changed font map is not reloaded — see FontHooks.ts's
3
+ // useRuntimeFonts), seeded synchronously so an already-loaded font never flashes unloaded.
4
+ import { useEffect, useState } from 'react';
5
+ import {
6
+ isFontMapLoaded,
7
+ loadAsync,
8
+ type FontSource,
9
+ type UseFontsResult,
10
+ } from '../../../core';
11
+
12
+ export function useFonts(
13
+ map: string | Record<string, FontSource>,
14
+ ): UseFontsResult {
15
+ const [loaded, setLoaded] = useState(() => isFontMapLoaded(map));
16
+ const [error, setError] = useState<Error | null>(null);
17
+
18
+ useEffect(() => {
19
+ let isMounted = true;
20
+ loadAsync(map)
21
+ .then(() => {
22
+ if (isMounted) setLoaded(true);
23
+ })
24
+ .catch((err: Error) => {
25
+ if (isMounted) setError(err);
26
+ });
27
+ return () => {
28
+ isMounted = false;
29
+ };
30
+ // Loads once on mount only — a later change to `map` is deliberately not reloaded,
31
+ // matching upstream's own useFonts contract.
32
+ // eslint-disable-next-line react-hooks/exhaustive-deps
33
+ }, []);
34
+
35
+ return [loaded, error];
36
+ }
@@ -0,0 +1,4 @@
1
+ // @symbiote-native/font/react: the React entry over the framework-agnostic core.
2
+
3
+ export { useFonts } from './hooks/use-fonts';
4
+ export * from '../core';
@@ -0,0 +1,6 @@
1
+ // @symbiote-native/font/solid: the Solid entry over the framework-agnostic core — mirrors the
2
+ // lifecycle-bucket naming convention of adapters/solid/src/primitives (never `hooks`/
3
+ // `composables`/`runes`, those are React's, Vue's and Svelte's terms).
4
+
5
+ export { createFonts } from './primitives/create-fonts';
6
+ export * from '../core';
@@ -0,0 +1,18 @@
1
+ // createFonts — the Solid twin of React's useFonts hook, over the framework-agnostic core
2
+ // (core/font.ts). Loads once from the primitive body, matching upstream's own contract.
3
+ import { createSignal, type Accessor } from 'solid-js';
4
+ import { isFontMapLoaded, loadAsync, type FontSource } from '../../core';
5
+
6
+ export function createFonts(map: string | Record<string, FontSource>): {
7
+ loaded: Accessor<boolean>;
8
+ error: Accessor<Error | null>;
9
+ } {
10
+ const [loaded, setLoaded] = createSignal(isFontMapLoaded(map));
11
+ const [error, setError] = createSignal<Error | null>(null);
12
+
13
+ loadAsync(map)
14
+ .then(() => setLoaded(true))
15
+ .catch(setError);
16
+
17
+ return { loaded, error };
18
+ }
@@ -0,0 +1,6 @@
1
+ // @symbiote-native/font/svelte: the Svelte entry over the framework-agnostic core — mirrors the
2
+ // lifecycle-bucket naming convention of adapters/svelte/src/runes (never `hooks`/`composables`,
3
+ // those are React's and Vue's terms).
4
+
5
+ export { useFonts } from './runes/use-fonts.svelte';
6
+ export * from '../core';
@@ -0,0 +1,30 @@
1
+ // Svelte lifecycle wiring over the framework-agnostic core (core/font.ts). Loads once per
2
+ // mount, matching upstream's own useFonts contract (a changed font map is not reloaded).
3
+ import { isFontMapLoaded, loadAsync, type FontSource } from '../../core';
4
+
5
+ export function useFonts(map: string | Record<string, FontSource>): {
6
+ readonly loaded: boolean;
7
+ readonly error: Error | null;
8
+ } {
9
+ let loaded = $state(isFontMapLoaded(map));
10
+ let error = $state<Error | null>(null);
11
+
12
+ $effect(() => {
13
+ loadAsync(map)
14
+ .then(() => {
15
+ loaded = true;
16
+ })
17
+ .catch((err: Error) => {
18
+ error = err;
19
+ });
20
+ });
21
+
22
+ return {
23
+ get loaded(): boolean {
24
+ return loaded;
25
+ },
26
+ get error(): Error | null {
27
+ return error;
28
+ },
29
+ };
30
+ }
@@ -0,0 +1,3 @@
1
+ // Pulls in svelte's ambient global declarations ($state/$effect/...) for this package's tsc
2
+ // program — see packages/network's identical file for the full rationale.
3
+ /// <reference types="svelte" />
@@ -0,0 +1,24 @@
1
+ // Vue lifecycle wiring over the framework-agnostic core (core/font.ts). Loads once on mount,
2
+ // matching upstream's own useFonts contract (a changed font map prop is not reloaded).
3
+ import { onMounted, ref, type Ref } from '@vue/runtime-core';
4
+ import { isFontMapLoaded, loadAsync, type FontSource } from '../../../core';
5
+
6
+ export function useFonts(map: string | Record<string, FontSource>): {
7
+ loaded: Ref<boolean>;
8
+ error: Ref<Error | null>;
9
+ } {
10
+ const loaded = ref(isFontMapLoaded(map));
11
+ const error = ref<Error | null>(null);
12
+
13
+ onMounted(() => {
14
+ loadAsync(map)
15
+ .then(() => {
16
+ loaded.value = true;
17
+ })
18
+ .catch((err: Error) => {
19
+ error.value = err;
20
+ });
21
+ });
22
+
23
+ return { loaded, error };
24
+ }