@ankhorage/surface 9.0.4 → 9.0.6

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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @ankhorage/surface
2
2
 
3
+ ## 9.0.6
4
+
5
+ ### Patch Changes
6
+
7
+ - a4d56dd: Render Surface icons through a native-free web adapter using the official React Native Vector Icons
8
+ glyphmaps and fonts, preventing browser bundles from reaching React Native codegen modules.
9
+
10
+ ## 9.0.5
11
+
12
+ ### Patch Changes
13
+
14
+ - 233f5ea: Update Ankhorage dependencies: `@ankhorage/contracts`.
15
+
3
16
  ## 9.0.4
4
17
 
5
18
  ### Patch Changes
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ import { Image, Text } from 'react-native';
3
+ import type { FontIconSource, SvgIconSource } from '../../../../types/icon';
4
+ interface PortableIconPresentationProps {
5
+ readonly color: string;
6
+ readonly size: number;
7
+ readonly testID?: string;
8
+ }
9
+ type WebPortableIconProps = (FontIconSource & PortableIconPresentationProps & {
10
+ readonly style?: React.ComponentProps<typeof Text>['style'];
11
+ }) | (SvgIconSource & PortableIconPresentationProps & {
12
+ readonly style?: React.ComponentProps<typeof Image>['style'];
13
+ });
14
+ /*** Renders Surface icons on web without React Native native modules or SVG codegen. */
15
+ export declare function PortableIcon(props: WebPortableIconProps): React.JSX.Element;
16
+ export {};
17
+ //# sourceMappingURL=PortableIcon.web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PortableIcon.web.d.ts","sourceRoot":"","sources":["../../../../../src/features/icon/adapters/inbound/PortableIcon.web.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,KAAK,EAAE,IAAI,EAAkB,MAAM,cAAc,CAAC;AAE3D,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAc5E,UAAU,6BAA6B;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,KAAK,oBAAoB,GACrB,CAAC,cAAc,GACb,6BAA6B,GAAG;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC;CAC7D,CAAC,GACJ,CAAC,aAAa,GACZ,6BAA6B,GAAG;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,cAAc,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,CAAC,CAAC;CAC9D,CAAC,CAAC;AAET,wFAAwF;AACxF,wBAAgB,YAAY,CAAC,KAAK,EAAE,oBAAoB,qBAGvD"}
@@ -0,0 +1,173 @@
1
+ import fontAwesomeFont from '@react-native-vector-icons/fontawesome/fonts/FontAwesome.ttf';
2
+ import fontAwesomeGlyphs from '@react-native-vector-icons/fontawesome/glyphmaps/FontAwesome.json';
3
+ import fontAwesome5BrandFont from '@react-native-vector-icons/fontawesome5/fonts/FontAwesome5_Brands.ttf';
4
+ import fontAwesome5RegularFont from '@react-native-vector-icons/fontawesome5/fonts/FontAwesome5_Regular.ttf';
5
+ import fontAwesome5SolidFont from '@react-native-vector-icons/fontawesome5/fonts/FontAwesome5_Solid.ttf';
6
+ import fontAwesome5BrandGlyphs from '@react-native-vector-icons/fontawesome5/glyphmaps/FontAwesome5_brand.json';
7
+ import fontAwesome5RegularGlyphs from '@react-native-vector-icons/fontawesome5/glyphmaps/FontAwesome5_regular.json';
8
+ import fontAwesome5SolidGlyphs from '@react-native-vector-icons/fontawesome5/glyphmaps/FontAwesome5_solid.json';
9
+ import fontAwesome6BrandFont from '@react-native-vector-icons/fontawesome6/fonts/FontAwesome6_Brands.ttf';
10
+ import fontAwesome6RegularFont from '@react-native-vector-icons/fontawesome6/fonts/FontAwesome6_Regular.ttf';
11
+ import fontAwesome6SolidFont from '@react-native-vector-icons/fontawesome6/fonts/FontAwesome6_Solid.ttf';
12
+ import fontAwesome6BrandGlyphs from '@react-native-vector-icons/fontawesome6/glyphmaps/FontAwesome6_brand.json';
13
+ import fontAwesome6RegularGlyphs from '@react-native-vector-icons/fontawesome6/glyphmaps/FontAwesome6_regular.json';
14
+ import fontAwesome6SolidGlyphs from '@react-native-vector-icons/fontawesome6/glyphmaps/FontAwesome6_solid.json';
15
+ import ioniconsFont from '@react-native-vector-icons/ionicons/fonts/Ionicons.ttf';
16
+ import ioniconsGlyphs from '@react-native-vector-icons/ionicons/glyphmaps/Ionicons.json';
17
+ import materialDesignFont from '@react-native-vector-icons/material-design-icons/fonts/MaterialDesignIcons.ttf';
18
+ import materialDesignGlyphs from '@react-native-vector-icons/material-design-icons/glyphmaps/MaterialDesignIcons.json';
19
+ import React from 'react';
20
+ import { Image, Text } from 'react-native';
21
+ const FONT_AWESOME_GLYPHS = new Map(Object.entries(fontAwesomeGlyphs));
22
+ const FONT_AWESOME_5_BRAND_GLYPHS = new Map(Object.entries(fontAwesome5BrandGlyphs));
23
+ const FONT_AWESOME_5_REGULAR_GLYPHS = new Map(Object.entries(fontAwesome5RegularGlyphs));
24
+ const FONT_AWESOME_5_SOLID_GLYPHS = new Map(Object.entries(fontAwesome5SolidGlyphs));
25
+ const FONT_AWESOME_6_BRAND_GLYPHS = new Map(Object.entries(fontAwesome6BrandGlyphs));
26
+ const FONT_AWESOME_6_REGULAR_GLYPHS = new Map(Object.entries(fontAwesome6RegularGlyphs));
27
+ const FONT_AWESOME_6_SOLID_GLYPHS = new Map(Object.entries(fontAwesome6SolidGlyphs));
28
+ const IONICONS_GLYPHS = new Map(Object.entries(ioniconsGlyphs));
29
+ const MATERIAL_DESIGN_GLYPHS = new Map(Object.entries(materialDesignGlyphs));
30
+ /*** Renders Surface icons on web without React Native native modules or SVG codegen. */
31
+ export function PortableIcon(props) {
32
+ if ('source' in props)
33
+ return <WebSvgIcon {...props}/>;
34
+ return <WebFontIcon {...props}/>;
35
+ }
36
+ /*** Renders one RNVI glyph through the same font and codepoint used by native. */
37
+ function WebFontIcon(props) {
38
+ const definition = resolveFontDefinition(props);
39
+ useWebFont(definition);
40
+ const codepoint = definition.glyphs.get(props.name);
41
+ if (codepoint === undefined)
42
+ return null;
43
+ return (<Text accessibilityElementsHidden aria-hidden style={[
44
+ props.style,
45
+ {
46
+ color: props.color,
47
+ fontFamily: definition.family,
48
+ fontSize: props.size,
49
+ fontWeight: definition.weight,
50
+ height: props.size,
51
+ lineHeight: props.size,
52
+ width: props.size,
53
+ },
54
+ ]} testID={props.testID}>
55
+ {String.fromCodePoint(codepoint)}
56
+ </Text>);
57
+ }
58
+ /*** Renders URL and bundled SVG sources through the portable web Image adapter. */
59
+ function WebSvgIcon(props) {
60
+ const source = typeof props.source === 'string' ? { uri: props.source } : props.source;
61
+ return (<Image accessibilityElementsHidden source={source} style={[
62
+ props.style,
63
+ {
64
+ height: props.size,
65
+ tintColor: props.color,
66
+ width: props.size,
67
+ },
68
+ ]} testID={props.testID}/>);
69
+ }
70
+ /*** Resolve the browser font, glyph map, and weight for one existing Surface icon contract. */
71
+ function resolveFontDefinition(source) {
72
+ const runtimeProvider = source.provider;
73
+ switch (source.provider) {
74
+ case undefined:
75
+ case 'Ionicons':
76
+ return { family: 'Ionicons', fontUrl: ioniconsFont, glyphs: IONICONS_GLYPHS };
77
+ case 'FontAwesome':
78
+ return { family: 'FontAwesome', fontUrl: fontAwesomeFont, glyphs: FONT_AWESOME_GLYPHS };
79
+ case 'FontAwesome5':
80
+ return resolveFontAwesome5Definition(source.variant);
81
+ case 'FontAwesome6':
82
+ return resolveFontAwesome6Definition(source.variant);
83
+ case 'MaterialDesignIcons':
84
+ return {
85
+ family: 'MaterialDesignIcons',
86
+ fontUrl: materialDesignFont,
87
+ glyphs: MATERIAL_DESIGN_GLYPHS,
88
+ };
89
+ default:
90
+ throw new Error(`Unsupported icon provider: ${String(runtimeProvider)}`);
91
+ }
92
+ }
93
+ /*** Resolve one FontAwesome5 style without invoking RNVI runtime components. */
94
+ function resolveFontAwesome5Definition(variant) {
95
+ const runtimeVariant = variant;
96
+ if (runtimeVariant === 'brand') {
97
+ return {
98
+ family: 'FontAwesome5Brands-Regular',
99
+ fontUrl: fontAwesome5BrandFont,
100
+ glyphs: FONT_AWESOME_5_BRAND_GLYPHS,
101
+ weight: 400,
102
+ };
103
+ }
104
+ if (runtimeVariant === 'solid') {
105
+ return {
106
+ family: 'FontAwesome5Free-Solid',
107
+ fontUrl: fontAwesome5SolidFont,
108
+ glyphs: FONT_AWESOME_5_SOLID_GLYPHS,
109
+ weight: 900,
110
+ };
111
+ }
112
+ if (runtimeVariant !== 'regular') {
113
+ throw new Error(`Unsupported icon FontAwesome5 variant: ${String(runtimeVariant)}`);
114
+ }
115
+ return {
116
+ family: 'FontAwesome5Free-Regular',
117
+ fontUrl: fontAwesome5RegularFont,
118
+ glyphs: FONT_AWESOME_5_REGULAR_GLYPHS,
119
+ weight: 400,
120
+ };
121
+ }
122
+ /*** Resolve one FontAwesome6 style without invoking RNVI runtime components. */
123
+ function resolveFontAwesome6Definition(variant) {
124
+ const runtimeVariant = variant;
125
+ if (runtimeVariant === 'brand') {
126
+ return {
127
+ family: 'FontAwesome6Brands-Regular',
128
+ fontUrl: fontAwesome6BrandFont,
129
+ glyphs: FONT_AWESOME_6_BRAND_GLYPHS,
130
+ weight: 400,
131
+ };
132
+ }
133
+ if (runtimeVariant === 'solid') {
134
+ return {
135
+ family: 'FontAwesome6Free-Solid',
136
+ fontUrl: fontAwesome6SolidFont,
137
+ glyphs: FONT_AWESOME_6_SOLID_GLYPHS,
138
+ weight: 900,
139
+ };
140
+ }
141
+ if (runtimeVariant !== 'regular') {
142
+ throw new Error(`Unsupported icon FontAwesome6 variant: ${String(runtimeVariant)}`);
143
+ }
144
+ return {
145
+ family: 'FontAwesome6Free-Regular',
146
+ fontUrl: fontAwesome6RegularFont,
147
+ glyphs: FONT_AWESOME_6_REGULAR_GLYPHS,
148
+ weight: 400,
149
+ };
150
+ }
151
+ /*** Register one icon font once before browser layout uses its glyphs. */
152
+ function useWebFont(definition) {
153
+ React.useInsertionEffect(() => {
154
+ if (typeof document === 'undefined')
155
+ return;
156
+ const id = createFontStyleId(definition);
157
+ if (document.getElementById(id) !== null)
158
+ return;
159
+ const style = document.createElement('style');
160
+ style.id = id;
161
+ style.textContent = createFontFaceCss(definition);
162
+ document.head.appendChild(style);
163
+ }, [definition.family, definition.fontUrl, definition.weight]);
164
+ }
165
+ /*** Create a stable DOM id for one font face variant. */
166
+ function createFontStyleId(definition) {
167
+ return `surface-icon-font-${definition.family}-${definition.weight ?? 'normal'}`;
168
+ }
169
+ /*** Create the browser font-face rule used by the portable font icon adapter. */
170
+ function createFontFaceCss(definition) {
171
+ return `@font-face{font-family:"${definition.family}";src:url("${definition.fontUrl}") format("truetype");font-style:normal;font-weight:${definition.weight ?? 400};font-display:block;}`;
172
+ }
173
+ //# sourceMappingURL=PortableIcon.web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PortableIcon.web.js","sourceRoot":"","sources":["../../../../../src/features/icon/adapters/inbound/PortableIcon.web.tsx"],"names":[],"mappings":"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","sourcesContent":["import fontAwesomeFont from '@react-native-vector-icons/fontawesome/fonts/FontAwesome.ttf';\nimport fontAwesomeGlyphs from '@react-native-vector-icons/fontawesome/glyphmaps/FontAwesome.json';\nimport fontAwesome5BrandFont from '@react-native-vector-icons/fontawesome5/fonts/FontAwesome5_Brands.ttf';\nimport fontAwesome5RegularFont from '@react-native-vector-icons/fontawesome5/fonts/FontAwesome5_Regular.ttf';\nimport fontAwesome5SolidFont from '@react-native-vector-icons/fontawesome5/fonts/FontAwesome5_Solid.ttf';\nimport fontAwesome5BrandGlyphs from '@react-native-vector-icons/fontawesome5/glyphmaps/FontAwesome5_brand.json';\nimport fontAwesome5RegularGlyphs from '@react-native-vector-icons/fontawesome5/glyphmaps/FontAwesome5_regular.json';\nimport fontAwesome5SolidGlyphs from '@react-native-vector-icons/fontawesome5/glyphmaps/FontAwesome5_solid.json';\nimport fontAwesome6BrandFont from '@react-native-vector-icons/fontawesome6/fonts/FontAwesome6_Brands.ttf';\nimport fontAwesome6RegularFont from '@react-native-vector-icons/fontawesome6/fonts/FontAwesome6_Regular.ttf';\nimport fontAwesome6SolidFont from '@react-native-vector-icons/fontawesome6/fonts/FontAwesome6_Solid.ttf';\nimport fontAwesome6BrandGlyphs from '@react-native-vector-icons/fontawesome6/glyphmaps/FontAwesome6_brand.json';\nimport fontAwesome6RegularGlyphs from '@react-native-vector-icons/fontawesome6/glyphmaps/FontAwesome6_regular.json';\nimport fontAwesome6SolidGlyphs from '@react-native-vector-icons/fontawesome6/glyphmaps/FontAwesome6_solid.json';\nimport ioniconsFont from '@react-native-vector-icons/ionicons/fonts/Ionicons.ttf';\nimport ioniconsGlyphs from '@react-native-vector-icons/ionicons/glyphmaps/Ionicons.json';\nimport materialDesignFont from '@react-native-vector-icons/material-design-icons/fonts/MaterialDesignIcons.ttf';\nimport materialDesignGlyphs from '@react-native-vector-icons/material-design-icons/glyphmaps/MaterialDesignIcons.json';\nimport React from 'react';\nimport { Image, Text, type TextStyle } from 'react-native';\n\nimport type { FontIconSource, SvgIconSource } from '../../../../types/icon';\n\ntype GlyphMap = ReadonlyMap<string, number>;\n\nconst FONT_AWESOME_GLYPHS: GlyphMap = new Map(Object.entries(fontAwesomeGlyphs));\nconst FONT_AWESOME_5_BRAND_GLYPHS: GlyphMap = new Map(Object.entries(fontAwesome5BrandGlyphs));\nconst FONT_AWESOME_5_REGULAR_GLYPHS: GlyphMap = new Map(Object.entries(fontAwesome5RegularGlyphs));\nconst FONT_AWESOME_5_SOLID_GLYPHS: GlyphMap = new Map(Object.entries(fontAwesome5SolidGlyphs));\nconst FONT_AWESOME_6_BRAND_GLYPHS: GlyphMap = new Map(Object.entries(fontAwesome6BrandGlyphs));\nconst FONT_AWESOME_6_REGULAR_GLYPHS: GlyphMap = new Map(Object.entries(fontAwesome6RegularGlyphs));\nconst FONT_AWESOME_6_SOLID_GLYPHS: GlyphMap = new Map(Object.entries(fontAwesome6SolidGlyphs));\nconst IONICONS_GLYPHS: GlyphMap = new Map(Object.entries(ioniconsGlyphs));\nconst MATERIAL_DESIGN_GLYPHS: GlyphMap = new Map(Object.entries(materialDesignGlyphs));\n\ninterface PortableIconPresentationProps {\n readonly color: string;\n readonly size: number;\n readonly testID?: string;\n}\n\ntype WebPortableIconProps =\n | (FontIconSource &\n PortableIconPresentationProps & {\n readonly style?: React.ComponentProps<typeof Text>['style'];\n })\n | (SvgIconSource &\n PortableIconPresentationProps & {\n readonly style?: React.ComponentProps<typeof Image>['style'];\n });\n\n/*** Renders Surface icons on web without React Native native modules or SVG codegen. */\nexport function PortableIcon(props: WebPortableIconProps) {\n if ('source' in props) return <WebSvgIcon {...props} />;\n return <WebFontIcon {...props} />;\n}\n\ninterface WebFontDefinition {\n readonly family: string;\n readonly fontUrl: string;\n readonly glyphs: GlyphMap;\n readonly weight?: TextStyle['fontWeight'];\n}\n\n/*** Renders one RNVI glyph through the same font and codepoint used by native. */\nfunction WebFontIcon(\n props: FontIconSource &\n PortableIconPresentationProps & {\n readonly style?: React.ComponentProps<typeof Text>['style'];\n },\n) {\n const definition = resolveFontDefinition(props);\n useWebFont(definition);\n const codepoint = definition.glyphs.get(props.name);\n if (codepoint === undefined) return null;\n\n return (\n <Text\n accessibilityElementsHidden\n aria-hidden\n style={[\n props.style,\n {\n color: props.color,\n fontFamily: definition.family,\n fontSize: props.size,\n fontWeight: definition.weight,\n height: props.size,\n lineHeight: props.size,\n width: props.size,\n },\n ]}\n testID={props.testID}\n >\n {String.fromCodePoint(codepoint)}\n </Text>\n );\n}\n\n/*** Renders URL and bundled SVG sources through the portable web Image adapter. */\nfunction WebSvgIcon(\n props: SvgIconSource &\n PortableIconPresentationProps & {\n readonly style?: React.ComponentProps<typeof Image>['style'];\n },\n) {\n const source = typeof props.source === 'string' ? { uri: props.source } : props.source;\n return (\n <Image\n accessibilityElementsHidden\n source={source}\n style={[\n props.style,\n {\n height: props.size,\n tintColor: props.color,\n width: props.size,\n },\n ]}\n testID={props.testID}\n />\n );\n}\n\n/*** Resolve the browser font, glyph map, and weight for one existing Surface icon contract. */\nfunction resolveFontDefinition(source: FontIconSource): WebFontDefinition {\n const runtimeProvider: unknown = source.provider;\n switch (source.provider) {\n case undefined:\n case 'Ionicons':\n return { family: 'Ionicons', fontUrl: ioniconsFont, glyphs: IONICONS_GLYPHS };\n case 'FontAwesome':\n return { family: 'FontAwesome', fontUrl: fontAwesomeFont, glyphs: FONT_AWESOME_GLYPHS };\n case 'FontAwesome5':\n return resolveFontAwesome5Definition(source.variant);\n case 'FontAwesome6':\n return resolveFontAwesome6Definition(source.variant);\n case 'MaterialDesignIcons':\n return {\n family: 'MaterialDesignIcons',\n fontUrl: materialDesignFont,\n glyphs: MATERIAL_DESIGN_GLYPHS,\n };\n default:\n throw new Error(`Unsupported icon provider: ${String(runtimeProvider)}`);\n }\n}\n\n/*** Resolve one FontAwesome5 style without invoking RNVI runtime components. */\nfunction resolveFontAwesome5Definition(\n variant: Extract<FontIconSource, { provider: 'FontAwesome5' }>['variant'],\n): WebFontDefinition {\n const runtimeVariant: unknown = variant;\n if (runtimeVariant === 'brand') {\n return {\n family: 'FontAwesome5Brands-Regular',\n fontUrl: fontAwesome5BrandFont,\n glyphs: FONT_AWESOME_5_BRAND_GLYPHS,\n weight: 400,\n };\n }\n if (runtimeVariant === 'solid') {\n return {\n family: 'FontAwesome5Free-Solid',\n fontUrl: fontAwesome5SolidFont,\n glyphs: FONT_AWESOME_5_SOLID_GLYPHS,\n weight: 900,\n };\n }\n if (runtimeVariant !== 'regular') {\n throw new Error(`Unsupported icon FontAwesome5 variant: ${String(runtimeVariant)}`);\n }\n return {\n family: 'FontAwesome5Free-Regular',\n fontUrl: fontAwesome5RegularFont,\n glyphs: FONT_AWESOME_5_REGULAR_GLYPHS,\n weight: 400,\n };\n}\n\n/*** Resolve one FontAwesome6 style without invoking RNVI runtime components. */\nfunction resolveFontAwesome6Definition(\n variant: Extract<FontIconSource, { provider: 'FontAwesome6' }>['variant'],\n): WebFontDefinition {\n const runtimeVariant: unknown = variant;\n if (runtimeVariant === 'brand') {\n return {\n family: 'FontAwesome6Brands-Regular',\n fontUrl: fontAwesome6BrandFont,\n glyphs: FONT_AWESOME_6_BRAND_GLYPHS,\n weight: 400,\n };\n }\n if (runtimeVariant === 'solid') {\n return {\n family: 'FontAwesome6Free-Solid',\n fontUrl: fontAwesome6SolidFont,\n glyphs: FONT_AWESOME_6_SOLID_GLYPHS,\n weight: 900,\n };\n }\n if (runtimeVariant !== 'regular') {\n throw new Error(`Unsupported icon FontAwesome6 variant: ${String(runtimeVariant)}`);\n }\n return {\n family: 'FontAwesome6Free-Regular',\n fontUrl: fontAwesome6RegularFont,\n glyphs: FONT_AWESOME_6_REGULAR_GLYPHS,\n weight: 400,\n };\n}\n\n/*** Register one icon font once before browser layout uses its glyphs. */\nfunction useWebFont(definition: WebFontDefinition) {\n React.useInsertionEffect(() => {\n if (typeof document === 'undefined') return;\n const id = createFontStyleId(definition);\n if (document.getElementById(id) !== null) return;\n\n const style = document.createElement('style');\n style.id = id;\n style.textContent = createFontFaceCss(definition);\n document.head.appendChild(style);\n }, [definition.family, definition.fontUrl, definition.weight]);\n}\n\n/*** Create a stable DOM id for one font face variant. */\nfunction createFontStyleId(definition: WebFontDefinition): string {\n return `surface-icon-font-${definition.family}-${definition.weight ?? 'normal'}`;\n}\n\n/*** Create the browser font-face rule used by the portable font icon adapter. */\nfunction createFontFaceCss(definition: WebFontDefinition): string {\n return `@font-face{font-family:\"${definition.family}\";src:url(\"${definition.fontUrl}\") format(\"truetype\");font-style:normal;font-weight:${definition.weight ?? 400};font-display:block;}`;\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ankhorage/surface",
3
3
  "type": "module",
