├── .editorconfig ├── .github ├── FUNDING.yml ├── contributing │ ├── oxford-comma.jpg │ └── writing-guide.md ├── dependabot.yml ├── pull_request_template.md ├── scripts │ └── tag-alert-blocks.js └── workflows │ └── automerge.yml ├── .gitignore ├── .prettierrc ├── .vitepress ├── config.ts ├── headerMdPlugin.ts ├── inlined-scripts │ └── restorePreference.js ├── textAdMdPlugin.ts └── theme │ ├── components │ ├── Banner.vue │ ├── Home.vue │ ├── NewsLetter.vue │ ├── PreferenceSwitch.vue │ ├── PreferenceTooltip.vue │ ├── ReplLoading.vue │ ├── SiteMap.vue │ ├── SponsorsAside.vue │ ├── SponsorsGroup.vue │ ├── TextAd.vue │ ├── VueJobs.vue │ ├── VueMasteryBanner.vue │ ├── VueMasteryModal.vue │ ├── VueSchoolLink.vue │ ├── preferences.ts │ └── sponsors.ts │ ├── index.ts │ └── styles │ ├── badges.css │ ├── index.css │ ├── inline-demo.css │ ├── options-boxes.css │ ├── pages.css │ ├── style-guide.css │ ├── utilities.css │ └── vue-mastery.css ├── LICENSE ├── README.md ├── env.d.ts ├── netlify.toml ├── package-lock.json ├── package.json ├── pnpm-lock.yaml ├── src ├── about │ ├── coc.md │ ├── community-guide.md │ ├── faq.md │ ├── images │ │ ├── ben-hong.jpeg │ │ └── evan-you.jpeg │ ├── releases.md │ ├── team.md │ └── team │ │ ├── Member.ts │ │ ├── TeamHero.vue │ │ ├── TeamList.vue │ │ ├── TeamMember.vue │ │ ├── TeamPage.vue │ │ ├── members-core.json │ │ ├── members-emeriti.json │ │ └── members-partner.json ├── api │ ├── ApiIndex.vue │ ├── api.data.ts │ ├── application.md │ ├── built-in-components.md │ ├── built-in-directives.md │ ├── built-in-special-attributes.md │ ├── built-in-special-elements.md │ ├── component-instance.md │ ├── composition-api-dependency-injection.md │ ├── composition-api-lifecycle.md │ ├── composition-api-setup.md │ ├── custom-renderer.md │ ├── general.md │ ├── index.md │ ├── options-composition.md │ ├── options-lifecycle.md │ ├── options-misc.md │ ├── options-rendering.md │ ├── options-state.md │ ├── reactivity-advanced.md │ ├── reactivity-core.md │ ├── reactivity-utilities.md │ ├── render-function.md │ ├── sfc-css-features.md │ ├── sfc-script-setup.md │ ├── sfc-spec.md │ ├── ssr.md │ └── utility-types.md ├── ecosystem │ ├── newsletters.md │ ├── themes.md │ └── themes │ │ ├── ThemeContact.vue │ │ ├── ThemeHero.vue │ │ ├── ThemeList.vue │ │ ├── ThemeListItem.vue │ │ ├── ThemePage.vue │ │ ├── ThemeProduct.vue │ │ └── themes.json ├── examples │ ├── ExampleRepl.vue │ ├── examples.data.ts │ ├── index.md │ ├── src │ │ ├── attribute-bindings │ │ │ ├── App │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ ├── style.css │ │ │ │ └── template.html │ │ │ └── description.txt │ │ ├── cells │ │ │ ├── App │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ ├── style.css │ │ │ │ └── template.html │ │ │ ├── Cell │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ ├── style.css │ │ │ │ └── template.html │ │ │ ├── description.txt │ │ │ └── store.js │ │ ├── circle-drawer │ │ │ ├── App │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ ├── style.css │ │ │ │ └── template.html │ │ │ └── description.txt │ │ ├── conditionals-and-loops │ │ │ ├── App │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ └── template.html │ │ │ └── description.txt │ │ ├── counter │ │ │ ├── App │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ └── template.html │ │ │ └── description.txt │ │ ├── crud │ │ │ ├── App │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ ├── style.css │ │ │ │ └── template.html │ │ │ └── description.txt │ │ ├── fetching-data │ │ │ ├── App │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ ├── style.css │ │ │ │ └── template.html │ │ │ └── description.txt │ │ ├── flight-booker │ │ │ ├── App │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ ├── style.css │ │ │ │ └── template.html │ │ │ └── description.txt │ │ ├── form-bindings │ │ │ ├── App │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ └── template.html │ │ │ └── description.txt │ │ ├── grid │ │ │ ├── App │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ └── template.html │ │ │ ├── Grid │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ ├── style.css │ │ │ │ └── template.html │ │ │ └── description.txt │ │ ├── handling-input │ │ │ ├── App │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ ├── style.css │ │ │ │ └── template.html │ │ │ └── description.txt │ │ ├── hello-world │ │ │ ├── App │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ └── template.html │ │ │ └── description.txt │ │ ├── list-transition │ │ │ ├── App │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ ├── style.css │ │ │ │ └── template.html │ │ │ ├── description.txt │ │ │ └── import-map.json │ │ ├── markdown │ │ │ ├── App │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ ├── style.css │ │ │ │ └── template.html │ │ │ ├── description.txt │ │ │ └── import-map.json │ │ ├── modal │ │ │ ├── App │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ └── template.html │ │ │ ├── Modal │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ ├── style.css │ │ │ │ └── template.html │ │ │ └── description.txt │ │ ├── simple-component │ │ │ ├── App │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ └── template.html │ │ │ ├── TodoItem │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ └── template.html │ │ │ └── description.txt │ │ ├── svg │ │ │ ├── App │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ ├── style.css │ │ │ │ └── template.html │ │ │ ├── AxisLabel │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ └── template.html │ │ │ ├── PolyGraph │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ └── template.html │ │ │ ├── description.txt │ │ │ └── util.js │ │ ├── temperature-converter │ │ │ ├── App │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ └── template.html │ │ │ └── description.txt │ │ ├── timer │ │ │ ├── App │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ ├── style.css │ │ │ │ └── template.html │ │ │ └── description.txt │ │ ├── todomvc │ │ │ ├── App │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ ├── style.css │ │ │ │ └── template.html │ │ │ └── description.txt │ │ └── tree │ │ │ ├── App │ │ │ ├── composition.js │ │ │ ├── options.js │ │ │ ├── style.css │ │ │ └── template.html │ │ │ ├── TreeItem │ │ │ ├── composition.js │ │ │ ├── options.js │ │ │ └── template.html │ │ │ └── description.txt │ └── utils.ts ├── glossary │ └── index.md ├── guide │ ├── best-practices │ │ ├── accessibility.md │ │ ├── images │ │ │ ├── AccessibilityChromeDeveloperTools.png │ │ │ ├── AccessibleARIAdescribedby.png │ │ │ ├── AccessibleARIAlabelDevTools.png │ │ │ ├── AccessibleARIAlabelledbyDevTools.png │ │ │ ├── AccessibleLabelChromeDevTools.png │ │ │ └── AccessiblePlaceholder.png │ │ ├── performance.md │ │ ├── production-deployment.md │ │ └── security.md │ ├── built-ins │ │ ├── images │ │ │ └── transition-classes.png │ │ ├── keep-alive-demos │ │ │ ├── CompA.vue │ │ │ ├── CompB.vue │ │ │ └── SwitchComponent.vue │ │ ├── keep-alive.md │ │ ├── suspense.md │ │ ├── teleport.md │ │ ├── transition-demos │ │ │ ├── Basic.vue │ │ │ ├── BetweenComponents.vue │ │ │ ├── BetweenElements.vue │ │ │ ├── CssAnimation.vue │ │ │ ├── JsHooks.vue │ │ │ ├── ListBasic.vue │ │ │ ├── ListMove.vue │ │ │ ├── ListStagger.vue │ │ │ ├── NestedTransitions.vue │ │ │ └── SlideFade.vue │ │ ├── transition-group.md │ │ └── transition.md │ ├── components │ │ ├── async.md │ │ ├── attrs.md │ │ ├── events.md │ │ ├── images │ │ │ ├── named-slots.png │ │ │ ├── prop-drilling.png │ │ │ ├── provide-inject.png │ │ │ ├── scoped-slots.svg │ │ │ └── slots.png │ │ ├── props.md │ │ ├── provide-inject.md │ │ ├── registration.md │ │ ├── slots.md │ │ └── v-model.md │ ├── essentials │ │ ├── application.md │ │ ├── class-and-style.md │ │ ├── component-basics.md │ │ ├── computed.md │ │ ├── conditional.md │ │ ├── event-handling.md │ │ ├── forms.md │ │ ├── images │ │ │ ├── components.png │ │ │ ├── directive.png │ │ │ └── lifecycle.png │ │ ├── lifecycle.md │ │ ├── list.md │ │ ├── reactivity-fundamentals.md │ │ ├── template-refs.md │ │ ├── template-syntax.md │ │ └── watchers.md │ ├── extras │ │ ├── animation.md │ │ ├── composition-api-faq.md │ │ ├── demos │ │ │ ├── AnimateWatcher.vue │ │ │ ├── Colors.vue │ │ │ ├── DisabledButton.vue │ │ │ ├── ElasticHeader.vue │ │ │ ├── SpreadSheet.vue │ │ │ ├── SpreadSheetCell.vue │ │ │ └── spreadSheetStore.js │ │ ├── images │ │ │ ├── composition-api-after.png │ │ │ ├── options-api.png │ │ │ └── render-pipeline.png │ │ ├── reactivity-in-depth.md │ │ ├── reactivity-transform.md │ │ ├── render-function.md │ │ ├── rendering-mechanism.md │ │ ├── ways-of-using-vue.md │ │ └── web-components.md │ ├── introduction.md │ ├── quick-start.md │ ├── reusability │ │ ├── composables.md │ │ ├── custom-directives.md │ │ ├── mouse.js │ │ └── plugins.md │ ├── scaling-up │ │ ├── images │ │ │ └── state-flow.png │ │ ├── routing.md │ │ ├── sfc.md │ │ ├── ssr.md │ │ ├── state-management.md │ │ ├── testing.md │ │ └── tooling.md │ └── typescript │ │ ├── composition-api.md │ │ ├── images │ │ └── takeover-mode.png │ │ ├── options-api.md │ │ └── overview.md ├── index.md ├── partners │ ├── [partnerId].md │ ├── [partnerId].paths.ts │ ├── all.md │ ├── components │ │ ├── PartnerAll.vue │ │ ├── PartnerCard.vue │ │ ├── PartnerHero.vue │ │ ├── PartnerJoin.vue │ │ ├── PartnerLanding.vue │ │ ├── PartnerList.vue │ │ ├── PartnerLocation.vue │ │ ├── PartnerPage.vue │ │ ├── type.ts │ │ └── utils.ts │ ├── index.md │ └── partners.json ├── public │ ├── _headers │ ├── _redirects │ ├── glossary.json │ ├── images │ │ ├── logo.png │ │ ├── partners │ │ │ ├── 64robots-hero.jpg │ │ │ ├── 64robots.svg │ │ │ ├── curotec-hero.jpg │ │ │ ├── curotec.png │ │ │ ├── epicmax.png │ │ │ ├── epicmax.svg │ │ │ ├── herodevs-dark.png │ │ │ ├── herodevs-hero.png │ │ │ ├── herodevs.png │ │ │ ├── jump24-dark.svg │ │ │ ├── jump24-hero.jpg │ │ │ ├── jump24.svg │ │ │ ├── monterail-dark.png │ │ │ ├── monterail-hero.png │ │ │ ├── monterail.png │ │ │ ├── passionatepeople-dark.png │ │ │ ├── passionatepeople-hero.jpg │ │ │ ├── passionatepeople.png │ │ │ ├── redberry-hero.jpg │ │ │ ├── redberry.png │ │ │ ├── tighten-dark.svg │ │ │ ├── tighten-hero.jpg │ │ │ ├── tighten.svg │ │ │ ├── vehikl-dark.svg │ │ │ ├── vehikl-hero.jpg │ │ │ ├── vehikl.svg │ │ │ ├── webreinvent-hero.jpg │ │ │ └── webreinvent.png │ │ └── paypal.png │ ├── logo.svg │ ├── service-worker.js │ └── vuemastery │ │ ├── background-bubbles-vuemastery.svg │ │ ├── lock-vuemastery.svg │ │ ├── unlock-vuemastery.svg │ │ └── vuemastery-white.svg ├── sponsor │ └── index.md ├── style-guide │ ├── index.md │ ├── rules-essential.md │ ├── rules-recommended.md │ ├── rules-strongly-recommended.md │ └── rules-use-with-caution.md ├── translations │ └── index.md └── tutorial │ ├── TutorialRepl.vue │ ├── index.md │ ├── src │ ├── step-1 │ │ ├── App │ │ │ └── template.html │ │ └── description.md │ ├── step-10 │ │ ├── App │ │ │ ├── composition.js │ │ │ ├── options.js │ │ │ └── template.html │ │ ├── _hint │ │ │ └── App │ │ │ │ ├── composition.js │ │ │ │ └── options.js │ │ └── description.md │ ├── step-11 │ │ ├── App │ │ │ ├── composition.js │ │ │ ├── options.js │ │ │ └── template.html │ │ ├── ChildComp │ │ │ └── template.html │ │ ├── _hint │ │ │ └── App │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ └── template.html │ │ └── description.md │ ├── step-12 │ │ ├── App │ │ │ ├── composition.js │ │ │ ├── options.js │ │ │ └── template.html │ │ ├── ChildComp │ │ │ ├── composition.js │ │ │ ├── options.js │ │ │ └── template.html │ │ ├── _hint │ │ │ └── App │ │ │ │ └── template.html │ │ └── description.md │ ├── step-13 │ │ ├── App │ │ │ ├── composition.js │ │ │ ├── options.js │ │ │ └── template.html │ │ ├── ChildComp │ │ │ ├── composition.js │ │ │ ├── options.js │ │ │ └── template.html │ │ ├── _hint │ │ │ └── App │ │ │ │ └── template.html │ │ └── description.md │ ├── step-14 │ │ ├── App │ │ │ ├── composition.js │ │ │ ├── options.js │ │ │ └── template.html │ │ ├── ChildComp │ │ │ └── template.html │ │ ├── _hint │ │ │ └── App │ │ │ │ └── template.html │ │ └── description.md │ ├── step-15 │ │ ├── App │ │ │ ├── composition.js │ │ │ ├── options.js │ │ │ ├── style.css │ │ │ └── template.html │ │ ├── description.md │ │ └── import-map.json │ ├── step-2 │ │ ├── App │ │ │ ├── composition.js │ │ │ ├── options.js │ │ │ └── template.html │ │ ├── _hint │ │ │ └── App │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ └── template.html │ │ └── description.md │ ├── step-3 │ │ ├── App │ │ │ ├── composition.js │ │ │ ├── options.js │ │ │ ├── style.css │ │ │ └── template.html │ │ ├── _hint │ │ │ └── App │ │ │ │ └── template.html │ │ └── description.md │ ├── step-4 │ │ ├── App │ │ │ ├── composition.js │ │ │ ├── options.js │ │ │ └── template.html │ │ ├── _hint │ │ │ └── App │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ └── template.html │ │ └── description.md │ ├── step-5 │ │ ├── App │ │ │ ├── composition.js │ │ │ ├── options.js │ │ │ └── template.html │ │ ├── _hint │ │ │ └── App │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ └── template.html │ │ └── description.md │ ├── step-6 │ │ ├── App │ │ │ ├── composition.js │ │ │ ├── options.js │ │ │ └── template.html │ │ ├── _hint │ │ │ └── App │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ └── template.html │ │ └── description.md │ ├── step-7 │ │ ├── App │ │ │ ├── composition.js │ │ │ ├── options.js │ │ │ └── template.html │ │ ├── _hint │ │ │ └── App │ │ │ │ ├── composition.js │ │ │ │ └── options.js │ │ └── description.md │ ├── step-8 │ │ ├── App │ │ │ ├── composition.js │ │ │ ├── options.js │ │ │ ├── style.css │ │ │ └── template.html │ │ ├── _hint │ │ │ └── App │ │ │ │ ├── composition.js │ │ │ │ ├── options.js │ │ │ │ └── template.html │ │ └── description.md │ └── step-9 │ │ ├── App │ │ ├── composition.js │ │ ├── options.js │ │ └── template.html │ │ ├── _hint │ │ └── App │ │ │ ├── composition.js │ │ │ └── options.js │ │ └── description.md │ └── tutorial.data.ts └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | indent_style = space 4 | indent_size = 2 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | 9 | [*.md] 10 | trim_trailing_whitespace = false 11 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | # github: matrunchyk,dddarkasss 4 | patreon: vuejsorgua 5 | # open_collective: # Replace with a single Open Collective username 6 | # ko_fi: # Replace with a single Ko-fi username 7 | # tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | # community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | # liberapay: # Replace with a single Liberapay username 10 | # issuehunt: # Replace with a single IssueHunt username 11 | # otechie: # Replace with a single Otechie username 12 | # lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | # custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/contributing/oxford-comma.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/.github/contributing/oxford-comma.jpg -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Enable version updates for npm 4 | - package-ecosystem: 'npm' 5 | # Look for `package.json` and `lock` files in the `root` directory 6 | directory: '/' 7 | # Check the npm registry for updates every day (weekdays) 8 | schedule: 9 | interval: 'daily' 10 | open-pull-requests-limit: 10 11 | versioning-strategy: lockfile-only 12 | allow: 13 | - dependency-name: 'vue' 14 | - dependency-name: 'vitepress' 15 | - dependency-name: '@vue/theme' 16 | - dependency-name: '@vue/repl' 17 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | - [ ] Я ознайомлений(а) з [Правилами спільноти](https://github.com/vuejs-translations/docs-uk/wiki/Правила-спільноти) та зобов'язуюсь їх дотримуватись. 2 | -------------------------------------------------------------------------------- /.github/scripts/tag-alert-blocks.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { exec } = require('child_process') 4 | 5 | /** 6 | * Execute a command and return stdout as string. 7 | * @param {string} command 8 | * @returns {Promise} 9 | */ 10 | function run(command) { 11 | return new Promise((resolve, reject) => { 12 | exec(command, { encoding: 'utf-8' }, (error, stdout) => 13 | error ? reject(error) : resolve(stdout) 14 | ) 15 | }) 16 | } 17 | 18 | const ALERT_BLOCK = /^\+\s*:::\s?(\w+)/m 19 | 20 | async function isUsingAlertBlock(base = 'origin/master') { 21 | const result = await run(`git diff --name-only ${base}`) 22 | const files = ( 23 | await Promise.all( 24 | result 25 | .trim() 26 | .split(/\r?\n/) 27 | .map(file => 28 | run(`git diff ${base} -- ${file}`) 29 | .then(diff => ALERT_BLOCK.test(diff)) 30 | .then(usesAlertBlock => (usesAlertBlock ? file : '')) 31 | ) 32 | ) 33 | ).filter(Boolean) 34 | 35 | if (files.length) { 36 | return true 37 | } 38 | 39 | return false 40 | } 41 | 42 | module.exports = { isUsingAlertBlock } 43 | -------------------------------------------------------------------------------- /.github/workflows/automerge.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot auto-merge 2 | on: pull_request 3 | 4 | permissions: 5 | pull-requests: write 6 | contents: write 7 | 8 | jobs: 9 | dependabot: 10 | runs-on: ubuntu-latest 11 | if: ${{ github.actor == 'dependabot[bot]' }} 12 | steps: 13 | - name: Dependabot metadata 14 | id: metadata 15 | uses: dependabot/fetch-metadata@v1.1.1 16 | with: 17 | github-token: '${{ secrets.GITHUB_TOKEN }}' 18 | - name: Enable auto-merge for theme 19 | if: ${{contains(steps.metadata.outputs.dependency-names, '@vue/theme') && steps.metadata.outputs.update-type != 'version-update:semver-major'}} 20 | run: gh pr merge --auto --merge "$PR_URL" 21 | env: 22 | PR_URL: ${{github.event.pull_request.html_url}} 23 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### OSX ### 2 | # General 3 | .DS_Store 4 | .AppleDouble 5 | .LSOverride 6 | 7 | # Thumbnails 8 | ._* 9 | 10 | # Files that might appear in the root of a volume 11 | .DocumentRevisions-V100 12 | .fseventsd 13 | .Spotlight-V100 14 | .TemporaryItems 15 | .Trashes 16 | .VolumeIcon.icns 17 | .com.apple.timemachine.donotpresent 18 | 19 | # Directories potentially created on remote AFP share 20 | .AppleDB 21 | .AppleDesktop 22 | Network Trash Folder 23 | Temporary Items 24 | .apdisk 25 | 26 | ### Node ### 27 | # Logs 28 | logs 29 | *.log 30 | npm-debug.log* 31 | yarn-debug.log* 32 | yarn-error.log* 33 | lerna-debug.log* 34 | 35 | # Diagnostic reports (https://nodejs.org/api/report.html) 36 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 37 | 38 | # Runtime data 39 | pids 40 | *.pid 41 | *.seed 42 | *.pid.lock 43 | 44 | # Directory for instrumented libs generated by jscoverage/JSCover 45 | lib-cov 46 | 47 | # Coverage directory used by tools like istanbul 48 | coverage 49 | *.lcov 50 | 51 | # nyc test coverage 52 | .nyc_output 53 | 54 | # node-waf configuration 55 | .lock-wscript 56 | 57 | # Compiled binary addons (https://nodejs.org/api/addons.html) 58 | build/Release 59 | 60 | # Dependency directories 61 | node_modules/ 62 | jspm_packages/ 63 | 64 | # TypeScript v1 declaration files 65 | typings/ 66 | 67 | # TypeScript cache 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | .npm 72 | 73 | # Optional eslint cache 74 | .eslintcache 75 | 76 | # Optional REPL history 77 | .node_repl_history 78 | 79 | # Output of 'npm pack' 80 | *.tgz 81 | 82 | # Yarn Integrity file 83 | .yarn-integrity 84 | 85 | # pnpm link folder 86 | pnpm-global 87 | 88 | # dotenv environment variables file 89 | .env 90 | .env.test 91 | 92 | # parcel-bundler cache (https://parceljs.org/) 93 | .cache 94 | 95 | # rollup.js default build output 96 | dist/ 97 | 98 | # vitepress build output 99 | .vitepress/dist 100 | .vitepress/cache 101 | 102 | # Serverless directories 103 | .serverless/ 104 | 105 | # Temporary folders 106 | tmp/ 107 | temp/ 108 | TODOs.md 109 | src/api/index.json 110 | src/examples/data.json 111 | src/tutorial/data.json 112 | draft.md 113 | 114 | .idea 115 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "none", 5 | "printWidth": 75 6 | } 7 | -------------------------------------------------------------------------------- /.vitepress/headerMdPlugin.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A markdown-it plugin to support custom header metadata 3 | * Headers that end with * are Options API only 4 | * Headers that end with ** are Composition API only 5 | * This plugin strips the markers and augments the extracted header data, 6 | * which can be then used by the theme to filter headers. 7 | * 8 | * TODO: we will likely also need special syntax for preserving the same anchor 9 | * links across translations similar to the one at 10 | * https://github.com/vitejs/docs-cn/tree/main/.vitepress/markdown-it-custom-anchor 11 | */ 12 | 13 | import MarkdownIt from 'markdown-it' 14 | import { Header } from 'vitepress' 15 | 16 | export interface AugmentedHeader extends Header { 17 | compositionOnly?: boolean 18 | optionsOnly?: boolean 19 | } 20 | 21 | export const headerPlugin = (md: MarkdownIt) => { 22 | md.renderer.rules.heading_open = (tokens, i, options, env, self) => { 23 | for (const child of tokens[i + 1].children!) { 24 | if (child.type === 'text' && child.content.endsWith('*')) { 25 | child.content = child.content.replace(/\s*\*+$/, '') 26 | } 27 | } 28 | return self.renderToken(tokens, i, options) 29 | } 30 | 31 | const render = md.render 32 | md.render = (content, env) => { 33 | const res = render(content, env) 34 | 35 | if (env && env.headers) { 36 | processHeaders(env.headers) 37 | } 38 | 39 | return res 40 | } 41 | } 42 | 43 | function processHeaders(headers: AugmentedHeader[]) { 44 | for (const h of headers) { 45 | if (h.title.endsWith('*')) { 46 | if (h.title.endsWith('**')) { 47 | h.compositionOnly = true 48 | } else { 49 | h.optionsOnly = true 50 | } 51 | h.title = h.title.replace(/\s*\*+$/, '') 52 | } 53 | if (h.children) { 54 | processHeaders(h.children) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /.vitepress/inlined-scripts/restorePreference.js: -------------------------------------------------------------------------------- 1 | ;(() => { 2 | const restore = (key, cls, def = false) => { 3 | const saved = localStorage.getItem(key) 4 | if (saved ? saved !== 'false' : def) { 5 | document.documentElement.classList.add(cls) 6 | } 7 | } 8 | restore('vue-docs-prefer-composition', 'prefer-composition', true) 9 | restore('vue-docs-prefer-sfc', 'prefer-sfc', true) 10 | 11 | window.__VUE_BANNER_ID__ = 'herodevs-vue2-nes' 12 | restore(`vue-docs-banner-${__VUE_BANNER_ID__}`, 'banner-dismissed') 13 | })() 14 | -------------------------------------------------------------------------------- /.vitepress/textAdMdPlugin.ts: -------------------------------------------------------------------------------- 1 | import MarkdownIt from 'markdown-it' 2 | 3 | const excludedPages = [ 4 | 'guide/introduction.md', 5 | // 'guide/quick-start.md', 6 | // 'guide/essentials/computed.md', 7 | // 'guide/essentials/conditional.md', 8 | // 'guide/essentials/list.md', 9 | // 'guide/essentials/event-handling.md', 10 | // 'guide/essentials/forms.md', 11 | // 'guide/components/registration.md', 12 | // 'guide/components/props.md', 13 | // 'guide/components/events.md', 14 | // 'guide/components/slots.md', 15 | // 'guide/built-ins/teleport.md', 16 | 'about/faq.md', 17 | 'about/team.md', 18 | 'about/releases.md', 19 | 'about/community-guide.md', 20 | 'about/coc.md', 21 | 'sponsor/index.md', 22 | 'translations/index.md' 23 | ] 24 | 25 | export const textAdPlugin = (md: MarkdownIt) => { 26 | md.renderer.rules.heading_close = (tokens, i, options, env, self) => { 27 | const relativePath = env.relativePath 28 | const renderedContent = self.renderToken(tokens, i, options) 29 | 30 | return excludedPages.includes(relativePath) 31 | ? renderedContent 32 | : renderedContent.replace(/<\/h1>/, '') 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.vitepress/theme/components/ReplLoading.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 60 | -------------------------------------------------------------------------------- /.vitepress/theme/components/SiteMap.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 27 | 28 | 68 | -------------------------------------------------------------------------------- /.vitepress/theme/components/SponsorsAside.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 14 | 15 | 26 | -------------------------------------------------------------------------------- /.vitepress/theme/components/TextAd.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | 20 | 54 | -------------------------------------------------------------------------------- /.vitepress/theme/components/VueSchoolLink.vue: -------------------------------------------------------------------------------- 1 | 13 | 21 | 60 | -------------------------------------------------------------------------------- /.vitepress/theme/components/preferences.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | import { AugmentedHeader } from '../../headerMdPlugin' 3 | 4 | export const inBrowser = typeof window !== 'undefined' 5 | const get = (key: string, defaultValue = false): boolean => 6 | inBrowser 7 | ? JSON.parse(localStorage.getItem(key) || String(defaultValue)) 8 | : defaultValue 9 | 10 | export const preferCompositionKey = 'vue-docs-prefer-composition' 11 | export const preferComposition = ref(get(preferCompositionKey, true)) 12 | 13 | export const preferSFCKey = 'vue-docs-prefer-sfc' 14 | export const preferSFC = ref(get(preferSFCKey, true)) 15 | 16 | export function filterHeadersByPreference(h: AugmentedHeader) { 17 | return preferComposition.value ? !h.optionsOnly : !h.compositionOnly 18 | } 19 | -------------------------------------------------------------------------------- /.vitepress/theme/components/sponsors.ts: -------------------------------------------------------------------------------- 1 | // shared data across instances so we load only once 2 | 3 | import { ref } from 'vue' 4 | 5 | declare global { 6 | const fathom: { 7 | trackGoal: (id: string, value: number) => any 8 | } 9 | } 10 | 11 | export interface Sponsor { 12 | url: string 13 | img: string 14 | name: string 15 | description?: string 16 | priority?: boolean 17 | } 18 | 19 | export interface SponsorData { 20 | special: Sponsor[] 21 | platinum: Sponsor[] 22 | platinum_china: Sponsor[] 23 | gold: Sponsor[] 24 | silver: Sponsor[] 25 | bronze: Sponsor[] 26 | } 27 | 28 | export const data = ref() 29 | export const pending = ref(false) 30 | 31 | export const base = `https://sponsors.vuejs.org` 32 | 33 | export const load = async () => { 34 | if (!pending.value) { 35 | pending.value = true 36 | data.value = await (await fetch(`${base}/data.json`)).json() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | import './styles/index.css' 2 | import { h, App } from 'vue' 3 | import { VPTheme } from '@vue/theme' 4 | import PreferenceSwitch from './components/PreferenceSwitch.vue' 5 | import { 6 | preferComposition, 7 | preferSFC, 8 | filterHeadersByPreference 9 | } from './components/preferences' 10 | import SponsorsAside from './components/SponsorsAside.vue' 11 | import VueSchoolLink from './components/VueSchoolLink.vue' 12 | import Banner from './components/Banner.vue' 13 | import VueMasteryBanner from './components/VueMasteryBanner.vue' 14 | // import TextAd from './components/TextAd.vue' 15 | 16 | export default Object.assign({}, VPTheme, { 17 | Layout: () => { 18 | // @ts-ignore 19 | return h(VPTheme.Layout, null, { 20 | // banner: () => h(Banner), 21 | banner: () => h(VueMasteryBanner), 22 | 'sidebar-top': () => h(PreferenceSwitch), 23 | 'aside-mid': () => h(SponsorsAside) 24 | }) 25 | }, 26 | enhanceApp({ app }: { app: App }) { 27 | app.provide('prefer-composition', preferComposition) 28 | app.provide('prefer-sfc', preferSFC) 29 | app.provide('filter-headers', filterHeadersByPreference) 30 | app.component('VueSchoolLink', VueSchoolLink) 31 | // app.component('TextAd', TextAd) 32 | } 33 | }) 34 | -------------------------------------------------------------------------------- /.vitepress/theme/styles/badges.css: -------------------------------------------------------------------------------- 1 | .vt-badge.wip:before { 2 | content: 'WIP'; 3 | } 4 | 5 | .vt-badge.ts { 6 | background-color: #3178c6; 7 | } 8 | .vt-badge.ts:before { 9 | content: 'TS'; 10 | } 11 | 12 | .vt-badge.dev-only, 13 | .vt-badge.experimental { 14 | color: var(--vt-c-text-light-1); 15 | background-color: var(--vt-c-yellow); 16 | } 17 | 18 | .vt-badge.dev-only:before { 19 | content: 'Dev only'; 20 | } 21 | 22 | .vt-badge.experimental:before { 23 | content: 'Experimental'; 24 | } 25 | 26 | .vt-badge[data-text]:before { 27 | content: attr(data-text); 28 | } 29 | -------------------------------------------------------------------------------- /.vitepress/theme/styles/index.css: -------------------------------------------------------------------------------- 1 | @import "./pages.css"; 2 | @import "./badges.css"; 3 | @import "./options-boxes.css"; 4 | @import "./inline-demo.css"; 5 | @import "./utilities.css"; 6 | @import "./style-guide.css"; 7 | 8 | /* vitepress rc.31 migrated to shijiki and need this to apply code styles */ 9 | .vp-code span { 10 | color: var(--shiki-dark, inherit); 11 | } 12 | -------------------------------------------------------------------------------- /.vitepress/theme/styles/inline-demo.css: -------------------------------------------------------------------------------- 1 | .vt-doc a[href^="https://play.vuejs.org"]:before 2 | { 3 | content: '▶'; 4 | width: 20px; 5 | height: 20px; 6 | display: inline-block; 7 | border-radius: 10px; 8 | vertical-align: middle; 9 | position: relative; 10 | top: -2px; 11 | color: var(--vt-c-green); 12 | border: 2px solid var(--vt-c-green); 13 | margin-right: 8px; 14 | margin-left: 4px; 15 | line-height: 15px; 16 | padding-left: 4.5px; 17 | font-size: 11px; 18 | } 19 | 20 | .demo { 21 | padding: 22px 24px; 22 | border-radius: 8px; 23 | box-shadow: var(--vt-shadow-2); 24 | margin-bottom: 1.2em; 25 | transition: background-color 0.5s ease; 26 | } 27 | 28 | .dark .demo { 29 | background-color: var(--vt-c-bg-soft); 30 | } 31 | 32 | .demo p { 33 | margin: 0; 34 | } 35 | 36 | .demo button { 37 | background-color: var(--vt-c-bg-mute); 38 | transition: background-color 0.5s; 39 | padding: 5px 12px; 40 | border: 1px solid var(--vt-c-divider); 41 | border-radius: 8px; 42 | font-size: 0.9em; 43 | font-weight: 600; 44 | } 45 | 46 | .demo button + button { 47 | margin-left: 1em; 48 | } 49 | 50 | .demo input, 51 | .demo textarea, 52 | .demo select { 53 | border: 1px solid var(--vt-c-divider); 54 | border-radius: 4px; 55 | padding: 0.2em 0.6em; 56 | margin-top: 10px; 57 | background: transparent; 58 | transition: background-color 0.5s; 59 | } 60 | 61 | .dark .demo select { 62 | background: var(--vt-c-bg-soft); 63 | } 64 | 65 | .dark .demo select option { 66 | background: transparent; 67 | } 68 | 69 | .demo input:not([type]):focus, 70 | .demo textarea:focus, 71 | .demo select:focus { 72 | outline: 1px solid blue; 73 | } 74 | 75 | .demo select { 76 | /* this was set by normalize.css */ 77 | -webkit-appearance: listbox; 78 | } 79 | 80 | .demo label { 81 | margin: 0 1em 0 0.4em; 82 | } 83 | 84 | .demo select[multiple] { 85 | width: 100px; 86 | } 87 | 88 | .demo h1 { 89 | margin: 10px 0 0; 90 | } 91 | -------------------------------------------------------------------------------- /.vitepress/theme/styles/options-boxes.css: -------------------------------------------------------------------------------- 1 | .next-steps { 2 | margin-top: 3rem; 3 | } 4 | 5 | .next-steps .vt-box { 6 | border: 1px solid var(--vt-c-bg-soft); 7 | } 8 | 9 | .next-steps .vt-box:hover { 10 | border-color: var(--vt-c-green-light); 11 | transition: border-color 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); 12 | } 13 | 14 | .vt-doc .next-steps-link { 15 | font-size: 20px; 16 | line-height: 1.4; 17 | letter-spacing: -0.02em; 18 | margin-bottom: 0.75em; 19 | display: block; 20 | color: var(--vt-c-green); 21 | } 22 | 23 | .vt-doc .next-steps-caption { 24 | margin-bottom: 0; 25 | color: var(--vt-c-text-2); 26 | transition: color 0.5s; 27 | } 28 | -------------------------------------------------------------------------------- /.vitepress/theme/styles/pages.css: -------------------------------------------------------------------------------- 1 | /* always show anchors on /api/ and /style-guide/ pages */ 2 | .vt-doc.api h2 .header-anchor, 3 | .vt-doc.style-guide h2 .header-anchor { 4 | opacity: 1; 5 | } 6 | 7 | .vt-doc.sponsor h3 { 8 | text-align: center; 9 | padding-bottom: 1em; 10 | border-bottom: 1px solid var(--vt-c-divider-light); 11 | } 12 | 13 | .vt-doc.sponsor h3 .header-anchor { 14 | display: none; 15 | } 16 | -------------------------------------------------------------------------------- /.vitepress/theme/styles/style-guide.css: -------------------------------------------------------------------------------- 1 | .style-example { 2 | border-radius: 8px 8px 12px 12px; 3 | margin: 1.6em 0; 4 | padding: 1.6em 1.6em 0.1px; 5 | position: relative; 6 | border: 1px solid transparent; 7 | transition: background-color 0.25s ease, border-color 0.25s ease; 8 | } 9 | 10 | .vt-doc .style-example h3 { 11 | margin: 0; 12 | font-size: 1.1em; 13 | } 14 | 15 | .style-example-bad { 16 | background: #f7e8e8; 17 | } 18 | .dark .style-example-bad { 19 | background: transparent; 20 | border-color: var(--vt-c-red); 21 | } 22 | 23 | .style-example-bad h3 { 24 | color: var(--vt-c-red); 25 | } 26 | 27 | .style-example-good { 28 | background: #ecfaf7; 29 | } 30 | .dark .style-example-good { 31 | background: transparent; 32 | border-color: var(--vt-c-green); 33 | } 34 | 35 | .style-example-good h3 { 36 | color: var(--vt-c-green); 37 | } 38 | 39 | .details summary { 40 | font-weight: bold !important; 41 | } 42 | 43 | .style-verb { 44 | font-size: 0.6em; 45 | display: inline-block; 46 | border-radius: 6px; 47 | font-size: 0.65em; 48 | line-height: 1; 49 | font-weight: 600; 50 | padding: 0.35em 0.4em 0.3em; 51 | position: relative; 52 | top: -0.15em; 53 | margin-right: 0.5em; 54 | color: var(--vt-c-bg); 55 | transition: color 0.5s; 56 | background-color: var(--vt-c-brand); 57 | } 58 | 59 | .style-verb.avoid { 60 | background-color: var(--vt-c-red); 61 | } 62 | -------------------------------------------------------------------------------- /.vitepress/theme/styles/utilities.css: -------------------------------------------------------------------------------- 1 | .nowrap { 2 | white-space: nowrap; 3 | } 4 | 5 | .sr-only { 6 | position: absolute; 7 | width: 1px; 8 | height: 1px; 9 | padding: 0; 10 | margin: -1px; 11 | overflow: hidden; 12 | clip: rect(0, 0, 0, 0); 13 | border: 0; 14 | } 15 | -------------------------------------------------------------------------------- /.vitepress/theme/styles/vue-mastery.css: -------------------------------------------------------------------------------- 1 | .vue-mastery-link { 2 | background-color: var(--vt-c-bg-soft); 3 | border-radius: 8px; 4 | padding: 8px 16px 8px 8px; 5 | transition: color 0.5s, background-color 0.5s; 6 | } 7 | 8 | .vue-mastery-link a { 9 | display: flex; 10 | align-items: center; 11 | } 12 | 13 | .vue-mastery-link .banner { 14 | background-color: var(--vt-c-white-soft); 15 | border-radius: 4px; 16 | width: 96px; 17 | height: 56px; 18 | object-fit: cover; 19 | } 20 | 21 | .vue-mastery-link .description { 22 | flex: 1; 23 | font-weight: 500; 24 | font-size: 14px; 25 | line-height: 20px; 26 | color: var(--vt-c-text-1); 27 | margin: 0 0 0 16px; 28 | transition: color 0.5s; 29 | } 30 | 31 | .vue-mastery-link .description span { 32 | color: var(--vt-c-brand); 33 | } 34 | 35 | .vue-mastery-link .logo-wrapper { 36 | position: relative; 37 | width: 48px; 38 | height: 48px; 39 | border-radius: 50%; 40 | background-color: var(--vt-c-white); 41 | display: flex; 42 | justify-content: center; 43 | align-items: center; 44 | } 45 | 46 | .vue-mastery-link .logo-wrapper img { 47 | width: 25px; 48 | object-fit: contain; 49 | } 50 | 51 | @media (max-width: 576px) { 52 | .vue-mastery-link .banner { 53 | width: 56px; 54 | } 55 | 56 | .vue-mastery-link .description { 57 | font-size: 12px; 58 | line-height: 18px; 59 | } 60 | .vue-mastery-link .logo-wrapper { 61 | position: relative; 62 | width: 32px; 63 | height: 32px; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 vuejs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '@vue/theme/config' { 4 | import { UserConfig } from 'vitepress' 5 | const config: () => Promise 6 | export default config 7 | } 8 | 9 | declare module '@vue/theme/highlight' { 10 | const createHighlighter: () => Promise<(input: string) => string> 11 | export default createHighlighter 12 | } 13 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build.environment] 2 | NODE_VERSION = "16" 3 | 4 | [build] 5 | publish = ".vitepress/dist" 6 | command = "pnpm run build" 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "engines": { 3 | "node": ">=14.0.0" 4 | }, 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vitepress", 8 | "build": "vitepress build", 9 | "preview": "vitepress preview", 10 | "preinstall": "npx only-allow pnpm" 11 | }, 12 | "dependencies": { 13 | "@vue/repl": "^3.0.0", 14 | "@vue/theme": "^2.2.5", 15 | "dynamics.js": "^1.1.5", 16 | "gsap": "^3.9.0", 17 | "vitepress": "1.0.0-rc.33", 18 | "vue": "^3.4.0-rc.3" 19 | }, 20 | "devDependencies": { 21 | "@types/markdown-it": "^12.2.3", 22 | "@types/node": "^20.10.1", 23 | "terser": "^5.14.2" 24 | }, 25 | "pnpm": { 26 | "overrides": { 27 | "@vitejs/plugin-vue": "5.0.0-beta.1" 28 | }, 29 | "peerDependencyRules": { 30 | "ignoreMissing": [ 31 | "@algolia/client-search", 32 | "react", 33 | "react-dom", 34 | "@types/react", 35 | "search-insights" 36 | ] 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/about/images/ben-hong.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/about/images/ben-hong.jpeg -------------------------------------------------------------------------------- /src/about/images/evan-you.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/about/images/evan-you.jpeg -------------------------------------------------------------------------------- /src/about/team.md: -------------------------------------------------------------------------------- 1 | --- 2 | page: true 3 | title: Meet the Team 4 | --- 5 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/about/team/Member.ts: -------------------------------------------------------------------------------- 1 | export interface Member { 2 | name: string 3 | avatarPic?: string 4 | title: string 5 | company?: string 6 | companyLink?: string 7 | projects: Link[] 8 | location: string 9 | languages: string[] 10 | website?: Link 11 | socials: Socials 12 | sponsor?: boolean | string 13 | reposPersonal?: string[] 14 | } 15 | 16 | export interface Link { 17 | label: string 18 | url: string 19 | } 20 | 21 | export interface Socials { 22 | github: string 23 | twitter?: string 24 | linkedin?: string 25 | codepen?: string 26 | } 27 | -------------------------------------------------------------------------------- /src/about/team/TeamHero.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 76 | -------------------------------------------------------------------------------- /src/api/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: API Reference 3 | sidebar: false 4 | page: true 5 | footer: false 6 | --- 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/ecosystem/newsletters.md: -------------------------------------------------------------------------------- 1 | # Інформаційні бюлетені спільноти {#community-newsletters} 2 | 3 | Є багато чудових інформаційних бюлетенів / блогів, присвячених Vue, від спільноти, які повідомляють вам останні новини та події в екосистемі Vue. Ось неповний список активних, на які ми натрапили: 4 | 5 | - [Vue.js Feed](https://vuejsfeed.com/) 6 | - [Michael Thiessen](https://michaelnthiessen.com/newsletter) 7 | - [Jakub Andrzejewski](https://dev.to/jacobandrewsky) 8 | - [Weekly Vue News](https://weekly-vue.news/) 9 | - [Vue.js Developers Newsletter](https://vuejsdevelopers.com/newsletter/) 10 | - [VueDose](https://vuedose.tips/articles#newsletter) 11 | 12 | Якщо ви знаєте чудовий, якого ще немає, надішліть запит на отримання за посиланням нижче! 13 | -------------------------------------------------------------------------------- /src/ecosystem/themes.md: -------------------------------------------------------------------------------- 1 | --- 2 | page: true 3 | --- 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/ecosystem/themes/ThemeContact.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 50 | -------------------------------------------------------------------------------- /src/ecosystem/themes/ThemeHero.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 56 | -------------------------------------------------------------------------------- /src/ecosystem/themes/ThemeList.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 15 | 16 | 42 | -------------------------------------------------------------------------------- /src/ecosystem/themes/ThemePage.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 21 | -------------------------------------------------------------------------------- /src/ecosystem/themes/ThemeProduct.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 30 | 31 | 99 | -------------------------------------------------------------------------------- /src/examples/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | page: true 3 | title: Examples 4 | aside: false 5 | footer: false 6 | outline: false 7 | --- 8 | 9 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/examples/src/attribute-bindings/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export default { 4 | setup() { 5 | const message = ref('Привіт, світе!') 6 | const isRed = ref(true) 7 | const color = ref('green') 8 | 9 | function toggleRed() { 10 | isRed.value = !isRed.value 11 | } 12 | 13 | function toggleColor() { 14 | color.value = color.value === 'green' ? 'blue' : 'green' 15 | } 16 | 17 | return { 18 | message, 19 | isRed, 20 | color, 21 | toggleRed, 22 | toggleColor 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/examples/src/attribute-bindings/App/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | message: 'Привіт, світе!', 5 | isRed: true, 6 | color: 'green' 7 | } 8 | }, 9 | methods: { 10 | toggleRed() { 11 | this.isRed = !this.isRed 12 | }, 13 | toggleColor() { 14 | this.color = this.color === 'green' ? 'blue' : 'green' 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/examples/src/attribute-bindings/App/style.css: -------------------------------------------------------------------------------- 1 | .red { 2 | color: red; 3 | } -------------------------------------------------------------------------------- /src/examples/src/attribute-bindings/App/template.html: -------------------------------------------------------------------------------- 1 |

