@ng-native/expo 0.17.0 → 0.18.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.
@@ -1 +1 @@
1
- {"version":3,"file":"screen-capture.js","sourceRoot":"","sources":["../src/screen-capture.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `ScreenCapture`, bound to `expo-screen-capture`: keeping a screen out of screenshots and\n * recordings, and knowing when one was taken.\n *\n * ```ts\n * private readonly capture = inject(ScreenCapture);\n *\n * constructor() {\n * void this.capture.prevent('card-details');\n * inject(DestroyRef).onDestroy(() => void this.capture.allow('card-details'));\n * }\n * ```\n *\n * `prevent()` and `allow()` take a key, and capture stays prevented while any key is still held:\n * two screens that each prevent it do not undo each other. Keys are the runtime's, as the window is:\n * an app that goes releases the keys it holds, and leaves those another app holds. `screenshots` counts the screenshots\n * the user has taken, and the listener behind it lives as long as the app does and is removed\n * when the app is destroyed. Without the module installed, nothing is prevented and the count\n * stays at zero.\n */\nimport { DestroyRef, InjectionToken, Service, inject, signal, type Signal } from '@angular/core';\nimport { onWindowFront, sharedBySource } from '@ng-native/device';\nimport { expoModule, present } from './native.ts';\nimport { Permission, UNAVAILABLE } from './permissions.ts';\n\ntype Expo = typeof import('expo-screen-capture');\n\n/** The module's functions this service calls. The real module is one; a test provides a fake. */\nexport type NativeScreenCapture = Pick<\n Expo,\n | 'isAvailableAsync'\n | 'preventScreenCaptureAsync'\n | 'allowScreenCaptureAsync'\n | 'enableAppSwitcherProtectionAsync'\n | 'disableAppSwitcherProtectionAsync'\n | 'addScreenshotListener'\n | 'getPermissionsAsync'\n | 'requestPermissionsAsync'\n> & {\n /** Calls `listener` when a window comes to the front, to be kept out of capture again. */\n onWindowFront?(listener: () => void): () => void;\n /**\n * Prevents capture on the window now in front, past the module's own record of its keys, which\n * would send nothing for a key it already has.\n */\n preventAgain?(): Promise<void>;\n};\n\n/** The key `expo-screen-capture` uses when none is given. */\nconst DEFAULT_KEY = 'default';\n\n@Service()\nexport class ScreenCapture {\n /** Overridden in a test to take screenshots nobody took. */\n static readonly SOURCE = new InjectionToken<NativeScreenCapture | null>(\n 'angular-native.screenCaptureSource',\n {\n factory: () => {\n const expo = expoModule(\n 'expo-screen-capture',\n () => require('expo-screen-capture') as Expo,\n );\n const loaded = present(expo);\n if (!loaded) return expo;\n // One per module, so every app the runtime mounts holds the same keys.\n return sharedBySource(loaded, () => ({\n ...loaded,\n onWindowFront,\n preventAgain: async () => {\n const modules = (globalThis as { expo?: { modules?: Record<string, unknown> } }).expo\n ?.modules;\n const native = modules?.['ExpoScreenCapture'] as\n { preventScreenCapture?(): Promise<void> } | undefined;\n await native?.preventScreenCapture?.();\n },\n }));\n },\n },\n );\n\n private readonly native = inject(ScreenCapture.SOURCE);\n private readonly count = signal(0);\n /** Every app's keys: the window is the runtime's, not an app's. */\n private readonly window = sharedBySource(this.native, () => new CaptureWindow(this.native));\n /** This app, among the apps holding keys. */\n private readonly owner = {};\n\n /**\n * The permission screenshot detection needs on Android 13 and earlier, where it reads the\n * photo library. Later Android needs none, and iOS always answers granted.\n */\n readonly permission: Permission = Permission.of(\n () => this.native?.getPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE),\n () => this.native?.requestPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE),\n );\n\n /** How many screenshots the user has taken while the app was in front. */\n readonly screenshots: Signal<number> = this.count.asReadonly();\n\n constructor() {\n const listener = present(this.native)?.addScreenshotListener(() =>\n this.count.update((n) => n + 1),\n );\n inject(DestroyRef).onDestroy(() => {\n listener?.remove();\n this.window.leave(this.owner);\n });\n }\n\n /** Whether the platform can prevent capture. */\n async available(): Promise<boolean> {\n return (await this.native?.isAvailableAsync()) ?? false;\n }\n\n /**\n * Keeps the app out of screenshots and screen recordings until `allow()` is called with the same\n * key, or the app goes. Omitting the key uses one shared default key.\n */\n async prevent(key?: string): Promise<void> {\n await this.window.prevent(this.owner, key);\n }\n\n /** Releases the key; capture is allowed again once no key is held. */\n async allow(key?: string): Promise<void> {\n await this.window.allow(this.owner, key);\n }\n\n /**\n * iOS: blurs the app while it is not in focus - in the app switcher, in the background, and\n * during interruptions - by `blurIntensity` from 0 to 1, half by default. Android already hides\n * it in the app switcher while capture is prevented.\n */\n async protectAppSwitcher(blurIntensity?: number): Promise<void> {\n await this.native?.enableAppSwitcherProtectionAsync(blurIntensity);\n }\n\n async unprotectAppSwitcher(): Promise<void> {\n await this.native?.disableAppSwitcherProtectionAsync();\n }\n}\n\n/** Screen capture, as every app on one module holds it. */\nclass CaptureWindow {\n /** The apps holding each key. */\n private readonly holders = new Map<string, Set<object>>();\n private readonly native: NativeScreenCapture | null;\n\n constructor(native: NativeScreenCapture | null) {\n this.native = native;\n // Lives as long as the module, which is the runtime's.\n present(native)?.onWindowFront?.(() => {\n if (this.holders.size > 0) void this.native?.preventAgain?.().catch(() => {});\n });\n }\n\n async prevent(owner: object, key: string | undefined): Promise<void> {\n const name = key ?? DEFAULT_KEY;\n const holders = this.holders.get(name) ?? new Set();\n this.holders.set(name, holders.add(owner));\n await this.native?.preventScreenCaptureAsync(key);\n }\n\n async allow(owner: object, key: string | undefined): Promise<void> {\n const name = key ?? DEFAULT_KEY;\n const holders = this.holders.get(name);\n holders?.delete(owner);\n // Another app holding the key keeps it: the module would release it for both.\n if (holders && holders.size > 0) return;\n this.holders.delete(name);\n await this.native?.allowScreenCaptureAsync(key);\n }\n\n /** Releases `owner`'s keys, for an app that goes: those another app holds stay held. */\n leave(owner: object): void {\n for (const name of [...this.holders.keys()]) void this.allow(owner, name).catch(() => {});\n }\n}\n"]}
1
+ {"version":3,"file":"screen-capture.js","sourceRoot":"","sources":["../src/screen-capture.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `ScreenCapture`, bound to `expo-screen-capture`: keeping a screen out of screenshots and\n * recordings, and knowing when one was taken.\n *\n * ```ts\n * private readonly capture = inject(ScreenCapture);\n *\n * constructor() {\n * void this.capture.prevent('card-details');\n * inject(DestroyRef).onDestroy(() => void this.capture.allow('card-details'));\n * }\n * ```\n *\n * `prevent()` and `allow()` take a key, and capture stays prevented while any key is still held:\n * two screens that each prevent it do not undo each other. Keys are the runtime's, as the window is:\n * an app that goes releases the keys it holds, and leaves those another app holds. `screenshots` counts the screenshots\n * the user has taken, and the listener behind it lives as long as the app does and is removed\n * when the app is destroyed. Without the module installed, nothing is prevented and the count\n * stays at zero.\n */\nimport { DestroyRef, InjectionToken, Service, inject, signal, type Signal } from '@angular/core';\nimport { onWindowFront, sharedBySource } from '@ng-native/device';\nimport { expoModule, present } from './native.ts';\nimport { Permission, UNAVAILABLE } from './permissions.ts';\n\ntype Expo = typeof import('expo-screen-capture');\n\n/** The module's functions this service calls. The real module is one; a test provides a fake. */\nexport type NativeScreenCapture = Pick<\n Expo,\n | 'isAvailableAsync'\n | 'preventScreenCaptureAsync'\n | 'allowScreenCaptureAsync'\n | 'enableAppSwitcherProtectionAsync'\n | 'disableAppSwitcherProtectionAsync'\n | 'addScreenshotListener'\n | 'getPermissionsAsync'\n | 'requestPermissionsAsync'\n> & {\n /** Calls `listener` when a window comes to the front, to be kept out of capture again. */\n onWindowFront?(listener: () => void): () => void;\n /**\n * Prevents capture on the window now in front, past the module's own record of its keys, which\n * would send nothing for a key it already has.\n */\n preventAgain?(): Promise<void>;\n};\n\n/** The key `expo-screen-capture` uses when none is given. */\nconst DEFAULT_KEY = 'default';\n\n@Service()\nexport class ScreenCapture {\n /** Overridden in a test to take screenshots nobody took. */\n static readonly SOURCE = new InjectionToken<NativeScreenCapture | null>(\n 'angular-native.screenCaptureSource',\n {\n factory: () => {\n const expo = expoModule(\n 'expo-screen-capture',\n () => require('expo-screen-capture') as Expo,\n );\n const loaded = present(expo);\n if (!loaded) return expo;\n // One per module, so every app the runtime mounts holds the same keys.\n return sharedBySource(ScreenCapture, loaded, () => ({\n ...loaded,\n onWindowFront,\n preventAgain: async () => {\n const modules = (globalThis as { expo?: { modules?: Record<string, unknown> } }).expo\n ?.modules;\n const native = modules?.['ExpoScreenCapture'] as\n { preventScreenCapture?(): Promise<void> } | undefined;\n await native?.preventScreenCapture?.();\n },\n }));\n },\n },\n );\n\n private readonly native = inject(ScreenCapture.SOURCE);\n private readonly count = signal(0);\n /** Every app's keys: the window is the runtime's, not an app's. */\n private readonly window = sharedBySource(\n CaptureWindow,\n this.native,\n () => new CaptureWindow(this.native),\n );\n /** This app, among the apps holding keys. */\n private readonly owner = {};\n\n /**\n * The permission screenshot detection needs on Android 13 and earlier, where it reads the\n * photo library. Later Android needs none, and iOS always answers granted.\n */\n readonly permission: Permission = Permission.of(\n () => this.native?.getPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE),\n () => this.native?.requestPermissionsAsync?.() ?? Promise.resolve(UNAVAILABLE),\n );\n\n /** How many screenshots the user has taken while the app was in front. */\n readonly screenshots: Signal<number> = this.count.asReadonly();\n\n constructor() {\n const listener = present(this.native)?.addScreenshotListener(() =>\n this.count.update((n) => n + 1),\n );\n inject(DestroyRef).onDestroy(() => {\n listener?.remove();\n this.window.leave(this.owner);\n });\n }\n\n /** Whether the platform can prevent capture. */\n async available(): Promise<boolean> {\n return (await this.native?.isAvailableAsync()) ?? false;\n }\n\n /**\n * Keeps the app out of screenshots and screen recordings until `allow()` is called with the same\n * key, or the app goes. Omitting the key uses one shared default key.\n */\n async prevent(key?: string): Promise<void> {\n await this.window.prevent(this.owner, key);\n }\n\n /** Releases the key; capture is allowed again once no key is held. */\n async allow(key?: string): Promise<void> {\n await this.window.allow(this.owner, key);\n }\n\n /**\n * iOS: blurs the app while it is not in focus - in the app switcher, in the background, and\n * during interruptions - by `blurIntensity` from 0 to 1, half by default. Android already hides\n * it in the app switcher while capture is prevented.\n */\n async protectAppSwitcher(blurIntensity?: number): Promise<void> {\n await this.native?.enableAppSwitcherProtectionAsync(blurIntensity);\n }\n\n async unprotectAppSwitcher(): Promise<void> {\n await this.native?.disableAppSwitcherProtectionAsync();\n }\n}\n\n/** Screen capture, as every app on one module holds it. */\nclass CaptureWindow {\n /** The apps holding each key. */\n private readonly holders = new Map<string, Set<object>>();\n private readonly native: NativeScreenCapture | null;\n\n constructor(native: NativeScreenCapture | null) {\n this.native = native;\n // Lives as long as the module, which is the runtime's.\n present(native)?.onWindowFront?.(() => {\n if (this.holders.size > 0) void this.native?.preventAgain?.().catch(() => {});\n });\n }\n\n async prevent(owner: object, key: string | undefined): Promise<void> {\n const name = key ?? DEFAULT_KEY;\n const holders = this.holders.get(name) ?? new Set();\n this.holders.set(name, holders.add(owner));\n await this.native?.preventScreenCaptureAsync(key);\n }\n\n async allow(owner: object, key: string | undefined): Promise<void> {\n const name = key ?? DEFAULT_KEY;\n const holders = this.holders.get(name);\n holders?.delete(owner);\n // Another app holding the key keeps it: the module would release it for both.\n if (holders && holders.size > 0) return;\n this.holders.delete(name);\n await this.native?.allowScreenCaptureAsync(key);\n }\n\n /** Releases `owner`'s keys, for an app that goes: those another app holds stay held. */\n leave(owner: object): void {\n for (const name of [...this.holders.keys()]) void this.allow(owner, name).catch(() => {});\n }\n}\n"]}
@@ -103,7 +103,7 @@ export class SearchField {
103
103
  });