4
- "version": "9.0.4",
4
+ "version": "9.0.6",
5
5
  "description": "Stable UI foundation for React Native and React Native Web.",
6
6
  "homepage": "https://github.com/ankhorage/surface#readme",
7
7
  "bugs": {
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@ankhorage/color-theory": "^0.3.1",
47
- "@ankhorage/contracts": "^22.0.3",
47
+ "@ankhorage/contracts": "^22.0.4",
48
48
  "@ankhorage/utility": "^1.4.0",
49
49
  "@gorhom/bottom-sheet": "^5.2.13"
50
50
  },
@@ -0,0 +1,17 @@
1
+ import { expect, test } from 'bun:test';
2
+
3
+ import type { PortableIcon } from './PortableIcon.web';
4
+
5
+ type WebPortableIcon = typeof PortableIcon;
6
+
7
+ test('web icon adapter remains a native-free platform boundary', async () => {
8
+ const portableIconType: WebPortableIcon | undefined = undefined;
9
+ expect(portableIconType).toBeUndefined();
10
+
11
+ const source = await Bun.file('src/features/icon/adapters/inbound/PortableIcon.web.tsx').text();
12
+
13
+ expect(source).not.toContain('react-native-svg');
14
+ expect(source).not.toContain('/static');
15
+ expect(source).toContain('/glyphmaps/');
16
+ expect(source).toContain('/fonts/');
17
+ });
@@ -0,0 +1,235 @@
1
+ import fontAwesomeFont from '@react-native-vector-icons/fontawesome/fonts/FontAwesome.ttf';
2
+ import fontAwesomeGlyphs from '@react-native-vector-icons/fontawesome/glyphmaps/FontAwesome.json';
3
+ import fontAwesome5BrandFont from '@react-native-vector-icons/fontawesome5/fonts/FontAwesome5_Brands.ttf';
4
+ import fontAwesome5RegularFont from '@react-native-vector-icons/fontawesome5/fonts/FontAwesome5_Regular.ttf';
5
+ import fontAwesome5SolidFont from '@react-native-vector-icons/fontawesome5/fonts/FontAwesome5_Solid.ttf';
6
+ import fontAwesome5BrandGlyphs from '@react-native-vector-icons/fontawesome5/glyphmaps/FontAwesome5_brand.json';
7
+ import fontAwesome5RegularGlyphs from '@react-native-vector-icons/fontawesome5/glyphmaps/FontAwesome5_regular.json';
8
+ import fontAwesome5SolidGlyphs from '@react-native-vector-icons/fontawesome5/glyphmaps/FontAwesome5_solid.json';
9
+ import fontAwesome6BrandFont from '@react-native-vector-icons/fontawesome6/fonts/FontAwesome6_Brands.ttf';
10
+ import fontAwesome6RegularFont from '@react-native-vector-icons/fontawesome6/fonts/FontAwesome6_Regular.ttf';
11
+ import fontAwesome6SolidFont from '@react-native-vector-icons/fontawesome6/fonts/FontAwesome6_Solid.ttf';
12
+ import fontAwesome6BrandGlyphs from '@react-native-vector-icons/fontawesome6/glyphmaps/FontAwesome6_brand.json';
13
+ import fontAwesome6RegularGlyphs from '@react-native-vector-icons/fontawesome6/glyphmaps/FontAwesome6_regular.json';
14
+ import fontAwesome6SolidGlyphs from '@react-native-vector-icons/fontawesome6/glyphmaps/FontAwesome6_solid.json';
15
+ import ioniconsFont from '@react-native-vector-icons/ionicons/fonts/Ionicons.ttf';
16
+ import ioniconsGlyphs from '@react-native-vector-icons/ionicons/glyphmaps/Ionicons.json';
17
+ import materialDesignFont from '@react-native-vector-icons/material-design-icons/fonts/MaterialDesignIcons.ttf';
18
+ import materialDesignGlyphs from '@react-native-vector-icons/material-design-icons/glyphmaps/MaterialDesignIcons.json';
19
+ import React from 'react';
20
+ import { Image, Text, type TextStyle } from 'react-native';
21
+
22
+ import type { FontIconSource, SvgIconSource } from '../../../../types/icon';
23
+
24
+ type GlyphMap = ReadonlyMap<string, number>;
25
+
26
+ const FONT_AWESOME_GLYPHS: GlyphMap = new Map(Object.entries(fontAwesomeGlyphs));
27
+ const FONT_AWESOME_5_BRAND_GLYPHS: GlyphMap = new Map(Object.entries(fontAwesome5BrandGlyphs));
28
+ const FONT_AWESOME_5_REGULAR_GLYPHS: GlyphMap = new Map(Object.entries(fontAwesome5RegularGlyphs));
29
+ const FONT_AWESOME_5_SOLID_GLYPHS: GlyphMap = new Map(Object.entries(fontAwesome5SolidGlyphs));
30
+ const FONT_AWESOME_6_BRAND_GLYPHS: GlyphMap = new Map(Object.entries(fontAwesome6BrandGlyphs));
31
+ const FONT_AWESOME_6_REGULAR_GLYPHS: GlyphMap = new Map(Object.entries(fontAwesome6RegularGlyphs));
32
+ const FONT_AWESOME_6_SOLID_GLYPHS: GlyphMap = new Map(Object.entries(fontAwesome6SolidGlyphs));
33
+ const IONICONS_GLYPHS: GlyphMap = new Map(Object.entries(ioniconsGlyphs));
34
+ const MATERIAL_DESIGN_GLYPHS: GlyphMap = new Map(Object.entries(materialDesignGlyphs));
35
+
36
+ interface PortableIconPresentationProps {
37
+ readonly color: string;
38
+ readonly size: number;
39
+ readonly testID?: string;
40
+ }
41
+
42
+ type WebPortableIconProps =
43
+ | (FontIconSource &
44
+ PortableIconPresentationProps & {
45
+ readonly style?: React.ComponentProps<typeof Text>['style'];
46
+ })
47
+ | (SvgIconSource &
48
+ PortableIconPresentationProps & {
49
+ readonly style?: React.ComponentProps<typeof Image>['style'];
50
+ });
51
+
52
+ /*** Renders Surface icons on web without React Native native modules or SVG codegen. */
53
+ export function PortableIcon(props: WebPortableIconProps) {
54
+ if ('source' in props) return <WebSvgIcon {...props} />;
55
+ return <WebFontIcon {...props} />;
56
+ }
57
+
58
+ interface WebFontDefinition {
59
+ readonly family: string;
60
+ readonly fontUrl: string;
61
+ readonly glyphs: GlyphMap;
62
+ readonly weight?: TextStyle['fontWeight'];
63
+ }
64
+
65
+ /*** Renders one RNVI glyph through the same font and codepoint used by native. */
66
+ function WebFontIcon(
67
+ props: FontIconSource &
68
+ PortableIconPresentationProps & {
69
+ readonly style?: React.ComponentProps<typeof Text>['style'];
70
+ },
71
+ ) {
72
+ const definition = resolveFontDefinition(props);
73
+ useWebFont(definition);
74
+ const codepoint = definition.glyphs.get(props.name);
75
+ if (codepoint === undefined) return null;
76
+
77
+ return (
78
+ <Text
79
+ accessibilityElementsHidden
80
+ aria-hidden
81
+ style={[
82
+ props.style,
83
+ {
84
+ color: props.color,
85
+ fontFamily: definition.family,
86
+ fontSize: props.size,
87
+ fontWeight: definition.weight,
88
+ height: props.size,
89
+ lineHeight: props.size,
90
+ width: props.size,
91
+ },
92
+ ]}
93
+ testID={props.testID}
94
+ >
95
+ {String.fromCodePoint(codepoint)}
96
+ </Text>
97
+ );
98
+ }
99
+
100
+ /*** Renders URL and bundled SVG sources through the portable web Image adapter. */
101
+ function WebSvgIcon(
102
+ props: SvgIconSource &
103
+ PortableIconPresentationProps & {
104
+ readonly style?: React.ComponentProps<typeof Image>['style'];
105
+ },
106
+ ) {
107
+ const source = typeof props.source === 'string' ? { uri: props.source } : props.source;
108
+ return (
109
+ <Image
110
+ accessibilityElementsHidden
111
+ source={source}
112
+ style={[
113
+ props.style,
114
+ {
115
+ height: props.size,
116
+ tintColor: props.color,
117
+ width: props.size,
118
+ },
119
+ ]}
120
+ testID={props.testID}
121
+ />
122
+ );
123
+ }
124
+
125
+ /*** Resolve the browser font, glyph map, and weight for one existing Surface icon contract. */
126
+ function resolveFontDefinition(source: FontIconSource): WebFontDefinition {
127
+ const runtimeProvider: unknown = source.provider;
128
+ switch (source.provider) {
129
+ case undefined:
130
+ case 'Ionicons':
131
+ return { family: 'Ionicons', fontUrl: ioniconsFont, glyphs: IONICONS_GLYPHS };
132
+ case 'FontAwesome':
133
+ return { family: 'FontAwesome', fontUrl: fontAwesomeFont, glyphs: FONT_AWESOME_GLYPHS };
134
+ case 'FontAwesome5':
135
+ return resolveFontAwesome5Definition(source.variant);
136
+ case 'FontAwesome6':
137
+ return resolveFontAwesome6Definition(source.variant);
138
+ case 'MaterialDesignIcons':
139
+ return {
140
+ family: 'MaterialDesignIcons',
141
+ fontUrl: materialDesignFont,
142
+ glyphs: MATERIAL_DESIGN_GLYPHS,
143
+ };
144
+ default:
145
+ throw new Error(`Unsupported icon provider: ${String(runtimeProvider)}`);
146
+ }
147
+ }
148
+
149
+ /*** Resolve one FontAwesome5 style without invoking RNVI runtime components. */
150
+ function resolveFontAwesome5Definition(
151
+ variant: Extract<FontIconSource, { provider: 'FontAwesome5' }>['variant'],
152
+ ): WebFontDefinition {
153
+ const runtimeVariant: unknown = variant;
154
+ if (runtimeVariant === 'brand') {
155
+ return {
156
+ family: 'FontAwesome5Brands-Regular',
157
+ fontUrl: fontAwesome5BrandFont,
158
+ glyphs: FONT_AWESOME_5_BRAND_GLYPHS,
159
+ weight: 400,
160
+ };
161
+ }
162
+ if (runtimeVariant === 'solid') {
163
+ return {
164
+ family: 'FontAwesome5Free-Solid',
165
+ fontUrl: fontAwesome5SolidFont,
166
+ glyphs: FONT_AWESOME_5_SOLID_GLYPHS,
167
+ weight: 900,
168
+ };
169
+ }
170
+ if (runtimeVariant !== 'regular') {
171
+ throw new Error(`Unsupported icon FontAwesome5 variant: ${String(runtimeVariant)}`);
172
+ }
173
+ return {
174
+ family: 'FontAwesome5Free-Regular',
175
+ fontUrl: fontAwesome5RegularFont,
176
+ glyphs: FONT_AWESOME_5_REGULAR_GLYPHS,
177
+ weight: 400,
178
+ };
179
+ }
180
+
181
+ /*** Resolve one FontAwesome6 style without invoking RNVI runtime components. */
182
+ function resolveFontAwesome6Definition(
183
+ variant: Extract<FontIconSource, { provider: 'FontAwesome6' }>['variant'],
184
+ ): WebFontDefinition {
185
+ const runtimeVariant: unknown = variant;
186
+ if (runtimeVariant === 'brand') {
187
+ return {
188
+ family: 'FontAwesome6Brands-Regular',
189
+ fontUrl: fontAwesome6BrandFont,
190
+ glyphs: FONT_AWESOME_6_BRAND_GLYPHS,
191
+ weight: 400,
192
+ };
193
+ }
194
+ if (runtimeVariant === 'solid') {
195
+ return {
196
+ family: 'FontAwesome6Free-Solid',
197
+ fontUrl: fontAwesome6SolidFont,
198
+ glyphs: FONT_AWESOME_6_SOLID_GLYPHS,
199
+ weight: 900,
200
+ };
201
+ }
202
+ if (runtimeVariant !== 'regular') {
203
+ throw new Error(`Unsupported icon FontAwesome6 variant: ${String(runtimeVariant)}`);
204
+ }
205
+ return {
206
+ family: 'FontAwesome6Free-Regular',
207
+ fontUrl: fontAwesome6RegularFont,
208
+ glyphs: FONT_AWESOME_6_REGULAR_GLYPHS,
209
+ weight: 400,
210
+ };
211
+ }
212
+
213
+ /*** Register one icon font once before browser layout uses its glyphs. */
214
+ function useWebFont(definition: WebFontDefinition) {
215
+ React.useInsertionEffect(() => {
216
+ if (typeof document === 'undefined') return;
217
+ const id = createFontStyleId(definition);
218
+ if (document.getElementById(id) !== null) return;
219
+
220
+ const style = document.createElement('style');
221
+ style.id = id;
222
+ style.textContent = createFontFaceCss(definition);
223
+ document.head.appendChild(style);
224
+ }, [definition.family, definition.fontUrl, definition.weight]);
225
+ }
226
+
227
+ /*** Create a stable DOM id for one font face variant. */
228
+ function createFontStyleId(definition: WebFontDefinition): string {
229
+ return `surface-icon-font-${definition.family}-${definition.weight ?? 'normal'}`;
230
+ }
231
+
232
+ /*** Create the browser font-face rule used by the portable font icon adapter. */
233
+ function createFontFaceCss(definition: WebFontDefinition): string {
234
+ return `@font-face{font-family:"${definition.family}";src:url("${definition.fontUrl}") format("truetype");font-style:normal;font-weight:${definition.weight ?? 400};font-display:block;}`;
235
+ }
@@ -0,0 +1,4 @@
1
+ declare module '*.ttf' {
2
+ const fontUrl: string;
3
+ export default fontUrl;
4
+ }