2 | 3 | Наведіть на мене вказівник миші на кілька секунд, щоб побачити мій 4 | динамічно пов'язаний заголовок! 5 | 6 |

7 | 8 | 12 |

13 | Це має бути червоним... але натисніть мене, щоб перемкнути це. 14 |

15 | 16 | 17 |

18 | Це має бути зеленим і має перемикатися між зеленим і синім після 19 | натискання. 20 |

21 | -------------------------------------------------------------------------------- /src/examples/src/attribute-bindings/description.txt: -------------------------------------------------------------------------------- 1 | Тут ми прив'язуємо реактивно атрибути / властивості елемента до стану. 2 | Синтаксис :title є скороченням від v-bind:title. 3 | -------------------------------------------------------------------------------- /src/examples/src/cells/App/composition.js: -------------------------------------------------------------------------------- 1 | import Cell from './Cell.vue' 2 | import { cells } from './store.js' 3 | 4 | export default { 5 | components: { 6 | Cell 7 | }, 8 | setup() { 9 | const cols = cells.map((_, i) => String.fromCharCode(65 + i)) 10 | return { 11 | cols, 12 | cells 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/examples/src/cells/App/options.js: -------------------------------------------------------------------------------- 1 | import Cell from './Cell.vue' 2 | import { cells } from './store.js' 3 | 4 | export default { 5 | components: { 6 | Cell 7 | }, 8 | data() { 9 | return { 10 | cols: cells.map((_, i) => String.fromCharCode(65 + i)), 11 | cells 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/examples/src/cells/App/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | table { 6 | border-collapse: collapse; 7 | table-layout: fixed; 8 | width: 100%; 9 | } 10 | 11 | th { 12 | background-color: #eee; 13 | } 14 | 15 | tr:first-of-type th { 16 | width: 100px; 17 | } 18 | 19 | tr:first-of-type th:first-of-type { 20 | width: 25px; 21 | } 22 | 23 | td { 24 | border: 1px solid #ccc; 25 | height: 1.5em; 26 | overflow: hidden; 27 | } 28 | -------------------------------------------------------------------------------- /src/examples/src/cells/App/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 |
{{ c }}
{{ i - 1 }} 12 | 13 |
17 | -------------------------------------------------------------------------------- /src/examples/src/cells/Cell/composition.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | import { cells, evalCell } from './store.js' 3 | 4 | export default { 5 | props: { 6 | c: Number, 7 | r: Number 8 | }, 9 | setup(props) { 10 | const editing = ref(false) 11 | 12 | function update(e) { 13 | editing.value = false 14 | cells[props.c][props.r] = e.target.value.trim() 15 | } 16 | 17 | return { 18 | cells, 19 | editing, 20 | evalCell, 21 | update 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/examples/src/cells/Cell/options.js: -------------------------------------------------------------------------------- 1 | import { cells, evalCell } from './store.js' 2 | 3 | export default { 4 | props: { 5 | c: Number, 6 | r: Number 7 | }, 8 | data() { 9 | return { 10 | editing: false, 11 | cells 12 | } 13 | }, 14 | methods: { 15 | evalCell, 16 | update(e) { 17 | this.editing = false 18 | cells[this.c][this.r] = e.target.value.trim() 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/examples/src/cells/Cell/style.css: -------------------------------------------------------------------------------- 1 | .cell, .cell input { 2 | height: 1.5em; 3 | line-height: 1.5; 4 | font-size: 15px; 5 | } 6 | 7 | .cell span { 8 | padding: 0 6px; 9 | } 10 | 11 | .cell input { 12 | width: 100%; 13 | box-sizing: border-box; 14 | } -------------------------------------------------------------------------------- /src/examples/src/cells/Cell/template.html: -------------------------------------------------------------------------------- 1 |
2 | 9 | {{ evalCell(cells[c][r]) }} 10 |
11 | -------------------------------------------------------------------------------- /src/examples/src/cells/description.txt: -------------------------------------------------------------------------------- 1 | https://eugenkiss.github.io/7guis/tasks/#cells -------------------------------------------------------------------------------- /src/examples/src/cells/store.js: -------------------------------------------------------------------------------- 1 | import { reactive } from 'vue' 2 | 3 | const COLS = 5 4 | const ROWS = 20 5 | 6 | export const cells = reactive( 7 | Array.from(Array(COLS).keys()).map((i) => 8 | Array.from(Array(ROWS).keys()).map((i) => '') 9 | ) 10 | ) 11 | 12 | // adapted from https://codesandbox.io/s/jotai-7guis-task7-cells-mzoit?file=/src/atoms.ts 13 | // by @dai-shi 14 | export function evalCell(exp) { 15 | if (!exp.startsWith('=')) { 16 | return exp 17 | } 18 | 19 | // = A1 + B2 ---> get(0,1) + get(1,2) 20 | exp = exp 21 | .slice(1) 22 | .replace( 23 | /\b([A-Z])(\d{1,2})\b/g, 24 | (_, c, r) => `get(${c.charCodeAt(0) - 65},${r})` 25 | ) 26 | 27 | try { 28 | return new Function('get', `return ${exp}`)(getCellValue) 29 | } catch (e) { 30 | return `#ERROR ${e}` 31 | } 32 | } 33 | 34 | function getCellValue(c, r) { 35 | const val = evalCell(cells[c][r]) 36 | const num = Number(val) 37 | return Number.isFinite(num) ? num : val 38 | } 39 | -------------------------------------------------------------------------------- /src/examples/src/circle-drawer/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref, shallowReactive, toRaw } from 'vue' 2 | 3 | export default { 4 | setup() { 5 | const history = shallowReactive([[]]) 6 | const index = ref(0) 7 | const circles = ref([]) 8 | const selected = ref() 9 | const adjusting = ref(false) 10 | 11 | function onClick({ clientX: x, clientY: y }) { 12 | if (adjusting.value) { 13 | adjusting.value = false 14 | selected.value = null 15 | push() 16 | return 17 | } 18 | 19 | selected.value = [...circles.value].reverse().find(({ cx, cy, r }) => { 20 | const dx = cx - x 21 | const dy = cy - y 22 | return Math.sqrt(dx * dx + dy * dy) <= r 23 | }) 24 | 25 | if (!selected.value) { 26 | circles.value.push({ 27 | cx: x, 28 | cy: y, 29 | r: 50 30 | }) 31 | push() 32 | } 33 | } 34 | 35 | function adjust(circle) { 36 | selected.value = circle 37 | adjusting.value = true 38 | } 39 | 40 | function push() { 41 | history.length = ++index.value 42 | history.push(clone(circles.value)) 43 | console.log(toRaw(history)) 44 | } 45 | 46 | function undo() { 47 | circles.value = clone(history[--index.value]) 48 | } 49 | 50 | function redo() { 51 | circles.value = clone(history[++index.value]) 52 | } 53 | 54 | function clone(circles) { 55 | return circles.map((c) => ({ ...c })) 56 | } 57 | 58 | return { 59 | history, 60 | index, 61 | circles, 62 | selected, 63 | adjusting, 64 | onClick, 65 | adjust, 66 | undo, 67 | redo 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/examples/src/circle-drawer/App/options.js: -------------------------------------------------------------------------------- 1 | function clone(circles) { 2 | return circles.map((c) => ({ ...c })) 3 | } 4 | 5 | export default { 6 | data() { 7 | return { 8 | history: [[]], 9 | index: 0, 10 | circles: [], 11 | selected: null, 12 | adjusting: false 13 | } 14 | }, 15 | methods: { 16 | onClick({ clientX: x, clientY: y }) { 17 | if (this.adjusting) { 18 | this.adjusting = false 19 | this.selected = null 20 | this.push() 21 | return 22 | } 23 | 24 | this.selected = [...this.circles].reverse().find(({ cx, cy, r }) => { 25 | const dx = cx - x 26 | const dy = cy - y 27 | return Math.sqrt(dx * dx + dy * dy) <= r 28 | }) 29 | 30 | if (!this.selected) { 31 | this.circles.push({ 32 | cx: x, 33 | cy: y, 34 | r: 50 35 | }) 36 | this.push() 37 | } 38 | }, 39 | 40 | adjust(circle) { 41 | this.selected = circle 42 | this.adjusting = true 43 | }, 44 | 45 | push() { 46 | this.history.length = ++this.index 47 | this.history.push(clone(this.circles)) 48 | }, 49 | 50 | undo() { 51 | this.circles = clone(this.history[--this.index]) 52 | }, 53 | 54 | redo() { 55 | this.circles = clone(this.history[++this.index]) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/examples/src/circle-drawer/App/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | overflow: hidden; 4 | } 5 | 6 | svg { 7 | width: 100vw; 8 | height: 100vh; 9 | background-color: #eee; 10 | } 11 | 12 | circle { 13 | stroke: #000; 14 | } 15 | 16 | .controls { 17 | position: fixed; 18 | top: 10px; 19 | left: 0; 20 | right: 0; 21 | text-align: center; 22 | } 23 | 24 | .controls button + button { 25 | margin-left: 6px; 26 | } 27 | 28 | .dialog { 29 | position: fixed; 30 | top: calc(50% - 50px); 31 | left: calc(50% - 175px); 32 | background: #fff; 33 | width: 350px; 34 | height: 100px; 35 | padding: 5px 20px; 36 | box-sizing: border-box; 37 | border-radius: 4px; 38 | text-align: center; 39 | box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.25); 40 | } 41 | 42 | .dialog input { 43 | display: block; 44 | width: 200px; 45 | margin: 0px auto; 46 | } 47 | 48 | .tip { 49 | text-align: center; 50 | padding: 0 50px; 51 | color: #bbb; 52 | } -------------------------------------------------------------------------------- /src/examples/src/circle-drawer/App/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | Клікніть на полотні, щоб намалювати коло. Клікніть на колі, щоб виділити його. 5 | Клікніть правою кнопкою миші на полотні, щоб змінити радіус вибраного кола. 6 |

7 |
8 | 17 |
18 | 19 |
20 | 21 | 22 |
23 | 24 |
25 |

Відрегулюйте радіус кола на ({{ selected.cx }}, {{ selected.cy }})

26 | 27 |
28 | -------------------------------------------------------------------------------- /src/examples/src/circle-drawer/description.txt: -------------------------------------------------------------------------------- 1 | https://eugenkiss.github.io/7guis/tasks/#circle -------------------------------------------------------------------------------- /src/examples/src/conditionals-and-loops/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export default { 4 | setup() { 5 | const show = ref(true) 6 | const list = ref([1, 2, 3]) 7 | 8 | return { 9 | show, 10 | list 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/examples/src/conditionals-and-loops/App/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | show: true, 5 | list: [1, 2, 3] 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /src/examples/src/conditionals-and-loops/App/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
    7 |
  • {{ item }}
  • 8 |
9 |

Список не порожній, але прихований.

10 |

Список порожній.

11 | -------------------------------------------------------------------------------- /src/examples/src/conditionals-and-loops/description.txt: -------------------------------------------------------------------------------- 1 | Ми можемо відтворювати вміст умовно або в циклі за допомогою директив v-if і v-for. 2 | -------------------------------------------------------------------------------- /src/examples/src/counter/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export default { 4 | setup() { 5 | const count = ref(0) 6 | 7 | return { 8 | count 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/examples/src/counter/App/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | count: 0 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /src/examples/src/counter/App/template.html: -------------------------------------------------------------------------------- 1 | {{ count }} 2 | 3 | -------------------------------------------------------------------------------- /src/examples/src/counter/description.txt: -------------------------------------------------------------------------------- 1 | https://eugenkiss.github.io/7guis/tasks/#counter -------------------------------------------------------------------------------- /src/examples/src/crud/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref, reactive, computed, watch } from 'vue' 2 | 3 | export default { 4 | setup() { 5 | const names = reactive(['Шевченко, Ірина', 'Матрунчик, Сергій', 'Федоренко, Роман']) 6 | const selected = ref('') 7 | const prefix = ref('') 8 | const first = ref('') 9 | const last = ref('') 10 | 11 | const filteredNames = computed(() => 12 | names.filter((n) => 13 | n.toLowerCase().startsWith(prefix.value.toLowerCase()) 14 | ) 15 | ) 16 | 17 | watch(selected, (name) => { 18 | ;[last.value, first.value] = name.split(', ') 19 | }) 20 | 21 | function create() { 22 | if (hasValidInput()) { 23 | const fullName = `${last.value}, ${first.value}` 24 | if (!names.includes(fullName)) { 25 | names.push(fullName) 26 | first.value = last.value = '' 27 | } 28 | } 29 | } 30 | 31 | function update() { 32 | if (hasValidInput() && selected.value) { 33 | const i = names.indexOf(selected.value) 34 | names[i] = selected.value = `${last.value}, ${first.value}` 35 | } 36 | } 37 | 38 | function del() { 39 | if (selected.value) { 40 | const i = names.indexOf(selected.value) 41 | names.splice(i, 1) 42 | selected.value = first.value = last.value = '' 43 | } 44 | } 45 | 46 | function hasValidInput() { 47 | return first.value.trim() && last.value.trim() 48 | } 49 | 50 | return { 51 | filteredNames, 52 | selected, 53 | prefix, 54 | first, 55 | last, 56 | create, 57 | update, 58 | del 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/examples/src/crud/App/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | names: ['Вознюк, Володимир', 'Матрунчик, Сергій', 'Федоренко, Роман'], 5 | selected: '', 6 | prefix: '', 7 | first: '', 8 | last: '' 9 | } 10 | }, 11 | computed: { 12 | filteredNames() { 13 | return this.names.filter((n) => 14 | n.toLowerCase().startsWith(this.prefix.toLowerCase()) 15 | ) 16 | } 17 | }, 18 | watch: { 19 | selected(name) { 20 | ;[this.last, this.first] = name.split(', ') 21 | } 22 | }, 23 | methods: { 24 | create() { 25 | if (this.hasValidInput()) { 26 | const fullName = `${this.last}, ${this.first}` 27 | if (!this.names.includes(fullName)) { 28 | this.names.push(fullName) 29 | this.first = this.last = '' 30 | } 31 | } 32 | }, 33 | update() { 34 | if (this.hasValidInput() && this.selected) { 35 | const i = this.names.indexOf(this.selected) 36 | this.names[i] = this.selected = `${this.last}, ${this.first}` 37 | } 38 | }, 39 | del() { 40 | if (this.selected) { 41 | const i = this.names.indexOf(this.selected) 42 | this.names.splice(i, 1) 43 | this.selected = this.first = this.last = '' 44 | } 45 | }, 46 | hasValidInput() { 47 | return this.first.trim() && this.last.trim() 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/examples/src/crud/App/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-size: inherit; 3 | } 4 | 5 | input { 6 | display: block; 7 | margin-bottom: 10px; 8 | } 9 | 10 | select { 11 | float: left; 12 | margin: 0 1em 1em 0; 13 | width: 14em; 14 | } 15 | 16 | .buttons { 17 | clear: both; 18 | } 19 | 20 | button + button { 21 | margin-left: 5px; 22 | } 23 | -------------------------------------------------------------------------------- /src/examples/src/crud/App/template.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 |
15 | -------------------------------------------------------------------------------- /src/examples/src/crud/description.txt: -------------------------------------------------------------------------------- 1 | https://eugenkiss.github.io/7guis/tasks/#crud -------------------------------------------------------------------------------- /src/examples/src/fetching-data/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref, watchEffect } from 'vue' 2 | 3 | const API_URL = `https://api.github.com/repos/vuejs/core/commits?per_page=3&sha=` 4 | const branches = ['main', 'v2-compat'] 5 | 6 | export default { 7 | setup() { 8 | const currentBranch = ref(branches[0]) 9 | const commits = ref(null) 10 | 11 | watchEffect(async () => { 12 | // цей метод буде запускатися негайно і потім 13 | // перезапускатися, коли змінюється currentBranch.value 14 | const url = `${API_URL}${currentBranch.value}` 15 | commits.value = await (await fetch(url)).json() 16 | }) 17 | 18 | function truncate(v) { 19 | const newline = v.indexOf('\n') 20 | return newline > 0 ? v.slice(0, newline) : v 21 | } 22 | 23 | function formatDate(v) { 24 | return v.replace(/T|Z/g, ' ') 25 | } 26 | 27 | return { 28 | branches, 29 | currentBranch, 30 | commits, 31 | truncate, 32 | formatDate 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/examples/src/fetching-data/App/options.js: -------------------------------------------------------------------------------- 1 | const API_URL = `https://api.github.com/repos/vuejs/core/commits?per_page=3&sha=` 2 | 3 | export default { 4 | data: () => ({ 5 | branches: ['main', 'v2-compat'], 6 | currentBranch: 'main', 7 | commits: null 8 | }), 9 | 10 | created() { 11 | // отримання даних при ініціалізації 12 | this.fetchData() 13 | }, 14 | 15 | watch: { 16 | // отримання дані щоразу, коли змінюється currentBranch 17 | currentBranch: 'fetchData' 18 | }, 19 | 20 | methods: { 21 | async fetchData() { 22 | const url = `${API_URL}${this.currentBranch}` 23 | this.commits = await (await fetch(url)).json() 24 | }, 25 | truncate(v) { 26 | const newline = v.indexOf('\n') 27 | return newline > 0 ? v.slice(0, newline) : v 28 | }, 29 | formatDate(v) { 30 | return v.replace(/T|Z/g, ' ') 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/examples/src/fetching-data/App/style.css: -------------------------------------------------------------------------------- 1 | a { 2 | text-decoration: none; 3 | color: #42b883; 4 | } 5 | li { 6 | line-height: 1.5em; 7 | margin-bottom: 20px; 8 | } 9 | .author, 10 | .date { 11 | font-weight: bold; 12 | } 13 | -------------------------------------------------------------------------------- /src/examples/src/fetching-data/App/template.html: -------------------------------------------------------------------------------- 1 |

Останні комміти ядра Vue

2 | 10 |

vuejs/vue@{{ currentBranch }}

11 | 21 | -------------------------------------------------------------------------------- /src/examples/src/fetching-data/description.txt: -------------------------------------------------------------------------------- 1 | Цей приклад отримує останні дані про комміти Vue.js з API GitHub і відображає їх у вигляді списку. 2 | Ви можете переключати дані між двома гілками. 3 | -------------------------------------------------------------------------------- /src/examples/src/flight-booker/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref, computed } from 'vue' 2 | 3 | export default { 4 | setup() { 5 | const flightType = ref('one-way flight') 6 | const departureDate = ref(dateToString(new Date())) 7 | const returnDate = ref(departureDate.value) 8 | 9 | const isReturn = computed(() => flightType.value === 'return flight') 10 | 11 | const canBook = computed( 12 | () => 13 | !isReturn.value || 14 | stringToDate(returnDate.value) > stringToDate(departureDate.value) 15 | ) 16 | 17 | function book() { 18 | alert( 19 | isReturn.value 20 | ? `Ви забронювали рейс з вильотом ${departureDate.value} та поверненням ${returnDate.value}.` 21 | : `Ви забронювали рейс в один бік на ${departureDate.value}.` 22 | ) 23 | } 24 | 25 | function stringToDate(str) { 26 | const [y, m, d] = str.split('-') 27 | return new Date(+y, m - 1, +d) 28 | } 29 | 30 | function dateToString(date) { 31 | return ( 32 | date.getFullYear() + 33 | '-' + 34 | pad(date.getMonth() + 1) + 35 | '-' + 36 | pad(date.getDate()) 37 | ) 38 | } 39 | 40 | function pad(n, s = String(n)) { 41 | return s.length < 2 ? `0${s}` : s 42 | } 43 | 44 | return { 45 | flightType, 46 | departureDate, 47 | returnDate, 48 | isReturn, 49 | canBook, 50 | book 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/examples/src/flight-booker/App/options.js: -------------------------------------------------------------------------------- 1 | function stringToDate(str) { 2 | const [y, m, d] = str.split('-') 3 | return new Date(+y, m - 1, +d) 4 | } 5 | 6 | function dateToString(date) { 7 | return ( 8 | date.getFullYear() + 9 | '-' + 10 | pad(date.getMonth() + 1) + 11 | '-' + 12 | pad(date.getDate()) 13 | ) 14 | } 15 | 16 | function pad(n, s = String(n)) { 17 | return s.length < 2 ? `0${s}` : s 18 | } 19 | 20 | export default { 21 | data() { 22 | return { 23 | flightType: 'one-way flight', 24 | departureDate: dateToString(new Date()), 25 | returnDate: dateToString(new Date()) 26 | } 27 | }, 28 | computed: { 29 | isReturn() { 30 | return this.flightType === 'return flight' 31 | }, 32 | canBook() { 33 | return ( 34 | !this.isReturn || 35 | stringToDate(this.returnDate) > stringToDate(this.departureDate) 36 | ) 37 | } 38 | }, 39 | methods: { 40 | book() { 41 | alert( 42 | this.isReturn 43 | ? `You have booked a return flight leaving on ${this.departureDate} and returning on ${this.returnDate}.` 44 | : `You have booked a one-way flight leaving on ${this.departureDate}.` 45 | ) 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/examples/src/flight-booker/App/style.css: -------------------------------------------------------------------------------- 1 | select, 2 | input, 3 | button { 4 | display: block; 5 | margin: 0.5em 0; 6 | font-size: 15px; 7 | } 8 | 9 | input[disabled] { 10 | color: #999; 11 | } 12 | 13 | p { 14 | color: red; 15 | } -------------------------------------------------------------------------------- /src/examples/src/flight-booker/App/template.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |

{{ canBook ? '' : 'Дата повернення повинна бути після дати вильоту..' }}

12 | -------------------------------------------------------------------------------- /src/examples/src/flight-booker/description.txt: -------------------------------------------------------------------------------- 1 | https://eugenkiss.github.io/7guis/tasks/#flight -------------------------------------------------------------------------------- /src/examples/src/form-bindings/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export default { 4 | setup() { 5 | const text = ref('Редагувати мене') 6 | const checked = ref(true) 7 | const checkedNames = ref(['Петро']) 8 | const picked = ref('Один') 9 | const selected = ref('А') 10 | const multiSelected = ref(['А']) 11 | 12 | return { 13 | text, 14 | checked, 15 | checkedNames, 16 | picked, 17 | selected, 18 | multiSelected 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/examples/src/form-bindings/App/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | text: 'Редагувати мене', 5 | checked: true, 6 | checkedNames: ['Петро'], 7 | picked: 'Один', 8 | selected: 'А', 9 | multiSelected: ['А'] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/examples/src/form-bindings/App/template.html: -------------------------------------------------------------------------------- 1 |

Введення тексту

2 | {{ text }} 3 | 4 |

Прапорець

5 | 6 | 7 | 8 | 12 |

Прапорці декількох значень

13 | 14 | 15 | 16 | 17 | 18 | 19 |

Відмічені імена:

{{ checkedNames }}

20 | 21 |

Радіобокс

22 | 23 | 24 |
25 | 26 | 27 |
28 | Обрано: {{ picked }} 29 | 30 |

Вибирання

31 | 37 | Обрано: {{ selected }} 38 | 39 |

Вибирання декількох значень

40 | 45 | Обрано: {{ multiSelected }} 46 | -------------------------------------------------------------------------------- /src/examples/src/form-bindings/description.txt: -------------------------------------------------------------------------------- 1 | Ми можемо створювати двосторонні зв'язки між вхідними даними стану та форми за допомогою директиви v-model. 2 | -------------------------------------------------------------------------------- /src/examples/src/grid/App/composition.js: -------------------------------------------------------------------------------- 1 | import DemoGrid from './Grid.vue' 2 | import { ref } from 'vue' 3 | 4 | export default { 5 | components: { 6 | DemoGrid 7 | }, 8 | setup() { 9 | const searchQuery = ref('') 10 | const gridColumns = ['імя', 'сила'] 11 | const gridData = [ 12 | { 'імя': 'Чак Норріс', 'сила': Infinity }, 13 | { 'імя': 'Брюс Лі', 'сила': 9000 }, 14 | { 'імя': 'Джекі Чан', 'сила': 7000 }, 15 | { 'імя': 'Джет Лі', 'сила': 8000 } 16 | ] 17 | 18 | return { 19 | searchQuery, 20 | gridColumns, 21 | gridData 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/examples/src/grid/App/options.js: -------------------------------------------------------------------------------- 1 | import DemoGrid from './Grid.vue' 2 | 3 | export default { 4 | components: { 5 | DemoGrid 6 | }, 7 | data: () => ({ 8 | searchQuery: '', 9 | gridColumns: ['імя', 'сила'], 10 | gridData: [ 11 | { 'імя': 'Чак Норріс', 'сила': Infinity }, 12 | { 'імя': 'Брюс Лі', 'сила': 9000 }, 13 | { 'імя': 'Джекі Чан', 'сила': 7000 }, 14 | { 'імя': 'Джет Лі', 'сила': 8000 } 15 | ] 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /src/examples/src/grid/App/template.html: -------------------------------------------------------------------------------- 1 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /src/examples/src/grid/Grid/composition.js: -------------------------------------------------------------------------------- 1 | import { ref, computed } from 'vue' 2 | 3 | export default { 4 | props: { 5 | data: Array, 6 | columns: Array, 7 | filterKey: String 8 | }, 9 | setup(props) { 10 | const sortKey = ref('') 11 | const sortOrders = ref( 12 | props.columns.reduce((o, key) => ((o[key] = 1), o), {}) 13 | ) 14 | 15 | const filteredData = computed(() => { 16 | let { data, filterKey } = props 17 | if (filterKey) { 18 | filterKey = filterKey.toLowerCase() 19 | data = data.filter((row) => { 20 | return Object.keys(row).some((key) => { 21 | return String(row[key]).toLowerCase().indexOf(filterKey) > -1 22 | }) 23 | }) 24 | } 25 | const key = sortKey.value 26 | if (key) { 27 | const order = sortOrders.value[key] 28 | data = data.slice().sort((a, b) => { 29 | a = a[key] 30 | b = b[key] 31 | return (a === b ? 0 : a > b ? 1 : -1) * order 32 | }) 33 | } 34 | return data 35 | }) 36 | 37 | function sortBy(key) { 38 | sortKey.value = key 39 | sortOrders.value[key] *= -1 40 | } 41 | 42 | function capitalize(str) { 43 | return str.charAt(0).toUpperCase() + str.slice(1) 44 | } 45 | 46 | return { 47 | sortKey, 48 | sortOrders, 49 | filteredData, 50 | sortBy, 51 | capitalize 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/examples/src/grid/Grid/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | data: Array, 4 | columns: Array, 5 | filterKey: String 6 | }, 7 | data() { 8 | return { 9 | sortKey: '', 10 | sortOrders: this.columns.reduce((o, key) => ((o[key] = 1), o), {}) 11 | } 12 | }, 13 | computed: { 14 | filteredData() { 15 | const sortKey = this.sortKey 16 | const filterKey = this.filterKey && this.filterKey.toLowerCase() 17 | const order = this.sortOrders[sortKey] || 1 18 | let data = this.data 19 | if (filterKey) { 20 | data = data.filter((row) => { 21 | return Object.keys(row).some((key) => { 22 | return String(row[key]).toLowerCase().indexOf(filterKey) > -1 23 | }) 24 | }) 25 | } 26 | if (sortKey) { 27 | data = data.slice().sort((a, b) => { 28 | a = a[sortKey] 29 | b = b[sortKey] 30 | return (a === b ? 0 : a > b ? 1 : -1) * order 31 | }) 32 | } 33 | return data 34 | } 35 | }, 36 | methods: { 37 | sortBy(key) { 38 | this.sortKey = key 39 | this.sortOrders[key] = this.sortOrders[key] * -1 40 | }, 41 | capitalize(str) { 42 | return str.charAt(0).toUpperCase() + str.slice(1) 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/examples/src/grid/Grid/style.css: -------------------------------------------------------------------------------- 1 | table { 2 | border: 2px solid #42b983; 3 | border-radius: 3px; 4 | background-color: #fff; 5 | } 6 | 7 | th { 8 | background-color: #42b983; 9 | color: rgba(255, 255, 255, 0.66); 10 | cursor: pointer; 11 | user-select: none; 12 | } 13 | 14 | td { 15 | background-color: #f9f9f9; 16 | } 17 | 18 | th, 19 | td { 20 | min-width: 120px; 21 | padding: 10px 20px; 22 | } 23 | 24 | th.active { 25 | color: #fff; 26 | } 27 | 28 | th.active .arrow { 29 | opacity: 1; 30 | } 31 | 32 | .arrow { 33 | display: inline-block; 34 | vertical-align: middle; 35 | width: 0; 36 | height: 0; 37 | margin-left: 5px; 38 | opacity: 0.66; 39 | } 40 | 41 | .arrow.asc { 42 | border-left: 4px solid transparent; 43 | border-right: 4px solid transparent; 44 | border-bottom: 4px solid #fff; 45 | } 46 | 47 | .arrow.dsc { 48 | border-left: 4px solid transparent; 49 | border-right: 4px solid transparent; 50 | border-top: 4px solid #fff; 51 | } 52 | -------------------------------------------------------------------------------- /src/examples/src/grid/Grid/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | 14 | 15 | 18 | 19 | 20 |
7 | {{ capitalize(key) }} 8 | 9 | 10 |
16 | {{entry[key]}} 17 |
21 |

Співпадінь не знайдено.

22 | -------------------------------------------------------------------------------- /src/examples/src/grid/description.txt: -------------------------------------------------------------------------------- 1 | Приклад створення багаторазового компонента таблиці та його використання із зовнішніми даними. 2 | -------------------------------------------------------------------------------- /src/examples/src/handling-input/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export default { 4 | setup() { 5 | const message = ref('Привіт світ!') 6 | 7 | function reverseMessage() { 8 | // Доступ/зміна значення реквізиту через 9 | // його властивість .value. 10 | message.value = message.value.split('').reverse().join('') 11 | } 12 | 13 | function notify() { 14 | alert('навігацію було припинено.') 15 | } 16 | 17 | return { 18 | message, 19 | reverseMessage, 20 | notify 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/examples/src/handling-input/App/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | message: 'Привіт світ!' 5 | } 6 | }, 7 | methods: { 8 | reverseMessage() { 9 | this.message = this.message.split('').reverse().join('') 10 | }, 11 | notify() { 12 | alert('навігацію було припинено.') 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/examples/src/handling-input/App/style.css: -------------------------------------------------------------------------------- 1 | button, a { 2 | display: block; 3 | margin-bottom: 1em; 4 | } -------------------------------------------------------------------------------- /src/examples/src/handling-input/App/template.html: -------------------------------------------------------------------------------- 1 | 6 |

{{ message }}

7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | 22 | Посилання з e.preventDefault() 23 | 24 | -------------------------------------------------------------------------------- /src/examples/src/handling-input/description.txt: -------------------------------------------------------------------------------- 1 | Цей приклад демонструє обробку введення користувача за допомогою директиви v-on. 2 | -------------------------------------------------------------------------------- /src/examples/src/hello-world/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export default { 4 | setup() { 5 | // A "ref" is a reactive data source that stores a value. 6 | // Technically, we don't need to wrap the string with ref() 7 | // in order to display it, but we will see in the next 8 | // example why it is needed if we ever intend to change 9 | // the value. 10 | const message = ref('Привіт, світе!') 11 | 12 | return { 13 | message 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/examples/src/hello-world/App/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | message: 'Привіт, світе!' 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/examples/src/hello-world/App/template.html: -------------------------------------------------------------------------------- 1 |

{{ message }}

-------------------------------------------------------------------------------- /src/examples/src/hello-world/description.txt: -------------------------------------------------------------------------------- 1 | Скажіть "Привіт, світе" разом з Vue! 2 | -------------------------------------------------------------------------------- /src/examples/src/list-transition/App/composition.js: -------------------------------------------------------------------------------- 1 | import { shuffle as _shuffle } from 'lodash-es' 2 | import { ref } from 'vue' 3 | 4 | export default { 5 | setup() { 6 | const getInitialItems = () => [1, 2, 3, 4, 5] 7 | const items = ref(getInitialItems()) 8 | let id = items.value.length + 1 9 | 10 | function insert() { 11 | const i = Math.round(Math.random() * items.value.length) 12 | items.value.splice(i, 0, id++) 13 | } 14 | 15 | function reset() { 16 | items.value = getInitialItems() 17 | id = items.value.length + 1 18 | } 19 | 20 | function shuffle() { 21 | items.value = _shuffle(items.value) 22 | } 23 | 24 | function remove(item) { 25 | const i = items.value.indexOf(item) 26 | if (i > -1) { 27 | items.value.splice(i, 1) 28 | } 29 | } 30 | 31 | return { 32 | items, 33 | insert, 34 | reset, 35 | shuffle, 36 | remove 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/examples/src/list-transition/App/options.js: -------------------------------------------------------------------------------- 1 | import { shuffle } from 'lodash-es' 2 | 3 | const getInitialItems = () => [1, 2, 3, 4, 5] 4 | let id = getInitialItems().length + 1 5 | 6 | export default { 7 | data() { 8 | return { 9 | items: getInitialItems() 10 | } 11 | }, 12 | methods: { 13 | insert() { 14 | const i = Math.round(Math.random() * this.items.length) 15 | this.items.splice(i, 0, id++) 16 | }, 17 | reset() { 18 | this.items = getInitialItems() 19 | id = getInitialItems().length + 1 20 | }, 21 | shuffle() { 22 | this.items = shuffle(this.items) 23 | }, 24 | remove(item) { 25 | const i = this.items.indexOf(item) 26 | if (i > -1) { 27 | this.items.splice(i, 1) 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/examples/src/list-transition/App/style.css: -------------------------------------------------------------------------------- 1 | .container { 2 | position: relative; 3 | padding: 0; 4 | } 5 | 6 | .item { 7 | width: 100%; 8 | height: 30px; 9 | background-color: #f3f3f3; 10 | border: 1px solid #666; 11 | box-sizing: border-box; 12 | } 13 | 14 | /* 1. Оголошення переходу */ 15 | .fade-move, 16 | .fade-enter-active, 17 | .fade-leave-active { 18 | transition: all 0.5s cubic-bezier(0.55, 0, 0.1, 1); 19 | } 20 | 21 | /* 2. Оголошення входу і виходу зі стану */ 22 | .fade-enter-from, 23 | .fade-leave-to { 24 | opacity: 0; 25 | transform: scaleY(0.01) translate(30px, 0); 26 | } 27 | 28 | /* 3. Переконайтеся, що елементи, які вилучаються з потоку макету 29 | є видалені, щоб анімації були розраховані коректно. */ 30 | .fade-leave-active { 31 | position: absolute; 32 | } 33 | -------------------------------------------------------------------------------- /src/examples/src/list-transition/App/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | {{ item }} 8 | 9 |
10 |
11 | -------------------------------------------------------------------------------- /src/examples/src/list-transition/description.txt: -------------------------------------------------------------------------------- 1 | FLIP переходи списку із вбудованим . 2 | https://aerotwist.com/blog/flip-your-animations/ 3 | -------------------------------------------------------------------------------- /src/examples/src/list-transition/import-map.json: -------------------------------------------------------------------------------- 1 | { 2 | "imports": { 3 | "lodash-es": "https://cdn.jsdelivr.net/npm/lodash-es/+esm" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/examples/src/markdown/App/composition.js: -------------------------------------------------------------------------------- 1 | import { marked } from 'marked' 2 | import { debounce } from 'lodash-es' 3 | import { ref, computed } from 'vue' 4 | 5 | export default { 6 | setup() { 7 | const input = ref('# привіт') 8 | 9 | const output = computed(() => marked(input.value)) 10 | 11 | const update = debounce((e) => { 12 | input.value = e.target.value 13 | }, 100) 14 | 15 | return { 16 | input, 17 | output, 18 | update 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/examples/src/markdown/App/options.js: -------------------------------------------------------------------------------- 1 | import { marked } from 'marked' 2 | import { debounce } from 'lodash-es' 3 | 4 | export default { 5 | data: () => ({ 6 | input: '# привіт' 7 | }), 8 | computed: { 9 | output() { 10 | return marked(this.input) 11 | } 12 | }, 13 | methods: { 14 | update: debounce(function (e) { 15 | this.input = e.target.value 16 | }, 100) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/examples/src/markdown/App/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | } 4 | 5 | .editor { 6 | height: 100vh; 7 | display: flex; 8 | } 9 | 10 | .input, 11 | .output { 12 | overflow: auto; 13 | width: 50%; 14 | height: 100%; 15 | box-sizing: border-box; 16 | padding: 0 20px; 17 | } 18 | 19 | .input { 20 | border: none; 21 | border-right: 1px solid #ccc; 22 | resize: none; 23 | outline: none; 24 | background-color: #f6f6f6; 25 | font-size: 14px; 26 | font-family: 'Monaco', courier, monospace; 27 | padding: 20px; 28 | } 29 | 30 | code { 31 | color: #f66; 32 | } 33 | -------------------------------------------------------------------------------- /src/examples/src/markdown/App/template.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 | -------------------------------------------------------------------------------- /src/examples/src/markdown/description.txt: -------------------------------------------------------------------------------- 1 | Простий редактор коду. 2 | -------------------------------------------------------------------------------- /src/examples/src/markdown/import-map.json: -------------------------------------------------------------------------------- 1 | { 2 | "imports": { 3 | "marked": "https://cdn.jsdelivr.net/npm/marked/+esm", 4 | "lodash-es": "https://cdn.jsdelivr.net/npm/lodash-es/+esm" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/examples/src/modal/App/composition.js: -------------------------------------------------------------------------------- 1 | import Modal from './Modal.vue' 2 | import { ref } from 'vue' 3 | 4 | export default { 5 | components: { 6 | Modal 7 | }, 8 | setup() { 9 | const showModal = ref(false) 10 | 11 | return { 12 | showModal 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/examples/src/modal/App/options.js: -------------------------------------------------------------------------------- 1 | import Modal from './Modal.vue' 2 | 3 | export default { 4 | components: { 5 | Modal 6 | }, 7 | data() { 8 | return { 9 | showModal: false 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/examples/src/modal/App/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/examples/src/modal/Modal/composition.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | show: Boolean 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/examples/src/modal/Modal/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | show: Boolean 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/examples/src/modal/Modal/style.css: -------------------------------------------------------------------------------- 1 | .modal-mask { 2 | position: fixed; 3 | z-index: 9998; 4 | top: 0; 5 | left: 0; 6 | width: 100%; 7 | height: 100%; 8 | background-color: rgba(0, 0, 0, 0.5); 9 | display: flex; 10 | transition: opacity 0.3s ease; 11 | } 12 | 13 | .modal-container { 14 | width: 300px; 15 | margin: auto; 16 | padding: 20px 30px; 17 | background-color: #fff; 18 | border-radius: 2px; 19 | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33); 20 | transition: all 0.3s ease; 21 | } 22 | 23 | .modal-header h3 { 24 | margin-top: 0; 25 | color: #42b983; 26 | } 27 | 28 | .modal-body { 29 | margin: 20px 0; 30 | } 31 | 32 | .modal-default-button { 33 | float: right; 34 | } 35 | 36 | /* 37 | * Наступні стилі застосовуються автоматично 38 | * до елементів з transition="modal", 39 | * коли їх видимість переключається Vue.js. 40 | * 41 | * Ви з легкістю можете пограти з появою 42 | * модального вікна, редагуючи ці стилі. 43 | */ 44 | 45 | .modal-enter-from { 46 | opacity: 0; 47 | } 48 | 49 | .modal-leave-to { 50 | opacity: 0; 51 | } 52 | 53 | .modal-enter-from .modal-container, 54 | .modal-leave-to .modal-container { 55 | -webkit-transform: scale(1.1); 56 | transform: scale(1.1); 57 | } 58 | -------------------------------------------------------------------------------- /src/examples/src/modal/Modal/template.html: -------------------------------------------------------------------------------- 1 | 2 | 23 | 24 | -------------------------------------------------------------------------------- /src/examples/src/modal/description.txt: -------------------------------------------------------------------------------- 1 | Модальний компонент із настроюваними слотами та переходами CSS. 2 | -------------------------------------------------------------------------------- /src/examples/src/simple-component/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | import TodoItem from './TodoItem.vue' 3 | 4 | export default { 5 | components: { 6 | TodoItem 7 | }, 8 | setup() { 9 | const groceryList = ref([ 10 | { id: 0, text: 'Овочі' }, 11 | { id: 1, text: 'Сир' }, 12 | { id: 2, text: 'Щось інше, що їдять люди' } 13 | ]) 14 | 15 | return { 16 | groceryList 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/examples/src/simple-component/App/options.js: -------------------------------------------------------------------------------- 1 | import TodoItem from './TodoItem.vue' 2 | 3 | export default { 4 | components: { 5 | TodoItem 6 | }, 7 | data() { 8 | return { 9 | groceryList: [ 10 | { id: 0, text: 'Овочі' }, 11 | { id: 1, text: 'Сир' }, 12 | { id: 2, text: 'Щось інше, що їдять люди' } 13 | ] 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/examples/src/simple-component/App/template.html: -------------------------------------------------------------------------------- 1 |
    2 | 8 | 13 |
14 | -------------------------------------------------------------------------------- /src/examples/src/simple-component/TodoItem/composition.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | todo: Object 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/examples/src/simple-component/TodoItem/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | todo: Object 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/examples/src/simple-component/TodoItem/template.html: -------------------------------------------------------------------------------- 1 |
  • {{ todo.text }}
  • -------------------------------------------------------------------------------- /src/examples/src/simple-component/description.txt: -------------------------------------------------------------------------------- 1 | Тут ми показуємо найпростіший можливий компонент, який приймає реквізит і рендерить його. 2 | Дізнайтеся більше про компоненти в посібнику! 3 | -------------------------------------------------------------------------------- /src/examples/src/svg/App/composition.js: -------------------------------------------------------------------------------- 1 | import PolyGraph from './PolyGraph.vue' 2 | import { ref, reactive } from 'vue' 3 | 4 | export default { 5 | components: { 6 | PolyGraph 7 | }, 8 | setup() { 9 | const newLabel = ref('') 10 | const stats = reactive([ 11 | { label: 'A', value: 100 }, 12 | { label: 'B', value: 100 }, 13 | { label: 'C', value: 100 }, 14 | { label: 'D', value: 100 }, 15 | { label: 'E', value: 100 }, 16 | { label: 'F', value: 100 } 17 | ]) 18 | 19 | function add(e) { 20 | e.preventDefault() 21 | if (!newLabel.value) return 22 | stats.push({ 23 | label: newLabel.value, 24 | value: 100 25 | }) 26 | newLabel.value = '' 27 | } 28 | 29 | function remove(stat) { 30 | if (stats.length > 3) { 31 | stats.splice(stats.indexOf(stat), 1) 32 | } else { 33 | alert("Більше видалити неможливо!") 34 | } 35 | } 36 | 37 | return { 38 | newLabel, 39 | stats, 40 | add, 41 | remove 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/examples/src/svg/App/options.js: -------------------------------------------------------------------------------- 1 | import PolyGraph from './PolyGraph.vue' 2 | 3 | export default { 4 | components: { 5 | PolyGraph 6 | }, 7 | data: () => ({ 8 | newLabel: '', 9 | stats: [ 10 | { label: 'A', value: 100 }, 11 | { label: 'B', value: 100 }, 12 | { label: 'C', value: 100 }, 13 | { label: 'D', value: 100 }, 14 | { label: 'E', value: 100 }, 15 | { label: 'F', value: 100 } 16 | ] 17 | }), 18 | methods: { 19 | add(e) { 20 | e.preventDefault() 21 | if (!this.newLabel) return 22 | this.stats.push({ 23 | label: this.newLabel, 24 | value: 100 25 | }) 26 | this.newLabel = '' 27 | }, 28 | remove(stat) { 29 | if (this.stats.length > 3) { 30 | this.stats.splice(this.stats.indexOf(stat), 1) 31 | } else { 32 | alert("Більше видалити неможливо!") 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/examples/src/svg/App/style.css: -------------------------------------------------------------------------------- 1 | polygon { 2 | fill: #42b983; 3 | opacity: 0.75; 4 | } 5 | 6 | circle { 7 | fill: transparent; 8 | stroke: #999; 9 | } 10 | 11 | text { 12 | font-size: 10px; 13 | fill: #666; 14 | } 15 | 16 | label { 17 | display: inline-block; 18 | margin-left: 10px; 19 | width: 20px; 20 | } 21 | 22 | #raw { 23 | position: absolute; 24 | top: 0; 25 | left: 300px; 26 | } 27 | -------------------------------------------------------------------------------- /src/examples/src/svg/App/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
    7 | 8 | 9 | {{stat.value}} 10 | 11 |
    12 | 13 |
    14 | 15 | 16 |
    17 | 18 |
    {{ stats }}
    19 | -------------------------------------------------------------------------------- /src/examples/src/svg/AxisLabel/composition.js: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue' 2 | import { valueToPoint } from './util.js' 3 | 4 | export default { 5 | props: { 6 | stat: Object, 7 | index: Number, 8 | total: Number 9 | }, 10 | setup(props) { 11 | const point = computed(() => 12 | valueToPoint(+props.stat.value + 10, props.index, props.total) 13 | ) 14 | 15 | return { 16 | point 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/examples/src/svg/AxisLabel/options.js: -------------------------------------------------------------------------------- 1 | import { valueToPoint } from './util.js' 2 | 3 | export default { 4 | props: { 5 | stat: Object, 6 | index: Number, 7 | total: Number 8 | }, 9 | computed: { 10 | point: function () { 11 | return valueToPoint(+this.stat.value + 10, this.index, this.total) 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/examples/src/svg/AxisLabel/template.html: -------------------------------------------------------------------------------- 1 | {{stat.label}} 2 | -------------------------------------------------------------------------------- /src/examples/src/svg/PolyGraph/composition.js: -------------------------------------------------------------------------------- 1 | import AxisLabel from './AxisLabel.vue' 2 | import { computed } from 'vue' 3 | import { valueToPoint } from './util.js' 4 | 5 | export default { 6 | components: { 7 | AxisLabel 8 | }, 9 | props: { 10 | stats: Array 11 | }, 12 | setup(props) { 13 | const points = computed(() => { 14 | const total = props.stats.length 15 | return props.stats 16 | .map((stat, i) => { 17 | const { x, y } = valueToPoint(stat.value, i, total) 18 | return `${x},${y}` 19 | }) 20 | .join(' ') 21 | }) 22 | 23 | return { 24 | points 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/examples/src/svg/PolyGraph/options.js: -------------------------------------------------------------------------------- 1 | import AxisLabel from './AxisLabel.vue' 2 | import { valueToPoint } from './util.js' 3 | 4 | export default { 5 | components: { 6 | AxisLabel 7 | }, 8 | props: { 9 | stats: Array 10 | }, 11 | computed: { 12 | // обчислювана властивість для точок багатокутника 13 | points() { 14 | const total = this.stats.length 15 | return this.stats 16 | .map((stat, i) => { 17 | const { x, y } = valueToPoint(stat.value, i, total) 18 | return `${x},${y}` 19 | }) 20 | .join(' ') 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/examples/src/svg/PolyGraph/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/examples/src/svg/description.txt: -------------------------------------------------------------------------------- 1 | Графік SVG 2 | -------------------------------------------------------------------------------- /src/examples/src/svg/util.js: -------------------------------------------------------------------------------- 1 | export function valueToPoint(value, index, total) { 2 | const x = 0 3 | const y = -value * 0.8 4 | const angle = ((Math.PI * 2) / total) * index 5 | const cos = Math.cos(angle) 6 | const sin = Math.sin(angle) 7 | const tx = x * cos - y * sin + 100 8 | const ty = x * sin + y * cos + 100 9 | return { 10 | x: tx, 11 | y: ty 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/examples/src/temperature-converter/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export default { 4 | setup() { 5 | const c = ref(0) 6 | const f = ref(32) 7 | 8 | function setC(e, v = +e.target.value) { 9 | c.value = v 10 | f.value = v * (9 / 5) + 32 11 | } 12 | 13 | function setF(e, v = +e.target.value) { 14 | f.value = v 15 | c.value = (v - 32) * (5 / 9) 16 | } 17 | 18 | return { 19 | c, 20 | f, 21 | setC, 22 | setF 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/examples/src/temperature-converter/App/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | c: 0, 5 | f: 32 6 | } 7 | }, 8 | methods: { 9 | setC(e, c = +e.target.value) { 10 | this.c = c 11 | this.f = c * (9 / 5) + 32 12 | }, 13 | setF(e, f = +e.target.value) { 14 | this.f = f 15 | this.c = (f - 32) * (5 / 9) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/examples/src/temperature-converter/App/template.html: -------------------------------------------------------------------------------- 1 | за Цельсієм = 2 | по Фаренгейту 3 | -------------------------------------------------------------------------------- /src/examples/src/temperature-converter/description.txt: -------------------------------------------------------------------------------- 1 | https://eugenkiss.github.io/7guis/tasks/#temp -------------------------------------------------------------------------------- /src/examples/src/timer/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref, onUnmounted, computed } from 'vue' 2 | export default { 3 | setup() { 4 | const duration = ref(15 * 1000) 5 | const elapsed = ref(0) 6 | 7 | let lastTime 8 | let handle 9 | 10 | const update = () => { 11 | elapsed.value = performance.now() - lastTime 12 | if (elapsed.value >= duration.value) { 13 | cancelAnimationFrame(handle) 14 | } else { 15 | handle = requestAnimationFrame(update) 16 | } 17 | } 18 | 19 | const reset = () => { 20 | elapsed.value = 0 21 | lastTime = performance.now() 22 | update() 23 | } 24 | 25 | const progressRate = computed(() => 26 | Math.min(elapsed.value / duration.value, 1) 27 | ) 28 | 29 | reset() 30 | 31 | onUnmounted(() => { 32 | cancelAnimationFrame(handle) 33 | }) 34 | return { 35 | duration, 36 | elapsed, 37 | progressRate, 38 | reset 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/examples/src/timer/App/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | duration: 15 * 1000, 5 | elapsed: 0 6 | } 7 | }, 8 | created() { 9 | this.reset() 10 | }, 11 | unmounted() { 12 | cancelAnimationFrame(this.handle) 13 | }, 14 | computed: { 15 | progressRate() { 16 | return Math.min(this.elapsed / this.duration, 1) 17 | } 18 | }, 19 | methods: { 20 | update() { 21 | this.elapsed = performance.now() - this.lastTime 22 | if (this.elapsed >= this.duration) { 23 | cancelAnimationFrame(this.handle) 24 | } else { 25 | this.handle = requestAnimationFrame(this.update) 26 | } 27 | }, 28 | reset() { 29 | this.elapsed = 0 30 | this.lastTime = performance.now() 31 | this.update() 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/examples/src/timer/App/style.css: -------------------------------------------------------------------------------- 1 | .elapsed-container { 2 | width: 300px; 3 | } 4 | 5 | .elapsed-bar { 6 | background-color: red; 7 | height: 10px; 8 | } -------------------------------------------------------------------------------- /src/examples/src/timer/App/template.html: -------------------------------------------------------------------------------- 1 | 4 | 5 |
    {{ (elapsed / 1000).toFixed(1) }}s
    6 | 7 |
    8 | Тривалість: 9 | {{ (duration / 1000).toFixed(1) }}s 10 |
    11 | 12 | 13 | -------------------------------------------------------------------------------- /src/examples/src/timer/description.txt: -------------------------------------------------------------------------------- 1 | https://eugenkiss.github.io/7guis/tasks/#timer -------------------------------------------------------------------------------- /src/examples/src/todomvc/App/style.css: -------------------------------------------------------------------------------- 1 | @import "https://unpkg.com/todomvc-app-css@2.4.1/index.css"; -------------------------------------------------------------------------------- /src/examples/src/todomvc/description.txt: -------------------------------------------------------------------------------- 1 | Повністю сумісна зі специфікаціями реалізація списку завдань 2 | https://todomvc.com/ 3 | -------------------------------------------------------------------------------- /src/examples/src/tree/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | import TreeItem from './TreeItem.vue' 3 | 4 | export default { 5 | components: { 6 | TreeItem 7 | }, 8 | setup() { 9 | const treeData = ref({ 10 | name: 'Моє дерево', 11 | children: [ 12 | { name: 'привіт' }, 13 | { name: 'світ' }, 14 | { 15 | name: 'дочірня папка', 16 | children: [ 17 | { 18 | name: 'дочірня папка', 19 | children: [{ name: 'привіт' }, { name: 'світ' }] 20 | }, 21 | { name: 'привіт' }, 22 | { name: 'світ' }, 23 | { 24 | name: 'дочірня папка', 25 | children: [{ name: 'привіт' }, { name: 'світ' }] 26 | } 27 | ] 28 | } 29 | ] 30 | }) 31 | 32 | return { 33 | treeData 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/examples/src/tree/App/options.js: -------------------------------------------------------------------------------- 1 | import TreeItem from './TreeItem.vue' 2 | 3 | const treeData = { 4 | name: 'Моє дерево', 5 | children: [ 6 | { name: 'привіт' }, 7 | { name: 'світ' }, 8 | { 9 | name: 'дочірня папка', 10 | children: [ 11 | { 12 | name: 'дочірня папка', 13 | children: [{ name: 'привіт' }, { name: 'світ' }] 14 | }, 15 | { name: 'привіт' }, 16 | { name: 'світ' }, 17 | { 18 | name: 'дочірня папка', 19 | children: [{ name: 'привіт' }, { name: 'світ' }] 20 | } 21 | ] 22 | } 23 | ] 24 | } 25 | 26 | export default { 27 | components: { 28 | TreeItem 29 | }, 30 | data() { 31 | return { 32 | treeData 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/examples/src/tree/App/style.css: -------------------------------------------------------------------------------- 1 | .item { 2 | cursor: pointer; 3 | line-height: 1.5; 4 | } 5 | .bold { 6 | font-weight: bold; 7 | } -------------------------------------------------------------------------------- /src/examples/src/tree/App/template.html: -------------------------------------------------------------------------------- 1 |
      2 | 3 |
    4 | -------------------------------------------------------------------------------- /src/examples/src/tree/TreeItem/composition.js: -------------------------------------------------------------------------------- 1 | import { ref, computed } from 'vue' 2 | 3 | export default { 4 | name: 'TreeItem', // необхідні для референції на самого себе 5 | props: { 6 | model: Object 7 | }, 8 | setup(props) { 9 | const isOpen = ref(false) 10 | const isFolder = computed(() => { 11 | return props.model.children && props.model.children.length 12 | }) 13 | 14 | function toggle() { 15 | isOpen.value = !isOpen.value 16 | } 17 | 18 | function changeType() { 19 | if (!isFolder.value) { 20 | props.model.children = [] 21 | addChild() 22 | isOpen.value = true 23 | } 24 | } 25 | 26 | function addChild() { 27 | props.model.children.push({ name: 'нова папка' }) 28 | } 29 | 30 | return { 31 | isOpen, 32 | isFolder, 33 | toggle, 34 | changeType, 35 | addChild 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/examples/src/tree/TreeItem/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: 'TreeItem', // необхідні для референції на самого себе 3 | props: { 4 | model: Object 5 | }, 6 | data() { 7 | return { 8 | isOpen: false 9 | } 10 | }, 11 | computed: { 12 | isFolder() { 13 | return this.model.children && this.model.children.length 14 | } 15 | }, 16 | methods: { 17 | toggle() { 18 | if (this.isFolder) { 19 | this.isOpen = !this.isOpen 20 | } 21 | }, 22 | changeType() { 23 | if (!this.isFolder) { 24 | this.model.children = [] 25 | this.addChild() 26 | this.isOpen = true 27 | } 28 | }, 29 | addChild() { 30 | this.model.children.push({ 31 | name: 'нова папка' 32 | }) 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/examples/src/tree/TreeItem/template.html: -------------------------------------------------------------------------------- 1 |
  • 2 |
    6 | {{ model.name }} 7 | [{{ isOpen ? '-' : '+' }}] 8 |
    9 |
      10 | 14 | 18 | 19 |
    • +
    • 20 |
    21 |
  • 22 | -------------------------------------------------------------------------------- /src/examples/src/tree/description.txt: -------------------------------------------------------------------------------- 1 | Вкладений компонент дерева, який рекурсивно рендерить сам себе. 2 | За допомогою подвійного натиску лівої кнопки миші, ви можете перетворити елемент на папку. 3 | -------------------------------------------------------------------------------- /src/guide/best-practices/images/AccessibilityChromeDeveloperTools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/guide/best-practices/images/AccessibilityChromeDeveloperTools.png -------------------------------------------------------------------------------- /src/guide/best-practices/images/AccessibleARIAdescribedby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/guide/best-practices/images/AccessibleARIAdescribedby.png -------------------------------------------------------------------------------- /src/guide/best-practices/images/AccessibleARIAlabelDevTools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/guide/best-practices/images/AccessibleARIAlabelDevTools.png -------------------------------------------------------------------------------- /src/guide/best-practices/images/AccessibleARIAlabelledbyDevTools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/guide/best-practices/images/AccessibleARIAlabelledbyDevTools.png -------------------------------------------------------------------------------- /src/guide/best-practices/images/AccessibleLabelChromeDevTools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/guide/best-practices/images/AccessibleLabelChromeDevTools.png -------------------------------------------------------------------------------- /src/guide/best-practices/images/AccessiblePlaceholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/guide/best-practices/images/AccessiblePlaceholder.png -------------------------------------------------------------------------------- /src/guide/built-ins/images/transition-classes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/guide/built-ins/images/transition-classes.png -------------------------------------------------------------------------------- /src/guide/built-ins/keep-alive-demos/CompA.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | -------------------------------------------------------------------------------- /src/guide/built-ins/keep-alive-demos/CompB.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 11 | -------------------------------------------------------------------------------- /src/guide/built-ins/keep-alive-demos/SwitchComponent.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 25 | -------------------------------------------------------------------------------- /src/guide/built-ins/transition-demos/Basic.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 14 | 15 | 26 | -------------------------------------------------------------------------------- /src/guide/built-ins/transition-demos/BetweenComponents.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 23 | -------------------------------------------------------------------------------- /src/guide/built-ins/transition-demos/BetweenElements.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 27 | 28 | 63 | -------------------------------------------------------------------------------- /src/guide/built-ins/transition-demos/CssAnimation.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 16 | 17 | 36 | -------------------------------------------------------------------------------- /src/guide/built-ins/transition-demos/JsHooks.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 56 | 57 | 66 | -------------------------------------------------------------------------------- /src/guide/built-ins/transition-demos/ListBasic.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 31 | 32 | 43 | -------------------------------------------------------------------------------- /src/guide/built-ins/transition-demos/ListMove.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 45 | 46 | 65 | -------------------------------------------------------------------------------- /src/guide/built-ins/transition-demos/ListStagger.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 63 | -------------------------------------------------------------------------------- /src/guide/built-ins/transition-demos/NestedTransitions.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 16 | 17 | 65 | -------------------------------------------------------------------------------- /src/guide/built-ins/transition-demos/SlideFade.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 14 | 15 | 30 | -------------------------------------------------------------------------------- /src/guide/components/images/named-slots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/guide/components/images/named-slots.png -------------------------------------------------------------------------------- /src/guide/components/images/prop-drilling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/guide/components/images/prop-drilling.png -------------------------------------------------------------------------------- /src/guide/components/images/provide-inject.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/guide/components/images/provide-inject.png -------------------------------------------------------------------------------- /src/guide/components/images/slots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/guide/components/images/slots.png -------------------------------------------------------------------------------- /src/guide/essentials/images/components.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/guide/essentials/images/components.png -------------------------------------------------------------------------------- /src/guide/essentials/images/directive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/guide/essentials/images/directive.png -------------------------------------------------------------------------------- /src/guide/essentials/images/lifecycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/guide/essentials/images/lifecycle.png -------------------------------------------------------------------------------- /src/guide/extras/demos/AnimateWatcher.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 24 | 25 | 31 | -------------------------------------------------------------------------------- /src/guide/extras/demos/Colors.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 21 | 22 | 29 | -------------------------------------------------------------------------------- /src/guide/extras/demos/DisabledButton.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 24 | 25 | 54 | -------------------------------------------------------------------------------- /src/guide/extras/demos/SpreadSheet.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 26 | 27 | 47 | -------------------------------------------------------------------------------- /src/guide/extras/demos/SpreadSheetCell.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 30 | 31 | 54 | -------------------------------------------------------------------------------- /src/guide/extras/demos/spreadSheetStore.js: -------------------------------------------------------------------------------- 1 | import { reactive } from 'vue' 2 | 3 | const COLS = 3 4 | const ROWS = 3 5 | 6 | export const cells = reactive( 7 | Array.from(Array(COLS).keys()).map((i) => 8 | Array.from(Array(ROWS).keys()).map((i) => '') 9 | ) 10 | ) 11 | 12 | // initial state for demo 13 | cells[0][0] = '1' 14 | cells[0][1] = '2' 15 | cells[0][2] = '= A0 + A1' 16 | 17 | // adapted from https://codesandbox.io/s/jotai-7guis-task7-cells-mzoit?file=/src/atoms.ts 18 | // by @dai-shi 19 | export function evalCell(exp) { 20 | if (!exp.startsWith('=')) { 21 | return exp 22 | } 23 | 24 | // = A1 + B2 ---> get(0,1) + get(1,2) 25 | exp = exp 26 | .slice(1) 27 | .replace( 28 | /\b([A-Z])(\d{1,2})\b/g, 29 | (_, c, r) => `get(${c.charCodeAt(0) - 65},${r})` 30 | ) 31 | 32 | try { 33 | return new Function('get', `return ${exp}`)(getCellValue) 34 | } catch (e) { 35 | return `#ERROR ${e}` 36 | } 37 | } 38 | 39 | function getCellValue(c, r) { 40 | const val = evalCell(cells[c][r]) 41 | const num = Number(val) 42 | return Number.isFinite(num) ? num : val 43 | } 44 | -------------------------------------------------------------------------------- /src/guide/extras/images/composition-api-after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/guide/extras/images/composition-api-after.png -------------------------------------------------------------------------------- /src/guide/extras/images/options-api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/guide/extras/images/options-api.png -------------------------------------------------------------------------------- /src/guide/extras/images/render-pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/guide/extras/images/render-pipeline.png -------------------------------------------------------------------------------- /src/guide/reusability/mouse.js: -------------------------------------------------------------------------------- 1 | import { ref, onMounted, onUnmounted } from 'vue' 2 | 3 | export function useMouse() { 4 | const x = ref(0) 5 | const y = ref(0) 6 | 7 | function update(event) { 8 | x.value = event.pageX 9 | y.value = event.pageY 10 | } 11 | 12 | onMounted(() => window.addEventListener('mousemove', update)) 13 | onUnmounted(() => window.removeEventListener('mousemove', update)) 14 | 15 | return { x, y } 16 | } 17 | -------------------------------------------------------------------------------- /src/guide/scaling-up/images/state-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/guide/scaling-up/images/state-flow.png -------------------------------------------------------------------------------- /src/guide/typescript/images/takeover-mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/guide/typescript/images/takeover-mode.png -------------------------------------------------------------------------------- /src/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | page: true 3 | title: Vue.js - Прогресивний JavaScript фреймворк 4 | --- 5 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/partners/[partnerId].md: -------------------------------------------------------------------------------- 1 | --- 2 | page: true 3 | footer: false 4 | --- 5 | 6 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/partners/[partnerId].paths.ts: -------------------------------------------------------------------------------- 1 | import partners from './partners.json' 2 | import { normalizeName } from './components/utils' 3 | 4 | export default { 5 | paths: partners.map((p) => { 6 | return { 7 | params: { 8 | partnerId: normalizeName(p.name) 9 | } 10 | } 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /src/partners/all.md: -------------------------------------------------------------------------------- 1 | --- 2 | page: true 3 | footer: false 4 | --- 5 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/partners/components/PartnerAll.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 35 | 36 | 58 | -------------------------------------------------------------------------------- /src/partners/components/PartnerHero.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 18 | 19 | 72 | -------------------------------------------------------------------------------- /src/partners/components/PartnerJoin.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 71 | -------------------------------------------------------------------------------- /src/partners/components/PartnerLanding.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 39 | 40 | 92 | -------------------------------------------------------------------------------- /src/partners/components/PartnerLocation.vue: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /src/partners/components/type.ts: -------------------------------------------------------------------------------- 1 | export interface Partner { 2 | name: string 3 | logo: string 4 | hero?: string 5 | flipLogo?: boolean 6 | intro: string 7 | description: string[] 8 | proficiencies: string[] 9 | region: string[] 10 | location: string[] 11 | website: { 12 | text: string 13 | url: string 14 | } 15 | contact: string 16 | hiring?: string 17 | platinum?: boolean 18 | } 19 | -------------------------------------------------------------------------------- /src/partners/components/utils.ts: -------------------------------------------------------------------------------- 1 | export function track() { 2 | fathom.trackGoal('TTDUIE6G', 0) 3 | } 4 | 5 | export function normalizeName(name: string) { 6 | return name.toLowerCase().replace(/\s+/g, '') 7 | } 8 | 9 | export function getHero(img: string | undefined, name: string) { 10 | return `/images/partners/${img || `${normalizeName(name)}-hero.jpg`}` 11 | } 12 | 13 | export function getLogo(img: string, flip = false) { 14 | if (flip) img = img.replace(/(\.\w+$)/, '-dark$1') 15 | return `/images/partners/${img}` 16 | } 17 | -------------------------------------------------------------------------------- /src/partners/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | page: true 3 | footer: false 4 | --- 5 | 6 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/public/_headers: -------------------------------------------------------------------------------- 1 | /* 2 | X-Frame-Options: ALLOW-FROM https://staging.certification.vuejs.org https://certification.vuejs.org https://certificates.dev https://staging.certificates.dev https://alemira.com https://*.alemira.com 3 | Content-Security-Policy: frame-ancestors https://staging.certification.vuejs.org https://certification.vuejs.org https://certificates.dev https://staging.certificates.dev https://alemira.com https://*.alemira.com 4 | 5 | /assets/* 6 | cache-control: max-age=31536000 7 | cache-control: immutable 8 | -------------------------------------------------------------------------------- /src/public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/public/images/logo.png -------------------------------------------------------------------------------- /src/public/images/partners/64robots-hero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/public/images/partners/64robots-hero.jpg -------------------------------------------------------------------------------- /src/public/images/partners/curotec-hero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/public/images/partners/curotec-hero.jpg -------------------------------------------------------------------------------- /src/public/images/partners/curotec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/public/images/partners/curotec.png -------------------------------------------------------------------------------- /src/public/images/partners/epicmax.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/public/images/partners/epicmax.png -------------------------------------------------------------------------------- /src/public/images/partners/herodevs-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/public/images/partners/herodevs-dark.png -------------------------------------------------------------------------------- /src/public/images/partners/herodevs-hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/public/images/partners/herodevs-hero.png -------------------------------------------------------------------------------- /src/public/images/partners/herodevs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/public/images/partners/herodevs.png -------------------------------------------------------------------------------- /src/public/images/partners/jump24-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/public/images/partners/jump24-hero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/public/images/partners/jump24-hero.jpg -------------------------------------------------------------------------------- /src/public/images/partners/jump24.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/public/images/partners/monterail-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/public/images/partners/monterail-dark.png -------------------------------------------------------------------------------- /src/public/images/partners/monterail-hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/public/images/partners/monterail-hero.png -------------------------------------------------------------------------------- /src/public/images/partners/monterail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/public/images/partners/monterail.png -------------------------------------------------------------------------------- /src/public/images/partners/passionatepeople-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/public/images/partners/passionatepeople-dark.png -------------------------------------------------------------------------------- /src/public/images/partners/passionatepeople-hero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/public/images/partners/passionatepeople-hero.jpg -------------------------------------------------------------------------------- /src/public/images/partners/passionatepeople.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/public/images/partners/passionatepeople.png -------------------------------------------------------------------------------- /src/public/images/partners/redberry-hero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/public/images/partners/redberry-hero.jpg -------------------------------------------------------------------------------- /src/public/images/partners/redberry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/public/images/partners/redberry.png -------------------------------------------------------------------------------- /src/public/images/partners/tighten-hero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/public/images/partners/tighten-hero.jpg -------------------------------------------------------------------------------- /src/public/images/partners/vehikl-hero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/public/images/partners/vehikl-hero.jpg -------------------------------------------------------------------------------- /src/public/images/partners/webreinvent-hero.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/public/images/partners/webreinvent-hero.jpg -------------------------------------------------------------------------------- /src/public/images/partners/webreinvent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/public/images/partners/webreinvent.png -------------------------------------------------------------------------------- /src/public/images/paypal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vuejs-translations/docs-uk/28b6af923ac4631b49938b25a8a240ad80fe1b21/src/public/images/paypal.png -------------------------------------------------------------------------------- /src/public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/public/service-worker.js: -------------------------------------------------------------------------------- 1 | // force clearing previous service worker 2 | self.addEventListener('install', function (e) { 3 | self.skipWaiting() 4 | }) 5 | 6 | self.addEventListener('activate', function (e) { 7 | self.registration 8 | .unregister() 9 | .then(function () { 10 | return self.clients.matchAll() 11 | }) 12 | .then(function (clients) { 13 | clients.forEach((client) => client.navigate(client.url)) 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /src/translations/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | aside: false 3 | --- 4 | 5 | # Переклади {#translations} 6 | 7 | ## Доступні мови {#available-languages} 8 | 9 | - [English / Англійська](https://vuejs.org/) [[джерело](https://github.com/vuejs/docs)] 10 | - [简体中文 / Спрощена Китайська](https://cn.vuejs.org/) [[джерело](https://github.com/vuejs-translations/docs-zh-cn)] 11 | - [日本語 / Японська](https://ja.vuejs.org/) [[джерело](https://github.com/vuejs-translations/docs-ja)] 12 | - [Українська](https://ua.vuejs.org) [[джерело](https://github.com/vuejs-translations/docs-ua)] 13 | - [Français / Французька](https://fr.vuejs.org) [[джерело](https://github.com/vuejs-translations/docs-fr)] 14 | - [한국어 / Корейська](https://ko.vuejs.org) [[джерело](https://github.com/vuejs-translations/docs-ko)] 15 | - [Português / Португальська](https://pt.vuejs.org) [[джерело](https://github.com/vuejs-translations/docs-pt)] 16 | - [বাংলা / Бенгальська](https://bn.vuejs.org) [[джерело](https://github.com/vuejs-translations/docs-bn)] 17 | - [Italiano / Італійська](https://it.vuejs.org) [[джерело](https://github.com/vuejs-translations/docs-it)] 18 | 19 | ## Work in Progress Languages {#work-in-progress-languages} 20 | 21 | - [فارسی / Persian](https://fa.vuejs.org/) [[source](https://github.com/vuejs-translations/docs-fa)] 22 | 23 | ## Початок нового перекладу {#starting-a-new-translation} 24 | 25 | Документація Vue нещодавно зазнала значних змін, тож переклади іншими мовами досі відсутні або в процесі перекладу. 26 | 27 | Ми вітаємо зусилля спільноти щодо надання більше перекладів. Роботою з перекладу керує організація [vuejs-translations](https://github.com/vuejs-translations/) на GitHub. Якщо ви зацікавлені зробити свій внесок, перегляньте [Інструкції з перекладу](https://github.com/vuejs-translations/guidelines/blob/main/README.md), щоб почати. 28 | -------------------------------------------------------------------------------- /src/tutorial/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | page: true 3 | title: Посібник 4 | sidebar: false 5 | aside: false 6 | footer: false 7 | returnToTop: false 8 | --- 9 | 10 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/tutorial/src/step-1/App/template.html: -------------------------------------------------------------------------------- 1 |

    Привіт, світе!

    2 | -------------------------------------------------------------------------------- /src/tutorial/src/step-1/description.md: -------------------------------------------------------------------------------- 1 | # Початок {#getting-started} 2 | 3 | Ласкаво просимо до підручника Vue! 4 | 5 | Мета цього підручника — швидко надати вам досвід роботи з Vue прямо в браузері. Він не має на меті бути вичерпним, і вам не потрібно все розуміти, перш ніж рухатися далі. Однак після його завершення обов'язково також прочитайте Гід, який докладніше розглядає кожну тему. 6 | 7 | ## Передумови {#prerequisites} 8 | 9 | Посібник передбачає базове знайомство з HTML, CSS і JavaScript. Якщо ви зовсім новачок у фронтенд розробці, можливо, як ваш перший крок переходити безпосередньо до фреймворку буде не найкращою ідеєю – осягніть основи, а потім повертайтеся! Попередній досвід роботи з іншими фреймворками допомагає, але не є обов'язковим. 10 | 11 | ## Як користуватися цим посібником {#how-to-use-this-tutorial} 12 | 13 | Ви можете відредагувати код праворучнижче та миттєво побачити оновлений результат. Кожен крок представлятиме основну функцію Vue, і від вас очікується завершення коду, щоб запустити демонстрацію. Якщо ви застрягли, у вас буде кнопка "Покажи мені!", яка відкриває для вас робочий код. Намагайтеся не надто покладатися на це - ви навчитеся швидше, з'ясовуючи все самостійно. 14 | 15 | Якщо ви досвідчений розробник із Vue 2 або інших фреймворків, є кілька налаштувань, які ви можете підправити, щоб якнайкраще використовувати цей посібник. Якщо ви новачок, рекомендуємо використовувати значення за промовчуванням. 16 | 17 |
    18 | Деталі налаштування підручника 19 | 20 | - Vue пропонує два стилі API: опційний та композиційний. Цей підручник призначений для обох – ви можете вибрати бажаний стиль за допомогою перемикачів параметрів API у верхній частині. Докладніше про стилі API. 21 | 22 | - Ви також можете перемикатися між режимом SFC або HTML. У першому буде показано приклади коду у форматі одно-файлових компонентів (SFC), який використовують більшість розробників із етапом збірки Vue. HTML-режим показує використання без етапу збірки. 23 | 24 |
    25 | 26 | Готові? Натисніть «Далі», щоб почати. 27 | -------------------------------------------------------------------------------- /src/tutorial/src/step-10/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export default { 4 | setup() { 5 | const todoId = ref(1) 6 | const todoData = ref(null) 7 | 8 | async function fetchData() { 9 | todoData.value = null 10 | const res = await fetch( 11 | `https://jsonplaceholder.typicode.com/todos/${todoId.value}` 12 | ) 13 | todoData.value = await res.json() 14 | } 15 | 16 | fetchData() 17 | 18 | return { 19 | todoId, 20 | todoData 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/tutorial/src/step-10/App/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | todoId: 1, 5 | todoData: null 6 | } 7 | }, 8 | methods: { 9 | async fetchData() { 10 | this.todoData = null 11 | const res = await fetch( 12 | `https://jsonplaceholder.typicode.com/todos/${this.todoId}` 13 | ) 14 | this.todoData = await res.json() 15 | } 16 | }, 17 | mounted() { 18 | this.fetchData() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/tutorial/src/step-10/App/template.html: -------------------------------------------------------------------------------- 1 |

    Завдання: {{ todoId }}

    2 | 3 |

    Завантажується...

    4 |
    {{ todoData }}
    5 | -------------------------------------------------------------------------------- /src/tutorial/src/step-10/_hint/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref, watch } from 'vue' 2 | 3 | export default { 4 | setup() { 5 | const todoId = ref(1) 6 | const todoData = ref(null) 7 | 8 | async function fetchData() { 9 | todoData.value = null 10 | const res = await fetch( 11 | `https://jsonplaceholder.typicode.com/todos/${todoId.value}` 12 | ) 13 | todoData.value = await res.json() 14 | } 15 | 16 | fetchData() 17 | 18 | watch(todoId, fetchData) 19 | 20 | return { 21 | todoId, 22 | todoData 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/tutorial/src/step-10/_hint/App/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | todoId: 1, 5 | todoData: null 6 | } 7 | }, 8 | methods: { 9 | async fetchData() { 10 | this.todoData = null 11 | const res = await fetch( 12 | `https://jsonplaceholder.typicode.com/todos/${this.todoId}` 13 | ) 14 | this.todoData = await res.json() 15 | } 16 | }, 17 | mounted() { 18 | this.fetchData() 19 | }, 20 | watch: { 21 | todoId() { 22 | this.fetchData() 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/tutorial/src/step-10/description.md: -------------------------------------------------------------------------------- 1 | # Спостерігачі {#watchers} 2 | 3 | Іноді нам потрібно виконати реактивно "побічні ефекти" - наприклад, вивести рахунок до консолі, коли він змінюється. Ми можемо досягти цього за допомогою спостерігачів: 4 | 5 |
    6 | 7 | ```js 8 | import { ref, watch } from 'vue' 9 | 10 | const count = ref(0) 11 | 12 | watch(count, (newCount) => { 13 | // так, console.log() це побічний ефект 14 | console.log(`Новий рахунок: ${newCount}`) 15 | }) 16 | ``` 17 | 18 | `watch()` може безпосередньо спостерігати за референцією і функція зворотного виклику запускається щоразу, коли змінюється значення `count`. `watch()` також може спостерігати за іншими типами даних - більш детальну інформацію можна знайти в гіді про спостерігачі. 19 | 20 |
    21 |
    22 | 23 | ```js 24 | export default { 25 | data() { 26 | return { 27 | count: 0 28 | } 29 | }, 30 | watch: { 31 | count(newCount) { 32 | // так, console.log() це побічний ефект 33 | console.log(`Новий рахунок: ${newCount}`) 34 | } 35 | } 36 | } 37 | ``` 38 | 39 | Тут ми використовуємо опцію `watch`, щоб спостерігати за властивістю `count`. Зворотній виклик спостерігача викликається тоді, коли змінюється `count` і отримує нове значення як аргумент. Більш детальну інформацію можна знайти в гіді про спостерігачі. 40 | 41 |
    42 | 43 | Більш практичним прикладом за виведення в консоль, було б отримання нових даних, коли змінюється ID. Код, який ми маємо, отримує дані про завдання з фіктивного API під час монтування компонента. Існує також кнопка, яка збільшує ID завдання, яке має бути отримане. Спробуйте реалізувати спостерігач, який буде отримувати нове завдання, якщо натиснути кнопку. 44 | -------------------------------------------------------------------------------- /src/tutorial/src/step-11/App/composition.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // зареєструйте компонент 3 | } 4 | -------------------------------------------------------------------------------- /src/tutorial/src/step-11/App/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // зареєструйте дочірній компонент 3 | } 4 | -------------------------------------------------------------------------------- /src/tutorial/src/step-11/App/template.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/tutorial/src/step-11/ChildComp/template.html: -------------------------------------------------------------------------------- 1 |

    Дочірній компонент!

    2 | -------------------------------------------------------------------------------- /src/tutorial/src/step-11/_hint/App/composition.js: -------------------------------------------------------------------------------- 1 | import ChildComp from './ChildComp.vue' 2 | 3 | export default { 4 | components: { 5 | ChildComp 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/tutorial/src/step-11/_hint/App/options.js: -------------------------------------------------------------------------------- 1 | import ChildComp from './ChildComp.vue' 2 | 3 | export default { 4 | components: { 5 | ChildComp 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/tutorial/src/step-11/_hint/App/template.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/tutorial/src/step-11/description.md: -------------------------------------------------------------------------------- 1 | # Компоненти {#components} 2 | 3 | Досі ми працювали лише з одним компонентом. Справжні додатки Vue зазвичай створюються за допомогою вкладених компонентів. 4 | 5 | Батьківський компонент може рендерити інший компонент у своєму шаблоні як дочірній. Щоб використовувати дочірній компонент, нам потрібно спочатку його імпортувати: 6 | 7 |
    8 |
    9 | 10 | ```js 11 | import ChildComp from './ChildComp.vue' 12 | ``` 13 | 14 |
    15 |
    16 | 17 |
    18 |
    19 | 20 | ```js 21 | import ChildComp from './ChildComp.vue' 22 | 23 | export default { 24 | components: { 25 | ChildComp 26 | } 27 | } 28 | ``` 29 | 30 | Нам також потрібно зареєструвати компонент за допомогою опції `components`. Тут ми використовуємо скорочення властивостей об'єкта, щоб зареєструвати компонент `ChildComp` під ключем `ChildComp`. 31 | 32 |
    33 |
    34 | 35 |
    36 | 37 | Потім ми можемо використовувати компонент у шаблоні як: 38 | 39 | ```vue-html 40 | 41 | ``` 42 | 43 |
    44 | 45 |
    46 | 47 | ```js 48 | import ChildComp from './ChildComp.js' 49 | 50 | createApp({ 51 | components: { 52 | ChildComp 53 | } 54 | }) 55 | ``` 56 | 57 | Нам також потрібно зареєструвати компонент за допомогою опції `components`. Тут ми використовуємо скорочення властивостей об'єкта, щоб зареєструвати компонент `ChildComp` під ключем `ChildComp`. 58 | 59 | Оскільки ми пишемо шаблон в DOM, він буде підпорядкований правилам парсингу браузера, які не враховують регістр для імен тегів. Тому для посилання на дочірній компонент нам потрібно використовувати ім'я в стилі `kebab-cased`: 60 | 61 | ```vue-html 62 | 63 | ``` 64 | 65 |
    66 | 67 | 68 | Тепер спробуйте самі - імпортуйте дочірній компонент і виведіть його в шаблоні. 69 | -------------------------------------------------------------------------------- /src/tutorial/src/step-12/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | import ChildComp from './ChildComp.vue' 3 | 4 | export default { 5 | components: { 6 | ChildComp 7 | }, 8 | setup() { 9 | const greeting = ref('Привіт від батьківського компонента!') 10 | 11 | return { 12 | greeting 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/tutorial/src/step-12/App/options.js: -------------------------------------------------------------------------------- 1 | import ChildComp from './ChildComp.vue' 2 | 3 | export default { 4 | components: { 5 | ChildComp 6 | }, 7 | data() { 8 | return { 9 | greeting: 'Привіт від батьківського компонента!' 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/tutorial/src/step-12/App/template.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/tutorial/src/step-12/ChildComp/composition.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | msg: String 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/tutorial/src/step-12/ChildComp/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | props: { 3 | msg: String 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/tutorial/src/step-12/ChildComp/template.html: -------------------------------------------------------------------------------- 1 |

    {{ msg || 'Реквізит ще не переданий' }}

    2 | -------------------------------------------------------------------------------- /src/tutorial/src/step-12/_hint/App/template.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/tutorial/src/step-12/description.md: -------------------------------------------------------------------------------- 1 | # Реквізити {#props} 2 | 3 | Дочірній компонент може приймати вхідні дані від батьківського компонента через **реквізити**. Спочатку йому потрібно оголосити реквізити, які він приймає: 4 | 5 |
    6 |
    7 | 8 | ```vue 9 | 10 | 15 | ``` 16 | 17 | Зверніть увагу, що `defineProps()` - це попередньо встановлений макрос, тому його не потрібно імпортувати. Після оголошення, реквізит `msg` стає доступним в `this` і може бути використаний у шаблоні дочірнього компонента. До нього також можна отримати доступ у JavaScript через повернутий об’єкт `defineProps()`. 18 | 19 |
    20 | 21 |
    22 | 23 | ```js 24 | // в дочірньому компоненті 25 | export default { 26 | props: { 27 | msg: String 28 | }, 29 | setup(props) { 30 | // доступ до props.msg 31 | } 32 | } 33 | ``` 34 | 35 | Після оголошення, реквізит `msg` стає доступним в `this` і може бути використаний у шаблоні дочірнього компонента. Отримані реквізити передаються в `setup()` як перший аргумент. 36 | 37 |
    38 | 39 |
    40 | 41 |
    42 | 43 | ```js 44 | // в дочірньому компоненті 45 | export default { 46 | props: { 47 | msg: String 48 | } 49 | } 50 | ``` 51 | 52 | Після оголошення, реквізит `msg` стає доступним в `this` і може бути використаний у шаблоні дочірнього компонента. 53 | 54 |
    55 | 56 | Батьки можуть передавати реквізити дочірньому елементу так само як і атрибути. Щоб передати динамічне значення, ми також можемо використовувати синтаксис `v-bind`: 57 | 58 |
    59 | 60 | ```vue-html 61 | 62 | ``` 63 | 64 |
    65 |
    66 | 67 | ```vue-html 68 | 69 | ``` 70 | 71 |
    72 | 73 | А тепер спробуйте самі в редакторі. 74 | -------------------------------------------------------------------------------- /src/tutorial/src/step-13/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | import ChildComp from './ChildComp.vue' 3 | 4 | export default { 5 | components: { 6 | ChildComp 7 | }, 8 | setup() { 9 | const childMsg = ref('Поки що немає повідомлення від дочірнього компонента') 10 | 11 | return { 12 | childMsg 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/tutorial/src/step-13/App/options.js: -------------------------------------------------------------------------------- 1 | import ChildComp from './ChildComp.vue' 2 | 3 | export default { 4 | components: { 5 | ChildComp 6 | }, 7 | data() { 8 | return { 9 | childMsg: 'Поки що немає повідомлення від дочірнього компонента' 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/tutorial/src/step-13/App/template.html: -------------------------------------------------------------------------------- 1 | 2 |

    {{ childMsg }}

    3 | -------------------------------------------------------------------------------- /src/tutorial/src/step-13/ChildComp/composition.js: -------------------------------------------------------------------------------- 1 | export default { 2 | emits: ['response'], 3 | setup(props, { emit }) { 4 | emit('response', 'привіт від дочірнього компонента') 5 | return {} 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/tutorial/src/step-13/ChildComp/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | emits: ['response'], 3 | created() { 4 | this.$emit('response', 'привіт від дочірнього компонента') 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/tutorial/src/step-13/ChildComp/template.html: -------------------------------------------------------------------------------- 1 |

    Дочірній компонент

    2 | -------------------------------------------------------------------------------- /src/tutorial/src/step-13/_hint/App/template.html: -------------------------------------------------------------------------------- 1 | 2 |

    {{ childMsg }}

    3 | -------------------------------------------------------------------------------- /src/tutorial/src/step-13/description.md: -------------------------------------------------------------------------------- 1 | # Випромінювання подій {#emits} 2 | 3 | Окрім отримання реквізитів, дочірній компонент також може випромінювати події батьківському компоненту: 4 | 5 |
    6 |
    7 | 8 | ```vue 9 | 16 | ``` 17 | 18 |
    19 | 20 |
    21 | 22 | ```js 23 | export default { 24 | // оголосіть випромінювані події 25 | emits: ['response'], 26 | setup(props, { emit }) { 27 | // передайте аргумент 28 | emit('response', 'привіт від дочірнього компонента') 29 | } 30 | } 31 | ``` 32 | 33 |
    34 | 35 |
    36 | 37 |
    38 | 39 | ```js 40 | export default { 41 | // оголосіть випромінювані події 42 | emits: ['response'], 43 | created() { 44 | // передайте аргумент 45 | this.$emit('response', 'привіт від дочірнього компонента') 46 | } 47 | } 48 | ``` 49 | 50 |
    51 | 52 | Першим аргументом функції `this.$emit()``emit()` є назва події. Будь-які додаткові аргументи передаються слухачу події. 53 | 54 | Батьківський компонент може прослуховувати події дочірніх компонентів за допомогою `v-on` - тут обробник отримує додатковий аргумент від виклику дочірнього компонента і присвоює його локальному стану: 55 | 56 |
    57 | 58 | ```vue-html 59 | 60 | ``` 61 | 62 |
    63 |
    64 | 65 | ```vue-html 66 | 67 | ``` 68 | 69 |
    70 | 71 | Тепер спробуйте самі в редакторі. 72 | -------------------------------------------------------------------------------- /src/tutorial/src/step-14/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | import ChildComp from './ChildComp.vue' 3 | 4 | export default { 5 | components: { 6 | ChildComp 7 | }, 8 | setup() { 9 | const msg = ref('з батьківського компонента') 10 | 11 | return { 12 | msg 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/tutorial/src/step-14/App/options.js: -------------------------------------------------------------------------------- 1 | import ChildComp from './ChildComp.vue' 2 | 3 | export default { 4 | components: { 5 | ChildComp 6 | }, 7 | data() { 8 | return { 9 | msg: 'з батьківського компонента' 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/tutorial/src/step-14/App/template.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/tutorial/src/step-14/ChildComp/template.html: -------------------------------------------------------------------------------- 1 | Запасний контент 2 | -------------------------------------------------------------------------------- /src/tutorial/src/step-14/_hint/App/template.html: -------------------------------------------------------------------------------- 1 | Повідомлення: {{ msg }} 2 | -------------------------------------------------------------------------------- /src/tutorial/src/step-14/description.md: -------------------------------------------------------------------------------- 1 | # Слоти {#slots} 2 | 3 | Окрім передачі даних через реквізити, батьківський компонент може також передавати фрагменти шаблону дочірньому компоненту через **слоти**: 4 | 5 |
    6 | 7 | ```vue-html 8 | 9 | Це деякий слот контент! 10 | 11 | ``` 12 | 13 |
    14 |
    15 | 16 | ```vue-html 17 | 18 | Це деякий слот контент! 19 | 20 | ``` 21 | 22 |
    23 | 24 | У дочірньому компоненті він може виводити контент слоту з батьківського, використовуючи елемент `` як вивід: 25 | 26 |
    27 | 28 | ```vue-html 29 | 30 | 31 | ``` 32 | 33 |
    34 |
    35 | 36 | ```vue-html 37 | 38 | 39 | ``` 40 | 41 |
    42 | 43 | Контент всередині `` буде розглядатися як "запасний": він буде показаний, якщо батьківський компонент не передав жодного контенту: 44 | 45 | ```vue-html 46 | Запасний контент 47 | ``` 48 | 49 | Наразі ми не передаємо жодного контенту слоту до ``, тому ви маєте бачити запасний контент. Надайте деякий контент слоту дочірньому компоненту, використовуючи для цього стан `msg` батьківського компонента. 50 | -------------------------------------------------------------------------------- /src/tutorial/src/step-15/App/composition.js: -------------------------------------------------------------------------------- 1 | import JSConfetti from 'js-confetti' 2 | 3 | const confetti = new JSConfetti() 4 | 5 | export default { 6 | setup() { 7 | function showConfetti() { 8 | confetti.addConfetti() 9 | } 10 | 11 | showConfetti() 12 | 13 | return { 14 | showConfetti 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/tutorial/src/step-15/App/options.js: -------------------------------------------------------------------------------- 1 | import JSConfetti from 'js-confetti' 2 | 3 | const confetti = new JSConfetti() 4 | 5 | export default { 6 | mounted() { 7 | this.showConfetti() 8 | }, 9 | methods: { 10 | showConfetti() { 11 | confetti.addConfetti() 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/tutorial/src/step-15/App/style.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | text-align: center; 3 | cursor: pointer; 4 | margin-top: 3em; 5 | } 6 | -------------------------------------------------------------------------------- /src/tutorial/src/step-15/App/template.html: -------------------------------------------------------------------------------- 1 |

    🎉 Вітаємо!

    2 | -------------------------------------------------------------------------------- /src/tutorial/src/step-15/description.md: -------------------------------------------------------------------------------- 1 | # Ви зробили це! {#you-did-it} 2 | 3 | Ви закінчили тренувальний курс! 4 | 5 | На цьому етапі ви повинні мати уявлення про те, як працювати з Vue. Однак ми розглянули багато речей дуже швидко і не зупинилися на деталях, тому обов'язково продовжуйте вчитися! Наступне, що ви можете зробити: 6 | 7 | - Створіть справжній проєкт Vue на своєму комп'ютері, дотримуючись інструкцій [швидкого старту](/guide/quick-start). 8 | 9 | - Перегляньте [основний гід](/guide/essentials/application), який більш детально охоплює всі теми, які ми вивчили до цього часу, і багато іншого. 10 | 11 | - Ознайомтесь ще з іншими [практичними прикладами](/examples/). 12 | 13 | Ми вже не можемо дочекатися, щоб побачити, що ви побудуєте далі! 14 | 15 | -------------------------------------------------------------------------------- /src/tutorial/src/step-15/import-map.json: -------------------------------------------------------------------------------- 1 | { 2 | "imports": { 3 | "js-confetti": "https://cdn.jsdelivr.net/npm/js-confetti/+esm" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/tutorial/src/step-2/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export default { 4 | setup() { 5 | // логіка компонента 6 | // оголосіть тут якийсь реактивний стан. 7 | 8 | return { 9 | // відкрийте для шаблону 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/tutorial/src/step-2/App/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // опції компоненти 3 | // оголосіть тут якийсь реактивний стан. 4 | } 5 | -------------------------------------------------------------------------------- /src/tutorial/src/step-2/App/template.html: -------------------------------------------------------------------------------- 1 |

    Зроби мене динамічним!

    2 | -------------------------------------------------------------------------------- /src/tutorial/src/step-2/_hint/App/composition.js: -------------------------------------------------------------------------------- 1 | import { reactive, ref } from 'vue' 2 | 3 | export default { 4 | setup() { 5 | const counter = reactive({ count: 0 }) 6 | const message = ref('Привіт, світе!') 7 | 8 | return { 9 | counter, 10 | message 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/tutorial/src/step-2/_hint/App/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | message: 'Привіт, світе!', 5 | counter: { 6 | count: 0 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/tutorial/src/step-2/_hint/App/template.html: -------------------------------------------------------------------------------- 1 |

    {{ message }}

    2 |

    Рахунок: {{ counter.count }}

    3 | -------------------------------------------------------------------------------- /src/tutorial/src/step-3/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export default { 4 | setup() { 5 | const titleClass = ref('title') 6 | 7 | return { 8 | titleClass 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/tutorial/src/step-3/App/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | titleClass: 'title' 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/tutorial/src/step-3/App/style.css: -------------------------------------------------------------------------------- 1 | .title { 2 | color: red; 3 | } 4 | -------------------------------------------------------------------------------- /src/tutorial/src/step-3/App/template.html: -------------------------------------------------------------------------------- 1 |

    Зробіть мене червоним

    2 | -------------------------------------------------------------------------------- /src/tutorial/src/step-3/_hint/App/template.html: -------------------------------------------------------------------------------- 1 |

    Зробіть мене червоним

    2 | -------------------------------------------------------------------------------- /src/tutorial/src/step-3/description.md: -------------------------------------------------------------------------------- 1 | # Прив'язування атрибутів {#attribute-bindings} 2 | 3 | Хвилясті дужки у Vue використовуються лише для інтерполяції тексту. Для того, щоб прив'язати атрибут до динамічного значення, ми використовуємо директиву `v-bind`: 4 | 5 | ```vue-html 6 |
    7 | ``` 8 | 9 | **Директива** - це спеціальний атрибут, який починається з префікса `v-`. Вона є частиною синтаксу темплейта Vue. Подібно до інтерполяції тексту, значеннями директив є вирази JavaScript, які мають доступ до стану компонента. Більш детально про `v-bind` та синтаксис директиви можна знайти в гіді про синтаксис шаблона. 10 | 11 | Частина, що після двокрапки (`:id`), є "аргументом" директиви. Тут атрибут `id` елемента буде синхронізовано з властивістю `dynamicId` зі стану компонента. 12 | 13 | Оскільки `v-bind` використовується дуже часто, він має спеціальний скорочений синтаксис: 14 | 15 | ```vue-html 16 |
    17 | ``` 18 | 19 | Тепер спробуйте додати динамічний `class`, прив'язавши його до `

    `, використовуючи властивість `titleClass` опції `data`референцію `titleClass` як її значення. Якщо він прив'язаний вірно, тоді текст стане червоним. 20 | -------------------------------------------------------------------------------- /src/tutorial/src/step-4/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export default { 4 | setup() { 5 | const count = ref(0) 6 | 7 | return { 8 | count 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/tutorial/src/step-4/App/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | count: 0 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/tutorial/src/step-4/App/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/tutorial/src/step-4/_hint/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export default { 4 | setup() { 5 | const count = ref(0) 6 | 7 | function increment() { 8 | count.value++ 9 | } 10 | 11 | return { 12 | count, 13 | increment 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/tutorial/src/step-4/_hint/App/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | count: 0 5 | } 6 | }, 7 | methods: { 8 | increment() { 9 | this.count++ 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/tutorial/src/step-4/_hint/App/template.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/tutorial/src/step-5/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export default { 4 | setup() { 5 | const text = ref('') 6 | 7 | function onInput(e) { 8 | text.value = e.target.value 9 | } 10 | 11 | return { 12 | text, 13 | onInput 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/tutorial/src/step-5/App/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | text: '' 5 | } 6 | }, 7 | methods: { 8 | onInput(e) { 9 | this.text = e.target.value 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/tutorial/src/step-5/App/template.html: -------------------------------------------------------------------------------- 1 | 2 |

    {{ text }}

    3 | -------------------------------------------------------------------------------- /src/tutorial/src/step-5/_hint/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export default { 4 | setup() { 5 | const text = ref('') 6 | 7 | return { 8 | text 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/tutorial/src/step-5/_hint/App/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | text: '' 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/tutorial/src/step-5/_hint/App/template.html: -------------------------------------------------------------------------------- 1 | 2 |

    {{ text }}

    3 | -------------------------------------------------------------------------------- /src/tutorial/src/step-5/description.md: -------------------------------------------------------------------------------- 1 | # Прив'язування елементів форми {#form-bindings} 2 | 3 | Використовуючи `v-bind` та `v-on` разом, ми можемо створити двосторонні прив'язки до полів введення форми: 4 | 5 | ```vue-html 6 | 7 | ``` 8 | 9 |
    10 | 11 | ```js 12 | methods: { 13 | onInput(e) { 14 | // v-on обробник отримує нативну подію DOM як аргумент. 15 | this.text = e.target.value 16 | } 17 | } 18 | ``` 19 | 20 |
    21 | 22 |
    23 | 24 | ```js 25 | function onInput(e) { 26 | // v-on обробник отримує нативну подію DOM як аргумент 27 | text.value = e.target.value 28 | } 29 | ``` 30 | 31 |
    32 | 33 | Спробуйте вписати щось в полі введення - ви повинні бачити текст в `

    `, який оновлюється під час введення. 34 | 35 | Щоб спростити двостороннє прив’язування, Vue надає директиву `v-model`, яка, по суті, є синтетичним цукром для вищесказаного: 36 | 37 | ```vue-html 38 | 39 | ``` 40 | 41 | `v-model` автоматично синхронізує значення `` з прив'язаним станом, тож для цього нам не потрібно більше використовувати обробника подій. 42 | 43 | `v-model` працює не лише з текстовими полями вводу, а також з іншими елементами такими як чекбокси, радіо кнопки та випадаючі списки. Більш детально ми це описали в гіді про прив'язку елементів введення. 44 | 45 | Тепер спробуйте змінити код, використовуючи `v-model`. 46 | -------------------------------------------------------------------------------- /src/tutorial/src/step-6/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export default { 4 | setup() { 5 | const awesome = ref(true) 6 | 7 | function toggle() { 8 | // ... 9 | } 10 | 11 | return { 12 | awesome, 13 | toggle 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/tutorial/src/step-6/App/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | awesome: true 5 | } 6 | }, 7 | methods: { 8 | toggle() { 9 | // ... 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/tutorial/src/step-6/App/template.html: -------------------------------------------------------------------------------- 1 | 2 |

    Vue - це круто!

    3 |

    Ой, ні 😢

    4 | -------------------------------------------------------------------------------- /src/tutorial/src/step-6/_hint/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export default { 4 | setup() { 5 | const awesome = ref(true) 6 | 7 | function toggle() { 8 | awesome.value = !awesome.value 9 | } 10 | 11 | return { 12 | awesome, 13 | toggle 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/tutorial/src/step-6/_hint/App/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | data() { 3 | return { 4 | awesome: true 5 | } 6 | }, 7 | methods: { 8 | toggle() { 9 | this.awesome = !this.awesome 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/tutorial/src/step-6/_hint/App/template.html: -------------------------------------------------------------------------------- 1 | 2 |

    Vue - це круто!

    3 |

    Ой, ні 😢

    4 | -------------------------------------------------------------------------------- /src/tutorial/src/step-6/description.md: -------------------------------------------------------------------------------- 1 | # Умовний рендеринг {#conditional-rendering} 2 | 3 | Ми можемо використовувати `v-if` директиву для умовного рендерингу елемента: 4 | 5 | ```vue-html 6 |

    Vue - це круто!

    7 | ``` 8 | 9 | Цей елемент `

    ` буде згенеровано лише якщо значення `awesome` є [правдивим](https://developer.mozilla.org/en-US/docs/Glossary/Truthy). Якщо ж `awesome` змінюється на [неправдиве значення](https://developer.mozilla.org/en-US/docs/Glossary/Falsy), тоді цей елемент буде видалено з DOM. 10 | 11 | Також можливо використовувати `v-else` та `v-else-if`, щоб вказати альтернативні варіанти умови: 12 | 13 | ```vue-html 14 |

    Vue - це круто!

    15 |

    Ой, ні 😢

    16 | ``` 17 | 18 | Наразі, демо показує обидва елементи `

    ` одночасно, і кнопка нічого не робить. Спробуйте додати директиви `v-if` та `v-else` і напишіть метод `toggle()` таким чином, щоб кнопка перемикала ці елементи. 19 | 20 | Більш детально про `v-if` в гіді про умовний рендеринг. 21 | -------------------------------------------------------------------------------- /src/tutorial/src/step-7/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export default { 4 | setup() { 5 | // надай кожному завданню унікальний id 6 | let id = 0 7 | 8 | const newTodo = ref('') 9 | const todos = ref([ 10 | { id: id++, text: 'Вивчити HTML' }, 11 | { id: id++, text: 'Вивчити JavaScript' }, 12 | { id: id++, text: 'Вивчити Vue' } 13 | ]) 14 | 15 | function addTodo() { 16 | // ... 17 | newTodo.value = '' 18 | } 19 | 20 | function removeTodo(todo) { 21 | // ... 22 | } 23 | 24 | return { 25 | newTodo, 26 | todos, 27 | addTodo, 28 | removeTodo 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/tutorial/src/step-7/App/options.js: -------------------------------------------------------------------------------- 1 | // надай кожному завданню унікальний id 2 | let id = 0 3 | 4 | export default { 5 | data() { 6 | return { 7 | newTodo: '', 8 | todos: [ 9 | { id: id++, text: 'Вивчити HTML' }, 10 | { id: id++, text: 'Вивчити JavaScript' }, 11 | { id: id++, text: 'Вивчити Vue' } 12 | ] 13 | } 14 | }, 15 | methods: { 16 | addTodo() { 17 | // ... 18 | this.newTodo = '' 19 | }, 20 | removeTodo(todo) { 21 | // ... 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/tutorial/src/step-7/App/template.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 |
    5 |
      6 |
    • 7 | {{ todo.text }} 8 | 9 |
    • 10 |
    11 | -------------------------------------------------------------------------------- /src/tutorial/src/step-7/_hint/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export default { 4 | setup() { 5 | // надай кожному завданню унікальний id 6 | let id = 0 7 | 8 | const newTodo = ref('') 9 | const todos = ref([ 10 | { id: id++, text: 'Вивчити HTML' }, 11 | { id: id++, text: 'Вивчити JavaScript' }, 12 | { id: id++, text: 'Вивчити Vue' } 13 | ]) 14 | 15 | function addTodo() { 16 | todos.value.push({ id: id++, text: newTodo.value }) 17 | newTodo.value = '' 18 | } 19 | 20 | function removeTodo(todo) { 21 | todos.value = todos.value.filter((t) => t !== todo) 22 | } 23 | 24 | return { 25 | newTodo, 26 | todos, 27 | addTodo, 28 | removeTodo 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/tutorial/src/step-7/_hint/App/options.js: -------------------------------------------------------------------------------- 1 | // надай кожному завданню унікальний id 2 | let id = 0 3 | 4 | export default { 5 | data() { 6 | return { 7 | newTodo: '', 8 | todos: [ 9 | { id: id++, text: 'Вивчити HTML' }, 10 | { id: id++, text: 'Вивчити JavaScript' }, 11 | { id: id++, text: 'Вивчити Vue' } 12 | ] 13 | } 14 | }, 15 | methods: { 16 | addTodo() { 17 | this.todos.push({ id: id++, text: this.newTodo }) 18 | this.newTodo = '' 19 | }, 20 | removeTodo(todo) { 21 | this.todos = this.todos.filter((t) => t !== todo) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/tutorial/src/step-7/description.md: -------------------------------------------------------------------------------- 1 | # Рендеринг списків {#list-rendering} 2 | 3 | Ми можемо використовувати директиву `v-for` для рендерингу списку елементів на основі вихідного масиву 4 | 5 | ```vue-html 6 |
      7 |
    • 8 | {{ todo.text }} 9 |
    • 10 |
    11 | ``` 12 | 13 | Тут `todo` — це локальна змінна, що представляє собою елемент масиву, над яким відбувається ітерація. Вона доступна лише в елементі `v-for` або всередині нього, подібно до області видимості функції. 14 | 15 | Зверніть увагу, що ми також надаємо кожному об’єкту todo унікальний `id` і прив’язуємо його як спеціальний атрибут `key` для кожного `
  • `. `key` дозволяє Vue точно переміщувати кожен `
  • ` відповідно до позиції його відповідного об’єкта в масиві. 16 | 17 | Оновити список можна двома способами: 18 | 19 | 1. Викликати [методи мутації](https://stackoverflow.com/questions/9009879/which-javascript-array-functions-are-mutating) вихідного масиву: 20 | 21 |
    22 | 23 | ```js 24 | todos.value.push(newTodo) 25 | ``` 26 | 27 |
    28 |
    29 | 30 | ```js 31 | this.todos.push(newTodo) 32 | ``` 33 | 34 |
    35 | 36 | 2. Замінити масив на новий: 37 | 38 |
    39 | 40 | ```js 41 | todos.value = todos.value.filter(/* ... */) 42 | ``` 43 | 44 |
    45 |
    46 | 47 | ```js 48 | this.todos = this.todos.filter(/* ... */) 49 | ``` 50 | 51 |
    52 | 53 | У нас є простий список завдань — спробуйте реалізувати логіку для методів `addTodo()` та `removeTodo()`, щоб він запрацював! 54 | 55 | Більш детально про `v-for` у гіді про рендеринг списків. 56 | -------------------------------------------------------------------------------- /src/tutorial/src/step-8/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export default { 4 | setup() { 5 | let id = 0 6 | 7 | const newTodo = ref('') 8 | const hideCompleted = ref(false) 9 | const todos = ref([ 10 | { id: id++, text: 'Вивчити HTML', done: true }, 11 | { id: id++, text: 'Вивчити JavaScript', done: true }, 12 | { id: id++, text: 'Вивчити Vue', done: false } 13 | ]) 14 | 15 | function addTodo() { 16 | todos.value.push({ id: id++, text: newTodo.value, done: false }) 17 | newTodo.value = '' 18 | } 19 | 20 | function removeTodo(todo) { 21 | todos.value = todos.value.filter((t) => t !== todo) 22 | } 23 | 24 | return { 25 | newTodo, 26 | hideCompleted, 27 | todos, 28 | addTodo, 29 | removeTodo 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/tutorial/src/step-8/App/options.js: -------------------------------------------------------------------------------- 1 | let id = 0 2 | 3 | export default { 4 | data() { 5 | return { 6 | newTodo: '', 7 | hideCompleted: false, 8 | todos: [ 9 | { id: id++, text: 'Вивчити HTML', done: true }, 10 | { id: id++, text: 'Вивчити JavaScript', done: true }, 11 | { id: id++, text: 'Вивчити Vue', done: false } 12 | ] 13 | } 14 | }, 15 | computed: { 16 | // ... 17 | }, 18 | methods: { 19 | addTodo() { 20 | this.todos.push({ id: id++, text: this.newTodo, done: false }) 21 | this.newTodo = '' 22 | }, 23 | removeTodo(todo) { 24 | this.todos = this.todos.filter((t) => t !== todo) 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/tutorial/src/step-8/App/style.css: -------------------------------------------------------------------------------- 1 | .done { 2 | text-decoration: line-through; 3 | } 4 | -------------------------------------------------------------------------------- /src/tutorial/src/step-8/App/template.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 |
    5 |
      6 |
    • 7 | 8 | {{ todo.text }} 9 | 10 |
    • 11 |
    12 | 15 | -------------------------------------------------------------------------------- /src/tutorial/src/step-8/_hint/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref, computed } from 'vue' 2 | 3 | export default { 4 | setup() { 5 | let id = 0 6 | 7 | const newTodo = ref('') 8 | const hideCompleted = ref(false) 9 | const todos = ref([ 10 | { id: id++, text: 'Вивчити HTML', done: true }, 11 | { id: id++, text: 'Вивчити JavaScript', done: true }, 12 | { id: id++, text: 'Вивчити Vue', done: false } 13 | ]) 14 | 15 | const filteredTodos = computed(() => { 16 | return hideCompleted.value 17 | ? todos.value.filter((t) => !t.done) 18 | : todos.value 19 | }) 20 | 21 | function addTodo() { 22 | todos.value.push({ id: id++, text: newTodo.value, done: false }) 23 | newTodo.value = '' 24 | } 25 | 26 | function removeTodo(todo) { 27 | todos.value = todos.value.filter((t) => t !== todo) 28 | } 29 | 30 | return { 31 | newTodo, 32 | hideCompleted, 33 | todos, 34 | filteredTodos, 35 | addTodo, 36 | removeTodo 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/tutorial/src/step-8/_hint/App/options.js: -------------------------------------------------------------------------------- 1 | let id = 0 2 | 3 | export default { 4 | data() { 5 | return { 6 | newTodo: '', 7 | hideCompleted: false, 8 | todos: [ 9 | { id: id++, text: 'Вивчити HTML', done: true }, 10 | { id: id++, text: 'Вивчити JavaScript', done: true }, 11 | { id: id++, text: 'Вивчити Vue', done: false } 12 | ] 13 | } 14 | }, 15 | computed: { 16 | filteredTodos() { 17 | return this.hideCompleted 18 | ? this.todos.filter((t) => !t.done) 19 | : this.todos 20 | } 21 | }, 22 | methods: { 23 | addTodo() { 24 | this.todos.push({ id: id++, text: this.newTodo, done: false }) 25 | this.newTodo = '' 26 | }, 27 | removeTodo(todo) { 28 | this.todos = this.todos.filter((t) => t !== todo) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/tutorial/src/step-8/_hint/App/template.html: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 4 |
    5 |
      6 |
    • 7 | 8 | {{ todo.text }} 9 | 10 |
    • 11 |
    12 | 15 | -------------------------------------------------------------------------------- /src/tutorial/src/step-9/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | export default { 4 | setup() { 5 | const pElementRef = ref(null) 6 | 7 | return { 8 | pElementRef 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/tutorial/src/step-9/App/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // ... 3 | } 4 | -------------------------------------------------------------------------------- /src/tutorial/src/step-9/App/template.html: -------------------------------------------------------------------------------- 1 |

    Привіт

    2 | -------------------------------------------------------------------------------- /src/tutorial/src/step-9/_hint/App/composition.js: -------------------------------------------------------------------------------- 1 | import { ref, onMounted } from 'vue' 2 | 3 | export default { 4 | setup() { 5 | const pElementRef = ref(null) 6 | 7 | onMounted(() => { 8 | pElementRef.value.textContent = 'Змонтовано!' 9 | }) 10 | 11 | return { 12 | pElementRef 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/tutorial/src/step-9/_hint/App/options.js: -------------------------------------------------------------------------------- 1 | export default { 2 | mounted() { 3 | this.$refs.pElementRef.textContent = 'Змонтовано!' 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/tutorial/tutorial.data.ts: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import { createMarkdownRenderer } from 'vitepress' 3 | import { readExamples, ExampleData } from '../examples/examples.data' 4 | 5 | export declare const data: Record 6 | 7 | export default { 8 | watch: './src/**', 9 | async load() { 10 | const md = await createMarkdownRenderer(process.cwd(), undefined, '/') 11 | const files = readExamples(path.resolve(__dirname, './src')) 12 | for (const step in files) { 13 | const stepFiles = files[step] 14 | const desc = stepFiles['description.md'] as string 15 | if (desc) { 16 | stepFiles['description.md'] = md.render(desc) 17 | } 18 | } 19 | return files 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "target": "esnext", 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "esModuleInterop": true, 8 | "resolveJsonModule": true, 9 | "allowJs": true, 10 | "strict": true, 11 | "jsx": "preserve", 12 | "baseUrl": ".", 13 | "paths": { 14 | "@theme/*": [".vitepress/theme/*"] 15 | } 16 | }, 17 | "include": ["env.d.ts", "src/**/*", ".vitepress/**/*"] 18 | } 19 | --------------------------------------------------------------------------------