.'
+ )
+ }
+ }
+ if (staticClass) {
+ el.staticClass = JSON.stringify(staticClass)
+ }
+ const classBinding = getBindingAttr(el, 'class', false /* getStatic */)
+ if (classBinding) {
+ el.classBinding = classBinding
+ }
+}
+
+function genData(el) {
+ let data = ''
+ if (el.staticClass) {
+ data += `staticClass:${el.staticClass},`
+ }
+ if (el.classBinding) {
+ data += `class:${el.classBinding},`
+ }
+ return data
+}
+
+export default {
+ staticKeys: ['staticClass'],
+ transformNode,
+ genData
+}
diff --git a/platform/nativescript/compiler/modules/for.js b/platform/nativescript/compiler/modules/for.js
new file mode 100644
index 00000000..0e6b8294
--- /dev/null
+++ b/platform/nativescript/compiler/modules/for.js
@@ -0,0 +1,42 @@
+import { getAndRemoveAttr, addRawAttr } from 'compiler/helpers'
+import { normalizeElementName } from '../../element-registry'
+import { parseFor } from 'compiler/parser/index'
+import { warn } from 'core/util/debug'
+
+function preTransformNode(el) {
+ let vfor
+
+ if (normalizeElementName(el.tag) === 'nativelistview') {
+ vfor = getAndRemoveAttr(el, 'v-for')
+ delete el.attrsMap['v-for']
+ if (process.env.NODE_ENV !== 'production' && vfor) {
+ warn(
+ `The v-for directive is not supported on a ${el.tag}, ` +
+ 'Use the "for" attribute instead. For example, instead of ' +
+ `<${el.tag} v-for="${vfor}"> use <${el.tag} for="${vfor}">.`
+ )
+ }
+ }
+
+ const exp = getAndRemoveAttr(el, 'for') || vfor
+ if (!exp) return
+
+ const res = parseFor(exp)
+ if (!res) {
+ if (process.env.NODE_ENV !== 'production') {
+ warn(`Invalid for expression: ${exp}`)
+ }
+ return
+ }
+
+ addRawAttr(el, ':items', res.for)
+ addRawAttr(el, '+alias', res.alias)
+
+ if (res.iterator1) {
+ addRawAttr(el, '+index', res.iterator1)
+ }
+}
+
+export default {
+ preTransformNode
+}
diff --git a/platform/nativescript/compiler/modules/index.js b/platform/nativescript/compiler/modules/index.js
new file mode 100644
index 00000000..d387711e
--- /dev/null
+++ b/platform/nativescript/compiler/modules/index.js
@@ -0,0 +1,8 @@
+import class_ from './class'
+import style from './style'
+import for_ from './for'
+import router from './router'
+import vTemplate from './v-template'
+import view from './view'
+
+export default [class_, style, vTemplate, for_, router, view]
diff --git a/platform/nativescript/compiler/modules/router.js b/platform/nativescript/compiler/modules/router.js
new file mode 100644
index 00000000..d6d801d1
--- /dev/null
+++ b/platform/nativescript/compiler/modules/router.js
@@ -0,0 +1,17 @@
+import { normalizeElementName } from '../../element-registry'
+import { addAttr } from 'compiler/helpers'
+
+function preTransformNode(el) {
+ if (el.tag !== 'router-view') return
+ if (
+ el.parent &&
+ el.parent.tag &&
+ normalizeElementName(el.parent.tag) === 'nativeframe'
+ ) {
+ addAttr(el.parent, 'hasRouterView', 'true')
+ }
+}
+
+export default {
+ preTransformNode
+}
diff --git a/platform/nativescript/compiler/modules/style.js b/platform/nativescript/compiler/modules/style.js
new file mode 100644
index 00000000..00551548
--- /dev/null
+++ b/platform/nativescript/compiler/modules/style.js
@@ -0,0 +1,75 @@
+import { getAndRemoveAttr, getBindingAttr, baseWarn } from 'compiler/helpers'
+import { parseText } from 'compiler/parser/text-parser'
+import { cached, camelize } from 'shared/util'
+
+const normalize = cached(camelize)
+
+function transformNode(el, options) {
+ const warn = options.warn || baseWarn
+ const staticStyle = getAndRemoveAttr(el, 'style')
+ const { dynamic, styleResult } = parseStaticStyle(staticStyle, options)
+ if (process.env.NODE_ENV !== 'production' && dynamic) {
+ warn(
+ `style="${String(staticStyle)}": ` +
+ 'Interpolation inside attributes has been deprecated. ' +
+ 'Use v-bind or the colon shorthand instead.'
+ )
+ }
+ if (!dynamic && styleResult) {
+ el.staticStyle = styleResult
+ }
+ const styleBinding = getBindingAttr(el, 'style', false /* getStatic */)
+ if (styleBinding) {
+ el.styleBinding = styleBinding
+ } else if (dynamic) {
+ el.styleBinding = styleResult
+ }
+}
+
+function genData(el) {
+ let data = ''
+ if (el.staticStyle) {
+ data += `staticStyle:${el.staticStyle},`
+ }
+ if (el.styleBinding) {
+ data += `style:${el.styleBinding},`
+ }
+ return data
+}
+
+function parseStaticStyle(staticStyle, options) {
+ // "width: 200px; height: 200px;" -> {width: 200, height: 200}
+ // "width: 200px; height: {{y}}" -> {width: 200, height: y}
+ let dynamic = false
+ let styleResult = ''
+ if (staticStyle) {
+ const styleList = staticStyle
+ .trim()
+ .split(';')
+ .map(style => {
+ const result = style.trim().split(':')
+ if (result.length !== 2) {
+ return
+ }
+ const key = normalize(result[0].trim())
+ const value = result[1].trim()
+ const dynamicValue = parseText(value, options.delimiters)
+ if (dynamicValue) {
+ dynamic = true
+ return key + ':' + dynamicValue
+ }
+ return key + ':' + JSON.stringify(value)
+ })
+ .filter(result => result)
+ if (styleList.length) {
+ styleResult = '{' + styleList.join(',') + '}'
+ }
+ }
+ return { dynamic, styleResult }
+}
+
+export default {
+ staticKeys: ['staticStyle'],
+ transformNode,
+ genData
+}
diff --git a/platform/nativescript/compiler/modules/v-template.js b/platform/nativescript/compiler/modules/v-template.js
new file mode 100644
index 00000000..319bae1a
--- /dev/null
+++ b/platform/nativescript/compiler/modules/v-template.js
@@ -0,0 +1,15 @@
+function preTransformNode(el) {
+ if (el.parent && el.parent.tag === 'v-template') {
+ let alias = el.parent.parent.attrsMap['+alias'] || 'item'
+ let index = el.parent.parent.attrsMap['+index'] || '$index'
+ el.slotScope = buildScopeString(alias, index)
+ }
+}
+
+export default {
+ preTransformNode
+}
+
+export function buildScopeString(alias, index) {
+ return `{ ${alias}, ${index}, $even, $odd }`
+}
diff --git a/platform/nativescript/compiler/modules/view.js b/platform/nativescript/compiler/modules/view.js
new file mode 100644
index 00000000..fd67130b
--- /dev/null
+++ b/platform/nativescript/compiler/modules/view.js
@@ -0,0 +1,21 @@
+import { getAndRemoveAttr, addDirective } from 'compiler/helpers'
+
+// transforms ~test -> v-view:test
+function transformNode(el) {
+ const attr = Object.keys(el.attrsMap).find(attr => attr.startsWith('~'))
+
+ if (attr) {
+ const attrName = attr.substr(1)
+ let [arg, ...modifiers] = attrName.split('.')
+ modifiers = modifiers.reduce((mods, mod) => {
+ mods[mod] = true
+ return mods
+ }, {})
+ getAndRemoveAttr(el, attr, true)
+ addDirective(el, 'view', `v-view:${attrName}`, '', arg, false, modifiers)
+ }
+}
+
+export default {
+ transformNode
+}
diff --git a/platform/nativescript/compiler/sfc/parser.js b/platform/nativescript/compiler/sfc/parser.js
new file mode 100644
index 00000000..e9ebaca9
--- /dev/null
+++ b/platform/nativescript/compiler/sfc/parser.js
@@ -0,0 +1,138 @@
+/* @flow */
+
+/**
+ * This file is a fork of https://github.com/vuejs/vue/blob/dev/src/sfc/parser.js
+ * which allows multiple template and script blocks in a SFC
+ */
+import deindent from 'de-indent'
+import { parseHTML } from 'compiler/parser/html-parser'
+import { makeMap } from 'shared/util'
+
+const splitRE = /\r?\n/g
+const replaceRE = /./g
+const isSpecialTag = makeMap('script,style,template', true)
+
+type Attribute = {
+ name: string,
+ value: string
+}
+
+/**
+ * Parse a single-file component (*.vue) file into an SFC Descriptor Object.
+ */
+export function parseComponent(
+ content: string,
+ options?: Object = {}
+): SFCDescriptor {
+ const sfc: SFCDescriptor = {
+ template: null,
+ templates: [],
+ script: null,
+ scripts: [],
+ styles: [],
+ customBlocks: []
+ }
+ let depth = 0
+ let currentBlock: ?SFCBlock = null
+
+ function start(
+ tag: string,
+ attrs: Array
,
+ unary: boolean,
+ start: number,
+ end: number
+ ) {
+ if (depth === 0) {
+ currentBlock = {
+ type: tag,
+ content: '',
+ tags: {
+ open: { start, end },
+ close: null
+ },
+ start: end,
+ attrs: attrs.reduce((cumulated, { name, value }) => {
+ cumulated[name] = value || true
+ return cumulated
+ }, {})
+ }
+ if (isSpecialTag(tag)) {
+ checkAttrs(currentBlock, attrs)
+ if (tag === 'style') {
+ sfc.styles.push(currentBlock)
+ } else if (tag === 'script') {
+ sfc.scripts.push(currentBlock)
+ } else if (tag === 'template') {
+ sfc.templates.push(currentBlock)
+ }
+ } else {
+ // custom blocks
+ sfc.customBlocks.push(currentBlock)
+ }
+ }
+ if (!unary) {
+ depth++
+ }
+ }
+
+ function checkAttrs(block: SFCBlock, attrs: Array) {
+ for (let i = 0; i < attrs.length; i++) {
+ const attr = attrs[i]
+ if (attr.name === 'lang') {
+ block.lang = attr.value
+ }
+ if (attr.name === 'scoped') {
+ block.scoped = true
+ }
+ if (attr.name === 'module') {
+ block.module = attr.value || true
+ }
+ if (attr.name === 'src') {
+ block.src = attr.value
+ }
+ }
+ }
+
+ function end(tag: string, start: number, end: number) {
+ if (depth === 1 && currentBlock) {
+ currentBlock.end = start
+ let text = deindent(content.slice(currentBlock.start, currentBlock.end))
+ // pad content so that linters and pre-processors can output correct
+ // line numbers in errors and warnings
+ if (currentBlock.type !== 'template' && options.pad) {
+ text = padContent(currentBlock, options.pad) + text
+ }
+ currentBlock.tags.close = { start, end }
+ currentBlock.content = text
+ currentBlock = null
+ }
+ depth--
+ }
+
+ function padContent(block: SFCBlock, pad: true | 'line' | 'space') {
+ if (pad === 'space') {
+ return content.slice(0, block.start).replace(replaceRE, ' ')
+ } else {
+ const offset = content.slice(0, block.start).split(splitRE).length
+ const padChar = block.type === 'script' && !block.lang ? '//\n' : '\n'
+ return Array(offset).join(padChar)
+ }
+ }
+
+ parseHTML(content, {
+ start,
+ end
+ })
+
+ // set template property for backwards compat
+ if (sfc.templates.length) {
+ sfc.template = sfc.templates[sfc.templates.length - 1]
+ }
+
+ // set script property for backwards compat
+ if (sfc.scripts.length) {
+ sfc.script = sfc.scripts[sfc.scripts.length - 1]
+ }
+
+ return sfc
+}
diff --git a/platform/nativescript/constants.js b/platform/nativescript/constants.js
new file mode 100644
index 00000000..8fd865ec
--- /dev/null
+++ b/platform/nativescript/constants.js
@@ -0,0 +1 @@
+export const VUE_VM_REF = '__vue_vm_ref__'
diff --git a/platform/nativescript/element-registry.js b/platform/nativescript/element-registry.js
new file mode 100644
index 00000000..53059895
--- /dev/null
+++ b/platform/nativescript/element-registry.js
@@ -0,0 +1,320 @@
+import * as builtInComponents from './runtime/components'
+import { trace } from './util'
+
+const elementMap = {}
+const nativeRegExp = /Native/gi
+const dashRegExp = /-/g
+
+const defaultViewMeta = {
+ skipAddToDom: false,
+ isUnaryTag: false,
+ tagNamespace: '',
+ canBeLeftOpenTag: false,
+ model: null,
+ component: null
+}
+
+export function normalizeElementName(elementName) {
+ return `native${elementName
+ .replace(nativeRegExp, '')
+ .replace(dashRegExp, '')
+ .toLowerCase()}`
+}
+
+export function registerElement(elementName, resolver, meta) {
+ const normalizedName = normalizeElementName(elementName)
+
+ meta = Object.assign({}, defaultViewMeta, meta)
+
+ // allow override of elements classes (N ones especially)
+ // this is very practical in case you want to test new component
+ // or simply override the global Button for example
+ if (elementMap[normalizedName]) {
+ trace(`Element for ${elementName} already registered.`)
+ }
+
+ if (!meta.component) {
+ // if no Vue component is passed, wrap the simpler vue component
+ // which bind the events and attributes to the NS one
+ meta.component = {
+ functional: true,
+ model: meta.model,
+ render: (h, { data, children }) => {
+ return h(normalizedName, data, children)
+ }
+ }
+ }
+ meta.component.name = elementName
+
+ elementMap[normalizedName] = {
+ resolver: resolver,
+ meta: meta
+ }
+}
+
+export function getElementMap() {
+ return elementMap
+}
+
+export function getViewClass(elementName) {
+ const normalizedName = normalizeElementName(elementName)
+ const entry = elementMap[normalizedName]
+
+ if (!entry) {
+ throw new TypeError(`No known component for element ${elementName}.`)
+ }
+
+ try {
+ return entry.resolver()
+ } catch (e) {
+ throw new TypeError(
+ `Could not load view for: ${elementName}. ${e} ${e.stack}`
+ )
+ }
+}
+
+export function getViewMeta(elementName) {
+ const normalizedName = normalizeElementName(elementName)
+
+ let meta = defaultViewMeta
+ const entry = elementMap[normalizedName]
+
+ if (entry && entry.meta) {
+ meta = entry.meta
+ }
+
+ return meta
+}
+
+export function isKnownView(elementName) {
+ return elementMap[normalizeElementName(elementName)]
+}
+
+registerElement('ActionBar', () => require('@nativescript/core').ActionBar, {
+ removeChild(parent, child) {
+ try {
+ parent.nativeView._removeView(child.nativeView)
+ } catch (e) {
+ // ignore exception - child is likely already removed/replaced
+ // fixes #76
+ }
+ },
+ component: builtInComponents.ActionBar
+})
+
+registerElement('ActionItem', () => require('@nativescript/core').ActionItem)
+
+registerElement('android', null, {
+ component: builtInComponents.android
+})
+
+registerElement('ios', null, {
+ component: builtInComponents.ios
+})
+
+registerElement('ListView', () => require('@nativescript/core').ListView, {
+ component: builtInComponents.ListView
+})
+
+registerElement(
+ 'NavigationButton',
+ () => require('@nativescript/core').NavigationButton
+)
+
+registerElement('TabView', () => require('@nativescript/core').TabView, {
+ model: {
+ prop: 'selectedIndex',
+ event: 'selectedIndexChange'
+ },
+ component: builtInComponents.TabView
+})
+
+registerElement(
+ 'TabViewItem',
+ () => require('@nativescript/core').TabViewItem,
+ {
+ skipAddToDom: true,
+ component: builtInComponents.TabViewItem
+ }
+)
+
+registerElement('transition', null, {
+ component: builtInComponents.transition
+})
+
+registerElement('v-template', null, {
+ component: builtInComponents.VTemplate
+})
+
+// NS components which uses the automatic registerElement Vue wrapper
+// as they do not need any special logic
+
+registerElement('Label', () => require('@nativescript/core').Label, {
+ model: {
+ prop: 'text',
+ event: 'textChange'
+ }
+})
+
+registerElement('DatePicker', () => require('@nativescript/core').DatePicker, {
+ model: {
+ prop: 'date',
+ event: 'dateChange'
+ }
+})
+
+registerElement(
+ 'AbsoluteLayout',
+ () => require('@nativescript/core').AbsoluteLayout
+)
+registerElement(
+ 'ActivityIndicator',
+ () => require('@nativescript/core').ActivityIndicator
+)
+registerElement('Button', () => require('@nativescript/core').Button)
+registerElement('ContentView', () => require('@nativescript/core').ContentView)
+registerElement('DockLayout', () => require('@nativescript/core').DockLayout)
+registerElement('GridLayout', () => require('@nativescript/core').GridLayout)
+registerElement('HtmlView', () => require('@nativescript/core').HtmlView)
+registerElement('Image', () => require('@nativescript/core').Image)
+registerElement('img', () => require('@nativescript/core').Image)
+registerElement('ListPicker', () => require('@nativescript/core').ListPicker, {
+ model: {
+ prop: 'selectedIndex',
+ event: 'selectedIndexChange'
+ }
+})
+registerElement('Page', () => require('@nativescript/core').Page, {
+ skipAddToDom: true,
+ component: builtInComponents.Page
+})
+
+registerElement('Placeholder', () => require('@nativescript/core').Placeholder)
+registerElement('Progress', () => require('@nativescript/core').Progress, {
+ model: {
+ prop: 'value',
+ event: 'valueChange'
+ }
+})
+registerElement(
+ 'ProxyViewContainer',
+ () => require('@nativescript/core').ProxyViewContainer
+)
+// registerElement(
+// 'Repeater',
+// () => require('@nativescript/core').Repeater
+// )
+registerElement('RootLayout', () => require('@nativescript/core').RootLayout)
+registerElement('ScrollView', () => require('@nativescript/core').ScrollView)
+registerElement('SearchBar', () => require('@nativescript/core').SearchBar, {
+ model: {
+ prop: 'text',
+ event: 'textChange'
+ }
+})
+registerElement(
+ 'SegmentedBar',
+ () => require('@nativescript/core').SegmentedBar,
+ {
+ model: {
+ prop: 'selectedIndex',
+ event: 'selectedIndexChange'
+ }
+ }
+)
+registerElement(
+ 'SegmentedBarItem',
+ () => require('@nativescript/core').SegmentedBarItem
+)
+registerElement('Slider', () => require('@nativescript/core').Slider, {
+ model: {
+ prop: 'value',
+ event: 'valueChange'
+ }
+})
+registerElement('StackLayout', () => require('@nativescript/core').StackLayout)
+registerElement(
+ 'FlexboxLayout',
+ () => require('@nativescript/core').FlexboxLayout
+)
+registerElement('Switch', () => require('@nativescript/core').Switch, {
+ model: {
+ prop: 'checked',
+ event: 'checkedChange'
+ }
+})
+
+registerElement('TextField', () => require('@nativescript/core').TextField, {
+ model: {
+ prop: 'text',
+ event: 'textChange'
+ }
+})
+registerElement('TextView', () => require('@nativescript/core').TextView, {
+ model: {
+ prop: 'text',
+ event: 'textChange'
+ }
+})
+registerElement('TimePicker', () => require('@nativescript/core').TimePicker, {
+ model: {
+ prop: 'time',
+ event: 'timeChange'
+ }
+})
+registerElement('WebView', () => require('@nativescript/core').WebView)
+registerElement('WrapLayout', () => require('@nativescript/core').WrapLayout)
+registerElement(
+ 'FormattedString',
+ () => require('@nativescript/core').FormattedString,
+ {
+ insertChild(parentNode, childNode, atIndex) {
+ if (atIndex > -1) {
+ parentNode.nativeView.spans.splice(atIndex, 0, childNode.nativeView)
+ return
+ }
+ parentNode.nativeView.spans.push(childNode.nativeView)
+ },
+ removeChild(parentNode, childNode) {
+ const index = parentNode.nativeView.spans.indexOf(childNode.nativeView)
+
+ if (index > -1) {
+ parentNode.nativeView.spans.splice(index, 1)
+ }
+ }
+ }
+)
+registerElement('Span', () => require('@nativescript/core').Span)
+
+registerElement(
+ 'DetachedContainer',
+ () => require('@nativescript/core').ProxyViewContainer,
+ {
+ skipAddToDom: true
+ }
+)
+registerElement(
+ 'DetachedText',
+ () => require('@nativescript/core').Placeholder,
+ {
+ skipAddToDom: true
+ }
+)
+registerElement('Comment', () => require('@nativescript/core').Placeholder)
+
+registerElement(
+ 'Document',
+ () => require('@nativescript/core').ProxyViewContainer,
+ {
+ skipAddToDom: true
+ }
+)
+
+registerElement('Frame', () => require('@nativescript/core').Frame, {
+ insertChild(parentNode, childNode, atIndex) {
+ // if (normalizeElementName(childNode.tagName) === 'nativepage') {
+ // parentNode.nativeView.navigate({ create: () => childNode.nativeView })
+ // }
+ },
+ component: builtInComponents.Frame
+})
diff --git a/platform/nativescript/framework.js b/platform/nativescript/framework.js
new file mode 100644
index 00000000..4ba881a0
--- /dev/null
+++ b/platform/nativescript/framework.js
@@ -0,0 +1,36 @@
+import Vue from './runtime/index'
+import ModalPlugin from './plugins/modal-plugin'
+import NavigatorPlugin from './plugins/navigator-plugin'
+
+import { setVue } from './util'
+
+Vue.config.silent = true
+Vue.config.suppressRenderLogs = false
+
+setVue(Vue)
+
+Vue.use(ModalPlugin)
+Vue.use(NavigatorPlugin)
+
+global.__onLiveSyncCore = () => {
+ const frame = require('@nativescript/core').Frame.topmost()
+ if (frame) {
+ if (frame.currentPage && frame.currentPage.modal) {
+ frame.currentPage.modal.closeModal()
+ }
+
+ if (frame.currentPage) {
+ frame.currentPage.addCssFile(
+ require('@nativescript/core').Application.getCssFileName()
+ )
+ }
+ }
+}
+
+// Fix a rollup problem which does not define
+// module.export.default = Vue
+// so a `import Vue from 'nativescript-vue'` will
+// fail from a Typescript file
+// Vue.default = Vue
+
+export default Vue
diff --git a/platform/nativescript/plugins/modal-plugin.js b/platform/nativescript/plugins/modal-plugin.js
new file mode 100644
index 00000000..93cd45e6
--- /dev/null
+++ b/platform/nativescript/plugins/modal-plugin.js
@@ -0,0 +1,110 @@
+import { isObject, isDef, isPrimitive } from 'shared/util'
+import { updateDevtools } from '../util'
+import { VUE_ELEMENT_REF } from '../renderer/ElementNode'
+
+let sequentialCounter = 0
+
+function serializeModalOptions(options) {
+ if (process.env.NODE_ENV === 'production') {
+ return null
+ }
+
+ const allowed = ['fullscreen']
+
+ return Object.keys(options)
+ .filter(key => allowed.includes(key))
+ .map(key => {
+ return `${key}: ${options[key]}`
+ })
+ .concat(`uid: ${++sequentialCounter}`)
+ .join(', ')
+}
+
+function getTargetView(target) {
+ if (isObject(target) && isDef(target.$el)) {
+ return target.$el.nativeView
+ } else if (isDef(target.nativeView)) {
+ return target.nativeView
+ } else if (target[VUE_ELEMENT_REF]) {
+ return target
+ }
+}
+
+function _findParentModalEntry(vm) {
+ if (!vm) {
+ return false
+ }
+
+ let entry = vm.$parent
+ while (entry && entry.$options.name !== 'ModalEntry') {
+ entry = entry.$parent
+ }
+
+ return entry
+}
+
+export default {
+ install(Vue) {
+ Vue.mixin({
+ created() {
+ const self = this
+ this.$modal = {
+ close(data) {
+ const entry = _findParentModalEntry(self)
+
+ if (entry) {
+ entry.closeCb(data)
+ }
+ }
+ }
+ }
+ })
+
+ Vue.prototype.$showModal = function (component, options) {
+ return new Promise(resolve => {
+ let resolved = false
+ const closeCb = data => {
+ if (resolved) return
+
+ resolved = true
+ resolve(data)
+ modalPage.closeModal()
+
+ // emitted to show up in devtools
+ // for debugging purposes
+ navEntryInstance.$emit('modal:close', data)
+ navEntryInstance.$destroy()
+ }
+
+ // build options object with defaults
+ options = Object.assign(
+ {
+ target: this.$root
+ },
+ options,
+ {
+ context: null,
+ closeCallback: closeCb
+ }
+ )
+
+ const navEntryInstance = new Vue({
+ name: 'ModalEntry',
+ parent: options.target,
+ methods: {
+ closeCb
+ },
+ render: h =>
+ h(component, {
+ props: options.props,
+ key: serializeModalOptions(options)
+ })
+ })
+ const modalPage = navEntryInstance.$mount().$el.nativeView
+ updateDevtools()
+
+ getTargetView(options.target).showModal(modalPage, options)
+ })
+ }
+ }
+}
diff --git a/platform/nativescript/plugins/navigator-plugin.js b/platform/nativescript/plugins/navigator-plugin.js
new file mode 100644
index 00000000..f6d3ee87
--- /dev/null
+++ b/platform/nativescript/plugins/navigator-plugin.js
@@ -0,0 +1,134 @@
+import { isObject, isDef, isPrimitive } from 'shared/util'
+import { getFrame } from '../util/frame'
+import { updateDevtools } from '../util'
+
+let sequentialCounter = 0
+
+function serializeNavigationOptions(options) {
+ if (process.env.NODE_ENV === 'production') {
+ return null
+ }
+
+ const allowed = ['backstackVisible', 'clearHistory']
+
+ return Object.keys(options)
+ .filter(key => allowed.includes(key))
+ .map(key => {
+ return `${key}: ${options[key]}`
+ })
+ .concat(`uid: ${++sequentialCounter}`)
+ .join(', ')
+}
+
+export function getFrameInstance(frame) {
+ // get the frame that we need to navigate
+ // this can be a frame id (String)
+ // a Vue ref to a frame
+ // a Frame ViewNode
+ // or a Frame instance
+ if (isObject(frame) && isDef(frame.$el)) {
+ frame = frame.$el.nativeView
+ } else if (isPrimitive(frame)) {
+ frame = require('@nativescript/core').Frame.getFrameById(frame)
+ } else if (isDef(frame.nativeView)) {
+ frame = frame.nativeView
+ }
+ // finally get the component instance for this frame
+ return getFrame(frame.id, frame)
+}
+
+export function findParentFrame(vm) {
+ if (!vm) {
+ return false
+ }
+
+ let entry = vm.$parent
+ while (entry && entry.$options.name !== 'Frame') {
+ entry = entry.$parent
+ }
+
+ return entry
+}
+
+export default {
+ install(Vue) {
+ Vue.navigateBack = Vue.prototype.$navigateBack = function (
+ options,
+ backstackEntry = null
+ ) {
+ const parentFrame = findParentFrame(this)
+ const defaultOptions = {
+ frame: parentFrame ? parentFrame : 'default'
+ }
+ options = Object.assign({}, defaultOptions, options)
+ const frame = getFrameInstance(options.frame)
+
+ frame.back(backstackEntry)
+ }
+
+ Vue.navigateTo = Vue.prototype.$navigateTo = function (component, options) {
+ const defaultOptions = {
+ frame: 'default'
+ }
+ // build options object with defaults
+ options = Object.assign({}, defaultOptions, options)
+
+ return new Promise(resolve => {
+ const frame = getFrameInstance(options.frame)
+ const key = serializeNavigationOptions(options)
+ const navEntryInstance = new Vue({
+ abstract: true,
+ functional: true,
+ name: 'NavigationEntry',
+ parent: frame,
+ frame,
+ render: h =>
+ h(component, {
+ props: options.props,
+ key
+ })
+ })
+ const page = navEntryInstance.$mount().$el.nativeView
+
+ updateDevtools()
+
+ const resolveOnEvent = options.resolveOnEvent
+ // ensure we dont resolve twice event though this should never happen!
+ let resolved = false
+
+ const handler = args => {
+ if (args.isBackNavigation) {
+ page.off('navigatedFrom', handler)
+ navEntryInstance.$destroy()
+ }
+ }
+ page.on('navigatedFrom', handler)
+
+ if (resolveOnEvent) {
+ const resolveHandler = args => {
+ if (!resolved) {
+ resolved = true
+ resolve(page)
+ }
+ page.off(resolveOnEvent, resolveHandler)
+ }
+ page.on(resolveOnEvent, resolveHandler)
+ }
+
+ // ensure that the navEntryInstance vue instance is destroyed when the
+ // page is disposed (clearHistory: true for example)
+ const dispose = page.disposeNativeView
+ page.disposeNativeView = (...args) => {
+ navEntryInstance.$destroy()
+ dispose.call(page, args)
+ }
+
+ frame.navigate(Object.assign({}, options, { create: () => page }))
+ if (!resolveOnEvent) {
+ resolved = true
+ resolve(page)
+ }
+ })
+ }
+ }
+}
diff --git a/platform/nativescript/renderer/CommentNode.js b/platform/nativescript/renderer/CommentNode.js
new file mode 100644
index 00000000..89050ce6
--- /dev/null
+++ b/platform/nativescript/renderer/CommentNode.js
@@ -0,0 +1,10 @@
+import ElementNode from './ElementNode'
+
+export default class CommentNode extends ElementNode {
+ constructor(text) {
+ super('comment')
+
+ this.nodeType = 8
+ this.text = text
+ }
+}
diff --git a/platform/nativescript/renderer/DocumentNode.js b/platform/nativescript/renderer/DocumentNode.js
new file mode 100644
index 00000000..9c4db4bf
--- /dev/null
+++ b/platform/nativescript/renderer/DocumentNode.js
@@ -0,0 +1,51 @@
+import CommentNode from './CommentNode'
+import ElementNode from './ElementNode'
+import ViewNode from './ViewNode'
+import TextNode from './TextNode'
+
+export default class DocumentNode extends ViewNode {
+ constructor() {
+ super()
+
+ this.nodeType = 9
+ 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) {
+ try {
+ return new CommentNode(text)
+ } catch (err) {
+ console.log(err)
+ }
+ }
+
+ static createElement(tagName) {
+ try {
+ return new ElementNode(tagName)
+ } catch (err) {
+ console.log(err)
+ }
+ }
+
+ static createElementNS(namespace, tagName) {
+ try {
+ return new ElementNode(namespace + ':' + tagName)
+ } catch (err) {
+ console.log(err)
+ }
+ }
+
+ static createTextNode(text) {
+ try {
+ return new TextNode(text)
+ } catch (err) {
+ console.log(err)
+ }
+ }
+}
diff --git a/platform/nativescript/renderer/ElementNode.js b/platform/nativescript/renderer/ElementNode.js
new file mode 100644
index 00000000..cf45a0e2
--- /dev/null
+++ b/platform/nativescript/renderer/ElementNode.js
@@ -0,0 +1,50 @@
+import { getViewClass } from '../element-registry'
+import ViewNode from './ViewNode'
+
+export const VUE_ELEMENT_REF = '__vue_element_ref__'
+
+export default class ElementNode extends ViewNode {
+ constructor(tagName) {
+ super()
+
+ this.nodeType = 1
+ this.tagName = tagName
+
+ const viewClass = getViewClass(tagName)
+ if (!viewClass) {
+ throw new TypeError(
+ `No native component for element tag name ${tagName}.`
+ )
+ }
+ this._nativeView = new viewClass()
+ this._nativeView[VUE_ELEMENT_REF] = this
+ }
+
+ toString() {
+ return this.nativeView.toString()
+ }
+
+ appendChild(childNode) {
+ super.appendChild(childNode)
+
+ if (childNode.nodeType === 3) {
+ this.setText(childNode.text)
+ }
+ }
+
+ insertBefore(childNode, referenceNode) {
+ super.insertBefore(childNode, referenceNode)
+
+ if (childNode.nodeType === 3) {
+ this.setText(childNode.text)
+ }
+ }
+
+ removeChild(childNode) {
+ super.removeChild(childNode)
+
+ if (childNode.nodeType === 3) {
+ this.setText('')
+ }
+ }
+}
diff --git a/platform/nativescript/renderer/TextNode.js b/platform/nativescript/renderer/TextNode.js
new file mode 100644
index 00000000..9ed539e1
--- /dev/null
+++ b/platform/nativescript/renderer/TextNode.js
@@ -0,0 +1,19 @@
+import ViewNode from './ViewNode'
+
+export default class TextNode extends ViewNode {
+ constructor(text) {
+ super()
+
+ this.nodeType = 3
+ this.text = text
+
+ this._meta = {
+ skipAddToDom: true
+ }
+ }
+
+ setText(text) {
+ this.text = text
+ this.parentNode.setText(text)
+ }
+}
diff --git a/platform/nativescript/renderer/ViewNode.js b/platform/nativescript/renderer/ViewNode.js
new file mode 100644
index 00000000..291ea3ea
--- /dev/null
+++ b/platform/nativescript/renderer/ViewNode.js
@@ -0,0 +1,272 @@
+import set from 'set-value'
+
+import { getViewMeta, normalizeElementName } from '../element-registry'
+import * as viewUtil from './utils'
+
+const XML_ATTRIBUTES = Object.freeze([
+ 'style',
+ 'rows',
+ 'columns',
+ 'fontAttributes'
+])
+
+export default class ViewNode {
+ constructor() {
+ this.nodeType = null
+ this._tagName = null
+ this.parentNode = null
+ this.childNodes = []
+ this.prevSibling = null
+ this.nextSibling = null
+
+ this._ownerDocument = null
+ this._nativeView = null
+ this._meta = null
+
+ /* istanbul ignore next
+ * make vue happy :)
+ */
+ this.hasAttribute = this.removeAttribute = () => false
+ }
+
+ /* istanbul ignore next */
+ toString() {
+ return `${this.constructor.name}(${this.tagName})`
+ }
+
+ set tagName(name) {
+ this._tagName = normalizeElementName(name)
+ }
+
+ get tagName() {
+ return this._tagName
+ }
+
+ get firstChild() {
+ return this.childNodes.length ? this.childNodes[0] : null
+ }
+
+ get lastChild() {
+ return this.childNodes.length
+ ? this.childNodes[this.childNodes.length - 1]
+ : null
+ }
+
+ get nativeView() {
+ return this._nativeView
+ }
+
+ set nativeView(view) {
+ if (this._nativeView) {
+ throw new Error(`Can't override native view.`)
+ }
+
+ this._nativeView = view
+ }
+
+ get meta() {
+ if (this._meta) {
+ return this._meta
+ }
+
+ return (this._meta = getViewMeta(this.tagName))
+ }
+
+ /* istanbul ignore next */
+ get ownerDocument() {
+ if (this._ownerDocument) {
+ return this._ownerDocument
+ }
+
+ let el = this
+ while ((el = el.parentNode).nodeType !== 9) {
+ // do nothing
+ }
+
+ return (this._ownerDocument = el)
+ }
+
+ getAttribute(key) {
+ return this.nativeView[key]
+ }
+
+ /* istanbul ignore next */
+ setAttribute(key, value) {
+ const isAndroid = global.isAndroid
+ const isIOS = global.isIOS
+ const nv = this.nativeView
+
+ try {
+ if (XML_ATTRIBUTES.indexOf(key) !== -1) {
+ nv[key] = value
+ } else {
+ // detect expandable attrs for boolean values
+ // See https://vuejs.org/v2/guide/components-props.html#Passing-a-Boolean
+ if (
+ require('@nativescript/core').Utils.isBoolean(nv[key]) &&
+ value === ''
+ ) {
+ value = true
+ }
+
+ if (isAndroid && key.startsWith('android:')) {
+ set(nv, key.substr(8), value)
+ } else if (isIOS && key.startsWith('ios:')) {
+ set(nv, key.substr(4), value)
+ } else if (key.endsWith('.decode')) {
+ set(
+ nv,
+ key.slice(0, -7),
+ require('@nativescript/core').XmlParser._dereferenceEntities(value)
+ )
+ } else {
+ set(nv, key, value)
+ }
+ }
+ } catch (e) {
+ // ignore
+ }
+ }
+
+ /* istanbul ignore next */
+ setStyle(property, value) {
+ if (!value || !(value = value.trim()).length) {
+ return
+ }
+
+ if (property.endsWith('Align')) {
+ // NativeScript uses Alignment instead of Align, this ensures that text-align works
+ property += 'ment'
+ }
+ this.nativeView.style[property] = value
+ }
+
+ /* istanbul ignore next */
+ setText(text) {
+ if (this.nodeType === 3) {
+ this.parentNode.setText(text)
+ } else {
+ this.setAttribute('text', text)
+ }
+ }
+
+ /* istanbul ignore next */
+ addEventListener(event, handler) {
+ this.nativeView.on(event, handler)
+ }
+
+ /* istanbul ignore next */
+ removeEventListener(event) {
+ this.nativeView.off(event)
+ }
+
+ insertBefore(childNode, referenceNode) {
+ if (!childNode) {
+ throw new Error(`Can't insert child.`)
+ }
+
+ if (
+ referenceNode &&
+ referenceNode.parentNode &&
+ referenceNode.parentNode !== this
+ ) {
+ throw new Error(
+ `Can't insert child, because the reference node has a different parent.`
+ )
+ }
+
+ if (childNode.parentNode && childNode.parentNode !== this) {
+ throw new Error(
+ `Can't insert child, because it already has a different parent.`
+ )
+ }
+
+ if (childNode.parentNode === this) {
+ // in case the childNode is already a child node of this view
+ // we need to first remove it to clean up childNodes, parentNode, prev/next siblings
+ // we are adding back the child right after - this is often the case when the order
+ // of children has to change (including comment nodes created by vue)
+ // fixes #608
+ this.removeChild(childNode)
+ // we don't need to throw an error here, because it is a valid case
+ // for example when switching the order of elements in the tree
+ // fixes #127 - see for more details
+ // fixes #240
+ // throw new Error(`Can't insert child, because it is already a child.`)
+ }
+
+ // in some rare cases insertBefore is called with a null referenceNode
+ // this makes sure that it get's appended as the last child
+ if (!referenceNode) {
+ return this.appendChild(childNode)
+ }
+
+ let index = this.childNodes.indexOf(referenceNode)
+
+ childNode.parentNode = this
+ childNode.nextSibling = referenceNode
+ childNode.prevSibling = this.childNodes[index - 1]
+ if (childNode.prevSibling) childNode.prevSibling.nextSibling = childNode
+
+ referenceNode.prevSibling = childNode
+ this.childNodes.splice(index, 0, childNode)
+
+ viewUtil.insertChild(this, childNode, index)
+ }
+
+ appendChild(childNode) {
+ if (!childNode) {
+ throw new Error(`Can't append child.`)
+ }
+
+ if (childNode.parentNode && childNode.parentNode !== this) {
+ throw new Error(
+ `Can't append child, because it already has a different parent.`
+ )
+ }
+
+ childNode.parentNode = this
+ if (this.lastChild) {
+ childNode.prevSibling = this.lastChild
+ this.lastChild.nextSibling = childNode
+ }
+
+ this.childNodes.push(childNode)
+
+ viewUtil.insertChild(this, childNode)
+ }
+
+ removeChild(childNode) {
+ if (!childNode) {
+ throw new Error(`Can't remove child.`)
+ }
+
+ if (!childNode.parentNode) {
+ throw new Error(`Can't remove child, because it has no parent.`)
+ }
+
+ if (childNode.parentNode !== this) {
+ throw new Error(`Can't remove child, because it has a different parent.`)
+ }
+
+ childNode.parentNode = null
+
+ if (childNode.prevSibling) {
+ childNode.prevSibling.nextSibling = childNode.nextSibling
+ }
+
+ if (childNode.nextSibling) {
+ childNode.nextSibling.prevSibling = childNode.prevSibling
+ }
+
+ // reset the prevSibling and nextSibling. If not, a keep-alived component will
+ // still have a filled nextSibling attribute so vue will not
+ // insert the node again to the parent. See #220
+ childNode.prevSibling = null
+ childNode.nextSibling = null
+
+ this.childNodes = this.childNodes.filter(node => node !== childNode)
+
+ viewUtil.removeChild(this, childNode)
+ }
+}
diff --git a/platform/nativescript/renderer/utils.js b/platform/nativescript/renderer/utils.js
new file mode 100644
index 00000000..6dac0a85
--- /dev/null
+++ b/platform/nativescript/renderer/utils.js
@@ -0,0 +1,100 @@
+let View
+export function isView(view) {
+ if (!View) {
+ View = require('@nativescript/core').View
+ }
+ return view instanceof View
+}
+
+let LayoutBase
+export function isLayout(view) {
+ if (!LayoutBase) {
+ LayoutBase = require('@nativescript/core').LayoutBase
+ }
+ return view instanceof LayoutBase
+}
+
+let ContentView
+export function isContentView(view) {
+ if (!ContentView) {
+ ContentView = require('@nativescript/core').ContentView
+ }
+ return view instanceof ContentView
+}
+
+export function insertChild(parentNode, childNode, atIndex = -1) {
+ if (!parentNode) {
+ return
+ }
+
+ if (parentNode.meta && typeof parentNode.meta.insertChild === 'function') {
+ return parentNode.meta.insertChild(parentNode, childNode, atIndex)
+ }
+
+ if (childNode.meta.skipAddToDom) {
+ return
+ }
+
+ const parentView = parentNode.nativeView
+ const childView = childNode.nativeView
+
+ if (isLayout(parentView)) {
+ if (childView.parent === parentView) {
+ let index = parentView.getChildIndex(childView)
+ if (index !== -1) {
+ parentView.removeChild(childView)
+ }
+ }
+ if (atIndex !== -1) {
+ parentView.insertChild(childView, atIndex)
+ } else {
+ parentView.addChild(childView)
+ }
+ } else if (isContentView(parentView)) {
+ if (childNode.nodeType === 8) {
+ parentView._addView(childView, atIndex)
+ } else {
+ parentView.content = childView
+ }
+ } else if (parentView && parentView._addChildFromBuilder) {
+ parentView._addChildFromBuilder(
+ childNode._nativeView.constructor.name,
+ childView
+ )
+ } else {
+ // throw new Error("Parent can"t contain children: " + parent.nodeName + ", " + parent);
+ }
+}
+
+export function removeChild(parentNode, childNode) {
+ if (!parentNode) {
+ return
+ }
+
+ if (parentNode.meta && typeof parentNode.meta.removeChild === 'function') {
+ return parentNode.meta.removeChild(parentNode, childNode)
+ }
+
+ if (childNode.meta.skipAddToDom) {
+ return
+ }
+
+ const parentView = parentNode.nativeView
+ const childView = childNode.nativeView
+
+ if (isLayout(parentView)) {
+ parentView.removeChild(childView)
+ } else if (isContentView(parentView)) {
+ if (parentView.content === childView) {
+ parentView.content = null
+ }
+
+ if (childNode.nodeType === 8) {
+ parentView._removeView(childView)
+ }
+ } else if (isView(parentView)) {
+ parentView._removeView(childView)
+ } else {
+ // throw new Error("Unknown parent type: " + parent);
+ }
+}
diff --git a/platform/nativescript/runtime/components/action-bar.js b/platform/nativescript/runtime/components/action-bar.js
new file mode 100644
index 00000000..d1a975c3
--- /dev/null
+++ b/platform/nativescript/runtime/components/action-bar.js
@@ -0,0 +1,7 @@
+export default {
+ template: `
+
+
+
+ `
+}
diff --git a/platform/nativescript/runtime/components/android.js b/platform/nativescript/runtime/components/android.js
new file mode 100644
index 00000000..170fa464
--- /dev/null
+++ b/platform/nativescript/runtime/components/android.js
@@ -0,0 +1,8 @@
+export default {
+ functional: true,
+ render(h, { children }) {
+ if (global.isAndroid) {
+ return children
+ }
+ }
+}
diff --git a/platform/nativescript/runtime/components/bottom-navigation.js b/platform/nativescript/runtime/components/bottom-navigation.js
new file mode 100644
index 00000000..14ded016
--- /dev/null
+++ b/platform/nativescript/runtime/components/bottom-navigation.js
@@ -0,0 +1,28 @@
+export default {
+ model: {
+ prop: 'selectedIndex',
+ event: 'selectedIndexChange'
+ },
+
+ render(h) {
+ return h(
+ 'NativeBottomNavigation',
+ {
+ on: this.$listeners,
+ attrs: this.$attrs
+ },
+ this.$slots.default
+ )
+ },
+
+ methods: {
+ registerTabStrip(tabStrip) {
+ this.$el.setAttribute('tabStrip', tabStrip)
+ },
+ registerTabContentItem(tabContentItem) {
+ const items = this.$el.nativeView.items || []
+
+ this.$el.setAttribute('items', items.concat([tabContentItem]))
+ }
+ }
+}
diff --git a/platform/nativescript/runtime/components/frame.js b/platform/nativescript/runtime/components/frame.js
new file mode 100644
index 00000000..cdb7bc2b
--- /dev/null
+++ b/platform/nativescript/runtime/components/frame.js
@@ -0,0 +1,150 @@
+import { setFrame, getFrame, deleteFrame } from '../../util/frame'
+import { warn } from 'core/util/debug'
+
+export default {
+ props: {
+ id: {
+ default: 'default'
+ },
+ transition: {
+ type: [String, Object],
+ required: false,
+ default: null
+ },
+ 'ios:transition': {
+ type: [String, Object],
+ required: false,
+ default: null
+ },
+ 'android:transition': {
+ type: [String, Object],
+ required: false,
+ default: null
+ },
+ clearHistory: {
+ type: Boolean,
+ required: false,
+ default: false
+ },
+ backstackVisible: {
+ type: Boolean,
+ required: false,
+ default: true
+ },
+ // injected by the template compiler
+ hasRouterView: {
+ default: false
+ }
+ },
+ data() {
+ return {
+ properties: {}
+ }
+ },
+ created() {
+ this.properties = Object.assign({}, this.$attrs, this.$props)
+
+ setFrame(this.properties.id, this)
+ },
+ destroyed() {
+ deleteFrame(this.properties.id)
+ },
+ render(h) {
+ let vnode = null
+
+ // Render slot to ensure default page is displayed
+ if (this.$slots.default) {
+ if (
+ process.env.NODE_ENV !== 'production' &&
+ this.$slots.default.length > 1
+ ) {
+ warn(
+ `The element can only have a single child element, that is the defaultPage.`
+ )
+ }
+ vnode = this.$slots.default[0]
+ vnode.key = 'default'
+ }
+
+ return h(
+ 'NativeFrame',
+ {
+ attrs: this.properties,
+ on: this.$listeners
+ },
+ [vnode]
+ )
+ },
+ methods: {
+ _getFrame() {
+ return this.$el.nativeView
+ },
+
+ _ensureTransitionObject(transition) {
+ if (typeof transition === 'string') {
+ return { name: transition }
+ }
+ return transition
+ },
+
+ _composeTransition(entry) {
+ const isAndroid = global.isAndroid
+ const platformEntryProp = `transition${isAndroid ? 'Android' : 'iOS'}`
+ const entryProp = entry[platformEntryProp]
+ ? platformEntryProp
+ : 'transition'
+ const platformProp = `${isAndroid ? 'android' : 'ios'}:transition`
+ const prop = this[platformProp] ? platformProp : 'transition'
+
+ if (entry[entryProp]) {
+ entry[entryProp] = this._ensureTransitionObject(entry[entryProp])
+ } else if (this[prop]) {
+ entry[entryProp] = this._ensureTransitionObject(this[prop])
+ }
+
+ return entry
+ },
+
+ notifyFirstPageMounted(pageVm) {
+ let options = {
+ backstackVisible: this.backstackVisible,
+ clearHistory: this.clearHistory,
+ create: () => pageVm.$el.nativeView
+ }
+ this.navigate(options)
+ },
+
+ navigate(entry, back = false) {
+ const frame = this._getFrame()
+
+ if (back) {
+ return frame.goBack(entry)
+ }
+
+ // resolve the page from the entry and attach a navigatedTo listener
+ // to fire the frame events
+ const page = entry.create()
+ page.once('navigatedTo', () => {
+ this.$emit('navigated', entry)
+ })
+
+ const handler = args => {
+ if (args.isBackNavigation) {
+ page.off('navigatedFrom', handler)
+
+ this.$emit('navigatedBack', entry)
+ }
+ }
+ page.on('navigatedFrom', handler)
+
+ entry.create = () => page
+
+ this._composeTransition(entry)
+ frame.navigate(entry)
+ },
+
+ back(backstackEntry = null) {
+ this.navigate(backstackEntry, true)
+ }
+ }
+}
diff --git a/platform/nativescript/runtime/components/index.js b/platform/nativescript/runtime/components/index.js
new file mode 100644
index 00000000..d1a4215d
--- /dev/null
+++ b/platform/nativescript/runtime/components/index.js
@@ -0,0 +1,15 @@
+export { default as ActionBar } from './action-bar'
+export { default as android } from './android'
+export { default as Frame } from './frame'
+export { default as ios } from './ios'
+export { default as ListView } from './list-view'
+export { default as Page } from './page'
+export { default as TabView } from './tab-view'
+export { default as TabViewItem } from './tab-view-item'
+export { default as BottomNavigation } from './bottom-navigation'
+export { default as Tabs } from './tabs'
+export { default as TabStrip } from './tab-strip'
+export { default as TabStripItem } from './tab-strip-item'
+export { default as TabContentItem } from './tab-content-item'
+export { default as transition } from './transition'
+export { default as VTemplate } from './v-template'
diff --git a/platform/nativescript/runtime/components/ios.js b/platform/nativescript/runtime/components/ios.js
new file mode 100644
index 00000000..0cb52a14
--- /dev/null
+++ b/platform/nativescript/runtime/components/ios.js
@@ -0,0 +1,8 @@
+export default {
+ functional: true,
+ render(h, { children }) {
+ if (global.isIOS) {
+ return children
+ }
+ }
+}
diff --git a/platform/nativescript/runtime/components/list-view.js b/platform/nativescript/runtime/components/list-view.js
new file mode 100644
index 00000000..9bb49829
--- /dev/null
+++ b/platform/nativescript/runtime/components/list-view.js
@@ -0,0 +1,113 @@
+import { VUE_VIEW } from './v-template'
+import { extend } from 'shared/util'
+
+export default {
+ props: {
+ items: {
+ type: [Array, Object],
+ validator: val => {
+ const ObservableArray = require('@nativescript/core').ObservableArray
+ return Array.isArray(val) || val instanceof ObservableArray
+ },
+ required: true
+ },
+ '+alias': {
+ type: String,
+ default: 'item'
+ },
+ '+index': {
+ type: String
+ }
+ },
+
+ template: `
+
+
+
+ `,
+
+ watch: {
+ items: {
+ handler(newVal) {
+ this.$refs.listView.setAttribute('items', newVal)
+ this.refresh()
+ },
+ deep: true
+ }
+ },
+
+ created() {
+ // we need to remove the itemTap handler from a clone of the $listeners
+ // object because we are emitting the event ourselves with added data.
+ const listeners = extend({}, this.$listeners)
+ delete listeners.itemTap
+ this.listeners = listeners
+
+ this.getItemContext = getItemContext.bind(this)
+ },
+
+ mounted() {
+ if (!this.$templates) {
+ return
+ }
+
+ this.$refs.listView.setAttribute(
+ 'itemTemplates',
+ this.$templates.getKeyedTemplates()
+ )
+ this.$refs.listView.setAttribute('itemTemplateSelector', (item, index) => {
+ return this.$templates.selectorFn(this.getItemContext(item, index))
+ })
+ },
+
+ methods: {
+ onItemTap(args) {
+ this.$emit('itemTap', extend({ item: this.getItem(args.index) }, args))
+ },
+ onItemLoading(args) {
+ if (!this.$templates) {
+ return
+ }
+
+ const index = args.index
+ const items = args.object.items
+
+ const currentItem = this.getItem(index)
+
+ const name = args.object._itemTemplateSelector(currentItem, index, items)
+ const context = this.getItemContext(currentItem, index)
+ const oldVnode = args.view && args.view[VUE_VIEW]
+
+ args.view = this.$templates.patchTemplate(name, context, oldVnode)
+ },
+ refresh() {
+ this.$refs.listView.nativeView.refresh()
+ },
+ getItem(idx) {
+ return typeof this.items.getItem === 'function'
+ ? this.items.getItem(idx)
+ : this.items[idx]
+ }
+ }
+}
+
+function getItemContext(
+ item,
+ index,
+ alias = this.$props['+alias'],
+ index_alias = this.$props['+index']
+) {
+ return {
+ [alias]: item,
+ [index_alias || '$index']: index,
+ $even: index % 2 === 0,
+ $odd: index % 2 !== 0
+ }
+}
diff --git a/platform/nativescript/runtime/components/page.js b/platform/nativescript/runtime/components/page.js
new file mode 100644
index 00000000..f295fa93
--- /dev/null
+++ b/platform/nativescript/runtime/components/page.js
@@ -0,0 +1,57 @@
+import { updateDevtools } from '../../util'
+
+export const PAGE_REF = '__vuePageRef__'
+
+export default {
+ render(h) {
+ return h(
+ 'NativePage',
+ {
+ attrs: this.$attrs,
+ on: this.$listeners
+ },
+ this.$slots.default
+ )
+ },
+ mounted() {
+ this.$el.nativeView[PAGE_REF] = this
+
+ let frame = this._findParentFrame()
+
+ // we only need call this for the "defaultPage" of the frame
+ // which is equivalent to testing if any page is "current" in the frame
+ if (frame && !frame.firstPageMounted && !frame.$el.nativeView.currentPage) {
+ frame.firstPageMounted = true
+ frame.notifyFirstPageMounted(this)
+ }
+
+ const handler = e => {
+ if (e.isBackNavigation) {
+ this.$el.nativeView.off('navigatedFrom', handler)
+ this.$parent.$destroy()
+ }
+ }
+
+ this.$el.nativeView.on('navigatedFrom', handler)
+
+ // ensure that the parent vue instance is destroyed when the
+ // page is disposed (clearHistory: true for example)
+ const dispose = this.$el.nativeView.disposeNativeView
+ this.$el.nativeView.disposeNativeView = (...args) => {
+ this.$parent.$destroy()
+ dispose.call(this.$el.nativeView, args)
+ updateDevtools()
+ }
+ },
+ methods: {
+ _findParentFrame() {
+ let frame = this.$parent
+
+ while (frame && frame.$options.name !== 'Frame') {
+ frame = frame.$parent
+ }
+
+ return frame
+ }
+ }
+}
diff --git a/platform/nativescript/runtime/components/tab-content-item.js b/platform/nativescript/runtime/components/tab-content-item.js
new file mode 100644
index 00000000..5da57777
--- /dev/null
+++ b/platform/nativescript/runtime/components/tab-content-item.js
@@ -0,0 +1,15 @@
+import { warn } from 'core/util/debug'
+
+export default {
+ template: ` `,
+
+ mounted() {
+ if (this.$el.childNodes.length > 1) {
+ warn('TabContentItem should contain only 1 root element', this)
+ }
+
+ let _nativeView = this.$el.nativeView
+ _nativeView.view = this.$el.childNodes[0].nativeView
+ this.$parent.registerTabContentItem(_nativeView)
+ }
+}
diff --git a/platform/nativescript/runtime/components/tab-strip-item.js b/platform/nativescript/runtime/components/tab-strip-item.js
new file mode 100644
index 00000000..6acf1972
--- /dev/null
+++ b/platform/nativescript/runtime/components/tab-strip-item.js
@@ -0,0 +1,17 @@
+export default {
+ render(h) {
+ return h(
+ 'NativeTabStripItem',
+ {
+ on: this.$listeners,
+ attrs: this.$attrs
+ },
+ this.$slots.default
+ )
+ },
+
+ mounted() {
+ let _nativeView = this.$el.nativeView
+ this.$parent.registerTabStripItem(_nativeView)
+ }
+}
diff --git a/platform/nativescript/runtime/components/tab-strip.js b/platform/nativescript/runtime/components/tab-strip.js
new file mode 100644
index 00000000..eebb91f9
--- /dev/null
+++ b/platform/nativescript/runtime/components/tab-strip.js
@@ -0,0 +1,25 @@
+export default {
+ render(h) {
+ return h(
+ 'NativeTabStrip',
+ {
+ on: this.$listeners,
+ attrs: this.$attrs
+ },
+ this.$slots.default
+ )
+ },
+
+ mounted() {
+ let _nativeView = this.$el.nativeView
+ this.$parent.registerTabStrip(_nativeView)
+ },
+
+ methods: {
+ registerTabStripItem(tabStripItem) {
+ const items = this.$el.nativeView.items || []
+
+ this.$el.setAttribute('items', items.concat([tabStripItem]))
+ }
+ }
+}
diff --git a/platform/nativescript/runtime/components/tab-view-item.js b/platform/nativescript/runtime/components/tab-view-item.js
new file mode 100644
index 00000000..9fd35697
--- /dev/null
+++ b/platform/nativescript/runtime/components/tab-view-item.js
@@ -0,0 +1,15 @@
+import { warn } from 'core/util/debug'
+
+export default {
+ template: ` `,
+
+ mounted() {
+ if (this.$el.childNodes.length > 1) {
+ warn('TabViewItem should contain only 1 root element', this)
+ }
+
+ let _nativeView = this.$el.nativeView
+ _nativeView.view = this.$el.childNodes[0].nativeView
+ this.$parent.registerTab(_nativeView)
+ }
+}
diff --git a/platform/nativescript/runtime/components/tab-view.js b/platform/nativescript/runtime/components/tab-view.js
new file mode 100644
index 00000000..1a6b3083
--- /dev/null
+++ b/platform/nativescript/runtime/components/tab-view.js
@@ -0,0 +1,25 @@
+export default {
+ model: {
+ prop: 'selectedIndex',
+ event: 'selectedIndexChange'
+ },
+
+ render(h) {
+ return h(
+ 'NativeTabView',
+ {
+ on: this.$listeners,
+ attrs: this.$attrs
+ },
+ this.$slots.default
+ )
+ },
+
+ methods: {
+ registerTab(tabView) {
+ const items = this.$el.nativeView.items || []
+
+ this.$el.setAttribute('items', items.concat([tabView]))
+ }
+ }
+}
diff --git a/platform/nativescript/runtime/components/tabs.js b/platform/nativescript/runtime/components/tabs.js
new file mode 100644
index 00000000..7d4976e0
--- /dev/null
+++ b/platform/nativescript/runtime/components/tabs.js
@@ -0,0 +1,28 @@
+export default {
+ model: {
+ prop: 'selectedIndex',
+ event: 'selectedIndexChange'
+ },
+
+ render(h) {
+ return h(
+ 'NativeTabs',
+ {
+ on: this.$listeners,
+ attrs: this.$attrs
+ },
+ this.$slots.default
+ )
+ },
+
+ methods: {
+ registerTabStrip(tabStrip) {
+ this.$el.setAttribute('tabStrip', tabStrip)
+ },
+ registerTabContentItem(tabContentItem) {
+ const items = this.$el.nativeView.items || []
+
+ this.$el.setAttribute('items', items.concat([tabContentItem]))
+ }
+ }
+}
diff --git a/platform/nativescript/runtime/components/transition.js b/platform/nativescript/runtime/components/transition.js
new file mode 100644
index 00000000..c59f9ceb
--- /dev/null
+++ b/platform/nativescript/runtime/components/transition.js
@@ -0,0 +1,8 @@
+export {
+ transitionProps,
+ extractTransitionData
+} from 'web/runtime/components/transition'
+
+import Transition from 'web/runtime/components/transition'
+
+export default Transition
diff --git a/platform/nativescript/runtime/components/v-template.js b/platform/nativescript/runtime/components/v-template.js
new file mode 100644
index 00000000..23f5b294
--- /dev/null
+++ b/platform/nativescript/runtime/components/v-template.js
@@ -0,0 +1,116 @@
+import { patch } from '../patch'
+import { flushCallbacks } from 'core/util/next-tick'
+
+export const VUE_VIEW = '__vueVNodeRef__'
+
+let tid = 0
+export default {
+ props: {
+ name: {
+ type: String
+ },
+ if: {
+ type: String
+ }
+ },
+
+ mounted() {
+ if (!this.$scopedSlots.default) {
+ return
+ }
+
+ this.$templates = this.$el.parentNode.$templates = this.$parent.$templates =
+ this.$parent.$templates || new TemplateBag()
+ this.$templates.registerTemplate(
+ this.$props.name || (this.$props.if ? `v-template-${tid++}` : 'default'),
+ this.$props.if,
+ this.$scopedSlots.default
+ )
+ },
+
+ render(h) {}
+}
+
+export class TemplateBag {
+ constructor() {
+ this._templateMap = new Map()
+ }
+
+ registerTemplate(name, condition, scopedFn) {
+ this._templateMap.set(name, {
+ scopedFn,
+ conditionFn: this.getConditionFn(condition),
+ keyedTemplate: new VueKeyedTemplate(name, scopedFn)
+ })
+ }
+
+ get selectorFn() {
+ let self = this
+ return function templateSelectorFn(item) {
+ const iterator = self._templateMap.entries()
+ let curr
+ while ((curr = iterator.next().value)) {
+ const [name, { conditionFn }] = curr
+ try {
+ if (conditionFn(item)) {
+ return name
+ }
+ } catch (err) {}
+ }
+ return 'default'
+ }
+ }
+
+ getConditionFn(condition) {
+ return new Function('ctx', `with(ctx) { return !!(${condition}) }`)
+ }
+
+ getKeyedTemplate(name) {
+ return this._templateMap.get(name).keyedTemplate
+ }
+
+ patchTemplate(name, context, oldVnode) {
+ let vnode = this._templateMap.get(name).scopedFn(context)
+ // in 2.6 scopedFn returns an array!
+ if (Array.isArray(vnode)) {
+ vnode = vnode[0]
+ }
+
+ const nativeView = patch(oldVnode, vnode).nativeView
+ nativeView[VUE_VIEW] = vnode
+
+ // force flush Vue callbacks so all changes are applied immediately
+ // rather than on next tick
+ flushCallbacks()
+
+ return nativeView
+ }
+
+ getAvailable() {
+ return Array.from(this._templateMap.keys())
+ }
+
+ getKeyedTemplates() {
+ return Array.from(this._templateMap.values()).map(
+ ({ keyedTemplate }) => keyedTemplate
+ )
+ }
+}
+
+export class VueKeyedTemplate /* implements KeyedTemplate */ {
+ constructor(key, scopedFn) {
+ this._key = key
+ this._scopedFn = scopedFn
+ }
+
+ get key() {
+ return this._key
+ }
+
+ createView() {
+ // we are returning null because we don't have the data here
+ // the view will be created in the `patchTemplate` method above.
+ // see https://github.com/nativescript-vue/nativescript-vue/issues/229#issuecomment-390330474
+ return null
+ }
+}
diff --git a/platform/nativescript/runtime/directives/index.js b/platform/nativescript/runtime/directives/index.js
new file mode 100644
index 00000000..5d636b02
--- /dev/null
+++ b/platform/nativescript/runtime/directives/index.js
@@ -0,0 +1,7 @@
+import show from './show'
+import view from './view'
+
+export default {
+ show,
+ view
+}
diff --git a/platform/nativescript/runtime/directives/show.js b/platform/nativescript/runtime/directives/show.js
new file mode 100644
index 00000000..795bf42c
--- /dev/null
+++ b/platform/nativescript/runtime/directives/show.js
@@ -0,0 +1,57 @@
+import { enter, leave } from '../modules/transition'
+
+// recursively search for possible transition defined inside the component root
+function locateNode(vnode) {
+ return vnode.componentInstance && (!vnode.data || !vnode.data.transition)
+ ? locateNode(vnode.componentInstance._vnode)
+ : vnode
+}
+
+export default {
+ bind(el, { value }, vnode) {
+ vnode = locateNode(vnode)
+ const transition = vnode.data && vnode.data.transition
+ const originalVisibility = (el.__vOriginalVisibility =
+ el.getAttribute('visibility') === 'none'
+ ? ''
+ : el.getAttribute('visibility'))
+ if (value && transition) {
+ vnode.data.show = true
+ enter(vnode, () => {
+ el.setAttribute('visibility', originalVisibility)
+ })
+ } else {
+ el.setAttribute('visibility', value ? originalVisibility : 'collapsed')
+ }
+ },
+
+ update(el, { value, oldValue }, vnode) {
+ /* istanbul ignore if */
+ if (!value === !oldValue) return
+ vnode = locateNode(vnode)
+ const transition = vnode.data && vnode.data.transition
+ if (transition) {
+ vnode.data.show = true
+ if (value) {
+ enter(vnode, () => {
+ el.setAttribute('visibility', el.__vOriginalVisibility)
+ })
+ } else {
+ leave(vnode, () => {
+ el.setAttribute('visibility', 'collapsed')
+ })
+ }
+ } else {
+ el.setAttribute(
+ 'visibility',
+ value ? el.__vOriginalVisibility : 'collapsed'
+ )
+ }
+ },
+
+ unbind(el, binding, vnode, oldVnode, isDestroy) {
+ if (!isDestroy) {
+ el.setAttribute('visibility', el.__vOriginalVisibility)
+ }
+ }
+}
diff --git a/platform/nativescript/runtime/directives/view.js b/platform/nativescript/runtime/directives/view.js
new file mode 100644
index 00000000..77260236
--- /dev/null
+++ b/platform/nativescript/runtime/directives/view.js
@@ -0,0 +1,13 @@
+export default {
+ inserted(el, { arg, modifiers }) {
+ const parent = el.parentNode.nativeView
+
+ if (parent) {
+ if (modifiers.array) {
+ parent[arg] = (parent[arg] || []).push(el.nativeView)
+ } else {
+ parent[arg] = el.nativeView
+ }
+ }
+ }
+}
diff --git a/platform/nativescript/runtime/index.js b/platform/nativescript/runtime/index.js
new file mode 100644
index 00000000..abc00149
--- /dev/null
+++ b/platform/nativescript/runtime/index.js
@@ -0,0 +1,86 @@
+import { Application } from '@nativescript/core'
+import { warn } from 'core/util/index'
+import { patch } from './patch'
+import { flushCallbacks } from 'core/util/next-tick'
+import { mountComponent } from 'core/instance/lifecycle'
+import { compileToFunctions } from '../compiler/index'
+import { mustUseProp, isReservedTag, isUnknownElement } from '../util/index'
+import { registerElement, getElementMap } from '../element-registry'
+
+import Vue from 'core/index'
+import DocumentNode from '../renderer/DocumentNode'
+import platformDirectives from './directives/index'
+
+Vue.config.mustUseProp = mustUseProp
+Vue.config.isReservedTag = isReservedTag
+Vue.config.isUnknownElement = isUnknownElement
+
+Vue.$document = Vue.prototype.$document = new DocumentNode()
+
+// Exposed for advanced uses only, not public API
+Vue.__flushCallbacks__ = flushCallbacks
+
+Vue.compile = compileToFunctions
+Vue.registerElement = registerElement
+
+Object.assign(Vue.options.directives, platformDirectives)
+
+Vue.prototype.__patch__ = patch
+
+Vue.prototype.$mount = function (el, hydrating) {
+ const options = this.$options
+ // resolve template/el and convert to render function
+ if (!options.render) {
+ let template = options.template
+ if (template && typeof template !== 'string') {
+ warn('invalid template option: ' + template, this)
+ return this
+ }
+
+ if (template) {
+ const { render, staticRenderFns } = compileToFunctions(
+ template,
+ {
+ delimiters: options.delimiters,
+ comments: options.comments
+ },
+ this
+ )
+ options.render = render
+ options.staticRenderFns = staticRenderFns
+ }
+ }
+
+ return mountComponent(this, el, hydrating)
+}
+
+Vue.prototype.$start = function () {
+ let self = this
+ const AppConstructor = Vue.extend(this.$options)
+
+ // register NS components into Vue
+ Object.values(getElementMap()).forEach(entry => {
+ Vue.component(entry.meta.component.name, entry.meta.component)
+ })
+
+ Application.run({
+ create() {
+ if (self.$el) {
+ self.$destroy()
+ self = new AppConstructor()
+ }
+
+ self.$mount()
+ return self.$el.nativeView
+ }
+ })
+}
+
+// Define a `nativeView` getter in every NS vue instance
+Object.defineProperty(Vue.prototype, 'nativeView', {
+ get() {
+ return this.$el ? this.$el.nativeView : undefined
+ }
+})
+
+export default Vue
diff --git a/platform/nativescript/runtime/modules/attrs.js b/platform/nativescript/runtime/modules/attrs.js
new file mode 100644
index 00000000..5cf6f6ff
--- /dev/null
+++ b/platform/nativescript/runtime/modules/attrs.js
@@ -0,0 +1,33 @@
+import { extend } from 'shared/util'
+
+function updateAttrs(oldVnode, vnode) {
+ if (!oldVnode.data.attrs && !vnode.data.attrs) {
+ return
+ }
+ let key, cur, old
+ const elm = vnode.elm
+ const oldAttrs = oldVnode.data.attrs || {}
+ let attrs = vnode.data.attrs || {}
+ // clone observed objects, as the user probably wants to mutate it
+ if (attrs.__ob__) {
+ attrs = vnode.data.attrs = extend({}, attrs)
+ }
+
+ for (key in attrs) {
+ cur = attrs[key]
+ old = oldAttrs[key]
+ if (old !== cur) {
+ elm.setAttribute(key, cur)
+ }
+ }
+ for (key in oldAttrs) {
+ if (attrs[key] == null) {
+ elm.setAttribute(key)
+ }
+ }
+}
+
+export default {
+ create: updateAttrs,
+ update: updateAttrs
+}
diff --git a/platform/nativescript/runtime/modules/class.js b/platform/nativescript/runtime/modules/class.js
new file mode 100644
index 00000000..e3484b55
--- /dev/null
+++ b/platform/nativescript/runtime/modules/class.js
@@ -0,0 +1,33 @@
+import { genClassForVnode, concat, stringifyClass } from 'web/util/index'
+
+function updateClass(oldVnode, vnode) {
+ const el = vnode.elm
+ const data = vnode.data
+ const oldData = oldVnode.data
+ if (
+ !data.staticClass &&
+ !data.class &&
+ (!oldData || (!oldData.staticClass && !oldData.class))
+ ) {
+ return
+ }
+
+ let cls = genClassForVnode(vnode)
+
+ // handle transition classes
+ const transitionClass = el._transitionClasses
+ if (transitionClass) {
+ cls = concat(cls, stringifyClass(transitionClass))
+ }
+
+ // set the class
+ if (cls !== el._prevClass) {
+ el.setAttribute('class', cls)
+ el._prevClass = cls
+ }
+}
+
+export default {
+ create: updateClass,
+ update: updateClass
+}
diff --git a/platform/nativescript/runtime/modules/events.js b/platform/nativescript/runtime/modules/events.js
new file mode 100644
index 00000000..977da69d
--- /dev/null
+++ b/platform/nativescript/runtime/modules/events.js
@@ -0,0 +1,50 @@
+import { updateListeners } from 'core/vdom/helpers/update-listeners'
+
+let target
+
+function createOnceHandler(event, handler, capture) {
+ const _target = target // save current target element in closure
+ return function onceHandler() {
+ const res = handler.apply(null, arguments)
+ if (res !== null) {
+ remove(event, onceHandler, capture, _target)
+ }
+ }
+}
+
+function add(event, handler, once, capture) {
+ if (capture) {
+ console.log('NativeScript-Vue do not support event in bubble phase.')
+ return
+ }
+ if (once) {
+ const oldHandler = handler
+ handler = (...args) => {
+ const res = oldHandler.call(null, ...args)
+ if (res !== null) {
+ remove(event, null, null, target)
+ }
+ }
+ }
+ target.addEventListener(event, handler)
+}
+
+function remove(event, handler, capture, _target = target) {
+ _target.removeEventListener(event)
+}
+
+function updateDOMListeners(oldVnode, vnode) {
+ if (!oldVnode.data.on && !vnode.data.on) {
+ return
+ }
+ const on = vnode.data.on || {}
+ const oldOn = oldVnode.data.on || {}
+ target = vnode.elm
+ updateListeners(on, oldOn, add, remove, createOnceHandler, vnode.context)
+ target = undefined
+}
+
+export default {
+ create: updateDOMListeners,
+ update: updateDOMListeners
+}
diff --git a/platform/nativescript/runtime/modules/index.js b/platform/nativescript/runtime/modules/index.js
new file mode 100644
index 00000000..9cc56bf9
--- /dev/null
+++ b/platform/nativescript/runtime/modules/index.js
@@ -0,0 +1,7 @@
+import attrs from './attrs'
+import class_ from './class'
+import events from './events'
+import style from './style'
+import transition from './transition'
+
+export default [class_, events, attrs, style, transition]
diff --git a/platform/nativescript/runtime/modules/style.js b/platform/nativescript/runtime/modules/style.js
new file mode 100644
index 00000000..f7704ece
--- /dev/null
+++ b/platform/nativescript/runtime/modules/style.js
@@ -0,0 +1,67 @@
+import { extend, cached, camelize } from 'shared/util'
+
+const normalize = cached(camelize)
+
+function createStyle(oldVnode, vnode) {
+ // console.log(`\t\t ===> createStyle(${oldVnode}, ${vnode})`)
+ if (!vnode.data.staticStyle) {
+ updateStyle(oldVnode, vnode)
+ return
+ }
+ const elm = vnode.elm
+ const staticStyle = vnode.data.staticStyle
+ for (const name in staticStyle) {
+ if (staticStyle[name]) {
+ elm.setStyle(normalize(name), staticStyle[name])
+ }
+ }
+ updateStyle(oldVnode, vnode)
+}
+
+function updateStyle(oldVnode, vnode) {
+ if (!oldVnode.data.style && !vnode.data.style) {
+ return
+ }
+ let cur, name
+ const elm = vnode.elm
+ const oldStyle = oldVnode.data.style || {}
+ let style = vnode.data.style || {}
+
+ const needClone = style.__ob__
+
+ // handle array syntax
+ if (Array.isArray(style)) {
+ style = vnode.data.style = toObject(style)
+ }
+
+ // clone the style for future updates,
+ // in case the user mutates the style object in-place.
+ if (needClone) {
+ style = vnode.data.style = extend({}, style)
+ }
+
+ for (name in oldStyle) {
+ if (!style[name]) {
+ elm.setStyle(normalize(name), '')
+ }
+ }
+ for (name in style) {
+ cur = style[name]
+ elm.setStyle(normalize(name), cur)
+ }
+}
+
+function toObject(arr) {
+ const res = {}
+ for (let i = 0; i < arr.length; i++) {
+ if (arr[i]) {
+ extend(res, arr[i])
+ }
+ }
+ return res
+}
+
+export default {
+ create: createStyle,
+ update: updateStyle
+}
diff --git a/platform/nativescript/runtime/modules/transition.js b/platform/nativescript/runtime/modules/transition.js
new file mode 100644
index 00000000..b101dd38
--- /dev/null
+++ b/platform/nativescript/runtime/modules/transition.js
@@ -0,0 +1,327 @@
+import { warn } from 'core/util/index'
+import { mergeVNodeHook } from 'core/vdom/helpers/index'
+import { activeInstance } from 'core/instance/lifecycle'
+
+import { once, isDef, isUndef, isObject, toNumber } from 'shared/util'
+
+import {
+ nextFrame,
+ resolveTransition,
+ whenTransitionEnds,
+ addTransitionClass,
+ removeTransitionClass
+} from 'web/runtime/transition-util'
+
+export function enter(vnode, toggleDisplay) {
+ const el = vnode.elm
+
+ // call leave callback now
+ if (isDef(el._leaveCb)) {
+ el._leaveCb.cancelled = true
+ el._leaveCb()
+ }
+
+ const data = resolveTransition(vnode.data.transition)
+
+ if (isUndef(data)) {
+ return
+ }
+
+ /* istanbul ignore if */
+ if (isDef(el._enterCb) || el.nodeType !== 1) {
+ return
+ }
+
+ const {
+ css,
+ type,
+ enterClass,
+ enterToClass,
+ enterActiveClass,
+ appearClass,
+ appearToClass,
+ appearActiveClass,
+ beforeEnter,
+ enter,
+ afterEnter,
+ enterCancelled,
+ beforeAppear,
+ appear,
+ afterAppear,
+ appearCancelled,
+ duration
+ } = data
+
+ // activeInstance will always be the component managing this
+ // transition. One edge case to check is when the is placed
+ // as the root node of a child component. In that case we need to check
+ // 's parent for appear check.
+ let context = activeInstance
+ let transitionNode = activeInstance.$vnode
+ while (transitionNode && transitionNode.parent) {
+ transitionNode = transitionNode.parent
+ context = transitionNode.context
+ }
+
+ const isAppear = !context._isMounted || !vnode.isRootInsert
+
+ if (isAppear && !appear && appear !== '') {
+ return
+ }
+
+ const startClass = isAppear && appearClass ? appearClass : enterClass
+ const activeClass =
+ isAppear && appearActiveClass ? appearActiveClass : enterActiveClass
+ const toClass = isAppear && appearToClass ? appearToClass : enterToClass
+
+ const beforeEnterHook = isAppear ? beforeAppear || beforeEnter : beforeEnter
+ const enterHook = isAppear
+ ? typeof appear === 'function'
+ ? appear
+ : enter
+ : enter
+ const afterEnterHook = isAppear ? afterAppear || afterEnter : afterEnter
+ const enterCancelledHook = isAppear
+ ? appearCancelled || enterCancelled
+ : enterCancelled
+
+ const explicitEnterDuration = toNumber(
+ isObject(duration) ? duration.enter : duration
+ )
+
+ if (process.env.NODE_ENV !== 'production' && explicitEnterDuration != null) {
+ checkDuration(explicitEnterDuration, 'enter', vnode)
+ }
+
+ const expectsCSS = css !== false
+ const userWantsControl = getHookArgumentsLength(enterHook)
+
+ const cb = (el._enterCb = once(() => {
+ if (expectsCSS) {
+ removeTransitionClass(el, toClass)
+ removeTransitionClass(el, activeClass)
+ }
+ if (cb.cancelled) {
+ if (expectsCSS) {
+ removeTransitionClass(el, startClass)
+ }
+ enterCancelledHook && enterCancelledHook(el)
+ } else {
+ afterEnterHook && afterEnterHook(el)
+ }
+ el._enterCb = null
+ }))
+
+ if (!vnode.data.show) {
+ // remove pending leave element on enter by injecting an insert hook
+ mergeVNodeHook(vnode, 'insert', () => {
+ const parent = el.parentNode
+ const pendingNode =
+ parent && parent._pending && parent._pending[vnode.key]
+ if (
+ pendingNode &&
+ pendingNode.tag === vnode.tag &&
+ pendingNode.elm._leaveCb
+ ) {
+ pendingNode.elm._leaveCb()
+ }
+ enterHook && enterHook(el, cb)
+ })
+ }
+
+ // start enter transition
+ beforeEnterHook && beforeEnterHook(el)
+ if (expectsCSS) {
+ addTransitionClass(el, startClass)
+ addTransitionClass(el, activeClass)
+ nextFrame(() => {
+ removeTransitionClass(el, startClass)
+ if (!cb.cancelled) {
+ addTransitionClass(el, toClass)
+ if (!userWantsControl) {
+ if (isValidDuration(explicitEnterDuration)) {
+ setTimeout(cb, explicitEnterDuration)
+ } else {
+ //whenTransitionEnds(el, type, cb)
+ }
+ }
+ }
+ })
+ }
+
+ if (vnode.data.show) {
+ toggleDisplay && toggleDisplay()
+ enterHook && enterHook(el, cb)
+ }
+
+ if (!expectsCSS && !userWantsControl) {
+ cb()
+ }
+}
+
+export function leave(vnode, rm) {
+ const el = vnode.elm
+
+ // call enter callback now
+ if (isDef(el._enterCb)) {
+ el._enterCb.cancelled = true
+ el._enterCb()
+ }
+
+ const data = resolveTransition(vnode.data.transition)
+ if (isUndef(data) || el.nodeType !== 1) {
+ return rm()
+ }
+
+ /* istanbul ignore if */
+ if (isDef(el._leaveCb)) {
+ return
+ }
+
+ const {
+ css,
+ type,
+ leaveClass,
+ leaveToClass,
+ leaveActiveClass,
+ beforeLeave,
+ leave,
+ afterLeave,
+ leaveCancelled,
+ delayLeave,
+ duration
+ } = data
+
+ const expectsCSS = css !== false
+ const userWantsControl = getHookArgumentsLength(leave)
+
+ const explicitLeaveDuration = toNumber(
+ isObject(duration) ? duration.leave : duration
+ )
+
+ if (process.env.NODE_ENV !== 'production' && isDef(explicitLeaveDuration)) {
+ checkDuration(explicitLeaveDuration, 'leave', vnode)
+ }
+
+ const cb = (el._leaveCb = once(() => {
+ if (el.parentNode && el.parentNode._pending) {
+ el.parentNode._pending[vnode.key] = null
+ }
+ if (expectsCSS) {
+ removeTransitionClass(el, leaveToClass)
+ removeTransitionClass(el, leaveActiveClass)
+ }
+ if (cb.cancelled) {
+ if (expectsCSS) {
+ removeTransitionClass(el, leaveClass)
+ }
+ leaveCancelled && leaveCancelled(el)
+ } else {
+ rm()
+ afterLeave && afterLeave(el)
+ }
+ el._leaveCb = null
+ }))
+
+ if (delayLeave) {
+ delayLeave(performLeave)
+ } else {
+ performLeave()
+ }
+
+ function performLeave() {
+ // the delayed leave may have already been cancelled
+ if (cb.cancelled) {
+ return
+ }
+ // record leaving element
+ if (!vnode.data.show) {
+ ;(el.parentNode._pending || (el.parentNode._pending = {}))[
+ vnode.key
+ ] = vnode
+ }
+ beforeLeave && beforeLeave(el)
+ if (expectsCSS) {
+ addTransitionClass(el, leaveClass)
+ addTransitionClass(el, leaveActiveClass)
+ nextFrame(() => {
+ removeTransitionClass(el, leaveClass)
+ if (!cb.cancelled) {
+ addTransitionClass(el, leaveToClass)
+ if (!userWantsControl) {
+ if (isValidDuration(explicitLeaveDuration)) {
+ setTimeout(cb, explicitLeaveDuration)
+ } else {
+ //whenTransitionEnds(el, type, cb)
+ }
+ }
+ }
+ })
+ }
+ leave && leave(el, cb)
+ if (!expectsCSS && !userWantsControl) {
+ cb()
+ }
+ }
+}
+
+// only used in dev mode
+function checkDuration(val, name, vnode) {
+ if (typeof val !== 'number') {
+ warn(
+ ` explicit ${name} duration is not a valid number - ` +
+ `got ${JSON.stringify(val)}.`,
+ vnode.context
+ )
+ } else if (isNaN(val)) {
+ warn(
+ ` explicit ${name} duration is NaN - ` +
+ 'the duration expression might be incorrect.',
+ vnode.context
+ )
+ }
+}
+
+function isValidDuration(val) {
+ return typeof val === 'number' && !isNaN(val)
+}
+
+/**
+ * Normalize a transition hook's argument length. The hook may be:
+ * - a merged hook (invoker) with the original in .fns
+ * - a wrapped component method (check ._length)
+ * - a plain function (.length)
+ */
+function getHookArgumentsLength(fn) {
+ if (isUndef(fn)) {
+ return false
+ }
+ const invokerFns = fn.fns
+ if (isDef(invokerFns)) {
+ // invoker
+ return getHookArgumentsLength(
+ Array.isArray(invokerFns) ? invokerFns[0] : invokerFns
+ )
+ } else {
+ return (fn._length || fn.length) > 1
+ }
+}
+
+function _enter(_, vnode) {
+ if (vnode.data.show !== true) {
+ enter(vnode)
+ }
+}
+
+export default {
+ create: _enter,
+ activate: _enter,
+ remove(vnode, rm) {
+ /* istanbul ignore else */
+ if (vnode.data.show !== true) {
+ leave(vnode, rm)
+ } else {
+ rm()
+ }
+ }
+}
diff --git a/platform/nativescript/runtime/node-ops.js b/platform/nativescript/runtime/node-ops.js
new file mode 100644
index 00000000..43b921b2
--- /dev/null
+++ b/platform/nativescript/runtime/node-ops.js
@@ -0,0 +1,75 @@
+import { default as document } from '../renderer/DocumentNode'
+import { trace } from '../util'
+
+export const namespaceMap = {}
+
+export function createElement(tagName, vnode) {
+ trace(`CreateElement(${tagName.replace(/^native/i, '')})`)
+ return document.createElement(tagName)
+}
+
+export function createElementNS(namespace, tagName) {
+ trace(`CreateElementNS(${namespace}#${tagName})`)
+ return document.createElementNS(namespace, tagName)
+}
+
+export function createTextNode(text) {
+ trace(`CreateTextNode(${text})`)
+ return document.createTextNode(text)
+}
+
+export function createComment(text) {
+ trace(`CreateComment(${text})`)
+
+ return document.createComment(text)
+}
+
+export function insertBefore(parentNode, newNode, referenceNode) {
+ trace(`InsertBefore(${parentNode}, ${newNode}, ${referenceNode})`)
+ return parentNode.insertBefore(newNode, referenceNode)
+}
+
+export function removeChild(node, child) {
+ trace(`RemoveChild(${node}, ${child})`)
+ return node.removeChild(child)
+}
+
+export function appendChild(node, child) {
+ trace(`AppendChild(${node}, ${child})`)
+
+ return node.appendChild(child)
+}
+
+export function parentNode(node) {
+ trace(`ParentNode(${node}) -> ${node.parentNode}`)
+
+ return node.parentNode
+}
+
+export function nextSibling(node) {
+ trace(`NextSibling(${node}) -> ${node.nextSibling}`)
+
+ return node.nextSibling
+}
+
+export function tagName(elementNode) {
+ trace(`TagName(${elementNode}) -> ${elementNode.tagName}`)
+
+ return elementNode.tagName
+}
+
+export function setTextContent(node, text) {
+ trace(`SetTextContent(${node}, ${text})`)
+
+ node.setText(text)
+}
+
+export function setAttribute(node, key, val) {
+ trace(`SetAttribute(${node}, ${key}, ${val})`)
+
+ node.setAttribute(key, val)
+}
+
+export function setStyleScope(node, scopeId) {
+ node.setAttribute(scopeId, '')
+}
diff --git a/platform/nativescript/runtime/patch.js b/platform/nativescript/runtime/patch.js
new file mode 100644
index 00000000..1a5022c1
--- /dev/null
+++ b/platform/nativescript/runtime/patch.js
@@ -0,0 +1,12 @@
+import { createPatchFunction } from 'core/vdom/patch'
+import baseModules from 'core/vdom/modules/index'
+
+import platformModules from './modules/index'
+import * as nodeOps from './node-ops'
+
+const modules = platformModules.concat(baseModules)
+
+export const patch = createPatchFunction({
+ nodeOps,
+ modules
+})
diff --git a/platform/nativescript/util/entity-decoder.js b/platform/nativescript/util/entity-decoder.js
new file mode 100644
index 00000000..81fc9043
--- /dev/null
+++ b/platform/nativescript/util/entity-decoder.js
@@ -0,0 +1,8 @@
+export default {
+ decode
+}
+
+export function decode(html) {
+ // todo?
+ return html
+}
diff --git a/platform/nativescript/util/frame.js b/platform/nativescript/util/frame.js
new file mode 100644
index 00000000..a7dc7291
--- /dev/null
+++ b/platform/nativescript/util/frame.js
@@ -0,0 +1,27 @@
+import { VUE_ELEMENT_REF } from '../renderer/ElementNode'
+
+const frames = new Map()
+
+export function setFrame(id, frame) {
+ return frames.set(id, frame)
+}
+
+export function getFrame(id, fallback) {
+ if (frames.has(id)) {
+ return frames.get(id)
+ }
+
+ // handle a fallback case where the frame with a same id might have been unmounted, but another one with the same id exists as a fallback...
+ if (fallback) {
+ const frameVM = fallback[VUE_ELEMENT_REF]['__vue__']
+ setFrame(id, frameVM)
+
+ return frameVM
+ }
+
+ return null
+}
+
+export function deleteFrame(id) {
+ return frames.delete(id)
+}
diff --git a/platform/nativescript/util/index.js b/platform/nativescript/util/index.js
new file mode 100644
index 00000000..76b3d77f
--- /dev/null
+++ b/platform/nativescript/util/index.js
@@ -0,0 +1,106 @@
+import { isKnownView, getViewMeta, getViewClass } from '../element-registry'
+import { makeMap, once } from 'shared/util'
+import { VUE_VM_REF } from '../constants'
+
+export const isReservedTag = makeMap('template', true)
+
+let _Vue
+
+export function setVue(Vue) {
+ _Vue = Vue
+}
+
+export const canBeLeftOpenTag = function (el) {
+ return getViewMeta(el).canBeLeftOpenTag
+}
+
+export const isUnaryTag = function (el) {
+ return getViewMeta(el).isUnaryTag
+}
+
+export function mustUseProp() {
+ // console.log('mustUseProp')
+}
+
+export function getTagNamespace(el) {
+ return getViewMeta(el).tagNamespace
+}
+
+export function isUnknownElement(el) {
+ return !isKnownView(el)
+}
+
+export function isPage(el) {
+ return el && el.tagName === 'nativepage'
+}
+
+/** @deprecated */
+export function ensurePage(el, vm) {
+ if (!isPage(el)) {
+ const page = new (getViewClass('page'))()
+ page.content = el.nativeView
+ if (vm) {
+ page[VUE_VM_REF] = vm
+ page.disposeNativeView = after(page.disposeNativeView, page, () =>
+ vm.$destroy()
+ )
+ }
+ return page
+ }
+
+ if (vm) {
+ el.nativeView[VUE_VM_REF] = vm
+ el.disposeNativeView = after(el.disposeNativeView, el, () => vm.$destroy())
+ }
+
+ return el.nativeView
+}
+
+export function query(el, renderer, document) {
+ // Todo
+}
+
+export const VUE_VERSION = process.env._VUE_VERSION
+export const NS_VUE_VERSION = process.env._NS_VUE_VERSION
+
+const infoTrace = once(() => {
+ console.log(
+ `NativeScript-Vue has "Vue.config.silent" set to true, to see output logs set it to false.`
+ )
+})
+
+export function trace(message) {
+ if (_Vue && _Vue.config.silent) {
+ return infoTrace()
+ }
+
+ if (_Vue && !_Vue.config.suppressRenderLogs) {
+ console.log(
+ `{NSVue (Vue: ${VUE_VERSION} | NSVue: ${NS_VUE_VERSION})} -> ${message}`
+ )
+ }
+}
+
+export function before(original, thisArg, wrap) {
+ return function (...args) {
+ wrap.call(null, ...args)
+ original.call(thisArg, ...args)
+ }
+}
+
+export function after(original, thisArg, wrap) {
+ return function (...args) {
+ original.call(thisArg, ...args)
+ wrap.call(null, ...args)
+ }
+}
+
+export function updateDevtools() {
+ if (global.__VUE_DEVTOOLS_GLOBAL_HOOK__) {
+ try {
+ global.__VUE_DEVTOOLS_GLOBAL_HOOK__.emit('flush')
+ } catch (err) {
+ //
+ }
+ }
+}
diff --git a/samples/app/127.js b/samples/app/127.js
new file mode 100644
index 00000000..a0a4248f
--- /dev/null
+++ b/samples/app/127.js
@@ -0,0 +1,25 @@
+const Vue = require('nativescript-vue')
+
+Vue.config.debug = true
+Vue.config.silent = false
+
+new Vue({
+ data: {
+ foo: false
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+
+ `,
+ created() {
+ console.log(Vue.compile(this.$options.template).render.toString())
+ }
+}).$start()
diff --git a/samples/app/171.js b/samples/app/171.js
new file mode 100644
index 00000000..bdb77b69
--- /dev/null
+++ b/samples/app/171.js
@@ -0,0 +1,25 @@
+const Vue = require('nativescript-vue')
+
+Vue.config.silent = false
+
+new Vue({
+ data: {
+ password: 'mypass'
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+}).$start()
diff --git a/samples/app/217.js b/samples/app/217.js
new file mode 100644
index 00000000..b5c79cbe
--- /dev/null
+++ b/samples/app/217.js
@@ -0,0 +1,44 @@
+const Vue = require('nativescript-vue')
+
+Vue.config.debug = true
+Vue.config.silent = false
+
+const CustomComponent = {
+ // defining props breaks this on iOS
+ // props: ['text'],
+ template: ` `
+}
+
+const PageContent = {
+ data() {
+ return {
+ normal: false,
+ custom: false
+ }
+ },
+ template: `
+
+
+
+
+
+
+ `,
+ components: {
+ CustomComponent
+ }
+}
+
+new Vue({
+ data: {
+ content: PageContent
+ },
+ template: `
+
+
+
+
+
+
+ `
+}).$start()
diff --git a/samples/app/220.js b/samples/app/220.js
new file mode 100644
index 00000000..ada556c4
--- /dev/null
+++ b/samples/app/220.js
@@ -0,0 +1,42 @@
+const Vue = require('nativescript-vue')
+
+Vue.config.debug = true
+Vue.config.silent = false
+
+const CompButton = {
+ template: `
+
+ `,
+ name: 'CompButton',
+ props: ['label', 'counter'],
+ destroyed() {
+ console.log('Component destroyed. This should not happen')
+ }
+}
+
+new Vue({
+ data: {
+ counter: 0
+ },
+ template: `
+
+
+
+
+
+
+
+ `,
+ created() {
+ console.log(Vue.compile(this.$options.template).render.toString())
+ },
+ methods: {
+ inc() {
+ console.log('\n\n=========INC=========\n\n')
+ this.counter++
+ }
+ },
+ components: {
+ CompButton
+ }
+}).$start()
diff --git a/samples/app/229.js b/samples/app/229.js
new file mode 100644
index 00000000..cfad5b7e
--- /dev/null
+++ b/samples/app/229.js
@@ -0,0 +1,24 @@
+const Vue = require('nativescript-vue')
+
+Vue.config.silent = false
+
+new Vue({
+ data: {
+ items: [{ color: 'red' }, { color: 'blue' }]
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+}).$start()
diff --git a/samples/app/231.js b/samples/app/231.js
new file mode 100644
index 00000000..8ed462fe
--- /dev/null
+++ b/samples/app/231.js
@@ -0,0 +1,56 @@
+const Vue = require('nativescript-vue')
+
+Vue.config.silent = false
+
+const ImageCard = {
+ props: {
+ src: {
+ type: String,
+ required: true
+ }
+ },
+ template: ' '
+}
+const url = 'https://api.giphy.com/v1/gifs/search'
+const key = 'ZboEpjHv00FzK6SI7l33H7wutWlMldQs'
+const filter = 'limit=25&offset=0&rating=G&lang=fr'
+
+new Vue({
+ components: {
+ ImageCard
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+
+ data() {
+ return {
+ imgs: [],
+ surprise: false,
+ q: ''
+ }
+ },
+
+ methods: {
+ search() {
+ this.$refs.search.nativeView.dismissSoftInput()
+ fetch(`${url}?api_key=${key}&q=${this.q}&${filter}`)
+ .then(response => response.json())
+ .then(json => (this.imgs = json.data))
+ }
+ }
+}).$start()
diff --git a/samples/app/240.js b/samples/app/240.js
new file mode 100644
index 00000000..59ec723e
--- /dev/null
+++ b/samples/app/240.js
@@ -0,0 +1,163 @@
+const Vue = require('nativescript-vue')
+
+Vue.config.debug = true
+Vue.config.silent = false
+
+new Vue({
+ computed: {
+ filteredFoo() {
+ if (this.submittedPhrase == '') {
+ return this.fooList
+ }
+
+ let foo = this.fooList.filter(item => {
+ return (
+ item.title
+ .toLowerCase()
+ .indexOf(this.submittedPhrase.toLowerCase()) !== -1
+ )
+ })
+
+ return foo
+ },
+ filteredBar() {
+ if (this.submittedPhrase == '') {
+ return this.barList
+ }
+
+ let bar = this.barList.filter(item => {
+ return (
+ item.title
+ .toLowerCase()
+ .indexOf(this.submittedPhrase.toLowerCase()) !== -1
+ )
+ })
+
+ return bar
+ },
+ filteredFooBar() {
+ if (this.submittedPhrase == '') {
+ return this.fooBarList
+ }
+
+ let fooBar = this.fooBarList.filter(item => {
+ return (
+ item.title
+ .toLowerCase()
+ .indexOf(this.submittedPhrase.toLowerCase()) !== -1
+ )
+ })
+
+ return fooBar
+ }
+ },
+ methods: {
+ onSearchSubmit(args) {
+ let searchBar = args.object
+ console.log('You are searching for [' + searchBar.text + ']')
+
+ this.submittedPhrase = searchBar.text
+ }
+ },
+ data() {
+ return {
+ searchPhrase: '',
+ submittedPhrase: '',
+ fooList: [
+ {
+ id: 1,
+ title: 'Foo 1'
+ },
+ {
+ id: 2,
+ title: 'Foo 2'
+ },
+ {
+ id: 3,
+ title: 'Foo 3'
+ }
+ ],
+ barList: [
+ {
+ id: 11,
+ title: 'Bar 1'
+ },
+ {
+ id: 12,
+ title: 'Bar 2'
+ },
+ {
+ id: 13,
+ title: 'Bar 3'
+ }
+ ],
+ fooBarList: [
+ {
+ id: 21,
+ title: 'Foo Bar 1'
+ },
+ {
+ id: 22,
+ title: 'Foo Bar 2'
+ },
+ {
+ id: 23,
+ title: 'Foo Bar 3'
+ }
+ ]
+ }
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+}).$start()
diff --git a/samples/app/272.js b/samples/app/272.js
new file mode 100644
index 00000000..12b45161
--- /dev/null
+++ b/samples/app/272.js
@@ -0,0 +1,42 @@
+const Vue = require('nativescript-vue')
+
+Vue.config.debug = true
+Vue.config.silent = false
+
+class User {
+ constructor(name) {
+ this.name = name.toUpperCase()
+ }
+}
+
+new Vue({
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ data() {
+ return {
+ userList: [new User('John'), new User('Paul')]
+ }
+ },
+ methods: {
+ onTap({ object }) {
+ console.log(`Tapped on ${object.text}`)
+ }
+ }
+}).$start()
diff --git a/samples/app/339.js b/samples/app/339.js
new file mode 100644
index 00000000..76500ae5
--- /dev/null
+++ b/samples/app/339.js
@@ -0,0 +1,92 @@
+const Vue = require('nativescript-vue')
+
+Vue.config.debug = true
+Vue.config.silent = false
+
+let step1_id = 0
+const Step1 = {
+ template: `
+
+
+
+
+
+
+
+ `,
+ created() {
+ console.log('Step 1 created.')
+ this.stepid = ++step1_id
+ this.interval = setInterval(() => {
+ console.log(`[${this.stepid}] step 1 is alive`)
+ }, 1000)
+ },
+ beforeDestroy() {
+ console.log('Destroying step 1...')
+ },
+ destroyed() {
+ console.log('Step 1 destroyed.')
+ clearInterval(this.interval)
+ },
+ methods: {
+ next() {
+ this.$navigateTo(Step2)
+ }
+ }
+}
+
+const Step2 = {
+ template: `
+
+
+
+
+
+
+
+ `,
+ created() {
+ console.log('Step 2 created.')
+ },
+ beforeDestroy() {
+ console.log('Destroying step 2...')
+ },
+ destroyed() {
+ console.log('Step 2 destroyed.')
+ },
+ methods: {
+ next() {
+ this.$navigateTo(Step3, { transition: 'slideTop' })
+ }
+ }
+}
+
+const Step3 = {
+ template: `
+
+
+
+
+
+
+
+ `,
+ created() {
+ console.log('Step 3 created.')
+ },
+ beforeDestroy() {
+ console.log('Destroying step 3...')
+ },
+ destroyed() {
+ console.log('Step 3 destroyed.')
+ },
+ methods: {
+ reset() {
+ this.$navigateTo(Step1, { clearHistory: true })
+ }
+ }
+}
+
+new Vue({
+ render: h => h('frame', [h(Step1)])
+}).$start()
diff --git a/samples/app/344.js b/samples/app/344.js
new file mode 100644
index 00000000..202abcbf
--- /dev/null
+++ b/samples/app/344.js
@@ -0,0 +1,140 @@
+import Vue from 'nativescript-vue'
+Vue.config.debug = true
+Vue.config.silent = false
+
+const CustomButton = {
+ template: ` `,
+ props: ['text']
+}
+
+let step1_id = 0
+const Step1 = {
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ components: { CustomButton },
+
+ created() {
+ console.log('Step 1 created.')
+ },
+
+ beforeDestroy() {
+ console.log('Destroying step 1...')
+ },
+
+ destroyed() {
+ console.log('Step 1 destroyed.')
+ },
+
+ methods: {
+ next() {
+ this.$navigateTo(Step2, { frame: 'second' })
+ }
+ }
+}
+
+const Step2 = {
+ template: `
+
+
+
+
+
+
+
+ `,
+ created() {
+ console.log('Step 2 created.')
+ },
+ beforeDestroy() {
+ console.log('Destroying step 2...')
+ },
+ destroyed() {
+ console.log('Step 2 destroyed.')
+ },
+ methods: {
+ next() {
+ this.$navigateTo(Step3, { frame: 'second' })
+ }
+ }
+}
+
+const Step3 = {
+ template: `
+
+
+
+
+
+
+
+ `,
+ created() {
+ console.log('Step 3 created.')
+ },
+ beforeDestroy() {
+ console.log('Destroying step 3...')
+ },
+ destroyed() {
+ console.log('Step 3 destroyed.')
+ },
+ methods: {
+ reset() {
+ this.$navigateTo(Step1, { frame: 'second', clearHistory: true })
+ }
+ }
+}
+
+const FirstTab = {
+ template: `
+
+
+
+
+
+
+ `
+}
+
+const TabBar = {
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ components: { FirstTab, Step1 },
+
+ data() {
+ return {
+ screen: 0
+ }
+ }
+}
+
+new Vue({
+ render: h => h(TabBar)
+}).$start()
diff --git a/samples/app/445.js b/samples/app/445.js
new file mode 100644
index 00000000..5605ccc3
--- /dev/null
+++ b/samples/app/445.js
@@ -0,0 +1,36 @@
+const Vue = require('nativescript-vue')
+
+Vue.config.debug = true
+Vue.config.silent = false
+
+new Vue({
+ components: {
+ ComponentWithSlot: {
+ data() {
+ return {
+ counter: 0
+ }
+ },
+ template: `
+
+
+
+
+
+ `
+ }
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+ `
+}).$start()
diff --git a/samples/app/464.js b/samples/app/464.js
new file mode 100644
index 00000000..3a39ccb5
--- /dev/null
+++ b/samples/app/464.js
@@ -0,0 +1,29 @@
+const Vue = require('nativescript-vue')
+const { ObservableArray } = require('@nativescript/core')
+
+Vue.config.debug = true
+Vue.config.silent = false
+
+new Vue({
+ data: {
+ items: new ObservableArray([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+ },
+ methods: {
+ onItemTap({ item }) {
+ this.items.push(`Item clicked: ${item} (shouldn't be \`undefined\`)`)
+ }
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+ `
+}).$start()
diff --git a/samples/app/522.js b/samples/app/522.js
new file mode 100644
index 00000000..a81a9b6b
--- /dev/null
+++ b/samples/app/522.js
@@ -0,0 +1,31 @@
+import Vue from 'nativescript-vue'
+import { ObservableArray } from '@nativescript/core'
+
+Vue.config.debug = true
+Vue.config.silent = false
+
+new Vue({
+ data: {
+ itemsArray: [1, 2, 3],
+ itemsObservableArray: new ObservableArray([1, 2, 3])
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+}).$start()
diff --git a/samples/app/536.js b/samples/app/536.js
new file mode 100644
index 00000000..6ad318da
--- /dev/null
+++ b/samples/app/536.js
@@ -0,0 +1,85 @@
+import Vue from 'nativescript-vue'
+
+Vue.config.debug = true
+Vue.config.silent = false
+
+const Step2 = {
+ template: `
+
+
+
+
+
+
+
+
+
+ `,
+ methods: {
+ prevStep() {
+ this.$navigateBack({
+ frame: 'wizard'
+ })
+ }
+ }
+}
+
+const WizardModal = {
+ template: `
+
+
+
+
+
+
+
+
+
+ `,
+ methods: {
+ nextStep() {
+ this.$navigateTo(Step2, {
+ frame: 'wizard'
+ })
+ }
+ }
+}
+
+const TabContent = {
+ template: `
+
+
+
+
+ `,
+ methods: {
+ openWizard() {
+ // show the wizard in a modal, and make sure it is fullscreen.
+ this.$showModal(WizardModal, {
+ fullscreen: true
+ }).then(res => {
+ console.log('wizard completed with res', res)
+ })
+ }
+ }
+}
+
+new Vue({
+ components: {
+ TabContent
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+}).$start()
diff --git a/samples/app/555.js b/samples/app/555.js
new file mode 100644
index 00000000..0dd490f0
--- /dev/null
+++ b/samples/app/555.js
@@ -0,0 +1,35 @@
+const Vue = require('nativescript-vue')
+
+Vue.config.debug = true
+Vue.config.silent = false
+
+new Vue({
+ data: {
+ foo: false
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+ `,
+ mounted() {
+ // this dialog is not shown because when the Vue mounted event
+ // is fired NS has not loaded all the UI components yet
+ // so for being able to use it change the tag to:
+ // instead of using the mounted event
+ this.greet()
+ },
+ methods: {
+ greet() {
+ alert('Hello!').then(() => {
+ console.log('Alert dialog closed.')
+ })
+ }
+ }
+}).$start()
diff --git a/samples/app/569.js b/samples/app/569.js
new file mode 100644
index 00000000..826f9e89
--- /dev/null
+++ b/samples/app/569.js
@@ -0,0 +1,46 @@
+const Vue = require('nativescript-vue')
+
+Vue.config.debug = true
+Vue.config.silent = false
+
+// console.log(Vue.compile(' ').render.toString())
+
+new Vue({
+ data() {
+ return {
+ items: [
+ { switch: false, value: 'Item 1' },
+ { switch: false, value: 'Item 2' },
+ { switch: false, value: 'Item 3' },
+ { switch: false, value: 'Item 4' },
+ { switch: false, value: 'Item 5' },
+ { switch: false, value: 'Item 6' },
+ { switch: false, value: 'Item 7' },
+ { switch: false, value: 'Item 8' },
+ { switch: false, value: 'Item 9' },
+ { switch: false, value: 'Item 10' },
+ { switch: false, value: 'Item 11' },
+ { switch: false, value: 'Item 12' },
+ { switch: false, value: 'Item 13' },
+ { switch: false, value: 'Item 14' },
+ { switch: false, value: 'Item 15' },
+ { switch: false, value: 'Item 16' },
+ { switch: false, value: 'Item 17' },
+ { switch: false, value: 'Item 18' },
+ { switch: false, value: 'Item 19' },
+ { switch: false, value: 'Item 20' }
+ ]
+ }
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+ `
+}).$start()
diff --git a/samples/app/595.js b/samples/app/595.js
new file mode 100644
index 00000000..88b7e842
--- /dev/null
+++ b/samples/app/595.js
@@ -0,0 +1,36 @@
+const Vue = require('nativescript-vue')
+
+Vue.config.debug = true
+Vue.config.silent = false
+
+Plugin = {
+ install(Vue, name) {
+ Vue.prototype.$name = name
+
+ Vue.mixin({
+ beforeCreate: function () {
+ setTimeout(() => {
+ console.log('this.$options: ', this.$options)
+ }, 5000)
+ }
+ })
+ }
+}
+
+Vue.use(Plugin, 'pluginName')
+
+new Vue({
+ data: {},
+ template: `
+
+
+
+
+
+
+
+
+
+
+ `
+}).$start()
diff --git a/samples/app/608.js b/samples/app/608.js
new file mode 100644
index 00000000..5eba944d
--- /dev/null
+++ b/samples/app/608.js
@@ -0,0 +1,19 @@
+const Vue = require('nativescript-vue')
+
+Vue.config.debug = true
+Vue.config.silent = false
+
+new Vue({
+ data() {
+ return {
+ boolValue: true
+ }
+ },
+ template: `
+
+
+
+
+
+ `
+}).$start()
diff --git a/samples/app/644.js b/samples/app/644.js
new file mode 100644
index 00000000..3b284f23
--- /dev/null
+++ b/samples/app/644.js
@@ -0,0 +1,29 @@
+const Vue = require('nativescript-vue')
+
+Vue.config.debug = true
+Vue.config.silent = false
+
+// console.log(Vue.compile(' ').render.toString())
+
+new Vue({
+ data() {
+ return {
+ currentStep: 0,
+ departure: false,
+ arrival: false
+ }
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+ `
+}).$start()
diff --git a/samples/app/748.js b/samples/app/748.js
new file mode 100644
index 00000000..ed26e320
--- /dev/null
+++ b/samples/app/748.js
@@ -0,0 +1,26 @@
+const Vue = require('nativescript-vue')
+
+Vue.config.debug = true
+Vue.config.silent = false
+
+new Vue({
+ data: {
+ foo: false
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+ `,
+ methods: {
+ onlyOnce() {
+ console.log('this log should only appear once')
+ }
+ }
+}).$start()
diff --git a/samples/app/76.js b/samples/app/76.js
new file mode 100644
index 00000000..4eb3488a
--- /dev/null
+++ b/samples/app/76.js
@@ -0,0 +1,37 @@
+const Vue = require('nativescript-vue')
+
+Vue.config.debug = true
+Vue.config.silent = false
+
+new Vue({
+ data: {
+ selectedTab: 0
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ created() {
+ console.log(Vue.compile(this.$options.template).render.toString())
+ }
+}).$start()
diff --git a/samples/app/app-to-check-android-events.js b/samples/app/app-to-check-android-events.js
new file mode 100644
index 00000000..79f2b5c7
--- /dev/null
+++ b/samples/app/app-to-check-android-events.js
@@ -0,0 +1,52 @@
+const Vue = require('nativescript-vue')
+const { Application } = require('@nativescript/core')
+
+Vue.config.silent = false
+Vue.config.debug = true
+
+new Vue({
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ data: {
+ androidEvents: [],
+ switchValue: false
+ },
+ created() {
+ if (global.isAndroid) {
+ const eventTypes = [
+ Application.AndroidApplication.activityCreatedEvent,
+ Application.AndroidApplication.activityDestroyedEvent,
+ Application.AndroidApplication.activityStartedEvent,
+ Application.AndroidApplication.activityPausedEvent,
+ Application.AndroidApplication.activityResumedEvent,
+ Application.AndroidApplication.activityStoppedEvent,
+ Application.AndroidApplication.saveActivityStateEvent,
+ Application.AndroidApplication.activityResultEvent,
+ Application.AndroidApplication.activityBackPressedEvent
+ ]
+ for (let i = 0; i < eventTypes.length; i++) {
+ Application.android.on(eventTypes[i], event => {
+ console.log(`Event: ${event.eventName}, Activity: ${event.activity}`)
+ this.androidEvents.push(event)
+ })
+ }
+ }
+ }
+}).$start()
diff --git a/samples/app/app-to-check-hmr.js b/samples/app/app-to-check-hmr.js
new file mode 100644
index 00000000..deee7516
--- /dev/null
+++ b/samples/app/app-to-check-hmr.js
@@ -0,0 +1,13 @@
+import Home from './components/Home'
+
+const Vue = require('nativescript-vue')
+
+Vue.config.silent = false
+Vue.config.debug = true
+
+new Vue({
+ components: {
+ Home
+ },
+ render: h => h('frame', [h(Home)])
+}).$start()
diff --git a/samples/app/app-to-check-v-slot.js b/samples/app/app-to-check-v-slot.js
new file mode 100644
index 00000000..69c5cc8e
--- /dev/null
+++ b/samples/app/app-to-check-v-slot.js
@@ -0,0 +1,13 @@
+const Vue = require('nativescript-vue')
+
+Vue.config.silent = false
+Vue.config.debug = true
+
+import VSlot from './components/VSlot'
+
+new Vue({
+ components: {
+ VSlot
+ },
+ render: h => h('frame', [h(VSlot)])
+}).$start()
diff --git a/samples/app/app-with-all-components.js b/samples/app/app-with-all-components.js
new file mode 100644
index 00000000..aae8b63e
--- /dev/null
+++ b/samples/app/app-with-all-components.js
@@ -0,0 +1,242 @@
+const Vue = require('nativescript-vue')
+const { Frame, Utils } = require('@nativescript/core')
+
+Vue.config.debug = true
+Vue.config.silent = false
+
+// for animated GIF search
+const url = 'https://api.giphy.com/v1/gifs/search'
+const key = 'ZboEpjHv00FzK6SI7l33H7wutWlMldQs'
+const filter = 'limit=10&offset=0&rating=G&lang=en'
+
+new Vue({
+ data() {
+ return {
+ activeTab: 0,
+ switchValue: false,
+ textfieldValue: 'Some text',
+ textviewValue: 'TextView\nhas\nmultiple\nlines',
+ selectedItem: 'first',
+ listOfItems: ['first', 'second', 'third'],
+ selectedIndex: 0,
+ timesPressed: 0,
+ labelCondition: true,
+ selectedDate: new Date(),
+ selectedTime: new Date(),
+ q: '',
+ listViewImgs: [],
+ progressValue: 50
+ }
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`,
+ computed: {
+ buttonText() {
+ return this.timesPressed > 0
+ ? `Pressed ${this.timesPressed} times`
+ : 'Press me'
+ }
+ },
+ methods: {
+ isTabActive(key) {
+ return this.tabs[this.activeTab].key === key
+ },
+ onSwitchChanged() {
+ console.log(`Switch changed to ${this.switchValue}`)
+ },
+ onTextFiedChanged() {
+ console.log(`TextField changed to "${this.textfieldValue}"`)
+ },
+ onTextViewChanged() {
+ console.log(`TextView changed to "${this.textviewValue}"`)
+ },
+ onListPickerChanged() {
+ console.log(`ListPicker selectedIndex changed to ${this.selectedIndex}`)
+ },
+ onDateChanged() {
+ console.log(`Date changed to ${this.selectedDate}`)
+ },
+ onTimeChanged() {
+ console.log(`Time changed to ${this.selectedTime.toTimeString()}`)
+ },
+ onButtonPress() {
+ console.log('Button pressed')
+ this.timesPressed++
+ },
+ onSearchGif() {
+ this.dismissKeyboard()
+ fetch(`${url}?api_key=${key}&q=${this.q}&${filter}`)
+ .then(response => response.json())
+ .then(json => {
+ this.listViewImgs = json.data
+ })
+ },
+ onImageLoaded(event) {
+ console.log('Image loaded')
+ },
+ onSegmentedBarChanged(event) {
+ console.log(`SegmentedBar index changed to ${event.value}`)
+ },
+ onSliderChanged() {
+ console.log(`Slider value changed to ${this.progressValue}`)
+ },
+ getImgUrl(img) {
+ return `${img.images.fixed_height_still.url}?${Date.now()}`
+ },
+ dismissKeyboard() {
+ if (global.isAndroid) {
+ Utils.android.dismissSoftInput()
+ } else {
+ Frame.topmost().nativeView.endEditing(true)
+ }
+ }
+ },
+ created() {
+ console.log(Vue.compile(this.$options.template).render.toString())
+ },
+ mounted() {
+ Object.keys(this.$refs).map(key => {
+ console.log(`this.$refs.${key} -> ${this.$refs[key]}`)
+ })
+ }
+}).$start()
diff --git a/samples/app/app-with-android-ios.js b/samples/app/app-with-android-ios.js
new file mode 100644
index 00000000..2f1eb8b1
--- /dev/null
+++ b/samples/app/app-with-android-ios.js
@@ -0,0 +1,22 @@
+const Vue = require('nativescript-vue')
+
+new Vue({
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+}).$start()
diff --git a/samples/app/app-with-bottom-navigation.js b/samples/app/app-with-bottom-navigation.js
new file mode 100644
index 00000000..2c8eb0fe
--- /dev/null
+++ b/samples/app/app-with-bottom-navigation.js
@@ -0,0 +1,51 @@
+const Vue = require('nativescript-vue')
+
+Vue.config.debug = true
+
+let app = new Vue({
+ data: {
+ activeTab: 1,
+ tabs: [
+ { title: 'First Tab', text: 'im the first tab' },
+ { title: 'Second Tab', text: 'im the second tab' },
+ { title: 'Third Tab', text: 'im the third tab' }
+ ]
+ },
+
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+})
+
+app.$start()
diff --git a/samples/app/app-with-formatted-string.js b/samples/app/app-with-formatted-string.js
new file mode 100644
index 00000000..f8d1cab3
--- /dev/null
+++ b/samples/app/app-with-formatted-string.js
@@ -0,0 +1,26 @@
+const Vue = require('nativescript-vue')
+
+Vue.config.silent = false
+
+new Vue({
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+ `,
+
+ comments: true,
+ data() {
+ return {
+ toggle: false
+ }
+ }
+}).$start()
diff --git a/samples/app/app-with-frame.js b/samples/app/app-with-frame.js
new file mode 100644
index 00000000..e6e412c7
--- /dev/null
+++ b/samples/app/app-with-frame.js
@@ -0,0 +1,41 @@
+import Vue from 'nativescript-vue'
+
+Vue.config.silent = false
+Vue.config.debug = true
+
+const createPage = title => ({
+ template: ` `,
+ components: {
+ Foo: {
+ template: ` `,
+ created() {
+ this.intv = setInterval(
+ () => console.log(`[${title}] INTERVAL FIRED.`),
+ 1000
+ )
+ },
+ destroyed() {
+ clearInterval(this.intv)
+ }
+ }
+ }
+})
+
+new Vue({
+ template: `
+
+
+
+ `,
+
+ methods: {
+ navigate() {
+ this.$navigateTo(createPage('test'))
+ },
+ log(name) {
+ console.log('FRAME EVENT: ' + name)
+ }
+ }
+}).$start()
diff --git a/samples/app/app-with-frames.js b/samples/app/app-with-frames.js
new file mode 100644
index 00000000..7f80b5f9
--- /dev/null
+++ b/samples/app/app-with-frames.js
@@ -0,0 +1,44 @@
+const Vue = require('nativescript-vue')
+
+Vue.config.silent = false
+Vue.config.debug = true
+
+new Vue({
+ data() {
+ return {
+ a: true
+ }
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+}).$start()
diff --git a/samples/app/app-with-http-requests.js b/samples/app/app-with-http-requests.js
new file mode 100644
index 00000000..78920621
--- /dev/null
+++ b/samples/app/app-with-http-requests.js
@@ -0,0 +1,29 @@
+const Vue = require('nativescript-vue')
+const http = require('http')
+
+Vue.config.debug = true
+
+new Vue({
+ template: `
+
+
+
+
+
+
+
+
+
+ `,
+
+ methods: {
+ async makeRequest() {
+ const res = await http.request({
+ url: 'https://httpbin.org/anything',
+ method: 'GET'
+ })
+
+ console.dir(res)
+ }
+ }
+}).$start()
diff --git a/samples/app/app-with-list-view.js b/samples/app/app-with-list-view.js
new file mode 100644
index 00000000..21e17696
--- /dev/null
+++ b/samples/app/app-with-list-view.js
@@ -0,0 +1,155 @@
+const Vue = require('nativescript-vue')
+const http = require('http')
+
+Vue.config.debug = true
+Vue.prototype.$http = http
+
+new Vue({
+ data: {
+ subreddit: '/r/funny',
+ page_num: 1,
+ last_page: '',
+ items: []
+ },
+
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+
+ created() {
+ this.fetchItems()
+ },
+
+ methods: {
+ onItemTap({ item }) {
+ if (item.type === 'page') {
+ return alert('You shall not pass.')
+ }
+
+ this.$showModal(
+ {
+ template: `
+
+
+
+
+
+
+
+
+
+
+ `
+ },
+ {
+ fullscreen: true
+ }
+ ).then(res => {
+ console.log('Modal closed')
+ console.dir(res)
+ })
+ },
+
+ onLoaded(e) {
+ console.log('The list has been loaded')
+ },
+
+ onLoadMoreItems(e) {
+ console.log('Loading more items')
+ return this.fetchItems()
+ },
+
+ refresh() {
+ this.items = []
+ this.page_num = 1
+ this.fetchItems()
+ },
+
+ chooseSubreddit() {
+ prompt({
+ title: 'Change subreddit:',
+ defaultText: this.subreddit,
+ okButtonText: 'Ok',
+ cancelButtonText: 'Cancel'
+ }).then(r => {
+ if (r.result) {
+ this.subreddit = r.text
+ this.refresh()
+ }
+ })
+ },
+
+ fetchItems() {
+ this.$http
+ .getJSON(
+ `https://www.reddit.com/${this.subreddit}.json?limit=10&count=10&after=${this.last_page}`
+ )
+ .then(res => {
+ this.items.push({
+ title: 'Page ' + this.page_num,
+ type: 'page'
+ })
+
+ res.data.children.forEach(item => {
+ const fullImage = item.data.preview
+ ? item.data.preview.images[0].source.url
+ : null
+ const image = item.data.preview ? item.data.preview.thumbnail : null
+ this.items.push({
+ title: item.data.title,
+ image: image,
+ fullImage: fullImage,
+ type: 'entry'
+ })
+ })
+ this.last_page = res.data.after
+ this.page_num++
+
+ console.log('Loaded more items')
+ })
+ .catch(err => {
+ console.log('err..' + err)
+ })
+ }
+ }
+}).$start()
diff --git a/samples/app/app-with-nested-object-list-view.js b/samples/app/app-with-nested-object-list-view.js
new file mode 100644
index 00000000..12b591ef
--- /dev/null
+++ b/samples/app/app-with-nested-object-list-view.js
@@ -0,0 +1,37 @@
+const Vue = require('nativescript-vue')
+
+new Vue({
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+
+ data: {
+ items: [
+ {
+ text: 'text 1',
+ user: { name: 'John', meta: { age: 10 } }
+ },
+ {
+ text: 'text 2',
+ user: { name: 'Lucy', meta: { age: 14 } }
+ },
+ {
+ text: 'text 3',
+ user: { name: 'Nick', meta: { age: 10 } }
+ }
+ ]
+ }
+}).$start()
diff --git a/samples/app/app-with-pages.js b/samples/app/app-with-pages.js
new file mode 100644
index 00000000..ca2e1f21
--- /dev/null
+++ b/samples/app/app-with-pages.js
@@ -0,0 +1,118 @@
+import Vue from 'nativescript-vue'
+
+Vue.config.devtools = true
+Vue.config.debug = false //true
+Vue.config.silent = false
+
+const ToggledComp = {
+ template: ` `
+}
+
+const ReceivePropsPage = {
+ name: 'ReceivePropsPage',
+
+ props: {
+ title: {
+ type: String,
+ default: ''
+ },
+ text: {
+ type: String,
+ default: ''
+ }
+ },
+
+ mounted() {
+ console.log(this.$props)
+ },
+
+ template: `
+
+
+
+
+
+
+
+ `,
+
+ methods: {
+ openPage() {
+ this.$navigateTo(DetailsPage)
+ }
+ }
+}
+
+const DefaultPage = {
+ name: 'DefaultPage',
+
+ components: { ToggledComp },
+ template: `
+
+
+
+
+
+
+
+
+ `,
+
+ methods: {
+ openPage() {
+ this.$navigateTo(DetailsPage)
+ },
+ openReceivePropsPage() {
+ this.$navigateTo(ReceivePropsPage, {
+ props: {
+ title: 'Receive props',
+ text: 'This text is passed from $navigateTo'
+ }
+ })
+ }
+ }
+}
+
+const DetailsPage = {
+ name: 'DetailsPage',
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+
+ methods: {
+ openDetails(options = {}) {
+ this.$navigateTo(DetailsPage, options)
+ },
+ openDefault(options) {
+ this.$navigateTo(DefaultPage, options)
+ },
+ goBack() {
+ this.$navigateBack()
+ }
+ }
+}
+
+const App = {
+ name: 'App',
+ components: { DefaultPage },
+ template: `
+
+
+
+ `
+}
+
+new Vue({
+ render: h => h(App)
+}).$start()
diff --git a/samples/app/app-with-shared-actionbar.js b/samples/app/app-with-shared-actionbar.js
new file mode 100644
index 00000000..4b39b5fa
--- /dev/null
+++ b/samples/app/app-with-shared-actionbar.js
@@ -0,0 +1,46 @@
+import Vue from 'nativescript-vue'
+
+Vue.config.silent = false
+Vue.config.debug = true
+
+let id = 1
+
+const page = name => ({
+ template: `
+
+
+
+
+
+ `
+})
+
+new Vue({
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ methods: {
+ forward() {
+ this.$navigateTo(page('OtherPage ID:' + id++))
+ },
+
+ backward() {
+ this.$navigateBack()
+ }
+ },
+ components: {
+ HomePage: page('Home')
+ }
+}).$start()
diff --git a/samples/app/app-with-tab-view.js b/samples/app/app-with-tab-view.js
new file mode 100644
index 00000000..da4b6cfc
--- /dev/null
+++ b/samples/app/app-with-tab-view.js
@@ -0,0 +1,47 @@
+const Vue = require('nativescript-vue')
+
+Vue.config.debug = true
+
+let app = new Vue({
+ data: {
+ activeTab: 1,
+ tabs: [
+ { title: 'First Tab', text: 'im the first tab' },
+ { title: 'Second Tab', text: 'im the second tab' },
+ { title: 'Third Tab', text: 'im the third tab' }
+ ]
+ },
+
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+})
+
+app.$start()
diff --git a/samples/app/app-with-tabs.js b/samples/app/app-with-tabs.js
new file mode 100644
index 00000000..41b75361
--- /dev/null
+++ b/samples/app/app-with-tabs.js
@@ -0,0 +1,54 @@
+const Vue = require('nativescript-vue')
+
+Vue.config.debug = true
+
+let app = new Vue({
+ data: {
+ activeTab: 1,
+ tabs: [
+ { title: 'First Tab', text: 'im the first tab' },
+ { title: 'Second Tab', text: 'im the second tab' },
+ { title: 'Third Tab', text: 'im the third tab' }
+ ]
+ },
+
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+})
+
+app.$start()
diff --git a/samples/app/app-with-v-template-components.js b/samples/app/app-with-v-template-components.js
new file mode 100644
index 00000000..47bea6b8
--- /dev/null
+++ b/samples/app/app-with-v-template-components.js
@@ -0,0 +1,36 @@
+const Vue = require('nativescript-vue')
+
+Vue.config.silent = false
+Vue.config.debug = true
+
+// Comment is a built-in tag/component so we use CommentComp instead
+// the framework should warn about this in the future!
+Vue.component('CommentComp', {
+ props: ['comment'],
+ template: ` `
+})
+
+new Vue({
+ data: {
+ comments: [
+ { content: 'hello1' },
+ { content: 'hello2' },
+ { content: 'hello3' },
+ { content: 'hello4' }
+ ]
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+}).$start()
diff --git a/samples/app/app-with-v-template.js b/samples/app/app-with-v-template.js
new file mode 100644
index 00000000..41f9e85d
--- /dev/null
+++ b/samples/app/app-with-v-template.js
@@ -0,0 +1,59 @@
+const Vue = require('nativescript-vue')
+
+new Vue({
+ data() {
+ return {
+ items: [
+ 'this',
+ 'is',
+ 'a',
+ 'list',
+ 'of',
+ 'random',
+ 'words',
+ 'to',
+ 'test',
+ 'the',
+ 'list',
+ 'view',
+ 'with',
+ 'multiple',
+ 'templates',
+ 'using',
+ 'the new',
+ 'v-template',
+ 'syntax',
+ 'pretty',
+ 'cool',
+ 'huh?'
+ ]
+ }
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `
+}).$start()
diff --git a/samples/app/app-with-vmodel.js b/samples/app/app-with-vmodel.js
new file mode 100644
index 00000000..eba53048
--- /dev/null
+++ b/samples/app/app-with-vmodel.js
@@ -0,0 +1,51 @@
+const Vue = require('nativescript-vue')
+
+new Vue({
+ data: {
+ test: 'testing',
+ test2: 50,
+ test3: 30,
+ date: new Date(),
+ time: new Date(),
+ list: ['a', 'b', 'c'],
+ listSel: 1,
+ test4: false
+ },
+
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+
+ methods: {
+ onTap() {
+ this.test = 'changed'
+ this.test2 = 42
+ this.test3 = 18
+ }
+ }
+}).$start()
diff --git a/samples/app/app-with-vuex.js b/samples/app/app-with-vuex.js
new file mode 100644
index 00000000..0a612868
--- /dev/null
+++ b/samples/app/app-with-vuex.js
@@ -0,0 +1,42 @@
+const Vue = require('nativescript-vue')
+const Vuex = require('vuex')
+
+Vue.use(Vuex)
+
+const store = new Vuex.Store({
+ state: {
+ count: 42
+ },
+
+ mutations: {
+ decrement(state) {
+ state.count--
+ }
+ }
+})
+
+new Vue({
+ store,
+ template: `
+
+
+
+
+
+
+
+
+ `,
+
+ computed: {
+ count() {
+ return this.$store.state.count
+ }
+ },
+
+ methods: {
+ decrement() {
+ this.$store.commit('decrement')
+ }
+ }
+}).$start()
diff --git a/samples/app/app.css b/samples/app/app.css
new file mode 100644
index 00000000..eaac87f6
--- /dev/null
+++ b/samples/app/app.css
@@ -0,0 +1,44 @@
+/* Your CSS goes here */
+
+.even {
+ color: red;
+}
+
+.odd {
+ color: blue;
+}
+
+
+.fade-enter-active {
+ animation-name: fade;
+ animation-duration: 3s;
+ animation-fill-mode: forwards;
+}
+.fade-leave-active {
+ animation-name: fade;
+ animation-duration: 3s;
+ animation-direction: reverse;
+ animation-fill-mode: forwards;
+}
+
+@keyframes fade {
+ from { opacity: 0; }
+ to { opacity: 1; }
+}
+
+.tabbar {
+ width: 100%;
+ vertical-align: bottom;
+ background-color: #eaeaea;
+}
+
+.tabbar__item {
+ padding: 20 0;
+ width: 50%;
+ text-align: center;
+ vertical-align: center;
+}
+
+.tabbar__item--active {
+ color: #5577dd;
+}
diff --git a/samples/app/app.js b/samples/app/app.js
new file mode 100644
index 00000000..f48bcf93
--- /dev/null
+++ b/samples/app/app.js
@@ -0,0 +1,59 @@
+const Vue = require('nativescript-vue')
+
+Vue.component('image-viewer', {
+ props: ['imgSrc'],
+
+ data() {
+ return {
+ img: ''
+ }
+ },
+
+ template: `
+
+
+
+
+
+
+
+
+ `,
+
+ mounted() {
+ this.img = this.imgSrc
+ }
+})
+
+new Vue({
+ template: `
+
+
+
+ TAP HERE
+ TAP HERE
+
+ Tap to see a trick!
+
+
+
+
+
+ `,
+
+ data: {
+ textRed: false,
+ showTrick: false,
+ imgSrc: '~/images/apple.jpg'
+ },
+
+ methods: {
+ onTap() {
+ alert('Nice Tap!')
+ }
+ }
+}).$start()
diff --git a/samples/app/child-order.js b/samples/app/child-order.js
new file mode 100644
index 00000000..9423ed62
--- /dev/null
+++ b/samples/app/child-order.js
@@ -0,0 +1,16 @@
+const Vue = require('nativescript-vue')
+import ChildOrder from './components/ChildOrder'
+
+Vue.config.debug = true
+Vue.config.silent = false
+
+new Vue({
+ template: `
+
+
+
+ `,
+ components: {
+ ChildOrder
+ }
+}).$start()
diff --git a/samples/app/components/ChildOrder.vue b/samples/app/components/ChildOrder.vue
new file mode 100644
index 00000000..b460319c
--- /dev/null
+++ b/samples/app/components/ChildOrder.vue
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/app/components/ComponentWithSlot.vue b/samples/app/components/ComponentWithSlot.vue
new file mode 100644
index 00000000..4d907e22
--- /dev/null
+++ b/samples/app/components/ComponentWithSlot.vue
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
diff --git a/samples/app/components/Details.vue b/samples/app/components/Details.vue
new file mode 100644
index 00000000..02add5e8
--- /dev/null
+++ b/samples/app/components/Details.vue
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/app/components/EmptyTemplate.vue b/samples/app/components/EmptyTemplate.vue
new file mode 100644
index 00000000..892a5321
--- /dev/null
+++ b/samples/app/components/EmptyTemplate.vue
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/samples/app/components/Home.vue b/samples/app/components/Home.vue
new file mode 100644
index 00000000..c97a7584
--- /dev/null
+++ b/samples/app/components/Home.vue
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/app/components/ListItem.vue b/samples/app/components/ListItem.vue
new file mode 100644
index 00000000..930e52e2
--- /dev/null
+++ b/samples/app/components/ListItem.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ listeners.toggleLike()"
+ :text="props.item.liked ? ' Unlike' : ' Like'"
+ :color="props.item.liked ? 'blue' : 'black'"
+ col="0" row="1"/>
+
+
+
+
+
+
diff --git a/samples/app/components/ListViewTest.vue b/samples/app/components/ListViewTest.vue
new file mode 100644
index 00000000..715c8658
--- /dev/null
+++ b/samples/app/components/ListViewTest.vue
@@ -0,0 +1,216 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/app/components/VSlot.vue b/samples/app/components/VSlot.vue
new file mode 100644
index 00000000..25376f33
--- /dev/null
+++ b/samples/app/components/VSlot.vue
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/app/entry.js b/samples/app/entry.js
new file mode 100644
index 00000000..4feb67d2
--- /dev/null
+++ b/samples/app/entry.js
@@ -0,0 +1 @@
+require('./multiple-default-frames')
diff --git a/samples/app/images/apple.jpg b/samples/app/images/apple.jpg
new file mode 100644
index 00000000..a1b38aea
Binary files /dev/null and b/samples/app/images/apple.jpg differ
diff --git a/samples/app/images/vue.png b/samples/app/images/vue.png
new file mode 100644
index 00000000..f790ac47
Binary files /dev/null and b/samples/app/images/vue.png differ
diff --git a/samples/app/list.js b/samples/app/list.js
new file mode 100644
index 00000000..4e3f96f6
--- /dev/null
+++ b/samples/app/list.js
@@ -0,0 +1,16 @@
+import Vue from 'nativescript-vue'
+import ListViewTest from './components/ListViewTest'
+
+// Vue.config.debug = true
+// Vue.config.silent = false
+
+new Vue({
+ template: `
+
+
+
+ `,
+ components: {
+ ListViewTest
+ }
+}).$start()
diff --git a/samples/app/modals-on-top-of-modals.js b/samples/app/modals-on-top-of-modals.js
new file mode 100644
index 00000000..874bd7a5
--- /dev/null
+++ b/samples/app/modals-on-top-of-modals.js
@@ -0,0 +1,84 @@
+import Vue from 'nativescript-vue'
+
+Vue.config.debug = true
+Vue.config.silent = false
+
+const SecondaryModal = {
+ name: 'SecondaryModalComponent',
+ template: `
+
+
+
+
+
+
+
+
+
+ `
+}
+
+const ModalComponent = {
+ props: ['foo'],
+ name: 'ModalComponent',
+ template: `
+
+
+
+
+
+
+
+
+
+ `,
+
+ methods: {
+ openModal() {
+ this.$showModal(SecondaryModal, { target: this })
+ }
+ }
+}
+new Vue({
+ data() {
+ return {
+ modalResult: 'No result yet.',
+ animated: true,
+ fullscreen: false,
+ stretched: false
+ }
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ methods: {
+ openModal() {
+ this.$showModal(ModalComponent, {
+ props: { foo: 'bar' },
+ animated: this.animated,
+ fullscreen: this.fullscreen,
+ stretched: this.stretched
+ })
+ }
+ }
+}).$start()
diff --git a/samples/app/modals.js b/samples/app/modals.js
new file mode 100644
index 00000000..9a3e4888
--- /dev/null
+++ b/samples/app/modals.js
@@ -0,0 +1,75 @@
+import Vue from 'nativescript-vue'
+
+Vue.config.debug = true
+Vue.config.silent = false
+
+const FooComp = {
+ template: `
+
+ `
+}
+
+const ModalComponent = {
+ props: ['foo'],
+ name: 'ModalComponent',
+ template: `
+
+
+
+
+
+
+
+
+
+
+ `,
+ components: { FooComp }
+}
+
+new Vue({
+ data() {
+ return {
+ modalResult: 'No result yet.',
+ animated: true,
+ fullscreen: true,
+ stretched: false
+ }
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ methods: {
+ openModal() {
+ this.$showModal(ModalComponent, {
+ props: { foo: 'bar' },
+ animated: this.animated,
+ fullscreen: this.fullscreen,
+ stretched: this.stretched
+ }).then(res => {
+ this.modalResult = res
+ })
+ }
+ }
+}).$start()
diff --git a/samples/app/multiple-default-frames.js b/samples/app/multiple-default-frames.js
new file mode 100644
index 00000000..c1e9f881
--- /dev/null
+++ b/samples/app/multiple-default-frames.js
@@ -0,0 +1,54 @@
+const Vue = require('nativescript-vue')
+
+Vue.config.silent = false
+
+const wait = ms => new Promise(resolve => setTimeout(resolve, ms))
+
+const pageComponent = title => ({
+ template: `
+
+
+
+ `
+})
+
+let app = new Vue({
+ data() {
+ return {
+ showSecondFrame: true
+ }
+ },
+ template: `
+
+
+
+ ${pageComponent('[top frame] Page 1').template}
+
+
+
+
+
+ ${pageComponent('[bottom frame] Page 2').template}
+
+
+
+
+ `,
+ mounted() {
+ this.testNavigations().catch(err => {
+ console.log(err)
+ })
+ },
+ methods: {
+ async testNavigations() {
+ await wait(3000)
+ this.$navigateTo(pageComponent('[bottom frame] Page 3'))
+ await wait(3000)
+ this.showSecondFrame = false
+ await wait(3000)
+ this.$navigateTo(pageComponent('[top frame] Page 4'))
+ }
+ }
+})
+
+app.$start()
diff --git a/samples/app/v-show.js b/samples/app/v-show.js
new file mode 100644
index 00000000..b6e451c7
--- /dev/null
+++ b/samples/app/v-show.js
@@ -0,0 +1,26 @@
+const Vue = require('nativescript-vue')
+
+Vue.config.debug = true
+Vue.config.silent = false
+
+new Vue({
+ data: {
+ foo: true
+ },
+ template: `
+
+
+
+
+
+
+
+
+
+
+
+ `,
+ created() {
+ console.log(Vue.compile(this.$options.template).render.toString())
+ }
+}).$start()
diff --git a/samples/app_resources/Android/app.gradle b/samples/app_resources/Android/app.gradle
new file mode 100644
index 00000000..2c02c1d3
--- /dev/null
+++ b/samples/app_resources/Android/app.gradle
@@ -0,0 +1,11 @@
+// Add your native dependencies here:
+
+android {
+ defaultConfig {
+ generatedDensities = []
+ applicationId = "org.nativescript.application"
+ }
+ aaptOptions {
+ additionalParameters "--no-version-vectors"
+ }
+}
diff --git a/samples/app_resources/Android/src/main/AndroidManifest.xml b/samples/app_resources/Android/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..7fcd2e44
--- /dev/null
+++ b/samples/app_resources/Android/src/main/AndroidManifest.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demo/App_Resources/Android/src/main/res/drawable-xhdpi/background.png b/samples/app_resources/Android/src/main/res/drawable-hdpi/background.png
similarity index 97%
rename from demo/App_Resources/Android/src/main/res/drawable-xhdpi/background.png
rename to samples/app_resources/Android/src/main/res/drawable-hdpi/background.png
index 3541570c..64200327 100644
Binary files a/demo/App_Resources/Android/src/main/res/drawable-xhdpi/background.png and b/samples/app_resources/Android/src/main/res/drawable-hdpi/background.png differ
diff --git a/samples/app_resources/Android/src/main/res/drawable-hdpi/icon.png b/samples/app_resources/Android/src/main/res/drawable-hdpi/icon.png
new file mode 100644
index 00000000..117b444a
Binary files /dev/null and b/samples/app_resources/Android/src/main/res/drawable-hdpi/icon.png differ
diff --git a/samples/app_resources/Android/src/main/res/drawable-hdpi/logo.png b/samples/app_resources/Android/src/main/res/drawable-hdpi/logo.png
new file mode 100644
index 00000000..711905f3
Binary files /dev/null and b/samples/app_resources/Android/src/main/res/drawable-hdpi/logo.png differ
diff --git a/demo/App_Resources/Android/src/main/res/drawable-ldpi/background.png b/samples/app_resources/Android/src/main/res/drawable-ldpi/background.png
similarity index 93%
rename from demo/App_Resources/Android/src/main/res/drawable-ldpi/background.png
rename to samples/app_resources/Android/src/main/res/drawable-ldpi/background.png
index f6a08eea..03befc22 100644
Binary files a/demo/App_Resources/Android/src/main/res/drawable-ldpi/background.png and b/samples/app_resources/Android/src/main/res/drawable-ldpi/background.png differ
diff --git a/samples/app_resources/Android/src/main/res/drawable-ldpi/icon.png b/samples/app_resources/Android/src/main/res/drawable-ldpi/icon.png
new file mode 100644
index 00000000..bd04848e
Binary files /dev/null and b/samples/app_resources/Android/src/main/res/drawable-ldpi/icon.png differ
diff --git a/samples/app_resources/Android/src/main/res/drawable-ldpi/logo.png b/samples/app_resources/Android/src/main/res/drawable-ldpi/logo.png
new file mode 100644
index 00000000..af908e46
Binary files /dev/null and b/samples/app_resources/Android/src/main/res/drawable-ldpi/logo.png differ
diff --git a/demo/App_Resources/Android/src/main/res/drawable-mdpi/background.png b/samples/app_resources/Android/src/main/res/drawable-mdpi/background.png
similarity index 94%
rename from demo/App_Resources/Android/src/main/res/drawable-mdpi/background.png
rename to samples/app_resources/Android/src/main/res/drawable-mdpi/background.png
index 0c90f0f7..cfe4a7c2 100644
Binary files a/demo/App_Resources/Android/src/main/res/drawable-mdpi/background.png and b/samples/app_resources/Android/src/main/res/drawable-mdpi/background.png differ
diff --git a/samples/app_resources/Android/src/main/res/drawable-mdpi/icon.png b/samples/app_resources/Android/src/main/res/drawable-mdpi/icon.png
new file mode 100644
index 00000000..32aa6176
Binary files /dev/null and b/samples/app_resources/Android/src/main/res/drawable-mdpi/icon.png differ
diff --git a/samples/app_resources/Android/src/main/res/drawable-mdpi/logo.png b/samples/app_resources/Android/src/main/res/drawable-mdpi/logo.png
new file mode 100644
index 00000000..c21ae444
Binary files /dev/null and b/samples/app_resources/Android/src/main/res/drawable-mdpi/logo.png differ
diff --git a/demo/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml b/samples/app_resources/Android/src/main/res/drawable-nodpi/splash_screen.xml
similarity index 100%
rename from demo/App_Resources/Android/src/main/res/drawable-nodpi/splash_screen.xml
rename to samples/app_resources/Android/src/main/res/drawable-nodpi/splash_screen.xml
diff --git a/demo/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png b/samples/app_resources/Android/src/main/res/drawable-xhdpi/background.png
similarity index 98%
rename from demo/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png
rename to samples/app_resources/Android/src/main/res/drawable-xhdpi/background.png
index abb0fc70..b06ae267 100644
Binary files a/demo/App_Resources/Android/src/main/res/drawable-xxhdpi/background.png and b/samples/app_resources/Android/src/main/res/drawable-xhdpi/background.png differ
diff --git a/samples/app_resources/Android/src/main/res/drawable-xhdpi/icon.png b/samples/app_resources/Android/src/main/res/drawable-xhdpi/icon.png
new file mode 100644
index 00000000..12950046
Binary files /dev/null and b/samples/app_resources/Android/src/main/res/drawable-xhdpi/icon.png differ
diff --git a/samples/app_resources/Android/src/main/res/drawable-xhdpi/logo.png b/samples/app_resources/Android/src/main/res/drawable-xhdpi/logo.png
new file mode 100644
index 00000000..4ad5346d
Binary files /dev/null and b/samples/app_resources/Android/src/main/res/drawable-xhdpi/logo.png differ
diff --git a/demo/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png b/samples/app_resources/Android/src/main/res/drawable-xxhdpi/background.png
similarity index 98%
rename from demo/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png
rename to samples/app_resources/Android/src/main/res/drawable-xxhdpi/background.png
index 10897752..9bc7f010 100644
Binary files a/demo/App_Resources/Android/src/main/res/drawable-xxxhdpi/background.png and b/samples/app_resources/Android/src/main/res/drawable-xxhdpi/background.png differ
diff --git a/samples/app_resources/Android/src/main/res/drawable-xxhdpi/icon.png b/samples/app_resources/Android/src/main/res/drawable-xxhdpi/icon.png
new file mode 100644
index 00000000..541e7591
Binary files /dev/null and b/samples/app_resources/Android/src/main/res/drawable-xxhdpi/icon.png differ
diff --git a/samples/app_resources/Android/src/main/res/drawable-xxhdpi/logo.png b/samples/app_resources/Android/src/main/res/drawable-xxhdpi/logo.png
new file mode 100644
index 00000000..bcc40119
Binary files /dev/null and b/samples/app_resources/Android/src/main/res/drawable-xxhdpi/logo.png differ
diff --git a/samples/app_resources/Android/src/main/res/drawable-xxxhdpi/background.png b/samples/app_resources/Android/src/main/res/drawable-xxxhdpi/background.png
new file mode 100644
index 00000000..d93c3d8f
Binary files /dev/null and b/samples/app_resources/Android/src/main/res/drawable-xxxhdpi/background.png differ
diff --git a/samples/app_resources/Android/src/main/res/drawable-xxxhdpi/icon.png b/samples/app_resources/Android/src/main/res/drawable-xxxhdpi/icon.png
new file mode 100644
index 00000000..072b6013
Binary files /dev/null and b/samples/app_resources/Android/src/main/res/drawable-xxxhdpi/icon.png differ
diff --git a/samples/app_resources/Android/src/main/res/drawable-xxxhdpi/logo.png b/samples/app_resources/Android/src/main/res/drawable-xxxhdpi/logo.png
new file mode 100644
index 00000000..96acb1ec
Binary files /dev/null and b/samples/app_resources/Android/src/main/res/drawable-xxxhdpi/logo.png differ
diff --git a/demo/App_Resources/Android/src/main/res/values/ic_launcher_background.xml b/samples/app_resources/Android/src/main/res/values-v21/colors.xml
similarity index 52%
rename from demo/App_Resources/Android/src/main/res/values/ic_launcher_background.xml
rename to samples/app_resources/Android/src/main/res/values-v21/colors.xml
index c5d5899f..a64641a9 100644
--- a/demo/App_Resources/Android/src/main/res/values/ic_launcher_background.xml
+++ b/samples/app_resources/Android/src/main/res/values-v21/colors.xml
@@ -1,4 +1,4 @@
- #FFFFFF
+ #3d5afe
\ No newline at end of file
diff --git a/samples/app_resources/Android/src/main/res/values-v21/strings.xml b/samples/app_resources/Android/src/main/res/values-v21/strings.xml
new file mode 100644
index 00000000..a625ad4d
--- /dev/null
+++ b/samples/app_resources/Android/src/main/res/values-v21/strings.xml
@@ -0,0 +1,5 @@
+
+
+ NativeScript-Vue Application
+ NativeScript-Vue Application
+
diff --git a/demo/App_Resources/Android/src/main/res/values-v21/styles.xml b/samples/app_resources/Android/src/main/res/values-v21/styles.xml
similarity index 50%
rename from demo/App_Resources/Android/src/main/res/values-v21/styles.xml
rename to samples/app_resources/Android/src/main/res/values-v21/styles.xml
index 04d8a065..acff7c9c 100644
--- a/demo/App_Resources/Android/src/main/res/values-v21/styles.xml
+++ b/samples/app_resources/Android/src/main/res/values-v21/styles.xml
@@ -1,34 +1,23 @@
-
-
+
+
-
-
-
-
+
\ No newline at end of file
diff --git a/samples/app_resources/Android/src/main/res/values/colors.xml b/samples/app_resources/Android/src/main/res/values/colors.xml
new file mode 100644
index 00000000..74ad8829
--- /dev/null
+++ b/samples/app_resources/Android/src/main/res/values/colors.xml
@@ -0,0 +1,7 @@
+
+
+ #F5F5F5
+ #757575
+ #33B5E5
+ #272734
+
\ No newline at end of file
diff --git a/samples/app_resources/Android/src/main/res/values/strings.xml b/samples/app_resources/Android/src/main/res/values/strings.xml
new file mode 100644
index 00000000..a625ad4d
--- /dev/null
+++ b/samples/app_resources/Android/src/main/res/values/strings.xml
@@ -0,0 +1,5 @@
+
+
+ NativeScript-Vue Application
+ NativeScript-Vue Application
+
diff --git a/demo/App_Resources/Android/src/main/res/values/styles.xml b/samples/app_resources/Android/src/main/res/values/styles.xml
similarity index 93%
rename from demo/App_Resources/Android/src/main/res/values/styles.xml
rename to samples/app_resources/Android/src/main/res/values/styles.xml
index 4f91b610..fae0f4b7 100644
--- a/demo/App_Resources/Android/src/main/res/values/styles.xml
+++ b/samples/app_resources/Android/src/main/res/values/styles.xml
@@ -10,8 +10,8 @@
- @color/ns_accent
- @drawable/splash_screen
-
- - true
+
+ - true
- true
@@ -30,7 +30,7 @@
-
+