104
104
  if (this.missing)
105
105
  warnMissing();
106
- const host = this.node.host ?? inject(HostEngine);
106
+ const host = this.node.engine ?? inject(HostEngine);
107
107
  const listen = (event, handle) => host.setEventListener(this.node, event, (raw) => handle(raw?.nativeEvent ?? {}));
108
108
  const stops = [
109
109
  listen('topChangeText', ({ text, eventCount }) => {
@@ -1 +1 @@
1
- {"version":3,"file":"search-field.js","sourceRoot":"","sources":["../src/search-field.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `<search-field>`: the platform's own search field, anywhere a view can go. `<native-search-bar>`\n * from `@ng-native/router` is the one in a navigation bar; this is the one for everywhere else, a\n * filter sheet, a page, a list's header:\n *\n * ```html\n * <ui-bottom-sheet [(open)]=\"filtering\">\n * <search-field placeholder=\"Search colours\" [(value)]=\"query\" />\n * ...\n * </ui-bottom-sheet>\n * ```\n *\n * It is `@ng-native/expo`'s own native view: UIKit's `UISearchBar` on iOS, with its Cancel button,\n * dictation and clear button, and AndroidX's `SearchView` on Android, which has no Cancel button.\n * The events are `<native-search-bar>`'s: `(search)` for the keyboard's search key with the text,\n * `(cancel)`, `(searchFocus)` and `(searchBlur)`, and `focus()`, `blur()` and `clear()` from code.\n * The text is a two-way `value` rather than the bar's `query`, because it is a Signal Forms control\n * as `<text-input>` is: `[formField]` binds `value`, `disabled`, and `touch` on blur.\n * Its type is the `font-family`, `font-size`, `font-weight` and `font-style` its CSS resolves to,\n * as a `<text>`'s is.\n *\n * Expo Go cannot load native code of its own, so there the element draws nothing, and in\n * development it says so once.\n */\nimport {\n Component,\n booleanAttribute,\n DestroyRef,\n ElementRef,\n InjectionToken,\n inject,\n input,\n model,\n output,\n signal,\n} from '@angular/core';\nimport {\n Engine,\n type EngineNode,\n HostEngine,\n type NativeSyntheticEvent,\n nativePlatform,\n type PostScriptNames,\n registerViewName,\n} from '@ng-native/fabric';\nimport { optional } from './native.ts';\nimport { expoViewName } from './register-expo-view.ts';\nimport { optionalBoolean } from './transforms.ts';\n\n/** The functions the native module defines on the view, called with the view's tag as `this`. */\nexport interface SearchFieldFunctions {\n focus?(this: { nativeTag: number }): Promise<void>;\n blur?(this: { nativeTag: number }): Promise<void>;\n}\n\ndeclare const ngDevMode: unknown;\n\nconst MODULE = 'NgNativeSearchField';\n\nlet warned = false;\n\n/** Says once, in development, that the build has no native search field to draw. */\nfunction warnMissing(): void {\n if ((typeof ngDevMode !== 'undefined' && !ngDevMode) || warned) return;\n warned = true;\n console.warn(\n '[angular-native] <search-field> is native code of @ng-native/expo, which this build does not ' +\n 'have, so it draws nothing. Expo Go cannot load it: run a development build.',\n );\n}\n\n/**\n * The field's face by the PostScript name UIKit finds it by: expo-font registers it under the name\n * the app gives it, which `UIFont(name:)` does not know. Android's font manager finds that name.\n */\nfunction fieldFontName(props: Record<string, unknown>, postScriptName: PostScriptNames): void {\n const family = props['fontFamily'];\n if (typeof family === 'string') props['fontFamily'] = postScriptName(family) ?? family;\n}\n\n@Component({\n selector: 'search-field',\n template: '',\n styles: ':host { height: 56px; }',\n host: {\n '[text]': 'value()',\n '[enabled]': '!disabled()',\n '[mostRecentEventCount]': 'eventCount()',\n '[placeholder]': 'placeholder()',\n '[showsCancelButton]': 'showsCancelButton()',\n '[autoCapitalize]': 'autoCapitalize()',\n '[tintColor]': 'tintColor()',\n // Fabric draws a view the build does not have as a placeholder box naming it.\n '[style.display]': 'missing ? \"none\" : undefined',\n },\n})\nexport class SearchField {\n /**\n * The functions native defines on the view, or null where the build has no native search\n * field. Overridden in a test.\n */\n static readonly SOURCE = new InjectionToken<SearchFieldFunctions | null>(\n 'angular-native.searchFieldSource',\n {\n factory: () =>\n optional(() => {\n const core = require('expo-modules-core') as typeof import('expo-modules-core');\n const module = core.requireOptionalNativeModule<{\n ViewPrototypes?: Record<string, SearchFieldFunctions>;\n }>(MODULE);\n return module ? (module.ViewPrototypes?.[MODULE] ?? {}) : null;\n }),\n },\n );\n\n /**\n * The text in the field: typing writes it, and text set from code is put in the field. A\n * `model()`, which is all `FormValueControl<string>` asks, so `[formField]` binds it.\n */\n readonly value = model('');\n /** Refuse input and show the field dimmed. Signal Forms' `disabled()` rule sets it. */\n readonly disabled = input(false, { transform: booleanAttribute });\n readonly placeholder = input<string>();\n /**\n * iOS: whether the Cancel button shows. Left unset it shows while the field has the keyboard,\n * as in a search bar under a navigation bar.\n */\n readonly showsCancelButton = input(undefined, { transform: optionalBoolean });\n readonly autoCapitalize = input<'none' | 'words' | 'sentences' | 'characters'>();\n /** The colour of the cursor and the Cancel button. */\n readonly tintColor = input<string>();\n\n /** The keyboard's search key was pressed, with the text in the field. */\n readonly search = output<string>();\n /** iOS: the Cancel button was pressed. The field is emptied and gives up the keyboard. */\n readonly cancel = output<void>();\n readonly searchFocus = output<void>();\n readonly searchBlur = output<void>();\n /** The field gave up the keyboard: what Signal Forms marks a field touched from. */\n readonly touch = output<void>();\n\n /**\n * The keystroke the text answers, as React Native's text input counts them: native skips a text\n * older than what has been typed since, which a fast typist's echo otherwise overwrites.\n */\n protected readonly eventCount = signal(0);\n\n private readonly functions = inject(SearchField.SOURCE);\n protected readonly missing = !this.functions;\n private readonly node = inject(ElementRef).nativeElement as EngineNode;\n private readonly engine = inject(Engine, { optional: true });\n\n constructor() {\n registerViewName('search-field', expoViewName(MODULE), undefined, {\n fontNames: nativePlatform() === 'ios' ? fieldFontName : undefined,\n });\n if (this.missing) warnMissing();\n const host =\n (this.node as { host?: Pick<Engine, 'setEventListener'> }).host ?? inject(HostEngine);\n const listen = (event: string, handle: (payload: Record<string, unknown>) => void) =>\n host.setEventListener(this.node, event, (raw) =>\n handle((raw as NativeSyntheticEvent<Record<string, unknown>>)?.nativeEvent ?? {}),\n );\n const stops = [\n listen('topChangeText', ({ text, eventCount }) => {\n if (typeof eventCount === 'number') this.eventCount.set(eventCount);\n this.value.set(String(text ?? ''));\n }),\n // Named apart from the outputs: a native event of an output's name reaches its listener too.\n listen('topSubmitSearch', ({ text }) => this.search.emit(String(text ?? this.value()))),\n listen('topPressCancel', () => this.cancel.emit()),\n listen('topBeginEditing', () => this.searchFocus.emit()),\n listen('topEndEditing', () => {\n this.searchBlur.emit();\n this.touch.emit();\n }),\n ];\n inject(DestroyRef).onDestroy(() => stops.forEach((stop) => stop()));\n }\n\n /** Give the field the keyboard. Does nothing before the view has reached the screen. */\n async focus(): Promise<void> {\n await this.call('focus');\n }\n\n /** Take the keyboard away from the field. */\n async blur(): Promise<void> {\n await this.call('blur');\n }\n\n /** Empty the field, and the value with it. */\n clear(): void {\n this.value.set('');\n }\n\n private async call(name: 'focus' | 'blur'): Promise<void> {\n const nativeTag = this.engine?.tagOf(this.node);\n if (nativeTag == null) return;\n await this.functions?.[name]?.call({ nativeTag });\n }\n}\n"]}
1
+ {"version":3,"file":"search-field.js","sourceRoot":"","sources":["../src/search-field.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * `<search-field>`: the platform's own search field, anywhere a view can go. `<native-search-bar>`\n * from `@ng-native/router` is the one in a navigation bar; this is the one for everywhere else, a\n * filter sheet, a page, a list's header:\n *\n * ```html\n * <ui-bottom-sheet [(open)]=\"filtering\">\n * <search-field placeholder=\"Search colours\" [(value)]=\"query\" />\n * ...\n * </ui-bottom-sheet>\n * ```\n *\n * It is `@ng-native/expo`'s own native view: UIKit's `UISearchBar` on iOS, with its Cancel button,\n * dictation and clear button, and AndroidX's `SearchView` on Android, which has no Cancel button.\n * The events are `<native-search-bar>`'s: `(search)` for the keyboard's search key with the text,\n * `(cancel)`, `(searchFocus)` and `(searchBlur)`, and `focus()`, `blur()` and `clear()` from code.\n * The text is a two-way `value` rather than the bar's `query`, because it is a Signal Forms control\n * as `<text-input>` is: `[formField]` binds `value`, `disabled`, and `touch` on blur.\n * Its type is the `font-family`, `font-size`, `font-weight` and `font-style` its CSS resolves to,\n * as a `<text>`'s is.\n *\n * Expo Go cannot load native code of its own, so there the element draws nothing, and in\n * development it says so once.\n */\nimport {\n Component,\n booleanAttribute,\n DestroyRef,\n ElementRef,\n InjectionToken,\n inject,\n input,\n model,\n output,\n signal,\n} from '@angular/core';\nimport {\n Engine,\n type EngineNode,\n HostEngine,\n type NativeSyntheticEvent,\n nativePlatform,\n type PostScriptNames,\n registerViewName,\n} from '@ng-native/fabric';\nimport { optional } from './native.ts';\nimport { expoViewName } from './register-expo-view.ts';\nimport { optionalBoolean } from './transforms.ts';\n\n/** The functions the native module defines on the view, called with the view's tag as `this`. */\nexport interface SearchFieldFunctions {\n focus?(this: { nativeTag: number }): Promise<void>;\n blur?(this: { nativeTag: number }): Promise<void>;\n}\n\ndeclare const ngDevMode: unknown;\n\nconst MODULE = 'NgNativeSearchField';\n\nlet warned = false;\n\n/** Says once, in development, that the build has no native search field to draw. */\nfunction warnMissing(): void {\n if ((typeof ngDevMode !== 'undefined' && !ngDevMode) || warned) return;\n warned = true;\n console.warn(\n '[angular-native] <search-field> is native code of @ng-native/expo, which this build does not ' +\n 'have, so it draws nothing. Expo Go cannot load it: run a development build.',\n );\n}\n\n/**\n * The field's face by the PostScript name UIKit finds it by: expo-font registers it under the name\n * the app gives it, which `UIFont(name:)` does not know. Android's font manager finds that name.\n */\nfunction fieldFontName(props: Record<string, unknown>, postScriptName: PostScriptNames): void {\n const family = props['fontFamily'];\n if (typeof family === 'string') props['fontFamily'] = postScriptName(family) ?? family;\n}\n\n@Component({\n selector: 'search-field',\n template: '',\n styles: ':host { height: 56px; }',\n host: {\n '[text]': 'value()',\n '[enabled]': '!disabled()',\n '[mostRecentEventCount]': 'eventCount()',\n '[placeholder]': 'placeholder()',\n '[showsCancelButton]': 'showsCancelButton()',\n '[autoCapitalize]': 'autoCapitalize()',\n '[tintColor]': 'tintColor()',\n // Fabric draws a view the build does not have as a placeholder box naming it.\n '[style.display]': 'missing ? \"none\" : undefined',\n },\n})\nexport class SearchField {\n /**\n * The functions native defines on the view, or null where the build has no native search\n * field. Overridden in a test.\n */\n static readonly SOURCE = new InjectionToken<SearchFieldFunctions | null>(\n 'angular-native.searchFieldSource',\n {\n factory: () =>\n optional(() => {\n const core = require('expo-modules-core') as typeof import('expo-modules-core');\n const module = core.requireOptionalNativeModule<{\n ViewPrototypes?: Record<string, SearchFieldFunctions>;\n }>(MODULE);\n return module ? (module.ViewPrototypes?.[MODULE] ?? {}) : null;\n }),\n },\n );\n\n /**\n * The text in the field: typing writes it, and text set from code is put in the field. A\n * `model()`, which is all `FormValueControl<string>` asks, so `[formField]` binds it.\n */\n readonly value = model('');\n /** Refuse input and show the field dimmed. Signal Forms' `disabled()` rule sets it. */\n readonly disabled = input(false, { transform: booleanAttribute });\n readonly placeholder = input<string>();\n /**\n * iOS: whether the Cancel button shows. Left unset it shows while the field has the keyboard,\n * as in a search bar under a navigation bar.\n */\n readonly showsCancelButton = input(undefined, { transform: optionalBoolean });\n readonly autoCapitalize = input<'none' | 'words' | 'sentences' | 'characters'>();\n /** The colour of the cursor and the Cancel button. */\n readonly tintColor = input<string>();\n\n /** The keyboard's search key was pressed, with the text in the field. */\n readonly search = output<string>();\n /** iOS: the Cancel button was pressed. The field is emptied and gives up the keyboard. */\n readonly cancel = output<void>();\n readonly searchFocus = output<void>();\n readonly searchBlur = output<void>();\n /** The field gave up the keyboard: what Signal Forms marks a field touched from. */\n readonly touch = output<void>();\n\n /**\n * The keystroke the text answers, as React Native's text input counts them: native skips a text\n * older than what has been typed since, which a fast typist's echo otherwise overwrites.\n */\n protected readonly eventCount = signal(0);\n\n private readonly functions = inject(SearchField.SOURCE);\n protected readonly missing = !this.functions;\n private readonly node = inject(ElementRef).nativeElement as EngineNode;\n private readonly engine = inject(Engine, { optional: true });\n\n constructor() {\n registerViewName('search-field', expoViewName(MODULE), undefined, {\n fontNames: nativePlatform() === 'ios' ? fieldFontName : undefined,\n });\n if (this.missing) warnMissing();\n const host =\n (this.node as { engine?: Pick<Engine, 'setEventListener'> }).engine ?? inject(HostEngine);\n const listen = (event: string, handle: (payload: Record<string, unknown>) => void) =>\n host.setEventListener(this.node, event, (raw) =>\n handle((raw as NativeSyntheticEvent<Record<string, unknown>>)?.nativeEvent ?? {}),\n );\n const stops = [\n listen('topChangeText', ({ text, eventCount }) => {\n if (typeof eventCount === 'number') this.eventCount.set(eventCount);\n this.value.set(String(text ?? ''));\n }),\n // Named apart from the outputs: a native event of an output's name reaches its listener too.\n listen('topSubmitSearch', ({ text }) => this.search.emit(String(text ?? this.value()))),\n listen('topPressCancel', () => this.cancel.emit()),\n listen('topBeginEditing', () => this.searchFocus.emit()),\n listen('topEndEditing', () => {\n this.searchBlur.emit();\n this.touch.emit();\n }),\n ];\n inject(DestroyRef).onDestroy(() => stops.forEach((stop) => stop()));\n }\n\n /** Give the field the keyboard. Does nothing before the view has reached the screen. */\n async focus(): Promise<void> {\n await this.call('focus');\n }\n\n /** Take the keyboard away from the field. */\n async blur(): Promise<void> {\n await this.call('blur');\n }\n\n /** Empty the field, and the value with it. */\n clear(): void {\n this.value.set('');\n }\n\n private async call(name: 'focus' | 'blur'): Promise<void> {\n const nativeTag = this.engine?.tagOf(this.node);\n if (nativeTag == null) return;\n await this.functions?.[name]?.call({ nativeTag });\n }\n}\n"]}
package/dist/symbol.d.ts CHANGED
@@ -27,10 +27,12 @@ export declare class ExpoSymbol {
27
27
  readonly colors: import("@angular/core").InputSignal<string | readonly string[] | undefined>;
28
28
  readonly resizeMode: import("@angular/core").InputSignal<SymbolResizeMode | undefined>;
29
29
  readonly animationSpec: import("@angular/core").InputSignal<SymbolAnimationSpec | undefined>;
30
- /** Points, both ways. */
30
+ /** The point size the symbol is drawn at. */
31
31
  readonly size: import("@angular/core").InputSignalWithTransform<number, unknown>;
32
32
  protected readonly colourList: import("@angular/core").Signal<readonly string[] | undefined>;
33
33
  protected readonly animated: import("@angular/core").Signal<boolean>;
34
+ /** Width and height: the symbol image's at the point size, or the square without native's. */
35
+ protected readonly box: import("@angular/core").Signal<number[] | readonly [number, number]>;
34
36
  static ɵfac: i0.ɵɵFactoryDeclaration<ExpoSymbol, never>;
35
37
  static ɵcmp: i0.ɵɵComponentDeclaration<ExpoSymbol, "expo-symbol", never, { "name": { "alias": "name"; "required": true; "isSignal": true; }; "type": { "alias": "type"; "required": false; "isSignal": true; }; "scale": { "alias": "scale"; "required": false; "isSignal": true; }; "weight": { "alias": "weight"; "required": false; "isSignal": true; }; "tintColor": { "alias": "tintColor"; "required": false; "isSignal": true; }; "colors": { "alias": "colors"; "required": false; "isSignal": true; }; "resizeMode": { "alias": "resizeMode"; "required": false; "isSignal": true; }; "animationSpec": { "alias": "animationSpec"; "required": false; "isSignal": true; }; "size": { "alias": "size"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
36
38
  }
package/dist/symbol.js CHANGED
@@ -2,9 +2,15 @@
2
2
  * `<expo-symbol>`: an SF Symbol, from `expo-symbols`, typed so a template is checked against the
3
3
  * props. iOS only. Registered with `registerExpoViews('expo-symbol')`.
4
4
  *
5
- * It does what the module's React component does before native sees the props: the view is `size`
6
- * points square (24 unless set), `type` is `monochrome` unless set, one colour or several become
7
- * the list native takes, and an `animationSpec` is what turns animation on.
5
+ * It does what the module's React component does before native sees the props: `type` is
6
+ * `monochrome` unless set, one colour or several become the list native takes, and an
7
+ * `animationSpec` is what turns animation on.
8
+ *
9
+ * `size` is a point size, as SwiftUI's `.font(.system(size:))` takes it (24 unless set): the view
10
+ * is the size of the symbol's image at that point size, weight and scale, which this package's
11
+ * native module reports, and the symbol fills it. Without that module, as in Expo Go, the view is
12
+ * `size` points square, as the React component makes it, and the symbol is fitted into it, which
13
+ * draws most symbols smaller than SwiftUI does.
8
14
  *
9
15
  * ```html
10
16
  * <expo-symbol name="heart.fill" [size]="28" tintColor="#ff2d55" weight="semibold" />
@@ -34,7 +40,7 @@ export class ExpoSymbol {
34
40
  ...(ngDevMode ? [undefined, { debugName: "resizeMode" }] : /* istanbul ignore next */ []));
35
41
  animationSpec = input(/* @ts-ignore */
36
42
  ...(ngDevMode ? [undefined, { debugName: "animationSpec" }] : /* istanbul ignore next */ []));
37
- /** Points, both ways. */
43
+ /** The point size the symbol is drawn at. */
38
44
  size = input(24, { ...(ngDevMode ? { debugName: "size" } : /* istanbul ignore next */ {}), transform: (value) => optionalNumber(value) ?? 24 });
39
45
  colourList = computed(() => {
40
46
  const colours = this.colors();
@@ -43,8 +49,14 @@ export class ExpoSymbol {
43
49
  ...(ngDevMode ? [{ debugName: "colourList" }] : /* istanbul ignore next */ []));
44
50
  animated = computed(() => this.animationSpec() !== undefined, /* @ts-ignore */
45
51
  ...(ngDevMode ? [{ debugName: "animated" }] : /* istanbul ignore next */ []));
52
+ /** Width and height: the symbol image's at the point size, or the square without native's. */
53
+ box = computed(() => {
54
+ const size = this.size();
55
+ return symbolImageSize(this.name(), size, this.weight(), this.scale()) ?? [size, size];
56
+ }, /* @ts-ignore */
57
+ ...(ngDevMode ? [{ debugName: "box" }] : /* istanbul ignore next */ []));
46
58
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: ExpoSymbol, deps: [], target: i0.ɵɵFactoryTarget.Component });
47
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.2.0", type: ExpoSymbol, isStandalone: true, selector: "expo-symbol", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, scale: { classPropertyName: "scale", publicName: "scale", isSignal: true, isRequired: false, transformFunction: null }, weight: { classPropertyName: "weight", publicName: "weight", isSignal: true, isRequired: false, transformFunction: null }, tintColor: { classPropertyName: "tintColor", publicName: "tintColor", isSignal: true, isRequired: false, transformFunction: null }, colors: { classPropertyName: "colors", publicName: "colors", isSignal: true, isRequired: false, transformFunction: null }, resizeMode: { classPropertyName: "resizeMode", publicName: "resizeMode", isSignal: true, isRequired: false, transformFunction: null }, animationSpec: { classPropertyName: "animationSpec", publicName: "animationSpec", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "name": "name()", "type": "type()", "scale": "scale()", "weight": "weight()", "tintColor": "tintColor()", "colors": "colourList()", "resizeMode": "resizeMode()", "animationSpec": "animationSpec()", "animated": "animated()", "style.width": "size()", "style.height": "size()" } }, ngImport: i0, template: '', isInline: true });
59
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.2.0", type: ExpoSymbol, isStandalone: true, selector: "expo-symbol", inputs: { name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: true, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, scale: { classPropertyName: "scale", publicName: "scale", isSignal: true, isRequired: false, transformFunction: null }, weight: { classPropertyName: "weight", publicName: "weight", isSignal: true, isRequired: false, transformFunction: null }, tintColor: { classPropertyName: "tintColor", publicName: "tintColor", isSignal: true, isRequired: false, transformFunction: null }, colors: { classPropertyName: "colors", publicName: "colors", isSignal: true, isRequired: false, transformFunction: null }, resizeMode: { classPropertyName: "resizeMode", publicName: "resizeMode", isSignal: true, isRequired: false, transformFunction: null }, animationSpec: { classPropertyName: "animationSpec", publicName: "animationSpec", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "name": "name()", "type": "type()", "scale": "scale()", "weight": "weight()", "tintColor": "tintColor()", "colors": "colourList()", "resizeMode": "resizeMode()", "animationSpec": "animationSpec()", "animated": "animated()", "style.width": "box()[0]", "style.height": "box()[1]" } }, ngImport: i0, template: '', isInline: true });
48
60
  }
49
61
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: ExpoSymbol, decorators: [{
50
62
  type: Component,
@@ -61,9 +73,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImpor
61
73
  '[resizeMode]': 'resizeMode()',
62
74
  '[animationSpec]': 'animationSpec()',
63
75
  '[animated]': 'animated()',
64
- '[style.width]': 'size()',
65
- '[style.height]': 'size()',
76
+ '[style.width]': 'box()[0]',
77
+ '[style.height]': 'box()[1]',
66
78
  },
67
79
  }]
68
80
  }], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], scale: [{ type: i0.Input, args: [{ isSignal: true, alias: "scale", required: false }] }], weight: [{ type: i0.Input, args: [{ isSignal: true, alias: "weight", required: false }] }], tintColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "tintColor", required: false }] }], colors: [{ type: i0.Input, args: [{ isSignal: true, alias: "colors", required: false }] }], resizeMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "resizeMode", required: false }] }], animationSpec: [{ type: i0.Input, args: [{ isSignal: true, alias: "animationSpec", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }] } });
81
+ /** The size of the symbol's image in points, or null where native cannot say. */
82
+ function symbolImageSize(name, pointSize, weight, scale) {
83
+ const native = globalThis.expo
84
+ ?.modules?.NgNativeSymbol;
85
+ const size = native?.imageSize?.(name, pointSize, weight, scale);
86
+ return size ? [size[0], size[1]] : null;
87
+ }
69
88
  //# sourceMappingURL=symbol.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"symbol.js","sourceRoot":"","sources":["../src/symbol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;;AA4DjD,MAAM,OAAO,UAAU;IACrB,iFAAiF;IACxE,IAAI,GAAG,KAAK,CAAC,QAAQ;6EAAU,CAAC;IAChC,IAAI,GAAG,KAAK,CAAa,YAAY;6EAAC,CAAC;IACvC,KAAK,GAAG,KAAK;yFAAe,CAAC;IAC7B,MAAM,GAAG,KAAK;0FAAgB,CAAC;IACxC,6DAA6D;IACpD,SAAS,GAAG,KAAK;6FAAU,CAAC;IACrC,0CAA0C;IACjC,MAAM,GAAG,KAAK;0FAA8B,CAAC;IAC7C,UAAU,GAAG,KAAK;8FAAoB,CAAC;IACvC,aAAa,GAAG,KAAK;iGAAuB,CAAC;IACtD,yBAAyB;IAChB,IAAI,GAAG,KAAK,CAAC,EAAE,4EAAI,SAAS,EAAE,CAAC,KAAc,EAAE,EAAE,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC;IAEvE,UAAU,GAAG,QAAQ,CAAC,GAAG,EAAE;QAC5C,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAC9B,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAC/F,CAAC;mFAAC,CAAC;IACgB,QAAQ,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,aAAa,EAAE,KAAK,SAAS;iFAAC,CAAC;uGAnBtE,UAAU;2FAAV,UAAU,u+CAfX,EAAE;;2FAeD,UAAU;kBAjBtB,SAAS;mBAAC;oBACT,QAAQ,EAAE,aAAa;oBACvB,QAAQ,EAAE,EAAE;oBACZ,IAAI,EAAE;wBACJ,QAAQ,EAAE,QAAQ;wBAClB,QAAQ,EAAE,QAAQ;wBAClB,SAAS,EAAE,SAAS;wBACpB,UAAU,EAAE,UAAU;wBACtB,aAAa,EAAE,aAAa;wBAC5B,UAAU,EAAE,cAAc;wBAC1B,cAAc,EAAE,cAAc;wBAC9B,iBAAiB,EAAE,iBAAiB;wBACpC,YAAY,EAAE,YAAY;wBAC1B,eAAe,EAAE,QAAQ;wBACzB,gBAAgB,EAAE,QAAQ;qBAC3B;iBACF","sourcesContent":["/**\n * `<expo-symbol>`: an SF Symbol, from `expo-symbols`, typed so a template is checked against the\n * props. iOS only. Registered with `registerExpoViews('expo-symbol')`.\n *\n * It does what the module's React component does before native sees the props: the view is `size`\n * points square (24 unless set), `type` is `monochrome` unless set, one colour or several become\n * the list native takes, and an `animationSpec` is what turns animation on.\n *\n * ```html\n * <expo-symbol name=\"heart.fill\" [size]=\"28\" tintColor=\"#ff2d55\" weight=\"semibold\" />\n * <expo-symbol name=\"cloud.sun.fill\" type=\"palette\" [colors]=\"['#fff', '#ffcc00']\" />\n * ```\n */\nimport { Component, computed, input } from '@angular/core';\nimport { optionalNumber } from './transforms.ts';\n\nexport type SymbolType = 'monochrome' | 'hierarchical' | 'palette' | 'multicolor';\nexport type SymbolScale = 'default' | 'unspecified' | 'small' | 'medium' | 'large';\nexport type SymbolWeight =\n | 'unspecified'\n | 'ultraLight'\n | 'thin'\n | 'light'\n | 'regular'\n | 'medium'\n | 'semibold'\n | 'bold'\n | 'heavy'\n | 'black';\nexport type SymbolResizeMode =\n | 'scaleToFill'\n | 'scaleAspectFit'\n | 'scaleAspectFill'\n | 'redraw'\n | 'center'\n | 'top'\n | 'bottom'\n | 'left'\n | 'right'\n | 'topLeft'\n | 'topRight'\n | 'bottomLeft'\n | 'bottomRight';\n\n/** How a symbol animates, as `expo-symbols` describes it. */\nexport interface SymbolAnimationSpec {\n readonly effect?: {\n readonly type: 'bounce' | 'pulse' | 'scale';\n readonly wholeSymbol?: boolean;\n readonly direction?: 'up' | 'down';\n };\n readonly repeating?: boolean;\n readonly repeatCount?: number;\n readonly speed?: number;\n readonly variableAnimationSpec?: Readonly<Record<string, boolean>>;\n}\n\n@Component({\n selector: 'expo-symbol',\n template: '',\n host: {\n '[name]': 'name()',\n '[type]': 'type()',\n '[scale]': 'scale()',\n '[weight]': 'weight()',\n '[tintColor]': 'tintColor()',\n '[colors]': 'colourList()',\n '[resizeMode]': 'resizeMode()',\n '[animationSpec]': 'animationSpec()',\n '[animated]': 'animated()',\n '[style.width]': 'size()',\n '[style.height]': 'size()',\n },\n})\nexport class ExpoSymbol {\n /** The symbol's name, as SF Symbols lists it: `heart.fill`, `cloud.sun.rain`. */\n readonly name = input.required<string>();\n readonly type = input<SymbolType>('monochrome');\n readonly scale = input<SymbolScale>();\n readonly weight = input<SymbolWeight>();\n /** The colour of a `monochrome` or `hierarchical` symbol. */\n readonly tintColor = input<string>();\n /** The layers' colours, for `palette`. */\n readonly colors = input<string | readonly string[]>();\n readonly resizeMode = input<SymbolResizeMode>();\n readonly animationSpec = input<SymbolAnimationSpec>();\n /** Points, both ways. */\n readonly size = input(24, { transform: (value: unknown) => optionalNumber(value) ?? 24 });\n\n protected readonly colourList = computed(() => {\n const colours = this.colors();\n return colours === undefined ? undefined : typeof colours === 'string' ? [colours] : colours;\n });\n protected readonly animated = computed(() => this.animationSpec() !== undefined);\n}\n"]}
1
+ {"version":3,"file":"symbol.js","sourceRoot":"","sources":["../src/symbol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;;AA4DjD,MAAM,OAAO,UAAU;IACrB,iFAAiF;IACxE,IAAI,GAAG,KAAK,CAAC,QAAQ;6EAAU,CAAC;IAChC,IAAI,GAAG,KAAK,CAAa,YAAY;6EAAC,CAAC;IACvC,KAAK,GAAG,KAAK;yFAAe,CAAC;IAC7B,MAAM,GAAG,KAAK;0FAAgB,CAAC;IACxC,6DAA6D;IACpD,SAAS,GAAG,KAAK;6FAAU,CAAC;IACrC,0CAA0C;IACjC,MAAM,GAAG,KAAK;0FAA8B,CAAC;IAC7C,UAAU,GAAG,KAAK;8FAAoB,CAAC;IACvC,aAAa,GAAG,KAAK;iGAAuB,CAAC;IACtD,6CAA6C;IACpC,IAAI,GAAG,KAAK,CAAC,EAAE,4EAAI,SAAS,EAAE,CAAC,KAAc,EAAE,EAAE,CAAC,cAAc,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC;IAEvE,UAAU,GAAG,QAAQ,CAAC,GAAG,EAAE;QAC5C,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;QAC9B,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAC/F,CAAC;mFAAC,CAAC;IACgB,QAAQ,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,aAAa,EAAE,KAAK,SAAS;iFAAC,CAAC;IACjF,8FAA8F;IAC3E,GAAG,GAAG,QAAQ,CAAC,GAAG,EAAE;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QACzB,OAAO,eAAe,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACzF,CAAC;4EAAC,CAAC;uGAxBQ,UAAU;2FAAV,UAAU,2+CAfX,EAAE;;2FAeD,UAAU;kBAjBtB,SAAS;mBAAC;oBACT,QAAQ,EAAE,aAAa;oBACvB,QAAQ,EAAE,EAAE;oBACZ,IAAI,EAAE;wBACJ,QAAQ,EAAE,QAAQ;wBAClB,QAAQ,EAAE,QAAQ;wBAClB,SAAS,EAAE,SAAS;wBACpB,UAAU,EAAE,UAAU;wBACtB,aAAa,EAAE,aAAa;wBAC5B,UAAU,EAAE,cAAc;wBAC1B,cAAc,EAAE,cAAc;wBAC9B,iBAAiB,EAAE,iBAAiB;wBACpC,YAAY,EAAE,YAAY;wBAC1B,eAAe,EAAE,UAAU;wBAC3B,gBAAgB,EAAE,UAAU;qBAC7B;iBACF;;AAsCD,iFAAiF;AACjF,SAAS,eAAe,CACtB,IAAY,EACZ,SAAiB,EACjB,MAAgC,EAChC,KAA8B;IAE9B,MAAM,MAAM,GAAI,UAAyE,CAAC,IAAI;QAC5F,EAAE,OAAO,EAAE,cAAc,CAAC;IAC5B,MAAM,IAAI,GAAG,MAAM,EAAE,SAAS,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;IACjE,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAE,EAAE,IAAI,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC5C,CAAC","sourcesContent":["/**\n * `<expo-symbol>`: an SF Symbol, from `expo-symbols`, typed so a template is checked against the\n * props. iOS only. Registered with `registerExpoViews('expo-symbol')`.\n *\n * It does what the module's React component does before native sees the props: `type` is\n * `monochrome` unless set, one colour or several become the list native takes, and an\n * `animationSpec` is what turns animation on.\n *\n * `size` is a point size, as SwiftUI's `.font(.system(size:))` takes it (24 unless set): the view\n * is the size of the symbol's image at that point size, weight and scale, which this package's\n * native module reports, and the symbol fills it. Without that module, as in Expo Go, the view is\n * `size` points square, as the React component makes it, and the symbol is fitted into it, which\n * draws most symbols smaller than SwiftUI does.\n *\n * ```html\n * <expo-symbol name=\"heart.fill\" [size]=\"28\" tintColor=\"#ff2d55\" weight=\"semibold\" />\n * <expo-symbol name=\"cloud.sun.fill\" type=\"palette\" [colors]=\"['#fff', '#ffcc00']\" />\n * ```\n */\nimport { Component, computed, input } from '@angular/core';\nimport { optionalNumber } from './transforms.ts';\n\nexport type SymbolType = 'monochrome' | 'hierarchical' | 'palette' | 'multicolor';\nexport type SymbolScale = 'default' | 'unspecified' | 'small' | 'medium' | 'large';\nexport type SymbolWeight =\n | 'unspecified'\n | 'ultraLight'\n | 'thin'\n | 'light'\n | 'regular'\n | 'medium'\n | 'semibold'\n | 'bold'\n | 'heavy'\n | 'black';\nexport type SymbolResizeMode =\n | 'scaleToFill'\n | 'scaleAspectFit'\n | 'scaleAspectFill'\n | 'redraw'\n | 'center'\n | 'top'\n | 'bottom'\n | 'left'\n | 'right'\n | 'topLeft'\n | 'topRight'\n | 'bottomLeft'\n | 'bottomRight';\n\n/** How a symbol animates, as `expo-symbols` describes it. */\nexport interface SymbolAnimationSpec {\n readonly effect?: {\n readonly type: 'bounce' | 'pulse' | 'scale';\n readonly wholeSymbol?: boolean;\n readonly direction?: 'up' | 'down';\n };\n readonly repeating?: boolean;\n readonly repeatCount?: number;\n readonly speed?: number;\n readonly variableAnimationSpec?: Readonly<Record<string, boolean>>;\n}\n\n@Component({\n selector: 'expo-symbol',\n template: '',\n host: {\n '[name]': 'name()',\n '[type]': 'type()',\n '[scale]': 'scale()',\n '[weight]': 'weight()',\n '[tintColor]': 'tintColor()',\n '[colors]': 'colourList()',\n '[resizeMode]': 'resizeMode()',\n '[animationSpec]': 'animationSpec()',\n '[animated]': 'animated()',\n '[style.width]': 'box()[0]',\n '[style.height]': 'box()[1]',\n },\n})\nexport class ExpoSymbol {\n /** The symbol's name, as SF Symbols lists it: `heart.fill`, `cloud.sun.rain`. */\n readonly name = input.required<string>();\n readonly type = input<SymbolType>('monochrome');\n readonly scale = input<SymbolScale>();\n readonly weight = input<SymbolWeight>();\n /** The colour of a `monochrome` or `hierarchical` symbol. */\n readonly tintColor = input<string>();\n /** The layers' colours, for `palette`. */\n readonly colors = input<string | readonly string[]>();\n readonly resizeMode = input<SymbolResizeMode>();\n readonly animationSpec = input<SymbolAnimationSpec>();\n /** The point size the symbol is drawn at. */\n readonly size = input(24, { transform: (value: unknown) => optionalNumber(value) ?? 24 });\n\n protected readonly colourList = computed(() => {\n const colours = this.colors();\n return colours === undefined ? undefined : typeof colours === 'string' ? [colours] : colours;\n });\n protected readonly animated = computed(() => this.animationSpec() !== undefined);\n /** Width and height: the symbol image's at the point size, or the square without native's. */\n protected readonly box = computed(() => {\n const size = this.size();\n return symbolImageSize(this.name(), size, this.weight(), this.scale()) ?? [size, size];\n });\n}\n\n/** This package's native symbol module, iOS only and absent from Expo Go. */\ninterface NativeSymbol {\n imageSize?(\n name: string,\n pointSize: number,\n weight?: SymbolWeight,\n scale?: SymbolScale,\n ): readonly number[] | null;\n}\n\n/** The size of the symbol's image in points, or null where native cannot say. */\nfunction symbolImageSize(\n name: string,\n pointSize: number,\n weight: SymbolWeight | undefined,\n scale: SymbolScale | undefined,\n): readonly [number, number] | null {\n const native = (globalThis as { expo?: { modules?: { NgNativeSymbol?: NativeSymbol } } }).expo\n ?.modules?.NgNativeSymbol;\n const size = native?.imageSize?.(name, pointSize, weight, scale);\n return size ? [size[0]!, size[1]!] : null;\n}\n"]}
@@ -1,5 +1,10 @@
1
1
  import { type NativeSyntheticEvent } from '@ng-native/fabric';
2
2
  import * as i0 from "@angular/core";
3
+ /** How Compose's sheet may be dismissed other than by a swipe, as `@expo/ui` names it. */
4
+ export interface UiModalBottomSheetProperties {
5
+ readonly shouldDismissOnBackPress?: boolean;
6
+ readonly shouldDismissOnClickOutside?: boolean;
7
+ }
3
8
  /** What SwiftUI's sheet sends when the user dismisses it: that it is no longer presented. */
4
9
  export type UiBottomSheetPresentedEvent = NativeSyntheticEvent<{
5
10
  readonly isPresented: boolean;
@@ -15,6 +20,10 @@ export declare class UiBottomSheetView {
15
20
  readonly containerColor: import("@angular/core").InputSignal<unknown>;
16
21
  /** Compose: the sheet opens at its full height rather than resting at half the screen's. */
17
22
  readonly skipPartiallyExpanded: import("@angular/core").InputSignal<boolean | undefined>;
23
+ /** Compose: whether the sheet can be dragged, between its heights and down to dismiss it. */
24
+ readonly sheetGesturesEnabled: import("@angular/core").InputSignal<boolean | undefined>;
25
+ /** Compose: whether the back button and a tap outside the sheet dismiss it. */
26
+ readonly properties: import("@angular/core").InputSignal<UiModalBottomSheetProperties | undefined>;
18
27
  /** SwiftUI: the user dismissed the sheet, or it was presented by something other than the prop. */
19
28
  readonly isPresentedChange: import("@angular/core").OutputEmitterRef<UiBottomSheetPresentedEvent>;
20
29
  /** SwiftUI: the sheet has finished dismissing, whoever dismissed it. */
@@ -23,5 +32,5 @@ export declare class UiBottomSheetView {
23
32
  readonly dismissRequest: import("@angular/core").OutputEmitterRef<NativeSyntheticEvent<Record<string, never>>>;
24
33
  constructor();
25
34
  static ɵfac: i0.ɵɵFactoryDeclaration<UiBottomSheetView, never>;
26
- static ɵcmp: i0.ɵɵComponentDeclaration<UiBottomSheetView, "ui-bottom-sheet-view", never, { "isPresented": { "alias": "isPresented"; "required": false; "isSignal": true; }; "fitToContents": { "alias": "fitToContents"; "required": false; "isSignal": true; }; "showDragHandle": { "alias": "showDragHandle"; "required": false; "isSignal": true; }; "containerColor": { "alias": "containerColor"; "required": false; "isSignal": true; }; "skipPartiallyExpanded": { "alias": "skipPartiallyExpanded"; "required": false; "isSignal": true; }; }, { "isPresentedChange": "isPresentedChange"; "dismiss": "dismiss"; "dismissRequest": "dismissRequest"; }, never, ["*"], true, never>;
35
+ static ɵcmp: i0.ɵɵComponentDeclaration<UiBottomSheetView, "ui-bottom-sheet-view", never, { "isPresented": { "alias": "isPresented"; "required": false; "isSignal": true; }; "fitToContents": { "alias": "fitToContents"; "required": false; "isSignal": true; }; "showDragHandle": { "alias": "showDragHandle"; "required": false; "isSignal": true; }; "containerColor": { "alias": "containerColor"; "required": false; "isSignal": true; }; "skipPartiallyExpanded": { "alias": "skipPartiallyExpanded"; "required": false; "isSignal": true; }; "sheetGesturesEnabled": { "alias": "sheetGesturesEnabled"; "required": false; "isSignal": true; }; "properties": { "alias": "properties"; "required": false; "isSignal": true; }; }, { "isPresentedChange": "isPresentedChange"; "dismiss": "dismiss"; "dismissRequest": "dismissRequest"; }, never, ["*"], true, never>;
27
36
  }
@@ -29,6 +29,12 @@ export class UiBottomSheetView {
29
29
  /** Compose: the sheet opens at its full height rather than resting at half the screen's. */
30
30
  skipPartiallyExpanded = input(/* @ts-ignore */
31
31
  ...(ngDevMode ? [undefined, { debugName: "skipPartiallyExpanded" }] : /* istanbul ignore next */ []));
32
+ /** Compose: whether the sheet can be dragged, between its heights and down to dismiss it. */
33
+ sheetGesturesEnabled = input(/* @ts-ignore */
34
+ ...(ngDevMode ? [undefined, { debugName: "sheetGesturesEnabled" }] : /* istanbul ignore next */ []));
35
+ /** Compose: whether the back button and a tap outside the sheet dismiss it. */
36
+ properties = input(/* @ts-ignore */
37
+ ...(ngDevMode ? [undefined, { debugName: "properties" }] : /* istanbul ignore next */ []));
32
38
  /** SwiftUI: the user dismissed the sheet, or it was presented by something other than the prop. */
33
39
  isPresentedChange = output();
34
40
  /** SwiftUI: the sheet has finished dismissing, whoever dismissed it. */
@@ -40,7 +46,7 @@ export class UiBottomSheetView {
40
46
  keepNativeView(inject(ElementRef).nativeElement);
41
47
  }
42
48
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: UiBottomSheetView, deps: [], target: i0.ɵɵFactoryTarget.Component });
43
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.2.0", type: UiBottomSheetView, isStandalone: true, selector: "ui-bottom-sheet-view", inputs: { isPresented: { classPropertyName: "isPresented", publicName: "isPresented", isSignal: true, isRequired: false, transformFunction: null }, fitToContents: { classPropertyName: "fitToContents", publicName: "fitToContents", isSignal: true, isRequired: false, transformFunction: null }, showDragHandle: { classPropertyName: "showDragHandle", publicName: "showDragHandle", isSignal: true, isRequired: false, transformFunction: null }, containerColor: { classPropertyName: "containerColor", publicName: "containerColor", isSignal: true, isRequired: false, transformFunction: null }, skipPartiallyExpanded: { classPropertyName: "skipPartiallyExpanded", publicName: "skipPartiallyExpanded", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { isPresentedChange: "isPresentedChange", dismiss: "dismiss", dismissRequest: "dismissRequest" }, host: { properties: { "isPresented": "isPresented()", "fitToContents": "fitToContents()", "showDragHandle": "showDragHandle()", "containerColor": "containerColor()", "skipPartiallyExpanded": "skipPartiallyExpanded()" } }, ngImport: i0, template: '<ng-content />', isInline: true });
49
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.2.0", type: UiBottomSheetView, isStandalone: true, selector: "ui-bottom-sheet-view", inputs: { isPresented: { classPropertyName: "isPresented", publicName: "isPresented", isSignal: true, isRequired: false, transformFunction: null }, fitToContents: { classPropertyName: "fitToContents", publicName: "fitToContents", isSignal: true, isRequired: false, transformFunction: null }, showDragHandle: { classPropertyName: "showDragHandle", publicName: "showDragHandle", isSignal: true, isRequired: false, transformFunction: null }, containerColor: { classPropertyName: "containerColor", publicName: "containerColor", isSignal: true, isRequired: false, transformFunction: null }, skipPartiallyExpanded: { classPropertyName: "skipPartiallyExpanded", publicName: "skipPartiallyExpanded", isSignal: true, isRequired: false, transformFunction: null }, sheetGesturesEnabled: { classPropertyName: "sheetGesturesEnabled", publicName: "sheetGesturesEnabled", isSignal: true, isRequired: false, transformFunction: null }, properties: { classPropertyName: "properties", publicName: "properties", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { isPresentedChange: "isPresentedChange", dismiss: "dismiss", dismissRequest: "dismissRequest" }, host: { properties: { "isPresented": "isPresented()", "fitToContents": "fitToContents()", "showDragHandle": "showDragHandle()", "containerColor": "containerColor()", "skipPartiallyExpanded": "skipPartiallyExpanded()", "sheetGesturesEnabled": "sheetGesturesEnabled()", "properties": "properties()" } }, ngImport: i0, template: '<ng-content />', isInline: true });
44
50
  }
45
51
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImport: i0, type: UiBottomSheetView, decorators: [{
46
52
  type: Component,
@@ -53,7 +59,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.2.0", ngImpor
53
59
  '[showDragHandle]': 'showDragHandle()',
54
60
  '[containerColor]': 'containerColor()',
55
61
  '[skipPartiallyExpanded]': 'skipPartiallyExpanded()',
62
+ '[sheetGesturesEnabled]': 'sheetGesturesEnabled()',
63
+ '[properties]': 'properties()',
56
64
  },
57
65
  }]
58
- }], ctorParameters: () => [], propDecorators: { isPresented: [{ type: i0.Input, args: [{ isSignal: true, alias: "isPresented", required: false }] }], fitToContents: [{ type: i0.Input, args: [{ isSignal: true, alias: "fitToContents", required: false }] }], showDragHandle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDragHandle", required: false }] }], containerColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "containerColor", required: false }] }], skipPartiallyExpanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "skipPartiallyExpanded", required: false }] }], isPresentedChange: [{ type: i0.Output, args: ["isPresentedChange"] }], dismiss: [{ type: i0.Output, args: ["dismiss"] }], dismissRequest: [{ type: i0.Output, args: ["dismissRequest"] }] } });
66
+ }], ctorParameters: () => [], propDecorators: { isPresented: [{ type: i0.Input, args: [{ isSignal: true, alias: "isPresented", required: false }] }], fitToContents: [{ type: i0.Input, args: [{ isSignal: true, alias: "fitToContents", required: false }] }], showDragHandle: [{ type: i0.Input, args: [{ isSignal: true, alias: "showDragHandle", required: false }] }], containerColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "containerColor", required: false }] }], skipPartiallyExpanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "skipPartiallyExpanded", required: false }] }], sheetGesturesEnabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "sheetGesturesEnabled", required: false }] }], properties: [{ type: i0.Input, args: [{ isSignal: true, alias: "properties", required: false }] }], isPresentedChange: [{ type: i0.Output, args: ["isPresentedChange"] }], dismiss: [{ type: i0.Output, args: ["dismiss"] }], dismissRequest: [{ type: i0.Output, args: ["dismissRequest"] }] } });
59
67
  //# sourceMappingURL=ui-bottom-sheet-view.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ui-bottom-sheet-view.js","sourceRoot":"","sources":["../src/ui-bottom-sheet-view.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC7E,OAAO,EAA6B,cAAc,EAAE,MAAM,mBAAmB,CAAC;;AAgB9E,MAAM,OAAO,iBAAiB;IAC5B,sFAAsF;IAC7E,WAAW,GAAG,KAAK;+FAAW,CAAC;IACxC,oEAAoE;IAC3D,aAAa,GAAG,KAAK;iGAAW,CAAC;IAC1C,iDAAiD;IACxC,cAAc,GAAG,KAAK;kGAAW,CAAC;IAC3C,gGAAgG;IACvF,cAAc,GAAG,KAAK;kGAAW,CAAC;IAC3C,4FAA4F;IACnF,qBAAqB,GAAG,KAAK;yGAAW,CAAC;IAClD,mGAAmG;IAC1F,iBAAiB,GAAG,MAAM,EAA+B,CAAC;IACnE,wEAAwE;IAC/D,OAAO,GAAG,MAAM,EAA+C,CAAC;IACzE,mFAAmF;IAC1E,cAAc,GAAG,MAAM,EAA+C,CAAC;IAEhF;QACE,iFAAiF;QACjF,cAAc,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,aAAa,CAAC,CAAC;IACnD,CAAC;uGArBU,iBAAiB;2FAAjB,iBAAiB,6oCATlB,gBAAgB;;2FASf,iBAAiB;kBAX7B,SAAS;mBAAC;oBACT,QAAQ,EAAE,sBAAsB;oBAChC,QAAQ,EAAE,gBAAgB;oBAC1B,IAAI,EAAE;wBACJ,eAAe,EAAE,eAAe;wBAChC,iBAAiB,EAAE,iBAAiB;wBACpC,kBAAkB,EAAE,kBAAkB;wBACtC,kBAAkB,EAAE,kBAAkB;wBACtC,yBAAyB,EAAE,yBAAyB;qBACrD;iBACF","sourcesContent":["/**\n * `<ui-bottom-sheet-view>`: `@expo/ui`'s sheet itself, with each platform's props typed.\n *\n * `UiBottomSheet` in `expo-ui-components.ts` is the component an app uses, and this is the native\n * view inside it: SwiftUI's `BottomSheetView` on iOS, Compose's `ModalBottomSheetView` on Android.\n * It has an element name of its own because `ui-bottom-sheet` is that component's, and a\n * component's template takes its own selector to be the component again.\n *\n * The two views share no prop. An input left unset never reaches native, so the component sets\n * the ones the platform reads. The events are outputs for their type only, as with the other\n * `@expo/ui` components: the element's own event is what arrives.\n */\nimport { Component, ElementRef, inject, input, output } from '@angular/core';\nimport { type NativeSyntheticEvent, keepNativeView } from '@ng-native/fabric';\n\n/** What SwiftUI's sheet sends when the user dismisses it: that it is no longer presented. */\nexport type UiBottomSheetPresentedEvent = NativeSyntheticEvent<{ readonly isPresented: boolean }>;\n\n@Component({\n selector: 'ui-bottom-sheet-view',\n template: '<ng-content />',\n host: {\n '[isPresented]': 'isPresented()',\n '[fitToContents]': 'fitToContents()',\n '[showDragHandle]': 'showDragHandle()',\n '[containerColor]': 'containerColor()',\n '[skipPartiallyExpanded]': 'skipPartiallyExpanded()',\n },\n})\nexport class UiBottomSheetView {\n /** SwiftUI: whether the sheet is presented. The view stays in the tree either way. */\n readonly isPresented = input<boolean>();\n /** SwiftUI: the sheet's one detent is the height of its content. */\n readonly fitToContents = input<boolean>();\n /** Compose: whether the drag handle is drawn. */\n readonly showDragHandle = input<boolean>();\n /** Compose: the sheet's colour behind its content. A colour prop, so the engine converts it. */\n readonly containerColor = input<unknown>();\n /** Compose: the sheet opens at its full height rather than resting at half the screen's. */\n readonly skipPartiallyExpanded = input<boolean>();\n /** SwiftUI: the user dismissed the sheet, or it was presented by something other than the prop. */\n readonly isPresentedChange = output<UiBottomSheetPresentedEvent>();\n /** SwiftUI: the sheet has finished dismissing, whoever dismissed it. */\n readonly dismiss = output<NativeSyntheticEvent<Record<string, never>>>();\n /** Compose: the user dismissed the sheet, which stays until it leaves the tree. */\n readonly dismissRequest = output<NativeSyntheticEvent<Record<string, never>>>();\n\n constructor() {\n // The host is the registered native view, not a plain one: see `keepNativeView`.\n keepNativeView(inject(ElementRef).nativeElement);\n }\n}\n"]}
