Repository: vuesomedev/vue-3-playground
Branch: master
Commit: 819e2b720a2a
Files: 58
Total size: 44.7 KB
Directory structure:
gitextract_mp3wsnit/
├── .github/
│ └── FUNDING.yml
├── .gitignore
├── .travis.yml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── cypress-javascript.json
├── cypress-typescript.json
├── e2e/
│ ├── javascript/
│ │ └── index.test.js
│ └── typescript/
│ └── index.test.ts
├── index.html
├── package.json
├── public/
│ └── .nojekyll
├── sandbox.config.json
├── src/
│ ├── javascript/
│ │ ├── App.vue
│ │ ├── at-sign.js
│ │ ├── components/
│ │ │ ├── AsyncPayment.js
│ │ │ ├── Cart.vue
│ │ │ ├── Checkout.vue
│ │ │ ├── Coupon.test.js
│ │ │ ├── Coupon.vue
│ │ │ ├── Exchange.vue
│ │ │ ├── Header.vue
│ │ │ ├── Item.vue
│ │ │ ├── Payment.jsx
│ │ │ ├── Spinner.js
│ │ │ ├── Username.test.js
│ │ │ └── Username.vue
│ │ ├── custom-element.js
│ │ ├── hooks.js
│ │ ├── main.js
│ │ ├── router.js
│ │ ├── store.js
│ │ └── version.js
│ └── typescript/
│ ├── App.vue
│ ├── at-sign.ts
│ ├── components/
│ │ ├── AsyncPayment.ts
│ │ ├── Cart.vue
│ │ ├── Checkout.vue
│ │ ├── Coupon.test.ts
│ │ ├── Coupon.vue
│ │ ├── Exchange.vue
│ │ ├── Header.vue
│ │ ├── Item.vue
│ │ ├── Payment.tsx
│ │ ├── Spinner.ts
│ │ ├── Username.test.ts
│ │ └── Username.vue
│ ├── hooks.ts
│ ├── main.ts
│ ├── router.ts
│ ├── sfc.d.ts
│ ├── store.ts
│ └── version.ts
├── tsconfig.json
├── vite.config.js
└── vitest.config.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: blacksonic
open_collective: # Replace with a single Open Collective username
ko_fi: blacksonic
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
================================================
FILE: .gitignore
================================================
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- "14"
script: npm test
before_deploy: npm run build
deploy:
provider: pages
local_dir: dist
skip_cleanup: true
github_token: $GITHUB_TOKEN # Set in the settings page of your repository, as a secure variable
keep_history: true
on:
branch: master
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at soos.gabor86@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing
## Issues
Issues are very valuable to this project.
* Ideas are a valuable source of contributions others can make
* Problems show where this project is lacking
* With a question you show where contributors can improve the user experience
Thank you for creating them.
## Pull Requests
Pull requests are, a great way to get your ideas into this repository.
When deciding if I merge in a pull request I look at the following things:
### Does it state intent
You should be clear which problem you're trying to solve with your contribution.
For example:
> Add link to code of conduct in README.md
Doesn't tell me anything about why you're doing that
> Add link to code of conduct in README.md because users don't always look in the CONTRIBUTING.md
Tells me the problem that you have found, and the pull request shows me the action you have taken to solve it.
### Is it of good quality
* There are no spelling mistakes
* It reads well
* For english language contributions: Has a good score on [Grammarly](grammarly.com) or [Hemingway App](http://www.hemingwayapp.com/)
### Does it move this repository closer to my vision for the repository
The aim of this repository is:
* To provide a README.md and assorted documents anyone can copy and paste, into their project
* The content is usable by someone who hasn't written something like this before
* Foster a culture of respect and gratitude in the open source community.
### Does it follow the contributor covenant
This repository has a [code of conduct](CODE_OF_CONDUCT.md), This repository has a code of conduct, I will remove things that do not respect it.
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2019 Gábor Soós
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Vue 3 Playground
Vue 3 Playground stands as a learning project for those who want to get familiar with [the new features of Vue 3](https://composition-api.vuejs.org/).
[The official documentation](https://v3.vuejs.org/) is now available!
The project aims to include all the new features. If you find something missing please create an issue/PR.
The application is a shopping cart where you can alter the name, price and quantity of the products and
recalculates the total price based on the items and used coupon.
The playground is available also [in online version](https://codesandbox.io/s/github/vuesomedev/vue-3-playground).
If you want to switch to the Typescript folder uncomment the link to the Typescript main file in `index.html`
and comment out the Javascript main file.

### New APIs covered
The Typescript equivalents can be found in the `src-typescript` folder (file names are the same).
- [createApp](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/main.js) - [API docs](https://v3.vuejs.org/api/application-api.html)
- [mount](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/main.js) - [API docs](https://v3.vuejs.org/api/application-api.html#mount)
- [use](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/main.js) - [API docs](https://v3.vuejs.org/api/application-api.html#use)
- [ref](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/hooks.js) - [API docs](https://v3.vuejs.org/api/refs-api.html#ref)
- [reactive](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/hooks.js) - [API docs](https://v3.vuejs.org/api/basic-reactivity.html#reactive)
- [computed](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/hooks.js) - [API docs](https://v3.vuejs.org/api/computed-watch-api.html#computed)
- [toRefs](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/components/Cart.vue) - [Composition API docs](https://composition-api.vuejs.org/#code-organization)
- [watchEffect](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/components/Cart.vue) - [API docs](https://v3.vuejs.org/api/computed-watch-api.html#watcheffect)
- [watch](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/components/Cart.vue) - [API docs](https://v3.vuejs.org/api/computed-watch-api.html#watch)
- [onMount](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/components/Cart.vue) - [API docs](https://v3.vuejs.org/api/composition-api.html#lifecycle-hooks)
- [onUnmount](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/components/Cart.vue) - [API docs](https://v3.vuejs.org/api/composition-api.html#lifecycle-hooks)
- [onUpdate](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/components/Cart.vue) - [API docs](https://v3.vuejs.org/api/composition-api.html#lifecycle-hooks)
- [onErrorCaptured](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/components/Cart.vue) - [API docs](https://v3.vuejs.org/api/composition-api.html#lifecycle-hooks)
- [useStore](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/App.vue) - [Vuex 4 docs](https://github.com/vuejs/vuex/tree/4.0)
- [useRoute](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/components/Checkout.vue) - [Vue 3 router docs](https://github.com/vuejs/vue-router-next)
- [emit](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/components/Coupon.vue)
- [provide](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/version.js) - [API docs](https://v3.vuejs.org/api/composition-api.html#provide-inject)
- [inject](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/version.js) - [API docs](https://v3.vuejs.org/api/composition-api.html#provide-inject)
- [createStore](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/store.js) - [Vuex 4 docs](https://github.com/vuejs/vuex/tree/4.0)
- [createRouter](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/router.js) - [Vue 3 router docs](https://github.com/vuejs/vue-router-next)
- [defineComponent](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/components/Payment.jsx) - [API docs](https://v3.vuejs.org/api/global-api.html#definecomponent)
- [defineAsyncComponent](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/components/AsyncPayment.js) - [API docs](https://v3.vuejs.org/api/global-api.html#defineasynccomponent)
- [h](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/components/Spinner.js) - [API docs](https://v3.vuejs.org/guide/render-function.html)
- [JSX](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/components/Payment.jsx)
- [Suspense](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/components/Cart.vue)
- [Async Component](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/components/Exchange.vue)
- [Teleport](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/components/Header.vue) - [API docs](https://v3.vuejs.org/guide/teleport.html#using-with-vue-components)
- [Fragments](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/App.vue)
- [Multiple v-models](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/components/Cart.vue) - [RFC docs](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0011-v-model-api-change.md)
- [Scoped slot](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/components/Cart.vue) - [RFC docs](https://github.com/vuejs/rfcs/blob/master/active-rfcs/0001-new-slot-syntax.md)
- [Custom directive](https://github.com/vuesomedev/vue-3-playground/blob/master/src/javascript/at-sign.js)
### Further articles/videos
- [Official documentation](https://v3.vuejs.org/)
- [Composition API docs](https://composition-api.vuejs.org/)
- [RFCs describing new features](https://github.com/vuejs/rfcs)
- [Reactivity: Vue 2 vs Vue 3](https://www.vuemastery.com/blog/Reactivity-Vue2-vs-Vue3/)
- [Global Vue Meetup featuring Evan You](https://www.youtube.com/watch?v=Nk3cC7xNfkk)
- [State of Vuenion](https://www.vuemastery.com/conferences/vueconf-us-2020/state-of-the-vuenion/)
- [The process: Making Vue 3](https://increment.com/frontend/making-vue-3/)
- [Vue 3 Async Components and Bundle Splitting](https://lmiller1990.github.io/electic/posts/20200503_vue_3_async_components_and_bundle_splitting.html)
- [Reactivity in Vue 2, 3, and the Composition API](https://vuejsdevelopers.com/2017/03/05/vue-js-reactivity/)
- [React Hooks vs. Vue 3 Composition API](https://academy.esveo.com/en/blog/Yr)
- [Vue 3 Composition API TodoMVC implementation with Vuex and testing](https://github.com/vuesomedev/todomvc-vue-composition-api)
### Contributing
If you have any idea how to include more and more new features of Vue 3 in this application feel free to share your ideas in an issue/PR.
================================================
FILE: cypress-javascript.json
================================================
{
"baseUrl": "http://localhost:3000",
"integrationFolder": "e2e/javascript",
"pluginsFile": false,
"supportFile": false,
"video": false
}
================================================
FILE: cypress-typescript.json
================================================
{
"baseUrl": "http://localhost:3000",
"integrationFolder": "e2e/typescript",
"pluginsFile": false,
"supportFile": false,
"video": false
}
================================================
FILE: e2e/javascript/index.test.js
================================================
describe('Vue 3 Playground', () => {
it('should display playground text on page', () => {
cy.visit('/');
cy.contains('h2', 'Vue 3.2.33 Playground');
});
});
================================================
FILE: e2e/typescript/index.test.ts
================================================
describe('Vue 3 Playground', () => {
it('should display playground text on page', () => {
cy.visit('/');
cy.contains('h2', 'Vue 3.2.33 Playground');
});
});
================================================
FILE: index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 3 Playground</title>
<link rel="icon" href="./logo.png" type="image/png">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css">
<style>
.container {
max-width: 960px;
}
.border-top { border-top: 1px solid #e5e5e5; }
.border-bottom { border-bottom: 1px solid #e5e5e5; }
.border-top-gray { border-top-color: #adb5bd; }
.box-shadow { box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); }
.lh-condensed { line-height: 1.25; }
</style>
</head>
<body class="bg-light">
<div class="container">
<div id="app"></div>
<footer class="my-5 pt-5 text-muted text-center text-small" id="footer"></footer>
</div>
<script type="module" src="./src/javascript/main.js"></script>
<!-- <script type="module" src="./src/typescript/main.ts"></script> -->
</body>
</html>
================================================
FILE: package.json
================================================
{
"name": "vue-3-playground",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/blacksonic/vue-3-playground.git"
},
"type": "module",
"scripts": {
"start": "vite --port 3000",
"serve": "vite --port 3000",
"build": "vite build",
"test": "npm run test:unit:javascript && npm run test:unit:typescript && npm run test:e2e:javascript && npm run test:e2e:typescript",
"test:unit:javascript": "vitest --dir src/javascript --run",
"test:unit:typescript": "vitest --dir src/typescript --run",
"test:e2e:javascript": "start-server-and-test start http-get://localhost:3000 e2e:javascript",
"test:e2e:typescript": "start-server-and-test start http-get://localhost:3000 e2e:typescript",
"e2e:javascript": "cypress run --config-file cypress-javascript.json",
"e2e:typescript": "cypress run --config-file cypress-typescript.json"
},
"dependencies": {
"vue": "^3.4.19",
"vue-router": "^4.3.0",
"vuex": "^4.1.0"
},
"devDependencies": {
"@babel/core": "^7.23.9",
"@babel/preset-env": "^7.23.9",
"@vitejs/plugin-vue": "^5.0.4",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"@vue/compiler-sfc": "^3.4.19",
"@vue/test-utils": "^2.4.4",
"cypress": "^13.6.6",
"jsdom": "^24.0.0",
"start-server-and-test": "^2.0.3",
"typescript": "^5.3.3",
"vite": "^5.1.4",
"vitest": "^1.3.1"
},
"babel": {
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "current"
}
}
]
]
},
"engines": {
"node": "20"
}
}
================================================
FILE: public/.nojekyll
================================================
================================================
FILE: sandbox.config.json
================================================
{
"infiniteLoopProtection": true,
"hardReloadOnChange": false,
"view": "browser",
"server": "true",
"container": {
"port": 3000,
"node": "14"
},
"template": "node",
"node": "14"
}
================================================
FILE: src/javascript/App.vue
================================================
<script>
import { defineComponent, onMounted } from 'vue';
import { useStore } from 'vuex';
import Header from './components/Header.vue';
import { provideVersion } from './version';
export default defineComponent({
name: 'App',
components: { Header },
setup() {
const { dispatch } = useStore();
provideVersion();
onMounted(() => {
dispatch('onSetYear', new Date().getFullYear());
});
}
});
</script>
<template>
<Header />
<router-view />
</template>
================================================
FILE: src/javascript/at-sign.js
================================================
export default {
beforeMount(el, binding, vnode, prevVnode) {
console.log(`directive: value - ${binding.value}, argument - ${binding.arg}, modifiers - ${JSON.stringify(binding.modifiers)}`);
el.innerHTML = '<span class="input-group-text">@</span>';
},
mounted() {},
beforeUpdate() {},
updated() {},
beforeUnmount() {},
unmounted() {}
};
================================================
FILE: src/javascript/components/AsyncPayment.js
================================================
import { defineAsyncComponent } from 'vue';
import Spinner from './Spinner';
export default defineAsyncComponent({
loader: () => import('./Payment.jsx'),
loadingComponent: Spinner,
delay: 0,
onError(error, retry, fail, attempts) {
console.log('Retrying to load component', error);
retry();
}
});
================================================
FILE: src/javascript/components/Cart.vue
================================================
<script>
import { toRefs, onMounted, onUpdated, onUnmounted, onErrorCaptured, watchEffect, watch, ref, defineComponent } from 'vue';
import Coupon from './Coupon.vue';
import Item from './Item.vue';
import Exchange from './Exchange.vue';
import AsyncPayment from './AsyncPayment';
import Spinner from './Spinner';
import Username from './Username.vue';
import { useCart } from '../hooks';
export default defineComponent({
name: 'Cart',
components: { Coupon, Item, Exchange, AsyncPayment, Spinner, Username },
setup() {
console.log('setup');
const { product, secondName, secondPrice, secondQuantity, total, coupon, setCoupon } = useCart();
watchEffect(() => console.log(`watchEffect: ${product.firstName} ${product.firstPrice}x${product.firstQuantity}`));
watch([secondName], () => console.log(`watch: ${secondName.value} ${secondPrice.value}x${secondQuantity.value}`));
onMounted(() => {
console.log('mounted');
});
onUpdated(() => console.log('updated'));
onUnmounted(() => console.log('onunmounted'));
const error = ref(null);
onErrorCaptured(e => {
error.value = e;
return true;
});
return { ...toRefs(product), secondName, secondPrice, secondQuantity, total, coupon, setCoupon, error };
}
});
</script>
<template>
<div class="row">
<div class="col-md-4 order-md-2 mb-4">
<h4 class="d-flex justify-content-between align-items-center mb-3">
<span class="text-muted">Cart</span>
</h4>
<ul class="list-group mb-3">
<li class="list-group-item d-flex justify-content-between lh-condensed">
<div>
<h6 class="my-0">{{ firstName }}</h6>
</div>
<span class="text-muted">${{ firstPrice * firstQuantity }}</span>
</li>
<li class="list-group-item d-flex justify-content-between lh-condensed">
<div>
<h6 class="my-0">{{ secondName }}</h6>
</div>
<span class="text-muted">${{ secondPrice * secondQuantity }}</span>
</li>
<li class="list-group-item d-flex justify-content-between">
<span>Total (USD)</span>
<strong>${{ total }}</strong>
</li>
</ul>
<Coupon :percent="10" @redeem="setCoupon" />
<div v-if="error">
{{ error }}
</div>
<Suspense v-else>
<template #default>
<Exchange />
</template>
<template #fallback>
<Spinner />
</template>
</Suspense>
</div>
<div class="col-md-8 order-md-1">
<h4 class="mb-3">Items</h4>
<form class="needs-validation">
<Item v-model:name="firstName" v-model:price="firstPrice" v-model:quantity="firstQuantity" />
<div class="row">
<div class="col-md-6 mb-3">
<label for="second-item-name">Name</label>
<input v-model="secondName" type="text" class="form-control" id="second-item-name">
</div>
<div class="col-md-3 mb-3">
<label for="second-item-price">Price</label>
<input v-model="secondPrice" type="number" class="form-control" id="second-item-price">
</div>
<div class="col-md-3 mb-3">
<label for="second-item-quantity">Quantity</label>
<input v-model="secondQuantity" type="number" class="form-control" id="second-item-quantity">
</div>
</div>
</form>
<Username v-slot="{ label }">
<label for="username">{{ label }}</label>
</Username>
<h4 class="mb-3">Payment</h4>
<AsyncPayment />
<hr class="mb-4">
<router-link to="/checkout" class="btn btn-primary btn-lg btn-block">Continue to checkout</router-link>
</div>
</div>
</template>
<style>
@keyframes rotate {
to { transform: rotate(360deg); }
}
.spinner-border {
display: inline-block;
width: 2rem;
height: 2rem;
vertical-align: text-bottom;
border: .25em solid currentColor;
border-right-color: transparent;
border-radius: 50%;
-webkit-animation: spinner-border .75s linear infinite;
animation: rotate 1s linear infinite;
}
</style>
================================================
FILE: src/javascript/components/Checkout.vue
================================================
<script>
import { defineComponent } from 'vue';
import { useRoute } from 'vue-router';
export default defineComponent({
name: 'Checkout',
setup() {
const { path } = useRoute();
return { path };
}
});
</script>
<template>
<div class="row">
<div class="col-md-8 order-md-1">
<h4 class="mb-3">Checkout</h4>
<p>{{ path }}</p>
<router-link to="/" class="btn btn-primary btn-lg btn-block">Back to cart</router-link>
</div>
</div>
</template>
================================================
FILE: src/javascript/components/Coupon.test.js
================================================
import { mount } from '@vue/test-utils';
import Coupon from './Coupon.vue';
describe('Coupon', () => {
it('should trigger coupon percent on button click', () => {
const wrapper = mount(Coupon, { props: { percent: 10 } });
wrapper.find('.btn').trigger('click');
expect(wrapper.emitted().redeem).toHaveLength(1);
expect(wrapper.emitted().redeem[0]).toEqual([10]);
});
});
================================================
FILE: src/javascript/components/Coupon.vue
================================================
<script>
import { defineComponent, ref } from 'vue';
export default defineComponent({
name: 'Coupon',
props: ['percent'],
emits: ['redeem'],
setup(props, { emit }) {
const percent = ref(props.percent);
const useCoupon = () => emit('redeem', percent.value);
return { percent, useCoupon };
}
});
</script>
<template>
<form class="card p-2">
<div class="input-group">
<input v-model="percent" type="text" class="form-control" placeholder="Promo code">
<div class="input-group-append">
<button @click="useCoupon" type="button" class="btn btn-secondary">Redeem</button>
</div>
</div>
</form>
</template>
================================================
FILE: src/javascript/components/Exchange.vue
================================================
<script>
import { defineComponent } from 'vue';
export default defineComponent({
async setup() {
const exchangeRates = await Promise.resolve({
base: 'EUR',
date: '2021-05-08',
rates: {
GBP: 0.870389,
USD: 1.216325,
}
});
return { rates: exchangeRates.rates };
}
});
</script>
<template>
<ul class="list-group">
<li class="list-group-item">Change to USD: {{ rates.USD }}</li>
<li class="list-group-item">Change to GBP: {{ rates.GBP }} </li>
</ul>
</template>
================================================
FILE: src/javascript/components/Header.vue
================================================
<script>
import { defineComponent } from 'vue';
import { useStore } from 'vuex';
import { useVersion } from '../version';
export default defineComponent({
name: 'Header',
setup() {
const { state } = useStore();
return {
version: useVersion(),
state
};
}
});
</script>
<template>
<div class="py-5 text-center">
<img class="d-block mx-auto mb-4" src="/logo.png" alt="" width="72" height="72">
<h2>Vue {{ version }} Playground</h2>
<p class="lead">
Below is an example form built with the Vue {{ version }} Composition API.
</p>
</div>
<Teleport to="#footer">
<p class="mb-1">© {{ state.year }} <x-username /></p>
</Teleport>
</template>
================================================
FILE: src/javascript/components/Item.vue
================================================
<script>
import { defineComponent } from 'vue';
export default defineComponent({
name: 'Item',
props: ['name', 'price', 'quantity'],
emits: ['update:name', 'update:price', 'update:quantity'],
setup(props, { emit }) {
const updateName = value => emit('update:name', value);
const updatePrice = value => emit('update:price', value);
const updateQuantity = value => emit('update:quantity', value);
return { updateName, updatePrice, updateQuantity };
}
});
</script>
<template>
<div class="row">
<div class="col-md-6 mb-3">
<label for="item-name">Name</label>
<input :value="name" @input="updateName($event.target.value)" type="text" class="form-control" id="item-name">
</div>
<div class="col-md-3 mb-3">
<label for="item-price">Price</label>
<input :value="price" @input="updatePrice($event.target.value)" type="number" class="form-control" id="item-price">
</div>
<div class="col-md-3 mb-3">
<label for="item-quantity">Quantity</label>
<input :value="quantity" @input="updateQuantity($event.target.value)" type="number" class="form-control" id="item-quantity">
</div>
</div>
</template>
================================================
FILE: src/javascript/components/Payment.jsx
================================================
import { defineComponent } from 'vue';
export default defineComponent({
name: 'Payment',
setup() {
const payments = [
{ id: 'credit', name: 'Credit card' },
{ id: 'debit', name: 'Debit card' },
{ id: 'paypal', name: 'Paypal' },
];
return { payments };
},
render() {
return (
<div className="d-block my-3">
{this.payments.map(payment => (
<div className="custom-control custom-radio">
<input id={payment.id} name="paymentMethod" type="radio" className="custom-control-input" />
<label className="custom-control-label" htmlFor={payment.id}>{payment.name}</label>
</div>
))}
</div>
);
}
});
================================================
FILE: src/javascript/components/Spinner.js
================================================
import { defineComponent, h } from 'vue';
export default defineComponent({
name: 'Spinner',
render() {
return h(
'div', { class: 'spinner-border', role: 'status' },
[
h('span', { class: 'sr-only' }, 'Loading...')
]
);
}
});
================================================
FILE: src/javascript/components/Username.test.js
================================================
import { mount } from '@vue/test-utils';
import Username from './Username.vue';
describe('Username', () => {
it('should display required text', () => {
const wrapper = mount(Username);
expect(wrapper.find('.invalid-feedback').text()).toEqual('Your username is required.');
});
});
================================================
FILE: src/javascript/components/Username.vue
================================================
<template>
<div class="mb-3">
<slot :label="label">
<label for="username">Default label</label>
</slot>
<div class="input-group">
<div class="input-group-prepend"></div>
<input type="text" class="form-control" id="username" placeholder="Username" />
<div class="invalid-feedback" style="width: 100%;">
Your username is required.
</div>
</div>
</div>
</template>
<script>
import { defineComponent } from 'vue';
export default defineComponent({
name: 'Username',
setup() {
return { label: 'Username' };
}
});
</script>
================================================
FILE: src/javascript/custom-element.js
================================================
export class XUsername extends HTMLElement {
connectedCallback() {
this.innerText = 'blacksonic';
}
}
customElements.define('x-username', XUsername);
================================================
FILE: src/javascript/hooks.js
================================================
import { reactive, ref, computed, readonly } from 'vue';
export const useCart = () => {
const product = reactive({ firstName: 'First Product', firstPrice: 10, firstQuantity: 1 });
const secondName = ref('Second Product');
const secondPrice = ref(20);
const secondQuantity = ref(2);
const total = computed(() => (product.firstPrice * product.firstQuantity + secondPrice.value * secondQuantity.value) * (100 - coupon.value) / 100);
const coupon = ref(10);
const setCoupon = percent => coupon.value = percent;
return { product, secondName, secondPrice, secondQuantity, total, coupon, setCoupon };
};
================================================
FILE: src/javascript/main.js
================================================
import { createApp } from 'vue';
import App from './App.vue';
import store from './store';
import router from './router';
import AtSign from './at-sign';
import './custom-element';
const app = createApp(App);
app.config.isCustomElement = tag => /^x-/.test(tag);
app.use(store).use(router);
app.directive('AtSign', AtSign);
app.mount('#app');
================================================
FILE: src/javascript/router.js
================================================
import { createRouter, createWebHashHistory } from 'vue-router';
import Cart from './components/Cart.vue';
import Checkout from './components/Checkout.vue';
export default createRouter({
history: createWebHashHistory(),
routes: [
{ path: '/', name: 'cart', component: Cart },
{ path: '/checkout', name: 'checkout', component: Checkout },
],
});
================================================
FILE: src/javascript/store.js
================================================
import { createStore } from 'vuex';
export default createStore({
state: {
year: '1970'
},
actions: {
onSetYear: ({ commit }, year) => {
commit('setYear', year);
}
},
mutations: {
setYear: (state, year) => {
state.year = year;
}
}
});
================================================
FILE: src/javascript/version.js
================================================
import { version, provide, inject } from 'vue';
const versionSymbol = Symbol('version');
export const provideVersion = () => provide(versionSymbol, version);
export const useVersion = () => inject(versionSymbol);
================================================
FILE: src/typescript/App.vue
================================================
<script lang="ts">
import { defineComponent, onMounted } from 'vue';
import { useStore } from 'vuex';
import Header from './components/Header.vue';
import { provideVersion } from './version';
import { State } from './store';
export default defineComponent({
name: 'App',
components: { Header },
setup() {
const { dispatch } = useStore<State>();
provideVersion();
onMounted(() => {
dispatch('onSetYear', new Date().getFullYear());
});
}
});
</script>
<template>
<Header />
<router-view />
</template>
================================================
FILE: src/typescript/at-sign.ts
================================================
import { DirectiveBinding, VNode } from 'vue';
export default {
beforeMount(el: HTMLElement, binding: DirectiveBinding, vnode: VNode, prevVnode: VNode | null) {
console.log(`directive: value - ${binding.value}, argument - ${binding.arg}, modifiers - ${JSON.stringify(binding.modifiers)}`);
el.innerHTML = '<span class="input-group-text">@</span>';
},
mounted() {},
beforeUpdate() {},
updated() {},
beforeUnmount() {},
unmounted() {}
};
================================================
FILE: src/typescript/components/AsyncPayment.ts
================================================
import { defineAsyncComponent } from 'vue';
import Spinner from './Spinner';
export default defineAsyncComponent({
loader: () => import('./Payment.tsx'),
loadingComponent: Spinner,
delay: 0,
onError(error: Error, retry: () => void, fail: () => void, attempts: number) {
console.log('Retrying to load component', error);
retry();
}
});
================================================
FILE: src/typescript/components/Cart.vue
================================================
<script lang="ts">
import { toRefs, onMounted, onUpdated, onUnmounted, onErrorCaptured, watchEffect, watch, ref, defineComponent } from 'vue';
import Coupon from './Coupon.vue';
import Item from './Item.vue';
import Exchange from './Exchange.vue';
import AsyncPayment from './AsyncPayment';
import Spinner from './Spinner';
import Username from './Username.vue';
import { useCart } from '../hooks';
export default defineComponent({
name: 'Cart',
components: { Coupon, Item, Exchange, AsyncPayment, Spinner, Username },
setup() {
console.log('setup');
const { product, secondName, secondPrice, secondQuantity, total, coupon, setCoupon } = useCart();
watchEffect(() => console.log(`watchEffect: ${product.firstName} ${product.firstPrice}x${product.firstQuantity}`));
watch([secondName], () => console.log(`watch: ${secondName.value} ${secondPrice.value}x${secondQuantity.value}`));
onMounted(() => {
console.log('mounted');
});
onUpdated(() => console.log('updated'));
onUnmounted(() => console.log('onunmounted'));
const error = ref<Error | null>(null);
onErrorCaptured((e: Error) => {
error.value = e;
return true;
});
return { ...toRefs(product), secondName, secondPrice, secondQuantity, total, coupon, setCoupon, error };
}
});
</script>
<template>
<div class="row">
<div class="col-md-4 order-md-2 mb-4">
<h4 class="d-flex justify-content-between align-items-center mb-3">
<span class="text-muted">Cart</span>
</h4>
<ul class="list-group mb-3">
<li class="list-group-item d-flex justify-content-between lh-condensed">
<div>
<h6 class="my-0">{{ firstName }}</h6>
</div>
<span class="text-muted">${{ firstPrice * firstQuantity }}</span>
</li>
<li class="list-group-item d-flex justify-content-between lh-condensed">
<div>
<h6 class="my-0">{{ secondName }}</h6>
</div>
<span class="text-muted">${{ secondPrice * secondQuantity }}</span>
</li>
<li class="list-group-item d-flex justify-content-between">
<span>Total (USD)</span>
<strong>${{ total }}</strong>
</li>
</ul>
<Coupon :percent="10" @redeem="setCoupon" />
<div v-if="error">
{{ error }}
</div>
<Suspense v-else>
<template #default>
<Exchange />
</template>
<template #fallback>
<Spinner />
</template>
</Suspense>
</div>
<div class="col-md-8 order-md-1">
<h4 class="mb-3">Items</h4>
<form class="needs-validation">
<Item v-model:name="firstName" v-model:price="firstPrice" v-model:quantity="firstQuantity" />
<div class="row">
<div class="col-md-6 mb-3">
<label for="second-item-name">Name</label>
<input v-model="secondName" type="text" class="form-control" id="second-item-name">
</div>
<div class="col-md-3 mb-3">
<label for="second-item-price">Price</label>
<input v-model="secondPrice" type="number" class="form-control" id="second-item-price">
</div>
<div class="col-md-3 mb-3">
<label for="second-item-quantity">Quantity</label>
<input v-model="secondQuantity" type="number" class="form-control" id="second-item-quantity">
</div>
</div>
</form>
<Username v-slot="{ label }">
<label for="username">{{ label }}</label>
</Username>
<h4 class="mb-3">Payment</h4>
<AsyncPayment />
<hr class="mb-4">
<router-link to="/checkout" class="btn btn-primary btn-lg btn-block">Continue to checkout</router-link>
</div>
</div>
</template>
<style>
@keyframes rotate {
to { transform: rotate(360deg); }
}
.spinner-border {
display: inline-block;
width: 2rem;
height: 2rem;
vertical-align: text-bottom;
border: .25em solid currentColor;
border-right-color: transparent;
border-radius: 50%;
-webkit-animation: spinner-border .75s linear infinite;
animation: rotate 1s linear infinite;
}
</style>
================================================
FILE: src/typescript/components/Checkout.vue
================================================
<script lang="ts">
import { defineComponent } from 'vue';
import { useRoute } from 'vue-router';
export default defineComponent({
name: 'Checkout',
setup() {
const { path } = useRoute();
return { path };
}
});
</script>
<template>
<div class="row">
<div class="col-md-8 order-md-1">
<h4 class="mb-3">Checkout</h4>
<p>{{ path }}</p>
<router-link to="/" class="btn btn-primary btn-lg btn-block">Back to cart</router-link>
</div>
</div>
</template>
================================================
FILE: src/typescript/components/Coupon.test.ts
================================================
import { mount } from '@vue/test-utils';
import Coupon from './Coupon.vue';
describe('Coupon', () => {
it('should trigger coupon percent on button click', () => {
const wrapper = mount(Coupon, { props: { percent: 10 } });
wrapper.find<HTMLButtonElement>('.btn').trigger('click');
expect(wrapper.emitted().redeem).toHaveLength(1);
expect(wrapper.emitted().redeem[0]).toEqual([10]);
});
});
================================================
FILE: src/typescript/components/Coupon.vue
================================================
<script lang="ts">
import { defineComponent, ref, SetupContext } from 'vue';
type EmitOption = 'redeem';
type Props = { percent: number };
export default defineComponent({
name: 'Coupon',
props: ['percent'],
emits: ['redeem'],
setup(props: Props, { emit }: SetupContext<EmitOption[]>) {
const percent = ref(props.percent);
const useCoupon = () => emit('redeem', percent.value);
return { percent, useCoupon };
}
});
</script>
<template>
<form class="card p-2">
<div class="input-group">
<input v-model="percent" type="text" class="form-control" placeholder="Promo code">
<div class="input-group-append">
<button @click="useCoupon" type="button" class="btn btn-secondary">Redeem</button>
</div>
</div>
</form>
</template>
================================================
FILE: src/typescript/components/Exchange.vue
================================================
<script lang="ts">
import { defineComponent } from 'vue';
interface ApiResponse {
date: string,
base: string,
rates: {
USD: number,
GBP: number
}
}
export default defineComponent({
async setup() {
const exchangeRates: ApiResponse = await Promise.resolve({
base: 'EUR',
date: '2021-05-08',
rates: {
GBP: 0.870389,
USD: 1.216325,
}
});
return { rates: exchangeRates.rates };
}
});
</script>
<template>
<ul class="list-group">
<li class="list-group-item">Change to USD: {{ rates.USD }}</li>
<li class="list-group-item">Change to GBP: {{ rates.GBP }} </li>
</ul>
</template>
================================================
FILE: src/typescript/components/Header.vue
================================================
<script lang="ts">
import { defineComponent } from 'vue';
import { useVersion } from '../version';
import { useStore } from 'vuex';
import { State } from '../store';
export default defineComponent({
name: 'Header',
setup() {
const { state } = useStore<State>();
return {
version: useVersion(),
state
};
}
});
</script>
<template>
<div class="py-5 text-center">
<img class="d-block mx-auto mb-4" src="../../../logo.png" alt="" width="72" height="72">
<h2>Vue {{ version }} Playground</h2>
<p class="lead">
Below is an example form built with the Vue {{ version }} Composition API.
</p>
</div>
<Teleport to="#footer">
<p class="mb-1">© {{ state.year }} blacksonic</p>
</Teleport>
</template>
================================================
FILE: src/typescript/components/Item.vue
================================================
<script lang="ts">
import { defineComponent, SetupContext } from 'vue';
type EmitOption = 'update:name' | 'update:price' | 'update:quantity';
type Props = { name: string, prices: number, quantity: number };
export default defineComponent({
name: 'Item',
props: ['name', 'price', 'quantity'],
emits: ['update:name', 'update:price', 'update:quantity'],
setup(props: Props, { emit }: SetupContext<EmitOption[]>) {
const updateName = value => emit('update:name', value);
const updatePrice = value => emit('update:price', value);
const updateQuantity = value => emit('update:quantity', value);
return { updateName, updatePrice, updateQuantity };
}
});
</script>
<template>
<div class="row">
<div class="col-md-6 mb-3">
<label for="item-name">Name</label>
<input :value="name" @input="updateName($event.target.value)" type="text" class="form-control" id="item-name">
</div>
<div class="col-md-3 mb-3">
<label for="item-price">Price</label>
<input :value="price" @input="updatePrice($event.target.value)" type="number" class="form-control" id="item-price">
</div>
<div class="col-md-3 mb-3">
<label for="item-quantity">Quantity</label>
<input :value="quantity" @input="updateQuantity($event.target.value)" type="number" class="form-control" id="item-quantity">
</div>
</div>
</template>
================================================
FILE: src/typescript/components/Payment.tsx
================================================
import { defineComponent } from 'vue';
export default defineComponent({
name: 'Payment',
setup() {
const payments = [
{ id: 'credit', name: 'Credit card' },
{ id: 'debit', name: 'Debit card' },
{ id: 'paypal', name: 'Paypal' },
];
return { payments };
},
render() {
return (
<div className="d-block my-3">
{this.payments.map(payment => (
<div className="custom-control custom-radio">
<input id={payment.id} name="paymentMethod" type="radio" className="custom-control-input" />
<label className="custom-control-label" htmlFor={payment.id}>{payment.name}</label>
</div>
))}
</div>
);
}
});
================================================
FILE: src/typescript/components/Spinner.ts
================================================
import { defineComponent, h } from 'vue';
export default defineComponent({
name: 'Spinner',
render() {
return h(
'div', { class: 'spinner-border', role: 'status' },
[
h('span', { class: 'sr-only' }, 'Loading...')
]
);
}
});
================================================
FILE: src/typescript/components/Username.test.ts
================================================
import { mount } from '@vue/test-utils';
import Username from './Username.vue';
describe('Username', () => {
it('should display required text', () => {
const wrapper = mount(Username);
expect(wrapper.find('.invalid-feedback').text()).toEqual('Your username is required.');
});
});
================================================
FILE: src/typescript/components/Username.vue
================================================
<template>
<div class="mb-3">
<slot :label="label">
<label for="username">Default label</label>
</slot>
<div class="input-group">
<div class="input-group-prepend"></div>
<input type="text" class="form-control" id="username" placeholder="Username" />
<div class="invalid-feedback" style="width: 100%;">
Your username is required.
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'Username',
setup() {
return { label: 'Username' };
}
});
</script>
================================================
FILE: src/typescript/hooks.ts
================================================
import { reactive, ref, computed } from 'vue';
export interface Product {
firstName: string;
firstPrice: number;
firstQuantity: number;
}
export const useCart = () => {
const product = reactive<Product>({ firstName: 'First Product', firstPrice: 10, firstQuantity: 1 });
const secondName = ref('Second Product');
const secondPrice = ref(20);
const secondQuantity = ref(2);
const total = computed<number>(() => (product.firstPrice * product.firstQuantity + secondPrice.value * secondQuantity.value) * (100 - coupon.value) / 100);
const coupon = ref(10);
const setCoupon = (percent): void => coupon.value = percent;
return { product, secondName, secondPrice, secondQuantity, total, coupon, setCoupon };
};
================================================
FILE: src/typescript/main.ts
================================================
import { createApp } from 'vue';
import App from './App.vue';
import store from './store';
import router from './router';
import AtSign from './at-sign';
createApp(App).use(store).use(router).directive('AtSign', AtSign).mount('#app');
================================================
FILE: src/typescript/router.ts
================================================
import { createRouter, createWebHashHistory } from 'vue-router';
import Cart from './components/Cart.vue';
import Checkout from './components/Checkout.vue';
export default createRouter({
history: createWebHashHistory(),
routes: [
{ path: '/', name: 'cart', component: Cart },
{ path: '/checkout', name: 'checkout', component: Checkout },
],
});
================================================
FILE: src/typescript/sfc.d.ts
================================================
declare module '*.vue' {
import { DefineComponent } from 'vue';
const component: DefineComponent<{}, {}, any>;
export default component;
}
================================================
FILE: src/typescript/store.ts
================================================
import { createStore, ActionContext } from 'vuex';
export interface State {
year: string;
}
export default createStore<State>({
state: {
year: '1970'
},
actions: {
onSetYear: (context: ActionContext<State, State>, year: string) => {
context.commit('setYear', year);
}
},
mutations: {
setYear(state: State, year: string) {
state.year = year;
}
}
});
================================================
FILE: src/typescript/version.ts
================================================
import { version, provide, inject } from 'vue';
const versionSymbol: unique symbol = Symbol('version');
export const provideVersion = (): void => provide(versionSymbol, version);
export const useVersion = (): string | undefined => inject(versionSymbol);
================================================
FILE: tsconfig.json
================================================
{
"compileOnSave": true,
"compilerOptions": {
"allowSyntheticDefaultImports": false,
"allowJs": true,
"alwaysStrict": true,
"declaration": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": false,
"lib": ["es2017", "dom"],
"module": "commonjs",
"moduleResolution": "node",
"noEmitHelpers": false,
"noEmitOnError": false,
"noFallthroughCasesInSwitch": true,
"noImplicitThis": true,
"noUnusedLocals": true,
"noUnusedParameters": false,
"pretty": true,
"removeComments": true,
"rootDir": "./",
"sourceMap": true,
"strictNullChecks": true,
"target": "ES2020"
},
"exclude": ["./node_modules"]
}
================================================
FILE: vite.config.js
================================================
import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
export default {
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: tag => /^x-/.test(tag)
}
}
}),
vueJsx()
],
base: './',
};
================================================
FILE: vitest.config.js
================================================
import { defineConfig } from 'vite';
import Vue from '@vitejs/plugin-vue';
import Jsx from '@vitejs/plugin-vue-jsx';
export default defineConfig({
plugins: [Vue(), Jsx()],
test: {
globals: true,
environment: 'jsdom',
transformMode: {
web: [/.[tj]sx$/],
},
},
});
gitextract_mp3wsnit/ ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── cypress-javascript.json ├── cypress-typescript.json ├── e2e/ │ ├── javascript/ │ │ └── index.test.js │ └── typescript/ │ └── index.test.ts ├── index.html ├── package.json ├── public/ │ └── .nojekyll ├── sandbox.config.json ├── src/ │ ├── javascript/ │ │ ├── App.vue │ │ ├── at-sign.js │ │ ├── components/ │ │ │ ├── AsyncPayment.js │ │ │ ├── Cart.vue │ │ │ ├── Checkout.vue │ │ │ ├── Coupon.test.js │ │ │ ├── Coupon.vue │ │ │ ├── Exchange.vue │ │ │ ├── Header.vue │ │ │ ├── Item.vue │ │ │ ├── Payment.jsx │ │ │ ├── Spinner.js │ │ │ ├── Username.test.js │ │ │ └── Username.vue │ │ ├── custom-element.js │ │ ├── hooks.js │ │ ├── main.js │ │ ├── router.js │ │ ├── store.js │ │ └── version.js │ └── typescript/ │ ├── App.vue │ ├── at-sign.ts │ ├── components/ │ │ ├── AsyncPayment.ts │ │ ├── Cart.vue │ │ ├── Checkout.vue │ │ ├── Coupon.test.ts │ │ ├── Coupon.vue │ │ ├── Exchange.vue │ │ ├── Header.vue │ │ ├── Item.vue │ │ ├── Payment.tsx │ │ ├── Spinner.ts │ │ ├── Username.test.ts │ │ └── Username.vue │ ├── hooks.ts │ ├── main.ts │ ├── router.ts │ ├── sfc.d.ts │ ├── store.ts │ └── version.ts ├── tsconfig.json ├── vite.config.js └── vitest.config.js
SYMBOL INDEX (25 symbols across 11 files)
FILE: src/javascript/at-sign.js
method beforeMount (line 2) | beforeMount(el, binding, vnode, prevVnode) {
method mounted (line 7) | mounted() {}
method beforeUpdate (line 8) | beforeUpdate() {}
method updated (line 9) | updated() {}
method beforeUnmount (line 10) | beforeUnmount() {}
method unmounted (line 11) | unmounted() {}
FILE: src/javascript/components/AsyncPayment.js
method onError (line 8) | onError(error, retry, fail, attempts) {
FILE: src/javascript/components/Payment.jsx
method setup (line 6) | setup() {
method render (line 16) | render() {
FILE: src/javascript/components/Spinner.js
method render (line 6) | render() {
FILE: src/javascript/custom-element.js
class XUsername (line 1) | class XUsername extends HTMLElement {
method connectedCallback (line 2) | connectedCallback() {
FILE: src/typescript/at-sign.ts
method beforeMount (line 4) | beforeMount(el: HTMLElement, binding: DirectiveBinding, vnode: VNode, pr...
method mounted (line 9) | mounted() {}
method beforeUpdate (line 10) | beforeUpdate() {}
method updated (line 11) | updated() {}
method beforeUnmount (line 12) | beforeUnmount() {}
method unmounted (line 13) | unmounted() {}
FILE: src/typescript/components/AsyncPayment.ts
method onError (line 8) | onError(error: Error, retry: () => void, fail: () => void, attempts: num...
FILE: src/typescript/components/Payment.tsx
method setup (line 6) | setup() {
method render (line 16) | render() {
FILE: src/typescript/components/Spinner.ts
method render (line 6) | render() {
FILE: src/typescript/hooks.ts
type Product (line 3) | interface Product {
FILE: src/typescript/store.ts
type State (line 3) | interface State {
method setYear (line 17) | setYear(state: State, year: string) {
Condensed preview — 58 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (51K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 653,
"preview": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [u"
},
{
"path": ".gitignore",
"chars": 214,
"preview": ".DS_Store\nnode_modules\n/dist\n\n# local env files\n.env.local\n.env.*.local\n\n# Log files\nnpm-debug.log*\nyarn-debug.log*\nyarn"
},
{
"path": ".travis.yml",
"chars": 292,
"preview": "language: node_js\nnode_js:\n - \"14\"\nscript: npm test\nbefore_deploy: npm run build\ndeploy:\n provider: pages\n local_dir:"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 3354,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": "CONTRIBUTING.md",
"chars": 1642,
"preview": "# Contributing\n\n## Issues\n\nIssues are very valuable to this project.\n\n* Ideas are a valuable source of contributions oth"
},
{
"path": "LICENSE",
"chars": 1067,
"preview": "MIT License\n\nCopyright (c) 2019 Gábor Soós\n\nPermission is hereby granted, free of charge, to any person obtaining a copy"
},
{
"path": "README.md",
"chars": 7205,
"preview": "# Vue 3 Playground\n\nVue 3 Playground stands as a learning project for those who want to get familiar with [the new featu"
},
{
"path": "cypress-javascript.json",
"chars": 148,
"preview": "{\n \"baseUrl\": \"http://localhost:3000\",\n \"integrationFolder\": \"e2e/javascript\",\n \"pluginsFile\": false,\n \"supportFile\""
},
{
"path": "cypress-typescript.json",
"chars": 148,
"preview": "{\n \"baseUrl\": \"http://localhost:3000\",\n \"integrationFolder\": \"e2e/typescript\",\n \"pluginsFile\": false,\n \"supportFile\""
},
{
"path": "e2e/javascript/index.test.js",
"chars": 169,
"preview": "describe('Vue 3 Playground', () => {\n it('should display playground text on page', () => {\n cy.visit('/');\n cy.co"
},
{
"path": "e2e/typescript/index.test.ts",
"chars": 169,
"preview": "describe('Vue 3 Playground', () => {\n it('should display playground text on page', () => {\n cy.visit('/');\n cy.co"
},
{
"path": "index.html",
"chars": 936,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Vue 3 Playground</title>\n <link rel=\"icon\" hr"
},
{
"path": "package.json",
"chars": 1616,
"preview": "{\n \"name\": \"vue-3-playground\",\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github."
},
{
"path": "public/.nojekyll",
"chars": 0,
"preview": ""
},
{
"path": "sandbox.config.json",
"chars": 204,
"preview": "{\n \"infiniteLoopProtection\": true,\n \"hardReloadOnChange\": false,\n \"view\": \"browser\",\n \"server\": \"true\",\n \"container"
},
{
"path": "src/javascript/App.vue",
"chars": 483,
"preview": "<script>\nimport { defineComponent, onMounted } from 'vue';\nimport { useStore } from 'vuex';\nimport Header from './compon"
},
{
"path": "src/javascript/at-sign.js",
"chars": 360,
"preview": "export default {\n beforeMount(el, binding, vnode, prevVnode) {\n console.log(`directive: value - ${binding.value}, ar"
},
{
"path": "src/javascript/components/AsyncPayment.js",
"chars": 315,
"preview": "import { defineAsyncComponent } from 'vue';\nimport Spinner from './Spinner';\n\nexport default defineAsyncComponent({\n lo"
},
{
"path": "src/javascript/components/Cart.vue",
"chars": 4008,
"preview": "<script>\nimport { toRefs, onMounted, onUpdated, onUnmounted, onErrorCaptured, watchEffect, watch, ref, defineComponent }"
},
{
"path": "src/javascript/components/Checkout.vue",
"chars": 470,
"preview": "<script>\nimport { defineComponent } from 'vue';\nimport { useRoute } from 'vue-router';\n\nexport default defineComponent({"
},
{
"path": "src/javascript/components/Coupon.test.js",
"chars": 393,
"preview": "import { mount } from '@vue/test-utils';\nimport Coupon from './Coupon.vue';\n\ndescribe('Coupon', () => {\n it('should tri"
},
{
"path": "src/javascript/components/Coupon.vue",
"chars": 647,
"preview": "<script>\nimport { defineComponent, ref } from 'vue';\n\nexport default defineComponent({\n name: 'Coupon',\n props: ['perc"
},
{
"path": "src/javascript/components/Exchange.vue",
"chars": 522,
"preview": "<script>\nimport { defineComponent } from 'vue';\n\nexport default defineComponent({\n async setup() {\n const exchangeRa"
},
{
"path": "src/javascript/components/Header.vue",
"chars": 688,
"preview": "<script>\nimport { defineComponent } from 'vue';\nimport { useStore } from 'vuex';\nimport { useVersion } from '../version'"
},
{
"path": "src/javascript/components/Item.vue",
"chars": 1153,
"preview": "<script>\nimport { defineComponent } from 'vue';\n\nexport default defineComponent({\n name: 'Item',\n props: ['name', 'pri"
},
{
"path": "src/javascript/components/Payment.jsx",
"chars": 712,
"preview": "import { defineComponent } from 'vue';\n\nexport default defineComponent({\n name: 'Payment',\n\n setup() {\n const payme"
},
{
"path": "src/javascript/components/Spinner.js",
"chars": 266,
"preview": "import { defineComponent, h } from 'vue';\n\nexport default defineComponent({\n name: 'Spinner',\n\n render() {\n return "
},
{
"path": "src/javascript/components/Username.test.js",
"chars": 295,
"preview": "import { mount } from '@vue/test-utils';\nimport Username from './Username.vue';\n\ndescribe('Username', () => {\n it('shou"
},
{
"path": "src/javascript/components/Username.vue",
"chars": 564,
"preview": "<template>\n<div class=\"mb-3\">\n <slot :label=\"label\">\n <label for=\"username\">Default label</label>\n </slot>\n <div c"
},
{
"path": "src/javascript/custom-element.js",
"chars": 159,
"preview": "export class XUsername extends HTMLElement {\n connectedCallback() {\n this.innerText = 'blacksonic';\n }\n}\n\ncustomEle"
},
{
"path": "src/javascript/hooks.js",
"chars": 615,
"preview": "import { reactive, ref, computed, readonly } from 'vue';\n\nexport const useCart = () => {\n const product = reactive({ fi"
},
{
"path": "src/javascript/main.js",
"chars": 343,
"preview": "import { createApp } from 'vue';\nimport App from './App.vue';\nimport store from './store';\nimport router from './router'"
},
{
"path": "src/javascript/router.js",
"chars": 360,
"preview": "import { createRouter, createWebHashHistory } from 'vue-router';\nimport Cart from './components/Cart.vue';\nimport Checko"
},
{
"path": "src/javascript/store.js",
"chars": 279,
"preview": "import { createStore } from 'vuex';\n\nexport default createStore({\n state: {\n year: '1970'\n },\n actions: {\n onSe"
},
{
"path": "src/javascript/version.js",
"chars": 215,
"preview": "import { version, provide, inject } from 'vue';\n\nconst versionSymbol = Symbol('version');\n\nexport const provideVersion ="
},
{
"path": "src/typescript/App.vue",
"chars": 537,
"preview": "<script lang=\"ts\">\nimport { defineComponent, onMounted } from 'vue';\nimport { useStore } from 'vuex';\nimport Header from"
},
{
"path": "src/typescript/at-sign.ts",
"chars": 460,
"preview": "import { DirectiveBinding, VNode } from 'vue';\n\nexport default {\n beforeMount(el: HTMLElement, binding: DirectiveBindin"
},
{
"path": "src/typescript/components/AsyncPayment.ts",
"chars": 354,
"preview": "import { defineAsyncComponent } from 'vue';\nimport Spinner from './Spinner';\n\nexport default defineAsyncComponent({\n lo"
},
{
"path": "src/typescript/components/Cart.vue",
"chars": 4041,
"preview": "<script lang=\"ts\">\nimport { toRefs, onMounted, onUpdated, onUnmounted, onErrorCaptured, watchEffect, watch, ref, defineC"
},
{
"path": "src/typescript/components/Checkout.vue",
"chars": 480,
"preview": "<script lang=\"ts\">\nimport { defineComponent } from 'vue';\nimport { useRoute } from 'vue-router';\n\nexport default defineC"
},
{
"path": "src/typescript/components/Coupon.test.ts",
"chars": 412,
"preview": "import { mount } from '@vue/test-utils';\nimport Coupon from './Coupon.vue';\n\ndescribe('Coupon', () => {\n it('should tri"
},
{
"path": "src/typescript/components/Coupon.vue",
"chars": 768,
"preview": "<script lang=\"ts\">\nimport { defineComponent, ref, SetupContext } from 'vue';\ntype EmitOption = 'redeem';\ntype Props = { "
},
{
"path": "src/typescript/components/Exchange.vue",
"chars": 651,
"preview": "<script lang=\"ts\">\nimport { defineComponent } from 'vue';\ninterface ApiResponse {\n date: string,\n base: string,\n rate"
},
{
"path": "src/typescript/components/Header.vue",
"chars": 743,
"preview": "<script lang=\"ts\">\nimport { defineComponent } from 'vue';\nimport { useVersion } from '../version';\nimport { useStore } f"
},
{
"path": "src/typescript/components/Item.vue",
"chars": 1347,
"preview": "<script lang=\"ts\">\nimport { defineComponent, SetupContext } from 'vue';\ntype EmitOption = 'update:name' | 'update:price'"
},
{
"path": "src/typescript/components/Payment.tsx",
"chars": 715,
"preview": "import { defineComponent } from 'vue';\n\nexport default defineComponent({\n name: 'Payment',\n\n setup() {\n const payme"
},
{
"path": "src/typescript/components/Spinner.ts",
"chars": 266,
"preview": "import { defineComponent, h } from 'vue';\n\nexport default defineComponent({\n name: 'Spinner',\n\n render() {\n return "
},
{
"path": "src/typescript/components/Username.test.ts",
"chars": 295,
"preview": "import { mount } from '@vue/test-utils';\nimport Username from './Username.vue';\n\ndescribe('Username', () => {\n it('shou"
},
{
"path": "src/typescript/components/Username.vue",
"chars": 574,
"preview": "<template>\n<div class=\"mb-3\">\n <slot :label=\"label\">\n <label for=\"username\">Default label</label>\n </slot>\n <div c"
},
{
"path": "src/typescript/hooks.ts",
"chars": 728,
"preview": "import { reactive, ref, computed } from 'vue';\n\nexport interface Product {\n firstName: string;\n firstPrice: number;\n "
},
{
"path": "src/typescript/main.ts",
"chars": 236,
"preview": "import { createApp } from 'vue';\nimport App from './App.vue';\nimport store from './store';\nimport router from './router'"
},
{
"path": "src/typescript/router.ts",
"chars": 360,
"preview": "import { createRouter, createWebHashHistory } from 'vue-router';\nimport Cart from './components/Cart.vue';\nimport Checko"
},
{
"path": "src/typescript/sfc.d.ts",
"chars": 145,
"preview": "declare module '*.vue' {\n import { DefineComponent } from 'vue';\n const component: DefineComponent<{}, {}, any>;\n exp"
},
{
"path": "src/typescript/store.ts",
"chars": 397,
"preview": "import { createStore, ActionContext } from 'vuex';\n\nexport interface State {\n year: string;\n}\n\nexport default createSto"
},
{
"path": "src/typescript/version.ts",
"chars": 256,
"preview": "import { version, provide, inject } from 'vue';\n\nconst versionSymbol: unique symbol = Symbol('version');\n\nexport const p"
},
{
"path": "tsconfig.json",
"chars": 722,
"preview": "{\n \"compileOnSave\": true,\n \"compilerOptions\": {\n \"allowSyntheticDefaultImports\": false,\n \"allowJs\": true,\n \"a"
},
{
"path": "vite.config.js",
"chars": 280,
"preview": "import vue from '@vitejs/plugin-vue';\nimport vueJsx from '@vitejs/plugin-vue-jsx';\n\nexport default {\n plugins: [\n vu"
},
{
"path": "vitest.config.js",
"chars": 292,
"preview": "import { defineConfig } from 'vite';\nimport Vue from '@vitejs/plugin-vue';\nimport Jsx from '@vitejs/plugin-vue-jsx';\n\nex"
}
]
About this extraction
This page contains the full source code of the vuesomedev/vue-3-playground GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 58 files (44.7 KB), approximately 13.2k tokens, and a symbol index with 25 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.