react-native-icon-switcher 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/IconSwitcher.podspec +20 -0
- package/LICENSE +20 -0
- package/README.md +252 -2
- package/android/build.gradle +51 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/iconswitcher/IconSwitcher.kt +380 -0
- package/android/src/main/java/com/iconswitcher/IconSwitcherExceptions.kt +24 -0
- package/android/src/main/java/com/iconswitcher/IconSwitcherModule.kt +67 -0
- package/android/src/main/java/com/iconswitcher/IconSwitcherPackage.kt +30 -0
- package/android/src/test/AndroidManifest.xml +39 -0
- package/android/src/test/java/com/iconswitcher/IconSwitcherTest.kt +392 -0
- package/app.plugin.js +1 -0
- package/ios/ISIconSwitcher.h +49 -0
- package/ios/ISIconSwitcher.m +205 -0
- package/ios/IconSwitcher.h +5 -0
- package/ios/IconSwitcher.mm +100 -0
- package/jest/index.d.ts +31 -0
- package/jest/index.js +98 -0
- package/lib/module/NativeIconSwitcher.js +5 -0
- package/lib/module/NativeIconSwitcher.js.map +1 -0
- package/lib/module/config.js +6 -0
- package/lib/module/config.js.map +1 -0
- package/lib/module/errors.js +28 -0
- package/lib/module/errors.js.map +1 -0
- package/lib/module/index.js +69 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/register.js +2 -0
- package/lib/module/register.js.map +1 -0
- package/lib/module/remote/index.js +119 -0
- package/lib/module/remote/index.js.map +1 -0
- package/lib/module/types.js +2 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/typetests/icon-name.typetest.js +8 -0
- package/lib/module/typetests/icon-name.typetest.js.map +1 -0
- package/lib/module/typetests/types-in-sync.typetest.js +7 -0
- package/lib/module/typetests/types-in-sync.typetest.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeIconSwitcher.d.ts +18 -0
- package/lib/typescript/src/NativeIconSwitcher.d.ts.map +1 -0
- package/lib/typescript/src/config.d.ts +4 -0
- package/lib/typescript/src/config.d.ts.map +1 -0
- package/lib/typescript/src/errors.d.ts +7 -0
- package/lib/typescript/src/errors.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +20 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/register.d.ts +7 -0
- package/lib/typescript/src/register.d.ts.map +1 -0
- package/lib/typescript/src/remote/index.d.ts +17 -0
- package/lib/typescript/src/remote/index.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +59 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +251 -4
- package/plugin/build/cli.d.ts +3 -0
- package/plugin/build/cli.js +89 -0
- package/plugin/build/core/assets.d.ts +80 -0
- package/plugin/build/core/assets.js +463 -0
- package/plugin/build/core/errors.d.ts +4 -0
- package/plugin/build/core/errors.js +11 -0
- package/plugin/build/core/validate.d.ts +6 -0
- package/plugin/build/core/validate.js +229 -0
- package/plugin/build/index.d.ts +6 -0
- package/plugin/build/index.js +134 -0
- package/plugin/build/load-config.d.ts +3 -0
- package/plugin/build/load-config.js +50 -0
- package/plugin/build/paths.d.ts +15 -0
- package/plugin/build/paths.js +153 -0
- package/plugin/build/sync.d.ts +12 -0
- package/plugin/build/sync.js +162 -0
- package/plugin/build/transforms/android.d.ts +5 -0
- package/plugin/build/transforms/android.js +212 -0
- package/plugin/build/transforms/errors.d.ts +4 -0
- package/plugin/build/transforms/errors.js +11 -0
- package/plugin/build/transforms/index.d.ts +4 -0
- package/plugin/build/transforms/index.js +12 -0
- package/plugin/build/transforms/ios.d.ts +4 -0
- package/plugin/build/transforms/ios.js +111 -0
- package/plugin/build/transforms/sampleConfigs.d.ts +3 -0
- package/plugin/build/transforms/sampleConfigs.js +33 -0
- package/plugin/build/transforms/types.d.ts +2 -0
- package/plugin/build/transforms/types.js +14 -0
- package/plugin/build/types.d.ts +61 -0
- package/plugin/build/types.js +5 -0
- package/src/NativeIconSwitcher.ts +17 -0
- package/src/config.ts +15 -0
- package/src/errors.ts +38 -0
- package/src/index.tsx +78 -0
- package/src/register.ts +4 -0
- package/src/remote/index.ts +142 -0
- package/src/types.ts +59 -0
- package/src/typetests/icon-name.typetest.ts +12 -0
- package/src/typetests/types-in-sync.typetest.ts +16 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { TurboModule } from 'react-native';
|
|
2
|
+
import { TurboModuleRegistry } from 'react-native';
|
|
3
|
+
|
|
4
|
+
export interface IconSwitcherConstants {
|
|
5
|
+
isSupported: boolean;
|
|
6
|
+
iconNames: string[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface Spec extends TurboModule {
|
|
10
|
+
getConstants(): IconSwitcherConstants;
|
|
11
|
+
getCurrent(): Promise<string>;
|
|
12
|
+
// Plain `string` status keeps codegen parsers happy on older RN versions.
|
|
13
|
+
set(name: string): Promise<{ status: string }>;
|
|
14
|
+
applyPending(): Promise<{ status: string }>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default TurboModuleRegistry.get<Spec>('IconSwitcher');
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { IconSwitcherConfig } from './types';
|
|
2
|
+
|
|
3
|
+
export type {
|
|
4
|
+
AndroidAdaptiveIcon,
|
|
5
|
+
HexColor,
|
|
6
|
+
IconSource,
|
|
7
|
+
IconSpec,
|
|
8
|
+
IconSwitcherConfig,
|
|
9
|
+
ImagePath,
|
|
10
|
+
IosIconVariants,
|
|
11
|
+
} from './types';
|
|
12
|
+
|
|
13
|
+
export function defineIcons(cfg: IconSwitcherConfig): IconSwitcherConfig {
|
|
14
|
+
return cfg;
|
|
15
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export type IconSwitcherErrorCode =
|
|
2
|
+
'unsupported' | 'unknown_icon' | 'os_rejected' | 'foreground';
|
|
3
|
+
|
|
4
|
+
export class IconSwitcherError extends Error {
|
|
5
|
+
readonly code: IconSwitcherErrorCode;
|
|
6
|
+
|
|
7
|
+
constructor(code: IconSwitcherErrorCode, message?: string) {
|
|
8
|
+
super(message ?? code);
|
|
9
|
+
this.name = 'IconSwitcherError';
|
|
10
|
+
this.code = code;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const _NATIVE_TO_JS: Record<string, IconSwitcherErrorCode> = {
|
|
15
|
+
ERR_UNSUPPORTED: 'unsupported',
|
|
16
|
+
ERR_UNKNOWN_ICON: 'unknown_icon',
|
|
17
|
+
ERR_OS_REJECTED: 'os_rejected',
|
|
18
|
+
ERR_FOREGROUND: 'foreground',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export function mapNativeError(error: unknown): IconSwitcherError {
|
|
22
|
+
if (error instanceof IconSwitcherError) {
|
|
23
|
+
return error;
|
|
24
|
+
}
|
|
25
|
+
const code =
|
|
26
|
+
typeof error === 'object' && error !== null && 'code' in error
|
|
27
|
+
? String((error as { code: unknown }).code)
|
|
28
|
+
: undefined;
|
|
29
|
+
const mapped = code ? _NATIVE_TO_JS[code] : undefined;
|
|
30
|
+
if (mapped) {
|
|
31
|
+
const message = error instanceof Error ? error.message : undefined;
|
|
32
|
+
return new IconSwitcherError(mapped, message);
|
|
33
|
+
}
|
|
34
|
+
return new IconSwitcherError(
|
|
35
|
+
'os_rejected',
|
|
36
|
+
error instanceof Error ? error.message : String(error)
|
|
37
|
+
);
|
|
38
|
+
}
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import native from './NativeIconSwitcher';
|
|
2
|
+
import { IconSwitcherError, mapNativeError } from './errors';
|
|
3
|
+
import type { IconName } from './register';
|
|
4
|
+
|
|
5
|
+
function _constants(): { isSupported: boolean; iconNames: string[] } {
|
|
6
|
+
if (native == null) {
|
|
7
|
+
return { isSupported: false, iconNames: [] };
|
|
8
|
+
}
|
|
9
|
+
return native.getConstants();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function _rejectUnsupported(): never {
|
|
13
|
+
throw new IconSwitcherError(
|
|
14
|
+
'unsupported',
|
|
15
|
+
'Alternate app icons are not supported'
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function _assertKnownIcon(name: string): void {
|
|
20
|
+
if (!_constants().iconNames.includes(name)) {
|
|
21
|
+
throw new IconSwitcherError('unknown_icon', `Unknown icon: ${name}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const IconSwitcher = {
|
|
26
|
+
get isSupported(): boolean {
|
|
27
|
+
return _constants().isSupported;
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
list(): IconName[] {
|
|
31
|
+
return [..._constants().iconNames] as IconName[];
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
async getCurrent(): Promise<IconName> {
|
|
35
|
+
if (native == null) {
|
|
36
|
+
_rejectUnsupported();
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const current = await native.getCurrent();
|
|
40
|
+
return current as IconName;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
throw mapNativeError(error);
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
async set(name: IconName): Promise<{ status: 'applied' | 'pending' }> {
|
|
47
|
+
if (!_constants().isSupported) {
|
|
48
|
+
_rejectUnsupported();
|
|
49
|
+
}
|
|
50
|
+
_assertKnownIcon(name as string);
|
|
51
|
+
try {
|
|
52
|
+
const result = await native!.set(name as string);
|
|
53
|
+
return result as { status: 'applied' | 'pending' };
|
|
54
|
+
} catch (error) {
|
|
55
|
+
throw mapNativeError(error);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
async reset(): Promise<{ status: 'applied' | 'pending' }> {
|
|
60
|
+
return IconSwitcher.set('default' as IconName);
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
async applyPending(): Promise<{ status: 'applied' | 'none' }> {
|
|
64
|
+
if (native == null) {
|
|
65
|
+
_rejectUnsupported();
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const result = await native.applyPending();
|
|
69
|
+
return result as { status: 'applied' | 'none' };
|
|
70
|
+
} catch (error) {
|
|
71
|
+
throw mapNativeError(error);
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export { IconSwitcher, IconSwitcherError };
|
|
77
|
+
export type { IconName, Register } from './register';
|
|
78
|
+
export default IconSwitcher;
|
package/src/register.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { AppState, Platform } from 'react-native';
|
|
2
|
+
|
|
3
|
+
import IconSwitcher from '../index';
|
|
4
|
+
import type { IconName } from '../register';
|
|
5
|
+
|
|
6
|
+
const STORAGE_KEY_APPLIED = 'icon-switcher:lastApplied';
|
|
7
|
+
const STORAGE_KEY_FAILED = 'icon-switcher:lastFailed';
|
|
8
|
+
|
|
9
|
+
const DEFAULT_RETRY_AFTER_MS = 24 * 60 * 60 * 1000;
|
|
10
|
+
|
|
11
|
+
export interface SyncIconStorage {
|
|
12
|
+
getItem(key: string): Promise<string | null>;
|
|
13
|
+
setItem(key: string, value: string): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface SyncIconOptions {
|
|
17
|
+
fetchChoice: () => Promise<string | null | undefined>;
|
|
18
|
+
storage?: SyncIconStorage;
|
|
19
|
+
/** How long to skip re-applying a name after `set` failed. Default 24 h. */
|
|
20
|
+
retryAfterMs?: number;
|
|
21
|
+
onError?: (error: unknown) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function _createMemoryStorage(): SyncIconStorage {
|
|
25
|
+
const data = new Map<string, string>();
|
|
26
|
+
return {
|
|
27
|
+
getItem: async (key) => data.get(key) ?? null,
|
|
28
|
+
setItem: async (key, value) => {
|
|
29
|
+
data.set(key, value);
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function _encodeFailed(name: string): string {
|
|
35
|
+
return JSON.stringify({ name, at: Date.now() });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function _decodeFailed(
|
|
39
|
+
raw: string | null
|
|
40
|
+
): { name: string; at: number } | null {
|
|
41
|
+
if (!raw || raw.length === 0) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const parsed = JSON.parse(raw) as { name?: string; at?: number };
|
|
46
|
+
if (typeof parsed.name === 'string' && typeof parsed.at === 'number') {
|
|
47
|
+
return { name: parsed.name, at: parsed.at };
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
// legacy: plain icon name with no timestamp — eligible for retry immediately
|
|
51
|
+
return { name: raw, at: 0 };
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function _isFailedBlocked(
|
|
57
|
+
failed: { name: string; at: number } | null,
|
|
58
|
+
choice: string,
|
|
59
|
+
retryAfterMs: number
|
|
60
|
+
): boolean {
|
|
61
|
+
if (!failed || failed.name !== choice) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
return Date.now() - failed.at < retryAfterMs;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Fetches the remote icon choice, validates it, and applies it via {@link IconSwitcher}.
|
|
69
|
+
* iOS: runs while the app is `active`. Android: calls `set` immediately (native may defer).
|
|
70
|
+
*/
|
|
71
|
+
export async function syncIcon(options: SyncIconOptions): Promise<() => void> {
|
|
72
|
+
const storage = options.storage ?? _createMemoryStorage();
|
|
73
|
+
const retryAfterMs = options.retryAfterMs ?? DEFAULT_RETRY_AFTER_MS;
|
|
74
|
+
const onError = options.onError;
|
|
75
|
+
|
|
76
|
+
let inFlight = false;
|
|
77
|
+
|
|
78
|
+
const run = async (): Promise<void> => {
|
|
79
|
+
if (inFlight) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
inFlight = true;
|
|
83
|
+
try {
|
|
84
|
+
const choice = await options.fetchChoice();
|
|
85
|
+
if (choice == null || choice === '') {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (!IconSwitcher.list().includes(choice as IconName)) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (!IconSwitcher.isSupported) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const failedRaw = await storage.getItem(STORAGE_KEY_FAILED);
|
|
96
|
+
const failed = _decodeFailed(failedRaw);
|
|
97
|
+
if (_isFailedBlocked(failed, choice, retryAfterMs)) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const current = await IconSwitcher.getCurrent();
|
|
102
|
+
if (choice === current) {
|
|
103
|
+
await storage.setItem(STORAGE_KEY_APPLIED, choice);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const result = await IconSwitcher.set(choice as IconName);
|
|
109
|
+
if (result.status === 'pending') {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
await storage.setItem(STORAGE_KEY_APPLIED, choice);
|
|
113
|
+
await storage.setItem(STORAGE_KEY_FAILED, '');
|
|
114
|
+
} catch {
|
|
115
|
+
await storage.setItem(STORAGE_KEY_FAILED, _encodeFailed(choice));
|
|
116
|
+
}
|
|
117
|
+
} catch (error) {
|
|
118
|
+
onError?.(error);
|
|
119
|
+
} finally {
|
|
120
|
+
inFlight = false;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const runWhenActive = (): void => {
|
|
125
|
+
if (AppState.currentState === 'active') {
|
|
126
|
+
void run();
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
if (Platform.OS === 'ios') {
|
|
131
|
+
const subscription = AppState.addEventListener('change', (state) => {
|
|
132
|
+
if (state === 'active') {
|
|
133
|
+
void run();
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
runWhenActive();
|
|
137
|
+
return () => subscription.remove();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
await run();
|
|
141
|
+
return () => {};
|
|
142
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/** A path to a square PNG master, ideally 1024 px. Relative to the project root. */
|
|
2
|
+
export type ImagePath = string;
|
|
3
|
+
|
|
4
|
+
/** Hex colour, `#RGB` or `#RRGGBB`. */
|
|
5
|
+
export type HexColor = string;
|
|
6
|
+
|
|
7
|
+
export interface IosIconVariants {
|
|
8
|
+
light: ImagePath;
|
|
9
|
+
/** Phase 2. */
|
|
10
|
+
dark?: ImagePath;
|
|
11
|
+
/** Phase 2. */
|
|
12
|
+
tinted?: ImagePath;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface AndroidAdaptiveIcon {
|
|
16
|
+
foreground: ImagePath;
|
|
17
|
+
background: ImagePath | HexColor;
|
|
18
|
+
/** Phase 2. Android 13+ themed icons. */
|
|
19
|
+
monochrome?: ImagePath;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface IconSpec {
|
|
23
|
+
ios?: ImagePath | IosIconVariants;
|
|
24
|
+
android?: ImagePath | AndroidAdaptiveIcon;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** A bare path means "same master for both platforms". */
|
|
28
|
+
export type IconSource = ImagePath | IconSpec;
|
|
29
|
+
|
|
30
|
+
export interface IconSwitcherConfig {
|
|
31
|
+
/** Primary icon. Omit on Expo to reuse `icon` / `android.adaptiveIcon`. */
|
|
32
|
+
default?: IconSource;
|
|
33
|
+
icons: Record<string, IconSource>;
|
|
34
|
+
ios?: {
|
|
35
|
+
/** Background colour that transparent pixels are flattened onto. Default `#FFFFFF`. */
|
|
36
|
+
flattenOn?: HexColor;
|
|
37
|
+
};
|
|
38
|
+
android?: {
|
|
39
|
+
/** Default `'background'`. */
|
|
40
|
+
applyOn?: 'background' | 'immediate';
|
|
41
|
+
/** Lower-cased `Build.BRAND` values where switching is disabled. Default `[]`. */
|
|
42
|
+
skipBrands?: string[];
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Config after validation and defaults; every icon normalised to per-platform specs. */
|
|
47
|
+
export interface ResolvedIcon {
|
|
48
|
+
name: string;
|
|
49
|
+
isDefault: boolean;
|
|
50
|
+
ios: IosIconVariants | null;
|
|
51
|
+
android: AndroidAdaptiveIcon | { legacy: ImagePath } | null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface ResolvedConfig {
|
|
55
|
+
/** `default` first, then alternates in declaration order. */
|
|
56
|
+
icons: ResolvedIcon[];
|
|
57
|
+
ios: { flattenOn: HexColor };
|
|
58
|
+
android: { applyOn: 'background' | 'immediate'; skipBrands: string[] };
|
|
59
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import 'react-native-icon-switcher';
|
|
2
|
+
import IconSwitcher from 'react-native-icon-switcher';
|
|
3
|
+
|
|
4
|
+
declare module 'react-native-icon-switcher' {
|
|
5
|
+
interface Register {
|
|
6
|
+
icons: 'default' | 'halloween' | 'midnight';
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
IconSwitcher.set('halloween');
|
|
11
|
+
// @ts-expect-error icon name must come from Register.icons
|
|
12
|
+
IconSwitcher.set('typo');
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// plugin/src/types.ts mirrors this package's config types because the plugin is compiled on its
|
|
2
|
+
// own (rootDir: plugin/src). This fails typecheck the moment the two copies drift apart.
|
|
3
|
+
import type * as Runtime from '../types';
|
|
4
|
+
import type * as Plugin from '../../plugin/src/types';
|
|
5
|
+
|
|
6
|
+
type Equal<X, Y> =
|
|
7
|
+
(<T>() => T extends X ? 1 : 2) extends <T>() => T extends Y ? 1 : 2
|
|
8
|
+
? true
|
|
9
|
+
: false;
|
|
10
|
+
|
|
11
|
+
export const typesInSync: [
|
|
12
|
+
Equal<Runtime.IconSource, Plugin.IconSource>,
|
|
13
|
+
Equal<Runtime.IconSwitcherConfig, Plugin.IconSwitcherConfig>,
|
|
14
|
+
Equal<Runtime.ResolvedIcon, Plugin.ResolvedIcon>,
|
|
15
|
+
Equal<Runtime.ResolvedConfig, Plugin.ResolvedConfig>,
|
|
16
|
+
] = [true, true, true, true];
|