mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-15 11:01:21 +08:00
feat: Scoped Packages (#7911)
* chore: move tns-core-modules to nativescript-core * chore: preparing compat generate script * chore: add missing definitions * chore: no need for http-request to be private * chore: packages chore * test: generate tests for tns-core-modules * chore: add anroid module for consistency * chore: add .npmignore * chore: added privateModulesWhitelist * chore(webpack): added bundle-entry-points * chore: scripts * chore: tests changed to use @ns/core * test: add scoped-packages test project * test: fix types * test: update test project * chore: build scripts * chore: update build script * chore: npm scripts cleanup * chore: make the compat pgk work with old wp config * test: generate diff friendly tests * chore: create barrel exports * chore: move files after rebase * chore: typedoc config * chore: compat mode * chore: review of barrels * chore: remove tns-core-modules import after rebase * chore: dev workflow setup * chore: update developer-workflow * docs: experiment with API extractor * chore: api-extractor and barrel exports * chore: api-extractor configs * chore: generate d.ts rollup with api-extractor * refactor: move methods inside Frame * chore: fic tests to use Frame static methods * refactor: create Builder class * refactor: use Builder class in tests * refactor: include Style in ui barrel * chore: separate compat build script * chore: fix tslint errors * chore: update NATIVESCRIPT_CORE_ARGS * chore: fix compat pack * chore: fix ui-test-app build with linked modules * chore: Application, ApplicationSettings, Connectivity and Http * chore: export Trace, Profiling and Utils * refactor: Static create methods for ImageSource * chore: fix deprecated usages of ImageSource * chore: move Span and FormattedString to ui * chore: add events-args and ImageSource to index files * chore: check for CLI >= 6.2 when building for IOS * chore: update travis build * chore: copy Pod file to compat package * chore: update error msg ui-tests-app * refactor: Apply suggestions from code review Co-Authored-By: Martin Yankov <m.i.yankov@gmail.com> * chore: typings and refs * chore: add missing d.ts files for public API * chore: adress code review FB * chore: update api-report * chore: dev-workflow for other apps * chore: api update * chore: update api-report
This commit is contained in:

committed by
GitHub

parent
6c7139477e
commit
cc97a16800
83
nativescript-core/ui/styling/css-selector/css-selector.d.ts
vendored
Normal file
83
nativescript-core/ui/styling/css-selector/css-selector.d.ts
vendored
Normal file
@ -0,0 +1,83 @@
|
||||
/**
|
||||
* @module "ui/styling/css-selector"
|
||||
*/ /** */
|
||||
|
||||
import { Node as ParserNode } from "../../../css";
|
||||
|
||||
/**
|
||||
* An interface describing the shape of a type on which the selectors may apply.
|
||||
* Note, the ui/core/view.View implements Node.
|
||||
*/
|
||||
export interface Node {
|
||||
parent?: Node;
|
||||
|
||||
id?: string;
|
||||
cssType?: string;
|
||||
cssClasses?: Set<string>;
|
||||
cssPseudoClasses?: Set<string>;
|
||||
getChildIndex?(node: Node): number
|
||||
getChildAt?(index: number): Node
|
||||
}
|
||||
|
||||
export interface Declaration {
|
||||
property: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface SelectorCore {
|
||||
/**
|
||||
* Dynamic selectors depend on attributes and pseudo classes.
|
||||
*/
|
||||
dynamic: boolean;
|
||||
match(node: Node): boolean;
|
||||
ruleset: RuleSet;
|
||||
}
|
||||
|
||||
export class RuleSet {
|
||||
/**
|
||||
* Gets the selectors in this ruleset's selector group.
|
||||
*/
|
||||
selectors: SelectorCore[];
|
||||
|
||||
/**
|
||||
* Gets the key-value list of declarations for the ruleset.
|
||||
*/
|
||||
declarations: Declaration[];
|
||||
|
||||
/**
|
||||
* Optional Tag for rules
|
||||
**/
|
||||
tag: string | Number;
|
||||
}
|
||||
|
||||
export class SelectorsMap {
|
||||
constructor(rules: RuleSet[]);
|
||||
|
||||
/**
|
||||
* Get a list of selectors that are likely to match the node.
|
||||
*/
|
||||
query<T extends Node>(node: T): SelectorsMatch<T>;
|
||||
}
|
||||
|
||||
export type ChangeMap<T extends Node> = Map<T, Changes>;
|
||||
|
||||
export interface Changes {
|
||||
attributes?: Set<string>;
|
||||
pseudoClasses?: Set<string>;
|
||||
}
|
||||
|
||||
export class SelectorsMatch<T extends Node> {
|
||||
/**
|
||||
* Gets the static selectors that match the queried node and the dynamic selectors that may potentially match the queried node.
|
||||
*/
|
||||
selectors: SelectorCore[];
|
||||
|
||||
/**
|
||||
* Gets a map of nodes to attributes and pseudo classes, that may affect the state of the dynamic
|
||||
*/
|
||||
changeMap: ChangeMap<T>;
|
||||
}
|
||||
|
||||
export function fromAstNodes(astRules: ParserNode[]): RuleSet[];
|
||||
|
||||
export function createSelector(sel: string): SelectorCore;
|
587
nativescript-core/ui/styling/css-selector/css-selector.ts
Normal file
587
nativescript-core/ui/styling/css-selector/css-selector.ts
Normal file
@ -0,0 +1,587 @@
|
||||
import { Node, Declaration, Changes, ChangeMap } from ".";
|
||||
import { isNullOrUndefined } from "../../../utils/types";
|
||||
import { escapeRegexSymbols } from "../../../utils/utils-common";
|
||||
|
||||
import * as cssParser from "../../../css";
|
||||
import * as parser from "../../../css/parser";
|
||||
|
||||
const enum Specificity {
|
||||
Inline = 0x01000000,
|
||||
Id = 0x00010000,
|
||||
Attribute = 0x00000100,
|
||||
Class = 0x00000100,
|
||||
PseudoClass = 0x00000100,
|
||||
Type = 0x00000001,
|
||||
Universal = 0x00000000,
|
||||
Invalid = 0x00000000
|
||||
}
|
||||
|
||||
const enum Rarity {
|
||||
Invalid = 4,
|
||||
Id = 3,
|
||||
Class = 2,
|
||||
Type = 1,
|
||||
PseudoClass = 0,
|
||||
Attribute = 0,
|
||||
Universal = 0,
|
||||
Inline = 0
|
||||
}
|
||||
|
||||
interface LookupSorter {
|
||||
sortById(id: string, sel: SelectorCore);
|
||||
sortByClass(cssClass: string, sel: SelectorCore);
|
||||
sortByType(cssType: string, sel: SelectorCore);
|
||||
sortAsUniversal(sel: SelectorCore);
|
||||
}
|
||||
|
||||
namespace Match {
|
||||
/**
|
||||
* Depends on attributes or pseudoclasses state;
|
||||
*/
|
||||
export const Dynamic = true;
|
||||
/**
|
||||
* Depends only on the tree structure.
|
||||
*/
|
||||
export const Static = false;
|
||||
}
|
||||
|
||||
function getNodeDirectSibling(node): null | Node {
|
||||
if (!node.parent || !node.parent.getChildIndex || !node.parent.getChildAt) {
|
||||
return null;
|
||||
}
|
||||
const nodeIndex = node.parent.getChildIndex(node);
|
||||
if (nodeIndex === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return node.parent.getChildAt(nodeIndex - 1);
|
||||
}
|
||||
|
||||
function SelectorProperties(specificity: Specificity, rarity: Rarity, dynamic: boolean = false): ClassDecorator {
|
||||
return cls => {
|
||||
cls.prototype.specificity = specificity;
|
||||
cls.prototype.rarity = rarity;
|
||||
cls.prototype.combinator = undefined;
|
||||
cls.prototype.dynamic = dynamic;
|
||||
|
||||
return cls;
|
||||
};
|
||||
}
|
||||
|
||||
declare type Combinator = "+" | ">" | "~" | " ";
|
||||
@SelectorProperties(Specificity.Universal, Rarity.Universal, Match.Static)
|
||||
export abstract class SelectorCore {
|
||||
public specificity: number;
|
||||
public rarity: Rarity;
|
||||
public combinator: Combinator;
|
||||
public ruleset: RuleSet;
|
||||
public dynamic: boolean;
|
||||
public abstract match(node: Node): boolean;
|
||||
/**
|
||||
* If the selector is static returns if it matches the node.
|
||||
* If the selector is dynamic returns if it may match the node, and accumulates any changes that may affect its state.
|
||||
*/
|
||||
public abstract accumulateChanges(node: Node, map: ChangeAccumulator): boolean;
|
||||
public lookupSort(sorter: LookupSorter, base?: SelectorCore): void { sorter.sortAsUniversal(base || this); }
|
||||
}
|
||||
|
||||
export abstract class SimpleSelector extends SelectorCore {
|
||||
public accumulateChanges(node: Node, map?: ChangeAccumulator): boolean {
|
||||
if (!this.dynamic) {
|
||||
return this.match(node);
|
||||
} else if (this.mayMatch(node)) {
|
||||
this.trackChanges(node, map);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
public mayMatch(node: Node): boolean { return this.match(node); }
|
||||
public trackChanges(node: Node, map: ChangeAccumulator): void {
|
||||
// No-op, silence the tslint 'block is empty'.
|
||||
// Some derived classes (dynamic) will actually fill the map with stuff here, some (static) won't do anything.
|
||||
}
|
||||
}
|
||||
|
||||
function wrap(text: string): string {
|
||||
return text ? ` ${text} ` : "";
|
||||
}
|
||||
|
||||
@SelectorProperties(Specificity.Invalid, Rarity.Invalid, Match.Static)
|
||||
export class InvalidSelector extends SimpleSelector {
|
||||
constructor(public e: Error) { super(); }
|
||||
public toString(): string { return `<error: ${this.e}>`; }
|
||||
public match(node: Node): boolean { return false; }
|
||||
public lookupSort(sorter: LookupSorter, base?: SelectorCore): void {
|
||||
// No-op, silence the tslint 'block is empty'.
|
||||
// It feels like tslint has problems with simple polymorphism...
|
||||
// This selector is invalid and will never match so we won't bother sorting it to further appear in queries.
|
||||
}
|
||||
}
|
||||
|
||||
@SelectorProperties(Specificity.Universal, Rarity.Universal, Match.Static)
|
||||
export class UniversalSelector extends SimpleSelector {
|
||||
public toString(): string { return `*${wrap(this.combinator)}`; }
|
||||
public match(node: Node): boolean { return true; }
|
||||
}
|
||||
|
||||
@SelectorProperties(Specificity.Id, Rarity.Id, Match.Static)
|
||||
export class IdSelector extends SimpleSelector {
|
||||
constructor(public id: string) { super(); }
|
||||
public toString(): string { return `#${this.id}${wrap(this.combinator)}`; }
|
||||
public match(node: Node): boolean { return node.id === this.id; }
|
||||
public lookupSort(sorter: LookupSorter, base?: SelectorCore): void { sorter.sortById(this.id, base || this); }
|
||||
}
|
||||
|
||||
@SelectorProperties(Specificity.Type, Rarity.Type, Match.Static)
|
||||
export class TypeSelector extends SimpleSelector {
|
||||
constructor(public cssType: string) { super(); }
|
||||
public toString(): string { return `${this.cssType}${wrap(this.combinator)}`; }
|
||||
public match(node: Node): boolean { return node.cssType === this.cssType; }
|
||||
public lookupSort(sorter: LookupSorter, base?: SelectorCore): void { sorter.sortByType(this.cssType, base || this); }
|
||||
}
|
||||
|
||||
@SelectorProperties(Specificity.Class, Rarity.Class, Match.Static)
|
||||
export class ClassSelector extends SimpleSelector {
|
||||
constructor(public cssClass: string) { super(); }
|
||||
public toString(): string { return `.${this.cssClass}${wrap(this.combinator)}`; }
|
||||
public match(node: Node): boolean { return node.cssClasses && node.cssClasses.has(this.cssClass); }
|
||||
public lookupSort(sorter: LookupSorter, base?: SelectorCore): void { sorter.sortByClass(this.cssClass, base || this); }
|
||||
}
|
||||
|
||||
declare type AttributeTest = "=" | "^=" | "$=" | "*=" | "=" | "~=" | "|=";
|
||||
@SelectorProperties(Specificity.Attribute, Rarity.Attribute, Match.Dynamic)
|
||||
export class AttributeSelector extends SimpleSelector {
|
||||
constructor(public attribute: string, public test?: AttributeTest, public value?: string) {
|
||||
super();
|
||||
|
||||
if (!test) {
|
||||
// HasAttribute
|
||||
this.match = node => !isNullOrUndefined(node[attribute]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!value) {
|
||||
this.match = node => false;
|
||||
}
|
||||
|
||||
let escapedValue = escapeRegexSymbols(value);
|
||||
let regexp: RegExp = null;
|
||||
switch (test) {
|
||||
case "^=": // PrefixMatch
|
||||
regexp = new RegExp("^" + escapedValue);
|
||||
break;
|
||||
case "$=": // SuffixMatch
|
||||
regexp = new RegExp(escapedValue + "$");
|
||||
break;
|
||||
case "*=": // SubstringMatch
|
||||
regexp = new RegExp(escapedValue);
|
||||
break;
|
||||
case "=": // Equals
|
||||
regexp = new RegExp("^" + escapedValue + "$");
|
||||
break;
|
||||
case "~=": // Includes
|
||||
if (/\s/.test(value)) {
|
||||
this.match = node => false;
|
||||
|
||||
return;
|
||||
}
|
||||
regexp = new RegExp("(^|\\s)" + escapedValue + "(\\s|$)");
|
||||
break;
|
||||
case "|=": // DashMatch
|
||||
regexp = new RegExp("^" + escapedValue + "(-|$)");
|
||||
break;
|
||||
}
|
||||
|
||||
if (regexp) {
|
||||
this.match = node => regexp.test(node[attribute] + "");
|
||||
|
||||
return;
|
||||
} else {
|
||||
this.match = node => false;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
public toString(): string { return `[${this.attribute}${wrap(this.test)}${(this.test && this.value) || ""}]${wrap(this.combinator)}`; }
|
||||
public match(node: Node): boolean { return false; }
|
||||
public mayMatch(node: Node): boolean { return true; }
|
||||
public trackChanges(node: Node, map: ChangeAccumulator): void { map.addAttribute(node, this.attribute); }
|
||||
}
|
||||
|
||||
@SelectorProperties(Specificity.PseudoClass, Rarity.PseudoClass, Match.Dynamic)
|
||||
export class PseudoClassSelector extends SimpleSelector {
|
||||
constructor(public cssPseudoClass: string) { super(); }
|
||||
public toString(): string { return `:${this.cssPseudoClass}${wrap(this.combinator)}`; }
|
||||
public match(node: Node): boolean { return node.cssPseudoClasses && node.cssPseudoClasses.has(this.cssPseudoClass); }
|
||||
public mayMatch(node: Node): boolean { return true; }
|
||||
public trackChanges(node: Node, map: ChangeAccumulator): void { map.addPseudoClass(node, this.cssPseudoClass); }
|
||||
}
|
||||
|
||||
export class SimpleSelectorSequence extends SimpleSelector {
|
||||
private head: SimpleSelector;
|
||||
constructor(public selectors: SimpleSelector[]) {
|
||||
super();
|
||||
this.specificity = selectors.reduce((sum, sel) => sel.specificity + sum, 0);
|
||||
this.head = this.selectors.reduce((prev, curr) => !prev || (curr.rarity > prev.rarity) ? curr : prev, null);
|
||||
this.dynamic = selectors.some(sel => sel.dynamic);
|
||||
}
|
||||
public toString(): string { return `${this.selectors.join("")}${wrap(this.combinator)}`; }
|
||||
public match(node: Node): boolean { return this.selectors.every(sel => sel.match(node)); }
|
||||
public mayMatch(node: Node): boolean {
|
||||
return this.selectors.every(sel => sel.mayMatch(node));
|
||||
}
|
||||
public trackChanges(node, map): void {
|
||||
this.selectors.forEach(sel => sel.trackChanges(node, map));
|
||||
}
|
||||
public lookupSort(sorter: LookupSorter, base?: SelectorCore): void {
|
||||
this.head.lookupSort(sorter, base || this);
|
||||
}
|
||||
}
|
||||
|
||||
export class Selector extends SelectorCore {
|
||||
// Grouped by ancestor combinators, then by direct child combinators.
|
||||
private groups: Selector.ChildGroup[];
|
||||
private last: SelectorCore;
|
||||
|
||||
constructor(public selectors: SimpleSelector[]) {
|
||||
super();
|
||||
const supportedCombinator = [undefined, " ", ">", "+"];
|
||||
let siblingGroup: SimpleSelector[];
|
||||
let lastGroup: SimpleSelector[][];
|
||||
let groups: SimpleSelector[][][] = [];
|
||||
selectors.reverse().forEach(sel => {
|
||||
if (supportedCombinator.indexOf(sel.combinator) === -1) {
|
||||
throw new Error(`Unsupported combinator "${sel.combinator}".`);
|
||||
}
|
||||
if (sel.combinator === undefined || sel.combinator === " ") {
|
||||
groups.push(lastGroup = [siblingGroup = []]);
|
||||
}
|
||||
if (sel.combinator === ">") {
|
||||
lastGroup.push(siblingGroup = []);
|
||||
}
|
||||
siblingGroup.push(sel);
|
||||
});
|
||||
this.groups = groups.map(g =>
|
||||
new Selector.ChildGroup(g.map(sg =>
|
||||
new Selector.SiblingGroup(sg)
|
||||
))
|
||||
);
|
||||
this.last = selectors[0];
|
||||
this.specificity = selectors.reduce((sum, sel) => sel.specificity + sum, 0);
|
||||
this.dynamic = selectors.some(sel => sel.dynamic);
|
||||
}
|
||||
|
||||
public toString(): string { return this.selectors.join(""); }
|
||||
|
||||
public match(node: Node): boolean {
|
||||
return this.groups.every((group, i) => {
|
||||
if (i === 0) {
|
||||
node = group.match(node);
|
||||
|
||||
return !!node;
|
||||
} else {
|
||||
let ancestor = node;
|
||||
while (ancestor = ancestor.parent) {
|
||||
if (node = group.match(ancestor)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public lookupSort(sorter: LookupSorter, base?: SelectorCore): void {
|
||||
this.last.lookupSort(sorter, this);
|
||||
}
|
||||
|
||||
public accumulateChanges(node: Node, map?: ChangeAccumulator): boolean {
|
||||
if (!this.dynamic) {
|
||||
return this.match(node);
|
||||
}
|
||||
|
||||
let bounds: Selector.Bound[] = [];
|
||||
let mayMatch = this.groups.every((group, i) => {
|
||||
if (i === 0) {
|
||||
let nextNode = group.mayMatch(node);
|
||||
bounds.push({ left: node, right: node });
|
||||
node = nextNode;
|
||||
|
||||
return !!node;
|
||||
} else {
|
||||
let ancestor = node;
|
||||
while (ancestor = ancestor.parent) {
|
||||
let nextNode = group.mayMatch(ancestor);
|
||||
if (nextNode) {
|
||||
bounds.push({ left: ancestor, right: null });
|
||||
node = nextNode;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// Calculating the right bounds for each selectors won't save much
|
||||
if (!mayMatch) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!map) {
|
||||
return mayMatch;
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.groups.length; i++) {
|
||||
let group = this.groups[i];
|
||||
if (!group.dynamic) {
|
||||
continue;
|
||||
}
|
||||
let bound = bounds[i];
|
||||
let node = bound.left;
|
||||
do {
|
||||
if (group.mayMatch(node)) {
|
||||
group.trackChanges(node, map);
|
||||
}
|
||||
} while ((node !== bound.right) && (node = node.parent));
|
||||
}
|
||||
|
||||
return mayMatch;
|
||||
}
|
||||
}
|
||||
export namespace Selector {
|
||||
// Non-spec. Selector sequences are grouped by ancestor then by child combinators for easier backtracking.
|
||||
export class ChildGroup {
|
||||
public dynamic: boolean;
|
||||
|
||||
constructor(private selectors: SiblingGroup[]) {
|
||||
this.dynamic = selectors.some(sel => sel.dynamic);
|
||||
}
|
||||
|
||||
public match(node: Node): Node {
|
||||
return this.selectors.every((sel, i) => (i === 0 ? node : node = node.parent) && !!sel.match(node)) ? node : null;
|
||||
}
|
||||
|
||||
public mayMatch(node: Node): Node {
|
||||
return this.selectors.every((sel, i) => (i === 0 ? node : node = node.parent) && !!sel.mayMatch(node)) ? node : null;
|
||||
}
|
||||
|
||||
public trackChanges(node: Node, map: ChangeAccumulator) {
|
||||
this.selectors.forEach((sel, i) => (i === 0 ? node : node = node.parent) && sel.trackChanges(node, map));
|
||||
}
|
||||
}
|
||||
export class SiblingGroup {
|
||||
public dynamic: boolean;
|
||||
|
||||
constructor(private selectors: SimpleSelector[]) {
|
||||
this.dynamic = selectors.some(sel => sel.dynamic);
|
||||
}
|
||||
|
||||
public match(node: Node): Node {
|
||||
return this.selectors.every((sel, i) => (i === 0 ? node : node = getNodeDirectSibling(node)) && sel.match(node)) ? node : null;
|
||||
}
|
||||
|
||||
public mayMatch(node: Node): Node {
|
||||
return this.selectors.every((sel, i) => (i === 0 ? node : node = getNodeDirectSibling(node)) && sel.mayMatch(node)) ? node : null;
|
||||
}
|
||||
|
||||
public trackChanges(node: Node, map: ChangeAccumulator) {
|
||||
this.selectors.forEach((sel, i) => (i === 0 ? node : node = getNodeDirectSibling(node)) && sel.trackChanges(node, map));
|
||||
}
|
||||
}
|
||||
export interface Bound {
|
||||
left: Node;
|
||||
right: Node;
|
||||
}
|
||||
}
|
||||
|
||||
export class RuleSet {
|
||||
constructor(public selectors: SelectorCore[], private declarations: Declaration[]) {
|
||||
this.selectors.forEach(sel => sel.ruleset = this);
|
||||
}
|
||||
public toString(): string { return `${this.selectors.join(", ")} {${this.declarations.map((d, i) => `${i === 0 ? " " : ""}${d.property}: ${d.value}`).join("; ")} }`; }
|
||||
public lookupSort(sorter: LookupSorter): void { this.selectors.forEach(sel => sel.lookupSort(sorter)); }
|
||||
}
|
||||
|
||||
export function fromAstNodes(astRules: cssParser.Node[]): RuleSet[] {
|
||||
return (<cssParser.Rule[]>astRules.filter(isRule)).map(rule => {
|
||||
let declarations = rule.declarations.filter(isDeclaration).map(createDeclaration);
|
||||
let selectors = rule.selectors.map(createSelector);
|
||||
let ruleset = new RuleSet(selectors, declarations);
|
||||
|
||||
return ruleset;
|
||||
});
|
||||
}
|
||||
|
||||
function createDeclaration(decl: cssParser.Declaration): any {
|
||||
return { property: decl.property.toLowerCase(), value: decl.value };
|
||||
}
|
||||
|
||||
function createSimpleSelectorFromAst(ast: parser.SimpleSelector): SimpleSelector {
|
||||
switch (ast.type) {
|
||||
case "*": return new UniversalSelector();
|
||||
case "#": return new IdSelector(ast.identifier);
|
||||
case "": return new TypeSelector(ast.identifier.replace(/-/, "").toLowerCase());
|
||||
case ".": return new ClassSelector(ast.identifier);
|
||||
case ":": return new PseudoClassSelector(ast.identifier);
|
||||
case "[]": return ast.test ? new AttributeSelector(ast.property, ast.test, ast.value) : new AttributeSelector(ast.property);
|
||||
}
|
||||
}
|
||||
|
||||
function createSimpleSelectorSequenceFromAst(ast: parser.SimpleSelectorSequence): SimpleSelectorSequence | SimpleSelector {
|
||||
if (ast.length === 0) {
|
||||
return new InvalidSelector(new Error("Empty simple selector sequence."));
|
||||
} else if (ast.length === 1) {
|
||||
return createSimpleSelectorFromAst(ast[0]);
|
||||
} else {
|
||||
return new SimpleSelectorSequence(ast.map(createSimpleSelectorFromAst));
|
||||
}
|
||||
}
|
||||
|
||||
function createSelectorFromAst(ast: parser.Selector): SimpleSelector | SimpleSelectorSequence | Selector {
|
||||
if (ast.length === 0) {
|
||||
return new InvalidSelector(new Error("Empty selector."));
|
||||
} else if (ast.length === 1) {
|
||||
return createSimpleSelectorSequenceFromAst(ast[0][0]);
|
||||
} else {
|
||||
let simpleSelectorSequences = [];
|
||||
let simpleSelectorSequence: SimpleSelectorSequence | SimpleSelector;
|
||||
let combinator: parser.Combinator;
|
||||
for (let i = 0; i < ast.length; i++) {
|
||||
simpleSelectorSequence = createSimpleSelectorSequenceFromAst(<parser.SimpleSelectorSequence>ast[i][0]);
|
||||
combinator = <parser.Combinator>ast[i][1];
|
||||
if (combinator) {
|
||||
simpleSelectorSequence.combinator = combinator;
|
||||
}
|
||||
simpleSelectorSequences.push(simpleSelectorSequence);
|
||||
}
|
||||
|
||||
return new Selector(simpleSelectorSequences);
|
||||
}
|
||||
}
|
||||
|
||||
export function createSelector(sel: string): SimpleSelector | SimpleSelectorSequence | Selector {
|
||||
try {
|
||||
let parsedSelector = parser.parseSelector(sel);
|
||||
if (!parsedSelector) {
|
||||
return new InvalidSelector(new Error("Empty selector"));
|
||||
}
|
||||
|
||||
return createSelectorFromAst(parsedSelector.value);
|
||||
} catch (e) {
|
||||
return new InvalidSelector(e);
|
||||
}
|
||||
}
|
||||
|
||||
function isRule(node: cssParser.Node): node is cssParser.Rule {
|
||||
return node.type === "rule";
|
||||
}
|
||||
function isDeclaration(node: cssParser.Node): node is cssParser.Declaration {
|
||||
return node.type === "declaration";
|
||||
}
|
||||
|
||||
interface SelectorInDocument {
|
||||
pos: number;
|
||||
sel: SelectorCore;
|
||||
}
|
||||
interface SelectorMap {
|
||||
[key: string]: SelectorInDocument[];
|
||||
}
|
||||
export class SelectorsMap<T extends Node> implements LookupSorter {
|
||||
private id: SelectorMap = {};
|
||||
private class: SelectorMap = {};
|
||||
private type: SelectorMap = {};
|
||||
private universal: SelectorInDocument[] = [];
|
||||
|
||||
private position = 0;
|
||||
|
||||
constructor(rulesets: RuleSet[]) {
|
||||
rulesets.forEach(rule => rule.lookupSort(this));
|
||||
}
|
||||
|
||||
query(node: T): SelectorsMatch<T> {
|
||||
let selectorClasses = [
|
||||
this.universal,
|
||||
this.id[node.id],
|
||||
this.type[node.cssType]
|
||||
];
|
||||
if (node.cssClasses) {
|
||||
node.cssClasses.forEach(c => selectorClasses.push(this.class[c]));
|
||||
}
|
||||
let selectors = selectorClasses
|
||||
.filter(arr => !!arr)
|
||||
.reduce((cur, next) => cur.concat(next), []);
|
||||
|
||||
let selectorsMatch = new SelectorsMatch<T>();
|
||||
|
||||
selectorsMatch.selectors = selectors
|
||||
.filter(sel => sel.sel.accumulateChanges(node, selectorsMatch))
|
||||
.sort((a, b) => a.sel.specificity - b.sel.specificity || a.pos - b.pos)
|
||||
.map(docSel => docSel.sel);
|
||||
|
||||
return selectorsMatch;
|
||||
}
|
||||
|
||||
sortById(id: string, sel: SelectorCore): void { this.addToMap(this.id, id, sel); }
|
||||
sortByClass(cssClass: string, sel: SelectorCore): void {
|
||||
this.addToMap(this.class, cssClass, sel);
|
||||
}
|
||||
sortByType(cssType: string, sel: SelectorCore): void {
|
||||
this.addToMap(this.type, cssType, sel);
|
||||
}
|
||||
sortAsUniversal(sel: SelectorCore): void { this.universal.push(this.makeDocSelector(sel)); }
|
||||
|
||||
private addToMap(map: SelectorMap, head: string, sel: SelectorCore): void {
|
||||
this.position++;
|
||||
let list = map[head];
|
||||
if (list) {
|
||||
list.push(this.makeDocSelector(sel));
|
||||
} else {
|
||||
map[head] = [this.makeDocSelector(sel)];
|
||||
}
|
||||
}
|
||||
|
||||
private makeDocSelector(sel: SelectorCore): SelectorInDocument {
|
||||
return { sel, pos: this.position++ };
|
||||
}
|
||||
}
|
||||
|
||||
interface ChangeAccumulator {
|
||||
addAttribute(node: Node, attribute: string): void;
|
||||
addPseudoClass(node: Node, pseudoClass: string): void;
|
||||
}
|
||||
|
||||
export class SelectorsMatch<T extends Node> implements ChangeAccumulator {
|
||||
public changeMap: ChangeMap<T> = new Map<T, Changes>();
|
||||
public selectors;
|
||||
|
||||
public addAttribute(node: T, attribute: string): void {
|
||||
let deps: Changes = this.properties(node);
|
||||
if (!deps.attributes) {
|
||||
deps.attributes = new Set();
|
||||
}
|
||||
deps.attributes.add(attribute);
|
||||
}
|
||||
|
||||
public addPseudoClass(node: T, pseudoClass: string): void {
|
||||
let deps: Changes = this.properties(node);
|
||||
if (!deps.pseudoClasses) {
|
||||
deps.pseudoClasses = new Set();
|
||||
}
|
||||
deps.pseudoClasses.add(pseudoClass);
|
||||
}
|
||||
|
||||
private properties(node: T): Changes {
|
||||
let set = this.changeMap.get(node);
|
||||
if (!set) {
|
||||
this.changeMap.set(node, set = {});
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
}
|
5
nativescript-core/ui/styling/css-selector/package.json
Normal file
5
nativescript-core/ui/styling/css-selector/package.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "css-selector",
|
||||
"main": "css-selector",
|
||||
"types": "css-selector.d.ts"
|
||||
}
|
Reference in New Issue
Block a user