mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-14 18:12:09 +08:00
fix(core): pseudo-class handlers failing to unsubscribe listeners (#10680)
This commit is contained in:

committed by
GitHub

parent
4902e27818
commit
e6beb1d816
@ -103,6 +103,7 @@ export class Button extends ButtonBase {
|
|||||||
this.on(GestureTypes[GestureTypes.touch], onButtonStateChange);
|
this.on(GestureTypes[GestureTypes.touch], onButtonStateChange);
|
||||||
} else {
|
} else {
|
||||||
this.off(GestureTypes[GestureTypes.touch], onButtonStateChange);
|
this.off(GestureTypes[GestureTypes.touch], onButtonStateChange);
|
||||||
|
this._removeVisualState('highlighted');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +29,12 @@ export class Button extends ButtonBase {
|
|||||||
|
|
||||||
public disposeNativeView(): void {
|
public disposeNativeView(): void {
|
||||||
this._tapHandler = null;
|
this._tapHandler = null;
|
||||||
|
|
||||||
|
if (this._stateChangedHandler) {
|
||||||
|
this._stateChangedHandler.stop();
|
||||||
|
this._stateChangedHandler = null;
|
||||||
|
}
|
||||||
|
|
||||||
super.disposeNativeView();
|
super.disposeNativeView();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,28 +43,32 @@ export class Button extends ButtonBase {
|
|||||||
return this.nativeViewProtected;
|
return this.nativeViewProtected;
|
||||||
}
|
}
|
||||||
|
|
||||||
public onUnloaded() {
|
|
||||||
super.onUnloaded();
|
|
||||||
if (this._stateChangedHandler) {
|
|
||||||
this._stateChangedHandler.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@PseudoClassHandler('normal', 'highlighted', 'pressed', 'active')
|
@PseudoClassHandler('normal', 'highlighted', 'pressed', 'active')
|
||||||
_updateButtonStateChangeHandler(subscribe: boolean) {
|
_updateButtonStateChangeHandler(subscribe: boolean) {
|
||||||
if (subscribe) {
|
if (subscribe) {
|
||||||
if (!this._stateChangedHandler) {
|
if (!this._stateChangedHandler) {
|
||||||
|
const viewRef = new WeakRef<Button>(this);
|
||||||
|
|
||||||
this._stateChangedHandler = new ControlStateChangeListener(this.nativeViewProtected, observableVisualStates, (state: string, add: boolean) => {
|
this._stateChangedHandler = new ControlStateChangeListener(this.nativeViewProtected, observableVisualStates, (state: string, add: boolean) => {
|
||||||
|
const view = viewRef?.deref?.();
|
||||||
|
|
||||||
|
if (view) {
|
||||||
if (add) {
|
if (add) {
|
||||||
this._addVisualState(state);
|
view._addVisualState(state);
|
||||||
} else {
|
} else {
|
||||||
this._removeVisualState(state);
|
view._removeVisualState(state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this._stateChangedHandler.start();
|
this._stateChangedHandler.start();
|
||||||
} else {
|
} else {
|
||||||
this._stateChangedHandler.stop();
|
this._stateChangedHandler.stop();
|
||||||
|
|
||||||
|
// Remove any possible pseudo-class leftovers
|
||||||
|
for (const state of observableVisualStates) {
|
||||||
|
this._removeVisualState(state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
|
|
||||||
@NativeClass
|
@NativeClass
|
||||||
class ObserverClass extends NSObject {
|
class ObserverClass extends NSObject {
|
||||||
public callback: WeakRef<ControlStateChangeListenerCallback>;
|
public callback: ControlStateChangeListenerCallback;
|
||||||
|
|
||||||
public static initWithCallback(callback: WeakRef<ControlStateChangeListenerCallback>): ObserverClass {
|
public static initWithCallback(callback: ControlStateChangeListenerCallback): ObserverClass {
|
||||||
const observer = <ObserverClass>ObserverClass.alloc().init();
|
const observer = <ObserverClass>ObserverClass.alloc().init();
|
||||||
observer.callback = callback;
|
observer.callback = callback;
|
||||||
|
|
||||||
@ -12,8 +12,7 @@ class ObserverClass extends NSObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public observeValueForKeyPathOfObjectChangeContext(path: string, object: UIControl) {
|
public observeValueForKeyPathOfObjectChangeContext(path: string, object: UIControl) {
|
||||||
const callback = this.callback?.deref();
|
const callback = this.callback;
|
||||||
|
|
||||||
if (callback) {
|
if (callback) {
|
||||||
callback(path, object[path]);
|
callback(path, object[path]);
|
||||||
}
|
}
|
||||||
@ -30,7 +29,7 @@ export class ControlStateChangeListener implements ControlStateChangeListenerDef
|
|||||||
constructor(control: UIControl, states: string[], callback: ControlStateChangeListenerCallback) {
|
constructor(control: UIControl, states: string[], callback: ControlStateChangeListenerCallback) {
|
||||||
this._control = control;
|
this._control = control;
|
||||||
this._states = states;
|
this._states = states;
|
||||||
this._observer = ObserverClass.initWithCallback(new WeakRef(callback));
|
this._observer = ObserverClass.initWithCallback(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
public start() {
|
public start() {
|
||||||
|
@ -53,19 +53,21 @@ export function viewMatchesModuleContext(view: ViewDefinition, context: ModuleCo
|
|||||||
|
|
||||||
export function PseudoClassHandler(...pseudoClasses: string[]): MethodDecorator {
|
export function PseudoClassHandler(...pseudoClasses: string[]): MethodDecorator {
|
||||||
const stateEventNames = pseudoClasses.map((s) => ':' + s);
|
const stateEventNames = pseudoClasses.map((s) => ':' + s);
|
||||||
const listeners = Symbol('listeners');
|
|
||||||
|
|
||||||
return <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => {
|
return <T>(target: Object, propertyKey: string, descriptor: TypedPropertyDescriptor<T>) => {
|
||||||
function update(change: number) {
|
// This will help keep track of pseudo-class subscription changes
|
||||||
const prev = this[listeners] || 0;
|
const subscribeKey = Symbol(propertyKey + '_flag');
|
||||||
const next = prev + change;
|
|
||||||
if (prev <= 0 && next > 0) {
|
function onSubscribe(subscribe: boolean) {
|
||||||
this[propertyKey](true);
|
if (subscribe != !!this[subscribeKey]) {
|
||||||
} else if (prev > 0 && next <= 0) {
|
this[subscribeKey] = subscribe;
|
||||||
this[propertyKey](false);
|
this[propertyKey](subscribe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stateEventNames.forEach((s) => (target[s] = update));
|
|
||||||
|
for (const eventName of stateEventNames) {
|
||||||
|
target[eventName] = onSubscribe;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,19 +34,25 @@ export abstract class EditableTextBase extends TextBase implements EditableTextB
|
|||||||
public autocorrect: boolean;
|
public autocorrect: boolean;
|
||||||
public hint: string;
|
public hint: string;
|
||||||
public maxLength: number;
|
public maxLength: number;
|
||||||
|
public placeholderColor: Color;
|
||||||
public valueFormatter: (value: string) => string;
|
public valueFormatter: (value: string) => string;
|
||||||
|
|
||||||
public abstract dismissSoftInput();
|
public abstract dismissSoftInput();
|
||||||
public abstract _setInputType(inputType: number): void;
|
public abstract _setInputType(inputType: number): void;
|
||||||
public abstract setSelection(start: number, stop?: number);
|
public abstract setSelection(start: number, stop?: number);
|
||||||
placeholderColor: Color;
|
|
||||||
|
|
||||||
@PseudoClassHandler('focus', 'blur')
|
@PseudoClassHandler('focus', 'blur')
|
||||||
_updateTextBaseFocusStateHandler(subscribe) {
|
_updateTextBaseFocusStateHandler(subscribe: boolean) {
|
||||||
const method = subscribe ? 'on' : 'off';
|
if (subscribe) {
|
||||||
|
this.on('focus', focusChangeHandler);
|
||||||
|
this.on('blur', focusChangeHandler);
|
||||||
|
} else {
|
||||||
|
this.off('focus', focusChangeHandler);
|
||||||
|
this.off('blur', focusChangeHandler);
|
||||||
|
|
||||||
this[method]('focus', focusChangeHandler);
|
this._removeVisualState('focus');
|
||||||
this[method]('blur', focusChangeHandler);
|
this._removeVisualState('blur');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -792,7 +792,7 @@ export class CssState {
|
|||||||
const eventName = ':' + pseudoClass;
|
const eventName = ':' + pseudoClass;
|
||||||
view.addEventListener(':' + pseudoClass, this._onDynamicStateChangeHandler);
|
view.addEventListener(':' + pseudoClass, this._onDynamicStateChangeHandler);
|
||||||
if (view[eventName]) {
|
if (view[eventName]) {
|
||||||
view[eventName](+1);
|
view[eventName](true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -812,7 +812,7 @@ export class CssState {
|
|||||||
const eventName = ':' + pseudoClass;
|
const eventName = ':' + pseudoClass;
|
||||||
view.removeEventListener(eventName, this._onDynamicStateChangeHandler);
|
view.removeEventListener(eventName, this._onDynamicStateChangeHandler);
|
||||||
if (view[eventName]) {
|
if (view[eventName]) {
|
||||||
view[eventName](-1);
|
view[eventName](false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user