react-native-nano-icons 0.1.8 → 0.2.1
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.
- package/README.md +64 -2
- package/android/src/main/java/com/nanoicons/NanoIconsFontLoaderModule.kt +63 -0
- package/android/src/main/java/com/nanoicons/NanoIconsPackage.kt +19 -2
- package/ios/NanoIconView.h +5 -0
- package/ios/NanoIconView.mm +35 -9
- package/ios/NanoIconsFontLoader.h +11 -0
- package/ios/NanoIconsFontLoader.mm +110 -0
- package/lib/commonjs/cli/build.d.ts +3 -0
- package/lib/commonjs/cli/build.js +8 -4
- package/lib/commonjs/cli/config.d.ts +1 -0
- package/lib/commonjs/cli/config.js +10 -0
- package/lib/commonjs/cli/expoConfig.d.ts +8 -0
- package/lib/commonjs/cli/expoConfig.js +34 -0
- package/lib/commonjs/cli/index.d.ts +2 -1
- package/lib/commonjs/cli/index.js +4 -1
- package/lib/commonjs/cli/link.js +32 -22
- package/lib/commonjs/plugin/src/types.d.ts +9 -0
- package/lib/commonjs/plugin/src/withNanoIconsFontLinking.d.ts +5 -0
- package/lib/commonjs/plugin/src/withNanoIconsFontLinking.js +24 -10
- package/lib/commonjs/scripts/cli.js +23 -11
- package/lib/commonjs/src/core/pipeline/config.d.ts +1 -0
- package/lib/commonjs/src/core/pipeline/managers.js +1 -2
- package/lib/commonjs/src/core/pipeline/run.js +1 -0
- package/lib/commonjs/src/core/svg/svg_dom.d.ts +1 -0
- package/lib/commonjs/src/core/svg/svg_dom.js +22 -17
- package/lib/commonjs/src/core/svg/svg_pathops.js +30 -16
- package/lib/commonjs/src/core/types.d.ts +3 -0
- package/lib/module/const/codegenPrimitives.js +2 -0
- package/lib/module/const/codegenPrimitives.js.map +1 -0
- package/lib/module/core/font/compile.js.map +1 -1
- package/lib/module/core/pipeline/config.js.map +1 -1
- package/lib/module/core/pipeline/managers.js.map +1 -1
- package/lib/module/core/pipeline/run.js +4 -1
- package/lib/module/core/pipeline/run.js.map +1 -1
- package/lib/module/core/svg/layers.js.map +1 -1
- package/lib/module/core/svg/svg_dom.js +19 -19
- package/lib/module/core/svg/svg_dom.js.map +1 -1
- package/lib/module/core/svg/svg_pathops.js.map +1 -1
- package/lib/module/createNanoIconsSet.js +2 -2
- package/lib/module/createNanoIconsSet.js.map +1 -1
- package/lib/module/createNanoIconsSet.native.js +43 -16
- package/lib/module/createNanoIconsSet.native.js.map +1 -1
- package/lib/module/createNanoIconsSet.shared.js +49 -20
- package/lib/module/createNanoIconsSet.shared.js.map +1 -1
- package/lib/module/createNanoIconsSet.web.js +78 -0
- package/lib/module/createNanoIconsSet.web.js.map +1 -0
- package/lib/module/loadDynamicFont.js +118 -0
- package/lib/module/loadDynamicFont.js.map +1 -0
- package/lib/module/specs/NanoIconViewNativeComponent.ts +9 -8
- package/lib/module/specs/NativeNanoIconsFontLoader.js +7 -0
- package/lib/module/specs/NativeNanoIconsFontLoader.js.map +1 -0
- package/lib/module/utils/glyphRuntime.js +37 -0
- package/lib/module/utils/glyphRuntime.js.map +1 -0
- package/lib/typescript/__tests__/glyphRuntime.unit.test.d.ts +2 -0
- package/lib/typescript/__tests__/glyphRuntime.unit.test.d.ts.map +1 -0
- package/lib/typescript/__tests__/link.unit.test.d.ts +3 -0
- package/lib/typescript/__tests__/link.unit.test.d.ts.map +1 -0
- package/lib/typescript/__tests__/loadDynamicFont.unit.test.d.ts +2 -0
- package/lib/typescript/__tests__/loadDynamicFont.unit.test.d.ts.map +1 -0
- package/lib/typescript/cli/build.d.ts +3 -0
- package/lib/typescript/cli/build.d.ts.map +1 -1
- package/lib/typescript/cli/link.d.ts +12 -0
- package/lib/typescript/cli/link.d.ts.map +1 -0
- package/lib/typescript/src/const/codegenPrimitives.d.ts +3 -0
- package/lib/typescript/src/const/codegenPrimitives.d.ts.map +1 -0
- package/lib/typescript/src/core/font/compile.d.ts.map +1 -1
- package/lib/typescript/src/core/pipeline/config.d.ts +1 -0
- package/lib/typescript/src/core/pipeline/config.d.ts.map +1 -1
- package/lib/typescript/src/core/pipeline/managers.d.ts.map +1 -1
- package/lib/typescript/src/core/pipeline/run.d.ts.map +1 -1
- package/lib/typescript/src/core/svg/svg_dom.d.ts +1 -0
- package/lib/typescript/src/core/svg/svg_dom.d.ts.map +1 -1
- package/lib/typescript/src/core/svg/svg_pathops.d.ts.map +1 -1
- package/lib/typescript/src/core/types.d.ts +3 -0
- package/lib/typescript/src/core/types.d.ts.map +1 -1
- package/lib/typescript/src/createNanoIconsSet.d.ts +1 -0
- package/lib/typescript/src/createNanoIconsSet.d.ts.map +1 -1
- package/lib/typescript/src/createNanoIconsSet.native.d.ts +1 -0
- package/lib/typescript/src/createNanoIconsSet.native.d.ts.map +1 -1
- package/lib/typescript/src/createNanoIconsSet.shared.d.ts +11 -0
- package/lib/typescript/src/createNanoIconsSet.shared.d.ts.map +1 -1
- package/lib/typescript/src/createNanoIconsSet.web.d.ts +7 -0
- package/lib/typescript/src/createNanoIconsSet.web.d.ts.map +1 -0
- package/lib/typescript/src/loadDynamicFont.d.ts +28 -0
- package/lib/typescript/src/loadDynamicFont.d.ts.map +1 -0
- package/lib/typescript/src/specs/NanoIconViewNativeComponent.d.ts +9 -8
- package/lib/typescript/src/specs/NanoIconViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/specs/NativeNanoIconsFontLoader.d.ts +8 -0
- package/lib/typescript/src/specs/NativeNanoIconsFontLoader.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +7 -1
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/utils/glyphRuntime.d.ts +18 -0
- package/lib/typescript/src/utils/glyphRuntime.d.ts.map +1 -0
- package/package.json +8 -5
- package/plugin/src/types.ts +9 -0
- package/plugin/src/withNanoIconsFontLinking.ts +29 -10
- package/react-native-nano-icons.podspec +1 -1
- package/scripts/cli.ts +31 -11
- package/src/const/codegenPrimitives.ts +14 -0
- package/src/core/font/compile.ts +1 -2
- package/src/core/pipeline/config.ts +1 -0
- package/src/core/pipeline/managers.ts +5 -10
- package/src/core/pipeline/run.ts +11 -6
- package/src/core/svg/layers.ts +4 -4
- package/src/core/svg/svg_dom.ts +26 -23
- package/src/core/svg/svg_pathops.ts +50 -24
- package/src/core/types.ts +10 -2
- package/src/createNanoIconsSet.native.tsx +78 -26
- package/src/createNanoIconsSet.shared.tsx +93 -40
- package/src/createNanoIconsSet.tsx +9 -1
- package/src/createNanoIconsSet.web.tsx +109 -0
- package/src/loadDynamicFont.ts +162 -0
- package/src/specs/NanoIconViewNativeComponent.ts +9 -8
- package/src/specs/NativeNanoIconsFontLoader.ts +11 -0
- package/src/types.ts +5 -1
- package/src/utils/glyphRuntime.ts +46 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { useSyncExternalStore } from 'react';
|
|
2
|
+
import { Image, Platform } from 'react-native';
|
|
3
|
+
import NanoIconsFontLoader from './specs/NativeNanoIconsFontLoader';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Runtime registration of dynamically-linked (`l:"d"`) fonts.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
type FontStatus = 'loading' | 'ready' | 'error';
|
|
10
|
+
|
|
11
|
+
const statusByFamily = new Map<string, FontStatus>();
|
|
12
|
+
const inFlight = new Map<string, Promise<void>>();
|
|
13
|
+
const listeners = new Set<() => void>();
|
|
14
|
+
|
|
15
|
+
function emit(): void {
|
|
16
|
+
for (const l of listeners) l();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function setStatus(family: string, status: FontStatus): void {
|
|
20
|
+
statusByFamily.set(family, status);
|
|
21
|
+
emit();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function getFontStatus(family: string): FontStatus | undefined {
|
|
25
|
+
return statusByFamily.get(family);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A font source: a require()'d module, an { uri }, or a path/uri string. */
|
|
29
|
+
export type FontSource = number | string | { uri: string };
|
|
30
|
+
|
|
31
|
+
export function resolveFontUri(font: unknown): string {
|
|
32
|
+
if (typeof font === 'number') {
|
|
33
|
+
const source = Image.resolveAssetSource(font);
|
|
34
|
+
if (!source?.uri) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
'[react-native-nano-icons] Could not resolve the font asset to a uri.'
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
return source.uri;
|
|
40
|
+
}
|
|
41
|
+
if (typeof font === 'string') return font;
|
|
42
|
+
if (
|
|
43
|
+
font != null &&
|
|
44
|
+
typeof font === 'object' &&
|
|
45
|
+
typeof (font as { uri?: unknown }).uri === 'string'
|
|
46
|
+
) {
|
|
47
|
+
return (font as { uri: string }).uri;
|
|
48
|
+
}
|
|
49
|
+
throw new Error(
|
|
50
|
+
'[react-native-nano-icons] Unsupported font source. Pass require("Foo.ttf"), { uri }, or a path string.'
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function register(family: string, uri: string): Promise<void> {
|
|
55
|
+
if (Platform.OS === 'web') {
|
|
56
|
+
// Browser FontFace API. Typed loosely so this compiles under both the RN-only
|
|
57
|
+
// lib config and a DOM-aware app config without depending on the DOM lib.
|
|
58
|
+
const g = globalThis as unknown as {
|
|
59
|
+
FontFace?: new (
|
|
60
|
+
family: string,
|
|
61
|
+
source: string
|
|
62
|
+
) => {
|
|
63
|
+
load(): Promise<unknown>;
|
|
64
|
+
};
|
|
65
|
+
document?: { fonts?: { add(face: unknown): void } };
|
|
66
|
+
};
|
|
67
|
+
if (!g.FontFace || !g.document?.fonts) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
'[react-native-nano-icons] FontFace API unavailable; cannot load dynamic font on this platform.'
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
const face = new g.FontFace(family, `url(${uri})`);
|
|
73
|
+
await face.load();
|
|
74
|
+
g.document.fonts.add(face);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!NanoIconsFontLoader) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
'[react-native-nano-icons] Native font loader is unavailable (Expo Go or module not built). ' +
|
|
81
|
+
'Load the font yourself, or use a development/production build.'
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
await NanoIconsFontLoader.registerFont(family, uri);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Register `font` under `family`. Concurrent calls share one in-flight load.
|
|
89
|
+
* Once settled, a later call retries after an error or no-ops if already
|
|
90
|
+
* registered — unless `opts.force` re-registers (used by the explicit `loadFont`).
|
|
91
|
+
*/
|
|
92
|
+
export function loadDynamicFont(
|
|
93
|
+
family: string,
|
|
94
|
+
font: unknown,
|
|
95
|
+
opts?: { force?: boolean }
|
|
96
|
+
): Promise<void> {
|
|
97
|
+
// Dedupe concurrent calls.
|
|
98
|
+
const existing = inFlight.get(family);
|
|
99
|
+
if (existing) return existing;
|
|
100
|
+
|
|
101
|
+
// Already registered → skip, unless the caller forces a reload.
|
|
102
|
+
if (!opts?.force && statusByFamily.get(family) === 'ready') {
|
|
103
|
+
return Promise.resolve();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
setStatus(family, 'loading');
|
|
107
|
+
|
|
108
|
+
const run = async (): Promise<void> => {
|
|
109
|
+
try {
|
|
110
|
+
await register(family, resolveFontUri(font));
|
|
111
|
+
setStatus(family, 'ready');
|
|
112
|
+
} catch (err) {
|
|
113
|
+
setStatus(family, 'error');
|
|
114
|
+
throw err;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const tracked = run();
|
|
119
|
+
|
|
120
|
+
// Release the slot once settled so later calls can retry/reload
|
|
121
|
+
inFlight.set(
|
|
122
|
+
family,
|
|
123
|
+
(async () => {
|
|
124
|
+
try {
|
|
125
|
+
await tracked;
|
|
126
|
+
} catch {
|
|
127
|
+
// status already reflects the failure
|
|
128
|
+
} finally {
|
|
129
|
+
inFlight.delete(family);
|
|
130
|
+
}
|
|
131
|
+
})()
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
return tracked;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Subscribe a component to a family's load state. Returns `true` while a managed
|
|
139
|
+
* font is still loading (so the icon should hide), `false` otherwise (ready,
|
|
140
|
+
* errored, or not managed by us).
|
|
141
|
+
*/
|
|
142
|
+
export function useDynamicFontPending(
|
|
143
|
+
managed: boolean,
|
|
144
|
+
family: string
|
|
145
|
+
): boolean {
|
|
146
|
+
const status = useSyncExternalStore(
|
|
147
|
+
(cb) => {
|
|
148
|
+
listeners.add(cb);
|
|
149
|
+
return () => listeners.delete(cb);
|
|
150
|
+
},
|
|
151
|
+
() => (managed ? statusByFamily.get(family) : undefined),
|
|
152
|
+
() => undefined
|
|
153
|
+
);
|
|
154
|
+
return managed && status === 'loading';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Test-only: reset module state between tests. */
|
|
158
|
+
export function __resetDynamicFontsForTests(): void {
|
|
159
|
+
statusByFamily.clear();
|
|
160
|
+
inFlight.clear();
|
|
161
|
+
listeners.clear();
|
|
162
|
+
}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { codegenNativeComponent } from 'react-native';
|
|
2
|
-
import type {
|
|
2
|
+
import type { ViewProps } from 'react-native';
|
|
3
|
+
import type { Float, Int32 } from '../const/codegenPrimitives';
|
|
3
4
|
|
|
4
5
|
export interface NativeProps extends ViewProps {
|
|
5
6
|
fontFamily: string;
|
|
6
|
-
codepoints: ReadonlyArray<
|
|
7
|
-
colors: ReadonlyArray<
|
|
8
|
-
fontSize:
|
|
9
|
-
advanceWidth:
|
|
10
|
-
unitsPerEm:
|
|
11
|
-
iconWidth:
|
|
12
|
-
iconHeight:
|
|
7
|
+
codepoints: ReadonlyArray<Int32>;
|
|
8
|
+
colors: ReadonlyArray<Int32>;
|
|
9
|
+
fontSize: Float;
|
|
10
|
+
advanceWidth: Int32;
|
|
11
|
+
unitsPerEm: Int32;
|
|
12
|
+
iconWidth: Float;
|
|
13
|
+
iconHeight: Float;
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
export default codegenNativeComponent<NativeProps>('NanoIconView');
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TurboModule } from 'react-native';
|
|
2
|
+
import { TurboModuleRegistry } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export interface Spec extends TurboModule {
|
|
5
|
+
/** Pass glyphMap.m.f as `family`. On iOS it must also match the TTF's embedded PostScript/full name. */
|
|
6
|
+
registerFont(family: string, uri: string): Promise<boolean>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// `get` (not `getEnforcing`) so the absence of the native module (web, Expo Go,
|
|
10
|
+
// module not built) returns null and the caller can degrade gracefully.
|
|
11
|
+
export default TurboModuleRegistry.get<Spec>('NanoIconsFontLoader');
|
package/src/types.ts
CHANGED
|
@@ -18,10 +18,14 @@ export type IconProps<Name> = {
|
|
|
18
18
|
accessible?: boolean;
|
|
19
19
|
accessibilityLabel?: string;
|
|
20
20
|
accessibilityRole?: AccessibilityRole;
|
|
21
|
+
accessibilityElementsHidden?: boolean; // iOS
|
|
22
|
+
importantForAccessibility?: 'auto' | 'yes' | 'no' | 'no-hide-descendants'; // Android
|
|
21
23
|
ref?: Ref<ViewRef>;
|
|
22
24
|
testID?: string;
|
|
23
25
|
};
|
|
24
26
|
|
|
25
27
|
export type IconComponent<GM extends NanoGlyphMapInput> = React.FC<
|
|
26
28
|
IconProps<keyof GM['i']>
|
|
27
|
-
|
|
29
|
+
> & {
|
|
30
|
+
loadFont: (font?: number | string | { uri: string }) => Promise<void>;
|
|
31
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { ColorValue } from 'react-native';
|
|
2
|
+
import type { NanoGlyphMapInput, GlyphEntry } from '../core/types';
|
|
3
|
+
|
|
4
|
+
/** Default icon size (px) shared by every platform renderer. */
|
|
5
|
+
export const DEFAULT_ICON_SIZE = 12;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Look up a glyph entry by name, falling back to a single '?' layer
|
|
9
|
+
* (codepoint 63) sized to the font's em when the name is unknown.
|
|
10
|
+
*/
|
|
11
|
+
export function resolveGlyphEntry<GM extends NanoGlyphMapInput>(
|
|
12
|
+
glyphMap: GM,
|
|
13
|
+
name: keyof GM['i']
|
|
14
|
+
): GlyphEntry {
|
|
15
|
+
return (glyphMap.i[name as string] ?? [
|
|
16
|
+
glyphMap.m.u,
|
|
17
|
+
[[63, 'black']],
|
|
18
|
+
]) as GlyphEntry;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** A memoized codepoint -> string converter, scoped per icon set. */
|
|
22
|
+
export function createCharCache(): (codepoint: number) => string {
|
|
23
|
+
const cache = new Map<number, string>();
|
|
24
|
+
return (codepoint) => {
|
|
25
|
+
let ch = cache.get(codepoint);
|
|
26
|
+
if (ch === undefined) {
|
|
27
|
+
ch = String.fromCodePoint(codepoint);
|
|
28
|
+
cache.set(codepoint, ch);
|
|
29
|
+
}
|
|
30
|
+
return ch;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Build a per-layer color resolver for one render: an explicit per-index
|
|
36
|
+
* color wins, else the last supplied palette color spills onto remaining
|
|
37
|
+
* layers, else the glyph's own source color, else black.
|
|
38
|
+
*/
|
|
39
|
+
export function createLayerColorResolver(
|
|
40
|
+
color: ColorValue | ColorValue[] | undefined
|
|
41
|
+
): (index: number, srcColor: string | undefined) => ColorValue {
|
|
42
|
+
const colorArray = Array.isArray(color) ? color : [color];
|
|
43
|
+
const lastPaletteColor = colorArray[colorArray.length - 1];
|
|
44
|
+
return (index, srcColor) =>
|
|
45
|
+
colorArray[index] ?? lastPaletteColor ?? srcColor ?? 'black';
|
|
46
|
+
}
|