Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit f970455

Browse filesBrowse files
authored
feat(core): support for simultaneous pseudo states (#10656)
1 parent a883a79 commit f970455
Copy full SHA for f970455

File tree

Expand file treeCollapse file tree

16 files changed

+210
-103
lines changed
Filter options
Expand file treeCollapse file tree

16 files changed

+210
-103
lines changed

‎apps/automated/src/ui/button/button-tests.ts

Copy file name to clipboardExpand all lines: apps/automated/src/ui/button/button-tests.ts
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ export var test_StateHighlighted_also_fires_pressedState = function () {
274274

275275
helper.waitUntilLayoutReady(view);
276276

277-
view._goToVisualState('highlighted');
277+
view._addVisualState('highlighted');
278278

279279
var actualResult = buttonTestsNative.getNativeBackgroundColor(view);
280280
TKUnit.assert(actualResult.hex === expectedNormalizedColor, 'Actual: ' + actualResult.hex + '; Expected: ' + expectedNormalizedColor);
@@ -291,7 +291,7 @@ export var test_StateHighlighted_also_fires_activeState = function () {
291291

292292
helper.waitUntilLayoutReady(view);
293293

294-
view._goToVisualState('highlighted');
294+
view._addVisualState('highlighted');
295295

296296
var actualResult = buttonTestsNative.getNativeBackgroundColor(view);
297297
TKUnit.assert(actualResult.hex === expectedNormalizedColor, 'Actual: ' + actualResult.hex + '; Expected: ' + expectedNormalizedColor);

‎apps/automated/src/ui/styling/style-tests.ts

Copy file name to clipboardExpand all lines: apps/automated/src/ui/styling/style-tests.ts
+6-6Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -602,9 +602,9 @@ export function test_restore_original_values_when_state_is_changed() {
602602
page.css = 'button { color: blue; } ' + 'button:pressed { color: red; } ';
603603

604604
helper.assertViewColor(btn, '#0000FF');
605-
btn._goToVisualState('pressed');
605+
btn._addVisualState('pressed');
606606
helper.assertViewColor(btn, '#FF0000');
607-
btn._goToVisualState('normal');
607+
btn._removeVisualState('pressed');
608608
helper.assertViewColor(btn, '#0000FF');
609609
}
610610

@@ -655,9 +655,9 @@ export const test_composite_selector_type_class_state = function () {
655655

656656
// The button with no class should not react to state changes.
657657
TKUnit.assertNull(btnWithNoClass.style.color, 'Color should not have a value.');
658-
btnWithNoClass._goToVisualState('pressed');
658+
btnWithNoClass._addVisualState('pressed');
659659
TKUnit.assertNull(btnWithNoClass.style.color, 'Color should not have a value.');
660-
btnWithNoClass._goToVisualState('normal');
660+
btnWithNoClass._removeVisualState('pressed');
661661
TKUnit.assertNull(btnWithNoClass.style.color, 'Color should not have a value.');
662662

663663
TKUnit.assertNull(lblWithClass.style.color, 'Color should not have a value');
@@ -864,11 +864,11 @@ function testSelectorsPrioritiesTemplate(css: string) {
864864
function testButtonPressedStateIsRed(btn: Button) {
865865
TKUnit.assert(btn.style.color === undefined, 'Color should not have a value.');
866866

867-
btn._goToVisualState('pressed');
867+
btn._addVisualState('pressed');
868868

869869
helper.assertViewColor(btn, '#FF0000');
870870

871-
btn._goToVisualState('normal');
871+
btn._removeVisualState('pressed');
872872

873873
TKUnit.assert(btn.style.color === undefined, 'Color should not have a value after returned to normal state.');
874874
}

‎apps/automated/src/ui/styling/visual-state-tests.ts

Copy file name to clipboardExpand all lines: apps/automated/src/ui/styling/visual-state-tests.ts
+51Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,54 @@ export var test_goToVisualState_NoState_ShouldGoToNormal = function () {
9494

9595
helper.do_PageTest_WithButton(test);
9696
};
97+
98+
export var test_addVisualState = function () {
99+
var test = function (views: Array<view.View>) {
100+
(<page.Page>views[0]).css = 'button:hovered { color: red; background-color: orange } button:pressed { color: white }';
101+
102+
var btn = views[1];
103+
104+
assertInState(btn, btn.defaultVisualState, ['hovered', 'pressed', btn.defaultVisualState]);
105+
106+
btn._addVisualState('hovered');
107+
108+
assertInState(btn, 'hovered', ['hovered', 'pressed', btn.defaultVisualState]);
109+
110+
TKUnit.assert(types.isDefined(btn.style.color) && btn.style.color.name === 'red');
111+
TKUnit.assert(types.isDefined(btn.style.backgroundColor) && btn.style.backgroundColor.name === 'orange');
112+
113+
btn._addVisualState('pressed');
114+
115+
assertInState(btn, 'hovered', ['hovered', btn.defaultVisualState]);
116+
assertInState(btn, 'pressed', ['pressed', btn.defaultVisualState]);
117+
118+
TKUnit.assert(types.isDefined(btn.style.color) && btn.style.color.name === 'white');
119+
TKUnit.assert(types.isDefined(btn.style.backgroundColor) && btn.style.backgroundColor.name === 'orange');
120+
};
121+
122+
helper.do_PageTest_WithButton(test);
123+
};
124+
125+
export var test_removeVisualState = function () {
126+
var test = function (views: Array<view.View>) {
127+
(<page.Page>views[0]).css = 'button { background-color: yellow; color: green } button:pressed { background-color: red; color: white }';
128+
129+
var btn = views[1];
130+
131+
btn._addVisualState('pressed');
132+
133+
assertInState(btn, 'pressed', ['pressed', 'hovered', btn.defaultVisualState]);
134+
135+
TKUnit.assert(types.isDefined(btn.style.color) && btn.style.color.name === 'white');
136+
TKUnit.assert(types.isDefined(btn.style.backgroundColor) && btn.style.backgroundColor.name === 'red');
137+
138+
btn._removeVisualState('pressed');
139+
140+
assertInState(btn, btn.defaultVisualState, ['hovered', 'pressed', btn.defaultVisualState]);
141+
142+
TKUnit.assert(types.isDefined(btn.style.color) && btn.style.color.name === 'green');
143+
TKUnit.assert(types.isDefined(btn.style.backgroundColor) && btn.style.backgroundColor.name === 'yellow');
144+
};
145+
146+
helper.do_PageTest_WithButton(test);
147+
};

‎packages/core/ui/button/index.android.ts

Copy file name to clipboardExpand all lines: packages/core/ui/button/index.android.ts
+16-16Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,24 @@ function initializeClickListener(): void {
4444
ClickListener = ClickListenerImpl;
4545
}
4646

47+
function onButtonStateChange(args: TouchGestureEventData) {
48+
const button = args.object as Button;
49+
50+
switch (args.action) {
51+
case TouchAction.up:
52+
case TouchAction.cancel:
53+
button._removeVisualState('highlighted');
54+
break;
55+
case TouchAction.down:
56+
button._addVisualState('highlighted');
57+
break;
58+
}
59+
}
60+
4761
export class Button extends ButtonBase {
4862
nativeViewProtected: android.widget.Button;
4963

5064
private _stateListAnimator: any;
51-
private _highlightedHandler: (args: TouchGestureEventData) => void;
5265

5366
@profile
5467
public createNativeView() {
@@ -87,22 +100,9 @@ export class Button extends ButtonBase {
87100
@PseudoClassHandler('normal', 'highlighted', 'pressed', 'active')
88101
_updateButtonStateChangeHandler(subscribe: boolean) {
89102
if (subscribe) {
90-
this._highlightedHandler =
91-
this._highlightedHandler ||
92-
((args: TouchGestureEventData) => {
93-
switch (args.action) {
94-
case TouchAction.up:
95-
case TouchAction.cancel:
96-
this._goToVisualState(this.defaultVisualState);
97-
break;
98-
case TouchAction.down:
99-
this._goToVisualState('highlighted');
100-
break;
101-
}
102-
});
103-
this.on(GestureTypes[GestureTypes.touch], this._highlightedHandler);
103+
this.on(GestureTypes[GestureTypes.touch], onButtonStateChange);
104104
} else {
105-
this.off(GestureTypes[GestureTypes.touch], this._highlightedHandler);
105+
this.off(GestureTypes[GestureTypes.touch], onButtonStateChange);
106106
}
107107
}
108108

‎packages/core/ui/button/index.ios.ts

Copy file name to clipboardExpand all lines: packages/core/ui/button/index.ios.ts
+8-2Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import { Color } from '../../color';
99

1010
export * from './button-common';
1111

12+
const observableVisualStates = ['highlighted']; // States like :disabled are handled elsewhere
13+
1214
export class Button extends ButtonBase {
1315
public nativeViewProtected: UIButton;
1416

@@ -46,8 +48,12 @@ export class Button extends ButtonBase {
4648
_updateButtonStateChangeHandler(subscribe: boolean) {
4749
if (subscribe) {
4850
if (!this._stateChangedHandler) {
49-
this._stateChangedHandler = new ControlStateChangeListener(this.nativeViewProtected, (s: string) => {
50-
this._goToVisualState(s);
51+
this._stateChangedHandler = new ControlStateChangeListener(this.nativeViewProtected, observableVisualStates, (state: string, add: boolean) => {
52+
if (add) {
53+
this._addVisualState(state);
54+
} else {
55+
this._removeVisualState(state);
56+
}
5157
});
5258
}
5359
this._stateChangedHandler.start();

‎packages/core/ui/core/control-state-change/index.android.ts

Copy file name to clipboardExpand all lines: packages/core/ui/core/control-state-change/index.android.ts
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
/* tslint:disable:no-unused-variable */
22
/* tslint:disable:no-empty */
3-
import { ControlStateChangeListener as ControlStateChangeListenerDefinition } from '.';
3+
import { ControlStateChangeListenerCallback, ControlStateChangeListener as ControlStateChangeListenerDefinition } from '.';
44

55
export class ControlStateChangeListener implements ControlStateChangeListenerDefinition {
6-
constructor(control: any /* UIControl */, callback: (state: string) => void) {
6+
constructor(control: any /* UIControl */, states: string[], callback: ControlStateChangeListenerCallback) {
77
console.log('ControlStateChangeListener is intended for IOS usage only.');
88
}
99
public start() {}

‎packages/core/ui/core/control-state-change/index.d.ts

Copy file name to clipboardExpand all lines: packages/core/ui/core/control-state-change/index.d.ts
+4-2Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
/**
1+
export type ControlStateChangeListenerCallback = (state: string, add: boolean) => void;
2+
3+
/**
24
* An utility class used for supporting styling infrastructure.
35
* WARNING: This class is intended for IOS only.
46
*/
@@ -8,7 +10,7 @@ export class ControlStateChangeListener {
810
* @param control An instance of the UIControl which state will be watched.
911
* @param callback A callback called when a visual state of the UIControl is changed.
1012
*/
11-
constructor(control: any /* UIControl */, callback: (state: string) => void);
13+
constructor(control: any /* UIControl */, states: string[], callback: ControlStateChangeListenerCallback);
1214

1315
start();
1416
stop();
+28-42Lines changed: 28 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,55 @@
1-
/* tslint:disable:no-unused-variable */
2-
import { ControlStateChangeListener as ControlStateChangeListenerDefinition } from '.';
1+
import { ControlStateChangeListenerCallback, ControlStateChangeListener as ControlStateChangeListenerDefinition } from '.';
32

43
@NativeClass
54
class ObserverClass extends NSObject {
6-
// NOTE: Refactor this - use Typescript property instead of strings....
7-
observeValueForKeyPathOfObjectChangeContext(path: string) {
8-
if (path === 'selected') {
9-
this['_owner']._onSelectedChanged();
10-
} else if (path === 'enabled') {
11-
this['_owner']._onEnabledChanged();
12-
} else if (path === 'highlighted') {
13-
this['_owner']._onHighlightedChanged();
5+
public callback: WeakRef<ControlStateChangeListenerCallback>;
6+
7+
public static initWithCallback(callback: WeakRef<ControlStateChangeListenerCallback>): ObserverClass {
8+
const observer = <ObserverClass>ObserverClass.alloc().init();
9+
observer.callback = callback;
10+
11+
return observer;
12+
}
13+
14+
public observeValueForKeyPathOfObjectChangeContext(path: string, object: UIControl) {
15+
const callback = this.callback?.deref();
16+
17+
if (callback) {
18+
callback(path, object[path]);
1419
}
1520
}
1621
}
1722

1823
export class ControlStateChangeListener implements ControlStateChangeListenerDefinition {
1924
private _observer: NSObject;
2025
private _control: UIControl;
21-
private _observing = false;
26+
private _observing: boolean = false;
2227

23-
private _callback: (state: string) => void;
28+
private readonly _states: string[];
2429

25-
constructor(control: UIControl, callback: (state: string) => void) {
26-
this._observer = ObserverClass.alloc().init();
27-
this._observer['_owner'] = this;
30+
constructor(control: UIControl, states: string[], callback: ControlStateChangeListenerCallback) {
2831
this._control = control;
29-
this._callback = callback;
32+
this._states = states;
33+
this._observer = ObserverClass.initWithCallback(new WeakRef(callback));
3034
}
3135

3236
public start() {
3337
if (!this._observing) {
34-
this._control.addObserverForKeyPathOptionsContext(this._observer, 'highlighted', NSKeyValueObservingOptions.New, null);
3538
this._observing = true;
36-
this._updateState();
39+
40+
for (const state of this._states) {
41+
this._control.addObserverForKeyPathOptionsContext(this._observer, state, NSKeyValueObservingOptions.New, null);
42+
}
3743
}
3844
}
3945

4046
public stop() {
4147
if (this._observing) {
42-
this._observing = false;
43-
this._control.removeObserverForKeyPath(this._observer, 'highlighted');
44-
}
45-
}
46-
47-
//@ts-ignore
48-
private _onEnabledChanged() {
49-
this._updateState();
50-
}
51-
52-
//@ts-ignore
53-
private _onSelectedChanged() {
54-
this._updateState();
55-
}
56-
57-
//@ts-ignore
58-
private _onHighlightedChanged() {
59-
this._updateState();
60-
}
48+
for (const state of this._states) {
49+
this._control.removeObserverForKeyPath(this._observer, state);
50+
}
6151

62-
private _updateState() {
63-
let state = 'normal';
64-
if (this._control.highlighted) {
65-
state = 'highlighted';
52+
this._observing = false;
6653
}
67-
this._callback(state);
6854
}
6955
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.