1
+ {"version":3,"file":"ui-bottom-sheet-view.js","sourceRoot":"","sources":["../src/ui-bottom-sheet-view.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC7E,OAAO,EAA6B,cAAc,EAAE,MAAM,mBAAmB,CAAC;;AAwB9E,MAAM,OAAO,iBAAiB;IAC5B,sFAAsF;IAC7E,WAAW,GAAG,KAAK;+FAAW,CAAC;IACxC,oEAAoE;IAC3D,aAAa,GAAG,KAAK;iGAAW,CAAC;IAC1C,iDAAiD;IACxC,cAAc,GAAG,KAAK;kGAAW,CAAC;IAC3C,gGAAgG;IACvF,cAAc,GAAG,KAAK;kGAAW,CAAC;IAC3C,4FAA4F;IACnF,qBAAqB,GAAG,KAAK;yGAAW,CAAC;IAClD,6FAA6F;IACpF,oBAAoB,GAAG,KAAK;wGAAW,CAAC;IACjD,+EAA+E;IACtE,UAAU,GAAG,KAAK;8FAAgC,CAAC;IAC5D,mGAAmG;IAC1F,iBAAiB,GAAG,MAAM,EAA+B,CAAC;IACnE,wEAAwE;IAC/D,OAAO,GAAG,MAAM,EAA+C,CAAC;IACzE,mFAAmF;IAC1E,cAAc,GAAG,MAAM,EAA+C,CAAC;IAEhF;QACE,iFAAiF;QACjF,cAAc,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,aAAa,CAAC,CAAC;IACnD,CAAC;uGAzBU,iBAAiB;2FAAjB,iBAAiB,ygDAXlB,gBAAgB;;2FAWf,iBAAiB;kBAb7B,SAAS;mBAAC;oBACT,QAAQ,EAAE,sBAAsB;oBAChC,QAAQ,EAAE,gBAAgB;oBAC1B,IAAI,EAAE;wBACJ,eAAe,EAAE,eAAe;wBAChC,iBAAiB,EAAE,iBAAiB;wBACpC,kBAAkB,EAAE,kBAAkB;wBACtC,kBAAkB,EAAE,kBAAkB;wBACtC,yBAAyB,EAAE,yBAAyB;wBACpD,wBAAwB,EAAE,wBAAwB;wBAClD,cAAc,EAAE,cAAc;qBAC/B;iBACF","sourcesContent":["/**\n * `<ui-bottom-sheet-view>`: `@expo/ui`'s sheet itself, with each platform's props typed.\n *\n * `UiBottomSheet` in `expo-ui-components.ts` is the component an app uses, and this is the native\n * view inside it: SwiftUI's `BottomSheetView` on iOS, Compose's `ModalBottomSheetView` on Android.\n * It has an element name of its own because `ui-bottom-sheet` is that component's, and a\n * component's template takes its own selector to be the component again.\n *\n * The two views share no prop. An input left unset never reaches native, so the component sets\n * the ones the platform reads. The events are outputs for their type only, as with the other\n * `@expo/ui` components: the element's own event is what arrives.\n */\nimport { Component, ElementRef, inject, input, output } from '@angular/core';\nimport { type NativeSyntheticEvent, keepNativeView } from '@ng-native/fabric';\n\n/** How Compose's sheet may be dismissed other than by a swipe, as `@expo/ui` names it. */\nexport interface UiModalBottomSheetProperties {\n readonly shouldDismissOnBackPress?: boolean;\n readonly shouldDismissOnClickOutside?: boolean;\n}\n\n/** What SwiftUI's sheet sends when the user dismisses it: that it is no longer presented. */\nexport type UiBottomSheetPresentedEvent = NativeSyntheticEvent<{ readonly isPresented: boolean }>;\n\n@Component({\n selector: 'ui-bottom-sheet-view',\n template: '<ng-content />',\n host: {\n '[isPresented]': 'isPresented()',\n '[fitToContents]': 'fitToContents()',\n '[showDragHandle]': 'showDragHandle()',\n '[containerColor]': 'containerColor()',\n '[skipPartiallyExpanded]': 'skipPartiallyExpanded()',\n '[sheetGesturesEnabled]': 'sheetGesturesEnabled()',\n '[properties]': 'properties()',\n },\n})\nexport class UiBottomSheetView {\n /** SwiftUI: whether the sheet is presented. The view stays in the tree either way. */\n readonly isPresented = input<boolean>();\n /** SwiftUI: the sheet's one detent is the height of its content. */\n readonly fitToContents = input<boolean>();\n /** Compose: whether the drag handle is drawn. */\n readonly showDragHandle = input<boolean>();\n /** Compose: the sheet's colour behind its content. A colour prop, so the engine converts it. */\n readonly containerColor = input<unknown>();\n /** Compose: the sheet opens at its full height rather than resting at half the screen's. */\n readonly skipPartiallyExpanded = input<boolean>();\n /** Compose: whether the sheet can be dragged, between its heights and down to dismiss it. */\n readonly sheetGesturesEnabled = input<boolean>();\n /** Compose: whether the back button and a tap outside the sheet dismiss it. */\n readonly properties = input<UiModalBottomSheetProperties>();\n /** SwiftUI: the user dismissed the sheet, or it was presented by something other than the prop. */\n readonly isPresentedChange = output<UiBottomSheetPresentedEvent>();\n /** SwiftUI: the sheet has finished dismissing, whoever dismissed it. */\n readonly dismiss = output<NativeSyntheticEvent<Record<string, never>>>();\n /** Compose: the user dismissed the sheet, which stays until it leaves the tree. */\n readonly dismissRequest = output<NativeSyntheticEvent<Record<string, never>>>();\n\n constructor() {\n // The host is the registered native view, not a plain one: see `keepNativeView`.\n keepNativeView(inject(ElementRef).nativeElement);\n }\n}\n"]}
@@ -1,7 +1,12 @@
1
1
  {
2
2
  "platforms": ["apple", "android"],
3
3
  "apple": {
4
- "modules": ["LiveActivityTapsModule", "ExpoUIModifiersModule", "SearchFieldModule"],
4
+ "modules": [
5
+ "LiveActivityTapsModule",
6
+ "ExpoUIModifiersModule",
7
+ "SearchFieldModule",
8
+ "SymbolImageSizeModule"
9
+ ],
5
10
  "appDelegateSubscribers": ["LiveActivityTapsObserver"]
6
11
  },
7
12
  "android": {
@@ -0,0 +1,53 @@
1
+ import ExpoModulesCore
2
+ import UIKit
3
+
4
+ /**
5
+ The size of an SF Symbol's image at a point size, weight and scale, which `<expo-symbol>` lays its
6
+ view out at. SwiftUI lays an `Image(systemName:)` out at this size, while `expo-symbols` builds the
7
+ symbol at the system font size and fits it into the view, so the view's size is what sets how
8
+ large the symbol draws.
9
+ */
10
+ public final class SymbolImageSizeModule: Module {
11
+ public func definition() -> ModuleDefinition {
12
+ Name("NgNativeSymbol")
13
+
14
+ Function("imageSize") { (name: String, pointSize: Double, weight: String?, scale: String?) -> [Double]? in
15
+ let configuration = UIImage.SymbolConfiguration(
16
+ pointSize: CGFloat(pointSize),
17
+ weight: Self.weight(weight),
18
+ scale: Self.scale(scale)
19
+ )
20
+ guard let image = UIImage(systemName: name, withConfiguration: configuration) else {
21
+ return nil
22
+ }
23
+ return [Double(image.size.width), Double(image.size.height)]
24
+ }
25
+ }
26
+
27
+ /// `expo-symbols`' weights, as UIKit's.
28
+ private static func weight(_ name: String?) -> UIImage.SymbolWeight {
29
+ switch name {
30
+ case "ultraLight": return .ultraLight
31
+ case "thin": return .thin
32
+ case "light": return .light
33
+ case "regular": return .regular
34
+ case "medium": return .medium
35
+ case "semibold": return .semibold
36
+ case "bold": return .bold
37
+ case "heavy": return .heavy
38
+ case "black": return .black
39
+ default: return .unspecified
40
+ }
41
+ }
42
+
43
+ /// `expo-symbols`' scales, as UIKit's.
44
+ private static func scale(_ name: String?) -> UIImage.SymbolScale {
45
+ switch name {
46
+ case "small": return .small
47
+ case "medium": return .medium
48
+ case "large": return .large
49
+ case "unspecified": return .unspecified
50
+ default: return .default
51
+ }
52
+ }
53
+ }