react-native-nano-icons 0.1.8 → 0.2.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 (116) hide show
  1. package/README.md +64 -2
  2. package/android/src/main/java/com/nanoicons/NanoIconsFontLoaderModule.kt +58 -0
  3. package/android/src/main/java/com/nanoicons/NanoIconsPackage.kt +19 -2
  4. package/ios/NanoIconView.h +5 -0
  5. package/ios/NanoIconView.mm +35 -9
  6. package/ios/NanoIconsFontLoader.h +11 -0
  7. package/ios/NanoIconsFontLoader.mm +110 -0
  8. package/lib/commonjs/cli/build.d.ts +3 -0
  9. package/lib/commonjs/cli/build.js +8 -4
  10. package/lib/commonjs/cli/config.d.ts +1 -0
  11. package/lib/commonjs/cli/config.js +10 -0
  12. package/lib/commonjs/cli/expoConfig.d.ts +8 -0
  13. package/lib/commonjs/cli/expoConfig.js +34 -0
  14. package/lib/commonjs/cli/index.d.ts +2 -1
  15. package/lib/commonjs/cli/index.js +4 -1
  16. package/lib/commonjs/cli/link.js +32 -22
  17. package/lib/commonjs/plugin/src/types.d.ts +9 -0
  18. package/lib/commonjs/plugin/src/withNanoIconsFontLinking.d.ts +5 -0
  19. package/lib/commonjs/plugin/src/withNanoIconsFontLinking.js +24 -10
  20. package/lib/commonjs/scripts/cli.js +23 -11
  21. package/lib/commonjs/src/core/pipeline/config.d.ts +1 -0
  22. package/lib/commonjs/src/core/pipeline/managers.js +1 -2
  23. package/lib/commonjs/src/core/pipeline/run.js +1 -0
  24. package/lib/commonjs/src/core/svg/svg_dom.d.ts +1 -0
  25. package/lib/commonjs/src/core/svg/svg_dom.js +22 -17
  26. package/lib/commonjs/src/core/svg/svg_pathops.js +30 -16
  27. package/lib/commonjs/src/core/types.d.ts +3 -0
  28. package/lib/module/const/codegenPrimitives.js +2 -0
  29. package/lib/module/const/codegenPrimitives.js.map +1 -0
  30. package/lib/module/core/font/compile.js.map +1 -1
  31. package/lib/module/core/pipeline/config.js.map +1 -1
  32. package/lib/module/core/pipeline/managers.js.map +1 -1
  33. package/lib/module/core/pipeline/run.js +4 -1
  34. package/lib/module/core/pipeline/run.js.map +1 -1
  35. package/lib/module/core/svg/layers.js.map +1 -1
  36. package/lib/module/core/svg/svg_dom.js +19 -19
  37. package/lib/module/core/svg/svg_dom.js.map +1 -1
  38. package/lib/module/core/svg/svg_pathops.js.map +1 -1
  39. package/lib/module/createNanoIconsSet.js +2 -2
  40. package/lib/module/createNanoIconsSet.js.map +1 -1
  41. package/lib/module/createNanoIconsSet.native.js +43 -16
  42. package/lib/module/createNanoIconsSet.native.js.map +1 -1
  43. package/lib/module/createNanoIconsSet.shared.js +49 -20
  44. package/lib/module/createNanoIconsSet.shared.js.map +1 -1
  45. package/lib/module/createNanoIconsSet.web.js +78 -0
  46. package/lib/module/createNanoIconsSet.web.js.map +1 -0
  47. package/lib/module/loadDynamicFont.js +118 -0
  48. package/lib/module/loadDynamicFont.js.map +1 -0
  49. package/lib/module/specs/NanoIconViewNativeComponent.ts +9 -8
  50. package/lib/module/specs/NativeNanoIconsFontLoader.js +7 -0
  51. package/lib/module/specs/NativeNanoIconsFontLoader.js.map +1 -0
  52. package/lib/module/utils/glyphRuntime.js +37 -0
  53. package/lib/module/utils/glyphRuntime.js.map +1 -0
  54. package/lib/typescript/__tests__/glyphRuntime.unit.test.d.ts +2 -0
  55. package/lib/typescript/__tests__/glyphRuntime.unit.test.d.ts.map +1 -0
  56. package/lib/typescript/__tests__/link.unit.test.d.ts +3 -0
  57. package/lib/typescript/__tests__/link.unit.test.d.ts.map +1 -0
  58. package/lib/typescript/__tests__/loadDynamicFont.unit.test.d.ts +2 -0
  59. package/lib/typescript/__tests__/loadDynamicFont.unit.test.d.ts.map +1 -0
  60. package/lib/typescript/cli/build.d.ts +3 -0
  61. package/lib/typescript/cli/build.d.ts.map +1 -1
  62. package/lib/typescript/cli/link.d.ts +12 -0
  63. package/lib/typescript/cli/link.d.ts.map +1 -0
  64. package/lib/typescript/src/const/codegenPrimitives.d.ts +3 -0
  65. package/lib/typescript/src/const/codegenPrimitives.d.ts.map +1 -0
  66. package/lib/typescript/src/core/font/compile.d.ts.map +1 -1
  67. package/lib/typescript/src/core/pipeline/config.d.ts +1 -0
  68. package/lib/typescript/src/core/pipeline/config.d.ts.map +1 -1
  69. package/lib/typescript/src/core/pipeline/managers.d.ts.map +1 -1
  70. package/lib/typescript/src/core/pipeline/run.d.ts.map +1 -1
  71. package/lib/typescript/src/core/svg/svg_dom.d.ts +1 -0
  72. package/lib/typescript/src/core/svg/svg_dom.d.ts.map +1 -1
  73. package/lib/typescript/src/core/svg/svg_pathops.d.ts.map +1 -1
  74. package/lib/typescript/src/core/types.d.ts +3 -0
  75. package/lib/typescript/src/core/types.d.ts.map +1 -1
  76. package/lib/typescript/src/createNanoIconsSet.d.ts +1 -0
  77. package/lib/typescript/src/createNanoIconsSet.d.ts.map +1 -1
  78. package/lib/typescript/src/createNanoIconsSet.native.d.ts +1 -0
  79. package/lib/typescript/src/createNanoIconsSet.native.d.ts.map +1 -1
  80. package/lib/typescript/src/createNanoIconsSet.shared.d.ts +11 -0
  81. package/lib/typescript/src/createNanoIconsSet.shared.d.ts.map +1 -1
  82. package/lib/typescript/src/createNanoIconsSet.web.d.ts +7 -0
  83. package/lib/typescript/src/createNanoIconsSet.web.d.ts.map +1 -0
  84. package/lib/typescript/src/loadDynamicFont.d.ts +28 -0
  85. package/lib/typescript/src/loadDynamicFont.d.ts.map +1 -0
  86. package/lib/typescript/src/specs/NanoIconViewNativeComponent.d.ts +9 -8
  87. package/lib/typescript/src/specs/NanoIconViewNativeComponent.d.ts.map +1 -1
  88. package/lib/typescript/src/specs/NativeNanoIconsFontLoader.d.ts +8 -0
  89. package/lib/typescript/src/specs/NativeNanoIconsFontLoader.d.ts.map +1 -0
  90. package/lib/typescript/src/types.d.ts +7 -1
  91. package/lib/typescript/src/types.d.ts.map +1 -1
  92. package/lib/typescript/src/utils/glyphRuntime.d.ts +18 -0
  93. package/lib/typescript/src/utils/glyphRuntime.d.ts.map +1 -0
  94. package/package.json +8 -5
  95. package/plugin/src/types.ts +9 -0
  96. package/plugin/src/withNanoIconsFontLinking.ts +29 -10
  97. package/react-native-nano-icons.podspec +1 -1
  98. package/scripts/cli.ts +31 -11
  99. package/src/const/codegenPrimitives.ts +14 -0
  100. package/src/core/font/compile.ts +1 -2
  101. package/src/core/pipeline/config.ts +1 -0
  102. package/src/core/pipeline/managers.ts +5 -10
  103. package/src/core/pipeline/run.ts +11 -6
  104. package/src/core/svg/layers.ts +4 -4
  105. package/src/core/svg/svg_dom.ts +26 -23
  106. package/src/core/svg/svg_pathops.ts +50 -24
  107. package/src/core/types.ts +10 -2
  108. package/src/createNanoIconsSet.native.tsx +78 -26
  109. package/src/createNanoIconsSet.shared.tsx +93 -40
  110. package/src/createNanoIconsSet.tsx +9 -1
  111. package/src/createNanoIconsSet.web.tsx +109 -0
  112. package/src/loadDynamicFont.ts +162 -0
  113. package/src/specs/NanoIconViewNativeComponent.ts +9 -8
  114. package/src/specs/NativeNanoIconsFontLoader.ts +11 -0
  115. package/src/types.ts +5 -1
  116. package/src/utils/glyphRuntime.ts +46 -0
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Runtime registration of dynamically-linked (`l:"d"`) fonts.
3
+ */
4
+ type FontStatus = 'loading' | 'ready' | 'error';
5
+ export declare function getFontStatus(family: string): FontStatus | undefined;
6
+ /** A font source: a require()'d module, an { uri }, or a path/uri string. */
7
+ export type FontSource = number | string | {
8
+ uri: string;
9
+ };
10
+ export declare function resolveFontUri(font: unknown): string;
11
+ /**
12
+ * Register `font` under `family`. Concurrent calls share one in-flight load.
13
+ * Once settled, a later call retries after an error or no-ops if already
14
+ * registered — unless `opts.force` re-registers (used by the explicit `loadFont`).
15
+ */
16
+ export declare function loadDynamicFont(family: string, font: unknown, opts?: {
17
+ force?: boolean;
18
+ }): Promise<void>;
19
+ /**
20
+ * Subscribe a component to a family's load state. Returns `true` while a managed
21
+ * font is still loading (so the icon should hide), `false` otherwise (ready,
22
+ * errored, or not managed by us).
23
+ */
24
+ export declare function useDynamicFontPending(managed: boolean, family: string): boolean;
25
+ /** Test-only: reset module state between tests. */
26
+ export declare function __resetDynamicFontsForTests(): void;
27
+ export {};
28
+ //# sourceMappingURL=loadDynamicFont.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loadDynamicFont.d.ts","sourceRoot":"","sources":["../../../src/loadDynamicFont.ts"],"names":[],"mappings":"AAIA;;GAEG;AAEH,KAAK,UAAU,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;AAehD,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAEpE;AAED,6EAA6E;AAC7E,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3D,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAqBpD;AAmCD;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,EACb,IAAI,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GACzB,OAAO,CAAC,IAAI,CAAC,CAuCf;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,GACb,OAAO,CAUT;AAED,mDAAmD;AACnD,wBAAgB,2BAA2B,IAAI,IAAI,CAIlD"}
@@ -1,13 +1,14 @@
1
- import type { CodegenTypes as CT, ViewProps } from 'react-native';
1
+ import type { ViewProps } from 'react-native';
2
+ import type { Float, Int32 } from '../const/codegenPrimitives';
2
3
  export interface NativeProps extends ViewProps {
3
4
  fontFamily: string;
4
- codepoints: ReadonlyArray<CT.Int32>;
5
- colors: ReadonlyArray<CT.Int32>;
6
- fontSize: CT.Float;
7
- advanceWidth: CT.Int32;
8
- unitsPerEm: CT.Int32;
9
- iconWidth: CT.Float;
10
- iconHeight: CT.Float;
5
+ codepoints: ReadonlyArray<Int32>;
6
+ colors: ReadonlyArray<Int32>;
7
+ fontSize: Float;
8
+ advanceWidth: Int32;
9
+ unitsPerEm: Int32;
10
+ iconWidth: Float;
11
+ iconHeight: Float;
11
12
  }
