mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-17 04:41:36 +08:00
Inital by-type split
Split type.class from CssTypeSelector to CssCompositeSelector, probably support type#id.class selectors Apply review comments, refactor css-selectors internally Applied refactoring, all tests pass, button does not notify changes Add tests for the css selectors parser. Added tests for css-selectors Added basic implementation of mayMatch and changeMap for css match state Implemented TKUnit.assertDeepEqual to check key and key/values in Map and Set Watch for property and pseudoClass changes Add one child group test Add typings for animations Added mechanism to enable/disable listeners for pseudo classes Count listeners instead of checking handlers, reverse subscription and unsubscription
This commit is contained in:
@ -1,6 +1,5 @@
|
||||
/* tslint:disable:no-unused-variable */
|
||||
import definition = require("ui/core/control-state-change");
|
||||
import * as visualStateConstants from "ui/styling/visual-state-constants";
|
||||
|
||||
var ObserverClass = NSObject.extend(
|
||||
{
|
||||
@ -36,6 +35,7 @@ export class ControlStateChangeListener implements definition.ControlStateChange
|
||||
if (!this._observing) {
|
||||
this._control.addObserverForKeyPathOptionsContext(this._observer, "highlighted", NSKeyValueObservingOptions.NSKeyValueObservingOptionNew, null);
|
||||
this._observing = true;
|
||||
this._updateState();
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,7 +59,7 @@ export class ControlStateChangeListener implements definition.ControlStateChange
|
||||
}
|
||||
|
||||
private _updateState() {
|
||||
var state = visualStateConstants.Normal;
|
||||
var state = "normal";
|
||||
if (this._control.highlighted) {
|
||||
state = "highlighted";
|
||||
}
|
||||
|
@ -14,17 +14,9 @@ import {PropertyMetadata, ProxyObject} from "ui/core/proxy";
|
||||
import {PropertyMetadataSettings, PropertyChangeData, Property, ValueSource, PropertyMetadata as doPropertyMetadata} from "ui/core/dependency-observable";
|
||||
import {registerSpecialProperty} from "ui/builder/special-properties";
|
||||
import {CommonLayoutParams, nativeLayoutParamsProperty} from "ui/styling/style";
|
||||
import * as visualStateConstants from "ui/styling/visual-state-constants";
|
||||
import * as visualStateModule from "../styling/visual-state";
|
||||
import * as animModule from "ui/animation";
|
||||
import { Source } from "utils/debug";
|
||||
|
||||
var visualState: typeof visualStateModule;
|
||||
function ensureVisualState() {
|
||||
if (!visualState) {
|
||||
visualState = require("../styling/visual-state");
|
||||
}
|
||||
}
|
||||
import {CssState} from "ui/styling/style-scope";
|
||||
import {Source} from "utils/debug";
|
||||
|
||||
registerSpecialProperty("class", (instance: definition.View, propertyValue: string) => {
|
||||
instance.className = propertyValue;
|
||||
@ -104,16 +96,31 @@ export function getAncestor(view: View, criterion: string | Function): definitio
|
||||
return null;
|
||||
}
|
||||
|
||||
export function PseudoClassHandler(... pseudoClasses: string[]): MethodDecorator {
|
||||
let stateEventNames = pseudoClasses.map(s => ":" + s);
|
||||
let listeners = Symbol("listeners");
|
||||
return <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => {
|
||||
function update(change: number) {
|
||||
let prev = this[listeners] || 0;
|
||||
let next = prev + change;
|
||||
if (prev <= 0 && next > 0) {
|
||||
this[propertyKey](true);
|
||||
} else if (prev > 0 && next <= 0) {
|
||||
this[propertyKey](false);
|
||||
}
|
||||
}
|
||||
stateEventNames.forEach(s => target[s] = update);
|
||||
}
|
||||
}
|
||||
|
||||
var viewIdCounter = 0;
|
||||
|
||||
function onCssClassPropertyChanged(data: PropertyChangeData) {
|
||||
var view = <View>data.object;
|
||||
|
||||
var classes = view.cssClasses;
|
||||
classes.clear();
|
||||
if (types.isString(data.newValue)) {
|
||||
view._cssClasses = (<string>data.newValue).split(" ");
|
||||
}
|
||||
else {
|
||||
view._cssClasses.length = 0;
|
||||
data.newValue.split(" ").forEach(c => classes.add(c));
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,18 +160,22 @@ export class View extends ProxyObject implements definition.View {
|
||||
private _parent: definition.View;
|
||||
private _style: style.Style;
|
||||
private _visualState: string;
|
||||
private _requestedVisualState: string;
|
||||
private _isLoaded: boolean;
|
||||
private _isLayoutValid: boolean = false;
|
||||
private _cssType: string;
|
||||
|
||||
private _updatingInheritedProperties: boolean;
|
||||
private _registeredAnimations: Array<keyframeAnimationModule.KeyframeAnimation>;
|
||||
|
||||
public _domId: number;
|
||||
public _isAddedToNativeVisualTree = false;
|
||||
public _cssClasses: Array<string> = [];
|
||||
public _gestureObservers = {};
|
||||
|
||||
public cssClasses: Set<string> = new Set();
|
||||
public cssPseudoClasses: Set<string> = new Set();
|
||||
|
||||
public _cssState: CssState;
|
||||
|
||||
public getGestureObservers(type: gestures.GestureTypes): Array<gestures.GesturesObserver> {
|
||||
return this._gestureObservers[type];
|
||||
}
|
||||
@ -174,7 +185,7 @@ export class View extends ProxyObject implements definition.View {
|
||||
|
||||
this._style = new style.Style(this);
|
||||
this._domId = viewIdCounter++;
|
||||
this._visualState = visualStateConstants.Normal;
|
||||
this._goToVisualState("normal");
|
||||
}
|
||||
|
||||
observe(type: gestures.GestureTypes, callback: (args: gestures.GestureEventData) => void, thisArg?: any): void {
|
||||
@ -510,12 +521,11 @@ export class View extends ProxyObject implements definition.View {
|
||||
throw new Error("isLayoutValid is read-only property.");
|
||||
}
|
||||
|
||||
get visualState(): string {
|
||||
return this._visualState;
|
||||
}
|
||||
|
||||
get cssType(): string {
|
||||
return this.typeName.toLowerCase();
|
||||
if (!this._cssType) {
|
||||
this._cssType = this.typeName.toLowerCase();
|
||||
}
|
||||
return this._cssType;
|
||||
}
|
||||
|
||||
get parent(): definition.View {
|
||||
@ -545,6 +555,9 @@ export class View extends ProxyObject implements definition.View {
|
||||
}
|
||||
|
||||
public onUnloaded() {
|
||||
this._onCssStateChange(this._cssState, null);
|
||||
this._cssState = null;
|
||||
|
||||
this._unloadEachChildView();
|
||||
|
||||
this._isLoaded = false;
|
||||
@ -651,6 +664,20 @@ export class View extends ProxyObject implements definition.View {
|
||||
//
|
||||
}
|
||||
|
||||
public addPseudoClass(name: string): void {
|
||||
if (!this.cssPseudoClasses.has(name)) {
|
||||
this.cssPseudoClasses.add(name);
|
||||
this.notifyPseudoClassChanged(name);
|
||||
}
|
||||
}
|
||||
|
||||
public deletePseudoClass(name: string): void {
|
||||
if (this.cssPseudoClasses.has(name)) {
|
||||
this.cssPseudoClasses.delete(name);
|
||||
this.notifyPseudoClassChanged(name);
|
||||
}
|
||||
}
|
||||
|
||||
public static resolveSizeAndState(size: number, specSize: number, specMode: number, childMeasuredState: number): number {
|
||||
var result = size;
|
||||
switch (specMode) {
|
||||
@ -1071,15 +1098,13 @@ export class View extends ProxyObject implements definition.View {
|
||||
if (trace.enabled) {
|
||||
trace.write(this + " going to state: " + state, trace.categories.Style);
|
||||
}
|
||||
if (state === this._visualState || this._requestedVisualState === state) {
|
||||
if (state === this._visualState) {
|
||||
return;
|
||||
}
|
||||
// we use lazy require to prevent cyclic dependencies issues
|
||||
ensureVisualState();
|
||||
this._visualState = visualState.goToState(this, state);
|
||||
|
||||
// TODO: What state should we set here - the requested or the actual one?
|
||||
this._requestedVisualState = state;
|
||||
this.deletePseudoClass(this._visualState);
|
||||
this._visualState = state;
|
||||
this.addPseudoClass(state);
|
||||
}
|
||||
|
||||
public _applyXmlAttribute(attribute, value): boolean {
|
||||
@ -1206,4 +1231,84 @@ export class View extends ProxyObject implements definition.View {
|
||||
// Check for a valid _nativeView instance
|
||||
return !!this._nativeView;
|
||||
}
|
||||
|
||||
private notifyPseudoClassChanged(pseudoClass: string): void {
|
||||
this.notify({ eventName: ":" + pseudoClass, object: this });
|
||||
}
|
||||
|
||||
// TODO: Make sure the state is set to null and this is called on unloaded to clean up change listeners...
|
||||
_onCssStateChange(previous: CssState, next: CssState): void {
|
||||
|
||||
if (!this._invalidateCssHandler) {
|
||||
this._invalidateCssHandler = () => {
|
||||
if (this._invalidateCssHandlerSuspended) {
|
||||
return;
|
||||
}
|
||||
this.applyCssState();
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
this._invalidateCssHandlerSuspended = true;
|
||||
|
||||
if (next) {
|
||||
next.changeMap.forEach((changes, view) => {
|
||||
if (changes.attributes) {
|
||||
changes.attributes.forEach(attribute => {
|
||||
view.addEventListener(attribute + "Change", this._invalidateCssHandler)
|
||||
});
|
||||
}
|
||||
if (changes.pseudoClasses) {
|
||||
changes.pseudoClasses.forEach(pseudoClass => {
|
||||
let eventName = ":" + pseudoClass;
|
||||
view.addEventListener(":" + pseudoClass, this._invalidateCssHandler);
|
||||
if (view[eventName]) {
|
||||
view[eventName](+1);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (previous) {
|
||||
previous.changeMap.forEach((changes, view) => {
|
||||
if (changes.attributes) {
|
||||
changes.attributes.forEach(attribute => {
|
||||
view.removeEventListener("onPropertyChanged:" + attribute, this._invalidateCssHandler)
|
||||
});
|
||||
}
|
||||
if (changes.pseudoClasses) {
|
||||
changes.pseudoClasses.forEach(pseudoClass => {
|
||||
let eventName = ":" + pseudoClass;
|
||||
view.removeEventListener(eventName, this._invalidateCssHandler)
|
||||
if (view[eventName]) {
|
||||
view[eventName](-1);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} finally {
|
||||
this._invalidateCssHandlerSuspended = false;
|
||||
}
|
||||
|
||||
this.applyCssState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify that some attributes or pseudo classes that may affect the current CssState had changed.
|
||||
*/
|
||||
private _invalidateCssHandler;
|
||||
private _invalidateCssHandlerSuspended: boolean;
|
||||
|
||||
private applyCssState(): void {
|
||||
if (!this._cssState) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.style._beginUpdate();
|
||||
this._cssState.apply();
|
||||
this.style._endUpdate();
|
||||
}
|
||||
}
|
29
tns-core-modules/ui/core/view.d.ts
vendored
29
tns-core-modules/ui/core/view.d.ts
vendored
@ -33,6 +33,8 @@ declare module "ui/core/view" {
|
||||
|
||||
export function isEventOrGesture(name: string, view: View): boolean;
|
||||
|
||||
export function PseudoClassHandler(... pseudoClasses: string[]): MethodDecorator;
|
||||
|
||||
/**
|
||||
* The Point interface describes a two dimensional location.
|
||||
* It has two properties x and y, representing the x and y coordinate of the location.
|
||||
@ -289,9 +291,14 @@ declare module "ui/core/view" {
|
||||
*/
|
||||
isLayoutValid: boolean;
|
||||
|
||||
/**
|
||||
* Gets the CSS fully qualified type name.
|
||||
* Using this as element type should allow for PascalCase and kebap-case selectors, when fully qualified, to match the element.
|
||||
*/
|
||||
cssType: string;
|
||||
|
||||
visualState: string;
|
||||
cssClasses: Set<string>;
|
||||
cssPseudoClasses: Set<string>;
|
||||
|
||||
/**
|
||||
* Gets owner page. This is a read-only property.
|
||||
@ -477,6 +484,20 @@ declare module "ui/core/view" {
|
||||
* Returns the actual size of the view in device-independent pixels.
|
||||
*/
|
||||
public getActualSize(): Size;
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @unstable
|
||||
* A widget can call this method to add a matching css pseudo class.
|
||||
*/
|
||||
public addPseudoClass(name: string): void;
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @unstable
|
||||
* A widget can call this method to discard mathing css pseudo class.
|
||||
*/
|
||||
public deletePseudoClass(name: string): void;
|
||||
|
||||
// Lifecycle events
|
||||
onLoaded(): void;
|
||||
@ -505,7 +526,9 @@ declare module "ui/core/view" {
|
||||
_gestureObservers: any;
|
||||
_isInheritedChange(): boolean;
|
||||
_domId: number;
|
||||
_cssClasses: Array<string>;
|
||||
|
||||
_cssState: any /* "ui/styling/style-scope" */;
|
||||
_onCssStateChange(previous: any /* "ui/styling/style-scope" */, any /* "ui/styling/style-scope" */);
|
||||
|
||||
_registerAnimation(animation: keyframeAnimationModule.KeyframeAnimation);
|
||||
_unregisterAnimation(animation: keyframeAnimationModule.KeyframeAnimation);
|
||||
@ -560,7 +583,7 @@ declare module "ui/core/view" {
|
||||
*/
|
||||
export class CustomLayoutView extends View {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Defines an interface for a View factory function.
|
||||
* Commonly used to specify the visualization of data objects.
|
||||
|
Reference in New Issue
Block a user