--- /dev/null
+<script src="../../dist/vue.global.js"></script>
+
+<div id="demo">
+ <h1>Latest Vue.js Commits</h1>
+ <template v-for="branch in branches">
+ <input type="radio"
+ :id="branch"
+ :value="branch"
+ name="branch"
+ v-model="currentBranch">
+ <label :for="branch">{{ branch }}</label>
+ </template>
+ <p>vuejs/vue@{{ currentBranch }}</p>
+ <ul>
+ <li v-for="{ html_url, sha, author, commit } in commits">
+ <a :href="html_url" target="_blank" class="commit">{{ sha.slice(0, 7) }}</a>
+ - <span class="message">{{ truncate(commit.message) }}</span><br>
+ by <span class="author"><a :href="author.html_url" target="_blank">{{ commit.author.name }}</a></span>
+ at <span class="date">{{ formatDate(commit.author.date) }}</span>
+ </li>
+ </ul>
+</div>
+
+<script>
+const API_URL = `https://api.github.com/repos/vuejs/vue-next/commits?per_page=3&sha=`
+
+const App = {
+ data: {
+ branches: ['master', 'sync'],
+ currentBranch: 'master',
+ commits: null
+ },
+
+ created() {
+ this.fetchData()
+ },
+
+ watch: {
+ currentBranch: 'fetchData'
+ },
+
+ methods: {
+ fetchData() {
+ fetch(`${API_URL}${this.currentBranch}`)
+ .then(res => res.json())
+ .then(data => {
+ this.commits = data
+ })
+ },
+ truncate(v) {
+ const newline = v.indexOf('\n')
+ return newline > 0 ? v.slice(0, newline) : v
+ },
+ formatDate(v) {
+ return v.replace(/T|Z/g, ' ')
+ }
+ }
+}
+
+Vue.createApp().mount(App, '#demo')
+</script>
+
+<style>
+ #demo {
+ font-family: 'Helvetica', Arial, sans-serif;
+ }
+ a {
+ text-decoration: none;
+ color: #f66;
+ }
+ li {
+ line-height: 1.5em;
+ margin-bottom: 20px;
+ }
+ .author, .date {
+ font-weight: bold;
+ }
+</style>
--- /dev/null
+<script src="../../dist/vue.global.js"></script>
+
+<!-- DemoGrid component template -->
+<script type="text/x-template" id="grid-template">
+ <table v-if="filteredData.length">
+ <thead>
+ <tr>
+ <th v-for="key in columns"
+ @click="sortBy(key)"
+ :class="{ active: sortKey == key }">
+ {{ capitalize(key) }}
+ <span class="arrow" :class="sortOrders[key] > 0 ? 'asc' : 'dsc'">
+ </span>
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr v-for="entry in filteredData">
+ <td v-for="key in columns">
+ {{entry[key]}}
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <p v-else>No matches found.</p>
+</script>
+<!-- DemoGrid component script -->
+<script>
+const DemoGrid = {
+ template: '#grid-template',
+ props: {
+ data: Array,
+ columns: Array,
+ filterKey: String
+ },
+ data() {
+ return {
+ sortKey: '',
+ sortOrders: this.columns.reduce((o, key) => (o[key] = 1, o), {})
+ }
+ },
+ computed: {
+ filteredData() {
+ const sortKey = this.sortKey
+ const filterKey = this.filterKey && this.filterKey.toLowerCase()
+ const order = this.sortOrders[sortKey] || 1
+ let data = this.data
+ if (filterKey) {
+ data = data.filter(row => {
+ return Object.keys(row).some(key => {
+ return String(row[key]).toLowerCase().indexOf(filterKey) > -1
+ })
+ })
+ }
+ if (sortKey) {
+ data = data.slice().sort((a, b) => {
+ a = a[sortKey]
+ b = b[sortKey]
+ return (a === b ? 0 : a > b ? 1 : -1) * order
+ })
+ }
+ return data
+ }
+ },
+ methods: {
+ sortBy(key) {
+ this.sortKey = key
+ this.sortOrders[key] = this.sortOrders[key] * -1
+ },
+ capitalize(str) {
+ return str.charAt(0).toUpperCase() + str.slice(1)
+ }
+ }
+}
+</script>
+
+<!-- App template (in DOM) -->
+<div id="demo">
+ <form id="search">
+ Search <input name="query" v-model="searchQuery">
+ </form>
+ <demo-grid
+ :data="gridData"
+ :columns="gridColumns"
+ :filter-key="searchQuery">
+ </demo-grid>
+</div>
+<!-- App script -->
+<script>
+const App = {
+ components: {
+ DemoGrid
+ },
+ data: {
+ searchQuery: '',
+ gridColumns: ['name', 'power'],
+ gridData: [
+ { name: 'Chuck Norris', power: Infinity },
+ { name: 'Bruce Lee', power: 9000 },
+ { name: 'Jackie Chan', power: 7000 },
+ { name: 'Jet Li', power: 8000 }
+ ]
+ }
+}
+
+Vue.createApp().mount(App, '#demo')
+</script>
+
+<style>
+body {
+ font-family: Helvetica Neue, Arial, sans-serif;
+ font-size: 14px;
+ color: #444;
+}
+
+table {
+ border: 2px solid #42b983;
+ border-radius: 3px;
+ background-color: #fff;
+}
+
+th {
+ background-color: #42b983;
+ color: rgba(255,255,255,0.66);
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+td {
+ background-color: #f9f9f9;
+}
+
+th, td {
+ min-width: 120px;
+ padding: 10px 20px;
+}
+
+th.active {
+ color: #fff;
+}
+
+th.active .arrow {
+ opacity: 1;
+}
+
+.arrow {
+ display: inline-block;
+ vertical-align: middle;
+ width: 0;
+ height: 0;
+ margin-left: 5px;
+ opacity: 0.66;
+}
+
+.arrow.asc {
+ border-left: 4px solid transparent;
+ border-right: 4px solid transparent;
+ border-bottom: 4px solid #fff;
+}
+
+.arrow.dsc {
+ border-left: 4px solid transparent;
+ border-right: 4px solid transparent;
+ border-top: 4px solid #fff;
+}
+</style>
--- /dev/null
+<script src="../../dist/vue.global.js"></script>
+<script>
+// math helper...
+function valueToPoint (value, index, total) {
+ var x = 0
+ var y = -value * 0.8
+ var angle = Math.PI * 2 / total * index
+ var cos = Math.cos(angle)
+ var sin = Math.sin(angle)
+ var tx = x * cos - y * sin + 100
+ var ty = x * sin + y * cos + 100
+ return {
+ x: tx,
+ y: ty
+ }
+}
+
+const AxisLabel = {
+ template: '<text :x="point.x" :y="point.y">{{stat.label}}</text>',
+ props: {
+ stat: Object,
+ index: Number,
+ total: Number
+ },
+ computed: {
+ point: function () {
+ return valueToPoint(
+ +this.stat.value + 10,
+ this.index,
+ this.total
+ )
+ }
+ }
+}
+</script>
+
+<!-- template for the polygraph component. -->
+<script type="text/x-template" id="polygraph-template">
+ <g>
+ <polygon :points="points"></polygon>
+ <circle cx="100" cy="100" r="80"></circle>
+ <axis-label
+ v-for="(stat, index) in stats"
+ :stat="stat"
+ :index="index"
+ :total="stats.length">
+ </axis-label>
+ </g>
+</script>
+
+<script>
+const Polygraph = {
+ props: ['stats'],
+ template: '#polygraph-template',
+ computed: {
+ // a computed property for the polygon's points
+ points() {
+ const total = this.stats.length
+ return this.stats.map((stat, i) => {
+ const point = valueToPoint(stat.value, i, total)
+ return point.x + ',' + point.y
+ }).join(' ')
+ }
+ },
+ components: {
+ AxisLabel
+ }
+}
+
+// math helper...
+function valueToPoint (value, index, total) {
+ var x = 0
+ var y = -value * 0.8
+ var angle = Math.PI * 2 / total * index
+ var cos = Math.cos(angle)
+ var sin = Math.sin(angle)
+ var tx = x * cos - y * sin + 100
+ var ty = x * sin + y * cos + 100
+ return {
+ x: tx,
+ y: ty
+ }
+}
+</script>
+
+<!-- demo root element -->
+<div id="demo">
+ <!-- Use the polygraph component -->
+ <svg width="200" height="200">
+ <polygraph :stats="stats"></polygraph>
+ </svg>
+ <!-- controls -->
+ <div v-for="stat in stats">
+ <label>{{stat.label}}</label>
+ <input type="range" v-model="stat.value" min="0" max="100">
+ <span>{{stat.value}}</span>
+ <button @click="remove(stat)" class="remove">X</button>
+ </div>
+ <form id="add">
+ <input name="newlabel" v-model="newLabel">
+ <button @click="add">Add a Stat</button>
+ </form>
+ <pre id="raw">{{ stats }}</pre>
+</div>
+
+<script>
+const App = {
+ components: {
+ Polygraph
+ },
+ data: {
+ newLabel: '',
+ stats: [
+ { label: 'A', value: 100 },
+ { label: 'B', value: 100 },
+ { label: 'C', value: 100 },
+ { label: 'D', value: 100 },
+ { label: 'E', value: 100 },
+ { label: 'F', value: 100 }
+ ]
+ },
+ methods: {
+ add(e) {
+ e.preventDefault()
+ if (!this.newLabel) return
+ this.stats.push({
+ label: this.newLabel,
+ value: 100
+ })
+ this.newLabel = ''
+ },
+ remove(stat) {
+ if (this.stats.length > 3) {
+ this.stats.splice(this.stats.indexOf(stat), 1)
+ } else {
+ alert('Can\'t delete more!')
+ }
+ }
+ }
+}
+
+Vue.createApp().mount(App, '#demo')
+</script>
+
+<style>
+body {
+ font-family: Helvetica Neue, Arial, sans-serif;
+}
+
+polygon {
+ fill: #42b983;
+ opacity: .75;
+}
+
+circle {
+ fill: transparent;
+ stroke: #999;
+}
+
+text {
+ font-family: Helvetica Neue, Arial, sans-serif;
+ font-size: 10px;
+ fill: #666;
+}
+
+label {
+ display: inline-block;
+ margin-left: 10px;
+ width: 20px;
+}
+
+#raw {
+ position: absolute;
+ top: 0;
+ left: 300px;
+}
+</style>
-<script src="../dist/vue.global.js"></script>
+<script src="../../dist/vue.global.js"></script>
<link rel="stylesheet" href="https://unpkg.com/todomvc-app-css/index.css">
<div id="app">
--- /dev/null
+<script src="../../dist/vue.global.js"></script>
+
+<div id="demo">
+ <h1>Latest Vue.js Commits</h1>
+ <template v-for="branch in branches">
+ <input type="radio"
+ :id="branch"
+ :value="branch"
+ name="branch"
+ v-model="currentBranch">
+ <label :for="branch">{{ branch }}</label>
+ </template>
+ <p>vuejs/vue@{{ currentBranch }}</p>
+ <ul>
+ <li v-for="{ html_url, sha, author, commit } in commits">
+ <a :href="html_url" target="_blank" class="commit">{{ sha.slice(0, 7) }}</a>
+ - <span class="message">{{ truncate(commit.message) }}</span><br>
+ by <span class="author"><a :href="author.html_url" target="_blank">{{ commit.author.name }}</a></span>
+ at <span class="date">{{ formatDate(commit.author.date) }}</span>
+ </li>
+ </ul>
+</div>
+
+<script>
+const { createApp, ref, watch } = Vue
+const API_URL = `https://api.github.com/repos/vuejs/vue-next/commits?per_page=3&sha=`
+
+const truncate = v => {
+ const newline = v.indexOf('\n')
+ return newline > 0 ? v.slice(0, newline) : v
+}
+
+const formatDate = v => v.replace(/T|Z/g, ' ')
+
+const App = {
+ setup() {
+ const currentBranch = ref('master')
+ const commits = ref(null)
+
+ watch(() => {
+ fetch(`${API_URL}${currentBranch.value}`)
+ .then(res => res.json())
+ .then(data => { commits.value = data })
+ })
+
+ return {
+ branches: ['master', 'sync'],
+ currentBranch,
+ commits,
+ truncate,
+ formatDate
+ }
+ }
+}
+
+createApp().mount(App, '#demo')
+</script>
+
+<style>
+ #demo {
+ font-family: 'Helvetica', Arial, sans-serif;
+ }
+ a {
+ text-decoration: none;
+ color: #f66;
+ }
+ li {
+ line-height: 1.5em;
+ margin-bottom: 20px;
+ }
+ .author, .date {
+ font-weight: bold;
+ }
+</style>
--- /dev/null
+<script src="../../dist/vue.global.js"></script>
+
+<!-- DemoGrid component template -->
+<script type="text/x-template" id="grid-template">
+ <table v-if="filteredData.length">
+ <thead>
+ <tr>
+ <th v-for="key in columns"
+ @click="sortBy(key)"
+ :class="{ active: sortKey == key }">
+ {{ capitalize(key) }}
+ <span class="arrow" :class="sortOrders[key] > 0 ? 'asc' : 'dsc'">
+ </span>
+ </th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr v-for="entry in filteredData">
+ <td v-for="key in columns">
+ {{entry[key]}}
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <p v-else>No matches found.</p>
+</script>
+<!-- DemoGrid component script -->
+<script>
+const { reactive, computed } = Vue
+
+const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1)
+
+const DemoGrid = {
+ template: '#grid-template',
+ props: {
+ data: Array,
+ columns: Array,
+ filterKey: String
+ },
+ setup(props) {
+ const state = reactive({
+ sortKey: '',
+ sortOrders: props.columns.reduce((o, key) => (o[key] = 1, o), {})
+ })
+
+ const filteredData = computed(() => {
+ let { data, filterKey } = props
+ if (filterKey) {
+ filterKey = filterKey.toLowerCase()
+ data = data.filter(row => {
+ return Object.keys(row).some(key => {
+ return String(row[key]).toLowerCase().indexOf(filterKey) > -1
+ })
+ })
+ }
+ const { sortKey } = state
+ if (sortKey) {
+ const order = state.sortOrders[sortKey]
+ data = data.slice().sort((a, b) => {
+ a = a[sortKey]
+ b = b[sortKey]
+ return (a === b ? 0 : a > b ? 1 : -1) * order
+ })
+ }
+ return data
+ })
+
+ function sortBy(key) {
+ state.sortKey = key
+ state.sortOrders[key] *= -1
+ }
+
+ return {
+ ...state,
+ filteredData,
+ sortBy,
+ capitalize
+ }
+ }
+}
+</script>
+
+<!-- App template (in DOM) -->
+<div id="demo">
+ <form id="search">
+ Search <input name="query" v-model="searchQuery">
+ </form>
+ <demo-grid
+ :data="gridData"
+ :columns="gridColumns"
+ :filter-key="searchQuery">
+ </demo-grid>
+</div>
+<!-- App script -->
+<script>
+const App = {
+ components: {
+ DemoGrid
+ },
+ data: {
+ searchQuery: '',
+ gridColumns: ['name', 'power'],
+ gridData: [
+ { name: 'Chuck Norris', power: Infinity },
+ { name: 'Bruce Lee', power: 9000 },
+ { name: 'Jackie Chan', power: 7000 },
+ { name: 'Jet Li', power: 8000 }
+ ]
+ }
+}
+
+Vue.createApp().mount(App, '#demo')
+</script>
+
+<style>
+body {
+ font-family: Helvetica Neue, Arial, sans-serif;
+ font-size: 14px;
+ color: #444;
+}
+
+table {
+ border: 2px solid #42b983;
+ border-radius: 3px;
+ background-color: #fff;
+}
+
+th {
+ background-color: #42b983;
+ color: rgba(255,255,255,0.66);
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+td {
+ background-color: #f9f9f9;
+}
+
+th, td {
+ min-width: 120px;
+ padding: 10px 20px;
+}
+
+th.active {
+ color: #fff;
+}
+
+th.active .arrow {
+ opacity: 1;
+}
+
+.arrow {
+ display: inline-block;
+ vertical-align: middle;
+ width: 0;
+ height: 0;
+ margin-left: 5px;
+ opacity: 0.66;
+}
+
+.arrow.asc {
+ border-left: 4px solid transparent;
+ border-right: 4px solid transparent;
+ border-bottom: 4px solid #fff;
+}
+
+.arrow.dsc {
+ border-left: 4px solid transparent;
+ border-right: 4px solid transparent;
+ border-top: 4px solid #fff;
+}
+</style>
--- /dev/null
+<script src="../../dist/vue.global.js"></script>
+<script>
+// math helper...
+function valueToPoint (value, index, total) {
+ var x = 0
+ var y = -value * 0.8
+ var angle = Math.PI * 2 / total * index
+ var cos = Math.cos(angle)
+ var sin = Math.sin(angle)
+ var tx = x * cos - y * sin + 100
+ var ty = x * sin + y * cos + 100
+ return {
+ x: tx,
+ y: ty
+ }
+}
+
+const AxisLabel = {
+ template: '<text :x="point.x" :y="point.y">{{stat.label}}</text>',
+ props: {
+ stat: Object,
+ index: Number,
+ total: Number
+ },
+ setup(props) {
+ return {
+ point: Vue.computed(() => valueToPoint(
+ +props.stat.value + 10,
+ props.index,
+ props.total
+ ))
+ }
+ }
+}
+</script>
+
+<!-- template for the polygraph component. -->
+<script type="text/x-template" id="polygraph-template">
+ <g>
+ <polygon :points="points"></polygon>
+ <circle cx="100" cy="100" r="80"></circle>
+ <axis-label
+ v-for="(stat, index) in stats"
+ :stat="stat"
+ :index="index"
+ :total="stats.length">
+ </axis-label>
+ </g>
+</script>
+
+<script>
+const Polygraph = {
+ props: ['stats'],
+ template: '#polygraph-template',
+ setup(props) {
+ return {
+ points: Vue.computed(() => {
+ const total = props.stats.length
+ return props.stats.map((stat, i) => {
+ const point = valueToPoint(stat.value, i, total)
+ return point.x + ',' + point.y
+ }).join(' ')
+ })
+ }
+ },
+ components: {
+ AxisLabel
+ }
+}
+</script>
+
+<!-- demo root element -->
+<div id="demo">
+ <!-- Use the polygraph component -->
+ <svg width="200" height="200">
+ <polygraph :stats="stats"></polygraph>
+ </svg>
+ <!-- controls -->
+ <div v-for="stat in stats">
+ <label>{{stat.label}}</label>
+ <input type="range" v-model="stat.value" min="0" max="100">
+ <span>{{stat.value}}</span>
+ <button @click="remove(stat)" class="remove">X</button>
+ </div>
+ <form id="add">
+ <input name="newlabel" v-model="newLabel">
+ <button @click="add">Add a Stat</button>
+ </form>
+ <pre id="raw">{{ stats }}</pre>
+</div>
+
+<script>
+const App = {
+ components: {
+ Polygraph
+ },
+ setup() {
+ const newLabel = Vue.ref('')
+ const stats = Vue.reactive([
+ { label: 'A', value: 100 },
+ { label: 'B', value: 100 },
+ { label: 'C', value: 100 },
+ { label: 'D', value: 100 },
+ { label: 'E', value: 100 },
+ { label: 'F', value: 100 }
+ ])
+
+ function add(e) {
+ e.preventDefault()
+ if (!newLabel.value) return
+ stats.push({
+ label: newLabel.value,
+ value: 100
+ })
+ newLabel.value = ''
+ }
+
+ function remove(stat) {
+ if (stats.length > 3) {
+ stats.splice(stats.indexOf(stat), 1)
+ } else {
+ alert('Can\'t delete more!')
+ }
+ }
+
+ return {
+ newLabel,
+ stats,
+ add,
+ remove
+ }
+ }
+}
+
+Vue.createApp().mount(App, '#demo')
+</script>
+
+<style>
+body {
+ font-family: Helvetica Neue, Arial, sans-serif;
+}
+
+polygon {
+ fill: #42b983;
+ opacity: .75;
+}
+
+circle {
+ fill: transparent;
+ stroke: #999;
+}
+
+text {
+ font-family: Helvetica Neue, Arial, sans-serif;
+ font-size: 10px;
+ fill: #666;
+}
+
+label {
+ display: inline-block;
+ margin-left: 10px;
+ width: 20px;
+}
+
+#raw {
+ position: absolute;
+ top: 0;
+ left: 300px;
+}
+</style>
-<script src="../dist/vue.global.js"></script>
+<script src="../../dist/vue.global.js"></script>
<link rel="stylesheet" href="https://unpkg.com/todomvc-app-css/index.css">
<div id="app">
<li><a href="#/active" :class="{ selected: state.visibility === 'active' }">Active</a></li>
<li><a href="#/completed" :class="{ selected: state.visibility === 'completed' }">Completed</a></li>
</ul>
-
+
<button class="clear-completed" @click="removeCompleted" v-if="state.todos.length > state.remaining">
Clear completed
</button>