12
13
  declare const _default: import("react-native/types_generated/Libraries/Utilities/codegenNativeComponent").NativeComponentType<NativeProps>;
13
14
  export default _default;
@@ -1 +1 @@
1
- {"version":3,"file":"NanoIconViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../../src/specs/NanoIconViewNativeComponent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,IAAI,EAAE,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAElE,MAAM,WAAW,WAAY,SAAQ,SAAS;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,aAAa,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IACpC,MAAM,EAAE,aAAa,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IAChC,QAAQ,EAAE,EAAE,CAAC,KAAK,CAAC;IACnB,YAAY,EAAE,EAAE,CAAC,KAAK,CAAC;IACvB,UAAU,EAAE,EAAE,CAAC,KAAK,CAAC;IACrB,SAAS,EAAE,EAAE,CAAC,KAAK,CAAC;IACpB,UAAU,EAAE,EAAE,CAAC,KAAK,CAAC;CACtB;;AAED,wBAAmE"}
1
+ {"version":3,"file":"NanoIconViewNativeComponent.d.ts","sourceRoot":"","sources":["../../../../src/specs/NanoIconViewNativeComponent.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,4BAA4B,CAAC;AAE/D,MAAM,WAAW,WAAY,SAAQ,SAAS;IAC5C,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC;IACjC,MAAM,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC;IAC7B,QAAQ,EAAE,KAAK,CAAC;IAChB,YAAY,EAAE,KAAK,CAAC;IACpB,UAAU,EAAE,KAAK,CAAC;IAClB,SAAS,EAAE,KAAK,CAAC;IACjB,UAAU,EAAE,KAAK,CAAC;CACnB;;AAED,wBAAmE"}
@@ -0,0 +1,8 @@
1
+ import type { TurboModule } from 'react-native';
2
+ export interface Spec extends TurboModule {
3
+ /** Pass glyphMap.m.f as `family`. On iOS it must also match the TTF's embedded PostScript/full name. */
4
+ registerFont(family: string, uri: string): Promise<boolean>;
5
+ }
6
+ declare const _default: Spec | null | undefined;
7
+ export default _default;
8
+ //# sourceMappingURL=NativeNanoIconsFontLoader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NativeNanoIconsFontLoader.d.ts","sourceRoot":"","sources":["../../../../src/specs/NativeNanoIconsFontLoader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAGhD,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,wGAAwG;IACxG,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC7D;;AAID,wBAAoE"}
@@ -11,9 +11,15 @@ export type IconProps<Name> = {
11
11
  accessible?: boolean;
12
12
  accessibilityLabel?: string;
13
13
  accessibilityRole?: AccessibilityRole;
14
+ accessibilityElementsHidden?: boolean;
15
+ importantForAccessibility?: 'auto' | 'yes' | 'no' | 'no-hide-descendants';
14
16
  ref?: Ref<ViewRef>;
15
17
  testID?: string;
16
18
  };
17
- export type IconComponent<GM extends NanoGlyphMapInput> = React.FC<IconProps<keyof GM['i']>>;
19
+ export type IconComponent<GM extends NanoGlyphMapInput> = React.FC<IconProps<keyof GM['i']>> & {
20
+ loadFont: (font?: number | string | {
21
+ uri: string;
22
+ }) => Promise<void>;
23
+ };
18
24
  export {};
19
25
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC/C,OAAO,KAAK,EACV,iBAAiB,EACjB,UAAU,EACV,IAAI,EACJ,SAAS,EACV,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEtD,KAAK,OAAO,GAAG,YAAY,CAAC,OAAO,IAAI,CAAC,CAAC;AAEzC,MAAM,MAAM,SAAS,CAAC,IAAI,IAAI;IAC5B,IAAI,EAAE,IAAI,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,UAAU,GAAG,UAAU,EAAE,CAAC;IAClC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,GAAG,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,aAAa,CAAC,EAAE,SAAS,iBAAiB,IAAI,KAAK,CAAC,EAAE,CAChE,SAAS,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CACzB,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC/C,OAAO,KAAK,EACV,iBAAiB,EACjB,UAAU,EACV,IAAI,EACJ,SAAS,EACV,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEtD,KAAK,OAAO,GAAG,YAAY,CAAC,OAAO,IAAI,CAAC,CAAC;AAEzC,MAAM,MAAM,SAAS,CAAC,IAAI,IAAI;IAC5B,IAAI,EAAE,IAAI,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,UAAU,GAAG,UAAU,EAAE,CAAC;IAClC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,2BAA2B,CAAC,EAAE,OAAO,CAAC;IACtC,yBAAyB,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI,GAAG,qBAAqB,CAAC;IAC1E,GAAG,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,aAAa,CAAC,EAAE,SAAS,iBAAiB,IAAI,KAAK,CAAC,EAAE,CAChE,SAAS,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CACzB,GAAG;IACF,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { ColorValue } from 'react-native';
2
+ import type { NanoGlyphMapInput, GlyphEntry } from '../core/types';
3
+ /** Default icon size (px) shared by every platform renderer. */
4
+ export declare const DEFAULT_ICON_SIZE = 12;
5
+ /**
6
+ * Look up a glyph entry by name, falling back to a single '?' layer
7
+ * (codepoint 63) sized to the font's em when the name is unknown.
8
+ */
9
+ export declare function resolveGlyphEntry<GM extends NanoGlyphMapInput>(glyphMap: GM, name: keyof GM['i']): GlyphEntry;
10
+ /** A memoized codepoint -> string converter, scoped per icon set. */
11
+ export declare function createCharCache(): (codepoint: number) => string;
12
+ /**
13
+ * Build a per-layer color resolver for one render: an explicit per-index
14
+ * color wins, else the last supplied palette color spills onto remaining
15
+ * layers, else the glyph's own source color, else black.
16
+ */
17
+ export declare function createLayerColorResolver(color: ColorValue | ColorValue[] | undefined): (index: number, srcColor: string | undefined) => ColorValue;
18
+ //# sourceMappingURL=glyphRuntime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glyphRuntime.d.ts","sourceRoot":"","sources":["../../../../src/utils/glyphRuntime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAEnE,gEAAgE;AAChE,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,SAAS,iBAAiB,EAC5D,QAAQ,EAAE,EAAE,EACZ,IAAI,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,GAClB,UAAU,CAKZ;AAED,qEAAqE;AACrE,wBAAgB,eAAe,IAAI,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAU/D;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,UAAU,GAAG,UAAU,EAAE,GAAG,SAAS,GAC3C,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,KAAK,UAAU,CAK7D"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "react-native-nano-icons",
3
- "version": "0.1.8",
4
- "description": "Use any svg as font. High-performance, build-time icon font generation and rendering for React Native & Expo.",
3
+ "version": "0.2.0",
4
+ "description": "High-performance icon rendering for React Native & Expo.",
5
5
  "react-native": "src/index.ts",
6
6
  "source": "src/index.ts",
7
7
  "bin": "lib/commonjs/scripts/cli.js",
@@ -37,23 +37,26 @@
37
37
  },
