Merge remote-tracking branch 'origin/main' into feat/v9-prerelease

This commit is contained in:
Nathan Walker
2025-11-02 08:54:59 -08:00
42 changed files with 729 additions and 417 deletions

View File

@@ -97,11 +97,6 @@ interface ApplicationEvents {
*/
on(event: 'livesync', callback: (args: ApplicationEventData) => void, thisArg?: any): void;
/**
* This event is raised when application css is changed.
*/
on(event: 'cssChanged', callback: (args: CssChangedEventData) => void, thisArg?: any): void;
/**
* This event is raised on application launchEvent.
*/

View File

@@ -19,7 +19,7 @@ export class ImageAssetBase extends Observable implements ImageAssetDefinition {
}
set options(value: ImageAssetOptions) {
this._options = value;
this._options = normalizeImageAssetOptions(value);
}
get nativeImage(): any {
@@ -35,6 +35,35 @@ export class ImageAssetBase extends Observable implements ImageAssetDefinition {
}
}
function toPositiveInt(value: any): number {
if (value == null) {
return 0;
}
if (typeof value === 'number') {
return value > 0 ? Math.floor(value) : 0;
}
if (typeof value === 'string') {
const parsed = parseInt(value, 10);
return isNaN(parsed) || parsed <= 0 ? 0 : parsed;
}
return 0;
}
function normalizeImageAssetOptions(options: ImageAssetOptions): ImageAssetOptions {
const normalized = options ? { ...options } : ({} as ImageAssetOptions);
// Coerce potential string values to positive integers; fallback to 0
// to trigger default sizing downstream
(normalized as any).width = toPositiveInt((options as any)?.width);
(normalized as any).height = toPositiveInt((options as any)?.height);
if (typeof normalized.keepAspectRatio !== 'boolean') {
normalized.keepAspectRatio = true;
}
if (typeof normalized.autoScaleFactor !== 'boolean') {
normalized.autoScaleFactor = true;
}
return normalized;
}
export function getAspectSafeDimensions(sourceWidth, sourceHeight, reqWidth, reqHeight) {
const widthCoef = sourceWidth / reqWidth;
const heightCoef = sourceHeight / reqHeight;
@@ -47,8 +76,9 @@ export function getAspectSafeDimensions(sourceWidth, sourceHeight, reqWidth, req
}
export function getRequestedImageSize(src: { width: number; height: number }, options: ImageAssetOptions): { width: number; height: number } {
let reqWidth = options.width || Math.min(src.width, Screen.mainScreen.widthPixels);
let reqHeight = options.height || Math.min(src.height, Screen.mainScreen.heightPixels);
const normalized = normalizeImageAssetOptions(options);
let reqWidth = normalized.width || Math.min(src.width, Screen.mainScreen.widthPixels);
let reqHeight = normalized.height || Math.min(src.height, Screen.mainScreen.heightPixels);
if (options && options.keepAspectRatio) {
const safeAspectSize = getAspectSafeDimensions(src.width, src.height, reqWidth, reqHeight);

View File

@@ -0,0 +1,27 @@
import { getRequestedImageSize } from './image-asset-common';
describe('ImageAssetOptions normalization', () => {
it('coerces string width/height to numbers', () => {
const src = { width: 2000, height: 1500 };
const result = getRequestedImageSize(src as any, { width: '300' as any, height: '200' as any, keepAspectRatio: false, autoScaleFactor: true } as any);
expect(result.width).toBe(300);
expect(result.height).toBe(200);
});
it('falls back to defaults when invalid strings provided', () => {
const src = { width: 800, height: 600 };
const result = getRequestedImageSize(src as any, { width: 'abc' as any, height: '' as any, keepAspectRatio: false } as any);
// should fall back to screen pixel defaults via getRequestedImageSize, but since
// we cannot easily control Screen.mainScreen here, we at least assert they are > 0
expect(result.width).toBeGreaterThan(0);
expect(result.height).toBeGreaterThan(0);
});
it('respects keepAspectRatio by adjusting to safe dimensions', () => {
const src = { width: 2000, height: 1000 };
const result = getRequestedImageSize(src as any, { width: '500' as any, height: '500' as any, keepAspectRatio: true } as any);
// current implementation scales using the smaller coefficient (min), so expect 1000x500
expect(result.width).toBe(1000);
expect(result.height).toBe(500);
});
});

View File

@@ -181,13 +181,14 @@ export class ImageSource implements ImageSourceDefinition {
const textBounds = new android.graphics.Rect();
paint.getTextBounds(source, 0, source.length, textBounds);
const textWidth = textBounds.width();
const textHeight = textBounds.height();
const padding = 1;
const textWidth = textBounds.width() + padding * 2;
const textHeight = textBounds.height() + padding * 2;
if (textWidth > 0 && textHeight > 0) {
const bitmap = android.graphics.Bitmap.createBitmap(textWidth, textHeight, android.graphics.Bitmap.Config.ARGB_8888);
const canvas = new android.graphics.Canvas(bitmap);
canvas.drawText(source, -textBounds.left, -textBounds.top, paint);
canvas.drawText(source, -textBounds.left + padding, -textBounds.top + padding, paint);
return new ImageSource(bitmap);
}

View File

Binary file not shown.

View File

@@ -4,7 +4,7 @@
/// <reference path="../types-ios/src/lib/ios/objc-x86_64/objc!Darwin.d.ts" />
/// <reference path="../types-ios/src/lib/ios/objc-x86_64/objc!DarwinFoundation.d.ts" />
/// <reference path="../types-ios/src/lib/ios/objc-x86_64/objc!Symbols.d.ts" />
/// <reference path="../types-android/src/lib/android-29.d.ts" />
/// <reference path="../types-android/src/lib/android-30.d.ts" />
/// <reference path="./platforms/ios/typings/objc!MaterialComponents.d.ts" />
/// <reference path="./platforms/ios/typings/objc!NativeScriptUtils.d.ts" />
/// <reference path="./global-types.d.ts" />

View File

@@ -1,3 +1,16 @@
export type { Pair, Transformation, TransformationType, TransformationValue, TransformFunctionsInfo, Point3D, AnimationPromise, Cancelable } from './animation-types';
export { Animation, _resolveAnimationCurve } from './animation';
export { KeyframeAnimation, KeyframeAnimationInfo, KeyframeDeclaration, KeyframeInfo } from './keyframe-animation';
import type { AnimationDefinition, AnimationPromise } from './animation-types';
/**
* Defines a animation set.
*/
export class Animation {
constructor(animationDefinitions: Array<AnimationDefinition>, playSequentially?: boolean);
public play: (resetOnFinish?: boolean) => AnimationPromise;
public cancel: () => void;
public isPlaying: boolean;
public _resolveAnimationCurve(curve: any): any;
}
export function _resolveAnimationCurve(curve: any): any;

View File

@@ -10,7 +10,7 @@ export class Button extends TextBase {
/**
* String value used when hooking to tap event.
*
* @nsEvent {EventData} string;
* @nsEvent {EventData} tap
*/
public static tapEvent: string;

View File

@@ -1,7 +1,7 @@
import type { Point, Position } from './view-interfaces';
import type { GestureTypes, GestureEventData } from '../../gestures';
import { getNativeScriptGlobals } from '../../../globals/global-utils';
import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty, testIDProperty, AndroidHelper } from './view-common';
import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty, testIDProperty, AndroidHelper, statusBarStyleProperty } from './view-common';
import { paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty } from '../../styling/style-properties';
import { Length } from '../../styling/length-shared';
import { layout } from '../../../utils';
@@ -52,6 +52,10 @@ const GRAVITY_FILL_HORIZONTAL = 7; // android.view.Gravity.FILL_HORIZONTAL
const GRAVITY_CENTER_VERTICAL = 16; // android.view.Gravity.CENTER_VERTICAL
const GRAVITY_FILL_VERTICAL = 112; // android.view.Gravity.FILL_VERTICAL
const SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = 0x00002000;
const STATUS_BAR_LIGHT_BCKG = -657931;
const STATUS_BAR_DARK_BCKG = 1711276032;
const modalMap = new Map<number, DialogOptions>();
let TouchListener: TouchListener;
@@ -1218,6 +1222,89 @@ export class View extends ViewCommon {
}
}
[statusBarStyleProperty.getDefault]() {
return this.style.statusBarStyle;
}
[statusBarStyleProperty.setNative](value: 'dark' | 'light' | { color: number; systemUiVisibility: number }) {
this.updateStatusBarStyle(value);
}
updateStatusBarStyle(value: 'dark' | 'light' | { color: number; systemUiVisibility: number }) {
if (SDK_VERSION < 21) return; // nothing we can do
const window = this.getClosestWindow();
// Ensure the window draws system bar backgrounds (required to color status bar)
window.clearFlags(android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.addFlags(android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
const decorView = window.getDecorView();
// API 30+ path (preferred)
const controller = window.getInsetsController?.();
if (controller && SDK_VERSION >= 30) {
const APPEARANCE_LIGHT_STATUS_BARS = android.view.WindowInsetsController?.APPEARANCE_LIGHT_STATUS_BARS;
if (typeof value === 'string') {
this.style.statusBarStyle = value;
if (value === 'light') {
// light icons/text
controller.setSystemBarsAppearance(0, APPEARANCE_LIGHT_STATUS_BARS);
} else {
// dark icons/text
controller.setSystemBarsAppearance(APPEARANCE_LIGHT_STATUS_BARS, APPEARANCE_LIGHT_STATUS_BARS);
}
} else {
if (value.color != null) window.setStatusBarColor(value.color);
// No direct passthrough for systemUiVisibility on API 30+, use appearances instead
}
return;
}
// API 2329 path (systemUiVisibility)
if (SDK_VERSION >= 23) {
if (typeof value === 'string') {
this.style.statusBarStyle = value;
let flags = decorView.getSystemUiVisibility();
if (value === 'light') {
// Add the LIGHT_STATUS_BAR bit without clobbering other flags
flags |= android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
decorView.setSystemUiVisibility(flags);
} else {
// Remove only the LIGHT_STATUS_BAR bit
flags &= ~android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
decorView.setSystemUiVisibility(flags);
}
} else {
if (value.color != null) window.setStatusBarColor(value.color);
if (value.systemUiVisibility != null) {
// Preserve existing flags, dont blindly overwrite to 0
const merged = (decorView.getSystemUiVisibility() & ~android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) | (value.systemUiVisibility & android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
decorView.setSystemUiVisibility(merged);
}
}
return;
}
// API 2122: you can only change the background color; icon color is fixed (light)
if (typeof value === 'object' && value.color != null) {
window.setStatusBarColor(value.color);
}
}
getClosestWindow(): android.view.Window {
// When it comes to modals, check parent as it may not be the modal root view itself
const view = this.parent ?? this;
const dialogFragment = (view as this)._dialogFragment;
if (dialogFragment) {
const dialog = dialogFragment.getDialog();
if (dialog) {
return dialog.getWindow();
}
}
return this._context.getWindow();
}
[testIDProperty.setNative](value: string) {
this.setAccessibilityIdentifier(this.nativeViewProtected, value);
}

View File

@@ -649,8 +649,10 @@ export abstract class View extends ViewCommon {
cssType: string;
/**
* (iOS only) Gets or sets the status bar style for this view.
* Note: You must remove Info.plist key `UIViewControllerBasedStatusBarAppearance`
* Gets or sets the status bar style for this view.
* Platform Notes:
* - Android: When using this property throughout navigations, ensure starting views have it set as well. Ensures it will reset on back navigation.
* - iOS: You must remove Info.plist key `UIViewControllerBasedStatusBarAppearance`
* It defaults to true when not present: https://developer.apple.com/documentation/bundleresources/information-property-list/uiviewcontrollerbasedstatusbarappearance
* Or you can explicitly set it to true:
* <key>UIViewControllerBasedStatusBarAppearance</key>

View File

@@ -1,5 +1,5 @@
import type { Point, Position } from './view-interfaces';
import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty, testIDProperty, iosGlassEffectProperty, GlassEffectType, GlassEffectVariant } from './view-common';
import { ViewCommon, isEnabledProperty, originXProperty, originYProperty, isUserInteractionEnabledProperty, testIDProperty, iosGlassEffectProperty, GlassEffectType, GlassEffectVariant, statusBarStyleProperty } from './view-common';
import { isAccessibilityServiceEnabled } from '../../../application';
import { updateA11yPropertiesCallback } from '../../../application/helpers-common';
import { ShowModalOptions, hiddenProperty } from '../view-base';
@@ -904,6 +904,28 @@ export class View extends ViewCommon {
}
}
[statusBarStyleProperty.getDefault]() {
return this.style.statusBarStyle;
}
[statusBarStyleProperty.setNative](value: 'light' | 'dark') {
this.style.statusBarStyle = value;
const parent = this.parent;
if (parent) {
const ctrl = parent.ios?.controller;
if (ctrl && ctrl instanceof UINavigationController) {
const navigationBar = ctrl.navigationBar;
if (!navigationBar) return;
if (typeof value === 'string') {
console.log('here:', value);
navigationBar.barStyle = value === 'dark' ? UIBarStyle.Black : UIBarStyle.Default;
} else {
navigationBar.barStyle = value;
}
}
}
}
[iosGlassEffectProperty.setNative](value: GlassEffectType) {
if (!this.nativeViewProtected || !supportsGlass()) {
return;

View File

@@ -6,7 +6,8 @@ import { layout } from '../../../utils';
import { isObject } from '../../../utils/types';
import { sanitizeModuleName } from '../../../utils/common';
import { Color } from '../../../color';
import { Property, InheritedProperty } from '../properties';
import { Property, InheritedProperty, CssProperty } from '../properties';
import { Style } from '../../styling/style';
import { EventData } from '../../../data/observable';
import { ViewHelper } from './view-helper';
import { setupAccessibleView } from '../../../application/helpers';
@@ -195,6 +196,12 @@ export abstract class ViewCommon extends ViewBase {
super.onLoaded();
setupAccessibleView(this);
if (this.statusBarStyle) {
// reapply status bar style on load
// helps back navigation cases to restore if overridden
this.updateStatusBarStyle(this.statusBarStyle);
}
}
public _closeAllModalViewsInternal(): boolean {
@@ -972,6 +979,14 @@ export abstract class ViewCommon extends ViewBase {
this.style.androidDynamicElevationOffset = value;
}
/**
* (Android only) Gets closest window parent considering modals.
*/
getClosestWindow(): android.view.Window {
// platform impl
return null;
}
//END Style property shortcuts
public originX: number;
@@ -1005,6 +1020,10 @@ export abstract class ViewCommon extends ViewBase {
this.style.statusBarStyle = value;
}
updateStatusBarStyle(value: 'dark' | 'light') {
// platform specific impl
}
get isLayoutRequired(): boolean {
return true;
}
@@ -1298,6 +1317,15 @@ export const isUserInteractionEnabledProperty = new Property<ViewCommon, boolean
});
isUserInteractionEnabledProperty.register(ViewCommon);
/**
* Property backing statusBarStyle.
*/
export const statusBarStyleProperty = new CssProperty<Style, 'light' | 'dark'>({
name: 'statusBarStyle',
cssName: 'status-bar-style',
});
statusBarStyleProperty.register(Style);
// Apple only
export const iosOverflowSafeAreaProperty = new Property<ViewCommon, boolean>({
name: 'iosOverflowSafeArea',

View File

@@ -634,6 +634,11 @@ class UINavigationControllerImpl extends UINavigationController {
}
}
}
// @ts-ignore
public get childViewControllerForStatusBarStyle() {
return this.topViewController;
}
}
function _getTransitionId(nativeTransition: UIViewAnimationTransition, transitionType: string): string {

View File

@@ -1,5 +1,5 @@
import { isAccessibilityServiceEnabled } from '../../application';
import { PageBase, actionBarHiddenProperty, statusBarStyleProperty, androidStatusBarBackgroundProperty } from './page-common';
import { PageBase, actionBarHiddenProperty, androidStatusBarBackgroundProperty } from './page-common';
import { View } from '../core/view';
import { Color } from '../../color';
import { ActionBar } from '../action-bar';
@@ -10,10 +10,6 @@ import { AndroidAccessibilityEvent, getLastFocusedViewOnPage } from '../../acces
export * from './page-common';
const SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = 0x00002000;
const STATUS_BAR_LIGHT_BCKG = -657931;
const STATUS_BAR_DARK_BCKG = 1711276032;
export class Page extends PageBase {
nativeViewProtected: org.nativescript.widgets.GridLayout;
@@ -69,19 +65,6 @@ export class Page extends PageBase {
}
}
private getClosestWindow() {
// When it comes to modals, check if page has a parent as it may not be the modal root view itself
const view = this.parent ?? this;
const dialogFragment = (<any>view)._dialogFragment;
if (dialogFragment) {
const dialog = dialogFragment.getDialog();
if (dialog) {
return dialog.getWindow();
}
}
return this._context.getWindow();
}
[actionBarHiddenProperty.setNative](value: boolean) {
// in case the actionBar is not created and actionBarHidden is changed to true
// the actionBar will be created by updateActionBar
@@ -90,44 +73,10 @@ export class Page extends PageBase {
}
}
[statusBarStyleProperty.getDefault](): {
color: number;
systemUiVisibility: number;
} {
if (SDK_VERSION >= 21) {
const window = this.getClosestWindow();
const decorView = window.getDecorView();
return {
color: (<any>window).getStatusBarColor(),
systemUiVisibility: decorView.getSystemUiVisibility(),
};
}
return null;
}
[statusBarStyleProperty.setNative](value: 'dark' | 'light' | { color: number; systemUiVisibility: number }) {
if (SDK_VERSION >= 21) {
const window = this.getClosestWindow();
const decorView = window.getDecorView();
if (value === 'light') {
(<any>window).setStatusBarColor(STATUS_BAR_LIGHT_BCKG);
decorView.setSystemUiVisibility(SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
} else if (value === 'dark') {
(<any>window).setStatusBarColor(STATUS_BAR_DARK_BCKG);
decorView.setSystemUiVisibility(0);
} else {
(<any>window).setStatusBarColor(value.color);
decorView.setSystemUiVisibility(value.systemUiVisibility);
}
}
}
[androidStatusBarBackgroundProperty.getDefault](): number {
if (SDK_VERSION >= 21) {
const window = this.getClosestWindow();
return (<any>window).getStatusBarColor();
return window.getStatusBarColor();
}
return null;
@@ -136,7 +85,7 @@ export class Page extends PageBase {
if (SDK_VERSION >= 21) {
const window = this.getClosestWindow();
const color = value instanceof Color ? value.android : value;
(<any>window).setStatusBarColor(color);
window.setStatusBarColor(color);
}
}

View File

@@ -2,7 +2,7 @@ import { isAccessibilityServiceEnabled } from '../../application';
import type { Frame } from '../frame';
import { BackstackEntry, NavigationType } from '../frame/frame-interfaces';
import { View, IOSHelper } from '../core/view';
import { PageBase, actionBarHiddenProperty, statusBarStyleProperty } from './page-common';
import { PageBase, actionBarHiddenProperty } from './page-common';
import { profile } from '../../profiling';
import { layout } from '../../utils/layout-helper';
@@ -350,7 +350,7 @@ class UIViewControllerImpl extends UIViewController {
const owner = this._owner?.deref();
if (owner) {
if (SDK_VERSION >= 13) {
return owner.statusBarStyle === 'dark' ? UIStatusBarStyle.DarkContent : UIStatusBarStyle.LightContent;
return owner.statusBarStyle === 'light' ? UIStatusBarStyle.LightContent : UIStatusBarStyle.DarkContent;
} else {
return owner.statusBarStyle === 'dark' ? UIStatusBarStyle.LightContent : UIStatusBarStyle.Default;
}
@@ -561,21 +561,6 @@ export class Page extends PageBase {
}
}
[statusBarStyleProperty.getDefault](): UIBarStyle {
return UIBarStyle.Default;
}
[statusBarStyleProperty.setNative](value: string | UIBarStyle) {
const frame = this.frame;
if (frame) {
const navigationBar = (<UINavigationController>frame.ios.controller).navigationBar;
if (typeof value === 'string') {
navigationBar.barStyle = value === 'dark' ? UIBarStyle.Black : UIBarStyle.Default;
} else {
navigationBar.barStyle = value;
}
}
}
public accessibilityScreenChanged(refocus = false): void {
if (!isAccessibilityServiceEnabled()) {
return;

View File

@@ -207,15 +207,6 @@ export const enableSwipeBackNavigationProperty = new Property<PageBase, boolean>
});
enableSwipeBackNavigationProperty.register(PageBase);
/**
* Property backing statusBarStyle.
*/
export const statusBarStyleProperty = new CssProperty<Style, 'light' | 'dark'>({
name: 'statusBarStyle',
cssName: 'status-bar-style',
});
statusBarStyleProperty.register(Style);
/**
* Property backing androidStatusBarBackground.
*/

View File

@@ -1,5 +1,5 @@
import { Font } from '../styling/font';
import { SearchBarBase, textProperty, hintProperty, textFieldHintColorProperty, textFieldBackgroundColorProperty } from './search-bar-common';
import { SearchBarBase, textProperty, hintProperty, textFieldHintColorProperty, textFieldBackgroundColorProperty, clearButtonColorProperty } from './search-bar-common';
import { isUserInteractionEnabledProperty, isEnabledProperty } from '../core/view';
import { ad } from '../../utils';
import { Color } from '../../color';
@@ -33,6 +33,7 @@ function initializeNativeClasses(): void {
constructor(private owner: SearchBar) {
super();
// @ts-ignore
return global.__native(this);
}
@@ -70,6 +71,7 @@ function initializeNativeClasses(): void {
constructor(private owner: SearchBar) {
super();
// @ts-ignore
return global.__native(this);
}
@@ -272,6 +274,25 @@ export class SearchBar extends SearchBarBase {
const color = value instanceof Color ? value.android : value;
textView.setHintTextColor(color);
}
[clearButtonColorProperty.setNative](value: Color) {
if (!this.nativeViewProtected || !value) {
return;
}
try {
// The close (clear) button inside the SearchView can be found by its resource ID
const closeButtonId = this.nativeViewProtected.getContext().getResources().getIdentifier('android:id/search_close_btn', null, null);
const closeButton = this.nativeViewProtected.findViewById(closeButtonId) as android.widget.ImageView;
const color = value instanceof Color ? value.android : new Color(value).android;
if (closeButton) {
closeButton.setColorFilter(color);
}
} catch (err) {
console.log('Error setting clear button color:', err);
}
}
private _getTextView(): android.widget.TextView {
if (!this._searchTextView) {

View File

@@ -61,6 +61,13 @@ export class SearchBar extends View {
*/
textFieldHintColor: Color;
/**
* Gets or sets the Clear Button color of the SearchBar component.
*
* @nsProperty
*/
clearButtonColor: Color | string;
/**
* Adds a listener for the specified event name.
*

View File

@@ -1,5 +1,5 @@
import { Font } from '../styling/font';
import { SearchBarBase, textProperty, hintProperty, textFieldHintColorProperty, textFieldBackgroundColorProperty } from './search-bar-common';
import { SearchBarBase, textProperty, hintProperty, textFieldHintColorProperty, textFieldBackgroundColorProperty, clearButtonColorProperty } from './search-bar-common';
import { isEnabledProperty } from '../core/view';
import { Color } from '../../color';
import { colorProperty, backgroundColorProperty, backgroundInternalProperty, fontInternalProperty } from '../styling/style-properties';
@@ -220,4 +220,13 @@ export class SearchBar extends SearchBarBase {
const attributedPlaceholder = NSAttributedString.alloc().initWithStringAttributes(stringValue, attributes);
this._getTextField().attributedPlaceholder = attributedPlaceholder;
}
[clearButtonColorProperty.setNative](value: Color | UIColor) {
const textField = this._getTextField();
if (!textField) return;
// Check if clear button is available in the text field
const clearButton = textField.valueForKey('clearButton');
if (!clearButton) return;
clearButton.tintColor = value instanceof Color ? value.ios : value;
}
}

View File

@@ -12,6 +12,7 @@ export abstract class SearchBarBase extends View implements SearchBarDefinition
public hint: string;
public textFieldBackgroundColor: Color;
public textFieldHintColor: Color;
public clearButtonColor: Color | string;
public abstract dismissSoftInput();
}
@@ -44,3 +45,11 @@ export const textFieldBackgroundColorProperty = new Property<SearchBarBase, Colo
valueConverter: (v) => new Color(v),
});
textFieldBackgroundColorProperty.register(SearchBarBase);
// --- Added property for clear button color ---
export const clearButtonColorProperty = new Property<SearchBarBase, Color>({
name: 'clearButtonColor',
equalityComparer: Color.equals,
valueConverter: (v) => new Color(v),
});
clearButtonColorProperty.register(SearchBarBase);