UNPKG

@hippy4tv/vue-native-components

Version:

Hippy Vue Native components for TV

489 lines (460 loc) 14 kB
/*! * @hippy/vue-native-components v1.0.0 * (Using Vue v2.6.11 and Hippy-Vue v1.0.0) * Build at: Wed Mar 03 2021 14:54:37 GMT+0800 (China Standard Time) * * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2017-2021 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. */ function objectWithoutProperties (obj, exclude) { var target = {}; for (var k in obj) if (Object.prototype.hasOwnProperty.call(obj, k) && exclude.indexOf(k) === -1) target[k] = obj[k]; return target; } function registerAnimation(Vue) { // Constants for animations var MODULE_NAME = 'AnimationModule'; var DEFAULT_OPTION = { valueType: undefined, delay: 0, startValue: 0, toValue: 0, duration: 0, direction: 'center', timingFunction: 'linear', repeatCount: 0, inputRange: [], outputRange: [], }; /** * Create the standalone animation */ function createAnimation(option) { var mode = option.mode; if ( mode === void 0 ) mode = 'timing'; var valueType = option.valueType; var rest = objectWithoutProperties( option, ["mode", "valueType"] ); var others = rest; var fullOption = Object.assign({}, DEFAULT_OPTION, others); if (valueType !== undefined) { fullOption.valueType = option.valueType; } var animationId = Vue.Native.callNativeWithCallbackId(MODULE_NAME, 'createAnimation', true, mode, fullOption); return { animationId: animationId, }; } /** * Create the animationSet */ function createAnimationSet(children, repeatCount) { if ( repeatCount === void 0 ) repeatCount = 0; return Vue.Native.callNativeWithCallbackId(MODULE_NAME, 'createAnimationSet', true, { children: children, repeatCount: repeatCount, }); } /** * Generate the styles from animation and animationSet Ids. */ function getStyle(actions) { var style = {}; Object.keys(actions).forEach(function (key) { if (Array.isArray(actions[key])) { // Process AnimationSet from Array. var actionSet = actions[key]; var animationSetActions = actionSet.map(function (a) { var action = createAnimation(a); action.follow = true; return action; }); var ref = actionSet[actionSet.length - 1]; var repeatCount = ref.repeatCount; var animationSetId = createAnimationSet(animationSetActions, repeatCount); style[key] = { animationId: animationSetId, }; } else { // Process standalone Animation. var action = actions[key]; var animation = createAnimation(action); style[key] = { animationId: animation.animationId, }; } }); return style; } /** * Get animationIds from style for start/pause/destroy actions. */ function getAnimationIds(style) { var transform = style.transform; var rest = objectWithoutProperties( style, ["transform"] ); var otherStyles = rest; var animationIds = Object.keys(otherStyles).map(function (key) { return style[key].animationId; }); if (Array.isArray(transform) && transform.length > 0) { var transformIds = Object.keys(transform[0]).map(function (key) { return transform[0][key].animationId; }); animationIds = animationIds.concat( transformIds); } return animationIds; } /** * Register the animation component. */ Vue.component('animation', { inheritAttrs: false, props: { tag: { type: String, default: 'div', }, playing: { type: Boolean, default: false, }, actions: { type: Object, required: true, }, props: Object, }, beforeMount: function beforeMount() { this.create(); }, mounted: function mounted() { var ref = this.$props; var playing = ref.playing; if (playing) { this.start(); } }, beforeDestroy: function beforeDestroy() { this.destroy(); }, data: function data() { return { style: {}, }; }, methods: { create: function create() { var ref = this.$props; var ref_actions = ref.actions; var transform = ref_actions.transform; var rest = objectWithoutProperties( ref_actions, ["transform"] ); var actions = rest; var style = getStyle(actions); if (transform) { var transformAnimations = getStyle(transform); style.transform = Object.keys(transformAnimations).map(function (key) { var obj; return (( obj = {}, obj[key] = transformAnimations[key], obj )); }); } // Turn to be true at first startAnimation, and be false again when destroy. this.$alreadyStarted = false; // Generated style this.style = style; }, start: function start() { var animationIds = getAnimationIds(this.style); if (!this.$alreadyStarted) { this.$alreadyStarted = true; animationIds.forEach(function (animationId) { return Vue.Native.callNative(MODULE_NAME, 'startAnimation', animationId); }); } else { animationIds.forEach(function (animationId) { return Vue.Native.callNative(MODULE_NAME, 'resumeAnimation', animationId); }); } }, pause: function pause() { if (!this.$alreadyStarted) { return; } var animationIds = getAnimationIds(this.style); animationIds.forEach(function (animationId) { return Vue.Native.callNative(MODULE_NAME, 'pauseAnimation', animationId); }); }, destroy: function destroy() { this.$alreadyStarted = false; var animationIds = getAnimationIds(this.style); animationIds.forEach(function (animationId) { return Vue.Native.callNative(MODULE_NAME, 'destroyAnimation', animationId); }); }, }, watch: { playing: function playing(to, from) { if (!from && to) { this.start(); } else if (from && !to) { this.pause(); } }, actions: function actions() { var this$1 = this; // FIXME: Should diff the props and use updateAnimation method to update the animation. // Hard restart the animation is no correct. var ref = this.$props; var playing = ref.playing; this.destroy(); this.create(); if (playing) { this.$nextTick(function () { return this$1.start(); }); } }, }, template: "\n <component :is=\"tag\" :useAnimation=\"true\" :style=\"style\" v-bind=\"props\">\n <slot />\n </component>\n ", }); } function registerDialog(Vue) { Vue.registerElement('dialog', { component: { name: 'Modal', defaultNativeProps: { transparent: true, immersionStatusBar: true, }, }, }); } /** * Capitalize a word * * @param {string} s The word input * @returns string */ function capitalize(str) { if (typeof str !== 'string') { return ''; } return ("" + (str.charAt(0).toUpperCase()) + (str.slice(1))); } /** * Get binding events redirector * * The function should be calld with `getEventRedirector.call(this, [])` * for binding this. * * @param {string[] | string[][]} events events will be redirect * @returns Object */ function getEventRedirector(events) { var this$1 = this; var on = {}; events.forEach(function (event) { if (Array.isArray(event)) { var exposedEventName = event[0]; var nativeEventName = event[1]; if (Object.prototype.hasOwnProperty.call(this$1.$listeners, exposedEventName)) { on[event] = this$1[("on" + (capitalize(nativeEventName)))]; } } else if (Object.prototype.hasOwnProperty.call(this$1.$listeners, event)) { on[event] = this$1[("on" + (capitalize(event)))]; } }); return on; } function registerUlRefresh(Vue) { Vue.registerElement('hi-ul-refresh-wrapper', { component: { name: 'RefreshWrapper', }, }); Vue.registerElement('hi-refresh-wrapper-item', { component: { name: 'RefreshWrapperItemView', }, }); Vue.component('ul-refresh-wrapper', { inheritAttrs: false, props: { bounceTime: { type: Number, defaultValue: 100, }, }, methods: { onRefresh: function onRefresh(evt) { this.$emit('refresh', evt); }, startRefresh: function startRefresh() { Vue.Native.callUIFunction(this.$refs.refreshWrapper, 'startRefresh', null); }, refreshCompleted: function refreshCompleted() { // FIXME: Here's a typo mistake `refreshComplected` in native sdk. Vue.Native.callUIFunction(this.$refs.refreshWrapper, 'refreshComplected', null); }, }, render: function render(h) { var on = getEventRedirector.call(this, [ 'refresh' ]); return h('hi-ul-refresh-wrapper', { on: on, ref: 'refreshWrapper', }, this.$slots.default); }, }); Vue.component('ul-refresh', { inheritAttrs: false, template: "\n <hi-refresh-wrapper-item :style=\"{position: 'absolute', left: 0, right: 0}\">\n <div>\n <slot />\n </div>\n </hi-refresh-wrapper-item>\n ", }); } /* eslint-disable no-param-reassign */ function registerSwiper(Vue) { Vue.registerElement('hi-swiper', { component: { name: 'ViewPager', processEventData: function processEventData(event, nativeEventName, nativeEventParams) { switch (nativeEventName) { case 'onPageSelected': event.currentSlide = nativeEventParams.position; break; case 'onPageScroll': event.nextSlide = nativeEventParams.position; event.offset = nativeEventParams.offset; break; case 'onPageScrollStateChanged': event.state = nativeEventParams.pageScrollState; break; case 'onFocusSearchFailed': event.child = { index:nativeEventParams.child.index, id:nativeEventParams.child.id, name:nativeEventParams.child.name, position:nativeEventParams.child.position, }; event.focused = { id:nativeEventParams.child.id, name:nativeEventParams.child.name, }; event.direction = nativeEventParams.direction; break; } return event; }, }, }); Vue.registerElement('swiper-slide', { component: { name: 'ViewPagerItem', defaultNativeStyle: { position: 'absolute', top: 0, right: 0, bottom: 0, left: 0, }, }, }); Vue.component('swiper', { inheritAttrs: false, props: { current: { type: Number, defaultValue: 0, }, needAnimation: { type: Boolean, defaultValue: true, }, }, beforeMount: function beforeMount() { this.$initialSlide = this.$props.current; }, methods: { setSlide: function setSlide(slideIndex) { Vue.Native.callUIFunction(this.$refs.swiper, 'setPage', [slideIndex]); }, setSlideWithoutAnimation: function setSlideWithoutAnimation(slideIndex) { Vue.Native.callUIFunction(this.$refs.swiper, 'setPageWithoutAnimation', [slideIndex]); }, // On dragging onPageScroll: function onPageScroll(evt) { this.$emit('dragging', evt); }, // On dropped finished dragging. onPageSelected: function onPageSelected(evt) { this.$emit('dropped', evt); }, // On page scroll state changed. onPageScrollStateChanged: function onPageScrollStateChanged(evt) { this.$emit('stateChanged', evt); }, }, watch: { current: function current(to) { if (this.$props.needAnimation) { this.setSlide(to); } else { this.setSlideWithoutAnimation(to); } }, }, render: function render(h) { var on = getEventRedirector.call(this, [ ['dropped', 'pageSelected'], ['dragging', 'pageScroll'], ['stateChanged', 'pageScrollStateChanged'] ]); return h('hi-swiper', { on: on, ref: 'swiper', attrs: { initialPage: this.$initialSlide, }, }, this.$slots.default); }, }); } /** * Register the Animation component only */ var AnimationComponent = { install: function install(Vue) { registerAnimation(Vue); }, }; /** * Register the modal component only. */ var DialogComponent = { install: function install(Vue) { registerDialog(Vue); }, }; /** * Register the ul refresh wrapper and refresh component. */ var ListRefreshComponent = { install: function install(Vue) { registerUlRefresh(Vue); }, }; /** * Register the swiper component. */ var SwiperComponent = { install: function install(Vue) { registerSwiper(Vue); }, }; /** * Register all of native components */ var HippyVueNativeComponents = { install: function install(Vue) { registerAnimation(Vue); registerDialog(Vue); registerUlRefresh(Vue); registerSwiper(Vue); }, }; export default HippyVueNativeComponents; export { AnimationComponent, DialogComponent, ListRefreshComponent, SwiperComponent };