38
38
  "dependencies": {
39
39
  "@expo/config-plugins": ">=9.0.0",
40
+ "@xmldom/xmldom": "^0.9.10",
40
41
  "chalk": "^5.6.2",
41
42
  "fonteditor-core": "^2.6.3",
42
- "jsdom": "^28.1.0",
43
43
  "ora": "^9.3.0",
44
44
  "pathkit-wasm": "^1.0.0",
45
45
  "plist": "^3.0.5",
46
46
  "pyodide": "^0.29.3",
47
47
  "svg2ttf": "^6.0.3",
48
- "svgicons2svgfont": "^15.0.1",
49
48
  "xcode": "^3.0.1"
50
49
  },
51
50
  "peerDependencies": {
51
+ "@expo/config": ">=9.0.0",
52
52
  "expo": "*",
53
53
  "react": "*",
54
54
  "react-native": "*"
55
55
  },
56
56
  "peerDependenciesMeta": {
57
+ "@expo/config": {
58
+ "optional": true
59
+ },
57
60
  "@expo/config-plugins": {
58
61
  "optional": true
59
62
  },
@@ -164,7 +167,7 @@
164
167
  },
165
168
  "codegenConfig": {
166
169
  "name": "RNNanoIconsSpec",
167
- "type": "components",
170
+ "type": "all",
168
171
  "jsSrcsDir": "./src/specs",
169
172
  "android": {
170
173
  "javaPackageName": "com.nanoicons"
@@ -14,6 +14,14 @@ export interface IconSetConfig {
14
14
  safeZone?: number;
15
15
  /** First Unicode codepoint for glyphs (default 0xe900). Hex string or number. */
16
16
  startUnicode?: number | string;
17
+ /**
18
+ * Delivery mode for the generated TTF. Defaults to `'static'`.
19
+ *
20
+ * - `'static'`: TTF is bundled into the native app.
21
+ * - `'dynamic'`: TTF is excluded from native bundling - the host app is responsible for
22
+ * delivering it (e.g. via OTA) and registering it under the same font family name.
23
+ */
24
+ linking?: 'static' | 'dynamic';
17
25
  }
18
26
 
19
27
  /**
@@ -30,4 +38,5 @@ export interface BuiltFont {
30
38
  fontFamily: string;
31
39
  ttfPath: string;
32
40
  glyphmapPath: string;
41
+ linking: 'static' | 'dynamic';
33
42
  }
@@ -11,9 +11,15 @@ import { getOrBuildFonts } from './buildFonts.js';
11
11
  import type { IconSetConfig } from './types.js';
12
12
 
13
13
  const ANDROID_ASSETS_FONTS_DIR = 'app/src/main/assets/fonts';
14
+ const IOS_FONTS_GROUP = 'Resources';
14
15
 
15
16
  /**
16
17
  * Add TTFs to the iOS project (Resources group + UIAppFonts in Info.plist).
18
+ *
19
+ * Copies each .ttf into ios/<projectName>/Resources/ on every prebuild so that
20
+ * Xcode's incremental build reliably picks up updated glyph data. Referencing
21
+ * the .ttf via a relative path outside ios/ leaves stale fonts in the .app
22
+ * bundle when only the file contents change.
17
23
  */
18
24
  export function withNanoIconsIos(
19
25
  config: Parameters<typeof withXcodeProject>[0],
@@ -24,16 +30,27 @@ export function withNanoIconsIos(
24
30
  config.modRequest.projectRoot,
25
31
  iconSets
26
32
  );
27
- if (!built?.length) return config;
28
- const ttfPaths = built.map((b) => b.ttfPath);
33
+ const bundled = built?.filter((b) => b.linking !== 'dynamic') ?? [];
34
+ if (!bundled.length) return config;
29
35
  const project = config.modResults;
30
36
  const platformProjectRoot = config.modRequest.platformProjectRoot;
31
- IOSConfig.XcodeUtils.ensureGroupRecursively(project, 'Resources');
32
- for (const fontPath of ttfPaths) {
33
- const relativePath = path.relative(platformProjectRoot, fontPath);
37
+ const projectName =
38
+ config.modRequest.projectName ??
39
+ IOSConfig.XcodeUtils.getProjectName(config.modRequest.projectRoot);
40
+ const fontsDir = path.join(
41
+ platformProjectRoot,
42
+ projectName,
43
+ IOS_FONTS_GROUP
44
+ );
45
+ await fs.mkdir(fontsDir, { recursive: true });
46
+ IOSConfig.XcodeUtils.ensureGroupRecursively(project, IOS_FONTS_GROUP);
47
+ for (const { ttfPath } of bundled) {
48
+ const dest = path.join(fontsDir, path.basename(ttfPath));
49
+ await fs.copyFile(ttfPath, dest);
50
+ const relativePath = path.relative(platformProjectRoot, dest);
34
51
  IOSConfig.XcodeUtils.addResourceFileToGroup({
35
52
  filepath: relativePath,
36
- groupName: 'Resources',
53
+ groupName: IOS_FONTS_GROUP,
37
54
  project,
38
55
  isBuildFile: true,
39
56
  verbose: true,
@@ -49,8 +66,9 @@ export function withNanoIconsIos(
49
66
  config.modRequest.projectRoot,
50
67
  iconSets
51
68
  );
52
- if (!built?.length) return config;
53
- const ttfPaths = built.map((b) => b.ttfPath);
69
+ const bundled = built?.filter((b) => b.linking !== 'dynamic') ?? [];
70
+ if (!bundled.length) return config;
71
+ const ttfPaths = bundled.map((b) => b.ttfPath);
54
72
  const existingFonts = getUIAppFonts(config.modResults);
55
73
  const fontList = ttfPaths.map((f) => path.basename(f));
56
74
  const allFonts = [...existingFonts, ...fontList];
@@ -88,13 +106,14 @@ export function withNanoIconsAndroid(
88
106
  config.modRequest.projectRoot,
89
107
  iconSets
90
108
  );
91
- if (!built?.length) return config;
109
+ const bundled = built?.filter((b) => b.linking !== 'dynamic') ?? [];
110
+ if (!bundled.length) return config;
92
111
  const fontsDir = path.join(
93
112
  config.modRequest.platformProjectRoot,
94
113
  ANDROID_ASSETS_FONTS_DIR
95
114
  );
96
115
  await fs.mkdir(fontsDir, { recursive: true });
97
- for (const b of built) {
116
+ for (const b of bundled) {
98
117
  const filename = path.basename(b.ttfPath);
99
118
  const dest = path.join(fontsDir, filename);
100
119
  await fs.copyFile(b.ttfPath, dest);
@@ -10,7 +10,7 @@ Pod::Spec.new do |s|
10
10
  s.license = package["license"]
11
11
  s.author = package["author"]
12
12
  s.source = { :git => package["repository"], :tag => "#{s.version}" }
13
- s.platforms = { :ios => "15.1" }
13
+ s.platforms = { :ios => "15.1", :tvos => "15.1" }
14
14
  s.source_files = "ios/**/*.{h,m,mm,cpp}"
15
15
  s.requires_arc = true
16
16
 
package/scripts/cli.ts CHANGED
@@ -1,26 +1,29 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Bare React Native workflow: build icon fonts and link them into the native project.
4
- *
5
- * Run from your app root: npx react-native-nano-icons [--verbose] [--path <dir>]
6
- *
7
- * Reads .nanoicons.json (same shape as Expo plugin options) so Expo and bare apps
8
- * share one config format.
3
+ * Run from your app root: npx react-native-nano-icons [--verbose] [--path <dir>] [--dynamic] [--app-config]
9
4
  *
10
5
  * Flags:
11
- * --verbose Show per-SVG processing details and pipeline timing
12
- * --path <dir> Directory containing .nanoicons.json (default: cwd)
6
+ * --verbose Show per-SVG processing details and pipeline timing
7
+ * --path <dir> Directory containing .nanoicons.json (default: cwd)
8
+ * --dynamic Rebuild only icon sets with linking: 'dynamic'. Skips native linking —
9
+ * use this for OTA font regeneration without running expo prebuild.
10
+ * --app-config Read config from Expo app config (app.json / app.config.js / app.config.ts)
11
+ * instead of .nanoicons.json. Must be combined with --dynamic.
13
12
  */
14
13
  import path from 'node:path';
15
14
  import {
16
15
  createOraLogger,
17
16
  loadNanoIconsConfig,
17
+ loadDynamicIconSets,
18
+ loadDynamicSetsFromAppConfig,
18
19
  buildAllFonts,
19
20
  linkBare,
20
21
  } from '../cli/index.js';
21
22
 
22
23
  async function main(): Promise<void> {
23
24
  const verbose = process.argv.includes('--verbose');
25
+ const dynamic = process.argv.includes('--dynamic');
26
+ const appConfig = process.argv.includes('--app-config');
24
27
  const level = verbose ? 'verbose' : 'normal';
25
28
 
26
29
  const pathIdx = process.argv.indexOf('--path');
@@ -31,9 +34,26 @@ async function main(): Promise<void> {
31
34
  : projectRoot;
32
35
 
33
36
  const logger = await createOraLogger(level);
34
- const config = loadNanoIconsConfig(configRoot);
35
- const built = await buildAllFonts(config.iconSets, projectRoot, { logger });
36
- await linkBare(projectRoot, built, logger);
37
+
38
+ if (dynamic) {
39
+ const source = appConfig ? 'Expo app config' : '.nanoicons.json';
40
+ logger.start(`Reading dynamic icon sets from ${source}...`);
41
+
42
+ const dynamicIconSets = appConfig
43
+ ? loadDynamicSetsFromAppConfig(projectRoot)
44
+ : loadDynamicIconSets(configRoot);
45
+
46
+ logger.succeed(
47
+ `Found ${dynamicIconSets.length} dynamic icon set(s) — skipping native linking.`
48
+ );
49
+
50
+ await buildAllFonts(dynamicIconSets, projectRoot, { logger });
51
+ } else {
52
+ const config = loadNanoIconsConfig(configRoot);
53
+ const built = await buildAllFonts(config.iconSets, projectRoot, { logger });
54
+
55
+ await linkBare(projectRoot, built, logger);
56
+ }
37
57
  }
38
58
 
39
59
  main().catch((err: unknown) => {
@@ -0,0 +1,14 @@
1
+ // Local numeric aliases for RN codegen TS spec parsing.
2
+ //
3
+ // RN 0.79 and lower's codegen resolves any TSTypeAliasDeclaration found in the spec
4
+ // file back to its original RHS before the array-element handler runs — so a
5
+ // local `type Int32 = CodegenTypes.Int32` still presents as a TSQualifiedName
6
+ // inside ReadonlyArray<> and trips "Unknown prop type ... TSTypeReference".
7
+ // Imported identifiers are NOT tracked in the codegen's known-types map, so
8
+ // importing `Int32` / `Float` from here leaves a bare identifier that matches
9
+ // the primitive-type branch (`case 'Int32'` / `case 'Float'`).
10
+ //
11
+ // These are pure number aliases at runtime; RN codegen only cares about the
12
+ // identifier name.
13
+ export type Int32 = number;
14
+ export type Float = number;
@@ -79,8 +79,7 @@ export async function compileTtfFromGlyphs(opts: {
79
79
  const { glyphs, outTtfPath, fontName, upm, ascent, descent } = opts;
80
80
  const lineGap = opts.lineGap ?? 0;
81
81
 
82
- if (glyphs.length === 0)
83
- throw new Error('No glyphs to compile');
82
+ if (glyphs.length === 0) throw new Error('No glyphs to compile');
84
83
 
85
84
  const svgFontString = buildSvgFontXml({
86
85
  fontName,
@@ -5,6 +5,7 @@ export type PipelineConfig = {
5
5
  upm: number;
6
6
  safeZone: number;
7
7
  startUnicode: number;
8
+ linking: 'static' | 'dynamic';
8
9
  };
9
10
 
10
11
  export type PipelinePaths = {
@@ -22,9 +22,8 @@ export class PathKitManager {
22
22
  const PathKitInit = require('pathkit-wasm/bin/pathkit.js') as (
23
23
  opts: unknown
24
24
  ) => any;
25
- const pathkitJsPath = require.resolve(
26
- 'pathkit-wasm/bin/pathkit.js'
27
- ) as string;
25
+ const pathkitJsPath =
26
+ require.resolve('pathkit-wasm/bin/pathkit.js') as string;
28
27
  const pathkitBinDir = path.dirname(pathkitJsPath);
29
28
  const pathkitWasmPath = path.join(pathkitBinDir, 'pathkit.wasm');
30
29
 
@@ -74,14 +73,10 @@ export class PyodideManager {
74
73
  await py.loadPackage(['micropip', 'lxml'], { messageCallback: () => {} });
75
74
 
76
75
  // Resolve local picosvg wheel path for offline-first installation.
77
- const picosvgWhlDir = path.join(
78
- getPackageRoot(),
79
- 'src',
80
- 'core',
81
- 'shims'
76
+ const picosvgWhlDir = path.join(getPackageRoot(), 'src', 'core', 'shims');
77
+ const picosvgWhl = (await fs.readdir(picosvgWhlDir)).find(
78
+ (f) => f.startsWith('picosvg-') && f.endsWith('.whl')
82
79
  );
83
- const picosvgWhl = (await fs.readdir(picosvgWhlDir))
84
- .find((f) => f.startsWith('picosvg-') && f.endsWith('.whl'));
85
80
  const localWhlUrl = picosvgWhl
86
81
  ? `file://${path.join(picosvgWhlDir, picosvgWhl)}`
87
82
  : null;
@@ -23,10 +23,7 @@ import {
23
23
  } from '../svg/svg_dom.js';
24
24
  import { computePlacement, transformPathForFont } from '../svg/layers.js';
25
25
  import { convertEvenoddToWinding } from '../svg/svg_pathops.js';
26
- import type {
27
- GlyphLayer,
28
- NanoGlyphMap,
29
- } from '../types.js';
26
+ import type { GlyphLayer, NanoGlyphMap } from '../types.js';
30
27
  import type { NanoLogger } from '../types.js';
31
28
 
32
29
  export type PipelineResult = {
@@ -38,7 +35,12 @@ export type PipelineResult = {
38
35
  // Same-color path merging
39
36
  // ---------------------------------------------------------------------------
40
37
 
41
- type ParsedPath = { d: string; fill: string | null; fillRule?: 'evenodd'; noMerge?: boolean };
38
+ type ParsedPath = {
39
+ d: string;
40
+ fill: string | null;
41
+ fillRule?: 'evenodd';
42
+ noMerge?: boolean;
43
+ };
42
44
 
43
45
  /**
44
46
  * Concatenate multiple SVG path `d` strings into a single compound path.
@@ -132,6 +134,7 @@ export async function runPipeline(
132
134
  u: config.upm,
133
135
  z: config.safeZone,
134
136
  s: config.startUnicode,
137
+ ...(config.linking === 'dynamic' ? { l: 'd' as const } : {}),
135
138
  },
136
139
  i: {},
137
140
  };
@@ -184,7 +187,9 @@ export async function runPipeline(
184
187
  }
185
188
  for (const p of parsed.paths) {
186
189
  if (p.fillRule === 'evenodd') {
187
- logger?.info(` ↻ Converting evenodd path to nonzero winding in "${file}"`);
190
+ logger?.info(
191
+ ` ↻ Converting evenodd path to nonzero winding in "${file}"`
192
+ );
188
193
  p.d = convertEvenoddToWinding(PathKit, p.d);
189
194
  delete p.fillRule;
190
195
  (p as ParsedPath).noMerge = true;
@@ -54,12 +54,12 @@ export async function writeLayerSvg(opts: {
54
54
 
55
55
  const layerSvg = `
56
56
  <svg viewBox="0 0 ${opts.adv} ${
57
- opts.upm
58
- }" xmlns="http://www.w3.org/2000/svg">
57
+ opts.upm
58
+ }" xmlns="http://www.w3.org/2000/svg">
59
59
  <rect width="${opts.adv}" height="${opts.upm}" fill="none" />
60
60
  <g transform="translate(${opts.xOff}, ${opts.yOff}) scale(${
61
- opts.scale
62
- }) translate(${-opts.vx}, ${-opts.vy})">
61
+ opts.scale
62
+ }) translate(${-opts.vx}, ${-opts.vy})">
63
63
  <path d="${opts.d}" fill="black" />
64
64
  </g>
65
65
  </svg>`.trim();
@@ -1,4 +1,4 @@
1
- import { JSDOM } from 'jsdom';
1
+ import { DOMParser, type Element } from '@xmldom/xmldom';
2
2
  import { parseColor } from '../../utils/parse';
3
3
 
4
4
  export type ParsedFlatSvg = {
@@ -8,7 +8,7 @@ export type ParsedFlatSvg = {
8
8
 
9
9
  // if the fill is implicit, walk ancestors for the first explicit fill value
10
10
  function resolveInheritedFill(el: Element): string {
11
- let current: Element | null = el.parentElement;
11
+ let current = el.parentElement;
12
12
  while (current !== null) {
13
13
  const fill = current.getAttribute('fill');
14
14
  if (fill !== null && fill !== 'inherit') return fill;
@@ -89,30 +89,27 @@ export function parseFlattenedSvg(
89
89
  flattenedSvg: string,
90
90
  options?: { onSanitize?: (original: string) => void }
91
91
  ): ParsedFlatSvg {
92
- const dom = new JSDOM(flattenedSvg);
93
- const doc = dom.window.document;
92
+ const doc = new DOMParser().parseFromString(flattenedSvg, 'image/svg+xml');
93
+ const svgEl = doc.documentElement;
94
94
 
95
- const svgEl = doc.querySelector('svg');
96
95
  const viewBoxRaw = svgEl
97
96
  ?.getAttribute('viewBox')
98
97
  ?.split(/\s+/)
99
98
  .map(Number) ?? [0, 0, 100, 100];
100
99
 
101
100
  const viewBox: [number, number, number, number] =
102
- viewBoxRaw.length === 4 && viewBoxRaw.every((n) => Number.isFinite(n))
101
+ viewBoxRaw.length === 4 && viewBoxRaw.every(Number.isFinite)
103
102
  ? [viewBoxRaw[0]!, viewBoxRaw[1]!, viewBoxRaw[2]!, viewBoxRaw[3]!]
104
103
  : [0, 0, 100, 100];
105
104
 
106
- const pathEls = Array.from(doc.querySelectorAll('path'));
105
+ const pathEls = svgEl ? Array.from(svgEl.getElementsByTagName('path')) : [];
107
106
 
108
107
  const paths = pathEls
109
108
  .map(parsePath)
110
109
  .filter((p) => p.d.trim() !== '')
111
110
  .map((p) => {
112
111
  const { d, sanitized } = sanitizePathData(p.d);
113
- if (sanitized) {
114
- options?.onSanitize?.(p.d);
115
- }
112
+ if (sanitized) options?.onSanitize?.(p.d);
116
113
  return { ...p, d };
117
114
  });
118
115
 
@@ -134,6 +131,12 @@ export function validateSvg(content: string): SvgValidation {
134
131
  if (/<filter[\s>]/i.test(content)) {
135
132
  return { valid: false, reason: '<filter> is not supported yet' };
136
133
  }
134
+ if (/<image[\s>]/i.test(content)) {
135
+ return {
136
+ valid: false,
137
+ reason: 'embedded raster <image> is not supported yet',
138
+ };
139
+ }
137
140
  return { valid: true };
138
141
  }
139
142
 
@@ -150,18 +153,20 @@ export function extractOriginalEvenoddDs(svgContent: string): string[] {
150
153
  return [];
151
154
  }
152
155
 
153
- const dom = new JSDOM(svgContent, { contentType: 'image/svg+xml' });
154
- const doc = dom.window.document;
155
- const results: string[] = [];
156
-
157
- const pathEls = doc.querySelectorAll(
158
- 'path[fill-rule="evenodd"], path[clip-rule="evenodd"]'
156
+ const doc = new DOMParser().parseFromString(svgContent, 'image/svg+xml');
157
+
158
+ return Array.from(doc.getElementsByTagName('path')).reduce<string[]>(
159
+ (acc, el) => {
160
+ const isEvenOdd =
161
+ el.getAttribute('fill-rule') === 'evenodd' ||
162
+ el.getAttribute('clip-rule') === 'evenodd';
163
+ if (!isEvenOdd) return acc;
164
+ const d = el.getAttribute('d');
165
+ if (d !== null && d !== '') acc.push(d);
166
+ return acc;
167
+ },
168
+ []
159
169
  );
160
- for (const el of pathEls) {
161
- const d = el.getAttribute('d');
162
- if (d) results.push(d);
163
- }
164
- return results;
165
170
  }
166
171
 
167
172
  /**
@@ -187,5 +192,3 @@ export function preprocessSvg(content: string): string {
187
192
  if (/xmlns\s*=/.test(content)) return content;
188
193
  return content.replace(/<svg\b/, '<svg xmlns="http://www.w3.org/2000/svg"');
189
194
  }
190
-
191
-