@symbiote-native/symbols 0.0.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.
- package/LICENSE +21 -0
- package/README.md +52 -0
- package/build/angular/index.d.ts +2 -0
- package/build/angular/index.js +3 -0
- package/build/angular/symbol-view.d.ts +22 -0
- package/build/angular/symbol-view.js +147 -0
- package/build/core/android/index.d.ts +11 -0
- package/build/core/android/index.js +10 -0
- package/build/core/android/symbols.json +4057 -0
- package/build/core/android/weights/bold/index.d.ts +3 -0
- package/build/core/android/weights/bold/index.js +6 -0
- package/build/core/android/weights/extraLight/index.d.ts +3 -0
- package/build/core/android/weights/extraLight/index.js +6 -0
- package/build/core/android/weights/light/index.d.ts +3 -0
- package/build/core/android/weights/light/index.js +6 -0
- package/build/core/android/weights/medium/index.d.ts +3 -0
- package/build/core/android/weights/medium/index.js +6 -0
- package/build/core/android/weights/regular/index.d.ts +3 -0
- package/build/core/android/weights/regular/index.js +6 -0
- package/build/core/android/weights/semiBold/index.d.ts +3 -0
- package/build/core/android/weights/semiBold/index.js +6 -0
- package/build/core/android/weights/thin/index.d.ts +3 -0
- package/build/core/android/weights/thin/index.js +6 -0
- package/build/core/index.d.ts +5 -0
- package/build/core/index.js +2 -0
- package/build/core/symbol-types.d.ts +95 -0
- package/build/core/symbol-types.js +1 -0
- package/build/core/symbols.d.ts +29 -0
- package/build/core/symbols.js +153 -0
- package/build/react/index.d.ts +3 -0
- package/build/react/index.js +3 -0
- package/build/react/symbol-view.d.ts +10 -0
- package/build/react/symbol-view.js +14 -0
- package/build/solid/index.d.ts +3 -0
- package/build/solid/index.js +3 -0
- package/build/solid/symbol-view.d.ts +11 -0
- package/build/solid/symbol-view.js +28 -0
- package/build/svelte/index.d.ts +3 -0
- package/build/svelte/index.js +3 -0
- package/build/svelte/symbol-props.d.ts +7 -0
- package/build/svelte/symbol-props.js +1 -0
- package/build/svelte/symbol-view.svelte +26 -0
- package/build/svelte/symbol-view.svelte.d.ts +4 -0
- package/build/vue/index.d.ts +3 -0
- package/build/vue/index.js +3 -0
- package/build/vue/symbol-view.d.ts +6 -0
- package/build/vue/symbol-view.js +26 -0
- package/build-ngc/angular/index.d.ts +2 -0
- package/build-ngc/angular/index.js +4 -0
- package/build-ngc/angular/index.js.map +1 -0
- package/build-ngc/angular/symbol-view.d.ts +25 -0
- package/build-ngc/angular/symbol-view.js +82 -0
- package/build-ngc/angular/symbol-view.js.map +1 -0
- package/build-ngc/core/android/index.d.ts +11 -0
- package/build-ngc/core/android/index.js +11 -0
- package/build-ngc/core/android/index.js.map +1 -0
- package/build-ngc/core/android/symbols.json +4057 -0
- package/build-ngc/core/android/weights/bold/index.d.ts +3 -0
- package/build-ngc/core/android/weights/bold/index.js +7 -0
- package/build-ngc/core/android/weights/bold/index.js.map +1 -0
- package/build-ngc/core/android/weights/extraLight/index.d.ts +3 -0
- package/build-ngc/core/android/weights/extraLight/index.js +7 -0
- package/build-ngc/core/android/weights/extraLight/index.js.map +1 -0
- package/build-ngc/core/android/weights/light/index.d.ts +3 -0
- package/build-ngc/core/android/weights/light/index.js +7 -0
- package/build-ngc/core/android/weights/light/index.js.map +1 -0
- package/build-ngc/core/android/weights/medium/index.d.ts +3 -0
- package/build-ngc/core/android/weights/medium/index.js +7 -0
- package/build-ngc/core/android/weights/medium/index.js.map +1 -0
- package/build-ngc/core/android/weights/regular/index.d.ts +3 -0
- package/build-ngc/core/android/weights/regular/index.js +7 -0
- package/build-ngc/core/android/weights/regular/index.js.map +1 -0
- package/build-ngc/core/android/weights/semiBold/index.d.ts +3 -0
- package/build-ngc/core/android/weights/semiBold/index.js +7 -0
- package/build-ngc/core/android/weights/semiBold/index.js.map +1 -0
- package/build-ngc/core/android/weights/thin/index.d.ts +3 -0
- package/build-ngc/core/android/weights/thin/index.js +7 -0
- package/build-ngc/core/android/weights/thin/index.js.map +1 -0
- package/build-ngc/core/index.d.ts +5 -0
- package/build-ngc/core/index.js +3 -0
- package/build-ngc/core/index.js.map +1 -0
- package/build-ngc/core/symbol-types.d.ts +95 -0
- package/build-ngc/core/symbol-types.js +2 -0
- package/build-ngc/core/symbol-types.js.map +1 -0
- package/build-ngc/core/symbols.d.ts +29 -0
- package/build-ngc/core/symbols.js +154 -0
- package/build-ngc/core/symbols.js.map +1 -0
- package/native-link.json +5 -0
- package/package.json +155 -0
- package/src/angular/index.ts +18 -0
- package/src/angular/symbol-view.ts +71 -0
- package/src/core/android/index.ts +15 -0
- package/src/core/android/symbols.json +4057 -0
- package/src/core/android/weights/bold/index.ts +8 -0
- package/src/core/android/weights/extraLight/index.ts +8 -0
- package/src/core/android/weights/light/index.ts +8 -0
- package/src/core/android/weights/medium/index.ts +8 -0
- package/src/core/android/weights/regular/index.ts +8 -0
- package/src/core/android/weights/semiBold/index.ts +8 -0
- package/src/core/android/weights/thin/index.ts +8 -0
- package/src/core/index.ts +25 -0
- package/src/core/symbol-types.ts +147 -0
- package/src/core/symbols.ts +192 -0
- package/src/react/index.ts +18 -0
- package/src/react/symbol-view.ts +33 -0
- package/src/solid/index.ts +18 -0
- package/src/solid/symbol-view.ts +53 -0
- package/src/svelte/index.ts +18 -0
- package/src/svelte/svelte-ambient.d.ts +6 -0
- package/src/svelte/svelte-compile.test-helper.ts +6 -0
- package/src/svelte/symbol-props.ts +10 -0
- package/src/svelte/symbol-view.svelte +26 -0
- package/src/vue/index.ts +18 -0
- package/src/vue/symbol-view.ts +45 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { MaterialSymbols_700Bold } from '@expo-google-fonts/material-symbols/700Bold';
|
|
2
|
+
|
|
3
|
+
import type { IAndroidSymbolWeight } from '../..';
|
|
4
|
+
const weight: IAndroidSymbolWeight = {
|
|
5
|
+
name: 'MaterialSymbols_700Bold',
|
|
6
|
+
font: MaterialSymbols_700Bold,
|
|
7
|
+
};
|
|
8
|
+
export default weight;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { MaterialSymbols_200ExtraLight } from '@expo-google-fonts/material-symbols/200ExtraLight';
|
|
2
|
+
|
|
3
|
+
import type { IAndroidSymbolWeight } from '../..';
|
|
4
|
+
const weight: IAndroidSymbolWeight = {
|
|
5
|
+
name: 'MaterialSymbols_200ExtraLight',
|
|
6
|
+
font: MaterialSymbols_200ExtraLight,
|
|
7
|
+
};
|
|
8
|
+
export default weight;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { MaterialSymbols_300Light } from '@expo-google-fonts/material-symbols/300Light';
|
|
2
|
+
|
|
3
|
+
import type { IAndroidSymbolWeight } from '../..';
|
|
4
|
+
const weight: IAndroidSymbolWeight = {
|
|
5
|
+
name: 'MaterialSymbols_300Light',
|
|
6
|
+
font: MaterialSymbols_300Light,
|
|
7
|
+
};
|
|
8
|
+
export default weight;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { MaterialSymbols_500Medium } from '@expo-google-fonts/material-symbols/500Medium';
|
|
2
|
+
|
|
3
|
+
import type { IAndroidSymbolWeight } from '../..';
|
|
4
|
+
const weight: IAndroidSymbolWeight = {
|
|
5
|
+
name: 'MaterialSymbols_500Medium',
|
|
6
|
+
font: MaterialSymbols_500Medium,
|
|
7
|
+
};
|
|
8
|
+
export default weight;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { MaterialSymbols_400Regular } from '@expo-google-fonts/material-symbols/400Regular';
|
|
2
|
+
|
|
3
|
+
import type { IAndroidSymbolWeight } from '../..';
|
|
4
|
+
const weight: IAndroidSymbolWeight = {
|
|
5
|
+
name: 'MaterialSymbols_400Regular',
|
|
6
|
+
font: MaterialSymbols_400Regular,
|
|
7
|
+
};
|
|
8
|
+
export default weight;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { MaterialSymbols_600SemiBold } from '@expo-google-fonts/material-symbols/600SemiBold';
|
|
2
|
+
|
|
3
|
+
import type { IAndroidSymbolWeight } from '../..';
|
|
4
|
+
const weight: IAndroidSymbolWeight = {
|
|
5
|
+
name: 'MaterialSymbols_600SemiBold',
|
|
6
|
+
font: MaterialSymbols_600SemiBold,
|
|
7
|
+
};
|
|
8
|
+
export default weight;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { MaterialSymbols_100Thin } from '@expo-google-fonts/material-symbols/100Thin';
|
|
2
|
+
|
|
3
|
+
import type { IAndroidSymbolWeight } from '../..';
|
|
4
|
+
const weight: IAndroidSymbolWeight = {
|
|
5
|
+
name: 'MaterialSymbols_100Thin',
|
|
6
|
+
font: MaterialSymbols_100Thin,
|
|
7
|
+
};
|
|
8
|
+
export default weight;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export {
|
|
2
|
+
SYMBOL_MODULE_NAME,
|
|
3
|
+
ensureSymbolRegistered,
|
|
4
|
+
loadSymbolFont,
|
|
5
|
+
renderSymbolView,
|
|
6
|
+
symbolFontOf,
|
|
7
|
+
symbolViewName,
|
|
8
|
+
unstable_getMaterialSymbolSourceAsync,
|
|
9
|
+
watchSymbolFont,
|
|
10
|
+
} from './symbols';
|
|
11
|
+
export { androidSymbolToString } from './android';
|
|
12
|
+
export type { IAndroidSymbol, IAndroidSymbolWeight } from './android';
|
|
13
|
+
export type {
|
|
14
|
+
IAnimationEffect,
|
|
15
|
+
IAnimationSpec,
|
|
16
|
+
IAnimationType,
|
|
17
|
+
IContentMode,
|
|
18
|
+
ISymbolName,
|
|
19
|
+
ISymbolScale,
|
|
20
|
+
ISymbolType,
|
|
21
|
+
ISymbolViewProps,
|
|
22
|
+
ISymbolWeight,
|
|
23
|
+
IVariableAnimationSpec,
|
|
24
|
+
} from './symbol-types';
|
|
25
|
+
export type { SFSymbol } from 'sf-symbols-typescript';
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// Типы порта `SymbolModule.types.ts` из expo-symbols, без React-типов: `fallback` объявляет адаптер
|
|
2
|
+
import type { SFSymbol } from 'sf-symbols-typescript';
|
|
3
|
+
import type {
|
|
4
|
+
IAccessibilityProps,
|
|
5
|
+
IAriaProps,
|
|
6
|
+
IResponderProps,
|
|
7
|
+
} from '@symbiote-native/components';
|
|
8
|
+
import type {
|
|
9
|
+
IColorValue,
|
|
10
|
+
IStyleProp,
|
|
11
|
+
ISymbioteEvent,
|
|
12
|
+
IViewStyle,
|
|
13
|
+
} from '@symbiote-native/engine';
|
|
14
|
+
import type { IAndroidSymbol, IAndroidSymbolWeight } from './android';
|
|
15
|
+
|
|
16
|
+
export type ISymbolWeight =
|
|
17
|
+
| 'unspecified'
|
|
18
|
+
| 'ultraLight'
|
|
19
|
+
| 'thin'
|
|
20
|
+
| 'light'
|
|
21
|
+
| 'regular'
|
|
22
|
+
| 'medium'
|
|
23
|
+
| 'semibold'
|
|
24
|
+
| 'bold'
|
|
25
|
+
| 'heavy'
|
|
26
|
+
| 'black';
|
|
27
|
+
|
|
28
|
+
export type ISymbolScale =
|
|
29
|
+
| 'default'
|
|
30
|
+
| 'unspecified'
|
|
31
|
+
| 'small'
|
|
32
|
+
| 'medium'
|
|
33
|
+
| 'large';
|
|
34
|
+
|
|
35
|
+
/** How the image is resized to fit its container */
|
|
36
|
+
export type IContentMode =
|
|
37
|
+
| 'scaleToFill'
|
|
38
|
+
| 'scaleAspectFit'
|
|
39
|
+
| 'scaleAspectFill'
|
|
40
|
+
| 'redraw'
|
|
41
|
+
| 'center'
|
|
42
|
+
| 'top'
|
|
43
|
+
| 'bottom'
|
|
44
|
+
| 'left'
|
|
45
|
+
| 'right'
|
|
46
|
+
| 'topLeft'
|
|
47
|
+
| 'topRight'
|
|
48
|
+
| 'bottomLeft'
|
|
49
|
+
| 'bottomRight';
|
|
50
|
+
|
|
51
|
+
/** The symbol variant, `palette` takes its colors from `colors` */
|
|
52
|
+
export type ISymbolType =
|
|
53
|
+
| 'monochrome'
|
|
54
|
+
| 'hierarchical'
|
|
55
|
+
| 'palette'
|
|
56
|
+
| 'multicolor';
|
|
57
|
+
|
|
58
|
+
export type IAnimationType = 'bounce' | 'pulse' | 'scale';
|
|
59
|
+
|
|
60
|
+
export type IAnimationEffect = {
|
|
61
|
+
type: IAnimationType;
|
|
62
|
+
/**
|
|
63
|
+
* Whether the entire symbol animates or just the individual layers
|
|
64
|
+
* @default false
|
|
65
|
+
*/
|
|
66
|
+
wholeSymbol?: boolean;
|
|
67
|
+
direction?: 'up' | 'down';
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Эффекты складываются: каждое значение `true` добавляет ещё один
|
|
71
|
+
export type IVariableAnimationSpec = {
|
|
72
|
+
/** An effect that reverses each time it repeats */
|
|
73
|
+
reversing?: boolean;
|
|
74
|
+
/** An effect that does not reverse each time it repeats */
|
|
75
|
+
nonReversing?: boolean;
|
|
76
|
+
/** Each successive layer stays enabled until the end of the cycle, cancels `iterative` */
|
|
77
|
+
cumulative?: boolean;
|
|
78
|
+
/** Momentarily enables each layer of a symbol in sequence */
|
|
79
|
+
iterative?: boolean;
|
|
80
|
+
/** Hides inactive layers completely instead of drawing them with reduced opacity */
|
|
81
|
+
hideInactiveLayers?: boolean;
|
|
82
|
+
/** Draws inactive layers with reduced, but nonzero, opacity */
|
|
83
|
+
dimInactiveLayers?: boolean;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export type IAnimationSpec = {
|
|
87
|
+
effect?: IAnimationEffect;
|
|
88
|
+
/** If the animation repeats */
|
|
89
|
+
repeating?: boolean;
|
|
90
|
+
/** The number of times the animation repeats */
|
|
91
|
+
repeatCount?: number;
|
|
92
|
+
/** The duration of the animation in seconds */
|
|
93
|
+
speed?: number;
|
|
94
|
+
variableAnimationSpec?: IVariableAnimationSpec;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
type IViewSurface = IAccessibilityProps &
|
|
98
|
+
IAriaProps &
|
|
99
|
+
IResponderProps & {
|
|
100
|
+
style?: IStyleProp<IViewStyle>;
|
|
101
|
+
testID?: string;
|
|
102
|
+
nativeID?: string;
|
|
103
|
+
onLayout?: (event: ISymbioteEvent) => void;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export type ISymbolName =
|
|
107
|
+
| SFSymbol
|
|
108
|
+
| { ios?: SFSymbol; android?: IAndroidSymbol; web?: IAndroidSymbol };
|
|
109
|
+
|
|
110
|
+
export type ISymbolViewProps = IViewSurface & {
|
|
111
|
+
/** SF Symbols names are listed in the Apple SF Symbols app */
|
|
112
|
+
name: ISymbolName;
|
|
113
|
+
/**
|
|
114
|
+
* @default 'monochrome'
|
|
115
|
+
* @platform ios
|
|
116
|
+
*/
|
|
117
|
+
type?: ISymbolType;
|
|
118
|
+
/**
|
|
119
|
+
* @default 'unspecified'
|
|
120
|
+
* @platform ios
|
|
121
|
+
*/
|
|
122
|
+
scale?: ISymbolScale;
|
|
123
|
+
/**
|
|
124
|
+
* On Android import the weight from `androidWeights/{weight}`
|
|
125
|
+
* @default 'unspecified'
|
|
126
|
+
*/
|
|
127
|
+
weight?:
|
|
128
|
+
| ISymbolWeight
|
|
129
|
+
| { ios: ISymbolWeight; android: IAndroidSymbolWeight };
|
|
130
|
+
/**
|
|
131
|
+
* Colors of a `palette` symbol
|
|
132
|
+
* @platform ios
|
|
133
|
+
*/
|
|
134
|
+
colors?: IColorValue | IColorValue[];
|
|
135
|
+
/**
|
|
136
|
+
* @default 24
|
|
137
|
+
*/
|
|
138
|
+
size?: number;
|
|
139
|
+
tintColor?: IColorValue;
|
|
140
|
+
/**
|
|
141
|
+
* @default 'scaleAspectFit'
|
|
142
|
+
* @platform ios
|
|
143
|
+
*/
|
|
144
|
+
resizeMode?: IContentMode;
|
|
145
|
+
/** @platform ios */
|
|
146
|
+
animationSpec?: IAnimationSpec;
|
|
147
|
+
};
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// Ядро `SymbolView`, порт expo-symbols: нативный view на iOS, шрифт Material Symbols на Android
|
|
2
|
+
import { Platform, requireNativeViewManager } from 'expo-modules-core';
|
|
3
|
+
import { el, txt } from '@symbiote-native/components';
|
|
4
|
+
import type { IDescriptor } from '@symbiote-native/components';
|
|
5
|
+
import {
|
|
6
|
+
PlatformColor,
|
|
7
|
+
defineExpoNativeView,
|
|
8
|
+
isRecord,
|
|
9
|
+
processColor,
|
|
10
|
+
} from '@symbiote-native/engine';
|
|
11
|
+
import type { IColorValue } from '@symbiote-native/engine';
|
|
12
|
+
import { loadAsync, renderToImageAsync } from '@symbiote-native/font';
|
|
13
|
+
import { androidSymbolToString } from './android';
|
|
14
|
+
import type { IAndroidSymbol, IAndroidSymbolWeight } from './android';
|
|
15
|
+
import symbols from './android/symbols.json';
|
|
16
|
+
import regular from './android/weights/regular';
|
|
17
|
+
|
|
18
|
+
export const SYMBOL_MODULE_NAME = 'SymbolModule';
|
|
19
|
+
|
|
20
|
+
// Регистрация идёт при рендере: побочный эффект барреля теряется в release
|
|
21
|
+
const symbolView = defineExpoNativeView(
|
|
22
|
+
requireNativeViewManager,
|
|
23
|
+
SYMBOL_MODULE_NAME,
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
export const symbolViewName = symbolView.name;
|
|
27
|
+
export const ensureSymbolRegistered = symbolView.ensureRegistered;
|
|
28
|
+
|
|
29
|
+
const DEFAULT_SIZE = 24;
|
|
30
|
+
const WEB_DEFAULT_COLOR = '#7d9bd4';
|
|
31
|
+
const ANDROID_DEFAULT_COLOR = '@android:color/system_primary_dark';
|
|
32
|
+
|
|
33
|
+
function runsOnIos(): boolean {
|
|
34
|
+
return Platform.select({ ios: true, default: false });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function defaultSymbolColor(): IColorValue {
|
|
38
|
+
return Platform.select({ android: true, default: false })
|
|
39
|
+
? PlatformColor(ANDROID_DEFAULT_COLOR)
|
|
40
|
+
: WEB_DEFAULT_COLOR;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isAndroidSymbol(value: unknown): value is IAndroidSymbol {
|
|
44
|
+
return typeof value === 'string' && Object.hasOwn(symbols, value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function isAndroidWeight(value: unknown): value is IAndroidSymbolWeight {
|
|
48
|
+
return (
|
|
49
|
+
isRecord(value) &&
|
|
50
|
+
typeof value['name'] === 'string' &&
|
|
51
|
+
typeof value['font'] === 'number'
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Значение `name` или `weight` для iOS: строка как есть, у объекта берётся запись `ios`
|
|
56
|
+
function iosEntryOf(value: unknown): unknown {
|
|
57
|
+
return isRecord(value) ? value['ios'] : value;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function glyphNameOf(props: object): IAndroidSymbol | null {
|
|
61
|
+
const name: unknown = Reflect.get(props, 'name');
|
|
62
|
+
const key = Platform.select({ android: 'android', default: 'web' });
|
|
63
|
+
const entry = isRecord(name) ? name[key] : null;
|
|
64
|
+
return isAndroidSymbol(entry) ? entry : null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The font of the Android weight, the regular one when none is given */
|
|
68
|
+
export function symbolFontOf(weight: unknown): IAndroidSymbolWeight {
|
|
69
|
+
const platformWeight = isRecord(weight) ? weight['android'] : null;
|
|
70
|
+
return isAndroidWeight(platformWeight) ? platformWeight : regular;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function sizedStyle(props: object, size: number): unknown {
|
|
74
|
+
return [{ width: size, height: size }, Reflect.get(props, 'style')];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function sizeOf(props: object): number {
|
|
78
|
+
const size: unknown = Reflect.get(props, 'size');
|
|
79
|
+
return typeof size === 'number' && size !== 0 ? size : DEFAULT_SIZE;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function renderNative(props: object): IDescriptor | null {
|
|
83
|
+
if (!ensureSymbolRegistered()) return null;
|
|
84
|
+
const name = iosEntryOf(Reflect.get(props, 'name'));
|
|
85
|
+
if (!name) return null;
|
|
86
|
+
|
|
87
|
+
const colors: unknown = Reflect.get(props, 'colors');
|
|
88
|
+
const colorList = Array.isArray(colors) ? colors : colors ? [colors] : [];
|
|
89
|
+
const size = sizeOf(props);
|
|
90
|
+
const style: unknown = Reflect.get(props, 'style');
|
|
91
|
+
return el(symbolViewName(), {
|
|
92
|
+
...Object.fromEntries(
|
|
93
|
+
Object.entries(props).filter(([key]) => key !== 'tintColor'),
|
|
94
|
+
),
|
|
95
|
+
name,
|
|
96
|
+
style: style ? sizedStyle(props, size) : { width: size, height: size },
|
|
97
|
+
colors: colorList.map(color => processColor(color)),
|
|
98
|
+
tint: processColor(Reflect.get(props, 'tintColor')),
|
|
99
|
+
weight: iosEntryOf(Reflect.get(props, 'weight')),
|
|
100
|
+
animated: !!Reflect.get(props, 'animationSpec'),
|
|
101
|
+
type: Reflect.get(props, 'type') ?? 'monochrome',
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function renderGlyph(props: object, isFontLoaded: boolean): IDescriptor | null {
|
|
106
|
+
const name = glyphNameOf(props);
|
|
107
|
+
if (!name) return null;
|
|
108
|
+
const size = sizeOf(props);
|
|
109
|
+
const style = sizedStyle(props, size);
|
|
110
|
+
if (!isFontLoaded) return el('view', { style });
|
|
111
|
+
const tint: unknown = Reflect.get(props, 'tintColor');
|
|
112
|
+
const glyph = txt(
|
|
113
|
+
{
|
|
114
|
+
style: {
|
|
115
|
+
fontFamily: symbolFontOf(Reflect.get(props, 'weight')).name,
|
|
116
|
+
color: tint ?? defaultSymbolColor(),
|
|
117
|
+
fontSize: size,
|
|
118
|
+
lineHeight: size,
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
[androidSymbolToString(name) ?? ''],
|
|
122
|
+
);
|
|
123
|
+
return el('view', { style }, [glyph]);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The symbol as a descriptor, `null` means the caller renders its own `fallback`
|
|
128
|
+
* `isFontLoaded` only matters off iOS, where the glyph needs the loaded font
|
|
129
|
+
*/
|
|
130
|
+
export function renderSymbolView(
|
|
131
|
+
props: object,
|
|
132
|
+
isFontLoaded: boolean,
|
|
133
|
+
): IDescriptor | null {
|
|
134
|
+
return runsOnIos() ? renderNative(props) : renderGlyph(props, isFontLoaded);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Loads the Material Symbols font, false when it fails and the symbol stays an empty View */
|
|
138
|
+
export async function loadSymbolFont(props: object): Promise<boolean> {
|
|
139
|
+
if (runsOnIos()) return true;
|
|
140
|
+
const font = symbolFontOf(Reflect.get(props, 'weight'));
|
|
141
|
+
const name = glyphNameOf(props);
|
|
142
|
+
try {
|
|
143
|
+
await loadAsync({
|
|
144
|
+
[font.name]: {
|
|
145
|
+
uri: font.font,
|
|
146
|
+
testString: name ? (androidSymbolToString(name) ?? undefined) : undefined,
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
return true;
|
|
150
|
+
} catch {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Loads the font once and reports the result, the returned function drops a late report
|
|
157
|
+
* Every adapter calls it from its own mount hook, so the lifecycle logic is written once
|
|
158
|
+
*/
|
|
159
|
+
export function watchSymbolFont(
|
|
160
|
+
props: object,
|
|
161
|
+
onLoaded: (isLoaded: boolean) => void,
|
|
162
|
+
): () => void {
|
|
163
|
+
let isActive = true;
|
|
164
|
+
void loadSymbolFont(props).then(isLoaded => {
|
|
165
|
+
if (isActive) onLoaded(isLoaded);
|
|
166
|
+
});
|
|
167
|
+
return () => {
|
|
168
|
+
isActive = false;
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Renders a Material Symbol to an image source, for APIs that take an image such as tab bar icons
|
|
174
|
+
* @platform android
|
|
175
|
+
*/
|
|
176
|
+
export async function unstable_getMaterialSymbolSourceAsync(
|
|
177
|
+
symbol: IAndroidSymbol | null,
|
|
178
|
+
size: number,
|
|
179
|
+
color: string,
|
|
180
|
+
): Promise<{ uri: string; width: number; height: number; scale: number } | null> {
|
|
181
|
+
if (runsOnIos() || !symbol) return null;
|
|
182
|
+
const glyph = androidSymbolToString(symbol);
|
|
183
|
+
if (!glyph) return null;
|
|
184
|
+
const font = regular;
|
|
185
|
+
await loadAsync({ [font.name]: font.font });
|
|
186
|
+
return renderToImageAsync(glyph, {
|
|
187
|
+
fontFamily: font.name,
|
|
188
|
+
size,
|
|
189
|
+
color,
|
|
190
|
+
lineHeight: size,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// @symbiote-native/symbols/react: компонент `SymbolView` поверх общего ядра
|
|
2
|
+
|
|
3
|
+
export { SymbolView } from './symbol-view';
|
|
4
|
+
export type { ISymbolViewReactProps as ISymbolViewProps } from './symbol-view';
|
|
5
|
+
export {
|
|
6
|
+
SYMBOL_MODULE_NAME,
|
|
7
|
+
androidSymbolToString,
|
|
8
|
+
symbolViewName,
|
|
9
|
+
unstable_getMaterialSymbolSourceAsync,
|
|
10
|
+
type IAndroidSymbol,
|
|
11
|
+
type IAnimationSpec,
|
|
12
|
+
type IContentMode,
|
|
13
|
+
type ISymbolName,
|
|
14
|
+
type ISymbolScale,
|
|
15
|
+
type ISymbolType,
|
|
16
|
+
type ISymbolWeight,
|
|
17
|
+
type SFSymbol,
|
|
18
|
+
} from '../core';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Fragment, createElement, useEffect, useState } from 'react';
|
|
2
|
+
import type { ReactElement, ReactNode } from 'react';
|
|
3
|
+
import { descriptorToReact } from '@symbiote-native/react';
|
|
4
|
+
import type { IViewProps } from '@symbiote-native/react';
|
|
5
|
+
import { renderSymbolView, watchSymbolFont } from '../core';
|
|
6
|
+
import type { ISymbolViewProps } from '../core';
|
|
7
|
+
|
|
8
|
+
// Поверхность View как у `ViewProps` в upstream, `fallback` и `className` свои у React
|
|
9
|
+
export type ISymbolViewReactProps = ISymbolViewProps &
|
|
10
|
+
Omit<IViewProps, keyof ISymbolViewProps | 'children'> & {
|
|
11
|
+
/** Rendered when a symbol for the current platform is not defined */
|
|
12
|
+
fallback?: ReactNode;
|
|
13
|
+
className?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/** React twin of `expo-symbols`' `SymbolView`, SF Symbols on iOS and Material Symbols elsewhere */
|
|
17
|
+
export function SymbolView(props: ISymbolViewReactProps): ReactElement {
|
|
18
|
+
const { fallback, ...rest } = props;
|
|
19
|
+
const [isFontLoaded, setIsFontLoaded] = useState(false);
|
|
20
|
+
|
|
21
|
+
// Шрифт грузится один раз при монтировании, как в upstream, смена веса его не перезагружает
|
|
22
|
+
useEffect(() => watchSymbolFont(rest, setIsFontLoaded), []);
|
|
23
|
+
|
|
24
|
+
const descriptor = renderSymbolView(rest, isFontLoaded);
|
|
25
|
+
if (!descriptor) return createElement(Fragment, null, fallback);
|
|
26
|
+
return createElement(
|
|
27
|
+
descriptor.type,
|
|
28
|
+
descriptor.props,
|
|
29
|
+
...descriptor.children.map(child =>
|
|
30
|
+
typeof child === 'string' ? child : descriptorToReact(child),
|
|
31
|
+
),
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// @symbiote-native/symbols/solid: компонент `SymbolView` поверх общего ядра
|
|
2
|
+
|
|
3
|
+
export { SymbolView } from './symbol-view';
|
|
4
|
+
export type { ISymbolViewSolidProps as ISymbolViewProps } from './symbol-view';
|
|
5
|
+
export {
|
|
6
|
+
SYMBOL_MODULE_NAME,
|
|
7
|
+
androidSymbolToString,
|
|
8
|
+
symbolViewName,
|
|
9
|
+
unstable_getMaterialSymbolSourceAsync,
|
|
10
|
+
type IAndroidSymbol,
|
|
11
|
+
type IAnimationSpec,
|
|
12
|
+
type IContentMode,
|
|
13
|
+
type ISymbolName,
|
|
14
|
+
type ISymbolScale,
|
|
15
|
+
type ISymbolType,
|
|
16
|
+
type ISymbolWeight,
|
|
17
|
+
type SFSymbol,
|
|
18
|
+
} from '../core';
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createMemo,
|
|
3
|
+
createSignal,
|
|
4
|
+
onCleanup,
|
|
5
|
+
onMount,
|
|
6
|
+
splitProps,
|
|
7
|
+
untrack,
|
|
8
|
+
} from 'solid-js';
|
|
9
|
+
import type { JSX } from 'solid-js';
|
|
10
|
+
import { el } from '@symbiote-native/components';
|
|
11
|
+
import { descriptorToSolid } from '@symbiote-native/solid';
|
|
12
|
+
import type { IViewProps } from '@symbiote-native/solid';
|
|
13
|
+
import type { IClassNameValue } from '@symbiote-native/engine';
|
|
14
|
+
import { renderSymbolView, watchSymbolFont } from '../core';
|
|
15
|
+
import type { ISymbolViewProps } from '../core';
|
|
16
|
+
|
|
17
|
+
// Поверхность View как у `ViewProps` в upstream, `fallback` и `class` свои у Solid
|
|
18
|
+
export type ISymbolViewSolidProps = ISymbolViewProps &
|
|
19
|
+
Omit<IViewProps, keyof ISymbolViewProps | 'children'> & {
|
|
20
|
+
/** Rendered when a symbol for the current platform is not defined */
|
|
21
|
+
fallback?: JSX.Element;
|
|
22
|
+
class?: IClassNameValue;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const FALLBACK_SHAPE = 'fallback';
|
|
26
|
+
|
|
27
|
+
/** Solid twin of `expo-symbols`' `SymbolView`, SF Symbols on iOS and Material Symbols elsewhere */
|
|
28
|
+
export function SymbolView(props: ISymbolViewSolidProps): JSX.Element {
|
|
29
|
+
const [local, rest] = splitProps(props, ['fallback']);
|
|
30
|
+
const [isFontLoaded, setIsFontLoaded] = createSignal(false);
|
|
31
|
+
|
|
32
|
+
// Шрифт грузится один раз при монтировании, как в upstream
|
|
33
|
+
onMount(() => onCleanup(watchSymbolFont({ ...rest }, setIsFontLoaded)));
|
|
34
|
+
|
|
35
|
+
// Мост Solid требует постоянную форму дескриптора, а она меняется вместе с загрузкой шрифта
|
|
36
|
+
// Поэтому узел пересобирается, когда меняется форма, а обычные пропсы он обновляет сам
|
|
37
|
+
const shape = createMemo(() => {
|
|
38
|
+
const descriptor = renderSymbolView({ ...rest }, isFontLoaded());
|
|
39
|
+
return descriptor
|
|
40
|
+
? `${descriptor.type}:${descriptor.children.length}`
|
|
41
|
+
: FALLBACK_SHAPE;
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return createMemo(() => {
|
|
45
|
+
if (shape() === FALLBACK_SHAPE) return local.fallback;
|
|
46
|
+
return untrack(() => {
|
|
47
|
+
const isLoadedAtBuild = isFontLoaded();
|
|
48
|
+
return descriptorToSolid(
|
|
49
|
+
() => renderSymbolView({ ...rest }, isLoadedAtBuild) ?? el('view', {}),
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// @symbiote-native/symbols/svelte: компонент `SymbolView` поверх общего ядра
|
|
2
|
+
|
|
3
|
+
export { default as SymbolView } from './symbol-view.svelte';
|
|
4
|
+
export type { ISymbolViewSvelteProps as ISymbolViewProps } from './symbol-props';
|
|
5
|
+
export {
|
|
6
|
+
SYMBOL_MODULE_NAME,
|
|
7
|
+
androidSymbolToString,
|
|
8
|
+
symbolViewName,
|
|
9
|
+
unstable_getMaterialSymbolSourceAsync,
|
|
10
|
+
type IAndroidSymbol,
|
|
11
|
+
type IAnimationSpec,
|
|
12
|
+
type IContentMode,
|
|
13
|
+
type ISymbolName,
|
|
14
|
+
type ISymbolScale,
|
|
15
|
+
type ISymbolType,
|
|
16
|
+
type ISymbolWeight,
|
|
17
|
+
type SFSymbol,
|
|
18
|
+
} from '../core';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Pulls in svelte's ambient global declarations ($state/$effect/...) for this package's tsc
|
|
2
|
+
// program. adapters/svelte gets these for free because render.ts does a real value import from
|
|
3
|
+
// 'svelte' (any file in a program importing a module loads its ambient globals for the whole
|
|
4
|
+
// program); this package has no such import, so a triple-slash reference does the same job
|
|
5
|
+
// explicitly rather than relying on an incidental side-effect.
|
|
6
|
+
/// <reference types="svelte" />
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { IViewProps } from '@symbiote-native/svelte';
|
|
3
|
+
import type { ISymbolViewProps } from '../core';
|
|
4
|
+
|
|
5
|
+
// Поверхность View как у `ViewProps` в upstream, запасной вариант идёт сниппетом `fallback`
|
|
6
|
+
export type ISymbolViewSvelteProps = ISymbolViewProps &
|
|
7
|
+
Omit<IViewProps, keyof ISymbolViewProps | 'children'> & {
|
|
8
|
+
/** Rendered when a symbol for the current platform is not defined */
|
|
9
|
+
fallback?: Snippet;
|
|
10
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { untrack } from 'svelte';
|
|
3
|
+
import DescriptorHost from '@symbiote-native/svelte/descriptor-host';
|
|
4
|
+
import { renderSymbolView, watchSymbolFont } from '../core';
|
|
5
|
+
import type { ISymbolViewSvelteProps } from './symbol-props';
|
|
6
|
+
|
|
7
|
+
const { fallback, ...rest }: ISymbolViewSvelteProps = $props();
|
|
8
|
+
let isFontLoaded = $state(false);
|
|
9
|
+
|
|
10
|
+
// Шрифт грузится один раз при монтировании, как в upstream, смена пропсов его не перезагружает
|
|
11
|
+
$effect(() =>
|
|
12
|
+
untrack(() =>
|
|
13
|
+
watchSymbolFont({ ...rest }, isLoaded => {
|
|
14
|
+
isFontLoaded = isLoaded;
|
|
15
|
+
}),
|
|
16
|
+
),
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
const descriptor = $derived(renderSymbolView({ ...rest }, isFontLoaded));
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
{#if descriptor}
|
|
23
|
+
<DescriptorHost {descriptor} />
|
|
24
|
+
{:else}
|
|
25
|
+
{@render fallback?.()}
|
|
26
|
+
{/if}
|
package/src/vue/index.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// @symbiote-native/symbols/vue: компонент `SymbolView` поверх общего ядра
|
|
2
|
+
|
|
3
|
+
export { SymbolView } from './symbol-view';
|
|
4
|
+
export type { ISymbolViewVueProps as ISymbolViewProps } from './symbol-view';
|
|
5
|
+
export {
|
|
6
|
+
SYMBOL_MODULE_NAME,
|
|
7
|
+
androidSymbolToString,
|
|
8
|
+
symbolViewName,
|
|
9
|
+
unstable_getMaterialSymbolSourceAsync,
|
|
10
|
+
type IAndroidSymbol,
|
|
11
|
+
type IAnimationSpec,
|
|
12
|
+
type IContentMode,
|
|
13
|
+
type ISymbolName,
|
|
14
|
+
type ISymbolScale,
|
|
15
|
+
type ISymbolType,
|
|
16
|
+
type ISymbolWeight,
|
|
17
|
+
type SFSymbol,
|
|
18
|
+
} from '../core';
|