diff --git a/driver/js/packages/hippy-vue-css-loader/package.json b/driver/js/packages/hippy-vue-css-loader/package.json index 75c877ec2f3..03669ac176f 100644 --- a/driver/js/packages/hippy-vue-css-loader/package.json +++ b/driver/js/packages/hippy-vue-css-loader/package.json @@ -20,6 +20,7 @@ "loader-utils": "^1.4.1" }, "devDependencies": { + "@types/loader-utils": "^2.0.6", "ansi-regex": "^5.0.1", "hosted-git-info": "~3.0.8", "lodash": "~4.17.21", diff --git a/driver/js/packages/hippy-vue-css-loader/src/__tests__/color-parser.test.js b/driver/js/packages/hippy-vue-css-loader/src/__tests__/color-parser.test.ts similarity index 100% rename from driver/js/packages/hippy-vue-css-loader/src/__tests__/color-parser.test.js rename to driver/js/packages/hippy-vue-css-loader/src/__tests__/color-parser.test.ts diff --git a/driver/js/packages/hippy-vue-css-loader/src/color-parser.js b/driver/js/packages/hippy-vue-css-loader/src/color-parser.ts similarity index 93% rename from driver/js/packages/hippy-vue-css-loader/src/color-parser.js rename to driver/js/packages/hippy-vue-css-loader/src/color-parser.ts index c414eb46317..0a153b5d0c2 100644 --- a/driver/js/packages/hippy-vue-css-loader/src/color-parser.js +++ b/driver/js/packages/hippy-vue-css-loader/src/color-parser.ts @@ -19,8 +19,11 @@ */ /* eslint-disable no-mixed-operators */ +type NameMapType = { + [key: string]: number; +}; -export const names = { +export const names: NameMapType = { transparent: 0x00000000, aliceblue: 0xf0f8ffff, antiquewhite: 0xfaebd7ff, @@ -173,7 +176,7 @@ export const names = { yellowgreen: 0x9acd32ff, }; -const call = (...args) => `\\(\\s*(${args.join(')\\s*,\\s*(')})\\s*\\)`; +const call = (...args: any[]) => `\\(\\s*(${args.join(')\\s*,\\s*(')})\\s*\\)`; const NUMBER = '[-+]?\\d*\\.?\\d+'; const PERCENTAGE = `${NUMBER}%`; @@ -189,7 +192,7 @@ const matchers = { hex8: /^#([0-9a-fA-F]{8})$/, }; -const parse255 = (str) => { +const parse255 = (str: string) => { const int = parseInt(str, 10); if (int < 0) { return 0; @@ -200,7 +203,7 @@ const parse255 = (str) => { return int; }; -const parse1 = (str) => { +const parse1 = (str: string) => { const num = parseFloat(str); if (num < 0) { return 0; @@ -211,7 +214,7 @@ const parse1 = (str) => { return Math.round(num * 255); }; -const hue2rgb = (p, q, tx) => { +const hue2rgb = (p: number, q: number, tx: number) => { let t = tx; if (t < 0) { t += 1; @@ -231,7 +234,7 @@ const hue2rgb = (p, q, tx) => { return p; }; -const hslToRgb = (h, s, l) => { +const hslToRgb = (h: number, s: number, l: number) => { const q = l < 0.5 ? l * (1 + s) : l + s - l * s; const p = 2 * l - q; const r = hue2rgb(p, q, h + 1 / 3); @@ -244,13 +247,13 @@ const hslToRgb = (h, s, l) => { ); }; -const parse360 = (str) => { +const parse360 = (str: string) => { const int = parseFloat(str); return (((int % 360) + 360) % 360) / 360; }; -const parsePercentage = (str) => { - const int = parseFloat(str, 10); +const parsePercentage = (str: string) => { + const int = parseFloat(str); if (int < 0) { return 0; } @@ -260,7 +263,7 @@ const parsePercentage = (str) => { return int / 100; }; -function baseColor(color) { +function baseColor(color: number | string) { let match; if (typeof color === 'number') { if (color >>> 0 === color && color >= 0 && color <= 0xffffffff) { @@ -345,7 +348,7 @@ function baseColor(color) { /** * Translate the color to make sure native understood. */ -function translateColor(color) { +function translateColor(color: string | number) { if (typeof color === 'string' && color.indexOf('var(') !== -1) return color; let int32Color = baseColor(color); if (int32Color === null) { diff --git a/driver/js/packages/hippy-vue-css-loader/src/css-loader.js b/driver/js/packages/hippy-vue-css-loader/src/css-loader.ts similarity index 88% rename from driver/js/packages/hippy-vue-css-loader/src/css-loader.js rename to driver/js/packages/hippy-vue-css-loader/src/css-loader.ts index 6b937096378..9544b8bd140 100644 --- a/driver/js/packages/hippy-vue-css-loader/src/css-loader.js +++ b/driver/js/packages/hippy-vue-css-loader/src/css-loader.ts @@ -29,7 +29,7 @@ let sourceId = 0; /** * Convert the CSS text to AST that able to parse by selector parser. */ -function hippyVueCSSLoader(source) { +function hippyVueCSSLoader(this: any, source: any) { const options = getOptions(this); const parsed = parseCSS(source, { source: sourceId }); @@ -38,12 +38,13 @@ function hippyVueCSSLoader(source) { const hash = crypto.createHash(hashType); const contentHash = hash.update(source).digest('hex'); sourceId += 1; - const rulesAst = parsed.stylesheet.rules.filter(n => n.type === 'rule').map(n => ({ + const rulesAst = parsed.stylesheet.rules.filter((n: any) => n.type === 'rule').map((n: any) => ({ hash: contentHash, selectors: n.selectors, - declarations: n.declarations.map((dec) => { + + declarations: n.declarations.map((dec: any) => { let { value } = dec; - const isVariableColor = dec.property && dec.property.startsWith('-') && typeof value === 'string' + const isVariableColor = dec.property?.startsWith('-') && typeof value === 'string' && ( value.includes('#') || value.includes('rgb') @@ -52,7 +53,7 @@ function hippyVueCSSLoader(source) { || value.trim() in colorNames ); if (dec.property && (dec.property.toLowerCase().indexOf('color') > -1 || isVariableColor)) { - value = translateColor(value, options); + value = translateColor(value); } return { type: dec.type, diff --git a/driver/js/packages/hippy-vue-css-loader/src/css-parser.js b/driver/js/packages/hippy-vue-css-loader/src/css-parser.ts similarity index 91% rename from driver/js/packages/hippy-vue-css-loader/src/css-parser.js rename to driver/js/packages/hippy-vue-css-loader/src/css-parser.ts index cda0bcccfd2..e7d4c2def4e 100644 --- a/driver/js/packages/hippy-vue-css-loader/src/css-parser.js +++ b/driver/js/packages/hippy-vue-css-loader/src/css-parser.ts @@ -18,14 +18,12 @@ * limitations under the License. */ -/* eslint-disable no-use-before-define */ -/* eslint-disable no-param-reassign */ import { camelize } from 'shared/util'; import { tryConvertNumber, warn } from '@vue/util/index'; import translateColor from './color-parser'; -const PROPERTIES_MAP = { +const PROPERTIES_MAP: any = { textDecoration: 'textDecorationLine', boxShadowOffset: 'shadowOffset', boxShadowOffsetX: 'shadowOffsetX', @@ -38,7 +36,7 @@ const PROPERTIES_MAP = { }; // linear-gradient direction description map -const LINEAR_GRADIENT_DIRECTION_MAP = { +const LINEAR_GRADIENT_DIRECTION_MAP: any = { totop: '0', totopright: 'totopright', toright: '90', @@ -60,7 +58,7 @@ const commentRegexp = /\/\*[^*]*\*+([^/*][^*]*\*+)*\//g; /** * Trim `str`. */ -function trim(str) { +function trim(str: any) { return str ? str.trim() : ''; } @@ -68,7 +66,7 @@ function trim(str) { /** * Adds non-enumerable parent node reference to each node. */ -function addParent(obj, parent) { +function addParent(obj: any, parent: any) { const isNode = obj && typeof obj.type === 'string'; const childParent = isNode ? obj : parent; Object.keys(obj).forEach((k) => { @@ -96,14 +94,14 @@ function addParent(obj, parent) { * Convert the px unit to pt directly. * We found to the behavior of convert the unit directly is correct. */ -function convertPxUnitToPt(value) { +function convertPxUnitToPt(value: any) { // If value is number just ignore if (Number.isInteger(value)) { return value; } // If value unit is px, change to use pt as 1:1. if (typeof value === 'string' && value.endsWith('px')) { - const num = parseFloat(value.slice(0, value.indexOf('px')), 10); + const num = parseFloat(value.slice(0, value.indexOf('px'))); if (!Number.isNaN(num)) { value = num; } @@ -114,7 +112,7 @@ function convertPxUnitToPt(value) { /** * Parse the CSS to be AST tree. */ -function parseCSS(css, options) { +function parseCSS(css: any, options: any) { options = options || {}; /** @@ -126,7 +124,7 @@ function parseCSS(css, options) { /** * Update lineno and column based on `str`. */ - function updatePosition(str) { + function updatePosition(str: any) { const lines = str.match(/\n/g); if (lines) lineno += lines.length; const i = str.lastIndexOf('\n'); @@ -138,7 +136,7 @@ function parseCSS(css, options) { */ function position() { const start = { line: lineno, column }; - return (node) => { + return (node: any) => { node.position = new Position(start); whitespace(); return node; @@ -149,7 +147,11 @@ function parseCSS(css, options) { * Store position information for a node */ class Position { - constructor(start) { + content: any; + end: any; + source: any; + start: any; + constructor(start: any) { this.start = start; this.end = { line: lineno, column }; this.source = options.source; @@ -160,14 +162,14 @@ function parseCSS(css, options) { /** * Error `msg`. */ - const errorsList = []; - function error(msg) { + const errorsList: any = []; + function error(msg: any) { const err = new Error(`${options.source}:${lineno}:${column}: ${msg}`); - err.reason = msg; - err.filename = options.source; - err.line = lineno; - err.column = column; - err.source = css; + (err as any).reason = msg; + (err as any).filename = options.source; + (err as any).line = lineno; + (err as any).column = column; + (err as any).source = css; if (options.silent) { errorsList.push(err); } else { @@ -210,7 +212,7 @@ function parseCSS(css, options) { */ function rules() { let node; - const rules = []; + const rules: any = []; whitespace(); comments(rules); // eslint-disable-next-line no-cond-assign @@ -226,7 +228,7 @@ function parseCSS(css, options) { /** * Match `re` and return captures. */ - function match(re) { + function match(re: any) { const m = re.exec(css); if (!m) { return null; @@ -247,7 +249,7 @@ function parseCSS(css, options) { /** * Parse comments; */ - function comments(rules = []) { + function comments(rules: any[] = []): any[] { let c; rules = rules || []; while ((c = comment()) !== null) { @@ -298,9 +300,9 @@ function parseCSS(css, options) { * http://ostermiller.org/findcomment.html */ return trim(m[0]) .replace(/\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*\/+/g, '') - .replace(/"(?:\\"|[^"])*"|'(?:\\'|[^'])*'/g, m => m.replace(/,/g, '\u200C')) + .replace(/"(?:\\"|[^"])*"|'(?:\\'|[^'])*'/g, (m: any) => m.replace(/,/g, '\u200C')) .split(/\s*(?![^(]*\)),\s*/) - .map(s => s.replace(/\u200C/g, ',')); + .map((s: any) => s.replace(/\u200C/g, ',')); } /** @@ -308,7 +310,7 @@ function parseCSS(css, options) { * @param {string} value * @param {string} unit */ - function convertToDegree(value, unit = DEGREE_UNIT.DEG) { + function convertToDegree(value: any, unit = DEGREE_UNIT.DEG) { const convertedNumValue = parseFloat(value); let result = value || ''; const [, decimals] = value.split('.'); @@ -333,7 +335,7 @@ function parseCSS(css, options) { * parse gradient angle or direction * @param {string} value */ - function getLinearGradientAngle(value) { + function getLinearGradientAngle(value: any) { const processedValue = (value || '').replace(/\s*/g, '').toLowerCase(); const reg = /^([+-]?(?=(?\d+))\k\.?\d*)+(deg|turn|rad)|(to\w+)$/g; const valueList = reg.exec(processedValue); @@ -355,7 +357,7 @@ function parseCSS(css, options) { * parse gradient color stop * @param {string} value */ - function getLinearGradientColorStop(value) { + function getLinearGradientColorStop(value: any) { const processedValue = (value || '').replace(/\s+/g, ' ').trim(); const [color, percentage] = processedValue.split(/\s+(?![^(]*?\))/); const percentageCheckReg = /^([+-]?\d+\.?\d*)%$/g; @@ -380,16 +382,16 @@ function parseCSS(css, options) { * @param {string|Object|number|boolean} value * @returns {(string|{})[]} */ - function parseBackgroundImage(property, value) { + function parseBackgroundImage(property: any, value: any) { let processedValue = value; let processedProperty = property; if (value.indexOf('linear-gradient') === 0) { processedProperty = 'linearGradient'; const valueString = value.substring(value.indexOf('(') + 1, value.lastIndexOf(')')); const tokens = valueString.split(/,(?![^(]*?\))/); - const colorStopList = []; + const colorStopList: any = []; processedValue = {}; - tokens.forEach((value, index) => { + tokens.forEach((value: any, index: any) => { if (index === 0) { // the angle of linear-gradient parameter can be optional const angle = getLinearGradientAngle(value); @@ -465,7 +467,7 @@ function parseCSS(css, options) { v = parseFloat(v); } - const transform = {}; + const transform: any = {}; transform[key] = v; value.push(transform); }; @@ -476,8 +478,8 @@ function parseCSS(css, options) { break; case 'textShadowOffset': { const pos = value.split(' ') - .filter(v => v) - .map(v => convertPxUnitToPt(v)); + .filter((v: any) => v) + .map((v: any) => convertPxUnitToPt(v)); const [width] = pos; let [, height] = pos; if (!height) { @@ -491,8 +493,8 @@ function parseCSS(css, options) { } case 'shadowOffset': { const pos = value.split(' ') - .filter(v => v) - .map(v => convertPxUnitToPt(v)); + .filter((v: any) => v) + .map((v: any) => convertPxUnitToPt(v)); const [x] = pos; let [, y] = pos; if (!y) { @@ -529,7 +531,7 @@ function parseCSS(css, options) { * Parse declarations. */ function declarations() { - let decls = []; + let decls: any = []; if (!open()) return error('missing \'{\''); comments(decls); // declarations @@ -553,7 +555,7 @@ function parseCSS(css, options) { */ function keyframe() { let m; - const vals = []; + const vals: any[] = []; const pos = position(); while ((m = match(/^((\d+\.\d+|\.\d+|\d+)%?|[a-z]+)\s*/)) !== null) { vals.push(m[1]); @@ -787,7 +789,7 @@ function parseCSS(css, options) { /** * Parse non-block at-rules */ - function compileAtRule(name) { + function compileAtRule(name: any) { const re = new RegExp(`^@${name}\\s*([^;]+);`); return () => { const pos = position(); @@ -795,7 +797,7 @@ function parseCSS(css, options) { if (!m) { return null; } - const ret = { type: name }; + const ret: any = { type: name }; ret[name] = m[1].trim(); return pos(ret); }; @@ -835,6 +837,7 @@ function parseCSS(css, options) { declarations: declarations(), }); } + // @ts-expect-error TS(2554): Expected 2 arguments, but got 1. return addParent(stylesheet()); } diff --git a/driver/js/packages/hippy-vue-css-loader/src/index.js b/driver/js/packages/hippy-vue-css-loader/src/index.ts similarity index 100% rename from driver/js/packages/hippy-vue-css-loader/src/index.js rename to driver/js/packages/hippy-vue-css-loader/src/index.ts diff --git a/driver/js/packages/hippy-vue-native-components/src/animation.js b/driver/js/packages/hippy-vue-native-components/src/animation.ts similarity index 70% rename from driver/js/packages/hippy-vue-native-components/src/animation.js rename to driver/js/packages/hippy-vue-native-components/src/animation.ts index 08957c18912..b860e4218a0 100644 --- a/driver/js/packages/hippy-vue-native-components/src/animation.js +++ b/driver/js/packages/hippy-vue-native-components/src/animation.ts @@ -18,7 +18,7 @@ * limitations under the License. */ -function registerAnimation(Vue) { +function registerAnimation(Vue: any) { // Constants for animations const DEFAULT_OPTION = { valueType: undefined, @@ -38,7 +38,7 @@ function registerAnimation(Vue) { * @param {string} valueType * @param {*} originalValue */ - function parseValue(valueType, originalValue) { + function parseValue(valueType: any, originalValue: any) { if (valueType === 'color' && ['number', 'string'].indexOf(typeof originalValue) >= 0) { return Vue.Native.parseColor(originalValue); } @@ -48,7 +48,7 @@ function registerAnimation(Vue) { /** * Create the standalone animation */ - function createAnimation(option) { + function createAnimation(option: any) { const { mode = 'timing', valueType, @@ -78,7 +78,7 @@ function registerAnimation(Vue) { /** * Create the animationSet */ - function createAnimationSet(children, repeatCount = 0) { + function createAnimationSet(children: any, repeatCount = 0) { const animation = new global.Hippy.AnimationSet({ children, repeatCount, @@ -90,7 +90,7 @@ function registerAnimation(Vue) { }; } - function repeatCountDict(repeatCount) { + function repeatCountDict(repeatCount: any) { if (repeatCount === 'loop') { return -1; } @@ -100,14 +100,14 @@ function registerAnimation(Vue) { /** * Generate the styles from animation and animationSet Ids. */ - function createStyle(actions, animationIdsMap = {}) { + function createStyle(actions: any, animationIdsMap = {}) { const style = {}; Object.keys(actions).forEach((key) => { if (Array.isArray(actions[key])) { // Process AnimationSet from Array. const actionSet = actions[key]; const { repeatCount } = actionSet[actionSet.length - 1]; - const animationSetActions = actionSet.map((animationChild) => { + const animationSetActions = actionSet.map((animationChild: any) => { const { animationId, animation } = createAnimation(Object.assign({}, animationChild, { repeatCount: 0 })); Object.assign(animationIdsMap, { [animationId]: animation, @@ -139,11 +139,11 @@ function registerAnimation(Vue) { /** * Get animationIds from style for start/pause/destroy actions. */ - function getAnimationIds(style) { + function getAnimationIds(style: any) { const { transform, ...otherStyles } = style; let animationIds = Object.keys(otherStyles).map(key => style[key].animationId); if (Array.isArray(transform) && transform.length > 0) { - const transformIds = []; + const transformIds: any = []; transform.forEach(entity => Object.keys(entity) .forEach((key) => { if (entity[key]) { @@ -159,8 +159,8 @@ function registerAnimation(Vue) { } /** - * Register the animation component. - */ + * Register the animation component. + */ Vue.component('Animation', { inheritAttrs: false, props: { @@ -187,20 +187,20 @@ function registerAnimation(Vue) { }; }, watch: { - playing(to, from) { + playing(to: any, from: any) { if (!from && to) { - this.start(); + (this as any).start(); } else if (from && !to) { - this.pause(); + (this as any).pause(); } }, actions() { - this.destroy(); - this.create(); + (this as any).destroy(); + (this as any).create(); // trigger actionsDidUpdate in setTimeout callback to make sure node style updated setTimeout(() => { - if (typeof this.$listeners.actionsDidUpdate === 'function') { - this.$listeners.actionsDidUpdate(); + if (typeof (this as any).$listeners.actionsDidUpdate === 'function') { + (this as any).$listeners.actionsDidUpdate(); } }); }, @@ -230,87 +230,87 @@ function registerAnimation(Vue) { }, methods: { create() { - const { actions: { transform, ...actions } } = this.$props; - this.animationIdsMap = {}; - const style = createStyle(actions, this.animationIdsMap); + const { actions: { transform, ...actions } } = (this as any).$props; + (this as any).animationIdsMap = {}; + const style = createStyle(actions, (this as any).animationIdsMap); if (transform) { - const transformAnimations = createStyle(transform, this.animationIdsMap); - style.transform = Object.keys(transformAnimations).map(key => ({ + const transformAnimations = createStyle(transform, (this as any).animationIdsMap); + (style as any).transform = Object.keys(transformAnimations).map(key => ({ [key]: transformAnimations[key], })); } // Turn to be true at first startAnimation, and be false again when destroyed. - this.$alreadyStarted = false; + (this as any).$alreadyStarted = false; // Generated style - this.style = style; + (this as any).style = style; }, removeAnimationEvent() { - this.animationIds.forEach((animationId) => { - const animation = this.animationIdsMap[animationId]; + (this as any).animationIds.forEach((animationId: any) => { + const animation = (this as any).animationIdsMap[animationId]; if (!animation) return; - Object.keys(this.animationEventMap).forEach((key) => { - if (typeof this.$listeners[key] !== 'function') return; - const eventName = this.animationEventMap[key]; + Object.keys((this as any).animationEventMap).forEach((key) => { + if (typeof (this as any).$listeners[key] !== 'function') return; + const eventName = (this as any).animationEventMap[key]; if (!eventName) return; animation.removeEventListener(eventName); }); }); }, addAnimationEvent() { - this.animationIds.forEach((animationId) => { - const animation = this.animationIdsMap[animationId]; + (this as any).animationIds.forEach((animationId: any) => { + const animation = (this as any).animationIdsMap[animationId]; if (!animation) return; - Object.keys(this.animationEventMap).forEach((key) => { - if (typeof this.$listeners[key] !== 'function') return; - const eventName = this.animationEventMap[key]; + Object.keys((this as any).animationEventMap).forEach((key) => { + if (typeof (this as any).$listeners[key] !== 'function') return; + const eventName = (this as any).animationEventMap[key]; if (!eventName) return; animation.addEventListener(eventName, () => { - this.$emit(key); + (this as any).$emit(key); }); }); }); }, reset() { - this.$alreadyStarted = false; + (this as any).$alreadyStarted = false; }, start() { - if (!this.$alreadyStarted) { - this.animationIds = getAnimationIds(this.style); - this.$alreadyStarted = true; + if (!(this as any).$alreadyStarted) { + (this as any).animationIds = getAnimationIds((this as any).style); + (this as any).$alreadyStarted = true; this.removeAnimationEvent(); this.addAnimationEvent(); - this.animationIds.forEach((animationId) => { - const animation = this.animationIdsMap[animationId]; - animation && animation.start(); + (this as any).animationIds.forEach((animationId: any) => { + const animation = (this as any).animationIdsMap[animationId]; + animation?.start(); }); } else { this.resume(); } }, resume() { - const animationIds = getAnimationIds(this.style); + const animationIds = getAnimationIds((this as any).style); animationIds.forEach((animationId) => { - const animation = this.animationIdsMap[animationId]; - animation && animation.resume(); + const animation = (this as any).animationIdsMap[animationId]; + animation?.resume(); }); }, pause() { - if (!this.$alreadyStarted) { + if (!(this as any).$alreadyStarted) { return; } - const animationIds = getAnimationIds(this.style); + const animationIds = getAnimationIds((this as any).style); animationIds.forEach((animationId) => { - const animation = this.animationIdsMap[animationId]; - animation && animation.pause(); + const animation = (this as any).animationIdsMap[animationId]; + animation?.pause(); }); }, destroy() { this.removeAnimationEvent(); - this.$alreadyStarted = false; - const animationIds = getAnimationIds(this.style); + (this as any).$alreadyStarted = false; + const animationIds = getAnimationIds((this as any).style); animationIds.forEach((animationId) => { - const animation = this.animationIdsMap[animationId]; - animation && animation.destroy(); + const animation = (this as any).animationIdsMap[animationId]; + animation?.destroy(); }); }, }, diff --git a/driver/js/packages/hippy-vue-native-components/src/dialog.js b/driver/js/packages/hippy-vue-native-components/src/dialog.ts similarity index 95% rename from driver/js/packages/hippy-vue-native-components/src/dialog.js rename to driver/js/packages/hippy-vue-native-components/src/dialog.ts index e9cdc85b347..aa08186e3bf 100644 --- a/driver/js/packages/hippy-vue-native-components/src/dialog.js +++ b/driver/js/packages/hippy-vue-native-components/src/dialog.ts @@ -18,13 +18,13 @@ * limitations under the License. */ -const getFirstComponent = (elements) => { +const getFirstComponent = (elements: any) => { if (!elements) return null; if (Array.isArray(elements)) return elements[0]; if (elements) return elements; }; -function registerDialog(Vue) { +function registerDialog(Vue: any) { Vue.registerElement('hi-dialog', { component: { name: 'Modal', @@ -49,7 +49,7 @@ function registerDialog(Vue) { default: true, }, }, - render(h) { + render(h: any) { const firstChild = getFirstComponent(this.$slots.default); if (firstChild) { // __modalFirstChild__ marked to remove absolute position to be compatible with hippy 2.0 diff --git a/driver/js/packages/hippy-vue-native-components/src/index.js b/driver/js/packages/hippy-vue-native-components/src/index.ts similarity index 98% rename from driver/js/packages/hippy-vue-native-components/src/index.js rename to driver/js/packages/hippy-vue-native-components/src/index.ts index f7193f82baa..ed26f53ee6b 100644 --- a/driver/js/packages/hippy-vue-native-components/src/index.js +++ b/driver/js/packages/hippy-vue-native-components/src/index.ts @@ -29,7 +29,7 @@ import WaterfallComponent from './waterfall'; * Register all of native components */ const HippyVueNativeComponents = { - install(Vue) { + install(Vue: any) { AnimationComponent(Vue); DialogComponent(Vue); ListRefreshComponent(Vue); diff --git a/driver/js/packages/hippy-vue-native-components/src/pulls.js b/driver/js/packages/hippy-vue-native-components/src/pulls.ts similarity index 58% rename from driver/js/packages/hippy-vue-native-components/src/pulls.js rename to driver/js/packages/hippy-vue-native-components/src/pulls.ts index 62b08eed155..6abc3c34c2f 100644 --- a/driver/js/packages/hippy-vue-native-components/src/pulls.js +++ b/driver/js/packages/hippy-vue-native-components/src/pulls.ts @@ -21,7 +21,7 @@ const PULLING_EVENT = 'pulling'; const IDLE_EVENT = 'idle'; -function registerPull(Vue) { +function registerPull(Vue: any) { const { callUIFunction } = Vue.Native; [ ['Header', 'header'], @@ -41,7 +41,7 @@ function registerPull(Vue) { Vue.registerElement(`hi-pull-${lowerCase}`, { component: { name: `Pull${capitalCase}View`, - processEventData(event, nativeEventName, nativeEventParams) { + processEventData(event: any, nativeEventName: any, nativeEventParams: any) { switch (nativeEventName) { case `on${capitalCase}Released`: case `on${capitalCase}Pulling`: @@ -53,67 +53,69 @@ function registerPull(Vue) { }, }, }); - Vue.component(`pull-${lowerCase}`, { methods: { /** - * Expand the PullView and display the content - */ + * Expand the PullView and display the content + */ [`expandPull${capitalCase}`]() { - callUIFunction(this.$refs.instance, `expandPull${capitalCase}`); + callUIFunction((this.$refs as any).instance, `expandPull${capitalCase}`); }, /** - * Collapse the PullView and hide the content - * @param {Object} [options] additional config for pull header - * @param {number} [options.time] time left to hide pullHeader after collapsePullHeader() called, unit is ms - */ - [`collapsePull${capitalCase}`](options) { + * Collapse the PullView and hide the content + * @param {Object} [options] additional config for pull header + * @param {number} [options.time] time left to hide pullHeader after collapsePullHeader() called, unit is ms + */ + [`collapsePull${capitalCase}`](options: any) { if (capitalCase === 'Header') { // options: { time } if (typeof options !== 'undefined') { - callUIFunction(this.$refs.instance, `collapsePull${capitalCase}WithOptions`, [options]); + callUIFunction((this.$refs as any).instance, `collapsePull${capitalCase}WithOptions`, [options]); } else { - callUIFunction(this.$refs.instance, `collapsePull${capitalCase}`); + callUIFunction((this.$refs as any).instance, `collapsePull${capitalCase}`); } } else { - callUIFunction(this.$refs.instance, `collapsePull${capitalCase}`); + callUIFunction((this.$refs as any).instance, `collapsePull${capitalCase}`); } }, /** - * Get the refresh height by @layout event - * @param {Object} evt - */ - onLayout(evt) { + * Get the refresh height by @layout event + * @param {Object} evt + */ + onLayout(evt: any) { this.$contentHeight = evt.height; }, /** - * Trigger when release the finger after pulling gap larger than the content height - * Convert to `released` event. - */ - [`on${capitalCase}Released`](evt) { + * Trigger when release the finger after pulling gap larger than the content height + * Convert to `released` event. + */ + [`on${capitalCase}Released`](evt: any) { + // @ts-expect-error TS(2554): Expected 1 arguments, but got 2. this.$emit('released', evt); }, /** - * Trigger when pulling - * Convert to `idle` event if dragging gap less than content height - * Convert to `pulling` event if dragging gap larger than content height - * - * @param {Object} evt Event Object - * @param {number} evt.contentOffset Dragging gap, either horizion and vertical direction. - */ - [`on${capitalCase}Pulling`](evt) { + * Trigger when pulling + * Convert to `idle` event if dragging gap less than content height + * Convert to `pulling` event if dragging gap larger than content height + * + * @param {Object} evt Event Object + * @param {number} evt.contentOffset Dragging gap, either horizion and vertical direction. + */ + [`on${capitalCase}Pulling`](evt: any) { if (evt.contentOffset > this.$contentHeight) { - if (this.$lastEvent !== PULLING_EVENT) { - this.$lastEvent = PULLING_EVENT; + if ((this as any).$lastEvent !== PULLING_EVENT) { + (this as any).$lastEvent = PULLING_EVENT; + // @ts-expect-error TS(2554): Expected 1 arguments, but got 2. this.$emit(PULLING_EVENT, evt); } - } else if (this.$lastEvent !== IDLE_EVENT) { - this.$lastEvent = IDLE_EVENT; + } else if ((this as any).$lastEvent !== IDLE_EVENT) { + (this as any).$lastEvent = IDLE_EVENT; + // @ts-expect-error TS(2554): Expected 1 arguments, but got 2. this.$emit(IDLE_EVENT, evt); } }, }, - render(h) { + render(h: any) { const { released, pulling, idle } = this.$listeners; const on = { layout: this.onLayout, diff --git a/driver/js/packages/hippy-vue-native-components/src/swiper.js b/driver/js/packages/hippy-vue-native-components/src/swiper.ts similarity index 80% rename from driver/js/packages/hippy-vue-native-components/src/swiper.js rename to driver/js/packages/hippy-vue-native-components/src/swiper.ts index 4997a47f5c9..189643c5eac 100644 --- a/driver/js/packages/hippy-vue-native-components/src/swiper.js +++ b/driver/js/packages/hippy-vue-native-components/src/swiper.ts @@ -22,11 +22,11 @@ import { getEventRedirector } from './utils'; -function registerSwiper(Vue) { +function registerSwiper(Vue: any) { Vue.registerElement('hi-swiper', { component: { name: 'ViewPager', - processEventData(event, nativeEventName, nativeEventParams) { + processEventData(event: any, nativeEventName: any, nativeEventParams: any) { switch (nativeEventName) { case 'onPageSelected': event.currentSlide = nativeEventParams.position; @@ -71,11 +71,11 @@ function registerSwiper(Vue) { }, }, watch: { - current(to) { - if (this.$props.needAnimation) { - this.setSlide(to); + current(to: any) { + if ((this as any).$props.needAnimation) { + (this as any).setSlide(to); } else { - this.setSlideWithoutAnimation(to); + (this as any).setSlideWithoutAnimation(to); } }, }, @@ -83,14 +83,14 @@ function registerSwiper(Vue) { this.$initialSlide = this.$props.current; }, methods: { - setSlide(slideIndex) { - Vue.Native.callUIFunction(this.$refs.swiper, 'setPage', [slideIndex]); + setSlide(slideIndex: any) { + Vue.Native.callUIFunction((this as any).$refs.swiper, 'setPage', [slideIndex]); }, - setSlideWithoutAnimation(slideIndex) { - Vue.Native.callUIFunction(this.$refs.swiper, 'setPageWithoutAnimation', [slideIndex]); + setSlideWithoutAnimation(slideIndex: any) { + Vue.Native.callUIFunction((this as any).$refs.swiper, 'setPageWithoutAnimation', [slideIndex]); }, }, - render(h) { + render(h: any) { const on = getEventRedirector.call(this, [ ['dropped', 'pageSelected'], ['dragging', 'pageScroll'], diff --git a/driver/js/packages/hippy-vue-native-components/src/ul-refresh.js b/driver/js/packages/hippy-vue-native-components/src/ul-refresh.ts similarity index 87% rename from driver/js/packages/hippy-vue-native-components/src/ul-refresh.js rename to driver/js/packages/hippy-vue-native-components/src/ul-refresh.ts index 0a01fedc485..f999ad686ca 100644 --- a/driver/js/packages/hippy-vue-native-components/src/ul-refresh.js +++ b/driver/js/packages/hippy-vue-native-components/src/ul-refresh.ts @@ -20,7 +20,7 @@ import { getEventRedirector } from './utils'; -function registerUlRefresh(Vue) { +function registerUlRefresh(Vue: any) { Vue.registerElement('hi-ul-refresh-wrapper', { component: { name: 'RefreshWrapper', @@ -43,14 +43,14 @@ function registerUlRefresh(Vue) { }, methods: { startRefresh() { - Vue.Native.callUIFunction(this.$refs.refreshWrapper, 'startRefresh', null); + Vue.Native.callUIFunction((this as any).$refs.refreshWrapper, 'startRefresh', null); }, refreshCompleted() { // FIXME: Here's a typo mistake `refreshComplected` in native sdk. - Vue.Native.callUIFunction(this.$refs.refreshWrapper, 'refreshComplected', null); + Vue.Native.callUIFunction((this as any).$refs.refreshWrapper, 'refreshComplected', null); }, }, - render(h) { + render(h: any) { const on = getEventRedirector.call(this, [ 'refresh', ]); diff --git a/driver/js/packages/hippy-vue-native-components/src/utils.js b/driver/js/packages/hippy-vue-native-components/src/utils.ts similarity index 83% rename from driver/js/packages/hippy-vue-native-components/src/utils.js rename to driver/js/packages/hippy-vue-native-components/src/utils.ts index 282767faa4b..9c553de4241 100644 --- a/driver/js/packages/hippy-vue-native-components/src/utils.js +++ b/driver/js/packages/hippy-vue-native-components/src/utils.ts @@ -24,7 +24,7 @@ * @param {string} str The word input * @returns string */ -function capitalize(str) { +function capitalize(str: any) { if (typeof str !== 'string') { return ''; } @@ -40,9 +40,9 @@ function capitalize(str) { * @param {string[] | string[][]} events events will be redirect * @returns Object */ -function getEventRedirector(events) { +function getEventRedirector(this: any, events: any) { const on = {}; - events.forEach((event) => { + events.forEach((event: any) => { if (Array.isArray(event)) { // exposedEventName is used in vue declared, nativeEventName is used in native const [exposedEventName, nativeEventName] = event; @@ -50,17 +50,19 @@ function getEventRedirector(events) { // Use event handler first if declared if (this[`on${capitalize(nativeEventName)}`]) { // event will be converted like "dropped,pageSelected" which assigned to "on" object + // @ts-expect-error TS(2538): Type 'any[]' cannot be used as an index type. on[event] = this[`on${capitalize(nativeEventName)}`]; } else { // if no event handler found, emit default exposedEventName. - on[event] = evt => this.$emit(exposedEventName, evt); + // @ts-expect-error TS(2538): Type 'any[]' cannot be used as an index type. + on[event] = (evt: any) => this.$emit(exposedEventName, evt); } } } else if (Object.prototype.hasOwnProperty.call(this.$listeners, event)) { if (this[`on${capitalize(event)}`]) { on[event] = this[`on${capitalize(event)}`]; } else { - on[event] = evt => this.$emit(event, evt); + on[event] = (evt: any) => this.$emit(event, evt); } } }); diff --git a/driver/js/packages/hippy-vue-native-components/src/waterfall.js b/driver/js/packages/hippy-vue-native-components/src/waterfall.ts similarity index 74% rename from driver/js/packages/hippy-vue-native-components/src/waterfall.js rename to driver/js/packages/hippy-vue-native-components/src/waterfall.ts index 3c7d26ec6b2..a037420eff4 100644 --- a/driver/js/packages/hippy-vue-native-components/src/waterfall.js +++ b/driver/js/packages/hippy-vue-native-components/src/waterfall.ts @@ -21,11 +21,11 @@ /* eslint-disable no-param-reassign */ import { getEventRedirector } from './utils'; -function registerWaterfall(Vue) { +function registerWaterfall(Vue: any) { Vue.registerElement('hi-waterfall', { component: { name: 'WaterfallView', - processEventData(event, nativeEventName, nativeEventParams) { + processEventData(event: any, nativeEventName: any, nativeEventParams: any) { switch (nativeEventName) { case 'onExposureReport': event.exposureInfo = nativeEventParams.exposureInfo; @@ -112,26 +112,27 @@ function registerWaterfall(Vue) { }, methods: { // call native methods - call(action, params) { - Vue.Native.callUIFunction(this.$refs.waterfall, action, params); + call(action: any, params: any) { + Vue.Native.callUIFunction((this as any).$refs.waterfall, action, params); }, startRefresh() { + // @ts-expect-error TS(2554): Expected 2 arguments, but got 1. this.call('startRefresh'); }, /** @param {number} type 1.same as startRefresh */ - startRefreshWithType(type) { + startRefreshWithType(type: any) { this.call('startRefreshWithType', [type]); }, callExposureReport() { this.call('callExposureReport', []); }, /** - * Scrolls to a given index of item, either immediately, with a smooth animation. - * - * @param {Object} scrollToIndex params - * @param {number} scrollToIndex.index - Scroll to specific index. - * @param {boolean} scrollToIndex.animated - With smooth animation. By default is true. - */ + * Scrolls to a given index of item, either immediately, with a smooth animation. + * + * @param {Object} scrollToIndex params + * @param {number} scrollToIndex.index - Scroll to specific index. + * @param {boolean} scrollToIndex.animated - With smooth animation. By default is true. + */ scrollToIndex({ index = 0, animated = true }) { if (typeof index !== 'number' || typeof animated !== 'boolean') { return; @@ -139,13 +140,13 @@ function registerWaterfall(Vue) { this.call('scrollToIndex', [index, index, animated]); }, /** - * Scrolls to a given x, y offset, either immediately, with a smooth animation. - * - * @param {Object} scrollToContentOffset params - * @param {number} scrollToContentOffset.xOffset - Scroll to horizon offset X. - * @param {number} scrollToContentOffset.yOffset - Scroll To vertical offset Y. - * @param {boolean} scrollToContentOffset.animated - With smooth animation. By default is true. - */ + * Scrolls to a given x, y offset, either immediately, with a smooth animation. + * + * @param {Object} scrollToContentOffset params + * @param {number} scrollToContentOffset.xOffset - Scroll to horizon offset X. + * @param {number} scrollToContentOffset.yOffset - Scroll To vertical offset Y. + * @param {boolean} scrollToContentOffset.animated - With smooth animation. By default is true. + */ scrollToContentOffset({ xOffset = 0, yOffset = 0, animated = true }) { if (typeof xOffset !== 'number' || typeof yOffset !== 'number' || typeof animated !== 'boolean') { return; @@ -153,13 +154,14 @@ function registerWaterfall(Vue) { this.call('scrollToContentOffset', [xOffset, yOffset, animated]); }, /** - * start to load more waterfall items - */ + * start to load more waterfall items + */ startLoadMore() { + // @ts-expect-error TS(2554): Expected 2 arguments, but got 1. this.call('startLoadMore'); }, }, - render(h) { + render(h: any) { const on = getEventRedirector.call(this, [ 'headerReleased', 'headerPulling', @@ -168,24 +170,20 @@ function registerWaterfall(Vue) { 'initialListReady', 'scroll', ]); - return h( - 'hi-waterfall', - { - on, - ref: 'waterfall', - attrs: { - numberOfColumns: this.numberOfColumns, - contentInset: this.contentInset, - columnSpacing: this.columnSpacing, - interItemSpacing: this.interItemSpacing, - preloadItemNumber: this.preloadItemNumber, - containBannerView: this.containBannerView, - containPullHeader: this.containPullHeader, - containPullFooter: this.containPullFooter, - }, + return h('hi-waterfall', { + on, + ref: 'waterfall', + attrs: { + numberOfColumns: this.numberOfColumns, + contentInset: this.contentInset, + columnSpacing: this.columnSpacing, + interItemSpacing: this.interItemSpacing, + preloadItemNumber: this.preloadItemNumber, + containBannerView: this.containBannerView, + containPullHeader: this.containPullHeader, + containPullFooter: this.containPullFooter, }, - this.$slots.default, - ); + }, this.$slots.default); }, }); Vue.component('WaterfallItem', { @@ -196,7 +194,7 @@ function registerWaterfall(Vue) { default: '', }, }, - render(h) { + render(h: any) { return h( 'hi-waterfall-item', { diff --git a/driver/js/packages/hippy-vue-next/src/util/event.ts b/driver/js/packages/hippy-vue-next/src/util/event.ts index 7565dd32bad..2fb2cff5ce0 100644 --- a/driver/js/packages/hippy-vue-next/src/util/event.ts +++ b/driver/js/packages/hippy-vue-next/src/util/event.ts @@ -44,11 +44,11 @@ const DOMEventPhase = { BUBBLING_PHASE: 3, }; -function isNativeGesture(name) { +function isNativeGesture(name: string) { return !!NativeEventMap[name]; } -function translateToNativeEventName(name) { +function translateToNativeEventName(name: string) { return name.replace(/^(on)?/g, '').toLocaleLowerCase(); } diff --git a/driver/js/packages/hippy-vue/package.json b/driver/js/packages/hippy-vue/package.json index 53375c9fb05..0ceef47b9fd 100644 --- a/driver/js/packages/hippy-vue/package.json +++ b/driver/js/packages/hippy-vue/package.json @@ -4,8 +4,10 @@ "description": "Vue binding for Hippy native framework", "author": "OpenHippy Team", "license": "Apache-2.0", - "main": "dist/index.js", - "types": "types/index.d.ts", + "entry": "./src/index.ts", + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", "homepage": "https://hippyjs.org", "repository": "https://github.com/Tencent/Hippy", "bugs": { @@ -25,6 +27,7 @@ "lodash": "~4.17.21", "minimist": "^1.2.6", "normalize-url": "^4.5.1", + "typescript": "^3.2.2", "y18n": "^4.0.1" } } diff --git a/driver/js/packages/hippy-vue/src/elements/__tests__/built-in.test.js b/driver/js/packages/hippy-vue/src/elements/__tests__/built-in.test.js deleted file mode 100644 index 44dacb06951..00000000000 --- a/driver/js/packages/hippy-vue/src/elements/__tests__/built-in.test.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making - * Hippy available. - * - * Copyright (C) 2017-2022 THL A29 Limited, a Tencent company. - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import test from 'ava'; -import { HIPPY_STATIC_PROTOCOL, HIPPY_DEBUG_ADDRESS } from '../../runtime/constants'; -import * as elements from '../built-in'; - -// See platform/hippy/renderer/__tests__/index.test.js - -test('img src for normal http', (t) => { - const url = elements.img.component.attributeMaps.src('https://hippyjs.org'); - t.is(url, 'https://hippyjs.org'); -}); - -test('img src for static resource', (t) => { - const url = elements.img.component.attributeMaps.src('assets/test.png'); - t.is(url, `${HIPPY_DEBUG_ADDRESS}assets/test.png`); -}); - -test('img src for static resource for production', (t) => { - process.env.NODE_ENV = 'production'; - const url = elements.img.component.attributeMaps.src('assets/test.png'); - delete process.env.NODE_ENV; - t.is(url, `${HIPPY_STATIC_PROTOCOL}./assets/test.png`); -}); - -test('img placeholder for normal http', (t) => { - const url = elements.img.component.attributeMaps.placeholder.propsValue('https://hippyjs.org'); - // Should be popup a warning. - t.is(url, 'https://hippyjs.org'); -}); - -test('img placeholder for base64 image', (t) => { - const url = elements.img.component.attributeMaps.placeholder.propsValue('base64:image/jpeg?xxxx'); - // Should not be popup a warning. - t.is(url, 'base64:image/jpeg?xxxx'); -}); - -test('img placeholder for local path image', (t) => { - const url = elements.img.component.attributeMaps.placeholder.propsValue('./assets/defaultImage.png'); - // Should not be popup a warning. - t.is(url, './assets/defaultImage.png'); -}); - -test('input disabled test', (t) => { - const disabled = elements.input.component.attributeMaps.disabled.propsValue(false); - // Should not be popup a warning. - t.true(disabled); -}); diff --git a/driver/js/packages/hippy-vue/src/elements/__tests__/index.test.js b/driver/js/packages/hippy-vue/src/elements/__tests__/index.test.js deleted file mode 100644 index 8b120da4e7c..00000000000 --- a/driver/js/packages/hippy-vue/src/elements/__tests__/index.test.js +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making - * Hippy available. - * - * Copyright (C) 2017-2022 THL A29 Limited, a Tencent company. - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import test, { before } from 'ava'; -import * as elements from '../index'; - -const EMPTY_TAG_NAME = 'test'; - -before(() => { - elements.registerElement(EMPTY_TAG_NAME, { - component: { - name: 'TestComp', - }, - }); -}); - -test('getElementMap test', (t) => { - const elementMap = elements.getElementMap(); - t.true(elementMap instanceof Map); -}); - -test('isKnownView test', (t) => { - t.true(elements.isKnownView(EMPTY_TAG_NAME)); -}); - -test('canBeLeftOpenTag test', (t) => { - t.false(elements.canBeLeftOpenTag(EMPTY_TAG_NAME)); -}); - -test('isUnaryTag test', (t) => { - t.false(elements.isUnaryTag(EMPTY_TAG_NAME)); -}); - -test('mustUseProp test', (t) => { - t.false(elements.mustUseProp(EMPTY_TAG_NAME)); -}); - -test('mustUseProp true test', (t) => { - elements.registerElement('testtag', { - mustUseProp(type, attr) { - return ['test'].indexOf(attr) > -1; - }, - component: { - name: 'Test', - }, - }); - t.true(elements.mustUseProp('testtag', null, 'test')); - t.false(elements.mustUseProp('testtag', null, 'test2')); -}); - -test('getTagNamespace test', (t) => { - t.is(elements.getTagNamespace(EMPTY_TAG_NAME), ''); -}); - -test('isUnknownElement test', (t) => { - t.false(elements.isUnknownElement(EMPTY_TAG_NAME)); -}); - -test('registerElement again test', (t) => { - const err = t.throws(() => { - elements.registerElement(EMPTY_TAG_NAME); - }, Error); - t.is(err.message, `Element for ${EMPTY_TAG_NAME} already registered.`); -}); - -test('registerElement with out meta defined', (t) => { - const elementName = 'elementWithoutComponent'; - elements.registerElement(elementName); - function render(tagName, data, children) { - return { - tagName, - data, - children, - }; - } - const viewMeta = elements.getViewMeta(elementName); - t.is(viewMeta.component.name, elementName); - t.deepEqual(viewMeta.component.render(render, { data: 1, children: 2 }), { - tagName: elements.normalizeElementName(elementName), - data: 1, - children: 2, - }); -}); - -test('registerElement with mustUseProp', (t) => { - const elementName = 'elementWithMustProps'; - elements.registerElement(elementName, { - component: { - name: 'Test', - }, - mustUseProp(attr) { - return attr === 'testAttr'; - }, - }); - const viewMeta = elements.getViewMeta(elementName); - t.is(viewMeta.component.name, 'Test'); - t.true(viewMeta.mustUseProp('testAttr')); - t.false(viewMeta.mustUseProp()); -}); - -test('register element with empty name', (t) => { - const err = t.throws(() => { - elements.registerElement(''); - }, Error); - t.is(err.message, 'RegisterElement cannot set empty name'); -}); diff --git a/driver/js/packages/hippy-vue/src/elements/built-in.js b/driver/js/packages/hippy-vue/src/elements/built-in.ts similarity index 89% rename from driver/js/packages/hippy-vue/src/elements/built-in.js rename to driver/js/packages/hippy-vue/src/elements/built-in.ts index 2ef08ff2d9f..f5115a56f22 100644 --- a/driver/js/packages/hippy-vue/src/elements/built-in.js +++ b/driver/js/packages/hippy-vue/src/elements/built-in.ts @@ -22,7 +22,12 @@ import { warn, convertImageLocalPath } from '../util'; import { HIPPY_DEBUG_ADDRESS } from '../runtime/constants'; -import NATIVE_COMPONENT_NAME_MAP, * as components from '../renderer/native/components'; +import NATIVE_COMPONENT_NAME_MAP, * as components from '../native/components'; +import { NeedToTyped } from '../types/native'; + +interface InputValueMapType { + [key: string]: string; +} function mapEvent(...args) { const map = {}; @@ -37,7 +42,7 @@ function mapEvent(...args) { return map; } -const INPUT_VALUE_MAP = { +const INPUT_VALUE_MAP: InputValueMapType = { number: 'numeric', text: 'default', search: 'web-search', @@ -99,15 +104,15 @@ const div = { attributeMaps: { ...accessibilityAttrMaps, }, - processEventData(event, nativeEventName, nativeEventParams) { + processEventData(event: NeedToTyped, nativeEventName: NeedToTyped, nativeEventParams: NeedToTyped) { switch (nativeEventName) { case 'onScroll': case 'onScrollBeginDrag': case 'onScrollEndDrag': case 'onMomentumScrollBegin': case 'onMomentumScrollEnd': - event.offsetX = nativeEventParams.contentOffset && nativeEventParams.contentOffset.x; - event.offsetY = nativeEventParams.contentOffset && nativeEventParams.contentOffset.y; + event.offsetX = nativeEventParams.contentOffset?.x; + event.offsetY = nativeEventParams.contentOffset?.y; break; case 'onTouchDown': case 'onTouchMove': @@ -159,7 +164,7 @@ const img = { attributeMaps: { placeholder: { name: 'defaultSource', - propsValue(value) { + propsValue(value: NeedToTyped) { const url = convertImageLocalPath(value); if (url && url.indexOf(HIPPY_DEBUG_ADDRESS) < 0 @@ -173,12 +178,12 @@ const img = { * For Android, will use src property * For iOS, will convert to use source property */ - src(value) { + src(value: NeedToTyped) { return convertImageLocalPath(value); }, ...accessibilityAttrMaps, }, - processEventData(event, nativeEventName, nativeEventParams) { + processEventData(event: NeedToTyped, nativeEventName: NeedToTyped, nativeEventParams: NeedToTyped) { switch (nativeEventName) { case 'onTouchDown': case 'onTouchMove': @@ -221,15 +226,15 @@ const ul = { ...accessibilityAttrMaps, }, eventNamesMap: mapEvent('listReady', 'initialListReady'), - processEventData(event, nativeEventName, nativeEventParams) { + processEventData(event: NeedToTyped, nativeEventName: NeedToTyped, nativeEventParams: NeedToTyped) { switch (nativeEventName) { case 'onScroll': case 'onScrollBeginDrag': case 'onScrollEndDrag': case 'onMomentumScrollBegin': case 'onMomentumScrollEnd': - event.offsetX = nativeEventParams.contentOffset && nativeEventParams.contentOffset.x; - event.offsetY = nativeEventParams.contentOffset && nativeEventParams.contentOffset.y; + event.offsetX = nativeEventParams.contentOffset?.x; + event.offsetY = nativeEventParams.contentOffset?.y; break; case 'onDelete': event.index = nativeEventParams.index; @@ -282,7 +287,7 @@ const a = { attributeMaps: { href: { name: 'href', - propsValue(value) { + propsValue(value: NeedToTyped) { if (['//', 'http://', 'https://'].filter(url => value.indexOf(url) === 0).length) { warn(`href attribute can't apply effect in native with url: ${value}`); return ''; @@ -302,7 +307,7 @@ const input = { attributeMaps: { type: { name: 'keyboardType', - propsValue(value) { + propsValue(value: NeedToTyped) { const newValue = INPUT_VALUE_MAP[value]; if (!newValue) { return value; @@ -312,7 +317,7 @@ const input = { }, disabled: { name: 'editable', - propsValue(value) { + propsValue(value: NeedToTyped) { return !value; }, }, @@ -335,7 +340,7 @@ const input = { ['change', 'onChangeText'], ['select', 'onSelectionChange'], ]), - processEventData(event, nativeEventName, nativeEventParams) { + processEventData(event: NeedToTyped, nativeEventName: NeedToTyped, nativeEventParams: NeedToTyped) { switch (nativeEventName) { case 'onChangeText': case 'onEndEditing': @@ -394,14 +399,14 @@ const iframe = { attributeMaps: { src: { name: 'source', - propsValue(value) { + propsValue(value: NeedToTyped) { return { uri: value, }; }, }, }, - processEventData(event, nativeEventName, nativeEventParams) { + processEventData(event: NeedToTyped, nativeEventName: NeedToTyped, nativeEventParams: NeedToTyped) { switch (nativeEventName) { case 'onLoad': case 'onLoadStart': diff --git a/driver/js/packages/hippy-vue/src/elements/index.js b/driver/js/packages/hippy-vue/src/elements/index.ts similarity index 80% rename from driver/js/packages/hippy-vue/src/elements/index.js rename to driver/js/packages/hippy-vue/src/elements/index.ts index f0fc5cdc14f..765659dcbed 100644 --- a/driver/js/packages/hippy-vue/src/elements/index.js +++ b/driver/js/packages/hippy-vue/src/elements/index.ts @@ -20,6 +20,7 @@ import { makeMap, camelize } from 'shared/util'; import { capitalizeFirstLetter, warn } from '../util'; +import { NeedToTyped } from '../types/native'; import * as BUILT_IN_ELEMENTS from './built-in'; const isReservedTag = makeMap( @@ -40,22 +41,25 @@ const defaultViewMeta = { component: null, }; -function getDefaultComponent(elementName, meta, normalizedName) { +function getDefaultComponent(elementName: NeedToTyped, meta: NeedToTyped, normalizedName: NeedToTyped) { return { name: elementName, functional: true, model: meta.model, - render(h, { data, children }) { + render(h: NeedToTyped, { + data, + children, + }: NeedToTyped) { return h(normalizedName, data, children); }, }; } -function normalizeElementName(elementName) { +function normalizeElementName(elementName: any) { return elementName.toLowerCase(); } -function registerElement(elementName, oldMeta) { +function registerElement(elementName: any, oldMeta: NeedToTyped) { if (!elementName) { throw new Error('RegisterElement cannot set empty name'); } @@ -82,48 +86,48 @@ function getElementMap() { return elementMap; } -function getViewMeta(elementName) { +function getViewMeta(elementName: NeedToTyped) { const normalizedName = normalizeElementName(elementName); let viewMeta = defaultViewMeta; const entry = elementMap.get(normalizedName); - if (entry && entry.meta) { + if (entry?.meta) { viewMeta = entry.meta; } return viewMeta; } -function isKnownView(elementName) { +function isKnownView(elementName: NeedToTyped) { return elementMap.has(normalizeElementName(elementName)); } -function canBeLeftOpenTag(el) { +function canBeLeftOpenTag(el: NeedToTyped) { return getViewMeta(el).canBeLeftOpenTag; } -function isUnaryTag(el) { +function isUnaryTag(el: NeedToTyped) { return getViewMeta(el).isUnaryTag; } -function mustUseProp(el, type, attr) { - const viewMeta = getViewMeta(el); +function mustUseProp(el: NeedToTyped, type: NeedToTyped, attr: NeedToTyped) { + const viewMeta: NeedToTyped = getViewMeta(el); if (!viewMeta.mustUseProp) { return false; } return viewMeta.mustUseProp(type, attr); } -function getTagNamespace(el) { +function getTagNamespace(el: NeedToTyped) { return getViewMeta(el).tagNamespace; } -function isUnknownElement(el) { +function isUnknownElement(el: NeedToTyped) { return !isKnownView(el); } // Register components function registerBuiltinElements() { Object.keys(BUILT_IN_ELEMENTS).forEach((tagName) => { - const meta = BUILT_IN_ELEMENTS[tagName]; + const meta = (BUILT_IN_ELEMENTS as NeedToTyped)[tagName]; registerElement(tagName, meta); }); } diff --git a/driver/js/packages/hippy-vue/src/renderer/native/event/dispatcher.js b/driver/js/packages/hippy-vue/src/event/dispatcher.ts similarity index 85% rename from driver/js/packages/hippy-vue/src/renderer/native/event/dispatcher.js rename to driver/js/packages/hippy-vue/src/event/dispatcher.ts index f38cbec5d8e..8fcd1967613 100644 --- a/driver/js/packages/hippy-vue/src/renderer/native/event/dispatcher.js +++ b/driver/js/packages/hippy-vue/src/event/dispatcher.ts @@ -20,21 +20,22 @@ /* eslint-disable no-underscore-dangle */ -import { trace, getApp, warn } from '../../../util'; -import { getNodeById } from '../../../util/node'; -import { DOMEventPhase } from '../../../util/event'; -import { Event } from './event'; +import { trace, getApp, warn } from '../util'; +import { getNodeById } from '../util/node'; +import { DOMEventPhase } from '../util/event'; +import { NeedToTyped } from '../types/native'; +import { LayoutEvent } from './layout-event'; const LOG_TYPE = ['%c[event]%c', 'color: green', 'color: auto']; -function isTouchEvent(eventName) { +function isTouchEvent(eventName: string) { return ['onTouchDown', 'onTouchMove', 'onTouchEnd', 'onTouchCancel'].indexOf(eventName) >= 0; } /** * convert events */ -function convertEvent(eventName, targetEvent, params) { +function convertEvent(eventName: string, targetEvent: NeedToTyped, params: NeedToTyped) { if (isTouchEvent(eventName)) { Object.assign(targetEvent, { touches: { @@ -53,7 +54,7 @@ const EventDispatcher = { /** * Redirect native events to Vue directly. */ - receiveNativeEvent(nativeEvent) { + receiveNativeEvent(nativeEvent: NeedToTyped) { trace(...LOG_TYPE, 'receiveNativeEvent', nativeEvent); if (!nativeEvent || !Array.isArray(nativeEvent) || nativeEvent.length < 2) { return; @@ -68,7 +69,7 @@ const EventDispatcher = { /** * Receive native interactive events. */ - receiveComponentEvent(nativeEvent, domEvent) { + receiveComponentEvent(nativeEvent: NeedToTyped, domEvent: NeedToTyped) { trace(...LOG_TYPE, 'receiveComponentEvent', nativeEvent); if (!nativeEvent || !domEvent) { warn(...LOG_TYPE, 'receiveComponentEvent', 'nativeEvent or domEvent not exist'); @@ -83,7 +84,7 @@ const EventDispatcher = { } try { if ([DOMEventPhase.AT_TARGET, DOMEventPhase.BUBBLING_PHASE].indexOf(eventPhase) > -1) { - const targetEvent = new Event(originalName); + const targetEvent = new LayoutEvent(originalName); Object.assign(targetEvent, { eventPhase, nativeParams: params || {} }); if (nativeName === 'onLayout') { const { layout: { x, y, height, width } } = params; diff --git a/driver/js/packages/hippy-vue/src/renderer/native/event/emitter.js b/driver/js/packages/hippy-vue/src/event/emitter.ts similarity index 76% rename from driver/js/packages/hippy-vue/src/renderer/native/event/emitter.js rename to driver/js/packages/hippy-vue/src/event/emitter.ts index c3350856243..2ceb2644544 100644 --- a/driver/js/packages/hippy-vue/src/renderer/native/event/emitter.js +++ b/driver/js/packages/hippy-vue/src/event/emitter.ts @@ -21,19 +21,24 @@ /* eslint-disable no-underscore-dangle */ import { looseEqual } from 'shared/util'; -import { isDev, isFunction, warn } from '../../../util'; +import { isDev, isFunction, warn } from '../util'; +import { CallbackType, NeedToTyped } from '../types/native'; +import ViewNode from '../renderer/view-node'; -class EventEmitter { - constructor(element) { +export class EventEmitter { + element: ViewNode; + observers: NeedToTyped; + + public constructor(element: ViewNode) { this.element = element; this.observers = {}; } - getEventListeners() { + public getEventListeners() { return this.observers; } - addEventListener(eventNames, callback, options) { + public addEventListener(eventNames: string, callback: CallbackType, options: NeedToTyped) { if (typeof eventNames !== 'string') { throw new TypeError('Events name(s) must be string.'); } @@ -50,7 +55,7 @@ class EventEmitter { warn(`@${eventName} is deprecated because it's not compatible with browser standard, please use @${eventName.toLowerCase()} to instead as so on.`); } } - const list = this._getEventList(eventName, true); + const list = this.getEventList(eventName, true); list.push({ callback, options, @@ -59,7 +64,7 @@ class EventEmitter { return this.observers; } - removeEventListener(eventNames, callback, options) { + public removeEventListener(eventNames: string, callback: CallbackType, options: NeedToTyped) { if (typeof eventNames !== 'string') { throw new TypeError('Events name(s) must be string.'); } @@ -72,9 +77,9 @@ class EventEmitter { for (let i = 0, l = events.length; i < l; i += 1) { const eventName = events[i].trim(); if (callback) { - const list = this._getEventList(eventName, false); + const list = this.getEventList(eventName, false); if (list) { - const index = this._indexOfListener(list, callback, options); + const index = this.indexOfListener(list, callback, options); if (index >= 0) { list.splice(index, 1); } @@ -89,7 +94,7 @@ class EventEmitter { return this.observers; } - emit(eventInstance) { + public emit(eventInstance: NeedToTyped) { const { type: eventName } = eventInstance; const observers = this.observers[eventName]; if (!observers) { @@ -97,10 +102,10 @@ class EventEmitter { } for (let i = observers.length - 1; i >= 0; i -= 1) { const entry = observers[i]; - if (entry.options && entry.options.once) { + if (entry.options?.once) { observers.splice(i, 1); } - if (entry.options && entry.options.thisArg) { + if (entry.options?.thisArg) { entry.callback.apply(entry.options.thisArg, [eventInstance]); } else { entry.callback(eventInstance); @@ -108,7 +113,7 @@ class EventEmitter { } } - _getEventList(eventName, createIfNeeded) { + private getEventList(eventName: string, createIfNeeded: boolean) { let list = this.observers[eventName]; if (!list && createIfNeeded) { list = []; @@ -117,8 +122,8 @@ class EventEmitter { return list; } - _indexOfListener(list, callback, options) { - return list.findIndex((entry) => { + private indexOfListener(list: NeedToTyped, callback: CallbackType, options: NeedToTyped) { + return list.findIndex((entry: NeedToTyped) => { if (options) { return entry.callback === callback && looseEqual(entry.options, options); } @@ -127,6 +132,3 @@ class EventEmitter { } } -export { - EventEmitter, -}; diff --git a/driver/js/packages/hippy-vue/src/renderer/native/event/event.js b/driver/js/packages/hippy-vue/src/event/event.ts similarity index 55% rename from driver/js/packages/hippy-vue/src/renderer/native/event/event.js rename to driver/js/packages/hippy-vue/src/event/event.ts index 79c76d72224..0c0646877b1 100644 --- a/driver/js/packages/hippy-vue/src/renderer/native/event/event.js +++ b/driver/js/packages/hippy-vue/src/event/event.ts @@ -18,10 +18,40 @@ * limitations under the License. */ -/* eslint-disable no-underscore-dangle */ +import ElementNode from '../renderer/element-node'; +import { NeedToTyped } from '../types/native'; + class Event { - constructor(eventName) { + // event name + public type: string; + + public value = ''; + + // the object that triggered the event, the original target + public target: ElementNode | null = null; + + // the current object that triggered the event, which is changing as the event bubbles up + public currentTarget: ElementNode | null = null; + + public originalTarget: ElementNode | null = null; + + // whether the event can bubble, the default is true + public bubbles = true; + + // native parameters + public nativeParams?: NeedToTyped; + + // whether the default behavior of the event can be canceled, the default is true + protected cancelable = true; + + // indicates which stage of event stream processing, useless for now + protected eventPhase = 0; + + // whether the event has been canceled + private isCanceled = false; + + public constructor(eventName: string) { this.type = eventName; this.bubbles = true; this.cancelable = true; @@ -33,15 +63,15 @@ class Event { this.isCanceled = false; } - get canceled() { + public get canceled() { return this.isCanceled; } - stopPropagation() { + public stopPropagation() { this.bubbles = false; } - preventDefault() { + public preventDefault() { if (!this.cancelable) { return; } @@ -51,7 +81,7 @@ class Event { /** * Old compatible. */ - initEvent(eventName, bubbles = true, cancelable = true) { + public initEvent(eventName: string, bubbles = true, cancelable = true) { this.type = eventName; if (bubbles === false) { this.bubbles = false; diff --git a/driver/js/packages/hippy-vue/src/renderer/native/event/index.js b/driver/js/packages/hippy-vue/src/event/index.ts similarity index 100% rename from driver/js/packages/hippy-vue/src/renderer/native/event/index.js rename to driver/js/packages/hippy-vue/src/event/index.ts diff --git a/driver/js/packages/hippy-vue/src/event/layout-event.ts b/driver/js/packages/hippy-vue/src/event/layout-event.ts new file mode 100644 index 00000000000..90499aed0b6 --- /dev/null +++ b/driver/js/packages/hippy-vue/src/event/layout-event.ts @@ -0,0 +1,43 @@ +/* + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Event } from './event'; +/** + * layout event + */ +export class LayoutEvent extends Event { + // distance from top + public top?: number; + + // distance from left + public left?: number; + + // distance from bottom + public bottom?: number; + + // distance from right + public right?: number; + + // width + public width?: number; + + // height + public height?: number; +} diff --git a/driver/js/packages/hippy-vue/src/index.js b/driver/js/packages/hippy-vue/src/index.ts similarity index 97% rename from driver/js/packages/hippy-vue/src/index.js rename to driver/js/packages/hippy-vue/src/index.ts index b8b5bddea0f..f919efb78e4 100644 --- a/driver/js/packages/hippy-vue/src/index.js +++ b/driver/js/packages/hippy-vue/src/index.ts @@ -22,8 +22,10 @@ import Vue from './runtime/index'; import { setVue } from './util/index'; import WebSocket from './runtime/websocket'; +// @ts-ignore global.process = global.process || {}; global.process.env = global.process.env || {}; +// @ts-ignore global.WebSocket = WebSocket; Vue.config.silent = false; diff --git a/driver/js/packages/hippy-vue/src/renderer/native/components.js b/driver/js/packages/hippy-vue/src/native/components.ts similarity index 100% rename from driver/js/packages/hippy-vue/src/renderer/native/components.js rename to driver/js/packages/hippy-vue/src/native/components.ts diff --git a/driver/js/packages/hippy-vue/src/renderer/native/index.js b/driver/js/packages/hippy-vue/src/native/index.ts similarity index 79% rename from driver/js/packages/hippy-vue/src/renderer/native/index.js rename to driver/js/packages/hippy-vue/src/native/index.ts index e9ecacd3ec7..170d5bdb03e 100644 --- a/driver/js/packages/hippy-vue/src/renderer/native/index.js +++ b/driver/js/packages/hippy-vue/src/native/index.ts @@ -29,7 +29,7 @@ import { GLOBAL_STYLE_NAME, GLOBAL_DISPOSE_STYLE_NAME, ROOT_VIEW_ID, -} from '../../runtime/constants'; +} from '../runtime/constants'; import { getApp, trace, @@ -40,29 +40,37 @@ import { deepCopy, isTraceEnabled, getBeforeRenderToNative, -} from '../../util'; +} from '../util'; import { EventHandlerType, NativeEventMap, translateToNativeEventName, isNativeGesture, -} from '../../util/event'; +} from '../util/event'; import { isRTL, -} from '../../util/i18n'; -import { isStyleMatched, preCacheNode } from '../../util/node'; -import { fromAstNodes, SelectorsMap } from './style'; +} from '../util/i18n'; +import { + setCacheNodeStyle, + deleteCacheNodeStyle, + getCacheNodeStyle, +} from '../util/node-style'; +import { isStyleMatched, preCacheNode } from '../util/node'; +import { fromAstNodes, SelectorsMap } from '../style'; +import { CallbackType, NeedToTyped } from '../types/native'; +import ElementNode from '../renderer/element-node'; +import ViewNode from '../renderer/view-node'; const LOG_TYPE = ['%c[native]%c', 'color: red', 'color: auto']; -if (typeof global.Symbol !== 'function') { - global.Symbol = str => str; +interface BatchType { + [key: string]: any; } /** * UI Operations */ -const NODE_OPERATION_TYPES = { +const NODE_OPERATION_TYPES: BatchType = { createNode: Symbol('createNode'), updateNode: Symbol('updateNode'), deleteNode: Symbol('deleteNode'), @@ -71,13 +79,13 @@ const NODE_OPERATION_TYPES = { }; let batchIdle = true; -let batchNodes = []; +let batchNodes: NeedToTyped = []; /** * Convert an ordered node array into multiple fragments */ -function chunkNodes(batchNodes) { - const result = []; +function chunkNodes(batchNodes: NeedToTyped) { + const result: NeedToTyped = []; for (let i = 0; i < batchNodes.length; i += 1) { const chunk = batchNodes[i]; const { type, nodes, eventNodes, printedNodes } = chunk; @@ -98,11 +106,11 @@ function chunkNodes(batchNodes) { return result; } -function handleEventListeners(eventNodes = [], sceneBuilder) { +function handleEventListeners(eventNodes: HippyTypes.EventNode[] = [], sceneBuilder: NeedToTyped) { eventNodes.forEach((eventNode) => { if (eventNode) { const { id, eventList } = eventNode; - eventList.forEach((eventAttribute) => { + (eventList as any).forEach((eventAttribute: NeedToTyped) => { const { name, type, listener } = eventAttribute; let nativeEventName; if (isNativeGesture(name)) { @@ -125,20 +133,20 @@ function handleEventListeners(eventNodes = [], sceneBuilder) { /** * Initial CSS Map; */ -let cssMap; +let cssMap: NeedToTyped; /** * print nodes operation log * @param {HippyTypes.PrintedNode[]} printedNodes * @param {string} nodeType */ -function printNodeOperation(printedNodes, nodeType) { +function printNodeOperation(printedNodes: HippyTypes.PrintedNode[], nodeType: NeedToTyped) { if (isTraceEnabled()) { trace(...LOG_TYPE, nodeType, printedNodes); } } -function endBatch(app) { +function endBatch(app: NeedToTyped) { if (!batchIdle) return; batchIdle = false; if (batchNodes.length === 0) { @@ -206,7 +214,7 @@ function getCssMap() { } if (global[GLOBAL_DISPOSE_STYLE_NAME]) { - global[GLOBAL_DISPOSE_STYLE_NAME].forEach((id) => { + global[GLOBAL_DISPOSE_STYLE_NAME].forEach((id: NeedToTyped) => { cssMap.delete(id); }); global[GLOBAL_DISPOSE_STYLE_NAME] = undefined; @@ -218,9 +226,9 @@ function getCssMap() { /** * Translate to native props from attributes and meta */ -function getNativeProps(node) { +function getNativeProps(node: ElementNode) { // Initial the props with empty - const props = {}; + const props: NeedToTyped = {}; // Get the default native props from meta if (node.meta.component.defaultNativeProps) { Object.keys(node.meta.component.defaultNativeProps).forEach((key) => { @@ -285,7 +293,7 @@ function getNativeProps(node) { * @param targetNode * @param style */ -function parseTextInputComponent(targetNode, style) { +function parseTextInputComponent(targetNode: NeedToTyped, style: NeedToTyped) { if (targetNode.meta.component.name === 'TextInput') { // Change textAlign to right if display direction is right to left. if (isRTL()) { @@ -302,7 +310,7 @@ function parseTextInputComponent(targetNode, style) { * @param nativeNode * @param style */ -function parseViewComponent(targetNode, nativeNode, style) { +function parseViewComponent(targetNode: NeedToTyped, nativeNode: NeedToTyped, style: NeedToTyped) { if (targetNode.meta.component.name === 'View') { // Change View to ScrollView when meet overflow=scroll style. if (style.overflowX === 'scroll' && style.overflowY === 'scroll') { @@ -338,12 +346,12 @@ function parseViewComponent(targetNode, nativeNode, style) { * @param {ElementNode} element * @returns {{}} */ -function getElemCss(element) { +function getElemCss(element: NeedToTyped) { const style = Object.create(null); try { - getCssMap().query(element).selectors.forEach((matchedSelector) => { + getCssMap().query(element).selectors.forEach((matchedSelector: NeedToTyped) => { if (!isStyleMatched(matchedSelector, element)) return; - matchedSelector.ruleSet.declarations.forEach((cssStyle) => { + matchedSelector.ruleSet.declarations.forEach((cssStyle: NeedToTyped) => { style[cssStyle.property] = cssStyle.value; }); }); @@ -358,7 +366,7 @@ function getElemCss(element) { * @param targetNode * @returns attributes|{} */ -function getTargetNodeAttributes(targetNode) { +function getTargetNodeAttributes(targetNode: NeedToTyped) { try { const targetNodeAttributes = deepCopy(targetNode.attributes); const classInfo = Array.from(targetNode.classList || []).join(' '); @@ -378,7 +386,7 @@ function getTargetNodeAttributes(targetNode) { } } -function processModalView(nativeNode) { +function processModalView(nativeNode: NeedToTyped) { if (nativeNode.props.__modalFirstChild__) { const nodeStyle = nativeNode.props.style; Object.keys(nodeStyle).some((styleKey) => { @@ -399,11 +407,11 @@ If you want to make dialog cover fullscreen, please use { flex: 1 } or set heigh * getEventNode - translate event info event node. * @param targetNode */ -function getEventNode(targetNode) { - let eventNode = undefined; +function getEventNode(targetNode: NeedToTyped) { + let eventNode: NeedToTyped = undefined; const eventsAttributes = targetNode.events; if (eventsAttributes) { - const eventList = []; + const eventList: NeedToTyped = []; Object.keys(eventsAttributes) .forEach((key) => { const { name, type, isCapture, listener } = eventsAttributes[key]; @@ -422,28 +430,10 @@ function getEventNode(targetNode) { return eventNode; } -/** - * getEventNode - translate to native node. - * @param targetNode - */ -function getNativeNode(rootViewId, targetNode, refInfo = {}, style) { - const nativeNode = { - id: targetNode.nodeId, - pId: (targetNode.parentNode && targetNode.parentNode.nodeId) || rootViewId, - name: targetNode.meta.component.name, - props: { - ...getNativeProps(targetNode), - style, - }, - tagName: targetNode.tagName, - }; - return [nativeNode, refInfo, { skipStyleDiff: true }]; -} - /** * Render Element to native */ -function renderToNative(rootViewId, targetNode, refInfo = {}) { +function renderToNative(rootViewId, targetNode, refInfo = {}, notUpdateStyle = false) { if (targetNode.meta.skipAddToDom) { return []; } @@ -451,18 +441,43 @@ function renderToNative(rootViewId, targetNode, refInfo = {}) { throw new Error(`Specific tag is not supported yet: ${targetNode.tagName}`); } - let style = getElemCss(targetNode); - style = { ...style, ...targetNode.style }; - getBeforeRenderToNative()(targetNode, style); - // use defaultNativeStyle later to avoid incorrect compute style from inherit node - // in beforeRenderToNative hook - if (targetNode.meta.component.defaultNativeStyle) { - style = { ...targetNode.meta.component.defaultNativeStyle, ...style }; + let style; + if (notUpdateStyle) { + // 不用更新 CSS,直接使用缓存 + style = getCacheNodeStyle(targetNode.nodeId); + } else { + // 重新计算 CSS 样式 + style = getElemCss(targetNode); + // 样式合并 + style = { ...style, ...targetNode.style }; + // CSS 预处理,该函数目前没被使用 + getBeforeRenderToNative()(); + + if (targetNode.parentNode) { + // 属性继承逻辑实现 + // 只继承 color 和 font属性 + const parentNodeStyle = getCacheNodeStyle(targetNode.parentNode.nodeId); + const styleAttributes = ['color', 'fontSize', 'fontWeight', 'fontFamily', 'fontStyle', 'textAlign', 'lineHeight']; + + styleAttributes.forEach((attribute) => { + if (!style[attribute] && parentNodeStyle[attribute]) { + style[attribute] = parentNodeStyle[attribute]; + } + }); + } + // use defaultNativeStyle later to avoid incorrect compute style from inherit node + // in beforeRenderToNative hook + if (targetNode.meta.component.defaultNativeStyle) { + style = { ...targetNode.meta.component.defaultNativeStyle, ...style }; + } + + // 缓存 CSS 结果 + setCacheNodeStyle(targetNode.nodeId, style); } // Translate to native node - const nativeNode = { + const nativeNode: NeedToTyped = { id: targetNode.nodeId, - pId: (targetNode.parentNode && targetNode.parentNode.nodeId) || rootViewId, + pId: (targetNode.parentNode?.nodeId) || rootViewId, name: targetNode.meta.component.name, props: { ...getNativeProps(targetNode), @@ -475,8 +490,8 @@ function renderToNative(rootViewId, targetNode, refInfo = {}) { parseViewComponent(targetNode, nativeNode, style); parseTextInputComponent(targetNode, style); // Convert to real native event - const eventNode = getEventNode(targetNode); - let printedNode = undefined; + const eventNode: NeedToTyped = getEventNode(targetNode); + let printedNode: NeedToTyped = undefined; // Add nativeNode attributes info for Element debugging if (isDev()) { // generate printedNode for debugging @@ -488,9 +503,9 @@ function renderToNative(rootViewId, targetNode, refInfo = {}) { }); } // Add nativeNode attributes info for debugging - nativeNode.props.attributes = getTargetNodeAttributes(targetNode); + (nativeNode.props as any).attributes = getTargetNodeAttributes(targetNode); Object.assign(printedNode = {}, nativeNode, refInfo); - printedNode.listeners = listenerProp; + (printedNode as any).listeners = listenerProp; } // convert to translatedNode const translatedNode = [nativeNode, refInfo]; @@ -505,11 +520,11 @@ function renderToNative(rootViewId, targetNode, refInfo = {}) { * @param {Object} refInfo - referenceNode information * @returns {[]} */ -function renderToNativeWithChildren(rootViewId, node, callback, refInfo = {}) { - const nativeLanguages = []; - const eventLanguages = []; - const printedLanguages = []; - node.traverseChildren((targetNode, refInfo) => { +function renderToNativeWithChildren(rootViewId: NeedToTyped, node: NeedToTyped, callback?: CallbackType, refInfo = {}) { + const nativeLanguages: NeedToTyped = []; + const eventLanguages: NeedToTyped = []; + const printedLanguages: NeedToTyped = []; + node.traverseChildren((targetNode: NeedToTyped, refInfo: NeedToTyped) => { const [nativeNode, eventNode, printedNode] = renderToNative(rootViewId, targetNode, refInfo); if (nativeNode) { nativeLanguages.push(nativeNode); @@ -527,7 +542,7 @@ function renderToNativeWithChildren(rootViewId, node, callback, refInfo = {}) { return [nativeLanguages, eventLanguages, printedLanguages]; } -function isLayout(node, rootView) { +function isLayout(node: NeedToTyped, rootView: NeedToTyped) { if (node.nodeId === ROOT_VIEW_ID) { return true; } @@ -543,7 +558,7 @@ function isLayout(node, rootView) { return node.id === rootView.slice(1 - rootView.length); } -function insertChild(parentNode, childNode, refInfo = {}) { +function insertChild(parentNode: ViewNode, childNode: ViewNode, refInfo = {}) { if (!parentNode || !childNode) { return; } @@ -563,7 +578,7 @@ function insertChild(parentNode, childNode, refInfo = {}) { const [nativeLanguages, eventLanguages, printedLanguages] = renderToNativeWithChildren( rootViewId, renderRootNodeCondition ? parentNode : childNode, - (node) => { + (node: ViewNode) => { if (!node.isMounted) { node.isMounted = true; } @@ -581,7 +596,7 @@ function insertChild(parentNode, childNode, refInfo = {}) { } } -function removeChild(parentNode, childNode) { +function removeChild(parentNode: NeedToTyped, childNode: NeedToTyped) { if (!childNode || childNode.meta.skipAddToDom) { return; } @@ -608,14 +623,14 @@ function removeChild(parentNode, childNode) { endBatch(app); } -function moveChild(parentNode, childNode, refInfo = {}) { - if (parentNode && parentNode.meta && isFunction(parentNode.meta.removeChild)) { +function moveChild(parentNode: NeedToTyped, childNode: NeedToTyped, refInfo = {}) { + if (parentNode?.meta && isFunction(parentNode.meta.removeChild)) { parentNode.meta.removeChild(parentNode, childNode); } if (!childNode || childNode.meta.skipAddToDom) { return; } - if (refInfo && refInfo.refId === childNode.nodeId) { + if (refInfo && (refInfo as any).refId === childNode.nodeId) { // ref节点与childNode节点相同, 属于无效操作, 这里先过滤 return; } @@ -641,7 +656,7 @@ function moveChild(parentNode, childNode, refInfo = {}) { endBatch(app); } -function updateEvent(parentNode) { +function updateEvent(parentNode: NeedToTyped) { if (!parentNode.isMounted) { return; } @@ -656,20 +671,13 @@ function updateEvent(parentNode) { endBatch(app); } -function updateChild(parentNode, notUpdateStyle = false) { +function updateChild(parentNode: NeedToTyped, notUpdateStyle = false) { if (!parentNode.isMounted) { return; } const app = getApp(); const { $options: { rootViewId } } = app; - let nativeNode; - let eventNode; - let printedNode; - if (notUpdateStyle) { - nativeNode = getNativeNode(rootViewId, parentNode); - } else { - [nativeNode, eventNode, printedNode] = renderToNative(rootViewId, parentNode); - } + const [nativeNode, eventNode, printedNode] = renderToNative(rootViewId, parentNode, {}, notUpdateStyle); if (nativeNode) { batchNodes.push({ type: NODE_OPERATION_TYPES.updateNode, @@ -681,7 +689,7 @@ function updateChild(parentNode, notUpdateStyle = false) { } } -function updateWithChildren(parentNode) { +function updateWithChildren(parentNode: NeedToTyped) { if (!parentNode.isMounted) { return; } diff --git a/driver/js/packages/hippy-vue/src/renderer/__tests__/before-render-to-native-hook.test.js b/driver/js/packages/hippy-vue/src/renderer/__tests__/before-render-to-native-hook.test.js deleted file mode 100644 index d69a986af35..00000000000 --- a/driver/js/packages/hippy-vue/src/renderer/__tests__/before-render-to-native-hook.test.js +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making - * Hippy available. - * - * Copyright (C) 2017-2022 THL A29 Limited, a Tencent company. - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Most nodes test is executed in node-ops.test.js -// here just test the lacked testing for ViewNode for coverage. - -import test, { before } from 'ava'; -import ViewNode from '../view-node'; -import ElementNode from '../element-node'; -import * as util from '../../util'; - -before(() => { - global.__GLOBAL__ = { - nodeId: 101, - }; -}); - -const logWhenFail = async (t, cb, err) => { - const tryTest = await t.try((t) => { - cb(t); - }); - - if (!tryTest.passed) { - t.log(err); - } - tryTest.commit(); -}; - -test('ElementNode API', async (t) => { - await logWhenFail(t, (t) => { - const node = new ElementNode('div'); - - t.true(util.isFunction(node.appendChild)); - t.true(util.isFunction(node.insertBefore)); - t.true(util.isFunction(node.moveChild)); - t.true(util.isFunction(node.removeChild)); - t.true(util.isFunction(node.getBoundingClientRect)); - - t.true(node.classList instanceof Set); - t.true(node.style instanceof Object); - t.true(node.attributes instanceof Object); - }, 'ElementNode APIs have breaking changes, please update const variable \'BEFORE_RENDER_TO_NATIVE_HOOK_VERSION\''); -}); - -test('ViewNode API', async (t) => { - await logWhenFail(t, (t) => { - const node = new ViewNode(); - - t.true(util.isFunction(node.appendChild)); - t.true(util.isFunction(node.insertBefore)); - t.true(util.isFunction(node.moveChild)); - t.true(util.isFunction(node.removeChild)); - - const childNode1 = new ViewNode(); - const childNode2 = new ViewNode(); - node.appendChild(childNode1); - node.appendChild(childNode2); - t.true(node.firstChild === childNode1); - t.true(node.lastChild === childNode2); - }, 'ViewNode APIs have breaking changes, please update const variable \'BEFORE_RENDER_TO_NATIVE_HOOK_VERSION\''); -}); diff --git a/driver/js/packages/hippy-vue/src/renderer/__tests__/document-node.test.js b/driver/js/packages/hippy-vue/src/renderer/__tests__/document-node.test.js deleted file mode 100644 index 1ccc13fabd3..00000000000 --- a/driver/js/packages/hippy-vue/src/renderer/__tests__/document-node.test.js +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making - * Hippy available. - * - * Copyright (C) 2017-2022 THL A29 Limited, a Tencent company. - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* eslint-disable no-underscore-dangle */ - -// Most nodes test is executed in node-ops.test.js -// here just test the lacked testing for DocumentNode for coverage. - -import test, { before } from 'ava'; -import DocumentNode from '../document-node'; - -before(() => { - global.__GLOBAL__ = { - nodeId: 101, - }; -}); - -test('Document.createElement test', (t) => { - const testNode1 = DocumentNode.createElement('div'); - t.is(testNode1.toString(), 'ElementNode(div)'); - const testNode2 = DocumentNode.createElement('input'); - t.is(testNode2.toString(), 'InputNode(input)'); - const testNode3 = DocumentNode.createElement('textarea'); - t.is(testNode3.toString(), 'InputNode(textarea)'); - const testNode4 = DocumentNode.createElement('ul'); - t.is(testNode4.toString(), 'ListNode(ul)'); - const testNode5 = DocumentNode.createElement('li'); - t.is(testNode5.toString(), 'ElementNode(li)'); -}); - -test('Document.createTextNode test', (t) => { - const testNode = DocumentNode.createTextNode('aaa'); - t.is(testNode.toString(), 'TextNode'); - t.is(testNode.text, 'aaa'); -}); - -test('Document.createComment test', (t) => { - const testNode = DocumentNode.createComment('aaa'); - t.is(testNode.toString(), 'CommentNode(comment)'); - t.is(testNode.text, 'aaa'); -}); - -test('Document.createEvent test', (t) => { - const testNode = DocumentNode.createEvent('addEvent'); - t.is(testNode.type, 'addEvent'); -}); diff --git a/driver/js/packages/hippy-vue/src/renderer/__tests__/element-node.test.js b/driver/js/packages/hippy-vue/src/renderer/__tests__/element-node.test.js deleted file mode 100644 index 726589c7aba..00000000000 --- a/driver/js/packages/hippy-vue/src/renderer/__tests__/element-node.test.js +++ /dev/null @@ -1,268 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making - * Hippy available. - * - * Copyright (C) 2017-2022 THL A29 Limited, a Tencent company. - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* eslint-disable no-underscore-dangle */ - -// Most nodes test is executed in node-ops.test.js -// here just test the lacked testing for ElementNode for coverage. - -import test, { before } from 'ava'; -import ElementNode from '../element-node'; -import DocumentNode from '../document-node'; -import ListNode from '../list-node'; - -before(() => { - global.__GLOBAL__ = { - nodeId: 101, - }; -}); - -test('Element.toString test', (t) => { - const testNode = new ElementNode('div'); - t.is(testNode.toString(), 'ElementNode(div)'); -}); - -test('Element.setAttribute("text") test', (t) => { - const testNode = new ElementNode('p'); - testNode.setAttribute('text', '123'); - t.is(testNode.getAttribute('text'), '123'); - testNode.setAttribute('value', 123); - t.is(testNode.getAttribute('value'), '123'); - t.throws(() => { - testNode.setAttribute('defaultValue', undefined); - }, TypeError); -}); - -test('text-input color attribute test', (t) => { - const caretColorNode = new ElementNode('div'); - caretColorNode.setAttribute('caretColor', '#abcdef'); - t.is(caretColorNode.attributes['caret-color'], 4289449455); - caretColorNode.setAttribute('caret-color', '#abc'); - t.is(caretColorNode.attributes['caret-color'], 4289379276); - caretColorNode.setAttribute('placeholderTextColor', '#abcdef'); - t.is(caretColorNode.attributes.placeholderTextColor, 4289449455); - caretColorNode.setAttribute('placeholder-text-color', '#abcdef'); - t.is(caretColorNode.attributes.placeholderTextColor, 4289449455); - caretColorNode.setAttribute('underlineColorAndroid', '#abc'); - t.is(caretColorNode.attributes.underlineColorAndroid, 4289379276); - caretColorNode.setAttribute('underline-color-android', '#abc'); - t.is(caretColorNode.attributes.underlineColorAndroid, 4289379276); -}); - -test('Element.setNativeProps test', (t) => { - const node = new ElementNode('p'); - node.setNativeProps({ abc: 'abc' }); - t.is(node.style.abc, undefined); - node.setNativeProps({ style: { abc: 'abc' } }); - t.is(node.style.abc, 'abc'); - node.setNativeProps({ style: { bcd: '123' } }); - t.is(node.style.bcd, 123); - node.setNativeProps({ style: { fontWeight: '100' } }); - t.is(node.style.fontWeight, '100'); - node.setNativeProps({ style: { fontWeight: 200 } }); - t.is(node.style.fontWeight, '200'); - node.setNativeProps({ style: { fontWeight: 'bold' } }); - t.is(node.style.fontWeight, 'bold'); - node.setNativeProps({ style: { width: '100px' } }); - t.is(node.style.width, 100); - node.setNativeProps({ style: { height: '100.201px' } }); - t.is(node.style.height, 100.201); - node.setNativeProps({ style: { cde: {} } }); - t.deepEqual(node.style.cde, {}); - node.setNativeProps({ style: { caretColor: '#abcdef' } }); - t.is(node.style['caret-color'], 4289449455); - node.setNativeProps({ style: { placeholderTextColor: '#abcdef' } }); - t.is(node.style.placeholderTextColor, 4289449455); - node.setNativeProps({ style: { underlineColorAndroid: '#abcdef' } }); - t.is(node.style.underlineColorAndroid, 4289449455); -}); - -test('Element.hasAttribute test', (t) => { - const testNode = new ElementNode('div'); - const key = 'test'; - t.false(testNode.hasAttribute(key)); - testNode.setAttribute(key, '123'); - t.true(testNode.hasAttribute(key)); -}); - -test('Element.removeAttribute test', (t) => { - const testNode = new ElementNode('div'); - const key = 'test'; - testNode.setAttribute(key, '123'); - t.is(testNode.getAttribute(key), '123'); - testNode.setAttribute(key, 123); - t.is(testNode.getAttribute(key), 123); - testNode.removeAttribute(key); - t.is(testNode.getAttribute(key), undefined); -}); - -test('Element.addEventListener and removeEventListener test', (t) => { - const node = new ElementNode('div'); - let called = false; - const callback = (event) => { - called = true; - return event; - }; - callback.called = false; - t.is(called, false); - t.is(node._emitter, null); - t.is(node.removeEventListener('click', callback), null); - node.addEventListener('click', callback); - t.true(!!node._emitter); - const event = DocumentNode.createEvent('click'); - node.dispatchEvent(event); - node.removeEventListener('click', callback); - t.true(!!node._emitter); -}); - -test('Element.dispatchEvent test', (t) => { - const parentNode = new ElementNode('div'); - const childNode = new ElementNode('div'); - const key = 'test'; - const event = DocumentNode.createEvent(key); - parentNode.appendChild(childNode); - const err = t.throws(() => { - parentNode.dispatchEvent(123); - }, Error); - t.is(err.message, 'dispatchEvent method only accept Event instance'); - childNode.dispatchEvent(event); -}); - -test('Element.setText test', (t) => { - const node = new ElementNode('p'); - node.setText('abc'); - t.is(node.getAttribute('text'), 'abc'); - node.setText(123); - t.is(node.getAttribute('text'), '123'); - node.setText({}); - t.is(node.getAttribute('text'), '[object Object]'); - node.setText(() => {}); - t.is(node.getAttribute('text'), '() => {}'); - node.setText(' '); - t.is(node.getAttribute('text'), ''); - node.setText('Â'); - t.is(node.getAttribute('text'), ' '); - node.setText('aÂÂÂb'); - t.is(node.getAttribute('text'), 'a b'); -}); - -test('Element.setStyle test', (t) => { - const node = new ElementNode('p'); - node.setStyle('abc', 'abc'); - t.is(node.style.abc, 'abc'); - node.setStyle('bcd', '123'); - t.is(node.style.bcd, 123); - node.setStyle('fontWeight', '100'); - t.is(node.style.fontWeight, '100'); - node.setStyle('fontWeight', 200); - t.is(node.style.fontWeight, '200'); - node.setStyle('fontWeight', 'bold'); - t.is(node.style.fontWeight, 'bold'); - node.setStyle('width', '100px'); - t.is(node.style.width, 100); - node.setStyle('height', '100.201px'); - t.is(node.style.height, 100.201); - node.setStyle('cde', {}); - t.deepEqual(node.style.cde, {}); - node.setStyle('caretColor', '#abcdef'); - t.is(node.style['caret-color'], 4289449455); - node.setStyle('caret-color', '#abcdef'); - t.is(node.style['caret-color'], 4289449455); - node.setStyle('placeholderTextColor', '#abcdef'); - t.is(node.style.placeholderTextColor, 4289449455); - node.setStyle('placeholder-text-color', '#abcdef'); - t.is(node.style.placeholderTextColor, 4289449455); - node.setStyle('underlineColorAndroid', '#abcdef'); - t.is(node.style.underlineColorAndroid, 4289449455); - node.setStyle('underline-color-android', '#abcdef'); - t.is(node.style.underlineColorAndroid, 4289449455); - node.setStyle('backgroundImage', 'linear-gradient(to top right, red, yellow, blue 10%)'); - t.deepEqual(node.style.linearGradient, { angle: 'totopright', colorStopList: [{ color: 4294901760 }, { color: 4294967040 }, { color: 4278190335, ratio: 0.1 }] }); - node.setStyle('backgroundImage', 'linear-gradient(90deg, red, 10%, blue 10%)'); - t.deepEqual(node.style.linearGradient, { angle: '90', colorStopList: [{ color: 4294901760 }, { color: 4278190335, ratio: 0.1 }] }); - node.setStyle('backgroundImage', 'linear-gradient(red, yellow 10%, blue 10%)'); - t.deepEqual(node.style.linearGradient, { angle: '180', colorStopList: [{ color: 4294901760 }, { color: 4294967040, ratio: 0.1 }, { color: 4278190335, ratio: 0.1 }] }); - node.setStyle('backgroundImage', 'linear-gradient(10.12341234deg, red, yellow 10%, blue 10%)'); - t.deepEqual(node.style.linearGradient, { angle: '10.12', colorStopList: [{ color: 4294901760 }, { color: 4294967040, ratio: 0.1 }, { color: 4278190335, ratio: 0.1 }] }); - node.setStyle('backgroundImage', 'linear-gradient(10.12341234deg, rgba(55, 11, 43, 0.5) 5%, rgb(55, 13, 43) 10%, rgba(55, 11, 43, 0.1) 23%)'); - t.deepEqual(node.style.linearGradient, { angle: '10.12', colorStopList: [{ ratio: 0.05, color: 2151090987 }, { ratio: 0.1, color: 4281797931 }, { ratio: 0.23, color: 439814955 }] }); - node.setStyle('textShadowRadius', 1); - t.deepEqual(node.style.textShadowRadius, 1); - node.setStyle('textShadowColor', '#abcdef'); - t.deepEqual(node.style.textShadowColor, 4289449455); - node.setStyle('textShadowOffsetX', 1); - node.setStyle('textShadowOffsetY', 2); - t.deepEqual(node.style.textShadowOffset, { width: 1, height: 2 }); - node.setStyle('textShadowOffsetX', 10); - t.deepEqual(node.style.textShadowOffset, { width: 10, height: 2 }); - node.setStyle('textShadowOffset', { x: 11, y: 8 }); - t.deepEqual(node.style.textShadowOffset, { width: 11, height: 8 }); -}); - -test('Element.setStyle with pre-processed style test', (t) => { - const node = new ElementNode('div'); - node.beforeLoadStyle = (decl) => { - const { property, value } = decl; - return { - property: property.slice(0, decl.property.length - 2), - value: value.slice(0, decl.value.length - 2), - }; - }; - node.setStyle('backgroundColor', 'white'); - node.setStyle('width', '100px'); - t.is(node.style.backgroundCol, 'whi'); - t.is(node.style.wid, 100); -}); - -test('Element.dispatchEvent with polyfill event', (t) => { - const node = new ListNode('ul'); - let called = false; - const callback = (event) => { - called = true; - return event; - }; - - t.is(called, false); - t.is(node._emitter, null); - t.is(node.removeEventListener('loadMore', callback), null); - node.addEventListener('loadMore', callback); - t.true(!!node._emitter); - let event = DocumentNode.createEvent('loadMore'); - node.dispatchEvent(event); - t.is(called, true); - node.removeEventListener('loadMore', callback); - t.true(!!node._emitter); - - called = false; - t.is(called, false); - node.addEventListener('endReached', callback); - event = DocumentNode.createEvent('endReached'); - node.dispatchEvent(event); - t.is(called, true); - node.removeEventListener('endReached', callback); -}); - -test('Element.setAttribute("nativeBackgroundAndroid") test', (t) => { - const nbaNode = new ElementNode('div'); - nbaNode.setAttribute('nativeBackgroundAndroid', { color: '#00000011' }); - t.is(nbaNode.attributes.nativeBackgroundAndroid.color, 285212672); - nbaNode.setAttribute('nativeBackgroundAndroid', { color: 'rgba(0,0,0,0.07)' }); - t.is(nbaNode.attributes.nativeBackgroundAndroid.color, 301989888); -}); diff --git a/driver/js/packages/hippy-vue/src/renderer/__tests__/list-node.test.js b/driver/js/packages/hippy-vue/src/renderer/__tests__/list-node.test.js deleted file mode 100644 index c874f0abfe6..00000000000 --- a/driver/js/packages/hippy-vue/src/renderer/__tests__/list-node.test.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making - * Hippy available. - * - * Copyright (C) 2017-2022 THL A29 Limited, a Tencent company. - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Most nodes test is executed in node-ops.test.js -// here just test the lacked testing for ViewNode for coverage. -/* eslint-disable no-underscore-dangle */ - -import test, { before } from 'ava'; -import ListNode from '../list-node'; - -before(() => { - global.__GLOBAL__ = { - nodeId: 101, - }; -}); - -test('ListNode.polyfillNativeEvents test', (t) => { - const listNode = new ListNode('ul'); - let called = false; - const callback = (event) => { - called = true; - return event; - }; - t.is(called, false); - t.is(listNode.removeEventListener('loadMore', callback), null); - listNode.addEventListener('loadMore', callback); - t.true(!!listNode._emitter); - t.not(listNode._emitter.getEventListeners().loadMore, undefined); - - called = false; - t.is(called, false); - listNode.removeEventListener('loadMore', callback); - listNode.addEventListener('endReached', callback); - t.true(!!listNode._emitter); - t.not(listNode._emitter.getEventListeners().endReached, undefined); -}); diff --git a/driver/js/packages/hippy-vue/src/renderer/__tests__/view-node.test.js b/driver/js/packages/hippy-vue/src/renderer/__tests__/view-node.test.js deleted file mode 100644 index dd5afdb4298..00000000000 --- a/driver/js/packages/hippy-vue/src/renderer/__tests__/view-node.test.js +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making - * Hippy available. - * - * Copyright (C) 2017-2022 THL A29 Limited, a Tencent company. - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Most nodes test is executed in node-ops.test.js -// here just test the lacked testing for ViewNode for coverage. - -import test, { before } from 'ava'; -import ViewNode from '../view-node'; - -before(() => { - global.__GLOBAL__ = { - nodeId: 101, - }; -}); - -test('firstChild test', (t) => { - const parentNode = new ViewNode(); - const childNode = new ViewNode(); - const childNode2 = new ViewNode(); - parentNode.appendChild(childNode); - parentNode.insertBefore(childNode2, childNode); - t.is(parentNode.firstChild, childNode2); -}); - -test('set isMounted test', (t) => { - const node = new ViewNode(); - t.is(node.isMounted, false); - node.isMounted = true; - t.is(node.isMounted, true); -}); - -test('append exist child test', (t) => { - const parentNode = new ViewNode(); - const childNode = new ViewNode(); - const childNode2 = new ViewNode(); - parentNode.appendChild(childNode); - parentNode.appendChild(childNode2); - parentNode.appendChild(childNode); - t.is(parentNode.lastChild, childNode); - parentNode.appendChild(childNode); - parentNode.appendChild(childNode); - t.is(parentNode.lastChild, childNode); - t.is(parentNode.childNodes.length, 3); -}); - -test('findChild test', (t) => { - const parentNode = new ViewNode(); - const childNode = new ViewNode(); - const childNode2 = new ViewNode(); - childNode2.metaTest = 123; - parentNode.appendChild(childNode); - childNode.appendChild(childNode2); - const targetNode = parentNode.findChild(node => node.metaTest === 123); - t.is(targetNode, childNode2); - const targetNode2 = parentNode.findChild(node => node.metaTest === 234); - t.is(targetNode2, null); -}); - -test('traverseChildren test', (t) => { - const parentNode = new ViewNode(); - const childNode = new ViewNode(); - const childNode2 = new ViewNode(); - childNode2.metaTest = 123; - parentNode.appendChild(childNode); - childNode.appendChild(childNode2); - parentNode.traverseChildren((node) => { - t.true([parentNode, childNode, childNode2].indexOf(node) > -1); - }); -}); diff --git a/driver/js/packages/hippy-vue/src/renderer/comment-node.js b/driver/js/packages/hippy-vue/src/renderer/comment-node.ts similarity index 87% rename from driver/js/packages/hippy-vue/src/renderer/comment-node.js rename to driver/js/packages/hippy-vue/src/renderer/comment-node.ts index becc8cbdf75..133e1a973d9 100644 --- a/driver/js/packages/hippy-vue/src/renderer/comment-node.js +++ b/driver/js/packages/hippy-vue/src/renderer/comment-node.ts @@ -20,11 +20,13 @@ /* eslint-disable no-underscore-dangle */ +import { Text } from '../native/components'; import ElementNode from './element-node'; -import { Text } from './native/components'; -class CommentNode extends ElementNode { - constructor(text) { +export class CommentNode extends ElementNode { + public text: string; + + constructor(text: string) { super('comment'); this.text = text; this._meta = { diff --git a/driver/js/packages/hippy-vue/src/renderer/document-node.js b/driver/js/packages/hippy-vue/src/renderer/document-node.ts similarity index 75% rename from driver/js/packages/hippy-vue/src/renderer/document-node.js rename to driver/js/packages/hippy-vue/src/renderer/document-node.ts index dd999637bdd..a194db172af 100644 --- a/driver/js/packages/hippy-vue/src/renderer/document-node.js +++ b/driver/js/packages/hippy-vue/src/renderer/document-node.ts @@ -18,30 +18,20 @@ * limitations under the License. */ +import { Event } from '../event'; import CommentNode from './comment-node'; import ElementNode from './element-node'; import ViewNode from './view-node'; import TextNode from './text-node'; import InputNode from './input-node'; import ListNode from './list-node'; -import { Event } from './native/event'; class DocumentNode extends ViewNode { - constructor() { - super(); - this.documentElement = new ElementNode('document'); - // make static methods accessible via this - this.createComment = this.constructor.createComment; - this.createElement = this.constructor.createElement; - this.createElementNS = this.constructor.createElementNS; - this.createTextNode = this.constructor.createTextNode; - } - - static createComment(text) { + static createComment(text: string) { return new CommentNode(text); } - static createElement(tagName) { + static createElement(tagName: string) { switch (tagName) { case 'input': case 'textarea': @@ -53,17 +43,22 @@ class DocumentNode extends ViewNode { } } - static createElementNS(namespace, tagName) { + static createElementNS(namespace: string, tagName: string) { return new ElementNode(`${namespace}:${tagName}`); } - static createTextNode(text) { + static createTextNode(text: string) { return new TextNode(text); } - static createEvent(eventName) { + static createEvent(eventName: string) { return new Event(eventName); } + public documentElement: ViewNode; + constructor() { + super(); + this.documentElement = new ElementNode('document'); + } } export default DocumentNode; diff --git a/driver/js/packages/hippy-vue/src/renderer/element-node.js b/driver/js/packages/hippy-vue/src/renderer/element-node.ts similarity index 76% rename from driver/js/packages/hippy-vue/src/renderer/element-node.js rename to driver/js/packages/hippy-vue/src/renderer/element-node.ts index a25e1f6f34d..0116c35faf1 100644 --- a/driver/js/packages/hippy-vue/src/renderer/element-node.js +++ b/driver/js/packages/hippy-vue/src/renderer/element-node.ts @@ -36,13 +36,16 @@ import { } from '../util'; import { EventMethod, EventHandlerType } from '../util/event'; import Native from '../runtime/native'; -import { updateChild, updateWithChildren, updateEvent } from './native'; -import { Event, EventDispatcher, EventEmitter } from './native/event'; -import { Text } from './native/components'; +import { updateChild, updateWithChildren, updateEvent } from '../native'; +import { Event, EventDispatcher, EventEmitter } from '../event'; +import { Text } from '../native/components'; +import { CallbackType, CommonMapParams, NativeNodeProps, NeedToTyped } from '../types/native'; import ViewNode from './view-node'; +import TextNode from './text-node'; + // linear-gradient direction description map -const LINEAR_GRADIENT_DIRECTION_MAP = { +const LINEAR_GRADIENT_DIRECTION_MAP: CommonMapParams = { totop: '0', totopright: 'totopright', toright: '90', @@ -64,7 +67,7 @@ const DEGREE_UNIT = { * @param {string} value * @param {string} unit */ -function convertToDegree(value, unit = DEGREE_UNIT.DEG) { +function convertToDegree(value: string, unit = DEGREE_UNIT.DEG) { const convertedNumValue = parseFloat(value); let result = value || ''; const [, decimals] = value.split('.'); @@ -89,7 +92,7 @@ function convertToDegree(value, unit = DEGREE_UNIT.DEG) { * parse gradient angle or direction * @param {string} value */ -function getLinearGradientAngle(value) { +function getLinearGradientAngle(value: string) { const processedValue = (value || '').replace(/\s*/g, '').toLowerCase(); const reg = /^([+-]?(?=(?\d+))\k\.?\d*)+(deg|turn|rad)|(to\w+)$/g; const valueList = reg.exec(processedValue); @@ -111,7 +114,7 @@ function getLinearGradientAngle(value) { * parse gradient color stop * @param {string} value */ -function getLinearGradientColorStop(value) { +function getLinearGradientColorStop(value: string) { const processedValue = (value || '').replace(/\s+/g, ' ').trim(); const [color, percentage] = processedValue.split(/\s+(?![^(]*?\))/); const percentageCheckReg = /^([+-]?\d+\.?\d*)%$/g; @@ -136,7 +139,7 @@ function getLinearGradientColorStop(value) { * @param {string|Object|number|boolean} value * @returns {(string|{})[]} */ -function parseBackgroundImage(property, value, style) { +function parseBackgroundImage(property: NeedToTyped, value: NeedToTyped, style: NeedToTyped) { // reset the backgroundImage and linear gradient property delete style[property]; removeLinearGradient(property, value, style); @@ -146,9 +149,9 @@ function parseBackgroundImage(property, value, style) { processedProperty = 'linearGradient'; const valueString = value.substring(value.indexOf('(') + 1, value.lastIndexOf(')')); const tokens = valueString.split(/,(?![^(]*?\))/); - const colorStopList = []; + const colorStopList: NeedToTyped = []; processedValue = {}; - tokens.forEach((value, index) => { + tokens.forEach((value: string, index: number) => { if (index === 0) { // the angle of linear-gradient parameter can be optional const angle = getLinearGradientAngle(value); @@ -182,13 +185,18 @@ function parseBackgroundImage(property, value, style) { * @param value * @param style */ -function removeLinearGradient(property, value, style) { +function removeLinearGradient(property: NeedToTyped, value: NeedToTyped, style: NeedToTyped) { if (property === 'backgroundImage' && style.linearGradient) { delete style.linearGradient; } } -const offsetMap = { +interface OffsetMapType { + textShadowOffsetX: string; + textShadowOffsetY: string; +} + +const offsetMap: OffsetMapType = { textShadowOffsetX: 'width', textShadowOffsetY: 'height', }; @@ -199,7 +207,7 @@ const offsetMap = { * @param style * @returns {(*|number)[]} */ -function parseTextShadowOffset(property, value = 0, style) { +function parseTextShadowOffset(property: NeedToTyped, value = 0, style: NeedToTyped) { style.textShadowOffset = style.textShadowOffset || {}; Object.assign(style.textShadowOffset, { [offsetMap[property]]: value, @@ -213,7 +221,7 @@ function parseTextShadowOffset(property, value = 0, style) { * @param value * @param style */ -function removeTextShadowOffset(property, value, style) { +function removeTextShadowOffset(property: NeedToTyped, value: NeedToTyped, style: NeedToTyped) { if ((property === 'textShadowOffsetX' || property === 'textShadowOffsetY') && style.textShadowOffset) { delete style.textShadowOffset[offsetMap[property]]; if (Object.keys(style.textShadowOffset).length === 0) { @@ -228,7 +236,7 @@ function removeTextShadowOffset(property, value, style) { * @param value * @param style */ -function removeStyle(property, value, style) { +function removeStyle(property: NeedToTyped, value: NeedToTyped, style: NeedToTyped) { if (value === undefined) { delete style[property]; removeLinearGradient(property, value, style); @@ -236,7 +244,7 @@ function removeStyle(property, value, style) { } } -function transverseEventNames(eventNames, callback) { +function transverseEventNames(eventNames: NeedToTyped, callback: CallbackType) { if (typeof eventNames !== 'string') return; const events = eventNames.split(','); for (let i = 0, l = events.length; i < l; i += 1) { @@ -245,8 +253,8 @@ function transverseEventNames(eventNames, callback) { } } -function createEventListener(nativeName, originalName) { - return (event) => { +function createEventListener(nativeName: NeedToTyped, originalName: NeedToTyped) { + return (event: NeedToTyped) => { const { id, currentId, params, eventPhase } = event; const dispatcherEvent = { id, @@ -260,8 +268,47 @@ function createEventListener(nativeName, originalName) { }; } -class ElementNode extends ViewNode { - constructor(tagName) { +interface OptionMapType { + notToNative?: boolean; + textUpdate?: boolean; + notUpdateStyle?: boolean; +} + +export class ElementNode extends ViewNode { + // id + public id = ''; + // style list, such as class="wrapper red" => ['wrapper', 'red'] + public classList: Set; + // attributes + public attributes: any; + // style + public style: NativeNodeProps; + // events map + public events: NativeNodeProps; + // element content for text element + public value?: string; + // additional processing of properties + public filterAttribute?: CallbackType; + // style preprocessor + public beforeLoadStyle: CallbackType; + // polyFill of native event + public polyfillNativeEvents?: ( + method: string, + eventNames: string, + callback: CallbackType, + options?: EventListenerOptions + ) => { + eventNames: string, + callback: CallbackType, + options?: EventListenerOptions + }; + // style scoped id for element + public scopeIdList: NeedToTyped = []; + private _emitter: EventEmitter | null; + // element tag name, such as div, ul, hi-swiper, etc. + private _tagName = ''; + + constructor(tagName: string) { super(); // Tag name this.tagName = tagName; @@ -283,19 +330,19 @@ class ElementNode extends ViewNode { this.beforeLoadStyle = getBeforeLoadStyle(); } - toString() { + public toString() { return `${this.constructor.name}(${this._tagName})`; } - set tagName(name) { + public set tagName(name) { this._tagName = normalizeElementName(name); } - get tagName() { + public get tagName() { return this._tagName; } - get meta() { + public get meta() { if (this._meta) { return this._meta; } @@ -303,19 +350,19 @@ class ElementNode extends ViewNode { return this._meta; } - get emitter() { + public get emitter() { return this._emitter; } - hasAttribute(key) { + public hasAttribute(key: string) { return !!this.attributes[key]; } - getAttribute(key) { + public getAttribute(key: string) { return this.attributes[key]; } - setAttribute(rawKey, rawValue, options = {}) { + public setAttribute(rawKey: string, rawValue: NeedToTyped, options: OptionMapType = {}) { try { let key = rawKey; let value = rawValue; @@ -330,7 +377,7 @@ class ElementNode extends ViewNode { } switch (key) { case 'class': { - const newClassList = new Set(value.split(' ').filter(x => x.trim())); + const newClassList = new Set(value.split(' ').filter((x: NeedToTyped) => x.trim()) as string); if (setsAreEqual(this.classList, newClassList)) { return; } @@ -356,7 +403,7 @@ class ElementNode extends ViewNode { try { value = value.toString(); } catch (err) { - warn(`Property ${key} must be string:${err.message}`); + warn(`Property ${key} must be string:${(err as Error).message}`); } } if (!options || !options.textUpdate) { @@ -414,11 +461,11 @@ class ElementNode extends ViewNode { } } - removeAttribute(key) { + public removeAttribute(key: string) { delete this.attributes[key]; } - setStyles(batchStyles) { + public setStyles(batchStyles: NeedToTyped) { if (!batchStyles || typeof batchStyles !== 'object' || Object.keys(batchStyles).length === 0) { return; } @@ -429,7 +476,7 @@ class ElementNode extends ViewNode { updateChild(this); } - setStyle(rawKey, rawValue, notToNative = false) { + public setStyle(rawKey: string, rawValue: NeedToTyped, notToNative = false) { // Preprocess the style let { value, @@ -498,7 +545,7 @@ class ElementNode extends ViewNode { /** * set native style props */ - setNativeProps(nativeProps) { + public setNativeProps(nativeProps: NeedToTyped) { if (nativeProps) { const { style } = nativeProps; this.setStyles(style); @@ -508,11 +555,11 @@ class ElementNode extends ViewNode { /** * repaint element with the latest style map, which maybe loaded from HMR chunk or dynamic chunk */ - repaintWithChildren() { + public repaintWithChildren() { updateWithChildren(this); } - setStyleScope(styleScopeId) { + public setStyleScope(styleScopeId: NeedToTyped) { if (typeof styleScopeId !== 'string') { styleScopeId = styleScopeId.toString(); } @@ -521,53 +568,57 @@ class ElementNode extends ViewNode { } } - get styleScopeId() { + public get styleScopeId() { return this.scopeIdList; } - appendChild(childNode) { - if (childNode && childNode.meta.symbol === Text) { + public isTextNode(childNode: ViewNode) { + return childNode?.meta.symbol === Text; + } + + public appendChild(childNode: ViewNode) { + if (childNode?.meta.symbol === Text && childNode instanceof TextNode) { this.setText(childNode.text, { notToNative: true }); } super.appendChild(childNode); } - insertBefore(childNode, referenceNode) { - if (childNode && childNode.meta.symbol === Text) { + public insertBefore(childNode: ViewNode, referenceNode: ViewNode) { + if (this.isTextNode(childNode) && childNode instanceof TextNode) { this.setText(childNode.text, { notToNative: true }); } super.insertBefore(childNode, referenceNode); } - moveChild(childNode, referenceNode) { - if (childNode && childNode.meta.symbol === Text) { + public moveChild(childNode: ViewNode, referenceNode: ViewNode) { + if (this.isTextNode(childNode) && childNode instanceof TextNode) { this.setText(childNode.text, { notToNative: true }); } super.moveChild(childNode, referenceNode); } - removeChild(childNode) { - if (childNode && childNode.meta.symbol === Text) { + public removeChild(childNode: ViewNode) { + if (this.isTextNode(childNode) && childNode instanceof TextNode) { this.setText('', { notToNative: true }); } super.removeChild(childNode); } - setText(text, options = {}) { + public setText(text: string, options: OptionMapType = {}) { // Hacking for textarea, use value props to instance text props if (this.tagName === 'textarea') { - return this.setAttribute('value', text, { notToNative: !!options.notToNative }); + return this.setAttribute('value', text, { notToNative: !!options.notToNative }); } return this.setAttribute('text', text, { notToNative: !!options.notToNative }); } - setListenerHandledType(key, type) { + public setListenerHandledType(key: string, type: string) { if (this.events[key]) { this.events[key].handledType = type; } } - isListenerHandled(key, type) { + public isListenerHandled(key: string, type: string) { if (this.events[key] && type !== this.events[key].handledType) { // if handledType not equals type params, this event needs updated // if handledType equals undefined, this event needs created @@ -577,18 +628,18 @@ class ElementNode extends ViewNode { return true; } - getNativeEventName(eventName) { + public getNativeEventName(eventName: string) { let nativeEventName = `on${capitalizeFirstLetter(eventName)}`; if (this.meta.component) { const { eventNamesMap } = this.meta.component; - if (eventNamesMap && eventNamesMap[eventName]) { + if (eventNamesMap?.[eventName]) { nativeEventName = eventNamesMap[eventName]; } } return nativeEventName; } - addEventListener(eventNames, callback, options) { + public addEventListener(eventNames: string, callback: CallbackType, options?: EventListenerOptions) { if (!this._emitter) { this._emitter = new EventEmitter(this); } @@ -608,7 +659,7 @@ class ElementNode extends ViewNode { )); } this._emitter.addEventListener(eventNames, callback, options); - transverseEventNames(eventNames, (eventName) => { + transverseEventNames(eventNames, (eventName: string) => { const nativeEventName = this.getNativeEventName(eventName); if (!this.events[nativeEventName]) { this.events[nativeEventName] = { @@ -624,7 +675,7 @@ class ElementNode extends ViewNode { updateEvent(this); } - removeEventListener(eventNames, callback, options) { + public removeEventListener(eventNames: string, callback: CallbackType, options?: EventListenerOptions) { if (!this._emitter) { return null; } @@ -637,7 +688,7 @@ class ElementNode extends ViewNode { )); } const observer = this._emitter.removeEventListener(eventNames, callback, options); - transverseEventNames(eventNames, (eventName) => { + transverseEventNames(eventNames, (eventName: string) => { const nativeEventName = this.getNativeEventName(eventName); if (this.events[nativeEventName]) { this.events[nativeEventName].type = EventHandlerType.REMOVE; @@ -647,7 +698,7 @@ class ElementNode extends ViewNode { return observer; } - dispatchEvent(eventInstance, targetNode, domEvent) { + public dispatchEvent(eventInstance: Event, targetNode: ElementNode, domEvent: HippyTypes.DOMEvent): void { if (!(eventInstance instanceof Event)) { throw new Error('dispatchEvent method only accept Event instance'); } @@ -659,7 +710,7 @@ class ElementNode extends ViewNode { if (!eventInstance.target) { eventInstance.target = targetNode || this; // IMPORTANT: It's important for vnode diff and directive trigger. - if (typeof eventInstance.value === 'string') { + if (typeof eventInstance.value === 'string' && eventInstance.target) { eventInstance.target.value = eventInstance.value; } } @@ -679,17 +730,23 @@ class ElementNode extends ViewNode { * * And if the element is out of visible area, result will be none. */ - getBoundingClientRect() { + public getBoundingClientRect() { return Native.measureInWindow(this); } /** * Scroll children to specific position. */ - scrollToPosition(x = 0, y = 0, duration = 1000) { + public scrollToPosition( + x: number | undefined = 0, + y: number | undefined = 0, + rawDuration: number | boolean = 1000, + ): void { if (typeof x !== 'number' || typeof y !== 'number') { return; } + let duration = rawDuration; + if (duration === false) { duration = 0; } @@ -699,22 +756,43 @@ class ElementNode extends ViewNode { /** * Native implementation for the Chrome/Firefox Element.scrollTop method */ - scrollTo(x, y, duration) { + public scrollTo( + x: + | number + | { + left: number; + top: number; + behavior: string; + duration: number | boolean; + }, + y?: number, + duration?: number | boolean, + ): void { let animationDuration = duration; if (typeof x === 'object' && x) { const { left, top, behavior = 'auto' } = x; ({ duration: animationDuration } = x); this.scrollToPosition(left, top, behavior === 'none' ? 0 : animationDuration); } else { - this.scrollToPosition(x, y, duration); + this.scrollToPosition(x as number, y, duration); } } - setPressed(pressed) { + /** + * Set pressed state + * @param pressed - whether to press + */ + public setPressed(pressed: boolean): void { Native.callUIFunction(this, 'setPressed', [pressed]); } - setHotspot(x, y) { + /** + * Set hot zone + * + * @param x - x coordinate + * @param y - y coordinate + */ + public setHotspot(x: number, y: number): void { Native.callUIFunction(this, 'setHotspot', [x, y]); } } diff --git a/driver/js/packages/hippy-vue/src/renderer/input-node.js b/driver/js/packages/hippy-vue/src/renderer/input-node.ts similarity index 80% rename from driver/js/packages/hippy-vue/src/renderer/input-node.js rename to driver/js/packages/hippy-vue/src/renderer/input-node.ts index c382d1d7624..3f7d6bddd8c 100644 --- a/driver/js/packages/hippy-vue/src/renderer/input-node.js +++ b/driver/js/packages/hippy-vue/src/renderer/input-node.ts @@ -19,23 +19,24 @@ */ import Native from '../runtime/native'; +import { NeedToTyped } from '../types/native'; import ElementNode from './element-node'; /** * Input and Textarea Element */ -class InputNode extends ElementNode { +export class InputNode extends ElementNode { /** * Get text input value */ - getValue() { - return new Promise(resolve => Native.callUIFunction(this, 'getValue', r => resolve(r.text))); + public getValue() { + return new Promise(resolve => Native.callUIFunction(this, 'getValue', (r: NeedToTyped) => resolve(r.text))); } /** * Set text input value */ - setValue(value) { + public setValue(value: NeedToTyped) { Native.callUIFunction(this, 'setValue', [value]); } @@ -43,28 +44,28 @@ class InputNode extends ElementNode { /** * Focus */ - focus() { + public focus() { Native.callUIFunction(this, 'focusTextInput', []); } /** * Blur */ - blur() { + public blur() { Native.callUIFunction(this, 'blurTextInput', []); } /** * Get text input focus status */ - isFocused() { - return new Promise(resolve => Native.callUIFunction(this, 'isFocused', r => resolve(r.value))); + public isFocused() { + return new Promise(resolve => Native.callUIFunction(this, 'isFocused', (r: NeedToTyped) => resolve(r.value))); } /** * Clear */ - clear() { + public clear() { Native.callUIFunction(this, 'clear', []); } @@ -72,7 +73,7 @@ class InputNode extends ElementNode { * Show input method selection dialog. * @deprecated */ - showInputMethod() { + public showInputMethod() { // noop } @@ -80,7 +81,7 @@ class InputNode extends ElementNode { * hideInputMethod * @deprecated */ - hideInputMethod() { + public hideInputMethod() { // noop } } diff --git a/driver/js/packages/hippy-vue/src/renderer/list-node.js b/driver/js/packages/hippy-vue/src/renderer/list-node.ts similarity index 90% rename from driver/js/packages/hippy-vue/src/renderer/list-node.js rename to driver/js/packages/hippy-vue/src/renderer/list-node.ts index 03702a924fd..11a58d79c28 100644 --- a/driver/js/packages/hippy-vue/src/renderer/list-node.js +++ b/driver/js/packages/hippy-vue/src/renderer/list-node.ts @@ -28,7 +28,7 @@ class ListNode extends ElementNode { /** * Scroll to child node with index */ - scrollToIndex(indexLeft = 0, indexTop = 0, needAnimation = true) { + public scrollToIndex(indexLeft = 0, indexTop = 0, needAnimation = true) { if (typeof indexLeft !== 'number' || typeof indexTop !== 'number') { return; } @@ -38,7 +38,7 @@ class ListNode extends ElementNode { /** * Scroll children to specific position. */ - scrollToPosition(posX = 0, posY = 0, needAnimation = true) { + public scrollToPosition(posX = 0, posY = 0, needAnimation = true) { if (typeof posX !== 'number' || typeof posY !== 'number') { return; } diff --git a/driver/js/packages/hippy-vue/src/renderer/native/__tests__/index.test.js b/driver/js/packages/hippy-vue/src/renderer/native/__tests__/index.test.js deleted file mode 100644 index 522d8525216..00000000000 --- a/driver/js/packages/hippy-vue/src/renderer/native/__tests__/index.test.js +++ /dev/null @@ -1,1187 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making - * Hippy available. - * - * Copyright (C) 2017-2022 THL A29 Limited, a Tencent company. - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import test, { before } from 'ava'; -import { registerBuiltinElements } from '../../../elements'; -import DocumentNode from '../../document-node'; -import { - renderToNative, - renderToNativeWithChildren, - insertChild, - removeChild, -} from '../index'; -import { setApp } from '../../../util'; -import { HIPPY_DEBUG_ADDRESS } from '../../../runtime/constants'; -import Native from '../../../runtime/native'; - -const ROOT_VIEW_ID = 10; - -before(() => { - registerBuiltinElements(); - global.__HIPPYNATIVEGLOBAL__ = { - Platform: { - }, - }; - global.Hippy.SceneBuilder = function SceneBuilder() { - this.create = () => {}; - this.update = () => {}; - this.delete = () => {}; - this.move = () => {}; - this.build = () => {}; - this.addEventListener = () => {}; - this.removeEventListener = () => {}; - }; - setApp({ - $options: { - rootViewId: 10, - rootView: '#root', - }, - $nextTick: (cb) => { - setTimeout(cb); - }, - }); -}); - -test('renderToNative simple test --debug mode', (t) => { - process.env.NODE_ENV = 'test'; - const node = DocumentNode.createElement('div'); - const [nativeNode, eventNode] = renderToNative(ROOT_VIEW_ID, node); - t.deepEqual(nativeNode, [{ - id: 1, - pId: ROOT_VIEW_ID, - name: 'View', - props: { - attributes: { - class: '', - id: '', - hippyNodeId: '1', - }, - style: {}, - }, - tagName: 'div', - }, {}]); - t.deepEqual(eventNode, { - id: 1, - eventList: [], - }); -}); - -test('renderToNative simple test --production mode', (t) => { - process.env.NODE_ENV = 'production'; - const node = DocumentNode.createElement('div'); - const [nativeNode] = renderToNative(ROOT_VIEW_ID, node); - t.deepEqual(nativeNode, [{ - id: 2, - pId: ROOT_VIEW_ID, - name: 'View', - props: { - style: {}, - }, - tagName: 'div', - }, {}]); -}); - -test('renderToNative test with children --debug mode', (t) => { - process.env.NODE_ENV = 'test'; - const parentNode = DocumentNode.createElement('div'); - const childNode1 = DocumentNode.createElement('div'); - const childNode2 = DocumentNode.createElement('p'); - const childNode3 = DocumentNode.createElement('span'); - const childNodeText = DocumentNode.createTextNode('Hello'); - childNode3.appendChild(childNodeText); - const childNode4 = DocumentNode.createElement('img'); - childNode4.setAttribute('src', 'https://hippyjs.org'); - const childNode5 = DocumentNode.createElement('input'); - childNode5.setAttribute('type', 'number'); - const childNode6 = DocumentNode.createElement('textarea'); - childNode6.setAttribute('rows', 10); - childNode6.setAttribute('type', 'url'); - childNode6.setAttribute('role', 'back button'); - childNode6.setAttribute('aria-label', 'back to home'); - childNode6.setAttribute('aria-disabled', false); - childNode6.setAttribute('aria-selected', true); - childNode6.setAttribute('aria-checked', false); - childNode6.setAttribute('aria-busy', true); - childNode6.setAttribute('aria-expanded', false); - childNode6.setAttribute('aria-valuemin', 2); - childNode6.setAttribute('aria-valuemax', 10); - childNode6.setAttribute('aria-valuenow', 7); - childNode6.setAttribute('aria-valuetext', 'high'); - parentNode.appendChild(childNode1); - parentNode.appendChild(childNode2); - parentNode.appendChild(childNode3); - parentNode.appendChild(childNode4); - parentNode.appendChild(childNode5); - parentNode.appendChild(childNode6); - const [nativeLanguages] = renderToNativeWithChildren(ROOT_VIEW_ID, parentNode); - t.true(Array.isArray(nativeLanguages)); - t.deepEqual(nativeLanguages, [ - [{ - id: 3, - pId: ROOT_VIEW_ID, - name: 'View', - props: { - attributes: { - class: '', - id: '', - hippyNodeId: '3', - }, - style: {}, - }, - tagName: 'div', - }, {}], - [{ - id: 4, - pId: 3, - name: 'View', - props: { - attributes: { - class: '', - id: '', - hippyNodeId: '4', - }, - style: {}, - }, - tagName: 'div', - }, {}], - [{ - id: 5, - pId: 3, - name: 'Text', - props: { - attributes: { - class: '', - id: '', - hippyNodeId: '5', - }, - text: '', - style: { - color: 4278190080, - }, - }, - tagName: 'p', - }, {}], - [{ - id: 6, - pId: 3, - name: 'Text', - props: { - attributes: { - class: '', - id: '', - hippyNodeId: '6', - }, - text: 'Hello', - style: { - color: 4278190080, - }, - }, - tagName: 'span', - }, {}], - [{ - id: 8, - pId: 3, - name: 'Image', - props: { - attributes: { - class: '', - id: '', - src: 'https://hippyjs.org', - hippyNodeId: '8', - }, - src: 'https://hippyjs.org', - style: { - backgroundColor: 0, - }, - }, - tagName: 'img', - }, {}], - [{ - id: 9, - pId: 3, - name: 'TextInput', - props: { - attributes: { - class: '', - id: '', - type: 'number', - hippyNodeId: '9', - }, - keyboardType: 'numeric', - multiline: false, - numberOfLines: 1, - underlineColorAndroid: 0, - style: { - color: 4278190080, - padding: 0, - }, - }, - tagName: 'input', - }, {}], - [{ - id: 11, - pId: 3, - name: 'TextInput', - props: { - attributes: { - class: '', - id: '', - hippyNodeId: '11', - rows: 10, - type: 'url', - 'aria-busy': true, - 'aria-checked': false, - 'aria-disabled': false, - 'aria-expanded': false, - 'aria-label': 'back to home', - 'aria-selected': true, - 'aria-valuemax': 10, - 'aria-valuemin': 2, - 'aria-valuenow': 7, - 'aria-valuetext': 'high', - role: 'back button', - }, - accessibilityRole: 'back button', - accessibilityLabel: 'back to home', - accessibilityState: { - disabled: false, - selected: true, - checked: false, - busy: true, - expanded: false, - }, - accessibilityValue: { - now: 7, - min: 2, - max: 10, - text: 'high', - }, - multiline: true, - keyboardType: 'url', - numberOfLines: 10, - underlineColorAndroid: 0, - style: { - color: 4278190080, - padding: 0, - }, - }, - tagName: 'textarea', - }, {}], - ]); -}); - -test('renderToNative test with children --production mode', (t) => { - process.env.NODE_ENV = 'production'; - const parentNode = DocumentNode.createElement('div'); - const childNode1 = DocumentNode.createElement('div'); - const childNode2 = DocumentNode.createElement('p'); - const childNode3 = DocumentNode.createElement('span'); - const childNodeText = DocumentNode.createTextNode('Hello'); - childNode3.appendChild(childNodeText); - const childNode4 = DocumentNode.createElement('img'); - childNode4.setAttribute('src', 'https://hippyjs.org'); - const childNode5 = DocumentNode.createElement('input'); - childNode5.setAttribute('type', 'number'); - const childNode6 = DocumentNode.createElement('textarea'); - childNode6.setAttribute('rows', 10); - childNode6.setAttribute('type', 'url'); - parentNode.appendChild(childNode1); - parentNode.appendChild(childNode2); - parentNode.appendChild(childNode3); - parentNode.appendChild(childNode4); - parentNode.appendChild(childNode5); - parentNode.appendChild(childNode6); - const [nativeLanguages] = renderToNativeWithChildren(ROOT_VIEW_ID, parentNode); - t.true(Array.isArray(nativeLanguages)); - t.deepEqual(nativeLanguages, [ - [{ - id: 12, - pId: ROOT_VIEW_ID, - name: 'View', - props: { - style: {}, - }, - tagName: 'div', - }, {}], - [{ - id: 13, - pId: 12, - name: 'View', - props: { - style: {}, - }, - tagName: 'div', - }, {}], - [{ - id: 14, - pId: 12, - name: 'Text', - props: { - text: '', - style: { - color: 4278190080, - }, - }, - tagName: 'p', - }, {}], - [{ - id: 15, - pId: 12, - name: 'Text', - props: { - text: 'Hello', - style: { - color: 4278190080, - }, - }, - tagName: 'span', - }, {}], - [{ - id: 17, - pId: 12, - name: 'Image', - props: { - src: 'https://hippyjs.org', - style: { - backgroundColor: 0, - }, - }, - tagName: 'img', - }, {}], - [{ - id: 18, - pId: 12, - name: 'TextInput', - props: { - keyboardType: 'numeric', - multiline: false, - numberOfLines: 1, - underlineColorAndroid: 0, - style: { - color: 4278190080, - padding: 0, - }, - }, - tagName: 'input', - }, {}], - [{ - id: 19, - pId: 12, - name: 'TextInput', - props: { - multiline: true, - keyboardType: 'url', - numberOfLines: 10, - underlineColorAndroid: 0, - style: { - color: 4278190080, - padding: 0, - }, - }, - tagName: 'textarea', - }, {}], - ]); -}); - - -test('img attributeMaps test --debug mode', (t) => { - process.env.NODE_ENV = 'test'; - const node = DocumentNode.createElement('img'); - node.setAttribute('src', 'https://user-images.githubusercontent.com/12878546/148736102-7cd9525b-aceb-41c6-a905-d3156219ef16.png'); - node.setAttribute('alt', 'Test'); - const [nativeNode] = renderToNative(ROOT_VIEW_ID, node); - t.deepEqual(nativeNode, [{ - id: 21, - name: 'Image', - pId: ROOT_VIEW_ID, - props: { - attributes: { - alt: 'Test', - class: '', - id: '', - hippyNodeId: '21', - src: 'https://user-images.githubusercontent.com/12878546/148736102-7cd9525b-aceb-41c6-a905-d3156219ef16.png', - }, - alt: 'Test', - src: 'https://user-images.githubusercontent.com/12878546/148736102-7cd9525b-aceb-41c6-a905-d3156219ef16.png', - style: { - backgroundColor: 0, - }, - }, - tagName: 'img', - }, {}]); -}); - -test('img attributeMaps test --production mode', (t) => { - process.env.NODE_ENV = 'production'; - const node = DocumentNode.createElement('img'); - node.setAttribute('src', 'https://user-images.githubusercontent.com/12878546/148736102-7cd9525b-aceb-41c6-a905-d3156219ef16.png'); - node.setAttribute('alt', 'Test'); - const [nativeNode] = renderToNative(ROOT_VIEW_ID, node); - t.deepEqual(nativeNode, [{ - id: 22, - name: 'Image', - pId: ROOT_VIEW_ID, - props: { - alt: 'Test', - src: 'https://user-images.githubusercontent.com/12878546/148736102-7cd9525b-aceb-41c6-a905-d3156219ef16.png', - style: { - backgroundColor: 0, - }, - }, - tagName: 'img', - }, {}]); -}); - -test('span attributeMaps test --debug mode', (t) => { - process.env.NODE_ENV = 'test'; - const node = DocumentNode.createElement('span'); - node.setAttribute('text', 'Test'); - const [nativeNode] = renderToNative(ROOT_VIEW_ID, node); - t.deepEqual(nativeNode, [{ - id: 23, - name: 'Text', - pId: ROOT_VIEW_ID, - props: { - attributes: { - class: '', - id: '', - hippyNodeId: '23', - }, - text: 'Test', - style: { - color: 4278190080, - }, - }, - tagName: 'span', - }, {}]); -}); - -test('span attributeMaps test --production mode', (t) => { - process.env.NODE_ENV = 'production'; - const node = DocumentNode.createElement('span'); - node.setAttribute('text', 'Test'); - const [nativeNode] = renderToNative(ROOT_VIEW_ID, node); - t.deepEqual(nativeNode, [{ - id: 24, - name: 'Text', - pId: ROOT_VIEW_ID, - props: { - text: 'Test', - style: { - color: 4278190080, - }, - }, - tagName: 'span', - }, {}]); -}); - -test('a href attribute test --debug mode', (t) => { - process.env.NODE_ENV = 'test'; - const node = DocumentNode.createElement('a'); - node.setAttribute('text', 'Test'); - node.setAttribute('href', '/test'); - const [nativeNode] = renderToNative(ROOT_VIEW_ID, node); - t.deepEqual(nativeNode, [{ - id: 25, - name: 'Text', - pId: ROOT_VIEW_ID, - props: { - attributes: { - class: '', - href: '/test', - id: '', - hippyNodeId: '25', - }, - text: 'Test', - href: '/test', - style: { - color: 4278190318, - }, - }, - tagName: 'a', - }, {}]); -}); - -test('a href attribute test --production mode', (t) => { - process.env.NODE_ENV = 'production'; - const node = DocumentNode.createElement('a'); - node.setAttribute('text', 'Test'); - node.setAttribute('href', '/test'); - const [nativeNode] = renderToNative(ROOT_VIEW_ID, node); - t.deepEqual(nativeNode, [{ - id: 26, - name: 'Text', - pId: ROOT_VIEW_ID, - props: { - text: 'Test', - href: '/test', - style: { - color: 4278190318, - }, - }, - tagName: 'a', - }, {}]); -}); - -test('a href attribute with http prefix test --debug mode', (t) => { - process.env.NODE_ENV = 'test'; - const node = DocumentNode.createElement('a'); - node.setAttribute('text', 'Test'); - node.setAttribute('href', 'https://hippyjs.org'); - const [nativeNode] = renderToNative(ROOT_VIEW_ID, node); - t.deepEqual(nativeNode, [{ - id: 27, - name: 'Text', - pId: ROOT_VIEW_ID, - props: { - attributes: { - class: '', - href: 'https://hippyjs.org', - id: '', - hippyNodeId: '27', - }, - text: 'Test', - href: '', - style: { - color: 4278190318, - }, - }, - tagName: 'a', - }, {}]); -}); - -test('a href attribute with http prefix test --production mode', (t) => { - process.env.NODE_ENV = 'production'; - const node = DocumentNode.createElement('a'); - node.setAttribute('text', 'Test'); - node.setAttribute('href', 'https://hippyjs.org'); - const [nativeNode] = renderToNative(ROOT_VIEW_ID, node); - t.deepEqual(nativeNode, [{ - id: 28, - name: 'Text', - pId: ROOT_VIEW_ID, - props: { - text: 'Test', - href: '', - style: { - color: 4278190318, - }, - }, - tagName: 'a', - }, {}]); -}); - -test('div with overflow-X scroll test --debug mode', (t) => { - process.env.NODE_ENV = 'test'; - Native.Localization = { direction: 0 }; - const node = DocumentNode.createElement('div'); - node.setStyle('overflowX', 'scroll'); - const [nativeNode] = renderToNative(ROOT_VIEW_ID, node); - t.deepEqual(nativeNode, [{ - id: 29, - name: 'ScrollView', - pId: ROOT_VIEW_ID, - props: { - attributes: { - class: '', - id: '', - hippyNodeId: '29', - }, - horizontal: true, - style: { - flexDirection: 'row', - overflowX: 'scroll', - }, - }, - tagName: 'div', - }, {}]); - Native.Localization = { direction: 1 }; - const [nativeNode2] = renderToNative(ROOT_VIEW_ID, node); - t.deepEqual(nativeNode2, [{ - id: 29, - name: 'ScrollView', - pId: ROOT_VIEW_ID, - props: { - attributes: { - class: '', - id: '', - hippyNodeId: '29', - }, - horizontal: true, - style: { - flexDirection: 'row-reverse', - overflowX: 'scroll', - }, - }, - tagName: 'div', - }, {}]); -}); - -test('div with overflow-X scroll test --production mode', (t) => { - process.env.NODE_ENV = 'production'; - Native.Localization = { direction: 0 }; - const node = DocumentNode.createElement('div'); - node.setStyle('overflowX', 'scroll'); - const [nativeNode] = renderToNative(ROOT_VIEW_ID, node); - t.deepEqual(nativeNode, [{ - id: 31, - name: 'ScrollView', - pId: ROOT_VIEW_ID, - props: { - horizontal: true, - style: { - flexDirection: 'row', - overflowX: 'scroll', - }, - }, - tagName: 'div', - }, {}]); - Native.Localization = { direction: 1 }; - const [nativeNode2] = renderToNative(ROOT_VIEW_ID, node); - t.deepEqual(nativeNode2, [{ - id: 31, - name: 'ScrollView', - pId: ROOT_VIEW_ID, - props: { - horizontal: true, - style: { - flexDirection: 'row-reverse', - overflowX: 'scroll', - }, - }, - tagName: 'div', - }, {}]); -}); - -test('div with overflowY scroll test --debug mode', (t) => { - process.env.NODE_ENV = 'test'; - const node = DocumentNode.createElement('div'); - node.setStyle('overflowY', 'scroll'); - const [nativeNode] = renderToNative(ROOT_VIEW_ID, node); - t.deepEqual(nativeNode, [{ - id: 32, - name: 'ScrollView', - pId: ROOT_VIEW_ID, - props: { - attributes: { - class: '', - id: '', - hippyNodeId: '32', - }, - style: { - overflowY: 'scroll', - }, - }, - tagName: 'div', - }, {}]); -}); - -test('div with overflowY scroll test --production mode', (t) => { - process.env.NODE_ENV = 'production'; - const node = DocumentNode.createElement('div'); - node.setStyle('overflowY', 'scroll'); - const [nativeNode] = renderToNative(ROOT_VIEW_ID, node); - t.deepEqual(nativeNode, [{ - id: 33, - name: 'ScrollView', - pId: ROOT_VIEW_ID, - props: { - style: { - overflowY: 'scroll', - }, - }, - tagName: 'div', - }, {}]); -}); - -test('div with overflowX and overflowY scroll test --debug mode', (t) => { - process.env.NODE_ENV = 'test'; - const node = DocumentNode.createElement('div'); - node.setStyle('overflowX', 'scroll'); - node.setStyle('overflowY', 'scroll'); - const [nativeNode] = renderToNative(ROOT_VIEW_ID, node); - t.deepEqual(nativeNode, [{ - id: 34, - name: 'ScrollView', - pId: ROOT_VIEW_ID, - props: { - attributes: { - class: '', - id: '', - hippyNodeId: '34', - }, - style: { - overflowX: 'scroll', - overflowY: 'scroll', - }, - }, - tagName: 'div', - }, {}]); -}); - -test('div with overflowX and overflowY scroll test --production mode', (t) => { - process.env.NODE_ENV = 'production'; - const node = DocumentNode.createElement('div'); - node.setStyle('overflowX', 'scroll'); - node.setStyle('overflowY', 'scroll'); - const [nativeNode] = renderToNative(ROOT_VIEW_ID, node); - t.deepEqual(nativeNode, [{ - id: 35, - name: 'ScrollView', - pId: ROOT_VIEW_ID, - props: { - style: { - overflowX: 'scroll', - overflowY: 'scroll', - }, - }, - tagName: 'div', - }, {}]); -}); - -test('div with child node and overflowX scroll test --debug mode', (t) => { - process.env.NODE_ENV = 'test'; - const node = DocumentNode.createElement('div'); - const childNode = DocumentNode.createElement('div'); - node.setStyle('overflowY', 'scroll'); - node.appendChild(childNode); - const [nativeLanguages] = renderToNativeWithChildren(ROOT_VIEW_ID, node); - t.deepEqual(nativeLanguages, [ - [{ - id: 36, - name: 'ScrollView', - pId: ROOT_VIEW_ID, - props: { - attributes: { - class: '', - id: '', - hippyNodeId: '36', - }, - style: { - overflowY: 'scroll', - }, - }, - tagName: 'div', - }, {}], - [{ - id: 37, - name: 'View', - pId: 36, - props: { - attributes: { - class: '', - id: '', - hippyNodeId: '37', - }, - style: { - collapsable: false, - }, - }, - tagName: 'div', - }, {}], - ]); -}); - -test('div with child node and overflowX scroll test --production mode', (t) => { - process.env.NODE_ENV = 'production'; - const node = DocumentNode.createElement('div'); - const childNode = DocumentNode.createElement('div'); - node.setStyle('overflowY', 'scroll'); - node.appendChild(childNode); - const [nativeLanguages] = renderToNativeWithChildren(ROOT_VIEW_ID, node); - t.deepEqual(nativeLanguages, [ - [{ - id: 38, - name: 'ScrollView', - pId: ROOT_VIEW_ID, - props: { - style: { - overflowY: 'scroll', - }, - }, - tagName: 'div', - }, {}], - [{ - id: 39, - name: 'View', - pId: 38, - props: { - style: { - collapsable: false, - }, - }, - tagName: 'div', - }, {}], - ]); -}); - -test('insertChild test', (t) => { - t.is(insertChild(), undefined); - const parentNode = DocumentNode.createElement('div'); - const pNode = DocumentNode.createElement('p'); - const textNode = DocumentNode.createTextNode('Hello'); - parentNode.nodeId = 3; - parentNode.appendChild(pNode); - pNode.appendChild(textNode); - insertChild(parentNode, pNode); - insertChild(pNode, textNode); -}); - -test('removeChild test', (t) => { - t.is(removeChild(), undefined); - const pNode = DocumentNode.createElement('p'); - const textNode = DocumentNode.createTextNode('Hello'); - pNode.appendChild(textNode); - t.is(removeChild(pNode, textNode), undefined); - const parentNode = DocumentNode.createElement('div'); - parentNode.appendChild(pNode); - t.is(removeChild(parentNode, pNode), undefined); -}); - -test('text element with number text test', (t) => { - const parentNode = DocumentNode.createElement('div'); - parentNode.setText(0); - parentNode.setAttribute('test', '123'); - t.is(parentNode.getAttribute('text'), '0'); - t.is(parentNode.getAttribute('test'), '123'); - parentNode.setAttribute('test', 123); - t.is(parentNode.getAttribute('test'), 123); - // debug mode - process.env.NODE_ENV = 'test'; - t.throws(() => { - parentNode.setText(null); - }, TypeError); -}); - -test('Image.setStyle(background-color) test --debug mode', (t) => { - process.env.NODE_ENV = 'test'; - const imgWithoutBg = DocumentNode.createElement('img'); - const [withoutBg] = renderToNative(ROOT_VIEW_ID, imgWithoutBg); - t.deepEqual(withoutBg, [{ - id: 48, - pId: 10, - name: 'Image', - props: { - attributes: { - class: '', - id: '', - hippyNodeId: '48', - }, - style: { - backgroundColor: 0, - }, - }, - tagName: 'img', - }, {}]); - const imgWithBg = DocumentNode.createElement('img'); - imgWithBg.setStyle('backgroundColor', '#abcdef'); - const [withBg] = renderToNative(ROOT_VIEW_ID, imgWithBg); - t.deepEqual(withBg, [{ - id: 49, - pId: 10, - name: 'Image', - props: { - attributes: { - class: '', - id: '', - hippyNodeId: '49', - }, - style: { - backgroundColor: 4289449455, - }, - }, - tagName: 'img', - }, {}]); -}); - -test('Image.setStyle(background-color) test --production mode', (t) => { - process.env.NODE_ENV = 'production'; - const imgWithoutBg = DocumentNode.createElement('img'); - const [withoutBg] = renderToNative(ROOT_VIEW_ID, imgWithoutBg); - t.deepEqual(withoutBg, [{ - id: 51, - pId: 10, - name: 'Image', - props: { - style: { - backgroundColor: 0, - }, - }, - tagName: 'img', - }, {}]); - const imgWithBg = DocumentNode.createElement('img'); - imgWithBg.setStyle('backgroundColor', '#abcdef'); - const [withBg] = renderToNative(ROOT_VIEW_ID, imgWithBg); - t.deepEqual(withBg, [{ - id: 52, - pId: 10, - name: 'Image', - props: { - style: { - backgroundColor: 4289449455, - }, - }, - tagName: 'img', - }, {}]); -}); - -test('img with accessibility test --debug mode', (t) => { - process.env.NODE_ENV = 'test'; - const node = DocumentNode.createElement('img'); - node.setAttribute('role', 'back button'); - node.setAttribute('aria-label', 'back to home'); - node.setAttribute('aria-disabled', false); - node.setAttribute('aria-selected', true); - node.setAttribute('aria-checked', false); - node.setAttribute('aria-busy', true); - node.setAttribute('aria-expanded', false); - node.setAttribute('aria-valuemin', 2); - node.setAttribute('aria-valuemax', 10); - node.setAttribute('aria-valuenow', 7); - node.setAttribute('aria-valuetext', 'high'); - const [nativeNode] = renderToNative(ROOT_VIEW_ID, node); - t.deepEqual(nativeNode, [{ - id: 53, - name: 'Image', - pId: 10, - props: { - attributes: { - class: '', - id: '', - hippyNodeId: '53', - 'aria-busy': true, - 'aria-checked': false, - 'aria-disabled': false, - 'aria-expanded': false, - 'aria-label': 'back to home', - 'aria-selected': true, - 'aria-valuemax': 10, - 'aria-valuemin': 2, - 'aria-valuenow': 7, - 'aria-valuetext': 'high', - role: 'back button', - }, - accessibilityRole: 'back button', - accessibilityLabel: 'back to home', - accessibilityState: { - disabled: false, - selected: true, - checked: false, - busy: true, - expanded: false, - }, - accessibilityValue: { - now: 7, - min: 2, - max: 10, - text: 'high', - }, - style: { - backgroundColor: 0, - }, - }, - tagName: 'img', - }, {}]); -}); - -test('img with accessibility test --production mode', (t) => { - process.env.NODE_ENV = 'production'; - const node = DocumentNode.createElement('img'); - node.setAttribute('role', 'back button'); - node.setAttribute('aria-label', 'back to home'); - node.setAttribute('aria-disabled', false); - node.setAttribute('aria-selected', true); - node.setAttribute('aria-checked', false); - node.setAttribute('aria-busy', true); - node.setAttribute('aria-expanded', false); - node.setAttribute('aria-valuemin', 2); - node.setAttribute('aria-valuemax', 10); - node.setAttribute('aria-valuenow', 7); - node.setAttribute('aria-valuetext', 'high'); - const [nativeNode] = renderToNative(ROOT_VIEW_ID, node); - t.deepEqual(nativeNode, [{ - id: 54, - name: 'Image', - pId: 10, - props: { - accessibilityRole: 'back button', - accessibilityLabel: 'back to home', - accessibilityState: { - disabled: false, - selected: true, - checked: false, - busy: true, - expanded: false, - }, - accessibilityValue: { - now: 7, - min: 2, - max: 10, - text: 'high', - }, - style: { - backgroundColor: 0, - }, - }, - tagName: 'img', - }, {}]); -}); - -test('div with backgroundImage local path test --debug mode', (t) => { - process.env.NODE_ENV = 'test'; - const node = DocumentNode.createElement('div'); - const originalPath = 'assets/DefaultSource.png'; - node.setStyle('backgroundImage', originalPath); - const [nativeNode] = renderToNative(ROOT_VIEW_ID, node); - t.deepEqual(nativeNode, [{ - id: 55, - name: 'View', - pId: ROOT_VIEW_ID, - props: { - attributes: { - class: '', - id: '', - hippyNodeId: '55', - }, - style: { - backgroundImage: `${HIPPY_DEBUG_ADDRESS}${originalPath}`, - }, - }, - tagName: 'div', - }, {}]); -}); - -test('div with backgroundImage local path test --production mode', (t) => { - process.env.NODE_ENV = 'production'; - const node = DocumentNode.createElement('div'); - const originalPath = 'assets/DefaultSource.png'; - node.setStyle('backgroundImage', originalPath); - const [nativeNode] = renderToNative(ROOT_VIEW_ID, node); - t.deepEqual(nativeNode, [{ - id: 56, - name: 'View', - pId: ROOT_VIEW_ID, - props: { - style: { - backgroundImage: `hpfile://./${originalPath}`, - }, - }, - tagName: 'div', - }, {}]); -}); - -test('div with accessibility test --debug mode', (t) => { - process.env.NODE_ENV = 'test'; - const node = DocumentNode.createElement('div'); - node.setAttribute('role', 'back button'); - node.setAttribute('aria-label', 'back to home'); - node.setAttribute('aria-disabled', false); - node.setAttribute('aria-selected', true); - node.setAttribute('aria-checked', false); - node.setAttribute('aria-busy', true); - node.setAttribute('aria-expanded', false); - node.setAttribute('aria-valuemin', 2); - node.setAttribute('aria-valuemax', 10); - node.setAttribute('aria-valuenow', 7); - node.setAttribute('aria-valuetext', 'high'); - const [nativeNode] = renderToNative(ROOT_VIEW_ID, node); - t.deepEqual(nativeNode, [{ - id: 57, - name: 'View', - pId: ROOT_VIEW_ID, - props: { - attributes: { - class: '', - id: '', - hippyNodeId: '57', - 'aria-busy': true, - 'aria-checked': false, - 'aria-disabled': false, - 'aria-expanded': false, - 'aria-selected': true, - 'aria-label': 'back to home', - 'aria-valuemax': 10, - 'aria-valuemin': 2, - 'aria-valuenow': 7, - 'aria-valuetext': 'high', - role: 'back button', - }, - accessibilityRole: 'back button', - accessibilityLabel: 'back to home', - accessibilityState: { - disabled: false, - selected: true, - checked: false, - busy: true, - expanded: false, - }, - accessibilityValue: { - now: 7, - min: 2, - max: 10, - text: 'high', - }, - style: {}, - }, - tagName: 'div', - }, {}]); -}); - -test('div with accessibility test --production mode', (t) => { - process.env.NODE_ENV = 'production'; - const node = DocumentNode.createElement('div'); - node.setAttribute('role', 'back button'); - node.setAttribute('aria-label', 'back to home'); - node.setAttribute('aria-disabled', false); - node.setAttribute('aria-selected', true); - node.setAttribute('aria-checked', false); - node.setAttribute('aria-busy', true); - node.setAttribute('aria-expanded', false); - node.setAttribute('aria-valuemin', 2); - node.setAttribute('aria-valuemax', 10); - node.setAttribute('aria-valuenow', 7); - node.setAttribute('aria-valuetext', 'high'); - const [nativeNode] = renderToNative(ROOT_VIEW_ID, node); - t.deepEqual(nativeNode, [{ - id: 58, - name: 'View', - pId: ROOT_VIEW_ID, - props: { - accessibilityRole: 'back button', - accessibilityLabel: 'back to home', - accessibilityState: { - disabled: false, - selected: true, - checked: false, - busy: true, - expanded: false, - }, - accessibilityValue: { - now: 7, - min: 2, - max: 10, - text: 'high', - }, - style: {}, - }, - tagName: 'div', - }, {}]); -}); diff --git a/driver/js/packages/hippy-vue/src/renderer/native/event/__tests__/dispatcher.test.js b/driver/js/packages/hippy-vue/src/renderer/native/event/__tests__/dispatcher.test.js deleted file mode 100644 index ca1000fbbb6..00000000000 --- a/driver/js/packages/hippy-vue/src/renderer/native/event/__tests__/dispatcher.test.js +++ /dev/null @@ -1,248 +0,0 @@ -import test, { before } from 'ava'; -import { registerBuiltinElements } from '../../../../elements'; -import { EventDispatcher } from '../dispatcher'; -import { setApp, getApp } from '../../../../util'; -import { preCacheNode } from '../../../../util/node'; -import ElementNode from '../../../element-node'; - -let childNode; -let textareaNode; -let listview; -let iframe; -const dispatcherEvent = { - id: 0, - nativeName: '', - originalName: '', - currentId: 0, - params: {}, - eventPhase: 2, -}; -before(() => { - registerBuiltinElements(); - const rootNode = new ElementNode('div'); - preCacheNode(rootNode, rootNode.nodeId); - childNode = new ElementNode('div'); - preCacheNode(childNode, childNode.nodeId); - textareaNode = new ElementNode('textarea'); - preCacheNode(textareaNode, textareaNode.nodeId); - listview = new ElementNode('ul'); - preCacheNode(listview, listview.nodeId); - iframe = new ElementNode('iframe'); - preCacheNode(iframe, iframe.nodeId); - rootNode.appendChild(childNode); - childNode.appendChild(textareaNode); - childNode.appendChild(listview); - textareaNode.addEventListener('test', () => {}); - const app = { - executed: null, - $el: rootNode, - $emit(eventName, eventParams) { - app.executed = { - eventName, - eventParams, - }; - }, - }; - setApp(app); -}); - -test('receiveNativeEvent test', (t) => { - const app = getApp(); - t.is(EventDispatcher.receiveNativeEvent(), undefined); - EventDispatcher.receiveNativeEvent([ - 'test', - 'executed', - ]); - t.deepEqual(app.executed, { - eventName: 'test', - eventParams: 'executed', - }); -}); - -test('receiveComponentEvent with wrong node id test', (t) => { - t.is(EventDispatcher.receiveComponentEvent(), undefined); - const nativeEvent = Object.assign({}, dispatcherEvent, { - currentId: 100, - nativeName: 'onTest', - }); - EventDispatcher.receiveComponentEvent(nativeEvent); - t.pass(); -}); - -test('receiveComponentEvent with wrong event name test', (t) => { - t.is(EventDispatcher.receiveComponentEvent(), undefined); - const nativeEvent = Object.assign({}, dispatcherEvent, { - currentId: 3, - nativeName: 'test', - }); - EventDispatcher.receiveComponentEvent(nativeEvent); - t.pass(); -}); - -test('receiveComponentEvent test', (t) => { - t.is(EventDispatcher.receiveComponentEvent(), undefined); - const nativeEvent = Object.assign({}, dispatcherEvent, { - currentId: 3, - nativeName: 'onTouchDown', - }); - EventDispatcher.receiveComponentEvent(nativeEvent); - t.pass(); -}); - -test('receiveComponentEvent with wrong node id type test', (t) => { - EventDispatcher.receiveComponentEvent({ - currentId: 'str', - }); - t.pass(); -}); - -test('receiveComponentEvent with wrong event name type test', (t) => { - EventDispatcher.receiveComponentEvent({ - currentId: 'str', - }); - t.pass(); -}); - -test('receiveComponentEvent onChangeText test', (t) => { - t.is(EventDispatcher.receiveComponentEvent(), undefined); - const nativeEvent = Object.assign({}, dispatcherEvent, { - currentId: textareaNode.nodeId, - nativeName: 'onChangeText', - params: { - text: 'Hello world', - }, - }); - EventDispatcher.receiveComponentEvent(nativeEvent); - t.pass(); -}); - -test('receiveComponentEvent onSelectionChange test', (t) => { - t.is(EventDispatcher.receiveComponentEvent(), undefined); - const nativeEvent = Object.assign({}, dispatcherEvent, { - currentId: textareaNode.nodeId, - nativeName: 'onSelectionChange', - params: { - selection: { - start: 1, - end: 2, - }, - }, - }); - EventDispatcher.receiveComponentEvent(nativeEvent); - t.pass(); -}); - -test('receiveComponentEvent onKeyboardWillShow test', (t) => { - t.is(EventDispatcher.receiveComponentEvent(), undefined); - const nativeEvent = Object.assign({}, dispatcherEvent, { - currentId: textareaNode.nodeId, - nativeName: 'onKeyboardWillShow', - params: { - keyboardHeight: 123, - }, - }); - EventDispatcher.receiveComponentEvent(nativeEvent); - t.pass(); -}); - -test('receiveComponentEvent onContentSizeChange test', (t) => { - t.is(EventDispatcher.receiveComponentEvent(), undefined); - const nativeEvent = Object.assign({}, dispatcherEvent, { - currentId: textareaNode.nodeId, - nativeName: 'onContentSizeChange', - params: { - contentSize: { - width: 1, - height: 2, - }, - }, - }); - EventDispatcher.receiveComponentEvent(nativeEvent); - t.pass(); -}); - -test('receiveComponentEvent div.onScroll test', (t) => { - t.is(EventDispatcher.receiveComponentEvent(), undefined); - const nativeEvent = Object.assign({}, dispatcherEvent, { - currentId: childNode.nodeId, - nativeName: 'onScroll', - params: { - contentOffset: { - x: 1, - y: 2, - }, - }, - }); - EventDispatcher.receiveComponentEvent(nativeEvent); - t.pass(); -}); - -test('receiveComponentEvent ul.onScroll test', (t) => { - t.is(EventDispatcher.receiveComponentEvent(), undefined); - const nativeEvent = Object.assign({}, dispatcherEvent, { - currentId: listview.nodeId, - nativeName: 'onScroll', - params: { - contentOffset: { - x: 1, - y: 2, - }, - }, - }); - EventDispatcher.receiveComponentEvent(nativeEvent); - t.pass(); -}); - -test('receiveComponentEvent onTouch test', (t) => { - t.is(EventDispatcher.receiveComponentEvent(), undefined); - const nativeEvent1 = Object.assign({}, dispatcherEvent, { - currentId: childNode.nodeId, - nativeName: 'onTouchDown', - params: { - page_x: 1, - page_y: 2, - }, - }); - const nativeEvent2 = Object.assign({}, dispatcherEvent, { - currentId: childNode.nodeId, - nativeName: 'onTouchMove', - params: { - page_x: 1, - page_y: 2, - }, - }); - EventDispatcher.receiveComponentEvent(nativeEvent1); - EventDispatcher.receiveComponentEvent(nativeEvent2); - t.pass(); -}); - -test('receiveComponentEvent onLayout test', (t) => { - t.is(EventDispatcher.receiveComponentEvent(), undefined); - const nativeEvent = Object.assign({}, dispatcherEvent, { - currentId: childNode.nodeId, - nativeName: 'onLayout', - params: { - layout: { - x: 1, - y: 2, - width: 100, - height: 200, - }, - }, - }); - EventDispatcher.receiveComponentEvent(nativeEvent); - t.pass(); -}); - -test('receiveComponentEvent onLoad test', (t) => { - t.is(EventDispatcher.receiveComponentEvent(), undefined); - const nativeEvent = Object.assign({}, dispatcherEvent, { - currentId: iframe.nodeId, - nativeName: 'onLoad', - params: { - url: 'http://hippyjs.org', - }, - }); - EventDispatcher.receiveComponentEvent(nativeEvent); - t.pass(); -}); diff --git a/driver/js/packages/hippy-vue/src/renderer/native/event/__tests__/emitter.test.js b/driver/js/packages/hippy-vue/src/renderer/native/event/__tests__/emitter.test.js deleted file mode 100644 index 20ca97d2416..00000000000 --- a/driver/js/packages/hippy-vue/src/renderer/native/event/__tests__/emitter.test.js +++ /dev/null @@ -1,108 +0,0 @@ -import test from 'ava'; -import { EventEmitter } from '../emitter'; -import { Event } from '../event'; - -test('addEventListener and getEventListeners test', (t) => { - const emitter = new EventEmitter(); - - // Error handler test - const eventNameError = t.throws(() => { - emitter.addEventListener(123, 123); - }, TypeError); - t.is(eventNameError.message, 'Events name(s) must be string.'); - const eventCallbackError = t.throws(() => { - emitter.addEventListener('test', 123); - }, TypeError); - t.is(eventCallbackError.message, 'callback must be function.'); - - // Real working test - emitter.addEventListener('test', () => {}); - t.is(emitter.getEventListeners().test.length, 1); - emitter.addEventListener('test', () => {}); - t.is(emitter.getEventListeners().test.length, 2); -}); - -test('removeEventListener and getEventListeners test', (t) => { - const emitter = new EventEmitter(); - - // Error handler testing - const eventNameError = t.throws(() => { - emitter.removeEventListener(123, 123); - }, TypeError); - t.is(eventNameError.message, 'Events name(s) must be string.'); - const eventCallbackError = t.throws(() => { - emitter.removeEventListener('test', 123); - }, TypeError); - t.is(eventCallbackError.message, 'callback must be function.'); - - // Real work test - const callback1 = () => {}; - const callback2 = () => {}; - const callback3 = () => {}; - const callback4 = () => {}; - emitter.addEventListener('test', callback1); - emitter.addEventListener('test', callback2); - emitter.addEventListener('test', callback3); - emitter.addEventListener('test', callback4, { - once: true, - }); - t.is(emitter.getEventListeners().test.length, 4); - emitter.removeEventListener('test', callback2); - t.is(emitter.getEventListeners().test.length, 3); - emitter.removeEventListener('test', callback4, { - once: true, - }); - t.is(emitter.getEventListeners().test.length, 2); - emitter.removeEventListener('test'); - t.is(emitter.getEventListeners().test, undefined); -}); - -test('emit without option test', (t) => { - const emitter = new EventEmitter(); - // Real work test - let executed = false; - const callback = () => { - executed = true; - }; - const event = new Event('test'); - emitter.emit(event); - emitter.addEventListener('test', callback); - emitter.emit(event); - t.true(executed); -}); - -test('emit with thisArg option test', (t) => { - const emitter = new EventEmitter(); - // Real work test - let executed = false; - function thisArg() {} - function callback() { - executed = true; - t.is(this, thisArg); - } - const event = new Event('test'); - emitter.addEventListener('test', callback, { - thisArg, - }); - emitter.emit(event); - t.true(executed); -}); - -test('emit with once option test', (t) => { - const emitter = new EventEmitter(); - // Real work test - let executedTimes = 0; - function callback() { - executedTimes += 1; - } - const event = new Event('test'); - emitter.addEventListener('test', callback, { - once: true, - }); - // Execute test event callback, executedTimes should be 1 - emitter.emit(event); - t.is(executedTimes, 1); - // Execute test event callback again, executedTimes should be 1 because callback was removed. - emitter.emit(event); - t.is(executedTimes, 1); -}); diff --git a/driver/js/packages/hippy-vue/src/renderer/native/event/__tests__/event.test.js b/driver/js/packages/hippy-vue/src/renderer/native/event/__tests__/event.test.js deleted file mode 100644 index 01c8e1b2b59..00000000000 --- a/driver/js/packages/hippy-vue/src/renderer/native/event/__tests__/event.test.js +++ /dev/null @@ -1,31 +0,0 @@ -import test from 'ava'; -import { Event } from '../event'; - -test('stopPropagation test', (t) => { - const event = new Event('test'); - t.true(event.bubbles); - event.stopPropagation(); - t.false(event.bubbles); -}); - -test('preventDefault with cancelable test', (t) => { - const event = new Event('test'); - t.false(event.canceled); - event.preventDefault(); - t.true(event.canceled); -}); - -test('preventDefault without cancelable test', (t) => { - const event = new Event('test'); - event.initEvent('test', true, false); - t.false(event.canceled); - event.preventDefault(); - t.false(event.canceled); -}); - -test('initEvent test', (t) => { - const event = new Event('test'); - event.initEvent('test', false, false); - t.false(event.cancelable); - t.false(event.bubbles); -}); diff --git a/driver/js/packages/hippy-vue/src/renderer/native/style/__tests__/hmr-dispose.test.js b/driver/js/packages/hippy-vue/src/renderer/native/style/__tests__/hmr-dispose.test.js deleted file mode 100644 index c6b8007887c..00000000000 --- a/driver/js/packages/hippy-vue/src/renderer/native/style/__tests__/hmr-dispose.test.js +++ /dev/null @@ -1,56 +0,0 @@ -import test, { before } from 'ava'; -import { getCssMap } from '../../index'; -import { GLOBAL_DISPOSE_STYLE_NAME, GLOBAL_STYLE_NAME } from '../../../../runtime/constants'; -import ElementNode from '../../../element-node'; -import TEST_AST from './test-css-ast.json'; -import TEST_AST_CHUNK_2 from './test-css-ast-chunk-2.json'; - -const DISPOSE_CHUNK_HASH = 'chunk-2'; -const CHUNK_2_STYLE_PROPERTY_SUFFIX = 'chunk-2'; -let cssMap; - -before(() => { - global[GLOBAL_STYLE_NAME] = TEST_AST.concat(TEST_AST_CHUNK_2); - cssMap = getCssMap(); - global[GLOBAL_DISPOSE_STYLE_NAME] = [DISPOSE_CHUNK_HASH]; - getCssMap(); -}); - -test('SelectorsMap delete API test', (t) => { - const noChunk2RuleSet = cssMap.ruleSets.every(ruleSet => ruleSet.hash !== DISPOSE_CHUNK_HASH); - t.is(noChunk2RuleSet, true); - t.is(cssMap.ruleSets.length, TEST_AST.length); -}); - -test('IdSelector dispose test', (t) => { - const node = new ElementNode('div'); - node.setAttribute('id', 'id'); - t.is(isDisposeStyleExcluded(node), true); -}); - -test('ClassSelector dispose test', (t) => { - const node = new ElementNode('div'); - node.setAttribute('class', 'class'); - t.is(isDisposeStyleExcluded(node), true); -}); - -test('TypeSelector dispose test', (t) => { - const node = new ElementNode('tag'); - t.is(isDisposeStyleExcluded(node), true); -}); - -function isDisposeStyleExcluded(node) { - const matchedCSS = cssMap.query(node); - const style = getMatchStyle(matchedCSS); - return Object.keys(style).every(key => !(key.includes(CHUNK_2_STYLE_PROPERTY_SUFFIX))); -} - -function getMatchStyle(selectorsMatched) { - const style = {}; - selectorsMatched.selectors.forEach((matchedSelector) => { - matchedSelector.ruleSet.declarations.forEach((cssStyle) => { - style[cssStyle.property] = cssStyle.value; - }); - }); - return style; -} diff --git a/driver/js/packages/hippy-vue/src/renderer/native/style/__tests__/index.test.js b/driver/js/packages/hippy-vue/src/renderer/native/style/__tests__/index.test.js deleted file mode 100644 index 80b7464f140..00000000000 --- a/driver/js/packages/hippy-vue/src/renderer/native/style/__tests__/index.test.js +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making - * Hippy available. - * - * Copyright (C) 2017-2022 THL A29 Limited, a Tencent company. - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import test, { before } from 'ava'; -import { fromAstNodes, SelectorsMap } from '../index'; -import ElementNode from '../../../element-node'; -import TEST_AST from './test-css-ast.json'; - -let cssMap; - -before(() => { - const rules = fromAstNodes(TEST_AST); - cssMap = new SelectorsMap(rules); -}); - -test('IdSelector test', (t) => { - const node = new ElementNode('div'); - node.setAttribute('id', 'id'); - const matchedCSS = cssMap.query(node); - t.is(matchedCSS.selectors.length, 7); -}); - -test('ClassSelector test', (t) => { - const node = new ElementNode('div'); - node.setAttribute('class', 'class'); - const matchedCSS = cssMap.query(node); - t.is(matchedCSS.selectors.length, 5); -}); - -test('TypeSelector test', (t) => { - const node = new ElementNode('tag'); - node.setStyle('color', 'red'); - const matchedCSS = cssMap.query(node); - t.is(matchedCSS.selectors.length, 4); -}); - -test('AppendSelector test', (t) => { - const APPEND_AST = [{ - hash: 'chunk-1', - selectors: [ - '#id', - '*', - '#id[attr="test"]', - ], - declarations: [ - { - type: 'declaration', - property: 'IdSelector', - value: 'IdSelector', - }, - { - type: 'declaration', - property: 'UniversalSelector', - value: 'UniversalSelector', - }, - { - type: 'declaration', - property: 'AttributeSelector', - value: 'AttributeSelector', - }, - ], - }]; - const appendRules = fromAstNodes(APPEND_AST); - cssMap.append(appendRules); - const node = new ElementNode('div'); - node.setAttribute('id', 'id'); - const matchedCSS = cssMap.query(node); - t.is(matchedCSS.selectors.length, 10); -}); diff --git a/driver/js/packages/hippy-vue/src/renderer/native/style/__tests__/parser.test.js b/driver/js/packages/hippy-vue/src/renderer/native/style/__tests__/parser.test.js deleted file mode 100644 index 13bda5bff65..00000000000 --- a/driver/js/packages/hippy-vue/src/renderer/native/style/__tests__/parser.test.js +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making - * Hippy available. - * - * Copyright (C) 2017-2022 THL A29 Limited, a Tencent company. - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import test from 'ava'; -import parseSelector from '../parser'; - -test('Id selector parser', (t) => { - t.deepEqual(parseSelector('#test'), { - end: 5, - start: undefined, - value: [ - [ - [ - { - type: '#', - identifier: 'test', - }, - ], - undefined, // FIXME: Strange undefined - ], - ], - }); -}); - -test('Class selector parser', (t) => { - t.deepEqual(parseSelector('.row'), { - end: 4, - start: undefined, - value: [ - [ - [ - { - type: '.', - identifier: 'row', - }, - ], - undefined, // FIXME: Strange undefined - ], - ], - }); -}); - -test('Union combinator selector parser', (t) => { - t.deepEqual(parseSelector('.button-demo-1.is-pressing'), { - end: 26, - start: undefined, - value: [ - [ - [ - { - type: '.', - identifier: 'button-demo-1', - }, - { - type: '.', - identifier: 'is-pressing', - }, - ], - undefined, // FIXME: Strange undefined - ], - ], - }); -}); - -test('Space combinator selector parser', (t) => { - t.deepEqual(parseSelector('#demo-img .image'), { - end: 16, - start: undefined, - value: [ - [ - [ - { - type: '#', - identifier: 'demo-img', - }, - ], - ' ', // FIXME: Strange space - ], - [ - [ - { - type: '.', - identifier: 'image', - }, - ], - undefined, // FIXME: Strange undefined - ], - ], - }); -}); diff --git a/driver/js/packages/hippy-vue/src/renderer/native/style/__tests__/test-css-ast-chunk-2.json b/driver/js/packages/hippy-vue/src/renderer/native/style/__tests__/test-css-ast-chunk-2.json deleted file mode 100644 index 51857f8ab1f..00000000000 --- a/driver/js/packages/hippy-vue/src/renderer/native/style/__tests__/test-css-ast-chunk-2.json +++ /dev/null @@ -1,123 +0,0 @@ -[ - { - "hash": "chunk-2", - "selectors": [], - "declarations": [] - }, - { - "hash": "chunk-2", - "selectors":[ - "*" - ], - "declarations":[ - { - "type":"declaration", - "property":"UniversalSelector-chunk-2", - "value":"UniversalSelector-chunk-2" - } - ] - }, - { - "hash": "chunk-2", - "selectors":[ - "#id" - ], - "declarations":[ - { - "type":"declaration", - "property":"IdSelector-chunk-2", - "value":"IdSelector-chunk-2" - } - ] - }, - { - "hash": "chunk-2", - "selectors":[ - "tag" - ], - "declarations":[ - { - "type":"declaration", - "property":"TypeSelector-chunk-2", - "value":"TypeSelector-chunk-2" - } - ] - }, - { - "hash": "chunk-2", - "selectors":[ - ".class" - ], - "declarations":[ - { - "type":"declaration", - "property":"ClassSelector-chunk-2", - "value":"ClassSelector-chunk-2" - } - ] - }, - { - "hash": "chunk-2", - "selectors":[ - "#id:hover" - ], - "declarations":[ - { - "type":"declaration", - "property":"PseudoClassSelector-chunk-2", - "value":"PseudoClassSelector-chunk-2" - } - ] - }, - { - "hash": "chunk-2", - "selectors":[ - "#id[attr=\"test\"]" - ], - "declarations":[ - { - "type":"declaration", - "property":"AttributeSelector-chunk-2", - "value":"AttributeSelector-chunk-2" - } - ] - }, - { - "hash": "chunk-2", - "selectors":[ - "#id", - "*" - ], - "declarations":[ - { - "type":"declaration", - "property":"IdSelector-chunk-2", - "value":"IdSelector-chunk-2" - }, - { - "type":"declaration", - "property":"UniversalSelector-chunk-2", - "value":"UniversalSelector-chunk-2" - } - ] - }, - { - "hash": "chunk-2", - "selectors":[ - ".class", - "*" - ], - "declarations":[ - { - "type":"declaration", - "property":"ClassSelector-chunk-2", - "value":"ClassSelector-chunk-2" - }, - { - "type":"declaration", - "property":"UniversalSelector-chunk-2", - "value":"UniversalSelector-chunk-2" - } - ] - } -] diff --git a/driver/js/packages/hippy-vue/src/renderer/native/style/__tests__/test-css-ast.json b/driver/js/packages/hippy-vue/src/renderer/native/style/__tests__/test-css-ast.json deleted file mode 100644 index b7cd496f520..00000000000 --- a/driver/js/packages/hippy-vue/src/renderer/native/style/__tests__/test-css-ast.json +++ /dev/null @@ -1,123 +0,0 @@ -[ - { - "hash": "chunk-1", - "selectors": [], - "declarations": [] - }, - { - "hash": "chunk-1", - "selectors":[ - "*" - ], - "declarations":[ - { - "type":"declaration", - "property":"UniversalSelector", - "value":"UniversalSelector" - } - ] - }, - { - "hash": "chunk-1", - "selectors":[ - "#id" - ], - "declarations":[ - { - "type":"declaration", - "property":"IdSelector", - "value":"IdSelector" - } - ] - }, - { - "hash": "chunk-1", - "selectors":[ - "tag" - ], - "declarations":[ - { - "type":"declaration", - "property":"TypeSelector", - "value":"TypeSelector" - } - ] - }, - { - "hash": "chunk-1", - "selectors":[ - ".class" - ], - "declarations":[ - { - "type":"declaration", - "property":"ClassSelector", - "value":"ClassSelector" - } - ] - }, - { - "hash": "chunk-1", - "selectors":[ - "#id:hover" - ], - "declarations":[ - { - "type":"declaration", - "property":"PseudoClassSelector", - "value":"PseudoClassSelector" - } - ] - }, - { - "hash": "chunk-1", - "selectors":[ - "#id[attr=\"test\"]" - ], - "declarations":[ - { - "type":"declaration", - "property":"AttributeSelector", - "value":"AttributeSelector" - } - ] - }, - { - "hash": "chunk-1", - "selectors":[ - "#id", - "*" - ], - "declarations":[ - { - "type":"declaration", - "property":"IdSelector", - "value":"IdSelector" - }, - { - "type":"declaration", - "property":"UniversalSelector", - "value":"UniversalSelector" - } - ] - }, - { - "hash": "chunk-1", - "selectors":[ - ".class", - "*" - ], - "declarations":[ - { - "type":"declaration", - "property":"ClassSelector", - "value":"ClassSelector" - }, - { - "type":"declaration", - "property":"UniversalSelector", - "value":"UniversalSelector" - } - ] - } -] diff --git a/driver/js/packages/hippy-vue/src/renderer/native/style/css-selectors-match.js b/driver/js/packages/hippy-vue/src/renderer/native/style/css-selectors-match.js deleted file mode 100644 index 8efe13bf7b1..00000000000 --- a/driver/js/packages/hippy-vue/src/renderer/native/style/css-selectors-match.js +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making - * Hippy available. - * - * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* eslint-disable no-param-reassign */ - -/** - * Selector Map - */ -class SelectorsMatch { - constructor() { - this.changeMap = new Map(); - } - - addAttribute(node, attribute) { - const deps = this.properties(node); - if (!deps.attributes) { - deps.attributes = new Set(); - } - deps.attributes.add(attribute); - } - - addPseudoClass(node, pseudoClass) { - const deps = this.properties(node); - if (!deps.pseudoClasses) { - deps.pseudoClasses = new Set(); - } - deps.pseudoClasses.add(pseudoClass); - } - - properties(node) { - let set = this.changeMap.get(node); - if (!set) { - this.changeMap.set(node, set = {}); - } - return set; - } -} - -class SelectorsMap { - constructor(ruleSets) { - this.id = {}; - this.class = {}; - this.type = {}; - this.universal = []; - this.position = 0; - this.ruleSets = ruleSets; - ruleSets.forEach(rule => rule.lookupSort(this)); - } - - append(appendRules) { - this.ruleSets = this.ruleSets.concat(appendRules); - appendRules.forEach(rule => rule.lookupSort(this)); - } - - delete(hash) { - const removedRuleSets = []; - this.ruleSets = this.ruleSets.filter((rule) => { - if (rule.hash !== hash) return true; - removedRuleSets.push(rule); - return false; - }); - removedRuleSets.forEach(rule => rule.removeSort(this)); - } - - query(node) { - const { tagName, id, classList } = node; - const selectorClasses = [ - this.universal, - this.id[id], - this.type[tagName], - ]; - if (classList.size) { - classList.forEach(c => selectorClasses.push(this.class[c])); - } - const selectors = selectorClasses - .filter(arr => !!arr) - .reduce((cur, next) => cur.concat(next || []), []); - - const selectorsMatch = new SelectorsMatch(); - - 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, sel) { - this.addToMap(this.id, id, sel); - } - - sortByClass(cssClass, sel) { - this.addToMap(this.class, cssClass, sel); - } - - sortByType(cssType, sel) { - this.addToMap(this.type, cssType, sel); - } - - removeById(id, sel) { - this.removeFromMap(this.id, id, sel); - } - - removeByClass(cssClass, sel) { - this.removeFromMap(this.class, cssClass, sel); - } - - removeByType(cssType, sel) { - this.removeFromMap(this.type, cssType, sel); - } - - sortAsUniversal(sel) { - this.universal.push(this.makeDocSelector(sel)); - } - - removeAsUniversal(sel) { - const index = this.universal.findIndex(item => item.sel.ruleSet.hash === sel.ruleSet.hash); - if (index !== -1) { - this.universal.splice(index); - } - } - - addToMap(map, head, sel) { - this.position += 1; - const list = map[head]; - if (list) { - list.push(this.makeDocSelector(sel)); - } else { - map[head] = [this.makeDocSelector(sel)]; - } - } - - removeFromMap(map, head, sel) { - const list = map[head]; - const index = list.findIndex(item => item.sel.ruleSet.hash === sel.ruleSet.hash); - if (index !== -1) { - list.splice(index, 1); - } - } - - makeDocSelector(sel) { - this.position += 1; - return { sel, pos: this.position }; - } -} - -export { - SelectorsMap, -}; diff --git a/driver/js/packages/hippy-vue/src/renderer/native/style/css-selectors.js b/driver/js/packages/hippy-vue/src/renderer/native/style/css-selectors.js deleted file mode 100644 index c2b3897ebe0..00000000000 --- a/driver/js/packages/hippy-vue/src/renderer/native/style/css-selectors.js +++ /dev/null @@ -1,604 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making - * Hippy available. - * - * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* eslint-disable import/prefer-default-export */ -/* eslint-disable prefer-destructuring */ -/* eslint-disable no-cond-assign */ -/* eslint-disable no-useless-escape */ -/* eslint-disable no-param-reassign */ - -import { isNullOrUndefined } from '../../../util'; - -function wrap(text) { - return text ? ` ${text} ` : ''; -} - -/** - * Base classes - */ -class SelectorCore { - lookupSort(sorter, base) { - sorter.sortAsUniversal(base || this); - } - - removeSort(sorter, base) { - sorter.removeAsUniversal(base || this); - } -} - -class SimpleSelector extends SelectorCore { - accumulateChanges(node, map) { - if (!this.dynamic) { - return this.match(node); - } - if (this.mayMatch(node)) { - this.trackChanges(node, map); - return true; - } - return false; - } - - mayMatch(node) { - return this.match(node); - } - - trackChanges() { - return null; - } -} - -class SimpleSelectorSequence extends SimpleSelector { - constructor(selectors) { - super(); - this.specificity = selectors.reduce((sum, sel) => sel.specificity + sum, 0); - this.head = selectors.reduce((prev, curr) => (!prev || (curr.rarity > prev.rarity) ? curr : prev), null); - this.dynamic = selectors.some(sel => sel.dynamic); - this.selectors = selectors; - } - - toString() { - return `${this.selectors.join('')}${wrap(this.combinator)}`; - } - - match(node) { - if (!node) return false; - return this.selectors.every(sel => sel.match(node)); - } - - mayMatch(node) { - if (!node) return false; - return this.selectors.every(sel => sel.mayMatch(node)); - } - - trackChanges(node, map) { - this.selectors.forEach(sel => sel.trackChanges(node, map)); - } - - lookupSort(sorter, base) { - this.head.lookupSort(sorter, base || this); - } - - removeSort(sorter, base) { - this.head.removeSort(sorter, base || this); - } -} - -/** - * Universal Selector - */ -class UniversalSelector extends SimpleSelector { - constructor() { - super(); - this.specificity = 0x00000000; - this.rarity = 0; - this.dynamic = false; - } - - toString() { - return `*${wrap(this.combinator)}`; - } - - match() { - return true; - } -} - -/** - * Id Selector - */ -class IdSelector extends SimpleSelector { - constructor(id) { - super(); - this.specificity = 0x00010000; - this.rarity = 3; - this.dynamic = false; - this.id = id; - } - - toString() { - return `#${this.id}${wrap(this.combinator)}`; - } - - match(node) { - if (!node) return false; - return node.id === this.id; - } - - lookupSort(sorter, base) { - sorter.sortById(this.id, base || this); - } - - removeSort(sorter, base) { - sorter.removeById(this.id, base || this); - } -} - - -/** - * Type Selector - */ -class TypeSelector extends SimpleSelector { - constructor(cssType) { - super(); - this.specificity = 0x00000001; - this.rarity = 1; - this.dynamic = false; - this.cssType = cssType; - } - - toString() { - return `${this.cssType}${wrap(this.combinator)}`; - } - - match(node) { - if (!node) return false; - return node.tagName === this.cssType; - } - - lookupSort(sorter, base) { - sorter.sortByType(this.cssType, base || this); - } - - removeSort(sorter, base) { - sorter.removeByType(this.cssType, base || this); - } -} - -/** - * Class Selector - */ -class ClassSelector extends SimpleSelector { - constructor(className) { - super(); - this.specificity = 0x00000100; - this.rarity = 2; - this.dynamic = false; - this.className = className; - } - - toString() { - return `.${this.className}${wrap(this.combinator)}`; - } - - match(node) { - if (!node) return false; - return node.classList && node.classList.size && node.classList.has(this.className); - } - - lookupSort(sorter, base) { - sorter.sortByClass(this.className, base || this); - } - - removeSort(sorter, base) { - sorter.removeByClass(this.className, base || this); - } -} - -/** - * Pseudo Class Selector - */ -class PseudoClassSelector extends SimpleSelector { - constructor(cssPseudoClass) { - super(); - this.specificity = 0x00000100; - this.rarity = 0; - this.dynamic = false; - this.cssPseudoClass = cssPseudoClass; - } - - toString() { - return `:${this.cssPseudoClass}${wrap(this.combinator)}`; - } - - match(node) { - return !!node; - } - - mayMatch() { - return true; - } - - trackChanges(node, map) { - map.addPseudoClass(node, this.cssPseudoClass); - } -} - -/** - * get node attribute or styleScopeId value - * @param node - * @param attribute - * @returns {*} - */ -const getNodeAttrVal = (node, attribute) => { - const attr = node.attributes[attribute]; - if (typeof attr !== 'undefined') { - return attr; - } - if (Array.isArray(node.styleScopeId) && node.styleScopeId.includes(attribute)) { - return attribute; - } -}; - -/** - * Attribute Selector - */ -class AttributeSelector extends SimpleSelector { - constructor(attribute, test, value) { - super(); - this.specificity = 0x00000100; - this.rarity = 0; - this.dynamic = true; - this.attribute = attribute; - this.test = test; - this.value = value; - - if (!test) { - // HasAttribute - this.match = (node) => { - if (!node || !node.attributes) return false; - return !isNullOrUndefined(getNodeAttrVal(node, attribute)); - }; - return; - } - - if (!value) { - this.match = () => false; - return; - } - - this.match = (node) => { - if (!node || !node.attributes) return false; - // const escapedValue = value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); - const attr = `${getNodeAttrVal(node, attribute)}`; - - if (test === '=') { - // Equals - return attr === value; - } - - if (test === '^=') { - // PrefixMatch - return attr.startsWith(value); - } - - if (test === '$=') { - // SuffixMatch - return attr.endsWith(value); - } - - if (test === '*=') { - // SubstringMatch - return attr.indexOf(value) !== -1; - } - - if (test === '~=') { - // Includes - const words = attr.split(' '); - return words && words.indexOf(value) !== -1; - } - - if (test === '|=') { - // DashMatch - return attr === value || attr.startsWith(`${value}-`); - } - return false; - }; - } - - toString() { - return `[${this.attribute}${wrap(this.test)}${(this.test && this.value) || ''}]${wrap(this.combinator)}`; - } - - match() { - return false; - } - - mayMatch() { - return true; - } - - trackChanges(node, map) { - map.addAttribute(node, this.attribute); - } -} - -/** - * Invalid Selector - */ -class InvalidSelector extends SimpleSelector { - constructor(err) { - super(); - this.specificity = 0x00000000; - this.rarity = 4; - this.dynamic = false; - this.combinator = undefined; - this.err = err; - } - - toString() { - return ``; - } - - match() { - return false; - } - - lookupSort() { - return null; - } - - removeSort() { - return null; - } -} - -class ChildGroup { - constructor(selectors) { - this.selectors = selectors; - this.dynamic = selectors.some(sel => sel.dynamic); - } - - match(node) { - if (!node) return false; - const pass = this.selectors.every((sel, i) => { - if (i !== 0) { - node = node.parentNode; - } - return !!node && !!sel.match(node); - }); - return pass ? node : null; - } - - mayMatch(node) { - if (!node) return false; - const pass = this.selectors.every((sel, i) => { - if (i !== 0) { - node = node.parentNode; - } - return !!node && !!sel.mayMatch(node); - }); - return pass ? node : null; - } - - trackChanges(node, map) { - this.selectors.forEach((sel, i) => { - if (i !== 0) { - node = node.parentNode; - } - if (!node) { - return; - } - sel.trackChanges(node, map); - }); - } -} - -class SiblingGroup { - constructor(selectors) { - this.selectors = selectors; - this.dynamic = selectors.some(sel => sel.dynamic); - } - - match(node) { - if (!node) return false; - const pass = this.selectors.every((sel, i) => { - if (i !== 0) { - node = node.nextSibling; - } - return !!node && !!sel.match(node); - }); - return pass ? node : null; - } - - mayMatch(node) { - if (!node) return false; - const pass = this.selectors.every((sel, i) => { - if (i !== 0) { - node = node.nextSibling; - } - return !!node && !!sel.mayMatch(node); - }); - return pass ? node : null; - } - - trackChanges(node, map) { - this.selectors.forEach((sel, i) => { - if (i !== 0) { - node = node.nextSibling; - } - if (!node) { - return; - } - sel.trackChanges(node, map); - }); - } -} - -/** - * Big Selector - */ -class Selector extends SelectorCore { - constructor(selectors) { - super(); - const supportedCombinator = [undefined, ' ', '>', '+']; - let siblingGroup; - let lastGroup; - const groups = []; - 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); - } - - toString() { - return this.selectors.join(''); - } - - match(node) { - return this.groups.every((group, i) => { - if (i === 0) { - node = group.match(node); - return !!node; - } - let ancestor = node; - while (ancestor = ancestor.parentNode) { - if (node = group.match(ancestor)) { - return true; - } - } - return false; - }); - } - - lookupSort(sorter) { - this.last.lookupSort(sorter, this); - } - - removeSort(sorter) { - this.last.removeSort(sorter, this); - } - - accumulateChanges(node, map) { - if (!this.dynamic) { - return this.match(node); - } - - const bounds = []; - const mayMatch = this.groups.every((group, i) => { - if (i === 0) { - const nextNode = group.mayMatch(node); - bounds.push({ left: node, right: node }); - node = nextNode; - return !!node; - } - let ancestor = node; - while (ancestor = ancestor.parentNode) { - const 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 += 1) { - const group = this.groups[i]; - if (!group.dynamic) { - continue; - } - const bound = bounds[i]; - let leftBound = bound.left; - do { - if (group.mayMatch(leftBound)) { - group.trackChanges(leftBound, map); - } - } while ((leftBound !== bound.right) && (leftBound = node.parentNode)); - } - - return mayMatch; - } -} -Selector.ChildGroup = ChildGroup; -Selector.SiblingGroup = SiblingGroup; - -/** - * Rule Set - */ -class RuleSet { - constructor(selectors, declarations, hash) { - selectors.forEach((sel) => { - sel.ruleSet = this; // FIXME: It makes circular dependency - return null; - }); - this.hash = hash; - this.selectors = selectors; - this.declarations = declarations; - } - - toString() { - return `${this.selectors.join(', ')} {${ - this.declarations.map((d, i) => `${i === 0 ? ' ' : ''}${d.property}: ${d.value}`).join('; ') - }}`; - } - - lookupSort(sorter) { - this.selectors.forEach(sel => sel.lookupSort(sorter)); - } - - removeSort(sorter) { - this.selectors.forEach(sel => sel.removeSort(sorter)); - } -} - -export { - InvalidSelector, - UniversalSelector, - IdSelector, - TypeSelector, - ClassSelector, - PseudoClassSelector, - AttributeSelector, - SimpleSelectorSequence, - Selector, - RuleSet, -}; diff --git a/driver/js/packages/hippy-vue/src/renderer/text-node.js b/driver/js/packages/hippy-vue/src/renderer/text-node.ts similarity index 79% rename from driver/js/packages/hippy-vue/src/renderer/text-node.js rename to driver/js/packages/hippy-vue/src/renderer/text-node.ts index eee1dd4606d..c5859223178 100644 --- a/driver/js/packages/hippy-vue/src/renderer/text-node.js +++ b/driver/js/packages/hippy-vue/src/renderer/text-node.ts @@ -20,11 +20,13 @@ /* eslint-disable no-underscore-dangle */ +import { Text } from '../native/components'; import ViewNode from './view-node'; -import { Text } from './native/components'; export default class TextNode extends ViewNode { - constructor(text) { + public text: string; + + public constructor(text: string) { super(); this.text = text; this._meta = { @@ -33,10 +35,10 @@ export default class TextNode extends ViewNode { }; } - setText(text) { + public setText(text: string) { this.text = text; - if (typeof this.parentNode.setText === 'function') { - this.parentNode.setText(text); + if (typeof (this.parentNode as TextNode).setText === 'function') { + (this.parentNode as TextNode).setText(text); } } } diff --git a/driver/js/packages/hippy-vue/src/renderer/view-node.js b/driver/js/packages/hippy-vue/src/renderer/view-node.ts similarity index 89% rename from driver/js/packages/hippy-vue/src/renderer/view-node.js rename to driver/js/packages/hippy-vue/src/renderer/view-node.ts index f2e40d51434..0be94b7432c 100644 --- a/driver/js/packages/hippy-vue/src/renderer/view-node.js +++ b/driver/js/packages/hippy-vue/src/renderer/view-node.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-this-alias */ /* * Tencent is pleased to support the open source community by making * Hippy available. @@ -22,13 +23,15 @@ /* eslint-disable no-param-reassign */ import { RelativeToRefType, findNotToSkipNode } from '../util/node'; -import { insertChild, removeChild, moveChild } from './native'; +import { insertChild, removeChild, moveChild } from '../native'; +import { CallbackType, NeedToTyped } from '../types/native'; const ROOT_VIEW_ID = 0; let currentNodeId = 0; if (global.__GLOBAL__ && Number.isInteger(global.__GLOBAL__.nodeId)) { currentNodeId = global.__GLOBAL__.nodeId; } + function getNodeId() { currentNodeId += 1; if (currentNodeId % 10 === 0) { @@ -40,12 +43,25 @@ function getNodeId() { return currentNodeId; } +export type Meta = { + [key: string]: NeedToTyped; +}; + class ViewNode { + public childNodes: ViewNode[]; + public index: number; + public nextSibling?: ViewNode; + public nodeId: number; + public parentNode?: ViewNode; + public prevSibling?: ViewNode; + protected _meta?: Meta; + private _isMounted: boolean; + private _ownerDocument: NeedToTyped; + constructor() { // Point to root document element. this._ownerDocument = null; // Component meta information, such as native component will use. - this._meta = null; // Will change to be true after insert into Native dom. this._isMounted = false; // Virtual DOM node id, will be used in native to identify. @@ -54,9 +70,6 @@ class ViewNode { this.index = 0; // Relation nodes. this.childNodes = []; - this.parentNode = null; - this.prevSibling = null; - this.nextSibling = null; } /* istanbul ignore next */ @@ -86,7 +99,7 @@ class ViewNode { return this._ownerDocument; } - let el = this; + let el: any = this; while (el.constructor.name !== 'DocumentNode') { el = el.parentNode; if (!el) { @@ -105,7 +118,7 @@ class ViewNode { this._isMounted = isMounted; } - insertBefore(childNode, referenceNode) { + insertBefore(childNode: ViewNode, referenceNode: ViewNode) { if (!childNode) { throw new Error('Can\'t insert child.'); } @@ -151,7 +164,7 @@ class ViewNode { ); } - moveChild(childNode, referenceNode) { + moveChild(childNode: ViewNode, referenceNode: ViewNode) { if (!childNode) { throw new Error('Can\'t move child.'); } @@ -207,7 +220,7 @@ class ViewNode { ); } - appendChild(childNode) { + appendChild(childNode: ViewNode) { if (!childNode) { throw new Error('Can\'t append child.'); } @@ -232,7 +245,7 @@ class ViewNode { ); } - removeChild(childNode) { + removeChild(childNode: ViewNode) { if (!childNode) { throw new Error('Can\'t remove child.'); } @@ -251,8 +264,8 @@ class ViewNode { if (childNode.nextSibling) { childNode.nextSibling.prevSibling = childNode.prevSibling; } - childNode.prevSibling = null; - childNode.nextSibling = null; + childNode.prevSibling = undefined; + childNode.nextSibling = undefined; const index = this.childNodes.indexOf(childNode); this.childNodes.splice(index, 1); removeChild(this, childNode); @@ -261,7 +274,7 @@ class ViewNode { /** * Find a specific target with condition */ - findChild(condition) { + public findChild(condition: CallbackType): ViewNode | null { const yes = condition(this); if (yes) { return this; @@ -281,11 +294,11 @@ class ViewNode { /** * Traverse the children and execute callback */ - traverseChildren(callback, refInfo) { + traverseChildren(callback: CallbackType, refInfo: NeedToTyped) { callback(this, refInfo); // Find the children if (this.childNodes.length) { - this.childNodes.forEach((childNode) => { + this.childNodes.forEach((childNode: ViewNode) => { this.traverseChildren.call(childNode, callback, {}); }); } diff --git a/driver/js/packages/hippy-vue/src/runtime/__tests__/modules.test.js b/driver/js/packages/hippy-vue/src/runtime/__tests__/modules.test.js deleted file mode 100644 index 488254ac729..00000000000 --- a/driver/js/packages/hippy-vue/src/runtime/__tests__/modules.test.js +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making - * Hippy available. - * - * Copyright (C) 2017-2022 THL A29 Limited, a Tencent company. - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* eslint-disable no-underscore-dangle */ - -import test, { before } from 'ava'; -import * as nodeOps from '../node-ops'; -import { setVue, setApp } from '../../util'; -import Native from '../native'; - -before(() => { - global.Hippy = { - platform: { - OS: 'android', - APILevel: 29, - }, - window: { - width: 423.5294196844927, - height: 749.0196218494269, - scale: 2.549999952316284, - fontScale: 2.549999952316284, - statusBarHeight: 28.235294645632848, - navigatorBarHeight: 0, - }, - screen: { - width: 423.5294196844927, - height: 749.0196218494269, - scale: 2.549999952316284, - fontScale: 2.549999952316284, - statusBarHeight: 72, - navigatorBarHeight: 0, - }, - pixelRatio: 2.549999952316284, - }; - setVue({ - config: { - silent: true, - }, - }); - setApp({ - $options: { - rootView: '#root', - }, - $on: () => {}, - $off: () => {}, - $nextTick: (cb) => { - setTimeout(cb); - }, - }); -}); - -test('native device info', (t) => { - t.is(Native.Platform, null); - t.is(Native.PixelRatio, 2); - t.is(Native.version, undefined); - t.is(Native.isIPhoneX, false); - t.is(Native.Device, 'Unknown device'); - t.is(Native.OSVersion, null); - t.is(Native.SDKVersion, null); - t.is(Native.APILevel, null); - t.is(Native.Dimensions.screen.width, undefined); - t.is(Native.Dimensions.screen.height, undefined); - t.is(Native.Dimensions.screen.statusBarHeight, undefined); - t.is(Native.OnePixel, 0.5); - t.pass(); -}); - -test('native ImageLoader', async (t) => { - t.is(Native.ImageLoader.prefetch(), undefined); - t.is(await Native.ImageLoader.getSize('https://user-images.githubusercontent.com/12878546/148736102-7cd9525b-aceb-41c6-a905-d3156219ef16.png'), undefined); -}); - -test('native backAndroid', async (t) => { - const handler = () => {}; - t.is(Native.BackAndroid.addListener(handler).remove(), undefined); -}); - -test('native NetInfo', (t) => { - const handler = () => {}; - const listener = Native.NetInfo.addEventListener('change', handler); - Native.NetInfo.removeEventListener('change', listener); - t.pass(); -}); - -test('native getElemCss', async (t) => { - const element = nodeOps.createElement('div'); - t.deepEqual(Native.getElemCss(element), {}); -}); diff --git a/driver/js/packages/hippy-vue/src/runtime/__tests__/node-ops.test.js b/driver/js/packages/hippy-vue/src/runtime/__tests__/node-ops.test.js deleted file mode 100644 index 1ceebfe97e5..00000000000 --- a/driver/js/packages/hippy-vue/src/runtime/__tests__/node-ops.test.js +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Tencent is pleased to support the open source community by making - * Hippy available. - * - * Copyright (C) 2017-2022 THL A29 Limited, a Tencent company. - * All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* eslint-disable no-underscore-dangle */ - -import test, { before } from 'ava'; -import * as nodeOps from '../node-ops'; -import CommentNode from '../../renderer/comment-node'; -import DocumentNode from '../../renderer/document-node'; -import ElementNode from '../../renderer/element-node'; -import TextNode from '../../renderer/text-node'; -import { setVue, setApp } from '../../util'; - -function createElement(tagName) { - return nodeOps.createElement(tagName); -} - -before(() => { - setVue({ - config: { - silent: true, - }, - }); - setApp({ - $options: { - rootView: '#root', - }, - $nextTick: (cb) => { - setTimeout(cb); - }, - }); - global.Hippy.SceneBuilder = function SceneBuilder() { - this.create = () => {}; - this.update = () => {}; - this.delete = () => {}; - this.move = () => {}; - this.build = () => {}; - this.addEventListener = () => {}; - this.removeEventListener = () => {}; - }; -}); - -test('create new DocumentNode', (t) => { - const documentNode = new DocumentNode(); - t.is(documentNode.documentElement.tagName, 'document'); - t.is(documentNode.createComment, DocumentNode.createComment); - t.is(documentNode.createElement, DocumentNode.createElement); - t.is(documentNode.createElementNS, DocumentNode.createElementNS); - t.is(documentNode.createTextNode, DocumentNode.createTextNode); -}); - -test('createElement test', (t) => { - const element = nodeOps.createElement('a'); - t.true(element instanceof ElementNode); - t.is(element.tagName, 'a'); -}); - -test('createElementNS test', (t) => { - const element = nodeOps.createElementNS('Native', 'Link'); - t.true(element instanceof ElementNode); - t.is(element.tagName, 'native:link'); -}); - -test('createTextNode test', (t) => { - const element = nodeOps.createTextNode('Hello'); - t.true(element instanceof TextNode); - t.is(element.text, 'Hello'); -}); - -test('createComment test', (t) => { - const element = nodeOps.createComment('World'); - t.true(element instanceof CommentNode); - t.is(element.text, 'World'); -}); - -test('insertBefore without referenceNode', (t) => { - const parentNode = createElement('div'); - const newNode = createElement('div'); - nodeOps.insertBefore(parentNode, newNode); - t.is(parentNode.childNodes.length, 1); - t.is(parentNode.childNodes[0], newNode); -}); - -test('insertBefore with referenceNode', (t) => { - const parentNode = createElement('div'); - const newNode = createElement('div'); - const referenceNode = createElement('p'); - nodeOps.insertBefore(parentNode, referenceNode); - t.is(parentNode.childNodes.length, 1); - t.is(parentNode.childNodes[0], referenceNode); - nodeOps.insertBefore(parentNode, newNode, referenceNode); - t.is(parentNode.childNodes.length, 2); - t.is(parentNode.childNodes[0], newNode); - t.is(parentNode.childNodes[1], referenceNode); -}); - -test('insertBefore a TextNode', (t) => { - const text = 'Hello world'; - const parentNode = createElement('div'); - const textNode = nodeOps.createTextNode(text); - nodeOps.insertBefore(parentNode, textNode); - t.is(parentNode.getAttribute('text'), text); -}); - -test('insertBefore error when no childNode', (t) => { - const parentNode = createElement('div'); - const err = t.throws(() => { - nodeOps.insertBefore(parentNode); - }, Error); - t.is(err.message, 'Can\'t insert child.'); -}); - -test('insertBefore error when childNode have another parent', (t) => { - const parentNode = createElement('div'); - const parentNode2 = createElement('div'); - const childNode = createElement('p'); - nodeOps.insertBefore(parentNode, childNode); - const err = t.throws(() => { - nodeOps.insertBefore(parentNode2, childNode); - }, Error); - t.is(err.message, 'Can\'t append child, because it already has a different parent.'); -}); - -test('insertBefore error when referenceNode have another parent', (t) => { - const parentNode = createElement('div'); - const parentNode2 = createElement('div'); - const childNode = createElement('p'); - const childNode2 = createElement('p'); - nodeOps.appendChild(parentNode, childNode); - nodeOps.insertBefore(parentNode, childNode2, childNode); - const err = t.throws(() => { - nodeOps.insertBefore(parentNode2, childNode, childNode2); - }, Error); - t.is(err.message, 'Can\'t insert child, because the reference node has a different parent.'); -}); - -test('insertBefore error when childNode with referenceNode have another parent', (t) => { - const parentNode = createElement('div'); - const parentNode2 = createElement('div'); - const childNode = createElement('p'); - const childNode2 = createElement('p'); - nodeOps.appendChild(parentNode, childNode); - nodeOps.appendChild(parentNode2, childNode2); - const err = t.throws(() => { - nodeOps.insertBefore(parentNode, childNode2, childNode); - }, Error); - t.is(err.message, 'Can\'t insert child, because it already has a different parent.'); -}); - -test('removeChild test', (t) => { - const parentNode = createElement('div'); - const childNode1 = createElement('div'); - const childNode2 = createElement('div'); - const childNode3 = createElement('div'); - nodeOps.appendChild(parentNode, childNode1); - nodeOps.appendChild(parentNode, childNode2); - nodeOps.appendChild(parentNode, childNode3); - nodeOps.removeChild(parentNode, childNode2); - t.is(parentNode.childNodes.length, 2); -}); - -test('removeChild a TextNode test', (t) => { - const text = 'Hello world'; - const parentNode = createElement('div'); - const textNode = nodeOps.createTextNode(text); - nodeOps.insertBefore(parentNode, textNode); - t.is(parentNode.getAttribute('text'), text); - nodeOps.removeChild(parentNode, textNode); - t.is(parentNode.getAttribute('text'), ''); -}); - -test('removeChild error when no childNode', (t) => { - const parentNode = createElement('div'); - const err = t.throws(() => { - nodeOps.removeChild(parentNode); - }, Error); - t.is(err.message, 'Can\'t remove child.'); -}); - -test('removeChild error when childNode has no parent', (t) => { - const parentNode = createElement('div'); - const childNode = createElement('div'); - const err = t.throws(() => { - nodeOps.removeChild(parentNode, childNode); - }, Error); - t.is(err.message, 'Can\'t remove child, because it has no parent.'); -}); - -test('removeChild error when childNode with another parentNode', (t) => { - const parentNode = createElement('div'); - const parentNode2 = createElement('div'); - const childNode = createElement('div'); - nodeOps.appendChild(parentNode2, childNode); - const err = t.throws(() => { - nodeOps.removeChild(parentNode, childNode); - }, Error); - t.is(err.message, 'Can\'t remove child, because it has a different parent.'); -}); - -test('appendChild test', (t) => { - const parentNode = createElement('div'); - const newNode = createElement('div'); - nodeOps.appendChild(parentNode, newNode); - t.is(parentNode.childNodes.length, 1); - t.is(parentNode.childNodes[0], newNode); - const newNode2 = createElement('p'); - nodeOps.appendChild(parentNode, newNode2); - t.is(parentNode.childNodes.length, 2); - t.is(parentNode.childNodes[1], newNode2); -}); - -test('appendChild error when no childNode', (t) => { - const parentNode = createElement('div'); - const err = t.throws(() => { - nodeOps.appendChild(parentNode); - }, Error); - t.is(err.message, 'Can\'t append child.'); -}); - -test('parentNode test', (t) => { - const parentNode = createElement('div'); - const newNode = createElement('div'); - nodeOps.insertBefore(parentNode, newNode); - t.is(nodeOps.parentNode(newNode), parentNode); - const parentNode2 = createElement('div'); - const newNode2 = createElement('div'); - nodeOps.appendChild(parentNode2, newNode2); - t.is(nodeOps.parentNode(newNode2), parentNode2); -}); - -test('nextSibling test', (t) => { - const parentNode = createElement('div'); - const newNode = createElement('div'); - const newNode2 = createElement('div'); - nodeOps.appendChild(parentNode, newNode); - nodeOps.insertBefore(parentNode, newNode2, newNode); - t.is(nodeOps.nextSibling(newNode2), newNode); -}); - -test('tagName test', (t) => { - const elementNode = createElement('div'); - t.is(nodeOps.tagName(elementNode), 'div'); -}); - -test('setTextContent for ElementNode test', (t) => { - // ElementNode setText - const parentNode = createElement('div'); - const text = 'Hello world'; - nodeOps.setTextContent(parentNode, text); - t.is(parentNode.getAttribute('text'), text); -}); - -test('setTextContent for TextNode test', (t) => { - const parentNode = createElement('div'); - const textNode = nodeOps.createTextNode(''); - const text = 'Hello world'; - nodeOps.appendChild(parentNode, textNode); - nodeOps.setTextContent(textNode, text); - t.is(textNode.text, text); -}); - -test('setTextContent for textarea element test', (t) => { - const text = 'Hello world'; - const textAreaNode = createElement('textarea'); - nodeOps.setTextContent(textAreaNode, text); - t.is(textAreaNode.getAttribute('value'), text); -}); - -test('setAttribute test', (t) => { - const node = createElement('div'); - const key = 'key'; - const value = 'value'; - nodeOps.setAttribute(node, key, value); - t.is(node.getAttribute(key), value); -}); - -test('setStyleScope test', (t) => { - const node = createElement('div'); - const styleScopeId = 123; - t.is(nodeOps.setStyleScope(node, styleScopeId), undefined); - t.is(node.scopeIdList[0], styleScopeId.toString()); -}); diff --git a/driver/js/packages/hippy-vue/src/runtime/backAndroid.js b/driver/js/packages/hippy-vue/src/runtime/backAndroid.ts similarity index 94% rename from driver/js/packages/hippy-vue/src/runtime/backAndroid.js rename to driver/js/packages/hippy-vue/src/runtime/backAndroid.ts index dbc798b1122..5fcf723bf13 100644 --- a/driver/js/packages/hippy-vue/src/runtime/backAndroid.js +++ b/driver/js/packages/hippy-vue/src/runtime/backAndroid.ts @@ -18,11 +18,12 @@ * limitations under the License. */ +import { NeedToTyped } from '../types/native'; import { getApp } from '../util'; import Native from './native'; const backPressSubscriptions = new Set(); -let app; +let app: NeedToTyped; let hasInitialized = false; /** * Android hardware back button event listener. @@ -36,7 +37,7 @@ const realBackAndroid = { * @param handler * @returns {{remove(): void}} */ - addListener(handler) { + addListener(handler: NeedToTyped) { if (!hasInitialized) { hasInitialized = true; realBackAndroid.initEventListener(); @@ -54,7 +55,7 @@ const realBackAndroid = { * removeBackPressListener * @param handler */ - removeListener(handler) { + removeListener(handler: NeedToTyped) { backPressSubscriptions.delete(handler); if (backPressSubscriptions.size === 0) { Native.callNative('DeviceEventModule', 'setListenBackPress', false); diff --git a/driver/js/packages/hippy-vue/src/runtime/constants.js b/driver/js/packages/hippy-vue/src/runtime/constants.ts similarity index 100% rename from driver/js/packages/hippy-vue/src/runtime/constants.js rename to driver/js/packages/hippy-vue/src/runtime/constants.ts diff --git a/driver/js/packages/hippy-vue/src/runtime/directives/index.js b/driver/js/packages/hippy-vue/src/runtime/directives/index.ts similarity index 100% rename from driver/js/packages/hippy-vue/src/runtime/directives/index.js rename to driver/js/packages/hippy-vue/src/runtime/directives/index.ts diff --git a/driver/js/packages/hippy-vue/src/runtime/directives/model.js b/driver/js/packages/hippy-vue/src/runtime/directives/model.ts similarity index 77% rename from driver/js/packages/hippy-vue/src/runtime/directives/model.js rename to driver/js/packages/hippy-vue/src/runtime/directives/model.ts index 1c0f6a32bf0..1bd535f44c3 100644 --- a/driver/js/packages/hippy-vue/src/runtime/directives/model.js +++ b/driver/js/packages/hippy-vue/src/runtime/directives/model.ts @@ -21,16 +21,18 @@ /* eslint-disable no-underscore-dangle */ /* eslint-disable no-param-reassign */ -import { Event } from '../../renderer/native/event'; +import { Event } from '../../event'; +import ElementNode from '../../renderer/element-node'; +import { NeedToTyped } from '../../types/native'; import Native from '../native'; -function androidUpdate(el, value, oldValue) { +function androidUpdate(el: ElementNode, value: NeedToTyped, oldValue: NeedToTyped) { if (value !== oldValue) { el.setAttribute('defaultValue', value, { textUpdate: true }); } } -function iOSUpdate(el, value) { +function iOSUpdate(el: ElementNode, value: NeedToTyped) { if (value !== el.attributes.defaultValue) { el.attributes.defaultValue = value; el.setAttribute('text', value, { textUpdate: true }); @@ -41,7 +43,7 @@ function iOSUpdate(el, value) { let update = androidUpdate; const model = { - inserted(el, binding) { + inserted(el: NeedToTyped, binding: NeedToTyped) { // Update the specific platform update function. if (Native.Platform === 'ios' && update !== iOSUpdate) { update = iOSUpdate; @@ -52,15 +54,18 @@ const model = { el.attributes.defaultValue = binding.value; // Binding event when typing if (!binding.modifiers.lazy) { - el.addEventListener('change', ({ value }) => { + el.addEventListener('change', ({ value }: NeedToTyped) => { const event = new Event('input'); - event.value = value; + (event as any).value = value; el.dispatchEvent(event); }); } } }, - update(el, { value, oldValue }) { + update(el: NeedToTyped, { + value, + oldValue, + }: NeedToTyped) { el.value = value; update(el, value, oldValue); }, diff --git a/driver/js/packages/hippy-vue/src/runtime/directives/show.js b/driver/js/packages/hippy-vue/src/runtime/directives/show.ts similarity index 77% rename from driver/js/packages/hippy-vue/src/runtime/directives/show.js rename to driver/js/packages/hippy-vue/src/runtime/directives/show.ts index 73b8619294e..dd96e5f8afc 100644 --- a/driver/js/packages/hippy-vue/src/runtime/directives/show.js +++ b/driver/js/packages/hippy-vue/src/runtime/directives/show.ts @@ -18,10 +18,12 @@ * limitations under the License. */ +import { NeedToTyped } from '../../types/native'; + /* eslint-disable no-underscore-dangle */ /* eslint-disable no-param-reassign */ -function toggle(el, value, vNode, originalDisplay) { +function toggle(el: NeedToTyped, value: NeedToTyped, vNode: NeedToTyped, originalDisplay: NeedToTyped) { if (value) { vNode.data.show = true; el.setStyle('display', originalDisplay); @@ -31,7 +33,9 @@ function toggle(el, value, vNode, originalDisplay) { } const show = { - bind(el, { value }, vNode) { + bind(el: NeedToTyped, { + value, + }: NeedToTyped, vNode: NeedToTyped) { // Set the display to 'block' when undefined if (el.style.display === undefined) { el.style.display = 'block'; @@ -40,13 +44,16 @@ const show = { el.__vOriginalDisplay = originalDisplay; toggle(el, value, vNode, originalDisplay); }, - update(el, { value, oldValue }, vNode) { + update(el: NeedToTyped, { + value, + oldValue, + }: NeedToTyped, vNode: NeedToTyped) { if (!value === !oldValue) { return; } toggle(el, value, vNode, el.__vOriginalDisplay); }, - unbind(el, binding, vNode, oldVNode, isDestroy) { + unbind(el: NeedToTyped, binding: NeedToTyped, vNode: NeedToTyped, oldVNode: NeedToTyped, isDestroy: NeedToTyped) { if (!isDestroy) { el.style.display = el.__vOriginalDisplay; } diff --git a/driver/js/packages/hippy-vue/src/runtime/index.js b/driver/js/packages/hippy-vue/src/runtime/index.ts similarity index 90% rename from driver/js/packages/hippy-vue/src/runtime/index.js rename to driver/js/packages/hippy-vue/src/runtime/index.ts index b454f1dd125..2b9dfd3a569 100644 --- a/driver/js/packages/hippy-vue/src/runtime/index.js +++ b/driver/js/packages/hippy-vue/src/runtime/index.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-this-alias */ /* * Tencent is pleased to support the open source community by making * Hippy available. @@ -52,7 +53,8 @@ import { setBeforeRenderToNative, } from '../util'; import DocumentNode from '../renderer/document-node'; -import { Event } from '../renderer/native/event'; +import { Event } from '../event'; +import { NeedToTyped } from '../types/native'; import { patch } from './patch'; import Native, { HippyRegister } from './native'; import * as iPhone from './iphone'; @@ -84,7 +86,7 @@ extend(Vue.options.directives, platformDirectives); Vue.prototype.__patch__ = patch; // Override $mount for attend the compiler. -Vue.prototype.$mount = function $mount(el, hydrating) { +Vue.prototype.$mount = function $mount(el: NeedToTyped, hydrating: NeedToTyped) { const options = this.$options; // resolve template/el and convert to render function if (!options.render) { @@ -116,7 +118,7 @@ Vue.prototype.$mount = function $mount(el, hydrating) { * @param {function} afterCallback - Callback after register completed. * @param {function} beforeCallback - Callback before register completed. */ -Vue.prototype.$start = function $start(afterCallback, beforeCallback) { +Vue.prototype.$start = function $start(afterCallback: NeedToTyped, beforeCallback: NeedToTyped) { setApp(this); // beforeLoadStyle is a hidden option for pre-process @@ -133,7 +135,7 @@ Vue.prototype.$start = function $start(afterCallback, beforeCallback) { // Register the entry point into Hippy // The callback will be executed when Native trigger loadInstance // or runApplication event. - HippyRegister.regist(this.$options.appName, (superProps) => { + HippyRegister.regist(this.$options.appName, (superProps: NeedToTyped) => { const { __instanceId__: rootViewId } = superProps; this.$options.$superProps = superProps; this.$options.rootViewId = rootViewId; @@ -173,18 +175,18 @@ Vue.prototype.$start = function $start(afterCallback, beforeCallback) { // Override component and extend of avoid built-in component warning. let cid = 1; -function initProps(Comp) { +function initProps(Comp: NeedToTyped) { const { props } = Comp.options; Object.keys(props).forEach(key => proxy(Comp.prototype, '_props', key)); } -function initComputed(Comp) { +function initComputed(Comp: NeedToTyped) { const { computed } = Comp.options; Object.keys(computed).forEach(key => defineComputed(Comp.prototype, key, computed[key])); } // Override component for avoid built-in component warning. -Vue.component = function component(id, definition) { +Vue.component = function component(id: NeedToTyped, definition: NeedToTyped) { if (!definition) { return this.options.components[id]; } @@ -197,7 +199,7 @@ Vue.component = function component(id, definition) { }; // Override extend for avoid built-in component warning. -Vue.extend = function hippyExtend(extendOptions) { +Vue.extend = function hippyExtend(extendOptions: NeedToTyped) { extendOptions = extendOptions || {}; const Super = this; const SuperId = Super.cid; @@ -206,7 +208,7 @@ Vue.extend = function hippyExtend(extendOptions) { return cachedCtors[SuperId]; } const name = extendOptions.name || Super.options.name; - const Sub = function VueComponent(options) { + const Sub: NeedToTyped = function VueComponent(this: NeedToTyped, options: NeedToTyped) { this._init(options); }; Sub.prototype = Object.create(Super.prototype); @@ -230,7 +232,7 @@ Vue.extend = function hippyExtend(extendOptions) { Sub.use = Super.use; // create asset registers, so extended classes // can have their private assets too. - ASSET_TYPES.forEach((type) => { + ASSET_TYPES.forEach((type: NeedToTyped) => { Sub[type] = Super[type]; }); // enable recursive self-lookup @@ -266,7 +268,7 @@ if (config.devtools && devtools) { * beforeRenderToNative hook */ const BEFORE_RENDER_TO_NATIVE_HOOK_VERSION = 1; -Vue.config._setBeforeRenderToNative = (hook, version) => { +Vue.config._setBeforeRenderToNative = (hook: NeedToTyped, version: NeedToTyped) => { if (isFunction(hook)) { if (BEFORE_RENDER_TO_NATIVE_HOOK_VERSION === version) { setBeforeRenderToNative(hook); @@ -284,7 +286,7 @@ const VueProxy = new Proxy(Vue, { if (!global.__VUE_ROOT_INSTANCES__) { global.__VUE_ROOT_INSTANCES__ = []; } - if (args && args.length && args[0].appName) { + if (args?.length && args[0].appName) { global.__VUE_ROOT_INSTANCES__.push(vm); } } diff --git a/driver/js/packages/hippy-vue/src/runtime/iphone.js b/driver/js/packages/hippy-vue/src/runtime/iphone.ts similarity index 93% rename from driver/js/packages/hippy-vue/src/runtime/iphone.js rename to driver/js/packages/hippy-vue/src/runtime/iphone.ts index 5a933851648..279515ac1e6 100644 --- a/driver/js/packages/hippy-vue/src/runtime/iphone.js +++ b/driver/js/packages/hippy-vue/src/runtime/iphone.ts @@ -19,12 +19,13 @@ */ import ElementNode from '../renderer/element-node'; +import { NeedToTyped } from '../types/native'; import Native from './native'; -function drawStatusBar(appOptions = {}) { +function drawStatusBar(appOptions: NeedToTyped = {}) { const { iPhone } = appOptions; - let statusBarOpts = {}; - if (iPhone && iPhone.statusBar) { + let statusBarOpts: NeedToTyped = {}; + if (iPhone?.statusBar) { statusBarOpts = iPhone.statusBar; } if (statusBarOpts.disabled) { diff --git a/driver/js/packages/hippy-vue/src/runtime/modules/attrs.js b/driver/js/packages/hippy-vue/src/runtime/modules/attrs.ts similarity index 88% rename from driver/js/packages/hippy-vue/src/runtime/modules/attrs.js rename to driver/js/packages/hippy-vue/src/runtime/modules/attrs.ts index 4504957633b..d46a9e406ec 100644 --- a/driver/js/packages/hippy-vue/src/runtime/modules/attrs.js +++ b/driver/js/packages/hippy-vue/src/runtime/modules/attrs.ts @@ -22,12 +22,13 @@ /* eslint-disable no-param-reassign */ import { extend } from 'shared/util'; +import { NeedToTyped } from '../../types/native'; -function updateAttrs(oldVNode, vNode) { +function updateAttrs(oldVNode: NeedToTyped, vNode: NeedToTyped) { if (!oldVNode.data.attrs && !vNode.data.attrs) { return; } - const updatePayload = {}; + const updatePayload: NeedToTyped = {}; const { elm } = vNode; const oldAttrs = oldVNode.data.attrs || {}; let attrs = vNode.data.attrs || {}; @@ -56,7 +57,7 @@ function updateAttrs(oldVNode, vNode) { }); } -export function setAttrs(vNode, customElem, options = {}) { +export function setAttrs(vNode: NeedToTyped, customElem: NeedToTyped, options: NeedToTyped = {}) { if (!vNode || !vNode.data) { return; } @@ -65,7 +66,7 @@ export function setAttrs(vNode, customElem, options = {}) { elm = customElem; } if (!elm) return; - let attrs = (vNode.data && vNode.data.attrs) || {}; + let attrs = (vNode.data?.attrs) || {}; // clone observed objects, as the user probably wants to mutate it if (attrs.__ob__) { attrs = extend({}, attrs); diff --git a/driver/js/packages/hippy-vue/src/runtime/modules/class.js b/driver/js/packages/hippy-vue/src/runtime/modules/class.ts similarity index 90% rename from driver/js/packages/hippy-vue/src/runtime/modules/class.js rename to driver/js/packages/hippy-vue/src/runtime/modules/class.ts index 4df97cdfc3a..3f09d5063a4 100644 --- a/driver/js/packages/hippy-vue/src/runtime/modules/class.js +++ b/driver/js/packages/hippy-vue/src/runtime/modules/class.ts @@ -22,8 +22,9 @@ /* eslint-disable no-param-reassign */ import { genClassForVnode, concat, stringifyClass } from 'web/util/index'; +import { NeedToTyped } from '../../types/native'; -function updateClass(oldVNode, vNode) { +function updateClass(oldVNode: NeedToTyped, vNode: NeedToTyped) { const { elm, data } = vNode; const oldData = oldVNode.data; if ( @@ -46,7 +47,7 @@ function updateClass(oldVNode, vNode) { } } -export function setClass(vNode, customElem, options = {}) { +export function setClass(vNode: NeedToTyped, customElem: NeedToTyped, options: NeedToTyped = {}) { if (!vNode || !vNode.data) { return; } diff --git a/driver/js/packages/hippy-vue/src/runtime/modules/events.js b/driver/js/packages/hippy-vue/src/runtime/modules/events.ts similarity index 77% rename from driver/js/packages/hippy-vue/src/runtime/modules/events.js rename to driver/js/packages/hippy-vue/src/runtime/modules/events.ts index 9dea2b68844..19d6fa584b1 100644 --- a/driver/js/packages/hippy-vue/src/runtime/modules/events.js +++ b/driver/js/packages/hippy-vue/src/runtime/modules/events.ts @@ -23,22 +23,29 @@ /* eslint-disable no-param-reassign */ import { updateListeners } from 'core/vdom/helpers/update-listeners'; +import { NeedToTyped } from '../../types/native'; -let target; +let target: NeedToTyped; -function remove(event, handler, capture, _target) { +function remove(event: NeedToTyped, handler: NeedToTyped, capture: NeedToTyped, _target: NeedToTyped) { (_target || target).removeEventListener(event); } // eslint-disable-next-line no-unused-vars -function add(event, handler, capture, passive, params) { +function add( + event: NeedToTyped, + handler: NeedToTyped, + capture: NeedToTyped, + passive: NeedToTyped, + params: NeedToTyped, +) { if (capture) { return; } target.addEventListener(event, handler); } -function createOnceHandler(event, handler, capture) { +function createOnceHandler(event: NeedToTyped, handler: NeedToTyped, capture: NeedToTyped) { const _target = target; // save current target element in closure return function onceHandler() { const res = handler(...arguments); @@ -48,7 +55,7 @@ function createOnceHandler(event, handler, capture) { }; } -function updateDOMListeners(oldVNode, vNode) { +function updateDOMListeners(oldVNode: NeedToTyped, vNode: NeedToTyped) { if (!oldVNode.data.on && !vNode.data.on) { return; } diff --git a/driver/js/packages/hippy-vue/src/runtime/modules/index.js b/driver/js/packages/hippy-vue/src/runtime/modules/index.ts similarity index 100% rename from driver/js/packages/hippy-vue/src/runtime/modules/index.js rename to driver/js/packages/hippy-vue/src/runtime/modules/index.ts diff --git a/driver/js/packages/hippy-vue/src/runtime/modules/style.js b/driver/js/packages/hippy-vue/src/runtime/modules/style.ts similarity index 89% rename from driver/js/packages/hippy-vue/src/runtime/modules/style.js rename to driver/js/packages/hippy-vue/src/runtime/modules/style.ts index aa24a0acd07..529ef8047ea 100644 --- a/driver/js/packages/hippy-vue/src/runtime/modules/style.js +++ b/driver/js/packages/hippy-vue/src/runtime/modules/style.ts @@ -23,10 +23,11 @@ import { extend, cached, camelize } from 'shared/util'; import { isNullOrUndefined } from '../../util'; +import { NeedToTyped } from '../../types/native'; const normalize = cached(camelize); -function toObject(arr) { +function toObject(arr: NeedToTyped) { const res = {}; for (let i = 0; i < arr.length; i += 1) { if (arr[i]) { @@ -36,7 +37,7 @@ function toObject(arr) { return res; } -function isStyleExisted(oldVNode, vNode) { +function isStyleExisted(oldVNode: NeedToTyped, vNode: NeedToTyped) { if (!oldVNode.data && !vNode.data) { return false; } @@ -49,8 +50,8 @@ function isStyleExisted(oldVNode, vNode) { return true; } -function mergeStyle(oldStyle, newStyle) { - const mergedStyle = {}; +function mergeStyle(oldStyle: NeedToTyped, newStyle: NeedToTyped) { + const mergedStyle: NeedToTyped = {}; Object.keys(oldStyle).forEach((name) => { const oldStyleValue = oldStyle[name]; const newStyleValue = newStyle[name]; @@ -68,7 +69,7 @@ function mergeStyle(oldStyle, newStyle) { return mergedStyle; } -function patchStyle(oldVNode, vNode) { +function patchStyle(oldVNode: NeedToTyped, vNode: NeedToTyped) { if (!vNode.elm || !isStyleExisted(oldVNode, vNode)) return; // get static style, i.e. style defined in the component, style="background-color: red" @@ -97,7 +98,7 @@ function patchStyle(oldVNode, vNode) { vNode.elm.setStyles({ ...batchedStaticStyle, ...batchedStyle });; } -export function setStyle(vNode, customElem, options = {}) { +export function setStyle(vNode: NeedToTyped, customElem: NeedToTyped, options: NeedToTyped = {}) { if (!vNode || !vNode.data) { return; } diff --git a/driver/js/packages/hippy-vue/src/runtime/native.js b/driver/js/packages/hippy-vue/src/runtime/native.ts similarity index 89% rename from driver/js/packages/hippy-vue/src/runtime/native.js rename to driver/js/packages/hippy-vue/src/runtime/native.ts index d6b23b303ac..5ad74bbfdd7 100644 --- a/driver/js/packages/hippy-vue/src/runtime/native.js +++ b/driver/js/packages/hippy-vue/src/runtime/native.ts @@ -25,7 +25,9 @@ import { warn, trace, } from '../util'; -import { getElemCss } from '../renderer/native/index'; +import { getElemCss } from '../native/index'; +import { NeedToTyped } from '../types/native'; +import ViewNode from '../renderer/view-node'; import BackAndroid from './backAndroid'; import * as NetInfo from './netInfo'; @@ -52,11 +54,22 @@ const { register: HippyRegister, } = Hippy; -const CACHE = {}; +interface CacheType { + // color parser map + COLOR_PARSER?: { + [key: string]: number; + }; + // one pixel size + OnePixel?: number; + isIPhoneX?: boolean; + Device?: string; +} + +const CACHE: CacheType = {}; const LOG_TYPE = ['%c[native]%c', 'color: red', 'color: auto']; -const measureInWindowByMethod = function measureInWindowByMethod(el, method) { +const measureInWindowByMethod = function measureInWindowByMethod(el: ViewNode, method: string) { const empty = { top: -1, left: -1, @@ -70,7 +83,7 @@ const measureInWindowByMethod = function measureInWindowByMethod(el, method) { } const { nodeId } = el; trace(...LOG_TYPE, 'callUIFunction', { nodeId, funcName: method, params: [] }); - return new Promise(resolve => UIManagerModule.callUIFunction(nodeId, method, [], (pos) => { + return new Promise(resolve => UIManagerModule.callUIFunction(nodeId, method, [], (pos: NeedToTyped) => { if (!pos || typeof pos !== 'object' || typeof nodeId === 'undefined') { return resolve(empty); } @@ -89,7 +102,7 @@ const measureInWindowByMethod = function measureInWindowByMethod(el, method) { /** * Native communication module */ -const Native = { +const Native: NeedToTyped = { /** * Class native methods */ @@ -151,7 +164,7 @@ const Native = { * @param {string} url - Get the cookies by specific url. * @return {Promise} - Cookie string, like `name=someone;gender=female`. */ - getAll(url) { + getAll(url: NeedToTyped) { if (!url) { throw new TypeError('Vue.Native.Cookie.getAll() must have url argument'); } @@ -163,7 +176,7 @@ const Native = { * @param {string} keyValue - Full of key values, like `name=someone;gender=female`. * @param {Date} expireDate - Specific date of expiration. */ - set(url, keyValue, expireDate) { + set(url: NeedToTyped, keyValue: NeedToTyped, expireDate: NeedToTyped) { if (!url) { throw new TypeError('Vue.Native.Cookie.getAll() must have url argument'); } @@ -189,7 +202,7 @@ const Native = { getString() { return callNativeWithPromise.call(this, 'ClipboardModule', 'getString'); }, - setString(content) { + setString(content: NeedToTyped) { callNative.call(this, 'ClipboardModule', 'setString', content); }, }, @@ -224,7 +237,7 @@ const Native = { get Device() { if (!isDef(CACHE.Device)) { if (Platform === 'ios') { - if (global.__HIPPYNATIVEGLOBAL__ && global.__HIPPYNATIVEGLOBAL__.Device) { + if (global.__HIPPYNATIVEGLOBAL__?.Device) { CACHE.Device = global.__HIPPYNATIVEGLOBAL__.Device; } else { CACHE.Device = 'iPhone'; @@ -317,7 +330,7 @@ const Native = { /** * Call native UI methods. */ - callUIFunction(...args) { + callUIFunction(...args: NeedToTyped[]) { const [el, funcName, ...options] = args; const { nodeId } = el; let [params = [], callback] = options; @@ -333,35 +346,35 @@ const Native = { * Measure the component size and position. * @deprecated */ - measureInWindow(el) { + measureInWindow(el: NeedToTyped) { return measureInWindowByMethod(el, 'measureInWindow'); }, /** * Measure the component size and position. */ - measureInAppWindow(el) { + measureInAppWindow(el: NeedToTyped) { if (Native.Platform === 'android') { return measureInWindowByMethod(el, 'measureInWindow'); } return measureInWindowByMethod(el, 'measureInAppWindow'); }, - getBoundingClientRect(el, options) { + getBoundingClientRect(el: NeedToTyped, options: NeedToTyped) { const { nodeId } = el; return new Promise((resolve, reject) => { if (!el.isMounted || !nodeId) { return reject(new Error(`getBoundingClientRect cannot get nodeId of ${el} or ${el} is not mounted`)); } trace(...LOG_TYPE, 'UIManagerModule', { nodeId, funcName: 'getBoundingClientRect', params: options }); - UIManagerModule.callUIFunction(nodeId, 'getBoundingClientRect', [options], (res) => { + UIManagerModule.callUIFunction(nodeId, 'getBoundingClientRect', [options], (res: NeedToTyped) => { // Android error handler. if (!res || res.errMsg) { - return reject(new Error((res && res.errMsg) || 'getBoundingClientRect error with no response')); + return reject(new Error((res?.errMsg) || 'getBoundingClientRect error with no response')); } const { x, y, width, height } = res; - let bottom = undefined; - let right = undefined; + let bottom = 0; + let right = 0; if (typeof y === 'number' && typeof height === 'number') { bottom = y + height; } @@ -388,14 +401,14 @@ const Native = { * @param { {platform: "ios" | "android"} } options * @returns { Number } int32Color */ - parseColor(color, options = { platform: Native.Platform }) { + parseColor(color: NeedToTyped, options = { platform: Native.Platform }) { if (Number.isInteger(color)) { return color; } const cache = CACHE.COLOR_PARSER || (CACHE.COLOR_PARSER = Object.create(null)); if (!cache[color]) { // cache the calculation result - cache[color] = colorParser(color, options); + cache[color] = colorParser(color); } return cache[color]; }, @@ -417,7 +430,7 @@ const Native = { * * @param {string} url - Get image url. */ - getSize(url) { + getSize(url: NeedToTyped) { return callNativeWithPromise.call(this, 'ImageLoaderModule', 'getSize', url); }, @@ -426,7 +439,7 @@ const Native = { * * @param {string} url - Prefetch image url. */ - prefetch(url) { + prefetch(url: NeedToTyped) { callNative.call(this, 'ImageLoaderModule', 'prefetch', url); }, }, diff --git a/driver/js/packages/hippy-vue/src/runtime/netInfo.js b/driver/js/packages/hippy-vue/src/runtime/netInfo.ts similarity index 88% rename from driver/js/packages/hippy-vue/src/runtime/netInfo.js rename to driver/js/packages/hippy-vue/src/runtime/netInfo.ts index 07dbcacbfb1..4506154e60c 100644 --- a/driver/js/packages/hippy-vue/src/runtime/netInfo.js +++ b/driver/js/packages/hippy-vue/src/runtime/netInfo.ts @@ -18,15 +18,18 @@ * limitations under the License. */ +import { NeedToTyped } from '../types/native'; import { getApp, warn } from '../util'; import Native from './native'; -let app; +let app: NeedToTyped; const DEVICE_CONNECTIVITY_EVENT = 'networkStatusDidChange'; const subscriptions = new Map(); class NetInfoRevoker { - constructor(eventName, listener) { + eventName: string; + listener: NeedToTyped; + constructor(eventName: string, listener: NeedToTyped) { this.eventName = eventName; this.listener = listener; } @@ -47,7 +50,7 @@ class NetInfoRevoker { * @param {function} listener - Event status event callback * @returns {object} NetInfoRevoker - The event revoker for destroy the network info event listener. */ -function addEventListener(eventName, listener) { +function addEventListener(eventName: string, listener: NeedToTyped) { if (typeof listener !== 'function') { warn('NetInfo listener is not a function'); return; @@ -77,7 +80,7 @@ function addEventListener(eventName, listener) { * use `change` for listen network change. * @param {NetInfoRevoker} [listener] - The specific event listener will remove. */ -function removeEventListener(eventName, listener) { +function removeEventListener(eventName: string, listener: NeedToTyped) { if (listener instanceof NetInfoRevoker) { listener.remove(); return; @@ -109,7 +112,7 @@ function removeEventListener(eventName, listener) { function fetch() { return Native .callNativeWithPromise('NetInfo', 'getCurrentConnectivity') - .then(resp => resp.network_info); + .then((resp: NeedToTyped) => resp.network_info); } export { diff --git a/driver/js/packages/hippy-vue/src/runtime/node-ops.js b/driver/js/packages/hippy-vue/src/runtime/node-ops.ts similarity index 72% rename from driver/js/packages/hippy-vue/src/runtime/node-ops.js rename to driver/js/packages/hippy-vue/src/runtime/node-ops.ts index 74c252cf007..5b27aa56847 100644 --- a/driver/js/packages/hippy-vue/src/runtime/node-ops.js +++ b/driver/js/packages/hippy-vue/src/runtime/node-ops.ts @@ -20,6 +20,8 @@ import document from '../renderer/document-node'; import { unCacheNodeOnIdle } from '../util/node'; +import { NeedToTyped } from '../types/native'; +import ElementNode from '../renderer/element-node'; import { setAttrs } from './modules/attrs'; import { setStyle } from './modules/style'; import { setClass } from './modules/class'; @@ -27,7 +29,7 @@ import { ROOT_VIEW_ID } from './constants'; const namespaceMap = {}; -function setRootViewAttr(elm, vnode) { +function setRootViewAttr(elm: ElementNode, vnode: NeedToTyped) { // If it is root container, Vue would not call createElement, which resulted in attributes missed to set. // So set root view attributes explicitly. let isRootView = false; @@ -41,25 +43,25 @@ function setRootViewAttr(elm, vnode) { } } -function createElement(name, vnode) { +function createElement(name: string, vnode: NeedToTyped) { const elm = document.createElement(name); setRootViewAttr(elm, vnode); return elm; } -function createElementNS(namespace, name) { +function createElementNS(namespace: string, name: string) { return document.createElementNS(namespace, name); } -function createTextNode(text) { +function createTextNode(text: string) { return document.createTextNode(text); } -function createComment(text) { +function createComment(text: string) { return document.createComment(text); } -function insertBefore(pNode, newNode, referenceNode) { +function insertBefore(pNode: NeedToTyped, newNode: NeedToTyped, referenceNode: NeedToTyped) { if (pNode.childNodes.indexOf(newNode) >= 0) { // move it if the node has existed pNode.moveChild(newNode, referenceNode); @@ -68,36 +70,36 @@ function insertBefore(pNode, newNode, referenceNode) { } } -function removeChild(node, child) { +function removeChild(node: ElementNode, child: ElementNode) { node.removeChild(child); unCacheNodeOnIdle(child); } -function appendChild(node, child) { +function appendChild(node: ElementNode, child: ElementNode) { node.appendChild(child); } -function parentNode(node) { +function parentNode(node: ElementNode) { return node.parentNode; } -function nextSibling(node) { +function nextSibling(node: ElementNode) { return node.nextSibling; } -function tagName(elementNode) { +function tagName(elementNode: ElementNode) { return elementNode.tagName; } -function setTextContent(node, text) { +function setTextContent(node: ElementNode, text: string) { node.setText(text); } -function setAttribute(node, key, val) { +function setAttribute(node: ElementNode, key: string, val: NeedToTyped) { node.setAttribute(key, val); } -function setStyleScope(node, styleScopeId) { +function setStyleScope(node: ElementNode, styleScopeId: string) { // Just ignore it so far. node.setStyleScope(styleScopeId); } diff --git a/driver/js/packages/hippy-vue/src/runtime/patch.js b/driver/js/packages/hippy-vue/src/runtime/patch.ts similarity index 100% rename from driver/js/packages/hippy-vue/src/runtime/patch.js rename to driver/js/packages/hippy-vue/src/runtime/patch.ts diff --git a/driver/js/packages/hippy-vue/src/runtime/websocket.js b/driver/js/packages/hippy-vue/src/runtime/websocket.ts similarity index 87% rename from driver/js/packages/hippy-vue/src/runtime/websocket.js rename to driver/js/packages/hippy-vue/src/runtime/websocket.ts index db9eeb6fad1..2c35999af04 100644 --- a/driver/js/packages/hippy-vue/src/runtime/websocket.js +++ b/driver/js/packages/hippy-vue/src/runtime/websocket.ts @@ -18,6 +18,7 @@ * limitations under the License. */ +import { CallbackType, NeedToTyped } from '../types/native'; import { getApp, warn, @@ -33,7 +34,14 @@ const READY_STATE_CLOSED = 3; const WEB_SOCKET_MODULE_NAME = 'websocket'; const WEB_SOCKET_NATIVE_EVENT = 'hippyWebsocketEvents'; -let app; +let app: NeedToTyped; + +export interface WebsocketCallback { + onOpen?: CallbackType[]; + onClose?: CallbackType[]; + onError?: CallbackType[]; + onMessage?: CallbackType[]; +} /** * The WebSocket API is an advanced technology that makes it possible to open a two-way @@ -42,6 +50,17 @@ let app; * poll the server for a reply. */ class WebSocket { + // status code of websocket + public readyState: number; + + // websocket link to access + public url: string; + + // callback of websocket + public webSocketCallbacks: NeedToTyped; + + // webSocketId + public webSocketId = -1; /** * Returns a newly created WebSocket object. * @@ -58,7 +77,11 @@ class WebSocket { * string is assumed. * @param {Object} extrasHeaders - Http headers will append to connection. */ - constructor(url, protocols, extrasHeaders) { + constructor( + url: string, + protocols?: string | string[], + extrasHeaders?: { [key: string]: NeedToTyped }, + ) { app = getApp(); this.url = url; this.readyState = READY_STATE_CONNECTING; @@ -80,7 +103,7 @@ class WebSocket { headers, url, }; - Native.callNativeWithPromise(WEB_SOCKET_MODULE_NAME, 'connect', params).then((resp) => { + Native.callNativeWithPromise(WEB_SOCKET_MODULE_NAME, 'connect', params).then((resp: NeedToTyped) => { if (!resp || resp.code !== 0 || typeof resp.id !== 'number') { warn('Fail to create websocket connection', resp); return; @@ -101,7 +124,7 @@ class WebSocket { * is closing. This string must be no longer than 123 bytes * of UTF-8 text (not characters). */ - close(code, reason) { + close(code: NeedToTyped, reason: NeedToTyped) { if (this.readyState !== READY_STATE_OPEN) { return; } @@ -118,7 +141,7 @@ class WebSocket { * * @param {string} data - The data to send to the server. Hippy supports string type only. */ - send(data) { + send(data: NeedToTyped) { if (this.readyState !== READY_STATE_OPEN) { warn('WebSocket is not connected'); return; @@ -135,7 +158,7 @@ class WebSocket { /** * Set an EventHandler that is called when the WebSocket connection's readyState changes to OPEN; */ - set onopen(callback) { + set onopen(callback: CallbackType) { this.webSocketCallbacks.onOpen = callback; } @@ -143,14 +166,14 @@ class WebSocket { * Set an EventHandler that is called when the WebSocket connection's readyState * changes to CLOSED. */ - set onclose(callback) { + set onclose(callback: CallbackType) { this.webSocketCallbacks.onClose = callback; } /** * Set an EventHandler that is called when a message is received from the server. */ - set onerror(callback) { + set onerror(callback: CallbackType) { this.webSocketCallbacks.onError = callback; } @@ -158,7 +181,7 @@ class WebSocket { * Set an event handler property is a function which gets called when an error * occurs on the WebSocket. */ - set onmessage(callback) { + set onmessage(callback: CallbackType) { this.webSocketCallbacks.onMessage = callback; } @@ -167,7 +190,7 @@ class WebSocket { * * @param {Object} param - Native response. */ - onWebSocketEvent(param) { + onWebSocketEvent(param: NeedToTyped) { if (typeof param !== 'object' || param.id !== this.webSocketId) { return; } diff --git a/driver/js/packages/hippy-vue/src/renderer/native/style/index.js b/driver/js/packages/hippy-vue/src/style/ast-parser.ts similarity index 61% rename from driver/js/packages/hippy-vue/src/renderer/native/style/index.js rename to driver/js/packages/hippy-vue/src/style/ast-parser.ts index 70749eb95a4..415485311e8 100644 --- a/driver/js/packages/hippy-vue/src/renderer/native/style/index.js +++ b/driver/js/packages/hippy-vue/src/style/ast-parser.ts @@ -18,27 +18,31 @@ * limitations under the License. */ -import { getBeforeLoadStyle, isDev } from '../../../util'; -import parseSelector from './parser'; -import { - RuleSet, - InvalidSelector, - UniversalSelector, - IdSelector, - TypeSelector, - ClassSelector, - PseudoClassSelector, - AttributeSelector, - SimpleSelectorSequence, - Selector, -} from './css-selectors'; +import { CallbackType, NeedToTyped } from '../types/native'; +import { getBeforeLoadStyle, isDev } from '../util'; +import { CssAttribute } from './css-selectors-map'; +import { SimpleSelectorSequence } from './group/simple-selector-sequence'; +import parseSelector, { ParsedSelectorValueType, SelectorType } from './parser'; +import { RuleSet } from './ruleset'; +import { AttributeSelector } from './selector/attribute-selector'; +import { ClassSelector } from './selector/class-selector'; +import { IdSelector } from './selector/ids-selector'; +import { InvalidSelector } from './selector/invalid-selector'; +import { PseudoClassSelector } from './selector/pseudo-class-selector'; +import { Selector } from './selector/selector'; +import { SimpleSelector } from './selector/simple-selector'; +import { TypeSelector } from './selector/type-selector'; +import { UniversalSelector } from './selector/universal-selector'; -function isDeclaration(node) { +// ast 核心解析逻辑处理 + + +export function isDeclaration(node: NeedToTyped) { return node.type === 'declaration'; } -function createDeclaration(beforeLoadStyle) { - return (decl) => { +export function createDeclaration(beforeLoadStyle: CallbackType) { + return (decl: NeedToTyped) => { const newDecl = beforeLoadStyle(decl); if (isDev()) { if (!newDecl) { @@ -49,7 +53,7 @@ function createDeclaration(beforeLoadStyle) { }; } -function createSimpleSelectorFromAst(ast) { +export function createSimpleSelectorFromAst(ast: any) { switch (ast.type) { case '*': return new UniversalSelector(); case '#': return new IdSelector(ast.identifier); @@ -57,12 +61,12 @@ function createSimpleSelectorFromAst(ast) { 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); - default: return null; + default: return new InvalidSelector(new Error('createSimpleSelectorFromAst type is null'));; } } -function createSimpleSelectorSequenceFromAst(ast) { - if (ast.length === 0) { +export function createSimpleSelectorSequenceFromAst(ast?: SelectorType[]) { + if (!ast || ast.length === 0) { return new InvalidSelector(new Error('Empty simple selector sequence.')); } if (ast.length === 1) { @@ -72,28 +76,28 @@ function createSimpleSelectorSequenceFromAst(ast) { return new SimpleSelectorSequence(ast.map(createSimpleSelectorFromAst)); } -function createSelectorFromAst(ast) { +export function createSelectorFromAst(ast: ParsedSelectorValueType) { if (ast.length === 0) { return new InvalidSelector(new Error('Empty selector.')); } if (ast.length === 1) { return createSimpleSelectorSequenceFromAst(ast[0][0]); } - const simpleSelectorSequences = []; + const simpleSelectorSequences: SimpleSelector[] = []; for (let i = 0; i < ast.length; i += 1) { const simpleSelectorSequence = createSimpleSelectorSequenceFromAst(ast[i][0]); const combinator = ast[i][1]; if (combinator) { - simpleSelectorSequence.combinator = combinator; + simpleSelectorSequence.combinator = combinator as string; } simpleSelectorSequences.push(simpleSelectorSequence); } return new Selector(simpleSelectorSequences); } -function createSelector(sel) { +export function createSelector(text: string) { try { - const parsedSelector = parseSelector(sel); + const parsedSelector = parseSelector(text); if (!parsedSelector) { return new InvalidSelector(new Error('Empty selector')); } @@ -101,12 +105,13 @@ function createSelector(sel) { // [[[{type: '#', identifier: 'root'}, {type: '[]', property: 'data-v-5ef48958'}], undefined]] return createSelectorFromAst(parsedSelector.value); } catch (e) { - return new InvalidSelector(e); + return new InvalidSelector(e as Error); } } -function fromAstNodes(astRules = []) { +export function fromAstNodes(astRules: CssAttribute[] = []): RuleSet[] { const beforeLoadStyle = getBeforeLoadStyle(); + return astRules.map((rule) => { const declarations = rule.declarations .filter(isDeclaration) @@ -116,7 +121,3 @@ function fromAstNodes(astRules = []) { }); } -export { SelectorsMap } from './css-selectors-match'; -export { - fromAstNodes, -}; diff --git a/driver/js/packages/hippy-vue/src/style/css-selectors-map.ts b/driver/js/packages/hippy-vue/src/style/css-selectors-map.ts new file mode 100644 index 00000000000..27e887522ca --- /dev/null +++ b/driver/js/packages/hippy-vue/src/style/css-selectors-map.ts @@ -0,0 +1,161 @@ +/* + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ElementNode from '../renderer/element-node'; +import { CommonMapParams } from '../types/native'; +import { SelectorsMatch } from './css-selectors-match'; +import { RuleSet } from './ruleset'; +import { SelectorCore } from './selector/core-selector'; +import { SimpleSelector } from './selector/simple-selector'; + +export type CssAttribute = CommonMapParams; +export interface DocSelector { + sel: SelectorCore; + pos: number; +} +export class SelectorsMap { + public id: CssAttribute; + + public class: CssAttribute; + + public type: CssAttribute; + + // generic selector for nodes, eg.* + public universal: DocSelector[]; + + // position of node + public position: number; + + // rule set + public ruleSets: RuleSet[]; + + constructor(ruleSets: RuleSet[]) { + this.id = {}; + this.class = {}; + this.type = {}; + this.universal = []; + this.position = 0; + this.ruleSets = ruleSets; + ruleSets.forEach((rule: RuleSet) => rule.lookupSort(this)); + } + + public append(appendRules: RuleSet[]) { + this.ruleSets = this.ruleSets.concat(appendRules); + appendRules.forEach((rule: RuleSet) => rule.lookupSort(this)); + } + + public delete(hash: string) { + const removedRuleSets: RuleSet[] = []; + this.ruleSets = this.ruleSets.filter((rule: RuleSet) => { + if (rule.hash !== hash) { + return true; + } + removedRuleSets.push(rule); + return false; + }); + removedRuleSets.forEach(rule => rule.removeSort(this)); + } + + public query(node: ElementNode) { + const { tagName, id, classList } = node; + const selectorClasses = [ + this.universal, + this.id[id], + this.type[tagName], + ]; + if (classList.size) { + classList.forEach((c: string) => selectorClasses.push(this.class[c])); + } + const selectors = selectorClasses + .filter(arr => !!arr) + .reduce((cur, next) => cur.concat(next || []), []); + + const selectorsMatch = new SelectorsMatch(); + + selectorsMatch.selectors = selectors + .filter((sel: DocSelector) => (sel.sel as SimpleSelector).accumulateChanges(node, selectorsMatch)) + .sort((a: DocSelector, b: DocSelector) => a.sel.specificity - b.sel.specificity || a.pos - b.pos) + .map((docSel: DocSelector) => docSel.sel); + + return selectorsMatch; + } + + public sortById(id: string, sel: SelectorCore) { + this.addToMap(this.id, id, sel); + } + + public sortByClass(cssClass: string, sel: SelectorCore) { + this.addToMap(this.class, cssClass, sel); + } + + public sortByType(cssType: string, sel: SelectorCore) { + this.addToMap(this.type, cssType, sel); + } + + public removeById(id: string, sel: SelectorCore) { + this.removeFromMap(this.id, id, sel); + } + + public removeByClass(cssClass: string, sel: SelectorCore) { + this.removeFromMap(this.class, cssClass, sel); + } + + public removeByType(cssType: string, sel: SelectorCore) { + this.removeFromMap(this.type, cssType, sel); + } + + public sortAsUniversal(sel: SelectorCore) { + this.universal.push(this.makeDocSelector(sel)); + } + + public removeAsUniversal(sel: SelectorCore) { + const index = this.universal.findIndex((item: DocSelector) => item.sel.ruleSet?.hash === sel.ruleSet?.hash); + if (index !== -1) { + this.universal.splice(index); + } + } + + public addToMap(attribute: CssAttribute, head: string, sel: SelectorCore) { + const map = attribute; + this.position += 1; + const list = map[head]; + if (list) { + list.push(this.makeDocSelector(sel)); + } else { + map[head] = [this.makeDocSelector(sel)]; + } + } + + public removeFromMap(attribute: CssAttribute, head: string, sel: SelectorCore) { + const map = attribute; + const list = map[head]; + const index = list.findIndex((item: DocSelector) => item.sel.ruleSet?.hash === sel.ruleSet?.hash); + if (index !== -1) { + list.splice(index, 1); + } + } + + public makeDocSelector(sel: SelectorCore) { + this.position += 1; + return { sel, pos: this.position }; + } +} + + diff --git a/driver/js/packages/hippy-vue/src/style/css-selectors-match.ts b/driver/js/packages/hippy-vue/src/style/css-selectors-match.ts new file mode 100644 index 00000000000..081394d8a80 --- /dev/null +++ b/driver/js/packages/hippy-vue/src/style/css-selectors-match.ts @@ -0,0 +1,58 @@ +/* + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ElementNode from '../renderer/element-node'; +import { CommonMapParams, NeedToTyped } from '../types/native'; + +/** + * Selector Map + */ +export class SelectorsMatch { + public changeMap: NeedToTyped; + public selectors: SelectorsMatch[] | undefined; + + constructor() { + this.changeMap = new Map(); + } + + public addAttribute(node: ElementNode, attribute: NeedToTyped): void { + const deps = this.properties(node); + if (!deps.attributes) { + deps.attributes = new Set(); + } + deps.attributes.add(attribute); + } + + public addPseudoClass(node: ElementNode, pseudoClass: string): void { + const deps = this.properties(node); + if (!deps.pseudoClasses) { + deps.pseudoClasses = new Set(); + } + deps.pseudoClasses.add(pseudoClass); + } + + public properties(node: ElementNode): CommonMapParams { + let set = this.changeMap.get(node); + if (!set) { + this.changeMap.set(node, set = {}); + } + return set; + } +} diff --git a/driver/js/packages/hippy-vue/src/style/group/child-group.ts b/driver/js/packages/hippy-vue/src/style/group/child-group.ts new file mode 100644 index 00000000000..a5beb55e33a --- /dev/null +++ b/driver/js/packages/hippy-vue/src/style/group/child-group.ts @@ -0,0 +1,70 @@ +/* + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ViewNode from '../../renderer/view-node'; +import { SelectorsMatch } from '../css-selectors-match'; +import { SiblingGroup } from './sibling-group'; + +export class ChildGroup { + public dynamic: boolean; + public selectors: SiblingGroup[]; + + public constructor(selectors: SiblingGroup[]) { + this.selectors = selectors; + this.dynamic = selectors.some((sel: SiblingGroup) => sel.dynamic); + } + + public match(matchNode?: ViewNode): ViewNode | undefined { + let node: ViewNode | undefined = matchNode; + if (!node) return undefined; + const pass = this.selectors.every((sel: SiblingGroup, i: number) => { + if (i !== 0) { + node = node?.parentNode; + } + return !!node && !!sel.match(node); + }); + return pass ? node : undefined; + } + + public mayMatch(matchNode?: ViewNode): ViewNode | undefined { + let node: ViewNode | undefined = matchNode; + if (!node) return undefined; + const pass = this.selectors.every((sel: SiblingGroup, i: number) => { + if (i !== 0) { + node = node?.parentNode; + } + return !!node && !!sel.mayMatch(node); + }); + return pass ? node : undefined; + } + + public trackChanges(matchNode: ViewNode, map: SelectorsMatch) { + let node: ViewNode | undefined = matchNode; + this.selectors.forEach((sel: SiblingGroup, i: number) => { + if (i !== 0) { + node = node?.parentNode; + } + if (!node) { + return; + } + sel.trackChanges(node, map); + }); + } +} diff --git a/driver/js/packages/hippy-vue/src/style/group/sibling-group.ts b/driver/js/packages/hippy-vue/src/style/group/sibling-group.ts new file mode 100644 index 00000000000..217cf52efe9 --- /dev/null +++ b/driver/js/packages/hippy-vue/src/style/group/sibling-group.ts @@ -0,0 +1,72 @@ +/* + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ElementNode from '../../renderer/element-node'; +import ViewNode from '../../renderer/view-node'; +import { SelectorsMatch } from '../css-selectors-match'; +import { SimpleSelector } from '../selector/simple-selector'; + +export class SiblingGroup { + public dynamic: boolean; + public selectors: SimpleSelector[]; + + public constructor(selectors: SimpleSelector[]) { + this.selectors = selectors; + this.dynamic = selectors.some((sel: SimpleSelector) => sel.dynamic); + } + + public match(matchNode?: ViewNode): ViewNode | undefined { + let node: ViewNode | undefined = matchNode; + if (!node) return undefined; + const pass = this.selectors.every((sel: SimpleSelector, i: number) => { + if (i !== 0) { + node = node?.nextSibling; + } + return !!node && !!sel.match(node); + }); + return pass ? node : undefined; + } + + public mayMatch(matchNode?: ViewNode): ViewNode | undefined { + let node: ViewNode | undefined = matchNode; + if (!node) return undefined; + const pass = this.selectors.every((sel: SimpleSelector, i: number) => { + if (i !== 0) { + node = node?.nextSibling; + } + return !!node && !!sel.mayMatch(node); + }); + return pass ? node : undefined; + } + + public trackChanges(matchNode: ViewNode, map: SelectorsMatch) { + let node: ViewNode | undefined = matchNode; + this.selectors.forEach((sel: SimpleSelector, i: number) => { + if (i !== 0) { + node = node?.nextSibling; + } + if (!node) { + return; + } + sel.trackChanges(node as ElementNode, map); + }); + } +} + diff --git a/driver/js/packages/hippy-vue/src/style/group/simple-selector-sequence.ts b/driver/js/packages/hippy-vue/src/style/group/simple-selector-sequence.ts new file mode 100644 index 00000000000..31abd2570e7 --- /dev/null +++ b/driver/js/packages/hippy-vue/src/style/group/simple-selector-sequence.ts @@ -0,0 +1,76 @@ +/* + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ElementNode from '../../renderer/element-node'; +import ViewNode from '../../renderer/view-node'; +import { SelectorsMap } from '../css-selectors-map'; +import { SelectorsMatch } from '../css-selectors-match'; +import { SelectorCore } from '../selector/core-selector'; +import { SimpleSelector } from '../selector/simple-selector'; +import { wrap } from '../util'; + +export class SimpleSelectorSequence extends SimpleSelector { + public head: SimpleSelector | null | boolean; + public selectors: SimpleSelector[]; + + public constructor(selectors: SimpleSelector[]) { + super(); + this.specificity = selectors.reduce((sum, sel) => sel.specificity + sum, 0); + this.head = selectors.reduce( + (prev: null | boolean | SimpleSelector, curr: SimpleSelector) => (!prev + || (prev instanceof SimpleSelector && curr.rarity > prev.rarity) + ? curr + : prev), + null, + ); + this.dynamic = selectors.some((sel: SimpleSelector) => sel.dynamic); + this.selectors = selectors; + } + + public toString() { + return `${this.selectors.join('')}${wrap(this.combinator || '')}`; + } + + public match(node: ViewNode): boolean { + if (!node) return false; + return this.selectors.every((sel: SimpleSelector) => sel.match(node)); + } + + public mayMatch(node: ViewNode): boolean { + if (!node) return false; + return this.selectors.every((sel: SimpleSelector) => sel.mayMatch(node)); + } + + public trackChanges(node: ViewNode, match: SelectorsMatch) { + this.selectors.forEach((sel: SimpleSelector) => sel.trackChanges(node as ElementNode, match)); + } + + public lookupSort(sorter: SelectorsMap, base: SelectorCore) { + if (this.head && this.head instanceof SimpleSelector) { + this.head.lookupSort(sorter, base || this); + } + } + + public removeSort(sorter: SelectorsMap, base: SelectorCore) { + if (this.head && this.head instanceof SimpleSelector) { + this.head.removeSort(sorter, base || this); + } + } +} diff --git a/driver/js/packages/hippy-vue/types/index.d.ts b/driver/js/packages/hippy-vue/src/style/index.ts similarity index 71% rename from driver/js/packages/hippy-vue/types/index.d.ts rename to driver/js/packages/hippy-vue/src/style/index.ts index b90d858a1ea..f16af5f6f97 100644 --- a/driver/js/packages/hippy-vue/types/index.d.ts +++ b/driver/js/packages/hippy-vue/src/style/index.ts @@ -18,18 +18,5 @@ * limitations under the License. */ -/* eslint-disable import/no-extraneous-dependencies */ - -import Vue from 'vue'; -// eslint-disable-next-line import/no-unresolved -import Native from './native'; - -export default Vue; - -export as namespace Vue; - -declare module 'vue/types/vue' { - interface VueConstructor { - Native: Native; - } -} +export * from './ast-parser'; +export * from './css-selectors-map'; diff --git a/driver/js/packages/hippy-vue/src/renderer/native/style/parser.js b/driver/js/packages/hippy-vue/src/style/parser.ts similarity index 70% rename from driver/js/packages/hippy-vue/src/renderer/native/style/parser.js rename to driver/js/packages/hippy-vue/src/style/parser.ts index a7cba8fd855..c8d8e04ddd5 100644 --- a/driver/js/packages/hippy-vue/src/renderer/native/style/parser.js +++ b/driver/js/packages/hippy-vue/src/style/parser.ts @@ -18,6 +18,8 @@ * limitations under the License. */ +import { NeedToTyped } from '../types/native'; + /* eslint-disable no-param-reassign */ // Check the Regexp is support sticky flag. @@ -31,7 +33,7 @@ const REGEXP_SUPPORTING_STICKY_FLAG = (() => { // Regexp strings -const REGEXP_STRINGS = { +const REGEXP_STRINGS: { [key: string]: string } = { whiteSpaceRegEx: '\\s*', universalSelectorRegEx: '\\*', simpleIdentifierSelectorRegEx: '(#|\\.|:|\\b)([_-\\w][_-\\w\\d]*)', @@ -39,11 +41,38 @@ const REGEXP_STRINGS = { combinatorRegEx: '\\s*(\\+|~|>)?\\s*', }; +export interface SelectorParsedType { + start: number | undefined; + end: number | undefined; + value: ParsedSelectorValueType; +} + +export type PairValueType = [SelectorType[] | undefined, undefined | string]; + +export interface CombinatorType { + start: number | undefined; + end: number | undefined; + value: string; +} +export interface SelectorType { + type: string; + identifier?: string; + property?: string; + test?: string; + value?: string; +} + +export type ParsedSelectorValueType = (SelectorType[][] | PairValueType)[]; + // RegExp instance cache -const REGEXPS = {}; +const REGEXPS: { [key: string]: RegExp } = {}; // Execute the RegExp -function execRegExp(regexpKey, text, start) { +function execRegExp( + regexpKey: string, + text: string, + start: number | undefined, +) { let flags = ''; // Check the sticky flag supporting, and cache the RegExp instance. if (REGEXP_SUPPORTING_STICKY_FLAG) { @@ -56,7 +85,7 @@ function execRegExp(regexpKey, text, start) { let result; // Fallback to split the text if sticky is not supported. if (REGEXP_SUPPORTING_STICKY_FLAG) { - regexp.lastIndex = start; + regexp.lastIndex = start || 0; result = regexp.exec(text); } else { text = text.slice(start, text.length); @@ -68,7 +97,7 @@ function execRegExp(regexpKey, text, start) { }; } // add start index to prevent infinite loop caused by class name like .aa_bb.aa - regexp.lastIndex = start + result[0].length; + regexp.lastIndex = start || 0 + result[0].length; } return { result, @@ -76,7 +105,10 @@ function execRegExp(regexpKey, text, start) { }; } -function parseUniversalSelector(text, start) { +function parseUniversalSelector( + text: string, + start: number | undefined, +) { const { result, regexp } = execRegExp('universalSelectorRegEx', text, start); if (!result) { return null; @@ -91,7 +123,10 @@ function parseUniversalSelector(text, start) { }; } -function parseSimpleIdentifierSelector(text, start) { +function parseSimpleIdentifierSelector( + text: string, + start: number | undefined, +) { const { result, regexp } = execRegExp('simpleIdentifierSelectorRegEx', text, start); if (!result) { return null; @@ -107,7 +142,10 @@ function parseSimpleIdentifierSelector(text, start) { }; } -function parseAttributeSelector(text, start) { +function parseAttributeSelector( + text: string, + start: number | undefined, +) { const { result, regexp } = execRegExp('attributeSelectorRegEx', text, start); if (!result) { return null; @@ -138,19 +176,25 @@ function parseAttributeSelector(text, start) { }; } -function parseSimpleSelector(text, start) { +function parseSimpleSelector( + text: string, + start: number | undefined, +) { return parseUniversalSelector(text, start) || parseSimpleIdentifierSelector(text, start) || parseAttributeSelector(text, start); } -function parseSimpleSelectorSequence(text, start) { +function parseSimpleSelectorSequence( + text: string, + start: number | undefined, +) { let simpleSelector = parseSimpleSelector(text, start); if (!simpleSelector) { return null; } let { end } = simpleSelector; - const value = []; + const value: NeedToTyped[] = []; while (simpleSelector) { value.push(simpleSelector.value); ({ end } = simpleSelector); @@ -163,7 +207,10 @@ function parseSimpleSelectorSequence(text, start) { }; } -function parseCombinator(text, start) { +function parseCombinator( + text: string, + start: number | undefined, +): CombinatorType | null { const { result, regexp } = execRegExp('combinatorRegEx', text, start); if (!result) { return null; @@ -182,23 +229,36 @@ function parseCombinator(text, start) { }; } -function parseSelector(text, start) { +/** + * parse the selector + * after parsing: + * 1、end is the index of the position where the selector ends + * 2、start is the specified start position + * 3、value is the value of the selector, including type: such as id selector, class selector, etc. + * + * @param text - selector content + * @param start - starting position + */ +function parseSelector( + text: string, + start?: number, +): SelectorParsedType { let end = start; const { result, regexp } = execRegExp('whiteSpaceRegEx', text, start); if (result) { end = regexp.lastIndex; } - const value = []; - let combinator; + const value: ParsedSelectorValueType = []; + let combinator: CombinatorType | null; let expectSimpleSelector = true; - let pair = []; + let pair: PairValueType = [undefined, undefined]; let cssText; if (REGEXP_SUPPORTING_STICKY_FLAG) { cssText = [text]; } else { cssText = text.split(' '); } - cssText.forEach((text) => { + cssText.forEach((text: string) => { if (!REGEXP_SUPPORTING_STICKY_FLAG) { if (text === '') { return; @@ -224,7 +284,7 @@ function parseSelector(text, start) { if (combinator) { ({ end } = combinator); } - expectSimpleSelector = combinator && combinator.value !== ' '; + expectSimpleSelector = !!(combinator && combinator.value !== ' '); } while (combinator); }); return { diff --git a/driver/js/packages/hippy-vue/src/style/ruleset.ts b/driver/js/packages/hippy-vue/src/style/ruleset.ts new file mode 100644 index 00000000000..32b56707107 --- /dev/null +++ b/driver/js/packages/hippy-vue/src/style/ruleset.ts @@ -0,0 +1,73 @@ +/* + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SelectorsMap } from './css-selectors-map'; +import { SelectorCore } from './selector/core-selector'; + +/** + * css node declaration type + * + * @public + */ +export interface CssDeclarationType { + type: string; + property: string; + value: string | number; +} + +type RuleSetSelector = SelectorCore & { ruleSet: RuleSet }; + +/** + * Rule Set + */ +export class RuleSet { + public selectors: SelectorCore[]; + public declarations: CssDeclarationType[]; + public hash: string; + + public constructor( + selectors: RuleSetSelector[], + declarations: CssDeclarationType[], + hash: string, + ) { + selectors.forEach((curSelector) => { + const selector = curSelector; + selector.ruleSet = this; + return null; + }); + this.hash = hash; + this.selectors = selectors; + this.declarations = declarations; + } + + public toString(): string { + return `${this.selectors.join(', ')} {${ + this.declarations.map((d: CssDeclarationType, i: number) => `${i === 0 ? ' ' : ''}${d.property}: ${d.value}`).join('; ') + }}`; + } + + public lookupSort(sorter: SelectorsMap): void { + this.selectors.forEach((sel: SelectorCore) => sel.lookupSort(sorter)); + } + + public removeSort(sorter: SelectorsMap): void { + this.selectors.forEach((sel: SelectorCore) => sel.removeSort(sorter)); + } +} diff --git a/driver/js/packages/hippy-vue/src/style/selector/attribute-selector.ts b/driver/js/packages/hippy-vue/src/style/selector/attribute-selector.ts new file mode 100644 index 00000000000..a134fbefff4 --- /dev/null +++ b/driver/js/packages/hippy-vue/src/style/selector/attribute-selector.ts @@ -0,0 +1,109 @@ +/* + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getNodeAttrVal, wrap } from '../util'; +import { SelectorsMatch } from '../css-selectors-match'; +import { isNullOrUndefined } from '../../util'; +import ElementNode from '../../renderer/element-node'; +import { SimpleSelector } from './simple-selector'; + +/** + * 属性选择器实现 + */ +export class AttributeSelector extends SimpleSelector { + // attribute of node + public attribute = ''; + + // property Test Conditions + public test = ''; + + // value of node + public value = ''; + + constructor(attribute: string, test = '', value = '') { + super(); + this.specificity = 0x00000100; + this.rarity = 0; + this.dynamic = true; + this.attribute = attribute; + this.test = test; + this.value = value; + } + + public match(node: ElementNode) { + if (!this.test) { + // HasAttribute + if (!node || !node.attributes) return false; + return !isNullOrUndefined(getNodeAttrVal(node, this.attribute)); + } + + if (!this.value) { + return false; + } + + if (!node || !node.attributes) return false; + // const escapedValue = value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); + const attr = `${getNodeAttrVal(node, this.attribute)}`; + + if (this.test === '=') { + // Equals + return attr === this.value; + } + + if (this.test === '^=') { + // PrefixMatch + return attr.startsWith(this.value); + } + + if (this.test === '$=') { + // SuffixMatch + return attr.endsWith(this.value); + } + + if (this.test === '*=') { + // SubstringMatch + return attr.indexOf(this.value) !== -1; + } + + if (this.test === '~=') { + // Includes + const words = attr.split(' '); + return words && words.indexOf(this.value) !== -1; + } + + if (this.test === '|=') { + // DashMatch + return attr === this.value || attr.startsWith(`${this.value}-`); + } + return false; + } + + public toString(): string { + return `[${this.attribute}${wrap(this.test)}${(this.test && this.value) || ''}]${wrap(this.combinator || '')}`; + } + + public mayMatch(): boolean { + return true; + } + + public trackChanges(node: ElementNode, map: SelectorsMatch) { + map.addAttribute(node, this.attribute); + } +} diff --git a/driver/js/packages/hippy-vue/src/style/selector/class-selector.ts b/driver/js/packages/hippy-vue/src/style/selector/class-selector.ts new file mode 100644 index 00000000000..e7330c4df32 --- /dev/null +++ b/driver/js/packages/hippy-vue/src/style/selector/class-selector.ts @@ -0,0 +1,59 @@ +/* + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ElementNode from '../../renderer/element-node'; +import { SelectorsMap } from '../css-selectors-map'; +import { wrap } from '../util'; +import { SelectorCore } from './core-selector'; +import { SimpleSelector } from './simple-selector'; + +/** + * 类选择器实现 + */ +export class ClassSelector extends SimpleSelector { + public className: string; + + constructor(className: string) { + super(); + this.specificity = 0x00000100; + this.rarity = 2; + this.dynamic = false; + this.className = className; + } + + public toString(): string { + return `.${this.className}${wrap(this.combinator || '')}`; + } + + public match(node: ElementNode): boolean { + if (!node) { + return false; + } + return !!node.classList?.size && node.classList.has(this.className); + } + + public lookupSort(sorter: SelectorsMap, base?: SelectorCore) { + sorter.sortByClass(this.className, base || this); + } + + public removeSort(sorter: SelectorsMap, base?: SelectorCore) { + sorter.removeByClass(this.className, base || this); + } +} diff --git a/driver/js/packages/hippy-vue/src/style/selector/core-selector.ts b/driver/js/packages/hippy-vue/src/style/selector/core-selector.ts new file mode 100644 index 00000000000..69d9c1a309c --- /dev/null +++ b/driver/js/packages/hippy-vue/src/style/selector/core-selector.ts @@ -0,0 +1,53 @@ +/* + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ElementNode from '../../renderer/element-node'; +import { SelectorsMap } from '../css-selectors-map'; +import { SelectorsMatch } from '../css-selectors-match'; +import { RuleSet } from '../ruleset'; + +/** + * 选择器基类 + */ +export class SelectorCore { + // is it a dynamic style + public dynamic?: boolean; + + // style weight + public specificity = 0; + + // rule set + public ruleSet?: RuleSet; + + public lookupSort(sorter: SelectorsMap, base?: SelectorCore): void { + sorter.sortAsUniversal(base || this); + } + + public removeSort(sorter: SelectorsMap, base?: SelectorCore): void { + sorter.removeAsUniversal(base || this); + } + + public trackChanges(node: ElementNode, map: SelectorsMatch) { + if (this.dynamic) { + // 插入动态属性 + map.addAttribute(node, ''); + } + } +} diff --git a/driver/js/packages/hippy-vue/src/style/selector/ids-selector.ts b/driver/js/packages/hippy-vue/src/style/selector/ids-selector.ts new file mode 100644 index 00000000000..f5228fedfd8 --- /dev/null +++ b/driver/js/packages/hippy-vue/src/style/selector/ids-selector.ts @@ -0,0 +1,59 @@ +/* + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ElementNode from '../../renderer/element-node'; +import { SelectorsMap } from '../css-selectors-map'; +import { wrap } from '../util'; +import { SelectorCore } from './core-selector'; +import { SimpleSelector } from './simple-selector'; + +/** + * id 选择器实现 + */ +export class IdSelector extends SimpleSelector { + public id: string; + + constructor(id: string) { + super(); + this.specificity = 0x00010000; + this.rarity = 3; + this.dynamic = false; + this.id = id; + } + + public toString(): string { + return `#${this.id}${wrap(this.combinator || '')}`; + } + + public match(node: ElementNode): boolean { + if (!node) { + return false; + } + return node.id === this.id; + } + + public lookupSort(sorter: SelectorsMap, base: SelectorCore): void { + sorter.sortById(this.id, base ?? this); + } + + public removeSort(sorter: SelectorsMap, base: SelectorCore): void { + sorter.removeById(this.id, base ?? this); + } +} diff --git a/driver/js/packages/hippy-vue/src/style/selector/invalid-selector.ts b/driver/js/packages/hippy-vue/src/style/selector/invalid-selector.ts new file mode 100644 index 00000000000..b395285d3e9 --- /dev/null +++ b/driver/js/packages/hippy-vue/src/style/selector/invalid-selector.ts @@ -0,0 +1,45 @@ +/* + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SimpleSelector } from './simple-selector'; + +/** + * 解析失败的无效选择器 + */ +export class InvalidSelector extends SimpleSelector { + public err: Error; + + public constructor(err: Error) { + super(); + this.specificity = 0x00000000; + this.rarity = 4; + this.dynamic = false; + this.combinator = undefined; + this.err = err; + } + + public toString(): string { + return ``; + } + + public match(): boolean { + return false; + } +} diff --git a/driver/js/packages/hippy-vue/src/style/selector/pseudo-class-selector.ts b/driver/js/packages/hippy-vue/src/style/selector/pseudo-class-selector.ts new file mode 100644 index 00000000000..38588f0f966 --- /dev/null +++ b/driver/js/packages/hippy-vue/src/style/selector/pseudo-class-selector.ts @@ -0,0 +1,56 @@ +/* + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ElementNode from '../../renderer/element-node'; +import { SelectorsMatch } from '../css-selectors-match'; +import { wrap } from '../util'; +import { SimpleSelector } from './simple-selector'; + +/** + * 伪类选择器实现 + */ +export class PseudoClassSelector extends SimpleSelector { + public cssPseudoClass: string; + + public constructor(cssPseudoClass: string) { + super(); + this.specificity = 0x00000100; + this.rarity = 0; + this.dynamic = true; + this.cssPseudoClass = cssPseudoClass; + } + + public toString(): string { + return `:${this.cssPseudoClass}${wrap(this.combinator || '')}`; + } + + public match(node: ElementNode): boolean { + return !!node; + } + + public mayMatch(): boolean { + return true; + } + + public trackChanges(node: ElementNode, map: SelectorsMatch) { + map.addPseudoClass(node, this.cssPseudoClass); + } +} + diff --git a/driver/js/packages/hippy-vue/src/style/selector/selector.ts b/driver/js/packages/hippy-vue/src/style/selector/selector.ts new file mode 100644 index 00000000000..a82bf700907 --- /dev/null +++ b/driver/js/packages/hippy-vue/src/style/selector/selector.ts @@ -0,0 +1,144 @@ +/* + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ViewNode from '../../renderer/view-node'; +import { SelectorsMap } from '../css-selectors-map'; +import { SelectorsMatch } from '../css-selectors-match'; +import { ChildGroup } from '../group/child-group'; +import { SiblingGroup } from '../group/sibling-group'; +import { SelectorCore } from './core-selector'; +import { SimpleSelector } from './simple-selector'; + +export class Selector extends SelectorCore { + public groups: ChildGroup[]; + public last: SelectorCore; + public selectors: SimpleSelector[]; + + public constructor(selectors: SimpleSelector[]) { + super(); + const supportedCombinator = [undefined, ' ', '>', '+']; + let siblingGroup: SimpleSelector[] = []; + let lastGroup: SimpleSelector[][] = []; + const groups: SimpleSelector[][][] = []; + this.selectors = selectors; + this.selectors.reverse().forEach((sel: SimpleSelector) => { + 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 ChildGroup(g.map(sg => new SiblingGroup(sg)))); + const [firstSelector] = selectors; + this.last = firstSelector; + this.specificity = selectors.reduce((sum: number, sel: SimpleSelector) => sel.specificity + sum, 0); + this.dynamic = selectors.some((sel: SimpleSelector) => sel.dynamic); + } + + public toString(): string { + return this.selectors.join(''); + } + + public match(matchNode?: ViewNode): boolean { + let node: ViewNode | undefined = matchNode; + return this.groups.every((group: ChildGroup, i: number) => { + if (i === 0) { + node = group.match(matchNode); + return !!node; + } + let ancestor: ViewNode | undefined = node; + while (ancestor = ancestor?.parentNode) { + if (node = group.match(ancestor)) { + return true; + } + } + return false; + }); + } + + public lookupSort(sorter: SelectorsMap): void { + this.last.lookupSort(sorter, this); + } + + public removeSort(sorter: SelectorsMap): void { + this.last.removeSort(sorter, this); + } + + public accumulateChanges(matchNode: ViewNode, map: SelectorsMatch): boolean { + let node: ViewNode | undefined = matchNode; + if (!this.dynamic) { + return this.match(node); + } + + const bounds: { + left: ViewNode; + right: ViewNode | undefined; + }[] = []; + const mayMatch = this.groups.every((group: ChildGroup, i: number) => { + if (i === 0) { + const nextNode = group.mayMatch(matchNode); + bounds.push({ left: matchNode, right: matchNode }); + node = nextNode; + return !!node; + } + let ancestor: ViewNode | undefined = matchNode; + while (ancestor = ancestor.parentNode) { + const nextNode = group.mayMatch(ancestor); + if (nextNode) { + bounds.push({ left: ancestor, right: undefined }); + 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 += 1) { + const group = this.groups[i]; + if (!group.dynamic) { + continue; + } + const bound = bounds[i]; + let leftBound: ViewNode | undefined = bound.left; + do { + if (group.mayMatch(leftBound)) { + group.trackChanges(leftBound, map); + } + } while ((leftBound !== bound.right) && (leftBound = node.parentNode)); + } + + return mayMatch; + } +} + diff --git a/driver/js/packages/hippy-vue/src/style/selector/simple-selector.ts b/driver/js/packages/hippy-vue/src/style/selector/simple-selector.ts new file mode 100644 index 00000000000..3400a860c55 --- /dev/null +++ b/driver/js/packages/hippy-vue/src/style/selector/simple-selector.ts @@ -0,0 +1,58 @@ +/* + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ElementNode from '../../renderer/element-node'; +import ViewNode from '../../renderer/view-node'; +import { SelectorsMatch } from '../css-selectors-match'; +import { SelectorCore } from './core-selector'; + +export class SimpleSelector extends SelectorCore { + // rarity of style + public rarity = 0; + + public combinator?: string; + + public accumulateChanges(node: ElementNode, map: SelectorsMatch): boolean { + if (!this.dynamic) { + return this.match(node); + } + if (this.mayMatch(node)) { + this.trackChanges(node, map); + return true; + } + return false; + } + + /** + * determine if the node matches + * @param node - target node + */ + public mayMatch(node: ViewNode): boolean { + return this.match(node); + } + + /** + * prejudgment + * @param _node - target node + */ + public match(_node?: ViewNode): boolean { + return false; + } +} diff --git a/driver/js/packages/hippy-vue/src/style/selector/type-selector.ts b/driver/js/packages/hippy-vue/src/style/selector/type-selector.ts new file mode 100644 index 00000000000..80c82bfcf36 --- /dev/null +++ b/driver/js/packages/hippy-vue/src/style/selector/type-selector.ts @@ -0,0 +1,57 @@ +/* + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ElementNode from '../../renderer/element-node'; +import { SelectorsMap } from '../css-selectors-map'; +import { wrap } from '../util'; +import { SelectorCore } from './core-selector'; +import { SimpleSelector } from './simple-selector'; + +/** + * 类型选择器实现 + */ +export class TypeSelector extends SimpleSelector { + public cssType: string; + + public constructor(cssType: string) { + super(); + this.specificity = 0x00000001; + this.rarity = 1; + this.dynamic = false; + this.cssType = cssType; + } + + public toString(): string { + return `${this.cssType}${wrap(this.combinator || '')}`; + } + + public match(node: ElementNode): boolean { + if (!node) return false; + return node.tagName === this.cssType; + } + + public lookupSort(sorter: SelectorsMap, base: SelectorCore) { + sorter.sortByType(this.cssType, base || this); + } + + public removeSort(sorter: SelectorsMap, base: SelectorCore) { + sorter.removeByType(this.cssType, base || this); + } +} diff --git a/driver/js/packages/hippy-vue/src/style/selector/universal-selector.ts b/driver/js/packages/hippy-vue/src/style/selector/universal-selector.ts new file mode 100644 index 00000000000..a5dd311b8d9 --- /dev/null +++ b/driver/js/packages/hippy-vue/src/style/selector/universal-selector.ts @@ -0,0 +1,42 @@ +/* + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { wrap } from '../util'; +import { SimpleSelector } from './simple-selector'; + +/** + * 通用选择器实现 + */ +export class UniversalSelector extends SimpleSelector { + public constructor() { + super(); + this.specificity = 0x00000000; + this.rarity = 0; + this.dynamic = false; + } + + public toString(): string { + return `*${wrap(this.combinator || '')}`; + } + + public match(): boolean { + return true; + } +} diff --git a/driver/js/packages/hippy-vue/src/style/util.ts b/driver/js/packages/hippy-vue/src/style/util.ts new file mode 100644 index 00000000000..99636a55020 --- /dev/null +++ b/driver/js/packages/hippy-vue/src/style/util.ts @@ -0,0 +1,43 @@ +/* + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import ElementNode from '../renderer/element-node'; + +export function wrap(text: string) { + return text ? ` ${text} ` : ''; +} + + +/** + * get node attribute or styleScopeId value + * @param node + * @param attribute + * @returns {*} + */ +export const getNodeAttrVal = (node: ElementNode, attribute: string) => { + const attr = node.attributes[attribute]; + if (typeof attr !== 'undefined') { + return attr; + } + if (Array.isArray(node.styleScopeId) && node.styleScopeId.includes(attribute)) { + return attribute; + } +}; + diff --git a/driver/js/packages/hippy-vue/src/types/index.ts b/driver/js/packages/hippy-vue/src/types/index.ts new file mode 100644 index 00000000000..784d4e4f627 --- /dev/null +++ b/driver/js/packages/hippy-vue/src/types/index.ts @@ -0,0 +1,48 @@ +/* + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint-disable import/no-extraneous-dependencies */ + +import Vue from 'vue'; +// eslint-disable-next-line import/no-unresolved +import Native, { NeedToTyped } from './native'; + +export default Vue; + +declare module 'vue/types/vue' { + interface VueConstructor { + Native: Native; + } +} + +declare global { + // eslint-disable-next-line no-var,@typescript-eslint/naming-convention,vars-on-top + var Hippy: NeedToTyped; + // eslint-disable-next-line no-var,@typescript-eslint/naming-convention,vars-on-top + var __VUE_ROOT_INSTANCES__: NeedToTyped; + // eslint-disable-next-line no-var,@typescript-eslint/naming-convention,vars-on-top + var ConsoleModule: NeedToTyped; + // eslint-disable-next-line no-var,@typescript-eslint/naming-convention,vars-on-top + var __HIPPYNATIVEGLOBAL__: NeedToTyped; + // eslint-disable-next-line no-var,@typescript-eslint/naming-convention,vars-on-top + var __GLOBAL__: NeedToTyped; + // eslint-disable-next-line no-var,vars-on-top + var localStorage: Storage; +} diff --git a/driver/js/packages/hippy-vue/types/native.d.ts b/driver/js/packages/hippy-vue/src/types/native.ts similarity index 78% rename from driver/js/packages/hippy-vue/types/native.d.ts rename to driver/js/packages/hippy-vue/src/types/native.ts index f93e449c031..d923b39a474 100644 --- a/driver/js/packages/hippy-vue/types/native.d.ts +++ b/driver/js/packages/hippy-vue/src/types/native.ts @@ -97,15 +97,15 @@ interface Native { * @param {string} eventName - The event name will be trigger. * @param {any} args - Event callback arguments. */ - emit: (eventName: string, ...args: any[]) => void; + emit: (eventName: string, ...args: NeedToTyped[]) => void; /** * Call native UI methods. */ callUIFunction: ( - el: Record, - funcName: any, - ...args: any[] + el: Record, + funcName: NeedToTyped, + ...args: NeedToTyped[] ) => void; /** @@ -121,7 +121,7 @@ interface Native { callNative: ( moduleName: CallNativeModuleName, methodName: CallNativeMethodName, - ...args: any[] + ...args: NeedToTyped[] ) => void; /** @@ -130,8 +130,8 @@ interface Native { callNativeWithPromise: ( moduleName: CallNativeModuleName, methodName: CallNativeMethodName, - ...args: any[] - ) => Promise; + ...args: NeedToTyped[] + ) => Promise; /** * Call native with callId returns @@ -139,8 +139,8 @@ interface Native { callNativeWithCallbackId: ( moduleName: CallNativeModuleName, methodName: CallNativeMethodName, - ...args: any[] - ) => any; + ...args: NeedToTyped[] + ) => NeedToTyped; /** * Draw UI with native language. @@ -148,24 +148,36 @@ interface Native { UIManagerModule: UIManagerModule; } +export interface NativeNodeProps { + [key: string]: NeedToTyped; +} + +export type NeedToTyped = any; + +export type CallbackType = Function; + +export interface CommonMapParams { + [key: string]: NeedToTyped; +} + interface UIManagerModule { - createNode: (rootViewId: any, queue: any) => void; - updateNode: (rootViewId: any, queue: any) => void; - deleteNode: (rootViewId: any, queue: any) => void; - flushBatch: (rootViewId: any, queue: any) => void; - setNodeTree: (rootViewId: any, newNodeTree: any) => void; - setNodeId: (rootViewId: any, cacheIdList: any) => void; - getNodeById: (nodeId: any) => any; - getNodeIdByRef: (ref: any) => any; + createNode: (rootViewId: NeedToTyped, queue: NeedToTyped) => void; + updateNode: (rootViewId: NeedToTyped, queue: NeedToTyped) => void; + deleteNode: (rootViewId: NeedToTyped, queue: NeedToTyped) => void; + flushBatch: (rootViewId: NeedToTyped, queue: NeedToTyped) => void; + setNodeTree: (rootViewId: NeedToTyped, newNodeTree: NeedToTyped) => void; + setNodeId: (rootViewId: NeedToTyped, cacheIdList: NeedToTyped) => void; + getNodeById: (nodeId: NeedToTyped) => NeedToTyped; + getNodeIdByRef: (ref: NeedToTyped) => NeedToTyped; callUIFunction: ( - node: any, - funcName: any, - paramList?: any, - callback?: any + node: NeedToTyped, + funcName: NeedToTyped, + paramList?: NeedToTyped, + callback?: NeedToTyped ) => void; - measureInWindow: (node: any, callBack: any) => void; - startBatch: (renderId: any) => void; - endBatch: (renderId: any) => void; + measureInWindow: (node: NeedToTyped, callBack: NeedToTyped) => void; + startBatch: (renderId: NeedToTyped) => void; + endBatch: (renderId: NeedToTyped) => void; sendRenderError: (error: Error) => void; } diff --git a/driver/js/packages/hippy-vue/src/util/__tests__/entity-decoder.test.js b/driver/js/packages/hippy-vue/src/util/__tests__/entity-decoder.test.js deleted file mode 100644 index e48131c7987..00000000000 --- a/driver/js/packages/hippy-vue/src/util/__tests__/entity-decoder.test.js +++ /dev/null @@ -1,7 +0,0 @@ -import test from 'ava'; -import { decode } from '../entity-decoder'; - -test('[entity-decoder] should return the html it self', (t) => { - const html = 'Hello world'; - t.is(decode(html), html); -}); diff --git a/driver/js/packages/hippy-vue/src/util/__tests__/i18n.test.js b/driver/js/packages/hippy-vue/src/util/__tests__/i18n.test.js deleted file mode 100644 index 7db1cb67dbf..00000000000 --- a/driver/js/packages/hippy-vue/src/util/__tests__/i18n.test.js +++ /dev/null @@ -1,6 +0,0 @@ -import test from 'ava'; -import { isRTL } from '../i18n'; - -test('isRTL check should return false by default', (t) => { - t.is(isRTL(), false); -}); diff --git a/driver/js/packages/hippy-vue/src/util/__tests__/index.test.js b/driver/js/packages/hippy-vue/src/util/__tests__/index.test.js deleted file mode 100644 index 05fdb7eb70c..00000000000 --- a/driver/js/packages/hippy-vue/src/util/__tests__/index.test.js +++ /dev/null @@ -1,130 +0,0 @@ -import test from 'ava'; -import * as util from '../index'; - -test('The Vue of setVue should equal the getVue', (t) => { - const Vue = {}; - t.is(util.setVue(Vue), undefined); - t.is(util.getVue(), Vue); -}); - -test('The Vue of setApp should equal the getApp', (t) => { - const App = {}; - t.is(util.setApp(App), undefined); - t.is(util.getApp(), App); -}); - -test('trace output test', (t) => { - const Vue = { - config: {}, - }; - const { release } = process; - delete process.release; - util.setVue(Vue); - util.trace('Hello world', { a: 1 }); - t.pass(); - Vue.config.silent = true; - util.trace('Hello world', { a: 1 }); - t.pass(); - Vue.config.silent = false; - process.env.NODE_ENV = 'production'; - util.trace('Hello world', { a: 1 }); - t.pass(); - delete process.env.NODE_ENV; - process.release = release; - // TODO: Added console.log simulate, but sinon can't work with nyc. -}); - -test('warn output test', (t) => { - const Vue = { - config: {}, - }; - util.setVue(Vue); - util.warn('Hello world', { a: 1 }); - t.pass(); - Vue.config.silent = true; - util.warn('Hello world', { a: 1 }); - t.pass(); - process.env.NODE_ENV = 'production'; - util.warn('Hello world', { a: 1 }); - t.pass(); - delete process.env.NODE_ENV; - // TODO: Added console.warn simulate, but sinon can't work with nyc. -}); - -test('capitalizeFirstLetter output test', (t) => { - t.is(util.capitalizeFirstLetter('hello'), 'Hello'); - t.is(util.capitalizeFirstLetter('World'), 'World'); - t.is(util.capitalizeFirstLetter('123'), '123'); -}); - -test('tryConvertNumber output test', (t) => { - t.is(util.tryConvertNumber(123), 123); - t.is(util.tryConvertNumber('123'), 123); - t.is(util.tryConvertNumber('abc'), 'abc'); - t.is(util.tryConvertNumber('123abc'), '123abc'); - t.is(util.tryConvertNumber('abc123'), 'abc123'); - t.is(util.tryConvertNumber('12e3'), 12000); - t.is(util.tryConvertNumber('123.12'), 123.12); - t.is(util.tryConvertNumber('123.'), 123); - t.is(util.tryConvertNumber('.123'), 0.123); - t.is(util.tryConvertNumber('+.123'), 0.123); - t.is(util.tryConvertNumber('-.123'), -0.123); - t.is(util.tryConvertNumber('.123.'), '.123.'); - t.is(util.tryConvertNumber('.123.1'), '.123.1'); - t.is(util.tryConvertNumber(''), ''); - const obj = {}; - t.is(util.tryConvertNumber(obj), obj); -}); - -test('unicodeToChar output test', (t) => { - t.is(util.unicodeToChar('Hippy Vue \\u793a\\u4f8b'), 'Hippy Vue 示例'); - t.is(util.unicodeToChar('Hippy Vue 示例'), 'Hippy Vue 示例'); - t.is(util.unicodeToChar('\\u5c71\\u6da6\\xb7\\u6c34\\u6da6\\xb7\\u7269\\u6da6\\xb7\\u6e29\\u6da6\\u4fdd\\u5c71'), '山润·水润·物润·温润保山'); -}); - -test('arrayCount test', (t) => { - const arr = new Array(10).fill(0) - .map((a, index) => index); - t.is(util.arrayCount(arr, a => a === 1), 1); - t.is(util.arrayCount(arr, a => a < 5), 5); -}); - -test('isFunction test', (t) => { - function foo() { - return true; - } - t.true(util.isFunction(foo)); - t.true(util.isFunction(() => true)); - t.false(util.isFunction(undefined)); - t.false(util.isFunction(null)); - t.false(util.isFunction({})); - t.false(util.isFunction(new Date())); // Date is function - t.false(util.isFunction(String('foobar'))); // String is function too - t.false(util.isFunction(123)); - t.false(util.isFunction('abc')); - t.false(util.isFunction(true)); -}); - -test('setsAreEqual test', (t) => { - t.true(util.setsAreEqual(new Set([1, 2, 3]), new Set([1, 2, 3]))); - t.true(util.setsAreEqual(new Set(['a', 'b', 'c']), new Set(['a', 'b', 'c']))); - t.false(util.setsAreEqual(new Set([1, 2]), new Set([1, 2, 3]))); - t.false(util.setsAreEqual(new Set([1, 2, 3]), new Set([1, 2]))); - t.false(util.setsAreEqual(new Set([1, 2, 3]), new Set([1, 2, 4]))); - const err2 = t.throws(() => { - util.setsAreEqual('a', 'b'); - }); - t.is(err2.message, 'as.values is not a function'); -}); - -test('endsWith test', (t) => { - t.true(util.endsWith('100px', 'px')); - t.false(util.endsWith('100', 'px')); - t.true(util.endsWith('px', 'px')); - t.false(util.endsWith('x', 'px')); - delete String.prototype.endsWith; - const str = 'To be, or not to be, that is the question.'; - t.true(util.endsWith(str, 'question.')); - t.false(util.endsWith(str, 'to be')); - t.true(util.endsWith(str, 'to be', 19)); -}); diff --git a/driver/js/packages/hippy-vue/src/util/__tests__/node.test.js b/driver/js/packages/hippy-vue/src/util/__tests__/node.test.js deleted file mode 100644 index 5fb085f858c..00000000000 --- a/driver/js/packages/hippy-vue/src/util/__tests__/node.test.js +++ /dev/null @@ -1,25 +0,0 @@ -import test from 'ava'; -import { recursivelyUnCacheNode, requestIdleCallback, cancelIdleCallback, preCacheNode, getNodeById } from '../node'; -import ElementNode from '../../renderer/element-node'; - -test('check node cache operation', (t) => { - const node = new ElementNode('div'); - const childNode = new ElementNode('div'); - node.nodeId = 12; - childNode.nodeId = 13; - node.childNodes = [childNode]; - preCacheNode(node, 12); - preCacheNode(childNode, 13); - let cachedCode = getNodeById(12); - t.is(node, cachedCode); - let cachedChildCode = getNodeById(13); - t.is(childNode, cachedChildCode); - recursivelyUnCacheNode(node); - cachedCode = getNodeById(12); - t.is(cachedCode, null); - cachedChildCode = getNodeById(13); - t.is(cachedChildCode, null); - const id = requestIdleCallback(() => {}); - cancelIdleCallback(id); - t.pass(); -}); diff --git a/driver/js/packages/hippy-vue/src/util/entity-decoder.js b/driver/js/packages/hippy-vue/src/util/entity-decoder.ts similarity index 97% rename from driver/js/packages/hippy-vue/src/util/entity-decoder.js rename to driver/js/packages/hippy-vue/src/util/entity-decoder.ts index 3493a9f7f68..1b7bd752f5e 100644 --- a/driver/js/packages/hippy-vue/src/util/entity-decoder.js +++ b/driver/js/packages/hippy-vue/src/util/entity-decoder.ts @@ -19,7 +19,6 @@ */ function decode(html) { - // TODO: decode html return html; } @@ -29,3 +28,5 @@ export default { export { decode, }; + + diff --git a/driver/js/packages/hippy-vue/src/util/event.js b/driver/js/packages/hippy-vue/src/util/event.ts similarity index 88% rename from driver/js/packages/hippy-vue/src/util/event.js rename to driver/js/packages/hippy-vue/src/util/event.ts index f9dcd8928e1..f43cf5216f1 100644 --- a/driver/js/packages/hippy-vue/src/util/event.js +++ b/driver/js/packages/hippy-vue/src/util/event.ts @@ -18,16 +18,16 @@ * limitations under the License. */ +import { NeedToTyped } from '../types/native'; + const EventHandlerType = { ADD: 0, REMOVE: 1, }; -const NativeEventMap = { +const NativeEventMap: NeedToTyped = { onClick: 'click', onLongClick: 'longclick', - // onPressIn: 'touchstart', // normalization - // onPressOut: 'touchend', // normalization onPressIn: 'pressin', onPressOut: 'pressout', onTouchDown: 'touchstart', // compatible with w3c standard name touchstart @@ -44,11 +44,11 @@ const DOMEventPhase = { BUBBLING_PHASE: 3, }; -function isNativeGesture(name) { +function isNativeGesture(name: keyof typeof NativeEventMap) { return !!NativeEventMap[name]; } -function translateToNativeEventName(name) { +function translateToNativeEventName(name: string) { return name.replace(/^(on)?/g, '').toLocaleLowerCase(); } diff --git a/driver/js/packages/hippy-vue/src/util/i18n.js b/driver/js/packages/hippy-vue/src/util/i18n.ts similarity index 87% rename from driver/js/packages/hippy-vue/src/util/i18n.js rename to driver/js/packages/hippy-vue/src/util/i18n.ts index 96a3e8fe0d4..baf3c973bdc 100644 --- a/driver/js/packages/hippy-vue/src/util/i18n.js +++ b/driver/js/packages/hippy-vue/src/util/i18n.ts @@ -20,14 +20,18 @@ import Native from '../runtime/native'; +export enum TextDirection { + RTL = 1 +} + /** * is right to left display * @returns {boolean} */ -export function isRTL() { +export function isRTL(): boolean { const localization = Native.Localization; if (localization) { - return localization.direction === 1; + return localization.direction === TextDirection.RTL; } return false; } diff --git a/driver/js/packages/hippy-vue/src/util/index.js b/driver/js/packages/hippy-vue/src/util/index.ts similarity index 81% rename from driver/js/packages/hippy-vue/src/util/index.js rename to driver/js/packages/hippy-vue/src/util/index.ts index 858ee13bdbf..028be8a41fa 100644 --- a/driver/js/packages/hippy-vue/src/util/index.js +++ b/driver/js/packages/hippy-vue/src/util/index.ts @@ -23,12 +23,13 @@ import { once } from 'shared/util'; import { HIPPY_DEBUG_ADDRESS, HIPPY_STATIC_PROTOCOL } from '../runtime/constants'; +import { CallbackType, NeedToTyped } from '../types/native'; const VUE_VERSION = process.env.VUE_VERSION; const HIPPY_VUE_VERSION = process.env.HIPPY_VUE_VERSION; -let _App; -let _Vue; +let _App: NeedToTyped; +let _Vue: NeedToTyped; /** * Style pre-process hook @@ -41,10 +42,10 @@ let _Vue; * @param {string|number} decl.value - Style property value. * @returns {Object} decl - Processed declaration, original declaration by default. */ -let _beforeLoadStyle = decl => decl; +let _beforeLoadStyle = (decl: NeedToTyped) => decl; let _beforeRenderToNative = () => {}; -function setVue(Vue) { +function setVue(Vue: NeedToTyped) { _Vue = Vue; } @@ -52,7 +53,7 @@ function getVue() { return _Vue; } -function setApp(app) { +function setApp(app: NeedToTyped) { _App = app; } @@ -60,7 +61,7 @@ function getApp() { return _App; } -function setBeforeLoadStyle(beforeLoadStyle) { +function setBeforeLoadStyle(beforeLoadStyle: NeedToTyped) { _beforeLoadStyle = beforeLoadStyle; } @@ -68,7 +69,7 @@ function getBeforeLoadStyle() { return _beforeLoadStyle; } -function setBeforeRenderToNative(beforeRenderToNative) { +function setBeforeRenderToNative(beforeRenderToNative: NeedToTyped) { _beforeRenderToNative = beforeRenderToNative; } @@ -86,26 +87,26 @@ function isDev() { function isTraceEnabled() { return !(!isDev() - || (process && process.release) - || (_Vue && _Vue.config.silent)); + || (process?.release) + || (_Vue?.config.silent)); } -function trace(...context) { +function trace(...context: NeedToTyped[]) { if (isTraceEnabled()) { console.log(...context); - } else if (_Vue && _Vue.config.silent) { + } else if (_Vue?.config.silent) { infoTrace(); } } -function warn(...context) { +function warn(...context: NeedToTyped[]) { if (!isDev()) { return null; } return console.warn(...context); } -function capitalizeFirstLetter(str) { +function capitalizeFirstLetter(str: NeedToTyped) { return str.charAt(0).toUpperCase() + str.slice(1); } @@ -113,7 +114,7 @@ function capitalizeFirstLetter(str) { * Convert string to number as possible */ const numberRegEx = new RegExp('^(?=.+)[+-]?\\d*\\.?\\d*([Ee][+-]?\\d+)?$'); -function tryConvertNumber(str) { +function tryConvertNumber(str: string) { if (typeof str === 'number') { return str; } @@ -127,11 +128,11 @@ function tryConvertNumber(str) { return str; } -function unicodeToChar(text) { - return text.replace(/\\u[\dA-F]{4}|\\x[\dA-F]{2}/gi, match => String.fromCharCode(parseInt(match.replace(/\\u|\\x/g, ''), 16))); +function unicodeToChar(text: string) { + return text.replace(/\\u[\dA-F]{4}|\\x[\dA-F]{2}/gi, (match: NeedToTyped) => String.fromCharCode(parseInt(match.replace(/\\u|\\x/g, ''), 16))); } -function arrayCount(arr, iterator) { +function arrayCount(arr: NeedToTyped, iterator: NeedToTyped) { let count = 0; for (let i = 0; i < arr.length; i += 1) { if (iterator(arr[i])) { @@ -144,14 +145,14 @@ function arrayCount(arr, iterator) { /** * Better function checking */ -function isFunction(func) { +function isFunction(func: CallbackType) { return Object.prototype.toString.call(func) === '[object Function]'; } /** * Compare two sets */ -function setsAreEqual(as, bs) { +function setsAreEqual(as: NeedToTyped, bs: NeedToTyped) { if (as.size !== bs.size) return false; const values = as.values(); let a = values.next().value; @@ -174,8 +175,8 @@ function setsAreEqual(as, bs) { * @param {number} length - If provided, it is used as the length of str. Defaults to str.length. * @return {boolean} */ -function endsWith(str, search, length) { - if (String.prototype.endsWith) { +function endsWith(str: string, search: string, length?: number) { + if (str.endsWith) { return str.endsWith(search, length); } let strLen = length; @@ -190,7 +191,7 @@ function endsWith(str, search, length) { * @param {string} originalUrl * @returns {string} */ -function convertImageLocalPath(originalUrl) { +function convertImageLocalPath(originalUrl: string) { let url = originalUrl; if (/^assets/.test(url)) { if (isDev()) { @@ -202,7 +203,7 @@ function convertImageLocalPath(originalUrl) { return url; } -function deepCopy(data, hash = new WeakMap()) { +function deepCopy(data: NeedToTyped, hash = new WeakMap()) { if (typeof data !== 'object' || data === null) { throw new TypeError('deepCopy data is object'); } @@ -219,8 +220,10 @@ function deepCopy(data, hash = new WeakMap()) { } else if (Array.isArray(currentDataValue)) { newData[value] = [...currentDataValue]; } else if (currentDataValue instanceof Set) { + // @ts-ignore newData[value] = new Set([...currentDataValue]); } else if (currentDataValue instanceof Map) { + // @ts-ignore newData[value] = new Map([...currentDataValue]); } else { hash.set(data, data); @@ -230,11 +233,11 @@ function deepCopy(data, hash = new WeakMap()) { return newData; } -function isNullOrUndefined(value) { +function isNullOrUndefined(value: NeedToTyped) { return typeof value === 'undefined' || value === null; } -function whitespaceFilter(str) { +function whitespaceFilter(str: string) { if (typeof str !== 'string') return str; // Adjusts template whitespace handling behavior. // "trimWhitespace": default behavior is true. diff --git a/driver/js/packages/hippy-vue/src/util/node-style.ts b/driver/js/packages/hippy-vue/src/util/node-style.ts new file mode 100644 index 00000000000..43f95a207a5 --- /dev/null +++ b/driver/js/packages/hippy-vue/src/util/node-style.ts @@ -0,0 +1,57 @@ +/* + * Tencent is pleased to support the open source community by making + * Hippy available. + * + * Copyright (C) 2017-2019 THL A29 Limited, a Tencent company. + * All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const nodeStyleMap = new Map(); + +/** + * setCacheNodeStyle - cache node style + * @param {number} nodeId + * @param style + */ +function setCacheNodeStyle(nodeId: number, style) { + nodeStyleMap.set(nodeId, style); +} + +/** + * deleteCacheNodeStyle - delete ViewNode from cache + * @param {number} nodeId + */ +function deleteCacheNodeStyle(nodeId: number) { + nodeStyleMap.delete(nodeId); +} + +/** + * getNodeById - get ViewNode by nodeId + * @param {number} nodeId + */ +function getCacheNodeStyle(nodeId: number) { + return nodeStyleMap.get(nodeId) || {}; +} + +function clearCacheNodeStyle() { + nodeStyleMap.clear(); +} + +export { + setCacheNodeStyle, + deleteCacheNodeStyle, + getCacheNodeStyle, + clearCacheNodeStyle, +}; diff --git a/driver/js/packages/hippy-vue/src/util/node.js b/driver/js/packages/hippy-vue/src/util/node.ts similarity index 78% rename from driver/js/packages/hippy-vue/src/util/node.js rename to driver/js/packages/hippy-vue/src/util/node.ts index 221b7df76f0..9fb42387531 100644 --- a/driver/js/packages/hippy-vue/src/util/node.js +++ b/driver/js/packages/hippy-vue/src/util/node.ts @@ -18,6 +18,9 @@ * limitations under the License. */ +import ViewNode from '../renderer/view-node'; +import { NeedToTyped } from '../types/native'; + /* eslint-disable no-param-reassign */ const nodeCache = new Map(); @@ -27,7 +30,7 @@ const nodeCache = new Map(); * @param {ViewNode} targetNode * @param {number} nodeId */ -function preCacheNode(targetNode, nodeId) { +function preCacheNode(targetNode: ViewNode, nodeId: number) { nodeCache.set(nodeId, targetNode); } @@ -35,7 +38,7 @@ function preCacheNode(targetNode, nodeId) { * unCacheNode - delete ViewNode from cache * @param {number} nodeId */ -function unCacheNode(nodeId) { +function unCacheNode(nodeId: number) { nodeCache.delete(nodeId); } @@ -43,7 +46,7 @@ function unCacheNode(nodeId) { * getNodeById - get ViewNode by nodeId * @param {number} nodeId */ -function getNodeById(nodeId) { +function getNodeById(nodeId: number) { return nodeCache.get(nodeId) || null; } @@ -51,7 +54,7 @@ function getNodeById(nodeId) { * unCacheViewNodeOnIdle - recursively delete ViewNode cache on idle * @param {ViewNode|number} node */ -function unCacheNodeOnIdle(node) { +function unCacheNodeOnIdle(node: ViewNode) { requestIdleCallback((deadline) => { // if idle time exists or callback invoked when timeout if (deadline.timeRemaining() > 0 || deadline.didTimeout) { @@ -64,14 +67,9 @@ function unCacheNodeOnIdle(node) { * recursivelyUnCacheNode - delete ViewNode cache recursively * @param {ViewNode|number} node */ -function recursivelyUnCacheNode(node) { - if (typeof node === 'number') { - // if leaf node (e.g. text node) - unCacheNode(node); - } else if (node) { - unCacheNode(node.nodeId); - node.childNodes && node.childNodes.forEach(node => recursivelyUnCacheNode(node)); - } +function recursivelyUnCacheNode(node: ViewNode) { + unCacheNode(node.nodeId); + node.childNodes?.forEach((node: ViewNode) => recursivelyUnCacheNode(node)); } /** @@ -79,10 +77,10 @@ function recursivelyUnCacheNode(node) { * @param {Function} cb * @param {{timeout: number}} [options] */ -function requestIdleCallback(cb, options) { +function requestIdleCallback(callback: any, options?: any) { if (!global.requestIdleCallback) { return setTimeout(() => { - cb({ + callback({ didTimeout: false, timeRemaining() { return Infinity; @@ -90,14 +88,14 @@ function requestIdleCallback(cb, options) { }); }, 1); } - return global.requestIdleCallback(cb, options); + return global.requestIdleCallback(callback, options); } /** * cancelIdleCallback polyfill * @param {ReturnType} id */ -function cancelIdleCallback(id) { +function cancelIdleCallback(id: number) { if (!global.cancelIdleCallback) { clearTimeout(id); } else { @@ -111,7 +109,7 @@ function cancelIdleCallback(id) { * @param targetNode * @returns {boolean|*} */ -function isStyleMatched(matchedSelector, targetNode) { +function isStyleMatched(matchedSelector: NeedToTyped, targetNode: ViewNode) { if (!targetNode || !matchedSelector) return false; return matchedSelector.match(targetNode); } @@ -122,11 +120,11 @@ function isStyleMatched(matchedSelector, targetNode) { * @param startIndex * @returns {*} */ -function findNotToSkipNode(nodes = [], startIndex = 0) { +function findNotToSkipNode(nodes: ViewNode[] = [], startIndex = 0) { let targetNode = nodes[startIndex]; for (let i = startIndex; i < nodes.length; i++) { const node = nodes[i]; - if (node && node.meta && !node.meta.skipAddToDom) { + if (node && (node as any).meta && !(node as any).meta.skipAddToDom) { targetNode = node; break; } diff --git a/driver/js/scripts/vue-configs.js b/driver/js/scripts/vue-configs.js index 1075708d705..c9e6ecddf31 100644 --- a/driver/js/scripts/vue-configs.js +++ b/driver/js/scripts/vue-configs.js @@ -24,6 +24,7 @@ const { babel } = require('@rollup/plugin-babel'); const cjs = require('@rollup/plugin-commonjs'); const replace = require('@rollup/plugin-replace'); const { nodeResolve } = require('@rollup/plugin-node-resolve'); +const typescript = require('rollup-plugin-typescript2'); const flow = require('rollup-plugin-flow-no-whitespace'); const VueVersion = require('vue/package.json').version; @@ -56,15 +57,15 @@ const aliases = { const builds = { '@hippy/vue': { - entry: resolvePackage('hippy-vue', 'src/index.js'), + entry: resolvePackage('hippy-vue', 'src/index.ts'), dest: resolvePackage('hippy-vue', 'dist/index.js'), format: 'es', banner: banner('@hippy/vue', hippyVuePackage.version, bannerStr), }, '@hippy/vue-css-loader': { entry: { - index: resolvePackage('hippy-vue-css-loader', 'src/index.js'), - 'css-loader': resolvePackage('hippy-vue-css-loader', 'src/css-loader.js'), + index: resolvePackage('hippy-vue-css-loader', 'src/index.ts'), + 'css-loader': resolvePackage('hippy-vue-css-loader', 'src/css-loader.ts'), }, dir: resolvePackage('hippy-vue-css-loader', 'dist'), entryFileNames: '[name].js', @@ -76,7 +77,7 @@ const builds = { }, }, '@hippy/vue-native-components': { - entry: resolvePackage('hippy-vue-native-components', 'src/index.js'), + entry: resolvePackage('hippy-vue-native-components', 'src/index.ts'), dest: resolvePackage('hippy-vue-native-components', 'dist/index.js'), format: 'es', moduleName: 'hippy-vue-native-components', @@ -112,6 +113,33 @@ function genConfig(name) { 'inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__': 'global.__VUE_DEVTOOLS_GLOBAL_HOOK__', }, }), + typescript({ + typescript: require('ttypescript'), + tsconfigDefaults: { + compilerOptions: { + plugins: [ + // only deal with d.ts,ignore js + // do not transform external npm package path in d.ts + { transform: 'typescript-transform-paths', afterDeclarations: true, exclude: ['**/vue/src/**'] }, + ], + }, + }, + tsconfig: path.resolve(__dirname, '../tsconfig.json'), + tsconfigOverride: { + compilerOptions: { + declaration: true, + declarationMap: false, + }, + exclude: ['**/__tests__/*.test.*'], + include: [ + 'packages/hippy-vue/src', + 'packages/hippy-vue-css-loader/src', + 'packages/global.d.ts', + 'node_modules/@types/web/index.d.ts', + 'node_modules/@types/node/index.d.ts', + ], + }, + }), flow(), alias({ entries: aliases,