From 77486c2b1ea08afb57e6c1c9692c207cbf669fc4 Mon Sep 17 00:00:00 2001 From: Razvan Stoenescu Date: Thu, 27 Feb 2025 14:11:20 +0200 Subject: [PATCH 01/32] fix(docs): q/app-vite > upgrade guide > wrong definition renderPreloadTag #17863 --- .../pages/quasar-cli-vite/upgrade-guide.md | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/docs/src/pages/quasar-cli-vite/upgrade-guide.md b/docs/src/pages/quasar-cli-vite/upgrade-guide.md index bafe0d209f4..5aa18453061 100644 --- a/docs/src/pages/quasar-cli-vite/upgrade-guide.md +++ b/docs/src/pages/quasar-cli-vite/upgrade-guide.md @@ -273,7 +273,7 @@ Preparations: + import { defineStore } from '#q-app/wrappers' - import { ssrMiddleware } from 'quasar/wrappers' - + import { defineSsrMiddleware }from '#q-app/wrappers' + + import { defineSsrMiddleware } from '#q-app/wrappers' - import { ssrCreate } from 'quasar/wrappers' + import { defineSsrCreate } from '#q-app/wrappers' @@ -677,7 +677,25 @@ app.on('activate', () => { The distributables (your production code) will be compiled to ESM form. ::: -Most changes refer to editing your `/src-ssr/server.js` file. Since you can now use HTTPS while developing your app too, you need to make the following changes to the file: +```diff /src-ssr/middlewares/* +- import { ssrMiddleware } from 'quasar/wrappers' ++ import { defineSsrMiddleware } from '#q-app/wrappers' + +- export default ssrMiddleware({ ++ export default defineSsrMiddleware(({ + app, + port, + resolve, + publicPath, + folders, + render, + serve +}) => { + // something to do with the server "app" +}) +``` + +The other changes refer to editing your `/src-ssr/server.js` file. Since you can now use HTTPS while developing your app too, you need to make the following changes to the file: ```diff /src-ssr/server.js > listen - import { ssrListen } from 'quasar/wrappers' @@ -764,7 +782,7 @@ Also, the `renderPreloadTag()` function can now take an additional parameter (`s - import { ssrRenderPreloadTag } from 'quasar/wrappers' + import { defineSsrRenderPreloadTag } from '#q-app/wrappers' -+ export const renderPreloadTag = ssrRenderPreloadTag((file, { ssrContext }) => { ++ export const renderPreloadTag = defineSsrRenderPreloadTag((file, { ssrContext }) => { + // ... + }) ``` From 55f00ca5ed737d7f9df64dd0476c1acb4389b708 Mon Sep 17 00:00:00 2001 From: Razvan Stoenescu Date: Thu, 27 Feb 2025 14:43:28 +0200 Subject: [PATCH 02/32] fix(create-quasar): vueTsEslintConfig is deprecated in estlint.config.js #17862 --- .../quasar-v2/ts-vite-2/BASE/_package.json | 2 +- .../ts-vite-2/eslint/_eslint.config.js | 25 +++++-------------- .../quasar-v2/ts-webpack-4/BASE/_package.json | 2 +- .../ts-webpack-4/eslint/_eslint.config.js | 25 +++++-------------- 4 files changed, 14 insertions(+), 40 deletions(-) diff --git a/create-quasar/templates/app/quasar-v2/ts-vite-2/BASE/_package.json b/create-quasar/templates/app/quasar-v2/ts-vite-2/BASE/_package.json index 1a72db6f6e2..50910f108a2 100644 --- a/create-quasar/templates/app/quasar-v2/ts-vite-2/BASE/_package.json +++ b/create-quasar/templates/app/quasar-v2/ts-vite-2/BASE/_package.json @@ -30,7 +30,7 @@ "eslint-plugin-vue": "^9.30.0", "globals": "^15.12.0", "vue-tsc": "^2.0.29", - "@vue/eslint-config-typescript": "^14.1.3", + "@vue/eslint-config-typescript": "^14.4.0", "vite-plugin-checker": "^0.8.0", <% } %> <% if (prettier) { %> diff --git a/create-quasar/templates/app/quasar-v2/ts-vite-2/eslint/_eslint.config.js b/create-quasar/templates/app/quasar-v2/ts-vite-2/eslint/_eslint.config.js index 24320bf8e41..6ef1fd3ca18 100644 --- a/create-quasar/templates/app/quasar-v2/ts-vite-2/eslint/_eslint.config.js +++ b/create-quasar/templates/app/quasar-v2/ts-vite-2/eslint/_eslint.config.js @@ -2,10 +2,10 @@ import js from '@eslint/js' import globals from 'globals' import pluginVue from 'eslint-plugin-vue' import pluginQuasar from '@quasar/app-vite/eslint' -import vueTsEslintConfig from '@vue/eslint-config-typescript'<% if (prettier) { %> +import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'<% if (prettier) { %> import prettierSkipFormatting from '@vue/eslint-config-prettier/skip-formatting'<% } %> -export default [ +export default defineConfigWithVueTs( { /** * Ignore the following files. @@ -18,7 +18,7 @@ export default [ // ignores: [] }, - ...pluginQuasar.configs.recommended(), + pluginQuasar.configs.recommended(), js.configs.recommended, /** @@ -33,7 +33,7 @@ export default [ * pluginVue.configs["flat/recommended"] * -> Above, plus rules to enforce subjective community defaults to ensure consistency. */ - ...pluginVue.configs[ 'flat/essential' ], + pluginVue.configs[ 'flat/essential' ], { files: ['**/*.ts', '**/*.vue'], @@ -45,20 +45,7 @@ export default [ } }, // https://github.com/vuejs/eslint-config-typescript - ...vueTsEslintConfig({ - // Optional: extend additional configurations from typescript-eslint'. - // Supports all the configurations in - // https://typescript-eslint.io/users/configs#recommended-configurations - extends: [ - // By default, only the 'recommendedTypeChecked' rules are enabled. - 'recommendedTypeChecked' - // You can also manually enable the stylistic rules. - // "stylistic", - - // Other utility configurations, such as 'eslintRecommended', (note that it's in camelCase) - // are also extendable here. But we don't recommend using them directly. - ] - }), + vueTsConfigs.recommendedTypeChecked, { languageOptions: { @@ -96,4 +83,4 @@ export default [ }<% if (prettier) { %>, prettierSkipFormatting<% } %> -] +) diff --git a/create-quasar/templates/app/quasar-v2/ts-webpack-4/BASE/_package.json b/create-quasar/templates/app/quasar-v2/ts-webpack-4/BASE/_package.json index b8a8e904285..b5055ef6404 100644 --- a/create-quasar/templates/app/quasar-v2/ts-webpack-4/BASE/_package.json +++ b/create-quasar/templates/app/quasar-v2/ts-webpack-4/BASE/_package.json @@ -30,7 +30,7 @@ "eslint": "^9.14.0", "eslint-plugin-vue": "^9.30.0", "eslint-webpack-plugin": "^4.2.0", - "@vue/eslint-config-typescript": "^14.1.3", + "@vue/eslint-config-typescript": "^14.4.0", "globals": "^15.12.0", <% } %> <% if (prettier) { %> diff --git a/create-quasar/templates/app/quasar-v2/ts-webpack-4/eslint/_eslint.config.js b/create-quasar/templates/app/quasar-v2/ts-webpack-4/eslint/_eslint.config.js index 1da668fd247..30bcc8e06c3 100644 --- a/create-quasar/templates/app/quasar-v2/ts-webpack-4/eslint/_eslint.config.js +++ b/create-quasar/templates/app/quasar-v2/ts-webpack-4/eslint/_eslint.config.js @@ -2,10 +2,10 @@ import js from '@eslint/js' import globals from 'globals' import pluginVue from 'eslint-plugin-vue' import pluginQuasar from '@quasar/app-webpack/eslint' -import vueTsEslintConfig from '@vue/eslint-config-typescript'<% if (prettier) { %> +import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'<% if (prettier) { %> import prettierSkipFormatting from '@vue/eslint-config-prettier/skip-formatting'<% } %> -export default [ +export default defineConfigWithVueTs( { /** * Ignore the following files. @@ -18,7 +18,7 @@ export default [ // ignores: [] }, - ...pluginQuasar.configs.recommended(), + pluginQuasar.configs.recommended(), js.configs.recommended, /** @@ -33,7 +33,7 @@ export default [ * pluginVue.configs["flat/recommended"] * -> Above, plus rules to enforce subjective community defaults to ensure consistency. */ - ...pluginVue.configs[ 'flat/essential' ], + pluginVue.configs[ 'flat/essential' ], // this rule needs to be above the vueTsEslintConfig to avoid error: 'You have used a rule which requires type information, but don't have parserOptions set to generate type information for this file.' { @@ -46,20 +46,7 @@ export default [ } }, // https://github.com/vuejs/eslint-config-typescript - ...vueTsEslintConfig({ - // Optional: extend additional configurations from typescript-eslint'. - // Supports all the configurations in - // https://typescript-eslint.io/users/configs#recommended-configurations - extends: [ - // By default, only the 'recommendedTypeChecked' rules are enabled. - 'recommendedTypeChecked' - // You can also manually enable the stylistic rules. - // "stylistic", - - // Other utility configurations, such as 'eslintRecommended', (note that it's in camelCase) - // are also extendable here. But we don't recommend using them directly. - ] - }), + vueTsConfigs.recommendedTypeChecked, { languageOptions: { @@ -97,4 +84,4 @@ export default [ }<% if (prettier) { %>, prettierSkipFormatting<% } %> -] +) From e758836969d4c5623a244a5a1a07c51fe82deac1 Mon Sep 17 00:00:00 2001 From: Razvan Stoenescu Date: Thu, 27 Feb 2025 14:43:53 +0200 Subject: [PATCH 03/32] feat(docs): update TS linting setup --- docs/src/pages/quasar-cli-vite/linter.md | 26 +++++---------------- docs/src/pages/quasar-cli-webpack/linter.md | 25 +++++--------------- 2 files changed, 12 insertions(+), 39 deletions(-) diff --git a/docs/src/pages/quasar-cli-vite/linter.md b/docs/src/pages/quasar-cli-vite/linter.md index ccb7244d832..9a2ae779bfc 100644 --- a/docs/src/pages/quasar-cli-vite/linter.md +++ b/docs/src/pages/quasar-cli-vite/linter.md @@ -181,12 +181,12 @@ import js from '@eslint/js' import globals from 'globals' import pluginVue from 'eslint-plugin-vue' import pluginQuasar from '@quasar/app-vite/eslint' -import vueTsEslintConfig from '@vue/eslint-config-typescript' +import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript' // the following is optional, if you want prettier too: import prettierSkipFormatting from '@vue/eslint-config-prettier/skip-formatting' -export default [ +export default defineConfigWithVueTs( { /** * Ignore the following files. @@ -199,7 +199,7 @@ export default [ // ignores: [] }, - ...pluginQuasar.configs.recommended(), + pluginQuasar.configs.recommended(), js.configs.recommended, /** @@ -214,7 +214,7 @@ export default [ * pluginVue.configs["flat/recommended"] * -> Above, plus rules to enforce subjective community defaults to ensure consistency. */ - ...pluginVue.configs[ 'flat/essential' ], + pluginVue.configs[ 'flat/essential' ], { files: ['**/*.ts', '**/*.vue'], @@ -225,21 +225,7 @@ export default [ ], } }, - // https://github.com/vuejs/eslint-config-typescript - ...vueTsEslintConfig({ - // Optional: extend additional configurations from typescript-eslint'. - // Supports all the configurations in - // https://typescript-eslint.io/users/configs#recommended-configurations - extends: [ - // By default, only the 'recommendedTypeChecked' rules are enabled. - 'recommendedTypeChecked' - // You can also manually enable the stylistic rules. - // "stylistic", - - // Other utility configurations, such as 'eslintRecommended', (note that it's in camelCase) - // are also extendable here. But we don't recommend using them directly. - ] - }), + vueTsConfigs.recommendedTypeChecked, { languageOptions: { @@ -277,7 +263,7 @@ export default [ }, prettierSkipFormatting // optional, if you want prettier -] +) ``` ## Performance and ignoring files diff --git a/docs/src/pages/quasar-cli-webpack/linter.md b/docs/src/pages/quasar-cli-webpack/linter.md index cc963666aa3..4692ae84029 100644 --- a/docs/src/pages/quasar-cli-webpack/linter.md +++ b/docs/src/pages/quasar-cli-webpack/linter.md @@ -184,12 +184,12 @@ import js from '@eslint/js' import globals from 'globals' import pluginVue from 'eslint-plugin-vue' import pluginQuasar from '@quasar/app-webpack/eslint' -import vueTsEslintConfig from '@vue/eslint-config-typescript' +import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript' // the following is optional, if you want prettier too: import prettierSkipFormatting from '@vue/eslint-config-prettier/skip-formatting' -export default [ +export default defineConfigWithVueTs( { /** * Ignore the following files. @@ -202,7 +202,7 @@ export default [ // ignores: [] }, - ...pluginQuasar.configs.recommended(), + pluginQuasar.configs.recommended(), js.configs.recommended, /** @@ -217,7 +217,7 @@ export default [ * pluginVue.configs["flat/recommended"] * -> Above, plus rules to enforce subjective community defaults to ensure consistency. */ - ...pluginVue.configs[ 'flat/essential' ], + pluginVue.configs[ 'flat/essential' ], { files: ['**/*.ts', '**/*.vue'], @@ -229,20 +229,7 @@ export default [ } }, // https://github.com/vuejs/eslint-config-typescript - ...vueTsEslintConfig({ - // Optional: extend additional configurations from typescript-eslint'. - // Supports all the configurations in - // https://typescript-eslint.io/users/configs#recommended-configurations - extends: [ - // By default, only the 'recommendedTypeChecked' rules are enabled. - 'recommendedTypeChecked' - // You can also manually enable the stylistic rules. - // "stylistic", - - // Other utility configurations, such as 'eslintRecommended', (note that it's in camelCase) - // are also extendable here. But we don't recommend using them directly. - ] - }), + vueTsConfigs.recommendedTypeChecked, { languageOptions: { @@ -280,7 +267,7 @@ export default [ }, prettierSkipFormatting // optional, if you want prettier -] +) ``` ## Performance and ignoring files From 88dc08f0cf8746ba1963dbd96788dce5d72f476e Mon Sep 17 00:00:00 2001 From: Razvan Stoenescu Date: Thu, 27 Feb 2025 14:44:26 +0200 Subject: [PATCH 04/32] chore(create-quasar): bump version --- create-quasar/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/create-quasar/package.json b/create-quasar/package.json index fb969105170..c22a8d7704d 100644 --- a/create-quasar/package.json +++ b/create-quasar/package.json @@ -1,6 +1,6 @@ { "name": "create-quasar", - "version": "2.0.0", + "version": "2.0.1", "description": "Scaffolds Quasar Apps, AppExtensions or UI kits", "type": "module", "author": { From 8f4ffd14fda6e0ffbf000b688f9e11807dcae82c Mon Sep 17 00:00:00 2001 From: Samantha Vanini <103991118+SamVanini@users.noreply.github.com> Date: Thu, 27 Feb 2025 13:57:28 +0100 Subject: [PATCH 05/32] fix(ui): add aria-label to table pagination buttons (fix: #17148) (#17809) * fix(ui): Added aria-label to table paginator buttons, managed labels internationalization #17148 * fix(ui): move aria-labels in separate object * fix(ui): add aria-label to pagination buttons #17148 * fix(ui): fix pagination labels * Update ui/lang/da.js Co-authored-by: Yusuf Kandemir * Update ui/lang/ja.js Co-authored-by: Yusuf Kandemir * Update ui/lang/kur-CKB.js Co-authored-by: Yusuf Kandemir --------- Co-authored-by: Razvan Stoenescu Co-authored-by: Yusuf Kandemir --- ui/lang/ar-TN.js | 6 ++++++ ui/lang/ar.js | 6 ++++++ ui/lang/az-Latn.js | 6 ++++++ ui/lang/bg.js | 6 ++++++ ui/lang/bn.js | 6 ++++++ ui/lang/bs-BA.js | 6 ++++++ ui/lang/ca.js | 6 ++++++ ui/lang/cs.js | 6 ++++++ ui/lang/da.js | 6 ++++++ ui/lang/de-CH.js | 6 ++++++ ui/lang/de-DE.js | 6 ++++++ ui/lang/de.js | 6 ++++++ ui/lang/el.js | 6 ++++++ ui/lang/en-GB.js | 6 ++++++ ui/lang/en-US.js | 6 ++++++ ui/lang/eo.js | 6 ++++++ ui/lang/es.js | 6 ++++++ ui/lang/et.js | 6 ++++++ ui/lang/eu.js | 6 ++++++ ui/lang/fa-IR.js | 6 ++++++ ui/lang/fa.js | 6 ++++++ ui/lang/fi.js | 6 ++++++ ui/lang/fr.js | 6 ++++++ ui/lang/gn.js | 6 ++++++ ui/lang/he.js | 6 ++++++ ui/lang/hi.js | 6 ++++++ ui/lang/hr.js | 6 ++++++ ui/lang/hu.js | 6 ++++++ ui/lang/id.js | 6 ++++++ ui/lang/is.js | 6 ++++++ ui/lang/it.js | 6 ++++++ ui/lang/ja.js | 6 ++++++ ui/lang/kk.js | 6 ++++++ ui/lang/km.js | 6 ++++++ ui/lang/ko-KR.js | 6 ++++++ ui/lang/kur-CKB.js | 6 ++++++ ui/lang/lt.js | 6 ++++++ ui/lang/lu.js | 6 ++++++ ui/lang/lv.js | 6 ++++++ ui/lang/mk.js | 6 ++++++ ui/lang/ml.js | 6 ++++++ ui/lang/mm.js | 6 ++++++ ui/lang/ms-MY.js | 6 ++++++ ui/lang/ms.js | 6 ++++++ ui/lang/my.js | 6 ++++++ ui/lang/nb-NO.js | 6 ++++++ ui/lang/nl.js | 6 ++++++ ui/lang/pl.js | 6 ++++++ ui/lang/pt-BR.js | 6 ++++++ ui/lang/pt.js | 6 ++++++ ui/lang/ro.js | 6 ++++++ ui/lang/ru.js | 6 ++++++ ui/lang/sk.js | 6 ++++++ ui/lang/sl.js | 6 ++++++ ui/lang/sm.js | 6 ++++++ ui/lang/sr-CYR.js | 6 ++++++ ui/lang/sr.js | 6 ++++++ ui/lang/sv.js | 6 ++++++ ui/lang/ta.js | 6 ++++++ ui/lang/th.js | 6 ++++++ ui/lang/tl.js | 6 ++++++ ui/lang/tr.js | 6 ++++++ ui/lang/uk.js | 6 ++++++ ui/lang/uz-Cyrl.js | 6 ++++++ ui/lang/uz-Latn.js | 6 ++++++ ui/lang/vi.js | 6 ++++++ ui/lang/zh-CN.js | 6 ++++++ ui/lang/zh-TW.js | 6 ++++++ ui/src/components/pagination/QPagination.js | 23 +++++++++++++++++---- ui/src/components/table/QTable.js | 4 ++++ 70 files changed, 431 insertions(+), 4 deletions(-) diff --git a/ui/lang/ar-TN.js b/ui/lang/ar-TN.js index 0bc6a10fedc..5741f795d6f 100644 --- a/ui/lang/ar-TN.js +++ b/ui/lang/ar-TN.js @@ -46,6 +46,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' من ' + total, columns: 'أعمدة' }, + pagination: { + first: 'الصفحة الأولى', + prev: 'الصفحة السابقة', + next: 'الصفحة التالية', + last: 'الصفحة الأخيرة' + }, editor: { url: 'رابط', bold: 'عريض', diff --git a/ui/lang/ar.js b/ui/lang/ar.js index 48ca95d7758..10075a4f251 100644 --- a/ui/lang/ar.js +++ b/ui/lang/ar.js @@ -46,6 +46,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' من ' + total, columns: 'أعمدة' }, + pagination: { + first: 'الصفحة الأولى', + prev: 'الصفحة السابقة', + next: 'الصفحة التالية', + last: 'الصفحة الأخيرة' + }, editor: { url: 'رابط', bold: 'عريض', diff --git a/ui/lang/az-Latn.js b/ui/lang/az-Latn.js index f105e4f2b38..28f5465a142 100644 --- a/ui/lang/az-Latn.js +++ b/ui/lang/az-Latn.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' cəmi ' + total, columns: 'Sütun' }, + pagination: { + first: 'İlk səhifə', + prev: 'Əvvəlki səhifə', + next: 'Növbəti səhifə', + last: 'Son səhifə' + }, editor: { url: 'URL', bold: 'Bold', diff --git a/ui/lang/bg.js b/ui/lang/bg.js index d3fb8993797..4fed095b7e7 100644 --- a/ui/lang/bg.js +++ b/ui/lang/bg.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' от ' + total, columns: 'Колони' }, + pagination: { + first: 'Първа страница', + prev: 'Предишна страница', + next: 'Следваща страница', + last: 'Последна страница' + }, editor: { url: 'URL', bold: 'Удебелен', diff --git a/ui/lang/bn.js b/ui/lang/bn.js index b74e1a977fe..06c76d013ea 100644 --- a/ui/lang/bn.js +++ b/ui/lang/bn.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' মধ্যে ' + total, columns: 'কলাম' }, + pagination: { + first: 'প্রথম পৃষ্ঠা', + prev: 'পূর্ববর্তী পৃষ্ঠা', + next: 'পরবর্তী পৃষ্ঠা', + last: 'শেষ পৃষ্ঠা' + }, editor: { url: 'ইউ আর এল', bold: 'মোটা', diff --git a/ui/lang/bs-BA.js b/ui/lang/bs-BA.js index b60a34d0913..2f7cb2c3194 100644 --- a/ui/lang/bs-BA.js +++ b/ui/lang/bs-BA.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' od ' + total, columns: 'Kolone' }, + pagination: { + first: 'Prva stranica', + prev: 'Prethodna stranica', + next: 'Sljedeća stranica', + last: 'Zadnja stranica' + }, editor: { url: 'URL', bold: 'Podebljano', diff --git a/ui/lang/ca.js b/ui/lang/ca.js index a9d317ed2df..cf95632d78a 100644 --- a/ui/lang/ca.js +++ b/ui/lang/ca.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' de ' + total, columns: 'Columnes' }, + pagination: { + first: 'Primera pàgina', + prev: 'Pàgina anterior', + next: 'Pàgina següent', + last: 'Última pàgina' + }, editor: { url: 'URL', bold: 'Negreta', diff --git a/ui/lang/cs.js b/ui/lang/cs.js index c8ea5916cd9..352fc383042 100644 --- a/ui/lang/cs.js +++ b/ui/lang/cs.js @@ -52,6 +52,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' z ' + total, columns: 'Sloupce' }, + pagination: { + first: 'První stránka', + prev: 'Předchozí stránka', + next: 'Další stránka', + last: 'Poslední stránka' + }, editor: { url: 'URL', bold: 'Tučně', diff --git a/ui/lang/da.js b/ui/lang/da.js index 1072309d9b6..8365e102dd2 100644 --- a/ui/lang/da.js +++ b/ui/lang/da.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' af ' + total, columns: 'Kolonner' }, + pagination: { + first: 'Første side', + prev: 'Forrige side', + next: 'Næste side', + last: 'Sidste side' + }, editor: { url: 'URL', bold: 'Fed', diff --git a/ui/lang/de-CH.js b/ui/lang/de-CH.js index e3ffd3c2f12..165269f63cc 100644 --- a/ui/lang/de-CH.js +++ b/ui/lang/de-CH.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' von ' + total, columns: 'Spalten' }, + pagination: { + first: 'Erste Seite', + prev: 'Vorherige Seite', + next: 'Nächste Seite', + last: 'Letzte Seite' + }, editor: { url: 'URL', bold: 'Fett', diff --git a/ui/lang/de-DE.js b/ui/lang/de-DE.js index 5a798b00299..df70b82d030 100644 --- a/ui/lang/de-DE.js +++ b/ui/lang/de-DE.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' von ' + total, columns: 'Spalten' }, + pagination: { + first: 'Erste Seite', + prev: 'Vorherige Seite', + next: 'Nächste Seite', + last: 'Letzte Seite' + }, editor: { url: 'URL', bold: 'Fett', diff --git a/ui/lang/de.js b/ui/lang/de.js index e5c429842d3..bf4d5099861 100644 --- a/ui/lang/de.js +++ b/ui/lang/de.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' von ' + total, columns: 'Spalten' }, + pagination: { + first: 'Erste Seite', + prev: 'Vorherige Seite', + next: 'Nächste Seite', + last: 'Letzte Seite' + }, editor: { url: 'URL', bold: 'Fett', diff --git a/ui/lang/el.js b/ui/lang/el.js index 19ce2e0cf82..816c1ca3129 100644 --- a/ui/lang/el.js +++ b/ui/lang/el.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' από ' + total, columns: 'Στήλες' }, + pagination: { + first: 'Πρώτη σελίδα', + prev: 'Προηγούμενη σελίδα', + next: 'Επόμενη σελίδα', + last: 'Τελευταία σελίδα' + }, editor: { url: 'Διεύθυνση URL', // Needs Translation bold: 'Έντονα', diff --git a/ui/lang/en-GB.js b/ui/lang/en-GB.js index 3f038d563cd..bfc671f37ef 100644 --- a/ui/lang/en-GB.js +++ b/ui/lang/en-GB.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' of ' + total, columns: 'Columns' }, + pagination: { + first: 'First page', + prev: 'Previous page', + next: 'Next page', + last: 'Last page' + }, editor: { url: 'URL', bold: 'Bold', diff --git a/ui/lang/en-US.js b/ui/lang/en-US.js index f234f76f1c1..9bd94984ba4 100644 --- a/ui/lang/en-US.js +++ b/ui/lang/en-US.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' of ' + total, columns: 'Columns' }, + pagination: { + first: 'First page', + prev: 'Previous page', + next: 'Next page', + last: 'Last page' + }, editor: { url: 'URL', bold: 'Bold', diff --git a/ui/lang/eo.js b/ui/lang/eo.js index d8b293da560..5b4aae5c916 100644 --- a/ui/lang/eo.js +++ b/ui/lang/eo.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' el ' + total, columns: 'Kolumnoj' }, + pagination: { + first: 'Unua paĝo', + prev: 'Antaŭa paĝo', + next: 'Sekva paĝo', + last: 'Lasta paĝo' + }, editor: { url: 'URL', bold: 'Grasa', diff --git a/ui/lang/es.js b/ui/lang/es.js index 40ad0cadc74..9740002b761 100644 --- a/ui/lang/es.js +++ b/ui/lang/es.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' de ' + total, columns: 'Columnas' }, + pagination: { + first: 'Primera página', + prev: 'Página anterior', + next: 'Próxima página', + last: 'Última página' + }, editor: { url: 'URL', bold: 'Negrita', diff --git a/ui/lang/et.js b/ui/lang/et.js index ab36f6c00ef..64b393620af 100644 --- a/ui/lang/et.js +++ b/ui/lang/et.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' / ' + total, columns: 'Veerud' }, + pagination: { + first: 'Esimene leht', + prev: 'Eelmine leht', + next: 'Järgmine leht', + last: 'Viimane leht' + }, editor: { url: 'URL', bold: 'Rasvane', diff --git a/ui/lang/eu.js b/ui/lang/eu.js index d8b55df3b4d..2a1099ab9e1 100644 --- a/ui/lang/eu.js +++ b/ui/lang/eu.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + 'tik -' + end + 'ra, guztira ' + total, columns: 'Zutabeak' }, + pagination: { + first: 'Lehen orria', + prev: 'Aurreko orria', + next: 'Hurrengo orria', + last: 'Azken orria' + }, editor: { url: 'URL', bold: 'Lodia', diff --git a/ui/lang/fa-IR.js b/ui/lang/fa-IR.js index 99858e6258f..579d0ff9820 100644 --- a/ui/lang/fa-IR.js +++ b/ui/lang/fa-IR.js @@ -42,6 +42,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' از ' + total, columns: 'ستون' }, + pagination: { + first: 'صفحه اول', + prev: 'صفحه قبلی', + next: 'صفحه بعدی', + last: 'صفحه آخر' + }, editor: { url: 'آدرس', bold: 'ضخیم', diff --git a/ui/lang/fa.js b/ui/lang/fa.js index e66874ad66d..a806186fa46 100644 --- a/ui/lang/fa.js +++ b/ui/lang/fa.js @@ -42,6 +42,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' از ' + total, columns: 'ستون' }, + pagination: { + first: 'صفحه اول', + prev: 'صفحه قبلی', + next: 'صفحه بعدی', + last: 'صفحه آخر' + }, editor: { url: 'آدرس', bold: 'کلفت', diff --git a/ui/lang/fi.js b/ui/lang/fi.js index dec6aa174ed..9e7f1008fdb 100644 --- a/ui/lang/fi.js +++ b/ui/lang/fi.js @@ -42,6 +42,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' / ' + total, columns: 'Sarakkeet' }, + pagination: { + first: 'Ensimmäinen sivu', + prev: 'Edellinen sivu', + next: 'Seuraava sivu', + last: 'Viimeinen sivu' + }, editor: { url: 'URL', bold: 'Lihavoitu', diff --git a/ui/lang/fr.js b/ui/lang/fr.js index 122decc7ff9..41d38e0c567 100644 --- a/ui/lang/fr.js +++ b/ui/lang/fr.js @@ -44,6 +44,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' sur ' + total, columns: 'Colonnes' }, + pagination: { + first: 'Première page', + prev: 'Page précédente', + next: 'Page suivante', + last: 'Dernière page' + }, editor: { url: 'URL', bold: 'Gras', diff --git a/ui/lang/gn.js b/ui/lang/gn.js index 4a713a5aafa..e14fc150588 100644 --- a/ui/lang/gn.js +++ b/ui/lang/gn.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' de ' + total, columns: 'Columnakuéra' }, + pagination: { + first: 'Aña kuatia', + prev: 'Kuatia ohasáva', + next: 'Kuatia ohasáva', + last: 'Kuatia última' + }, editor: { url: 'URL', bold: 'Negrita', diff --git a/ui/lang/he.js b/ui/lang/he.js index 6361089f02f..26429d083d5 100644 --- a/ui/lang/he.js +++ b/ui/lang/he.js @@ -42,6 +42,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' מתוך ' + total, columns: 'עמודות' }, + pagination: { + first: 'עמוד ראשון', + prev: 'עמוד קודם', + next: 'העמוד הבא', + last: 'העמוד האחרון' + }, editor: { url: 'כתובת אתר', bold: 'בולט', diff --git a/ui/lang/hi.js b/ui/lang/hi.js index 02edc3d0fef..7a2d2935e4d 100644 --- a/ui/lang/hi.js +++ b/ui/lang/hi.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' कुल ' + total, columns: 'कॉलम' }, + pagination: { + first: 'पहला पृष्ठ', + prev: 'पिछला पृष्ठ', + next: 'अगला पृष्ठ', + last: 'अंतिम पृष्ठ' + }, editor: { url: 'URL', bold: 'बोल्ड', diff --git a/ui/lang/hr.js b/ui/lang/hr.js index 56c2f8b3823..9710ab24275 100644 --- a/ui/lang/hr.js +++ b/ui/lang/hr.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' od ' + total, columns: 'Stupci' }, + pagination: { + first: 'Prva stranica', + prev: 'Prethodna stranica', + next: 'Sljedeća stranica', + last: 'Posljednja stranica' + }, editor: { url: 'URL', bold: 'Podebljano', diff --git a/ui/lang/hu.js b/ui/lang/hu.js index 5b1cea83713..8ae44202666 100644 --- a/ui/lang/hu.js +++ b/ui/lang/hu.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' / ' + total, columns: 'Oszlopok' }, + pagination: { + first: 'Első oldal', + prev: 'Előző oldal', + next: 'Következő oldal', + last: 'Utolsó oldal' + }, editor: { url: 'URL', bold: 'Félkövér', diff --git a/ui/lang/id.js b/ui/lang/id.js index 5f10cb0db54..3fd99b1c98f 100644 --- a/ui/lang/id.js +++ b/ui/lang/id.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' dari ' + total, columns: 'Kolom' }, + pagination: { + first: 'Halaman pertama', + prev: 'Halaman sebelumnya', + next: 'Halaman berikutnya', + last: 'Halaman terakhir' + }, editor: { url: 'URL', bold: 'Tebal', diff --git a/ui/lang/is.js b/ui/lang/is.js index 59f2dbbfb5d..899c65838e6 100644 --- a/ui/lang/is.js +++ b/ui/lang/is.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' af ' + total, columns: 'Dálkar' }, + pagination: { + first: 'Fyrsta blaðsíða', + prev: 'Fyrri blaðsíða', + next: 'Næsta blaðsíða', + last: 'Síðasta blaðsíða' + }, editor: { url: 'Slóð', bold: 'Feitletra', diff --git a/ui/lang/it.js b/ui/lang/it.js index 27c086d6919..ba3a6f9a0e8 100644 --- a/ui/lang/it.js +++ b/ui/lang/it.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' di ' + total, columns: 'Colonne' }, + pagination: { + first: 'Prima pagina', + prev: 'Pagina precedente', + next: 'Prossima pagina', + last: 'Ultima pagina' + }, editor: { url: 'URL', bold: 'Grassetto', diff --git a/ui/lang/ja.js b/ui/lang/ja.js index 61ff280e1e7..f2d09043a58 100644 --- a/ui/lang/ja.js +++ b/ui/lang/ja.js @@ -40,6 +40,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' / ' + total, columns: '列' // 'Columns' }, + pagination: { + first: '最初のページ', + prev: '前のページ', + next: '次のページ', + last: '最後のページ' + }, editor: { url: 'URL', // 'URL', bold: '太字', // 'Bold', diff --git a/ui/lang/kk.js b/ui/lang/kk.js index c562a0b1c7b..0d080633477 100644 --- a/ui/lang/kk.js +++ b/ui/lang/kk.js @@ -45,6 +45,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' из ' + total, columns: 'Бағандар' }, + pagination: { + first: 'Бірінші бет', + prev: 'Алдыңғы бет', + next: 'Келесі бет', + last: 'Соңғы бет' + }, editor: { url: 'URL', bold: 'Қалың', diff --git a/ui/lang/km.js b/ui/lang/km.js index 1d5748dab89..2ddc4769d5b 100644 --- a/ui/lang/km.js +++ b/ui/lang/km.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' នៃ ' + total, columns: 'ជួរឈរ' }, + pagination: { + first: 'ទំព័រដំបូង', + prev: 'ទំព័រមុន', + next: 'ទំព័របន្ទាប់', + last: 'ទំព័រចុងក្រោយ' + }, editor: { url: 'URL', bold: 'ដិត', diff --git a/ui/lang/ko-KR.js b/ui/lang/ko-KR.js index 01fd7bc89ba..2f702664492 100644 --- a/ui/lang/ko-KR.js +++ b/ui/lang/ko-KR.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => total + ' 중 ' + start + '-' + end, columns: '열' }, + pagination: { + first: '첫 페이지', + prev: '이전 페이지', + next: '다음 페이지', + last: '마지막 페이지' + }, editor: { url: 'URL', bold: '굵게', diff --git a/ui/lang/kur-CKB.js b/ui/lang/kur-CKB.js index c496e2b38a4..d76ec6aa6c8 100644 --- a/ui/lang/kur-CKB.js +++ b/ui/lang/kur-CKB.js @@ -51,6 +51,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' لە ' + total, columns: 'ڕیز' }, + pagination: { + first: 'پەڕەی یەکەمی', + prev: 'پەڕەی پێشوو', + next: 'پەڕەی داهاتوو', + last: 'پەڕەی کۆتایی' + }, editor: { url: 'لینک', bold: 'تۆخ', diff --git a/ui/lang/lt.js b/ui/lang/lt.js index ddc3b684947..0972da3ef33 100644 --- a/ui/lang/lt.js +++ b/ui/lang/lt.js @@ -53,6 +53,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' iš ' + total, columns: 'Stulpeliai' }, + pagination: { + first: 'Pirmasis puslapis', + prev: 'Ankstesnis puslapis', + next: 'Kitas puslapis', + last: 'Paskutinis puslapis' + }, editor: { url: 'URL', bold: 'Paryškintasis', diff --git a/ui/lang/lu.js b/ui/lang/lu.js index ae2d545c1d0..e8eb4ff0ddf 100644 --- a/ui/lang/lu.js +++ b/ui/lang/lu.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' vun ' + total, columns: 'Kolonnen' }, + pagination: { + first: 'Éischt Säit', + prev: 'Virdrun Säit', + next: 'Nächst Säit', + last: 'Lescht Säit' + }, editor: { url: 'URL', bold: 'Fett', diff --git a/ui/lang/lv.js b/ui/lang/lv.js index 26f3e692737..3fb382c6a1a 100644 --- a/ui/lang/lv.js +++ b/ui/lang/lv.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' no ' + total, columns: 'Kolonnas' }, + pagination: { + first: 'Pirmā lapa', + prev: 'Iepriekšējā lapa', + next: 'Nākamā lapa', + last: 'Pēdējā lapa' + }, editor: { url: 'URL', bold: 'Trekns', diff --git a/ui/lang/mk.js b/ui/lang/mk.js index e1cae335aac..31fcdd4f240 100644 --- a/ui/lang/mk.js +++ b/ui/lang/mk.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' од ' + total, columns: 'Колони' }, + pagination: { + first: 'Прва страница', + prev: 'Претходна страница', + next: 'Следната страница', + last: 'Последна страница' + }, editor: { url: 'URL', bold: 'Задебелено', diff --git a/ui/lang/ml.js b/ui/lang/ml.js index 64fcf71a345..c6d7cfd9bf9 100644 --- a/ui/lang/ml.js +++ b/ui/lang/ml.js @@ -45,6 +45,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' മൊത്തം ' + total + ' ൽ നിന്നും', columns: 'നിരകൾ' }, + pagination: { + first: 'ആദ്യ പേജ്', + prev: 'മുമ്പത്തെ പേജ്', + next: 'അടുത്ത പേജ്', + last: 'അവസാന പേജ്' + }, editor: { url: 'യുആർഎൽ', bold: 'ബോൾഡ്', diff --git a/ui/lang/mm.js b/ui/lang/mm.js index eb65432ae5c..da3a9a4d46a 100644 --- a/ui/lang/mm.js +++ b/ui/lang/mm.js @@ -42,6 +42,12 @@ export default { pagination: (start, end, total) => start + 'မှ' + end + 'ထိ' + 'အားလုံး' + total + 'ရှိ', columns: 'ကော်လံ' }, + pagination: { + first: 'ပထမစာမျက်နှာ', + prev: 'မူရင်းစာမျက်နှာ', + next: 'နောက်စာမျက်နှာ', + last: 'နောက်ဆုံးစာမျက်နှာ' + }, editor: { url: 'URL', bold: 'Bold', diff --git a/ui/lang/ms-MY.js b/ui/lang/ms-MY.js index 819640959e9..265b0ffc98c 100644 --- a/ui/lang/ms-MY.js +++ b/ui/lang/ms-MY.js @@ -44,6 +44,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' / ' + total, columns: 'Senaraikan' }, + pagination: { + first: 'Halaman pertama', + prev: 'Halaman sebelumnya', + next: 'Halaman seterusnya', + last: 'Halaman terakhir' + }, editor: { url: 'URL', bold: 'Tebal', diff --git a/ui/lang/ms.js b/ui/lang/ms.js index a04b5e0317c..ba456be4a8c 100644 --- a/ui/lang/ms.js +++ b/ui/lang/ms.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' dari ' + total, columns: 'Kolum' }, + pagination: { + first: 'Halaman pertama', + prev: 'Halaman sebelumnya', + next: 'Halaman seterusnya', + last: 'Halaman terakhir' + }, editor: { url: 'URL', bold: 'Tebal', diff --git a/ui/lang/my.js b/ui/lang/my.js index a8fd48f814c..3440f299ca4 100644 --- a/ui/lang/my.js +++ b/ui/lang/my.js @@ -40,6 +40,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' / ' + total, columns: 'Senaraikan' }, + pagination: { + first: 'Halaman pertama', + prev: 'Halaman sebelumnya', + next: 'Halaman seterusnya', + last: 'Halaman terakhir' + }, editor: { url: 'URL', bold: 'berani', diff --git a/ui/lang/nb-NO.js b/ui/lang/nb-NO.js index 22397bd758f..4d71778c730 100644 --- a/ui/lang/nb-NO.js +++ b/ui/lang/nb-NO.js @@ -42,6 +42,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' av ' + total, columns: 'Kolonner' }, + pagination: { + first: 'Første side', + prev: 'Forrige side', + next: 'Neste side', + last: 'Siste side' + }, editor: { url: 'URL', bold: 'Fet', diff --git a/ui/lang/nl.js b/ui/lang/nl.js index 924f5fd619d..1c809d1c085 100644 --- a/ui/lang/nl.js +++ b/ui/lang/nl.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' van ' + total, columns: 'Kolommen' }, + pagination: { + first: 'Eerste pagina', + prev: 'Vorige pagina', + next: 'Volgende pagina', + last: 'Laatste pagina' + }, editor: { url: 'URL', bold: 'Vet', diff --git a/ui/lang/pl.js b/ui/lang/pl.js index 25f39b0aa3a..ce10aa6006a 100644 --- a/ui/lang/pl.js +++ b/ui/lang/pl.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' z ' + total, columns: 'Kolumny' }, + pagination: { + first: 'Pierwsza strona', + prev: 'Poprzednia strona', + next: 'Następna strona', + last: 'Ostatnia strona' + }, editor: { url: 'URL', bold: 'Pogrubienie', diff --git a/ui/lang/pt-BR.js b/ui/lang/pt-BR.js index f9a9969ad06..8708a5005d6 100644 --- a/ui/lang/pt-BR.js +++ b/ui/lang/pt-BR.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' de ' + total, columns: 'Colunas' }, + pagination: { + first: 'Primeira página', + prev: 'Página anterior', + next: 'Próxima página', + last: 'Última página' + }, editor: { url: 'URL', bold: 'Negrito', diff --git a/ui/lang/pt.js b/ui/lang/pt.js index 25e4ce69171..e97275730a8 100644 --- a/ui/lang/pt.js +++ b/ui/lang/pt.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' de ' + total, columns: 'Colunas' }, + pagination: { + first: 'Primeira página', + prev: 'Página anterior', + next: 'Próxima página', + last: 'Última página' + }, editor: { url: 'URL', bold: 'Negrito', diff --git a/ui/lang/ro.js b/ui/lang/ro.js index 2c2303c8487..8dde5c2e7e2 100644 --- a/ui/lang/ro.js +++ b/ui/lang/ro.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' din ' + total, columns: 'Coloane' }, + pagination: { + first: 'Prima pagină', + prev: 'Pagina anterioară', + next: 'Pagina următoare', + last: 'Ultima pagină' + }, editor: { url: 'URL', bold: 'Îngroșat', diff --git a/ui/lang/ru.js b/ui/lang/ru.js index 7ed031d4102..4cb150406cb 100644 --- a/ui/lang/ru.js +++ b/ui/lang/ru.js @@ -45,6 +45,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' из ' + total, columns: 'Колонки' }, + pagination: { + first: 'Первая страница', + prev: 'Предыдущая страница', + next: 'Следующая страница', + last: 'Последняя страница' + }, editor: { url: 'URL', bold: 'Полужирный', diff --git a/ui/lang/sk.js b/ui/lang/sk.js index 490ff9485f8..12b511a142b 100644 --- a/ui/lang/sk.js +++ b/ui/lang/sk.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' z ' + total, columns: 'Stĺpce' }, + pagination: { + first: 'Prvá stránka', + prev: 'Predchádzajúca stránka', + next: 'Ďalšia stránka', + last: 'Posledná stránka' + }, editor: { url: 'URL', bold: 'Tučné', diff --git a/ui/lang/sl.js b/ui/lang/sl.js index f4d02a87d6b..565bf0100f5 100644 --- a/ui/lang/sl.js +++ b/ui/lang/sl.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' od ' + total, columns: 'Stolpci' }, + pagination: { + first: 'Prva stran', + prev: 'Prejšnja stran', + next: 'Naslednja stran', + last: 'Zadnja stran' + }, editor: { url: 'URL', bold: 'Krepko', diff --git a/ui/lang/sm.js b/ui/lang/sm.js index e35e9cb70cd..09b85990e31 100644 --- a/ui/lang/sm.js +++ b/ui/lang/sm.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' o ' + total, columns: 'Poutū' }, + pagination: { + first: 'Itulau muamua', + prev: 'Itulau muamua', + next: 'Isi Itulau', + last: 'Itulau mulimuli' + }, editor: { url: 'Tuātusi initaneti', bold: 'Fa\'aolaola', diff --git a/ui/lang/sr-CYR.js b/ui/lang/sr-CYR.js index fd70ca72614..408b6fc86f4 100644 --- a/ui/lang/sr-CYR.js +++ b/ui/lang/sr-CYR.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' од ' + total, columns: 'Колоне' }, + pagination: { + first: 'Прва страница', + prev: 'Претходна страница', + next: 'Следећа страница', + last: 'Последња страна' + }, editor: { url: 'УРЛ', bold: 'Подебљано', diff --git a/ui/lang/sr.js b/ui/lang/sr.js index 9b633027a7b..47aa9474f83 100644 --- a/ui/lang/sr.js +++ b/ui/lang/sr.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' od ' + total, columns: 'Kolone' }, + pagination: { + first: 'Prva stranica', + prev: 'Prethodna stranica', + next: 'Sledeća stranica', + last: 'Poslednja stranica' + }, editor: { url: 'URL', bold: 'Podebljano', diff --git a/ui/lang/sv.js b/ui/lang/sv.js index 58119f0f805..e18e226b86b 100644 --- a/ui/lang/sv.js +++ b/ui/lang/sv.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' av ' + total, columns: 'Kolumner' }, + pagination: { + first: 'Första sidan', + prev: 'Föregående sida', + next: 'Nästa sida', + last: 'Sista sidan' + }, editor: { url: 'URL', bold: 'Fet', diff --git a/ui/lang/ta.js b/ui/lang/ta.js index 6fe98a6760f..6e7326443ce 100644 --- a/ui/lang/ta.js +++ b/ui/lang/ta.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' மொத்தம் ' + total, columns: 'பத்திகள்' }, + pagination: { + first: 'முதல் பக்கம்', + prev: 'முந்தைய பக்கம்', + next: 'அடுத்த பக்கம்', + last: 'கடைசி பக்கம்' + }, editor: { url: 'URL', bold: 'தடித்த', diff --git a/ui/lang/th.js b/ui/lang/th.js index 54d0f54082c..43d373c9364 100644 --- a/ui/lang/th.js +++ b/ui/lang/th.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' of ' + total, columns: 'คอลัมน์' }, + pagination: { + first: 'หน้าแรก', + prev: 'หน้าก่อนหน้า', + next: 'หน้าถัดไป', + last: 'หน้าสุดท้าย' + }, editor: { url: 'URL', bold: 'ตัวหนา', diff --git a/ui/lang/tl.js b/ui/lang/tl.js index 790084ab324..6dfe42cfbc5 100644 --- a/ui/lang/tl.js +++ b/ui/lang/tl.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' ng ' + total, columns: 'Mga hanay' }, + pagination: { + first: 'Unang pahina', + prev: 'Nakaraang pahina', + next: 'Susunod na pahina', + last: 'Huling pahina' + }, editor: { url: 'URL', bold: 'Matapang', diff --git a/ui/lang/tr.js b/ui/lang/tr.js index 405e897dec9..25284eafa51 100644 --- a/ui/lang/tr.js +++ b/ui/lang/tr.js @@ -37,6 +37,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' toplam ' + total, columns: 'Sütunlar' }, + pagination: { + first: 'İlk sayfa', + prev: 'Önceki sayfa', + next: 'Sonraki Sayfa', + last: 'Son Sayfa' + }, editor: { url: 'URL', bold: 'Kalın', diff --git a/ui/lang/uk.js b/ui/lang/uk.js index 9daa7f8457f..fe52fb9e4b7 100644 --- a/ui/lang/uk.js +++ b/ui/lang/uk.js @@ -45,6 +45,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' з ' + total, columns: 'Колонки' }, + pagination: { + first: 'Перша сторінка', + prev: 'Попередня сторінка', + next: 'Наступна сторінка', + last: 'Остання сторінка' + }, editor: { url: 'URL', bold: 'Напівжирний', diff --git a/ui/lang/uz-Cyrl.js b/ui/lang/uz-Cyrl.js index 83b5cd90ff3..2cdce210bb9 100644 --- a/ui/lang/uz-Cyrl.js +++ b/ui/lang/uz-Cyrl.js @@ -43,6 +43,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' жами ' + total, columns: 'Устунлар' }, + pagination: { + first: 'Биринчи саҳифа', + prev: 'Олдинги саҳифа', + next: 'Кейинги саҳифа', + last: 'Сўнгги саҳифа' + }, editor: { url: 'УРЛ', bold: 'Қалин', diff --git a/ui/lang/uz-Latn.js b/ui/lang/uz-Latn.js index a981f3f6c94..2ee7320e833 100644 --- a/ui/lang/uz-Latn.js +++ b/ui/lang/uz-Latn.js @@ -47,6 +47,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' jami ' + total, columns: 'Ustunlar' }, + pagination: { + first: 'Birinchi sahifa', + prev: 'Oldingi sahifa', + next: 'Keyingi sahifa', + last: 'So\'nggi sahifa' + }, editor: { url: 'URL', bold: 'Qalin', diff --git a/ui/lang/vi.js b/ui/lang/vi.js index 112ddb02c01..75b8a3d6781 100644 --- a/ui/lang/vi.js +++ b/ui/lang/vi.js @@ -41,6 +41,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' của ' + total, columns: 'Cột' }, + pagination: { + first: 'Trang đầu tiên', + prev: 'Trang trước', + next: 'Trang tiếp theo', + last: 'Trang cuối cùng' + }, editor: { url: 'URL', bold: 'Đậm', diff --git a/ui/lang/zh-CN.js b/ui/lang/zh-CN.js index ee635cd1acb..b16a1d434df 100644 --- a/ui/lang/zh-CN.js +++ b/ui/lang/zh-CN.js @@ -40,6 +40,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' / ' + total, columns: '列' }, + pagination: { + first: '第一页', + prev: '上一页', + next: '下一页', + last: '最后一页' + }, editor: { url: 'URL', bold: '粗体', diff --git a/ui/lang/zh-TW.js b/ui/lang/zh-TW.js index aa3b9f4cf34..ad27fe9deb9 100644 --- a/ui/lang/zh-TW.js +++ b/ui/lang/zh-TW.js @@ -40,6 +40,12 @@ export default { pagination: (start, end, total) => start + '-' + end + ' 列,共 ' + total + ' 列', columns: '欄位' }, + pagination: { + first: '首頁', + prev: '上一頁', + next: '下一頁', + last: '最後一頁' + }, editor: { url: '網址', bold: '粗體', diff --git a/ui/src/components/pagination/QPagination.js b/ui/src/components/pagination/QPagination.js index 2fd8fc299cb..6a75c839fef 100644 --- a/ui/src/components/pagination/QPagination.js +++ b/ui/src/components/pagination/QPagination.js @@ -172,6 +172,17 @@ export default createComponent({ return $q.lang.rtl === true ? ico.reverse() : ico }) + const ariaLabels = computed(() => { + const labels = [ + $q.lang.pagination.first, + $q.lang.pagination.prev, + $q.lang.pagination.next, + $q.lang.pagination.last + ] + + return $q.lang.rtl === true ? labels.reverse() : labels + }) + const attrs = computed(() => ({ 'aria-disabled': props.disable === true ? 'true' : 'false', role: 'navigation' @@ -317,7 +328,8 @@ export default createComponent({ getBtn({ key: 'bls', disable: props.disable || props.modelValue <= minProp.value, - icon: icons.value[ 0 ] + icon: icons.value[ 0 ], + 'aria-label': ariaLabels.value[ 0 ] }, minProp.value) ) @@ -325,7 +337,8 @@ export default createComponent({ getBtn({ key: 'ble', disable: props.disable || props.modelValue >= maxProp.value, - icon: icons.value[ 3 ] + icon: icons.value[ 3 ], + 'aria-label': ariaLabels.value[ 3 ] }, maxProp.value) ) } @@ -335,7 +348,8 @@ export default createComponent({ getBtn({ key: 'bdp', disable: props.disable || props.modelValue <= minProp.value, - icon: icons.value[ 1 ] + icon: icons.value[ 1 ], + 'aria-label': ariaLabels.value[ 1 ] }, props.modelValue - 1) ) @@ -343,7 +357,8 @@ export default createComponent({ getBtn({ key: 'bdn', disable: props.disable || props.modelValue >= maxProp.value, - icon: icons.value[ 2 ] + icon: icons.value[ 2 ], + 'aria-label': ariaLabels.value[ 2 ] }, props.modelValue + 1) ) } diff --git a/ui/src/components/table/QTable.js b/ui/src/components/table/QTable.js index c7e3601ca4e..9db5f14832c 100644 --- a/ui/src/components/table/QTable.js +++ b/ui/src/components/table/QTable.js @@ -847,6 +847,7 @@ export default createComponent({ ...btnProps, icon: navIcon.value[ 0 ], disable: isFirstPage.value, + ariaLabel: $q.lang.pagination.first, onClick: firstPage }) ) @@ -857,6 +858,7 @@ export default createComponent({ ...btnProps, icon: navIcon.value[ 1 ], disable: isFirstPage.value, + ariaLabel: $q.lang.pagination.prev, onClick: prevPage }), @@ -865,6 +867,7 @@ export default createComponent({ ...btnProps, icon: navIcon.value[ 2 ], disable: isLastPage.value, + ariaLabel: $q.lang.pagination.next, onClick: nextPage }) ) @@ -875,6 +878,7 @@ export default createComponent({ ...btnProps, icon: navIcon.value[ 3 ], disable: isLastPage.value, + ariaLabel: $q.lang.pagination.last, onClick: lastPage }) ) From 77887cb6f04cbdc22bdfb2ee0cd5eda99318dcdc Mon Sep 17 00:00:00 2001 From: Alexey Goncharuk <60663550+Raidzin@users.noreply.github.com> Date: Thu, 27 Feb 2025 16:01:55 +0300 Subject: [PATCH 06/32] feat(QRating): Add color transition for QRating icons (#17798) When `color` and `color-selected` is different animations looks sharp --- ui/src/components/rating/QRating.sass | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/components/rating/QRating.sass b/ui/src/components/rating/QRating.sass index eb5878bd6b8..f96ecb3ce58 100644 --- a/ui/src/components/rating/QRating.sass +++ b/ui/src/components/rating/QRating.sass @@ -14,7 +14,7 @@ text-shadow: $rating-shadow position: relative opacity: .4 - transition: transform .2s ease-in, opacity .2s ease-in + transition: transform .2s ease-in, opacity .2s ease-in, color .2s ease-in &--hovered transform: scale(1.3) From baae2d03339488635b2e12b630598e489c103cc4 Mon Sep 17 00:00:00 2001 From: Razvan Stoenescu Date: Thu, 27 Feb 2025 15:10:30 +0200 Subject: [PATCH 07/32] fix(ui/test): update Lang tests (fix: #17148) (#17809) --- ui/src/plugins/lang/Lang.test.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ui/src/plugins/lang/Lang.test.js b/ui/src/plugins/lang/Lang.test.js index e7a41f0308e..f2467117015 100644 --- a/ui/src/plugins/lang/Lang.test.js +++ b/ui/src/plugins/lang/Lang.test.js @@ -61,6 +61,12 @@ describe('[Lang API]', () => { pagination: expect.any(Function), columns: expect.any(String) }, + pagination: { + first: expect.any(String), + last: expect.any(String), + next: expect.any(String), + prev: expect.any(String) + }, editor: { url: expect.any(String), bold: expect.any(String), @@ -173,6 +179,12 @@ describe('[Lang API]', () => { pagination: (start, end, total) => start + '-' + end + ' of ' + total, columns: 'Columns' }, + pagination: { + first: expect.any(String), + last: expect.any(String), + next: expect.any(String), + prev: expect.any(String) + }, editor: { url: 'URL', bold: 'Bold', From f7c6769fe4cb2f6f1fa51bae10eeaa59330d05ef Mon Sep 17 00:00:00 2001 From: Razvan Stoenescu Date: Thu, 27 Feb 2025 15:19:17 +0200 Subject: [PATCH 08/32] fix(QDialog): JSON > "escape-key" event > description mentions "no-esc-key" instead of "no-esc-dismiss" --- ui/src/components/dialog/QDialog.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/components/dialog/QDialog.json b/ui/src/components/dialog/QDialog.json index 096165de131..3c5354a4daa 100644 --- a/ui/src/components/dialog/QDialog.json +++ b/ui/src/components/dialog/QDialog.json @@ -134,7 +134,7 @@ }, "escape-key": { - "desc": "Emitted when ESC key is pressed; Does not get emitted if Dialog is 'persistent' or it has 'no-esc-key' set" + "desc": "Emitted when ESC key is pressed; Does not get emitted if Dialog is 'persistent' or it has 'no-esc-dismiss' set" }, "click": { "internal": true } From d51664cc0a63196ad443287d5cb3cae0bab77b2b Mon Sep 17 00:00:00 2001 From: maxfromit Date: Thu, 27 Feb 2025 16:21:16 +0300 Subject: [PATCH 09/32] feat(QMenu): Add noEscDismiss props to QMenu (#17784) * ui/QMenu: add noEscDismiss prop to control escape key behavior * ui/QMenu: add no-esc-dismiss prop description --------- Co-authored-by: maximfromit --- ui/src/components/menu/QMenu.js | 4 ++-- ui/src/components/menu/QMenu.json | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ui/src/components/menu/QMenu.js b/ui/src/components/menu/QMenu.js index 637f50458ea..4aacaf9377a 100644 --- a/ui/src/components/menu/QMenu.js +++ b/ui/src/components/menu/QMenu.js @@ -38,7 +38,7 @@ export default createComponent({ persistent: Boolean, autoClose: Boolean, separateClosePopup: Boolean, - + noEscDismiss: Boolean, noRouteDismiss: Boolean, noRefocus: Boolean, noFocus: Boolean, @@ -322,7 +322,7 @@ export default createComponent({ function onEscapeKey (evt) { emit('escapeKey') - hide(evt) + if (!props.noEscDismiss) hide(evt) } function updatePosition () { diff --git a/ui/src/components/menu/QMenu.json b/ui/src/components/menu/QMenu.json index f48efdeb9be..0a05d25f42b 100644 --- a/ui/src/components/menu/QMenu.json +++ b/ui/src/components/menu/QMenu.json @@ -67,6 +67,12 @@ "category": "behavior" }, + "no-esc-dismiss": { + "type": "Boolean", + "desc": "User cannot dismiss the popup by hitting ESC key; No need to set it if 'persistent' prop is also set", + "category": "behavior" + }, + "no-route-dismiss": { "type": "Boolean", "desc": "Changing route app won't dismiss the popup; No need to set it if 'persistent' prop is also set", From 9ccb944b1d833b6adf51f760ee095237bca5e42d Mon Sep 17 00:00:00 2001 From: Razvan Stoenescu Date: Thu, 27 Feb 2025 15:24:25 +0200 Subject: [PATCH 10/32] feat(QMenu): slight change to previous QMenu PR #17784 --- ui/src/components/menu/QMenu.js | 6 ++++-- ui/src/components/menu/QMenu.json | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/ui/src/components/menu/QMenu.js b/ui/src/components/menu/QMenu.js index 4aacaf9377a..d2f0fe34dfd 100644 --- a/ui/src/components/menu/QMenu.js +++ b/ui/src/components/menu/QMenu.js @@ -321,8 +321,10 @@ export default createComponent({ } function onEscapeKey (evt) { - emit('escapeKey') - if (!props.noEscDismiss) hide(evt) + if (props.noEscDismiss !== true) { + emit('escapeKey') + hide(evt) + } } function updatePosition () { diff --git a/ui/src/components/menu/QMenu.json b/ui/src/components/menu/QMenu.json index 0a05d25f42b..523b4ec26d4 100644 --- a/ui/src/components/menu/QMenu.json +++ b/ui/src/components/menu/QMenu.json @@ -70,7 +70,8 @@ "no-esc-dismiss": { "type": "Boolean", "desc": "User cannot dismiss the popup by hitting ESC key; No need to set it if 'persistent' prop is also set", - "category": "behavior" + "category": "behavior", + "addedIn": "v2.18.0" }, "no-route-dismiss": { @@ -134,7 +135,7 @@ "events": { "escape-key": { - "desc": "Emitted when ESC key is pressed; Does not get emitted if Menu is 'persistent'" + "desc": "Emitted when ESC key is pressed; Does not get emitted if Menu is 'persistent' or it has 'no-esc-dismiss' set" }, "click": { "internal": true } From 7d597bf47a4e9daf45d9c125adb952bf3dfe810d Mon Sep 17 00:00:00 2001 From: Razvan Stoenescu Date: Thu, 27 Feb 2025 15:31:24 +0200 Subject: [PATCH 11/32] chore(ui/testing): clear out obsolete files --- ui/src/components/date/__tests__/QDate.cy.js | 189 -- .../date/__tests__/use-datetime.cy.js | 83 - .../components/editor/__tests__/QEditor.cy.js | 195 -- .../components/field/__tests__/QField.cy.js | 156 -- .../components/input/__tests__/QInput.cy.js | 786 ------- .../components/input/__tests__/use-mask.cy.js | 124 - ui/src/components/menu/__tests__/QMenu.cy.js | 634 ------ .../components/menu/__tests__/WrapperOne.vue | 51 - .../components/menu/__tests__/WrapperTwo.vue | 38 - .../components/select/__tests__/QSelect.cy.js | 2018 ----------------- .../components/table/__tests__/QTable.cy.js | 635 ------ ui/src/components/table/__tests__/QTd.cy.js | 35 - ui/src/components/table/__tests__/QTh.cy.js | 27 - ui/src/components/table/__tests__/QTr.cy.js | 27 - ui/src/components/tabs/__tests__/QTab.cy.js | 79 - ui/src/components/tabs/__tests__/QTabs.cy.js | 147 -- .../uploader/__tests__/QUploader.cy.js | 191 -- ui/src/composables/__tests__/FieldWrapper.vue | 54 - ui/src/composables/__tests__/use-anchor.cy.js | 98 - ui/src/composables/__tests__/use-field.cy.js | 547 ----- ui/src/composables/__tests__/use-file.cy.js | 69 - .../__tests__/use-fullscreen.cy.js | 37 - .../__tests__/use-model-toggle.cy.js | 350 --- ui/src/composables/__tests__/use-portal.cy.js | 4 - .../__tests__/use-router-link.cy.js | 55 - .../composables/__tests__/use-validate.cy.js | 257 --- 26 files changed, 6886 deletions(-) delete mode 100644 ui/src/components/date/__tests__/QDate.cy.js delete mode 100644 ui/src/components/date/__tests__/use-datetime.cy.js delete mode 100644 ui/src/components/editor/__tests__/QEditor.cy.js delete mode 100644 ui/src/components/field/__tests__/QField.cy.js delete mode 100644 ui/src/components/input/__tests__/QInput.cy.js delete mode 100644 ui/src/components/input/__tests__/use-mask.cy.js delete mode 100644 ui/src/components/menu/__tests__/QMenu.cy.js delete mode 100644 ui/src/components/menu/__tests__/WrapperOne.vue delete mode 100644 ui/src/components/menu/__tests__/WrapperTwo.vue delete mode 100644 ui/src/components/select/__tests__/QSelect.cy.js delete mode 100644 ui/src/components/table/__tests__/QTable.cy.js delete mode 100644 ui/src/components/table/__tests__/QTd.cy.js delete mode 100644 ui/src/components/table/__tests__/QTh.cy.js delete mode 100644 ui/src/components/table/__tests__/QTr.cy.js delete mode 100644 ui/src/components/tabs/__tests__/QTab.cy.js delete mode 100644 ui/src/components/tabs/__tests__/QTabs.cy.js delete mode 100644 ui/src/components/uploader/__tests__/QUploader.cy.js delete mode 100644 ui/src/composables/__tests__/FieldWrapper.vue delete mode 100644 ui/src/composables/__tests__/use-anchor.cy.js delete mode 100644 ui/src/composables/__tests__/use-field.cy.js delete mode 100644 ui/src/composables/__tests__/use-file.cy.js delete mode 100644 ui/src/composables/__tests__/use-fullscreen.cy.js delete mode 100644 ui/src/composables/__tests__/use-model-toggle.cy.js delete mode 100644 ui/src/composables/__tests__/use-portal.cy.js delete mode 100644 ui/src/composables/__tests__/use-router-link.cy.js delete mode 100644 ui/src/composables/__tests__/use-validate.cy.js diff --git a/ui/src/components/date/__tests__/QDate.cy.js b/ui/src/components/date/__tests__/QDate.cy.js deleted file mode 100644 index cfaaa2441c4..00000000000 --- a/ui/src/components/date/__tests__/QDate.cy.js +++ /dev/null @@ -1,189 +0,0 @@ -describe('Date API', () => { - describe('Props', () => { - describe('Category: behavior', () => { - describe('(prop): years-in-month-view', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: content', () => { - describe('(prop): title', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): subtitle', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): today-btn', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): minimal', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: model', () => { - describe('(prop): model-value', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): default-year-month', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): default-view', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): events', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): options', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): first-day-of-week', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): emit-immediately', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: model|selection', () => { - describe('(prop): multiple', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): range', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: selection', () => { - describe('(prop): navigation-min-year-month', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): navigation-max-year-month', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): no-unset', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: style', () => { - describe('(prop): event-color', () => { - it.skip(' ', () => { - // - }) - }) - }) - }) - - describe('Slots', () => { - describe('(slot): default', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Events', () => { - describe('(event): update:model-value', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(event): navigation', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(event): range-start', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(event): range-end', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Methods', () => { - describe('(method): setToday', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): setView', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): offsetCalendar', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): setCalendarTo', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): setEditingRange', () => { - it.skip(' ', () => { - // - }) - }) - }) -}) diff --git a/ui/src/components/date/__tests__/use-datetime.cy.js b/ui/src/components/date/__tests__/use-datetime.cy.js deleted file mode 100644 index 2fd14c73603..00000000000 --- a/ui/src/components/date/__tests__/use-datetime.cy.js +++ /dev/null @@ -1,83 +0,0 @@ -describe('use-datetime API', () => { - describe('Props', () => { - describe('Category: behavior', () => { - describe('(prop): landscape', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: model', () => { - describe('(prop): mask', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): locale', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): calendar', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: state', () => { - describe('(prop): readonly', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): disable', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: style', () => { - describe('(prop): color', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): text-color', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): dark', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): square', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): flat', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): bordered', () => { - it.skip(' ', () => { - // - }) - }) - }) - }) -}) diff --git a/ui/src/components/editor/__tests__/QEditor.cy.js b/ui/src/components/editor/__tests__/QEditor.cy.js deleted file mode 100644 index 8000b979b79..00000000000 --- a/ui/src/components/editor/__tests__/QEditor.cy.js +++ /dev/null @@ -1,195 +0,0 @@ -describe('Editor API', () => { - describe('Props', () => { - describe('Category: behavior', () => { - describe('(prop): paragraph-tag', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: content', () => { - describe('(prop): placeholder', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: model', () => { - describe('(prop): model-value', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: state', () => { - describe('(prop): readonly', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): disable', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: style', () => { - describe('(prop): square', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): flat', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): dense', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): dark', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): min-height', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): max-height', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): height', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): content-style', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): content-class', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: toolbar', () => { - describe('(prop): definitions', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): fonts', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): toolbar', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): toolbar-color', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): toolbar-text-color', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): toolbar-toggle-color', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): toolbar-bg', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: toolbar|style', () => { - describe('(prop): toolbar-outline', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): toolbar-push', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): toolbar-rounded', () => { - it.skip(' ', () => { - // - }) - }) - }) - }) - - describe('Events', () => { - describe('(event): update:model-value', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Methods', () => { - describe('(method): runCmd', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): refreshToolbar', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): focus', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): getContentEl', () => { - it.skip(' ', () => { - // - }) - }) - }) -}) diff --git a/ui/src/components/field/__tests__/QField.cy.js b/ui/src/components/field/__tests__/QField.cy.js deleted file mode 100644 index 716827c6d18..00000000000 --- a/ui/src/components/field/__tests__/QField.cy.js +++ /dev/null @@ -1,156 +0,0 @@ -import QField from '../QField' -import { vModelAdapter } from '@quasar/quasar-app-extension-testing-e2e-cypress' -import { ref } from 'vue' -import Icons from '../../../../icon-set/material-icons.mjs' - -function getHostElement () { - return cy.get('.q-field') -} - -function mountQField (options) { - return cy.mount(QField, options) -} - -describe('Field API', () => { - describe('Props', () => { - describe('Category: model', () => { - describe('(prop): maxlength', () => { - it.skip(' ', () => { - // It is tricky to test this since it will require that we setup a control slot with v-model. - // This is already tested in QInput and others using use-field composable, so are not testing it. - }) - }) - }) - }) - - describe('Slots', () => { - describe('(slot): control', () => { - it('should use control slot as content of the field', () => { - const controlSlot = 'Hello there' - mountQField({ - slots: { - control: () => controlSlot - } - }) - - getHostElement().get('.q-field__control-container').should('contain', controlSlot) - }) - }) - }) - - describe('Events', () => { - describe('(event): update:model-value', () => { - it('should emit onUpdate:modelValue event', () => { - const model = ref('text') - const fn = cy.stub() - mountQField({ - props: { - ...vModelAdapter(model), - 'onUpdate:modelValue': fn, - clearable: true - } - }) - - getHostElement().get('button[type="button"]') - .contains(Icons.field.clear).click() - .then(() => { - expect(fn).to.be.calledWith() - }) - }) - }) - - describe('(event): focus', () => { - it('should emit the focus event', () => { - const fn = cy.stub() - - mountQField({ - props: { - onfocus: fn - }, - slots: { - control: 'text' - } - }) - - getHostElement().click() - - getHostElement().then(() => { - expect(fn).to.be.calledWith() - }) - }) - }) - - describe('(event): blur', () => { - it('should emit blur event', () => { - const fn = cy.stub() - mountQField({ - props: { - onblur: fn - }, - slots: { - control: 'text' - } - }) - - getHostElement() - .click() - - getHostElement().then(() => { - Cypress.vueWrapper.vm.blur() - }) - - getHostElement() - .then(() => { - expect(fn).to.be.calledWith() - }) - }) - }) - }) - - describe('Methods', () => { - describe('(method): focus', () => { - it('should focus the component', () => { - mountQField({ - slots: { - control: 'text' - } - }) - - getHostElement() - .get('.q-field--focused') - .should('not.exist') - - getHostElement() - .then(() => { - Cypress.vueWrapper.vm.focus() - }) - getHostElement() - .get('.q-field--focused') - .should('exist') - }) - }) - - describe('(method): blur', () => { - it('should blur the component', () => { - mountQField({ - slots: { - control: 'text' - } - }) - - getHostElement().click() - - getHostElement() - .get('.q-field--focused') - .should('exist') - - getHostElement() - .then(() => { - Cypress.vueWrapper.vm.blur() - }) - - cy.get('.q-field--focused').should('not.exist') - }) - }) - }) -}) diff --git a/ui/src/components/input/__tests__/QInput.cy.js b/ui/src/components/input/__tests__/QInput.cy.js deleted file mode 100644 index f5649549375..00000000000 --- a/ui/src/components/input/__tests__/QInput.cy.js +++ /dev/null @@ -1,786 +0,0 @@ -import QInput from '../QInput' -import { ref } from 'vue' -import { vModelAdapter } from '@quasar/quasar-app-extension-testing-e2e-cypress' - -function getHostElement () { - return cy.get('.q-field') -} - -function mountQInput (options) { - return cy.mount(QInput, options) -} - -describe('Input API', () => { - describe('Props', () => { - describe('Category: behaviour', () => { - describe('(prop): error', () => { - it('should mark the field as having an error', () => { - mountQInput() - getHostElement().should('not.have.class', 'q-field--error') - - mountQInput({ - props: { - error: true - } - }) - - getHostElement().should('have.class', 'q-field--error') - }) - }) - - describe('(prop): rules', () => { - it('should validate value using custom validation logic', () => { - const errorMessage = 'Use a max 3 of characters' - const model = ref('') - mountQInput({ - props: { - ...vModelAdapter(model), - rules: [ val => val.length <= 3 || errorMessage ] - } - }) - getHostElement().get('input').type('1234') - getHostElement().get('.q-field__messages').should('contain', errorMessage) - }) - - it('should validate email using inbuilt validation logic', () => { - const errorMessage = 'Enter a valid email address' - const model = ref('') - mountQInput({ - props: { - ...vModelAdapter(model), - rules: [ (val, rules) => rules.email(val) || errorMessage ] - } - }) - getHostElement().get('input').type('1234') - getHostElement().get('.q-field__messages').should('contain', errorMessage) - }) - }) - - describe('(prop): loading', () => { - it('should set the component into a loading state', () => { - mountQInput({ - props: { - loading: true - } - }) - - getHostElement().get('.q-spinner').should('exist') - }) - }) - - describe('(prop): clearable', () => { - it('should append a cancel icon', () => { - const model = ref('') - - mountQInput({ - props: { - ...vModelAdapter(model), - clearable: true - } - }) - - getHostElement().get('input').type('1') - getHostElement().get('button').should('exist').should('contain', 'cancel') - }) - }) - - describe('(prop): autofocus', () => { - it('should autofocus on component', () => { - mountQInput({ - props: { - autofocus: true - } - }) - - getHostElement() - .get('.q-field--focused') - .should('exist') - .get('input') - .should('have.focus') - }) - }) - - describe('(prop): lazy-rules', () => { - it('should validate the input only when component loses focus', () => { - const errorMessage = 'Use a max 3 of characters' - const model = ref('') - mountQInput({ - props: { - ...vModelAdapter(model), - rules: [ val => val.length <= 3 || errorMessage ], - lazyRules: true - } - }) - - getHostElement().get('input').type('1234') - getHostElement().get('.q-field__messages').should('not.contain', errorMessage) - - getHostElement().get('input').then(() => { - Cypress.vueWrapper.vm.blur() - getHostElement().get('.q-field__messages').should('contain', errorMessage) - }) - }) - - it('should validate the input only when component\'s validate() method is called', () => { - const errorMessage = 'Use a max 3 of characters' - const model = ref('') - mountQInput({ - props: { - ...vModelAdapter(model), - rules: [ val => val.length <= 3 || errorMessage ], - lazyRules: 'ondemand' - } - }) - - getHostElement().get('input').type('1234') - getHostElement().get('.q-field__messages').should('not.contain', errorMessage) - - getHostElement().get('input').then(() => { - Cypress.vueWrapper.vm.blur() - getHostElement().get('.q-field__messages').should('not.contain', errorMessage) - Cypress.vueWrapper.vm.validate() - getHostElement().get('.q-field__messages').should('contain', errorMessage) - }) - }) - }) - }) - - describe('Category: content', () => { - describe('(prop): error-message', () => { - it('should display validation error message if error = true', () => { - const errorMessage = 'Username must have at least 3 characters' - mountQInput({ - props: { - errorMessage - } - }) - getHostElement().should('not.contain', errorMessage) - - mountQInput({ - props: { - error: true, - errorMessage - } - }) - getHostElement().should('contain', errorMessage) - }) - }) - - describe('(prop): no-error-icon', () => { - it('should hide error icon when there is an error', () => { - mountQInput({ - props: { - error: true, - errorMessage: 'error message' - } - }) - - getHostElement().get('.q-field__append .q-icon').should('contain', 'error') - - mountQInput({ - props: { - error: true, - errorMessage: 'error message', - noErrorIcon: true - } - }) - - getHostElement().get('.q-field__append .q-icon').should('not.exist') - }) - }) - - describe('(prop): label', () => { - it('should display the label set', () => { - const label = 'Hello there!' - - mountQInput({ - props: { - label - } - }) - - getHostElement().get('.q-field__label').should('contain', label) - }) - }) - - describe('(prop): stack-label', () => { - it('should stack label', () => { - const label = 'Hello there!' - mountQInput({ - props: { - label - } - }) - getHostElement().should('not.have.class', 'q-field--float') - - mountQInput({ - props: { - label, - stackLabel: true - } - }) - - getHostElement().should('have.class', 'q-field--float') - }) - }) - - describe('(prop): hint', () => { - it('should display the hint message', () => { - const hint = 'hint message' - mountQInput({ - props: { - hint - } - }) - - getHostElement().get('.q-field__messages').should('contain', hint) - }) - }) - - describe('(prop): hide-hint', () => { - it('should hide hint when element is not focused', () => { - const hint = 'hint message' - mountQInput({ - props: { - hint, - hideHint: true - } - }) - getHostElement().get('.q-field__messages').should('not.contain', hint) - - getHostElement().get('input').then(() => { - Cypress.vueWrapper.vm.focus() - getHostElement().get('.q-field__messages').should('contain', hint) - }) - }) - }) - - describe('(prop): prefix', () => { - it('should display a prefix', () => { - const prefix = 'Hello there!' - mountQInput({ - props: { - prefix - } - }) - - getHostElement().get('.q-field__prefix').should('exist').should('contain', prefix) - }) - }) - - describe('(prop): suffix', () => { - it('should display a suffix', () => { - const suffix = 'Hello there!' - mountQInput({ - props: { - suffix - } - }) - - getHostElement().get('.q-field__suffix').should('exist').should('contain', suffix) - }) - }) - - describe('(prop): clear-icon', () => { - it('should display custom clear-icon when one is set', () => { - const model = ref('') - const clearIcon = 'custom-clear-icon' - mountQInput({ - props: { - ...vModelAdapter(model), - clearable: true, - clearIcon - } - }) - - getHostElement().get('input').type('123') - getHostElement().get('.q-field__append').get('button').should('contain', clearIcon) - }) - }) - - describe('(prop): label-slot', () => { - it('should force use the label slot when a label is not set', () => { - const labelSlot = 'Hello there' - mountQInput({ - props: { - labelSlot: true - }, - slots: { - label: () => labelSlot - } - }) - - getHostElement().get('.q-field__label').should('contain', labelSlot) - }) - }) - - describe('(prop): counter', () => { - it('should show an automatic counter on bottom right', () => { - const model = ref('') - mountQInput({ - props: { - ...vModelAdapter(model), - counter: true - } - }) - - const value = '1234' - getHostElement().get('input').type(value) - getHostElement().get('.q-field__counter').should('contain', value.length) - }) - }) - - describe('(prop): shadow-text', () => { - it('should render shadow-text', () => { - const shadowText = 'Shadow Text' - - mountQInput({ - props: { - shadowText - } - }) - - getHostElement().get('.q-field__shadow').should('exist').contains(shadowText) - }) - }) - - describe('(prop): autogrow', () => { - it('should use textarea for input field', () => { - mountQInput() - getHostElement().get('textarea').should('not.exist').get('input').should('exist') - - mountQInput({ - props: { - autogrow: true - } - }) - - getHostElement().get('textarea').should('exist') - getHostElement().get('input').should('not.exist') - }) - }) - }) - - describe('Category: general', () => { - describe('(prop): type', () => { - it('should render an input text type by default', () => { - mountQInput() - - getHostElement().get('input[type=text]').should('exist') - }) - - it('should render with the appropriate type set', () => { - const types = [ 'text', 'password', 'email', 'search', 'tel', 'file', 'number', 'url', 'time', 'date' ] - - types.forEach((type) => { - mountQInput({ - props: { - type - } - }) - - getHostElement().get(`input[type="${ type }"]`).should('exist') - }) - - mountQInput({ - props: { - type: 'textarea' - } - }) - - getHostElement().get('input').should('not.exist').get('textarea').should('exist') - }) - }) - }) - - describe('Category: model', () => { - describe('(prop): model-value', () => { - it('should render with the correct model value', () => { - const model = ref('Input Model Value') - mountQInput() - getHostElement().get('input').should('not.have.value', model.value) - - mountQInput({ - props: { - modelValue: model.value - } - }) - - getHostElement().get('input').should('have.value', model.value) - }) - }) - - describe('(prop): debounce', () => { - it('should no input debounce by default', () => { - const fn = cy.stub() - const text = 'Hello there' - mountQInput({ - props: { - 'onUpdate:modelValue': fn - } - }) - getHostElement() - .get('input') - .type(text) - .then(() => { - expect(fn).to.be.calledWith(text) - }) - }) - - it('should use a custom input-debounce', () => { - const fn = cy.stub() - const text = 'Hello there' - mountQInput({ - props: { - 'onUpdate:modelValue': fn, - debounce: 800 - } - }) - getHostElement() - .get('input') - .type(text) - .wait(500) - .then(() => { - expect(fn).not.to.be.calledWith(text) - }) - .wait(300) - .then(() => { - expect(fn).to.be.calledWith(text) - }) - }) - }) - - describe('(prop): maxlength', () => { - it('should respect the maxLength specified', () => { - mountQInput() - getHostElement() - .get('input') - .type('1234567890abcdefghij') - .should('have.value', '1234567890abcdefghij') - - mountQInput({ - props: { - maxlength: 10 - } - }) - - getHostElement() - .get('input') - .type('1234567890abcdefghij') - .should('have.value', '1234567890') - }) - }) - }) - - describe('Category: style', () => { - describe('(prop): input-class', () => { - it('should apply the input class to the underlying element', () => { - mountQInput() - getHostElement().get('input.input-class').should('not.exist') - - mountQInput({ - props: { - inputClass: 'input-class' - } - }) - - getHostElement().get('input.input-class').should('exist') - }) - - it('should apply the input class specified as an object', () => { - mountQInput({ - props: { - inputClass: { 'input-class': true } - } - }) - - getHostElement().get('input.input-class').should('exist') - }) - }) - - describe('(prop): input-style', () => { - it('should set input-style as string', () => { - mountQInput({ - props: { - inputStyle: 'background-color: red' - } - }) - - getHostElement().get('input[style="background-color: red;"]').should('exist') - }) - - it('should set input-style as an object', () => { - mountQInput({ - props: { - inputStyle: { backgroundColor: 'red' } - } - }) - - getHostElement().get('input[style="background-color: red;"]').should('exist') - }) - }) - - describe('(prop): label-color', () => { - it('should display a label color', () => { - const label = 'Hello there!' - mountQInput({ - props: { - label, - labelColor: 'red' - } - }) - - getHostElement().get('.q-field__label.text-red').should('contain', label) - }) - }) - - describe('(prop): color', () => { - it('should set a color on the component', () => { - mountQInput() - getHostElement().get('.q-field__control.text-red').should('not.exist') - mountQInput({ - props: { - color: 'red' - } - }) - - getHostElement().get('input').then(() => { - Cypress.vueWrapper.vm.focus() - getHostElement().get('.q-field__control.text-red').should('exist') - }) - }) - }) - - describe('(prop): bg-color', () => { - it('should display a background color', () => { - mountQInput({ - props: { - bgColor: 'red' - } - }) - - getHostElement().get('.q-field__control.bg-red').should('exist') - }) - }) - - describe('(prop): filled', () => { - it('should use a filled design', () => { - mountQInput({ - props: { - filled: true - } - }) - - getHostElement().get('.q-field--filled').should('exist') - }) - }) - - describe('(prop): outlined', () => { - it('should use a outlined design', () => { - mountQInput({ - props: { - outlined: true - } - }) - - getHostElement().get('.q-field--outlined').should('exist') - }) - }) - - describe('(prop): borderless', () => { - it('should use a borderless design', () => { - mountQInput({ - props: { - borderless: true - } - }) - - getHostElement().get('.q-field--borderless').should('exist') - }) - }) - - describe('(prop): standout', () => { - it('should display a standout color', () => { - mountQInput({ - props: { - standout: true - } - }) - - getHostElement().get('.q-field--standout').should('exist') - }) - }) - - describe('(prop): hide-bottom-space', () => { - it('should hide bottom space', () => { - mountQInput({ - props: { - rules: [ (value) => !!value || '' ] - } - }) - getHostElement().get('.q-field__messages').should('exist') - - mountQInput({ - props: { - rules: [ (value) => !!value || '' ], - hideBottomSpace: true - } - }) - - getHostElement().get('.q-field__messages').should('not.exist') - }) - }) - - describe('(prop): rounded', () => { - it('should use a rounded design', () => { - mountQInput({ - props: { - rounded: true - } - }) - - getHostElement().get('.q-field--rounded').should('exist') - }) - }) - - describe('(prop): square', () => { - it('should use a square design', () => { - mountQInput({ - props: { - square: true - } - }) - - getHostElement().get('.q-field--square').should('exist') - }) - }) - - describe('(prop): dense', () => { - it('should use a dense design', () => { - mountQInput({ - props: { - dense: true - } - }) - - getHostElement().get('.q-field--dense').should('exist') - }) - }) - }) - }) - - describe('Events', () => { - describe('(event): update:model-value', () => { - it('should emit onUpdate:modelValue event', () => { - const fn = cy.stub() - const text = 'Hello there' - mountQInput({ - props: { - 'onUpdate:modelValue': fn - } - }) - getHostElement() - .get('input') - .type(text) - .then(() => { - expect(fn).to.be.calledWith(text) - }) - }) - }) - - describe('(event): focus', () => { - it('should emit focus event', () => { - const fn = cy.stub() - mountQInput({ - props: { - onfocus: fn, - autoFocus: true - } - }) - - getHostElement() - .get('input') - .as('input') - .focus() - - cy.get('@input').then(() => { - expect(fn).to.be.calledWith() - }) - }) - }) - - describe('(event): blur', () => { - it('should emit blur event', () => { - const fn = cy.stub() - mountQInput({ - props: { - onblur: fn - } - }) - - getHostElement() - .get('input') - .focus() - .blur() - .then(() => { - expect(fn).to.be.calledWith() - }) - }) - }) - }) - - describe('Methods', () => { - describe('(method): focus', () => { - it('should focus the component', () => { - mountQInput() - - getHostElement() - .get('.q-field--focused') - .should('not.exist') - .get('input') - .should('not.have.focus') - getHostElement() - .then(() => { - Cypress.vueWrapper.vm.focus() - }) - getHostElement() - .get('.q-field--focused') - .should('exist') - .get('input') - .should('have.focus') - }) - }) - - describe('(method): blur', () => { - it('should blur the component', () => { - mountQInput() - getHostElement() - .get('input').focus() - getHostElement() - .get('.q-field--focused') - .should('exist') - .get('input') - .should('have.focus') - - getHostElement() - .then(() => { - Cypress.vueWrapper.vm.blur() - }) - - getHostElement().get('.q-field--focused').should('not.exist').get('input').should('not.have.focus') - }) - }) - - describe('(method): select', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): getNativeElement', () => { - it.skip(' ', () => { - // - }) - }) - }) -}) diff --git a/ui/src/components/input/__tests__/use-mask.cy.js b/ui/src/components/input/__tests__/use-mask.cy.js deleted file mode 100644 index 04dc5828b31..00000000000 --- a/ui/src/components/input/__tests__/use-mask.cy.js +++ /dev/null @@ -1,124 +0,0 @@ -import QInput from '../QInput' - -function getHostElement () { - return cy.get('.q-field') -} - -function mountQInput (options) { - return cy.mount(QInput, options) -} - -describe('use-mask API', () => { - describe('Props', () => { - describe('Category: behavior', () => { - describe('(prop): mask', () => { - const masks = [ - { - mask: 'card', - value: '4242424242424242', - valueWithMask: '4242 4242 4242 4242' - }, - { - mask: 'phone', - value: '2374234234', - valueWithMask: '(237) 423 - 4234' - }, - { - mask: '###@#*#', - value: '22222', - valueWithMask: '222@2*2' - }, - { - mask: 'fulltime', - value: '222222', - valueWithMask: '22:22:22' - }, - { - mask: 'time', - value: '222222', - valueWithMask: '22:22' - }, - { - mask: 'date', - value: '20230501', - valueWithMask: '2023/05/01' - }, - { - mask: 'datetime', - value: '2023050106:33', - valueWithMask: '2023/05/01 06:33' - } - ] - masks.forEach(({ value, valueWithMask, ...props }) => { - it(`should enforce a '${ props.mask }' mask`, () => { - mountQInput({ - props - }) - - getHostElement().get('input').type(value) - - getHostElement().get('input') - .should('not.have.value', value) - .should('have.value', valueWithMask) - }) - }) - }) - - describe('(prop): fill-mask', () => { - it('should use a default fill mask if one is not set', () => { - mountQInput({ - props: { - fillMask: true, - mask: '###-###' - } - }) - - getHostElement().get('input').type('222') - - getHostElement().get('input') - .should('not.have.value', '222') - .should('have.value', '222-___') - }) - - it('should use a default fill mask if one is not set', () => { - mountQInput({ - props: { - fillMask: '@', - mask: '###-###' - } - }) - - getHostElement().get('input').type('222') - - getHostElement().get('input') - .should('not.have.value', '222') - .should('have.value', '222-@@@') - }) - }) - - describe('(prop): reverse-fill-mask', () => { - it('should enforce a reverse-fill-mask', () => { - mountQInput({ - props: { - fillMask: true, - reverseFillMask: true, - mask: '###-###' - } - }) - - getHostElement().get('input').type('12345') - - getHostElement().get('input') - .should('not.have.value', '12345') - .should('have.value', '_12-345') - }) - }) - - describe('(prop): unmasked-value', () => { - it.skip(' ', () => { - // - }) - }) - }) - }) -}) diff --git a/ui/src/components/menu/__tests__/QMenu.cy.js b/ui/src/components/menu/__tests__/QMenu.cy.js deleted file mode 100644 index ef1ea21580f..00000000000 --- a/ui/src/components/menu/__tests__/QMenu.cy.js +++ /dev/null @@ -1,634 +0,0 @@ -/* eslint-disable no-unused-expressions */ -import WrapperOne from './WrapperOne.vue' -import WrapperTwo from './WrapperTwo.vue' - -describe('Menu API', () => { - describe('Props', () => { - describe('Category: behavior', () => { - describe('(prop): scroll-target', () => { - it.skip(' ', () => { - // Todo: Check if test for this needs to be added. - }) - }) - - describe('(prop): touch-position', () => { - it('should show menu at the position of the click', () => { - cy.mount(WrapperOne, { - props: { - 'touch-position': true - } - }) - - const mouseX = 75 - const mouseY = 25 - let elementX = 0 - let elementY = 0 - - cy.dataCy('wrapper') - .then(($el) => { - const rect = $el[ 0 ].getBoundingClientRect() - elementX = rect.left - elementY = rect.top - }) - .click(mouseX, mouseY) - cy.dataCy('menu') - .should('exist') - .then(($el) => { - expect($el[ 0 ].offsetLeft).to.equal(mouseX + elementX) - // TODO: check if the Y position being off by 1 is intentional - expect($el[ 0 ].offsetTop).to.equal(mouseY + 1 + elementY) - }) - }) - }) - - describe('(prop): persistent', () => { - it('should close the menu when clicking outside the menu', () => { - cy.mount(WrapperOne) - - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .should('exist') - cy.get('body') - .click(499, 0) - cy.get('body').dataCy('menu') - .should('not.exist') - }) - - it('should close the menu when hitting the escape key', () => { - cy.mount(WrapperOne) - - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .should('exist') - cy.get('body') - .type('{esc}') - cy.get('body').dataCy('menu') - .should('not.exist') - }) - - it('should not close the menu when clicking outside the menu when persistent', () => { - cy.mount(WrapperOne, { - props: { - persistent: true - } - }) - - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .should('exist') - cy.get('body') - .click(499, 0, { waitForAnimations: true }) // Await menu animation otherwise it always passes - cy.get('body') - .dataCy('menu') - .should('exist') - }) - - it('should not close the menu when hitting the escape key when persistent', () => { - cy.mount(WrapperOne, { - props: { - persistent: true - } - }) - - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .should('exist') - cy.get('body') - .type('{esc}', { waitForAnimations: true }) // Await menu animation otherwise it always passes - cy.get('body') - .dataCy('menu') - .should('exist') - }) - }) - - describe('(prop): no-route-dismiss', () => { - it.skip(' ', () => { - // This needs a vue-router, not possible with a unit test - }) - }) - - describe('(prop): auto-close', () => { - it('should not close the menu when clicking a menu child without v-close-popup', () => { - cy.mount(WrapperOne) - - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .should('exist') - .dataCy('keep-open') - .click({ waitForAnimations: true }) // Await menu animation otherwise it always passes - cy.dataCy('menu') - .should('exist') - }) - - it('should close the menu when clicking a menu child without v-close-popup when auto-close is true', () => { - cy.mount(WrapperOne, { - props: { - 'auto-close': true - } - }) - - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .should('exist') - .dataCy('keep-open') - .click({ waitForAnimations: true }) // Await menu animation otherwise it always passes - cy.dataCy('menu') - .should('not.exist') - }) - }) - - describe('(prop): separate-close-popup', () => { - it.skip(' ', () => { - // Todo: Check if this needs to be tested - }) - }) - - describe('(prop): no-refocus', () => { - // TODO: it is not clear from the docs that refocussing does not happen when clicking outside or closing by escape/programatticaly. Should this be added? - it('should switch focus back to parent element when closing', () => { - cy.mount(WrapperOne) - - cy.dataCy('wrapper') - .focus() - cy.dataCy('wrapper') - .should('have.focus') - .click() - cy.dataCy('menu') - .should('exist') - .should('have.focus') - cy.dataCy('menu') - .dataCy('close-popup') - .click({ waitForAnimations: true }) // Wait for menu transition - cy.dataCy('wrapper') - .get('.q-focus-helper') - .should('have.focus') - }) - - it('should not switch focus back to parent element when closing if no-refocus is true', () => { - cy.mount(WrapperOne, { - props: { - 'no-refocus': true - } - }) - - cy.dataCy('wrapper') - .focus() - cy.dataCy('wrapper') - .should('have.focus') - .click() - cy.dataCy('menu') - .should('exist') - .should('have.focus') - cy.dataCy('menu') - .dataCy('close-popup') - .click({ waitForAnimations: true }) // Wait for menu transition - cy.dataCy('wrapper') - .get('.q-focus-helper') - .should('not.have.focus') - }) - }) - - describe('(prop): no-focus', () => { - it('should switch focus to the menu when opening', () => { - cy.mount(WrapperOne) - - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .should('exist') - .should('have.focus') - }) - - it('should no switch focus to the menu when opening with no-focus is true', () => { - cy.mount(WrapperOne, { - props: { - 'no-focus': true - } - }) - - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .should('exist') - .should('not.have.focus') - }) - }) - }) - - describe('Category: position', () => { - describe('(prop): fit', () => { - it('should show a menu that matches the full with of the target when fit is supplied', () => { - cy.mount(WrapperOne, { - props: { - target: '.other-target', - fit: true - } - }) - let targetWidth = 0 - cy.dataCy('other-target') - .then(($el) => { - targetWidth = $el[ 0 ].clientWidth - }) - .click() - cy.dataCy('menu') - .then(($el) => { - expect($el[ 0 ].clientWidth).to.equal(targetWidth) - }) - }) - - it('should show a menu that not matches the full with of the target when fit is false', () => { - cy.mount(WrapperOne, { - props: { - target: '.other-target', - fit: false - } - }) - let targetWidth = 0 - cy.dataCy('other-target') - .then(($el) => { - targetWidth = $el[ 0 ].clientWidth - }) - .click() - cy.dataCy('menu') - .then(($el) => { - expect($el[ 0 ].clientWidth).not.to.equal(targetWidth) - }) - }) - }) - - describe('(prop): cover', () => { - it('should show a menu that overlays the target when using cover', () => { - cy.mount(WrapperOne, { - props: { - cover: true - } - }) - cy.dataCy('wrapper') - .click() - - cy.dataCy('menu') - .checkVerticalPosition('wrapper', 'center', 'center') - - cy.dataCy('menu') - .checkHorizontalPosition('wrapper', 'middle', 'middle') - }) - - it('should show a menu that overlays the target when using cover', () => { - cy.mount(WrapperOne, { - props: { - cover: true, - target: '.other-target' - } - }) - cy.dataCy('other-target') - .click() - - cy.dataCy('menu') - .checkVerticalPosition('other-target', 'center', 'center') - - cy.dataCy('menu') - .checkHorizontalPosition('other-target', 'middle', 'middle') - }) - - it('should ignore self property when using cover', () => { - cy.mount(WrapperOne, { - props: { - cover: true, - self: 'center right', - target: '.other-target' - } - }) - cy.dataCy('other-target') - .click() - - cy.dataCy('menu') - .checkVerticalPosition('other-target', 'center', 'center') - - cy.dataCy('menu') - .checkHorizontalPosition('other-target', 'middle', 'middle') - }) - }) - - describe('(prop): anchor & self', () => { - it('should show a menu at anchor: bottom left and self: top left by default', () => { - cy.mount(WrapperOne) - - cy.dataCy('wrapper') - .click() - - cy.dataCy('menu') - .checkVerticalPosition('wrapper', 'bottom', 'top') - - cy.dataCy('menu') - .checkHorizontalPosition('wrapper', 'left', 'left') - }) - - const verticalAnchor = [ 'top', 'center', 'bottom' ] - const horizontalAnchor = [ 'left', 'middle', 'right' ] - const verticalSelf = [ 'top', 'center', 'bottom' ] - const horizontalSelf = [ 'left', 'middle', 'right' ] - verticalAnchor.forEach((vA) => { - horizontalAnchor.forEach((hA) => { - verticalSelf.forEach((vS) => { - horizontalSelf.forEach((hS) => { - it(`should position Anchor(${ vA } ${ hA }) & Self(${ vS } ${ hS }) correctly`, () => { - cy.mount(WrapperOne, { - props: { - anchor: `${ vA } ${ hA }`, - self: `${ vS } ${ hS }` - } - }) - - cy.dataCy('wrapper') - .click() - - cy.dataCy('menu') - .checkVerticalPosition('wrapper', vA, vS) - - cy.dataCy('menu') - .checkHorizontalPosition('wrapper', hA, hS) - }) - }) - }) - }) - }) - }) - - describe.skip('(prop): self', () => { - // This property is tested together with anchor above - }) - - describe('(prop): offset', () => { - const verticalAnchor = [ 'top', 'center', 'bottom' ] - const verticalSelf = [ 'top', 'center', 'bottom' ] - verticalAnchor.forEach((vA) => { - verticalSelf.forEach((vS) => { - it(`should offset vertical position Anchor(${ vA } left) & Self(${ vS } left) correctly`, () => { - cy.mount(WrapperOne, { - props: { - anchor: `${ vA } left`, - self: `${ vS } left`, - offset: [ 0, 20 ] - } - }) - - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .checkVerticalPosition('wrapper', vA, vS, 20) - }) - }) - }) - - const horizontalAnchor = [ 'left', 'middle', 'right' ] - const horizontalSelf = [ 'left', 'middle', 'right' ] - horizontalAnchor.forEach((hA) => { - horizontalSelf.forEach((hS) => { - it(`should offset horizontal position Anchor(top ${ hA }) & Self(top ${ hS }) correctly`, () => { - cy.mount(WrapperOne, { - props: { - anchor: `top ${ hA }`, - self: `top ${ hS }`, - offset: [ 20, 0 ] - } - }) - - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .checkHorizontalPosition('wrapper', hA, hS, 20) - }) - }) - }) - }) - }) - - describe('Category: style', () => { - describe('(prop): dark', () => { - it('should set the --q-dark color as background and white text color', () => { - cy.mount(WrapperOne, { - props: { - dark: true - } - }) - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .should('have.color', 'white') - .should('have.backgroundColor', 'var(--q-dark)') - }) - }) - - describe('(prop): square', () => { - it('should not have border-radius when using this prop', () => { - cy.mount(WrapperOne, { - props: { - square: true - } - }) - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .should('have.css', 'border-radius', '0px') - }) - }) - - describe('(prop): max-height', () => { - it('should specify a max-height when setting this prop', () => { - const maxHeight = '30px' - cy.mount(WrapperOne, { - props: { - maxHeight - } - }) - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .should('have.css', 'max-height', maxHeight) - }) - }) - - describe('(prop): max-width', () => { - it('should specify a max-width when setting this prop', () => { - const maxWidth = '30px' - cy.mount(WrapperOne, { - props: { - maxWidth - } - }) - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .should('have.css', 'max-width', maxWidth) - }) - }) - }) - }) - - describe('Slots', () => { - describe('(slot): default', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Events', () => { - describe('(event): escape-key', () => { - it('should emit @escape-key event when escape key is pressed', () => { - const fn = cy.stub() - cy.mount(WrapperOne, { - props: { - onEscapeKey: fn - } - }) - - expect(fn).not.to.be.called - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .should('exist') - .then(() => { - expect(fn).not.to.be.called - }) - cy.get('body') - .type('{esc}') - cy.get('body') - .then(() => { - expect(fn).to.be.called - }) - }) - - it('should not emit @escape-key event when menu is persistent', () => { - const fn = cy.stub() - cy.mount(WrapperOne, { - props: { - onEscapeKey: fn, - persistent: true - } - }) - - expect(fn).not.to.be.called - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .should('exist') - .then(() => { - expect(fn).not.to.be.called - }) - cy.get('body') - .type('{esc}') - cy.get('body') - .then(() => { - expect(fn).not.to.be.called - }) - }) - }) - }) - - describe('Methods', () => { - describe('(method): updatePosition', () => { - it('should reposition the menu when it is no longer in correct position', () => { - cy.mount(WrapperTwo, { - props: { - anchor: 'bottom left', - self: 'bottom left' - } - }) - - let bottom = null - let left = null - - cy.dataCy('wrapper') - .click({ waitForAnimations: true }) - cy.dataCy('menu') - .should('exist') - .checkVerticalPosition('wrapper', 'bottom', 'bottom') - - cy.dataCy('menu') - .checkHorizontalPosition('wrapper', 'left', 'left') - - cy.dataCy('menu') - .then(($el) => { - const rect = $el[ 0 ].getBoundingClientRect() - bottom = rect.bottom - left = rect.left - }) - - cy.dataCy('div') - .then(($el) => { - $el[ 0 ].style.height = '100px' - cy.dataCy('menu') - .then(($el) => { - const rect = $el[ 0 ].getBoundingClientRect() - expect(rect.bottom).to.equal(bottom - 100) - expect(rect.left).to.equal(left) - }) - - cy.dataCy('wrapper') - .then(() => { - Cypress.vueWrapper.vm.updatePosition() - }) - - cy.dataCy('menu') - .checkVerticalPosition('wrapper', 'bottom', 'bottom') - - cy.dataCy('menu') - .checkHorizontalPosition('wrapper', 'left', 'left') - }) - }) - }) - - describe('(method): focus', () => { - it('should focus the menu', () => { - cy.mount(WrapperOne, { - props: { - 'no-focus': true - } - }) - - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .should('exist') - .should('not.have.focus') - cy.dataCy('wrapper') - .then(() => { - // Need to call method from wrapper - // Click a button closes the menu - Cypress.vueWrapper.vm.focusMethod() - }) - cy.dataCy('menu') - .should('have.focus') - }) - - it('should focus the autofocus element inside the menu', () => { - cy.mount(WrapperTwo, { - props: { - 'no-focus': true - } - }) - - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .should('exist') - .should('not.have.focus') - cy.dataCy('wrapper') - .then(() => { - // Need to call method from wrapper - // Click a button closes the menu - Cypress.vueWrapper.vm.focusMethod() - }) - cy.dataCy('input-2') - .should('have.focus') - }) - }) - }) -}) diff --git a/ui/src/components/menu/__tests__/WrapperOne.vue b/ui/src/components/menu/__tests__/WrapperOne.vue deleted file mode 100644 index a543804ccf0..00000000000 --- a/ui/src/components/menu/__tests__/WrapperOne.vue +++ /dev/null @@ -1,51 +0,0 @@ - - - diff --git a/ui/src/components/menu/__tests__/WrapperTwo.vue b/ui/src/components/menu/__tests__/WrapperTwo.vue deleted file mode 100644 index 599c7b9655f..00000000000 --- a/ui/src/components/menu/__tests__/WrapperTwo.vue +++ /dev/null @@ -1,38 +0,0 @@ - - - diff --git a/ui/src/components/select/__tests__/QSelect.cy.js b/ui/src/components/select/__tests__/QSelect.cy.js deleted file mode 100644 index eb40d27cffc..00000000000 --- a/ui/src/components/select/__tests__/QSelect.cy.js +++ /dev/null @@ -1,2018 +0,0 @@ -/* eslint-disable no-unused-expressions */ -import { vModelAdapter } from '@quasar/quasar-app-extension-testing-e2e-cypress' -import { h, ref } from 'vue' -import QSelect from '../QSelect.js' - -function getHostElement (extendedSelector = '') { - // A majority of tests click on the select, so we ensure the select is visible before the click happens. - // See https://github.com/cypress-io/cypress/discussions/14889 - // See https://www.cypress.io/blog/2020/08/17/when-can-the-test-navigate#visible-elements - return extendedSelector ? cy.get(`.q-select ${ extendedSelector }`) : cy.get('.q-select').should('be.visible') -} - -function mountQSelect (options = {}) { - if (!options.props?.modelValue) { - options.props = { - modelValue: null, - ...options.props ?? {} - } - } - - return cy.mount(QSelect, options) -} - -// QSelect does not set the `data-cy` attribute on the root element, but on the `.q-field__native` element -// This means we cannot use data-cy everywhere, but instead use a custom class `select-root` for this purpose -describe('QSelect API', () => { - describe('Props', () => { - describe('Category: behavior', () => { - describe('(prop): fill-input', () => { - it.skip(' ', () => { - // - }) - }) - describe('(prop): new-value-mode', () => { - it.skip(' ', () => { - // - }) - }) - describe('(prop): autocomplete', () => { - it.skip(' ', () => { - // - }) - }) - describe('(prop): transition-show', () => { - it.skip(' ', () => { - // - }) - }) - describe('(prop): transition-hide', () => { - it.skip(' ', () => { - // - }) - }) - describe('(prop): transition-duration', () => { - it.skip(' ', () => { - // - }) - }) - describe('(prop): behavior', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: content', () => { - describe('(prop): dropdown-icon', () => { - it('should use the dropdown-icon supplied', () => { - const icon = 'map' - mountQSelect({ - props: { - dropdownIcon: icon - } - }) - getHostElement() - .get(`div:contains(${ icon })`) - .should('exist') - }) - }) - - describe('(prop): use-input', () => { - it('should render an input inside the select', () => { - mountQSelect({ - props: { - useInput: true - } - }) - getHostElement() - .get('input') - .should('exist') - }) - - it('should render an input, but it shouldn\'t be visible', () => { - mountQSelect() - - getHostElement() - .get('input') - .should('not.be.visible') - }) - - it.skip('should not render an input by default', () => { - // Native input is now always rendered, due to having a target for autocomplete - // Refer to commit: https://github.com/quasarframework/quasar/commit/21a3af0dfe01bac0da617737562b599edee397a2 - }) - }) - - describe('(prop): input-debounce', () => { - it('should use an input-debounce of 500ms by default', () => { - const fn = cy.stub() - const text = 'Hello there' - mountQSelect({ - props: { - useInput: true, - onFilter: fn - } - }) - getHostElement() - .get('input') - .type(text) - .then(() => { - expect(fn).not.to.be.calledWith(text) - }) - .wait(500) - .then(() => { - expect(fn).to.be.calledWith(text) - }) - }) - - it('should use a custom input-debounce', () => { - const fn = cy.stub() - const text = 'Hello there' - mountQSelect({ - props: { - useInput: true, - onFilter: fn, - inputDebounce: 800 - } - }) - getHostElement() - .get('input') - .type(text) - .wait(500) - .then(() => { - expect(fn).not.to.be.calledWith(text) - }) - .wait(300) - .then(() => { - expect(fn).to.be.calledWith(text) - }) - }) - }) - }) - - describe('Category: content|behavior', () => { - describe('(prop): hide-dropdown-icon', () => { - it('should show the dropdown-icon when this property is not supplied', () => { - mountQSelect() - getHostElement() - .get('.q-icon') - .should('exist') - }) - - it('should hide the dropdown-icon when this property is true', () => { - mountQSelect({ - props: { - hideDropdownIcon: true - } - }) - getHostElement() - .get('.q-icon') - .should('not.exist') - }) - }) - }) - - describe('Category: general', () => { - describe('(prop): tabindex', () => { - it('should have a default tabindex of 0', () => { - mountQSelect() - getHostElement('[tabindex="0"]') - .should('exist') - }) - - it('should set the tabindex to the supplied value', () => { - const tabindex = 2 - mountQSelect({ - props: { - tabindex - } - }) - getHostElement(`[tabindex="${ tabindex }"]`) - .should('exist') - getHostElement('[tabindex="0"]') - .should('not.exist') - }) - }) - }) - - describe('Category: model', () => { - describe('(prop): model-value', () => { - it('should have the option selected passed in the model-value', () => { - const modelValue = 'Option 1' - mountQSelect({ - props: { - modelValue, - options: [ 'Option 1', 'Option 2', 'Option 3' ] - } - }) - getHostElement() - .should('include.text', modelValue) - }) - }) - - describe('(prop): emit-value', () => { - it('should emit the value under the value key, if options are objects', () => { - const fn = cy.stub() - mountQSelect({ - props: { - emitValue: true, - 'onUpdate:modelValue': fn, - options: [ { label: 'Option 1', value: 1 }, { label: 'Option 2', value: 2 } ] - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .contains('Option 1') - .should('be.visible') - .click() - cy.get('.q-menu') - .then(() => { - expect(fn).to.have.been.calledWith(1) - }) - }) - - it('should emit the whole object by default if options are objects', () => { - const fn = cy.stub() - const options = [ { label: 'Option 1', value: 1 }, { label: 'Option 2', value: 2 } ] - mountQSelect({ - props: { - 'onUpdate:modelValue': fn, - options - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .contains('Option 1') - .should('be.visible') - .click() - cy.get('.q-menu') - .then(() => { - expect(fn).to.have.been.calledWith(options[ 0 ]) - }) - }) - }) - }) - - describe('Category: model|selection', () => { - describe('(prop): multiple', () => { - it('should select only one option by default', () => { - const options = [ 'Option 1', 'Option 2' ] - const model = ref(null) - mountQSelect({ - props: { - ...vModelAdapter(model), - options - } - }) - - getHostElement().click() - cy.withinSelectMenu(() => { - cy.contains('Option 1') - .should('be.visible') - .click() - cy.contains('Option 1') - .then(() => { - expect(model.value).to.equal(options[ 0 ]) - }) - }) - - getHostElement().click() - cy.withinSelectMenu(() => { - cy.contains('Option 2') - .should('be.visible') - .click() - cy.contains('Option 2') - .then(() => { - expect(model.value).to.equal(options[ 1 ]) - }) - }) - }) - - it('should select multiple options if multiple is true', () => { - const options = [ 'Option 1', 'Option 2' ] - const model = ref([]) - mountQSelect({ - props: { - ...vModelAdapter(model), - multiple: true, - options - } - }) - - getHostElement().click() - cy.withinSelectMenu({ - persistent: true, - fn: () => { - cy.contains('Option 1') - .should('be.visible') - .click() - cy.contains('Option 1') - .then(() => { - expect(model.value).to.eql([ options[ 0 ] ]) - }) - - cy.contains('Option 2') - .should('be.visible') - .click() - cy.contains('Option 2') - .then(() => { - expect(model.value).to.eql(options) - }) - } - }) - }) - }) - }) - - describe('Category: options', () => { - describe('(prop): options', () => { - it('should show each option when opening the dropdown', () => { - const options = [ 'Option 1', 'Option 2', 'Option 3', 'Option 4' ] - mountQSelect({ - props: { - options - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .children() - .should('contain', options[ 0 ]) - .and('contain', options[ 1 ]) - .and('contain', options[ 2 ]) - .and('contain', options[ 3 ]) - }) - }) - - describe('(prop): option-value', () => { - it('should use the value key as option-value by default', () => { - const options = [ { label: 'Option one', value: 1 }, { label: 'Option two', value: 2 } ] - const model = ref(null) - mountQSelect({ - props: { - ...vModelAdapter(model), - options, - emitValue: true - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .contains(options[ 0 ].label) - .should('be.visible') - .click() - cy.get('.q-menu') - .then(() => { - expect(model.value).to.equal(options[ 0 ].value) - }) - }) - - it('should use a custom key supplied by option-value', () => { - const options = [ { label: 'Option one', test: 1 }, { label: 'Option two', test: 2 } ] - const model = ref(null) - mountQSelect({ - props: { - ...vModelAdapter(model), - options, - emitValue: true, - optionValue: 'test' - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .contains(options[ 0 ].label) - .should('be.visible') - .click() - cy.get('.q-menu') - .then(() => { - expect(model.value).to.equal(options[ 0 ].test) - }) - }) - - it('should accept a function as option-value', () => { - const options = [ { label: 'Option one', test: 1 }, { label: 'Option two', test: 2 } ] - const model = ref(null) - mountQSelect({ - props: { - ...vModelAdapter(model), - options, - emitValue: true, - optionValue: (val) => val.test - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .contains(options[ 0 ].label) - .should('be.visible') - .click() - cy.get('.q-menu') - .then(() => { - expect(model.value).to.equal(options[ 0 ].test) - }) - }) - }) - - describe('(prop): option-label', () => { - it('should use the "label" key by default as option-label', () => { - const options = [ { label: 'Option one', value: 1 }, { label: 'Option two', value: 2 } ] - mountQSelect({ - props: { - options - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .children() - .should('contain', options[ 0 ].label) - .and('contain', options[ 1 ].label) - }) - - it('should use the key supplied by option-label', () => { - const options = [ { test: 'Option one', value: 1 }, { test: 'Option two', value: 2 } ] - mountQSelect({ - props: { - options, - optionLabel: 'test' - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .children() - .should('contain', options[ 0 ].test) - .and('contain', options[ 1 ].test) - }) - - it('should accept a function as option-label', () => { - const options = [ { test: 'Option one', value: 1 }, { test: 'Option two', value: 2 } ] - mountQSelect({ - props: { - options, - optionLabel: (item) => (item === null ? 'Null' : item.test) - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .children() - .should('contain', options[ 0 ].test) - .and('contain', options[ 1 ].test) - }) - }) - describe('(prop): option-disable', () => { - it('should use the "disable" key by default as option-disable', () => { - const options = [ { label: 'Option one', value: 1, disable: true }, { label: 'Option two', value: 2, disable: true } ] - mountQSelect({ - props: { - options - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .get('[role="option"][aria-disabled="true"]') - .should('have.length', 2) - }) - - it('should use the key supplied by option-disable', () => { - const options = [ { label: 'Option one', value: 1, test: true }, { label: 'Option two', value: 2, disable: true } ] - mountQSelect({ - props: { - options, - optionDisable: 'test' - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .get('[role="option"][aria-disabled="true"]') - .should('have.length', 1) - .should('have.text', options[ 0 ].label) - }) - - it('should accept a function as option-disable', () => { - const options = [ { label: 'Option one', value: 1, test: true }, { label: 'Option two', value: 2, disable: true } ] - mountQSelect({ - props: { - options, - optionDisable: (item) => (item === null ? true : item.test) - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .get('[role="option"][aria-disabled="true"]') - .should('have.length', 1) - .should('have.text', options[ 0 ].label) - }) - }) - - describe('(prop): options-dense', () => { - it('should show options list dense', () => { - const options = [ 'Option 1', 'Option 2', 'Option 3', 'Option 4' ] - mountQSelect({ - props: { - options, - optionsDense: true - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .get('.q-item') - .should('have.class', 'q-item--dense') - }) - }) - - describe('(prop): options-dark', () => { - it('should show options list in dark mode', () => { - const options = [ 'Option 1', 'Option 2', 'Option 3', 'Option 4' ] - mountQSelect({ - props: { - options, - optionsDark: true - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .get('.q-item') - .should('have.class', 'q-item--dark') - }) - }) - - describe('(prop): options-selected-class', () => { - it('should have text-{color} applied as selected by default', () => { - const options = [ 'Option 1', 'Option 2', 'Option 3', 'Option 4' ] - mountQSelect({ - props: { - options, - modelValue: 'Option 1', - color: 'orange' - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .contains('[role="option"]', options[ 0 ]) - .should('have.class', 'text-orange') - }) - - it('should not have default active class when passed option is empty', () => { - const options = [ 'Option 1', 'Option 2', 'Option 3', 'Option 4' ] - mountQSelect({ - props: { - options, - modelValue: 'Option 1', - optionsSelectedClass: '', - color: 'orange' - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .contains('[role="option"]', options[ 0 ]) - .should('not.have.class', 'text-orange') - }) - - it('should have class name supplied by options-selected-class on active item', () => { - const options = [ 'Option 1', 'Option 2', 'Option 3', 'Option 4' ] - mountQSelect({ - props: { - options, - modelValue: 'Option 1', - optionsSelectedClass: 'test-class', - color: 'orange' - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .contains('[role="option"]', options[ 0 ]) - .should('not.have.class', 'text-orange') - .should('have.class', 'test-class') - cy.get('.q-menu') - .contains('[role="option"]', options[ 1 ]) - .should('not.have.class', 'text-orange') - .should('not.have.class', 'test-class') - }) - }) - - describe('(prop): options-html', () => { - it('should not render options with html by default', () => { - const options = [ 'Option 1', 'Option 2' ] - mountQSelect({ - props: { - options - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .contains('Option 1') - .should('have.color', 'black') - .should('not.have.css', 'font-weight', '700') - }) - - it('should render options with html when options-html is true', () => { - const options = [ 'Option 1', 'Option 2' ] - mountQSelect({ - props: { - options, - optionsHtml: true - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .contains('Option 1') - .should('have.color', 'red') - .should('have.css', 'font-weight', '700') - }) - }) - - describe('(prop): options-cover', () => { - it('should make the popup menu cover the select', (done) => { - const options = [ 'Option 1', 'Option 2', 'Option 3', 'Option 4' ] - mountQSelect({ - props: { - options, - optionsCover: true - } - }) - getHostElement() - .click() - cy.get('.q-menu').should('be.visible') - getHostElement() - .isNotActionable(done) - }) - - it('should not make the popup menu cover the select when use-input is used', () => { - const options = [ 'Option 1', 'Option 2', 'Option 3', 'Option 4' ] - mountQSelect({ - props: { - options, - optionsCover: true, - useInput: true - } - }) - getHostElement() - .click() - .click({ timeout: 100 }) - }) - }) - - describe('(prop): menu-shrink', () => { - it('should shrink the menu', () => { - const options = [ '1', '2', '3', '4' ] - mountQSelect({ - props: { - options, - menuShrink: true - } - }) - let selectWidth = 0 - getHostElement() - .then(($el) => { - selectWidth = $el[ 0 ].clientWidth - }) - .click() - cy.get('.q-menu') - .then(($el) => { - expect($el[ 0 ].clientWidth).to.below(selectWidth) - }) - }) - }) - - describe('(prop): map-options', () => { - it('should display the label of the selected value instead of the value itself', () => { - const options = [ { label: 'Option one', value: 1 }, { label: 'Option two', value: 2 } ] - mountQSelect({ - props: { - options, - modelValue: 1, - mapOptions: true - } - }) - getHostElement() - .contains(options[ 0 ].label) - }) - - it('should display the selected value as string by default', () => { - const options = [ { label: 'Option one', value: 1 }, { label: 'Option two', value: 2 } ] - mountQSelect({ - props: { - options, - modelValue: 1 - } - }) - getHostElement() - .contains(options[ 0 ].value) - }) - }) - }) - - describe('Category: position', () => { - describe.skip('(prop): menu-anchor', () => { - // This is a menu property which is tested in QMenu.spec.js - }) - - describe.skip('(prop): menu-self', () => { - // This is a menu property which is tested in QMenu.spec.js - }) - - describe.skip('(prop): menu-offset', () => { - // This is a menu property which is tested in QMenu.spec.js - }) - }) - - describe('Category: selection', () => { - describe('(prop): display-value', () => { - it('should override the default selection string', () => { - const options = [ { label: 'Option one', value: 1 }, { label: 'Option two', value: 2 } ] - mountQSelect({ - props: { - options, - modelValue: 1, - displayValue: 'Test' - } - }) - getHostElement() - .should('not.contain', options[ 0 ].value) - .should('contain', 'Test') - }) - - it('should not override the default selection string when using `use-chips`', () => { - const options = [ { label: 'Option one', value: 1 }, { label: 'Option two', value: 2 } ] - mountQSelect({ - props: { - options, - modelValue: 1, - displayValue: 'Test', - useChips: true - } - }) - getHostElement() - .should('contain', options[ 0 ].value) - .should('not.contain', 'Test') - }) - - it('should not override the default selection string when using `selected` slot', () => { - const options = [ { label: 'Option one', value: 1 }, { label: 'Option two', value: 2 } ] - mountQSelect({ - props: { - options, - modelValue: 1, - displayValue: 'Test' - }, - slots: { - selected: () => 'Hello there' - } - }) - getHostElement() - .should('not.contain', options[ 0 ].value) - .should('not.contain', 'Test') - .should('contain', 'Hello there') - }) - }) - - describe('(prop): display-value-html', () => { - it('should render the selected option as html', () => { - const options = [ 'Option 1', 'Option 2' ] - mountQSelect({ - props: { - options, - modelValue: options[ 0 ], - displayValueHtml: true - } - }) - getHostElement() - .contains('Option 1') - .should('have.color', 'red') - .should('have.css', 'font-weight', '700') - }) - - it('should not render the selected option as html when using `selected` slot', () => { - const html = 'Option 1' - const options = [ 'Option 1', 'Option 2' ] - mountQSelect({ - props: { - options, - modelValue: options[ 0 ], - displayValueHtml: true - }, - slots: { - selected: () => html - } - }) - getHostElement() - .contains(html) - }) - - it('should not render the selected option as html when using `selected-item` slot', () => { - const html = 'Option 1' - const options = [ 'Option 1', 'Option 2' ] - mountQSelect({ - props: { - options, - modelValue: options[ 0 ], - displayValueHtml: true - }, - slots: { - 'selected-item': () => html - } - }) - getHostElement() - .contains(html) - }) - }) - - describe('(prop): hide-selected', () => { - it('should not show the selected value', () => { - const options = [ 'Option 1', 'Option 2' ] - mountQSelect({ - props: { - options, - modelValue: options[ 0 ], - hideSelected: true - } - }) - getHostElement() - .should('not.contain', options[ 0 ]) - }) - - it('should set the value on the underlying input when using hide-selected', () => { - // Todo: it its not really clear from the docs that you need to use `useInput` and `fillInput` together with this prop to achieve this - const options = [ 'Option 1', 'Option 2' ] - mountQSelect({ - props: { - options, - modelValue: options[ 0 ], - hideSelected: true, - fillInput: true, - useInput: true - } - }) - getHostElement() - .get('input') - .should('have.value', options[ 0 ]) - }) - }) - - describe('(prop): max-values', () => { - it('should allow a maximum number of selections', () => { - const max = 3 - const options = [ '1', '2', '3', '4', '5' ] - const model = ref([]) - mountQSelect({ - props: { - ...vModelAdapter(model), - options, - maxValues: max, - multiple: true - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .get('[role="option"]') - .should('be.visible') - .as('clicked') - .click({ multiple: true }) - cy.get('@clicked') - .then(() => { - expect(model.value.length).to.equal(max) - }) - }) - }) - - describe('(prop): use-chips', () => { - it('should use QChips to show the selected value', () => { - const options = [ 'Option 1', 'Option 2' ] - mountQSelect({ - props: { - options, - modelValue: options[ 0 ], - useChips: true - } - }) - getHostElement() - .get('.q-chip') - .should('contain', options[ 0 ]) - }) - }) - }) - - describe('Category: style', () => { - describe('(prop): popup-content-class', () => { - it('should apply the class to the popup element', () => { - const className = 'test-class' - mountQSelect({ - props: { - options: [ '1', '2 ' ], - popupContentClass: className - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .should('have.class', className) - }) - }) - - describe('(prop): popup-content-style', () => { - it('should apply the style definitions to the popup element', () => { - const style = 'background: red;' - mountQSelect({ - props: { - options: [ '1', '2 ' ], - popupContentStyle: style - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .should('have.backgroundColor', 'red') - }) - }) - - describe('(prop): input-class', () => { - it('should apply a class to the input element when using `useInput`', () => { - const className = 'test-class' - mountQSelect({ - props: { - useInput: true, - inputClass: className - } - }) - getHostElement() - .get('input') - .should('have.class', className) - }) - }) - - describe('(prop): input-style', () => { - it('should apply a style to the input element when using `useInput`', () => { - const style = 'font-size: 30px' - mountQSelect({ - props: { - useInput: true, - inputStyle: style - } - }) - getHostElement() - .get('input') - .should('have.css', 'font-size', '30px') - }) - }) - }) - }) - - describe('Slots', () => { - describe('(slot): selected', () => { - it('should display when something is selected', () => { - const selectedString = 'Test slot selected' - const options = [ 'Option 1', 'Option 2' ] - mountQSelect({ - props: { - options, - modelValue: options[ 0 ] - }, - slots: { - selected: () => selectedString - } - }) - getHostElement() - .should('contain', selectedString) - }) - }) - - describe('(slot): loading', () => { - it('should display when element is loading', () => { - const loadingString = 'Test slot loading' - mountQSelect({ - props: { - loading: true - }, - slots: { - loading: () => loadingString - } - }) - getHostElement() - .should('contain', loadingString) - }) - - it('should not display when element is loading', () => { - const loadingString = 'Test slot loading' - mountQSelect({ - props: { - loading: false - }, - slots: { - loading: () => loadingString - } - }) - getHostElement() - .should('not.contain', loadingString) - }) - }) - - describe('(slot): before-options', () => { - it('should display the slot content before the options', () => { - mountQSelect({ - props: { - options: [ '1', '2', '3' ] - }, - slots: { - 'before-options': () => h('div', { class: 'dummyClass' }, 'Hello') - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .children().first() - .should('have.class', 'dummyClass') - }) - }) - - describe('(slot): after-options', () => { - it('should display the slot content after the options', () => { - mountQSelect({ - props: { - options: [ '1', '2', '3' ] - }, - slots: { - 'after-options': () => h('div', { class: 'dummyClass' }, 'Hello') - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .children().last() - .should('have.class', 'dummyClass') - }) - }) - - describe('(slot): no-option', () => { - it('should display the slot content when there are no options', () => { - const compareString = 'No options :(' - mountQSelect({ - props: { - options: [ ] - }, - slots: { - 'no-option': () => compareString - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .should('contain', compareString) - }) - - it('should pass the inputValue to the slot scope', () => { - const compareString = 'No options :(' - mountQSelect({ - props: { - options: [ ], - useInput: true - }, - slots: { - 'no-option': (scope) => compareString + scope.inputValue - } - }) - getHostElement() - .click() - .type('Hello') - cy.get('.q-menu') - .should('contain', compareString + 'Hello') - }) - - it('should not display the slot content when there are options', () => { - const compareString = 'No options :(' - mountQSelect({ - props: { - options: [ '1', '2', '3' ] - }, - slots: { - 'no-option': () => compareString - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .should('not.contain', compareString) - }) - }) - - describe('(slot): selected-item', () => { - it('should override the default selection slot', () => { - const options = [ { label: 'Option one', value: 1 }, { label: 'Option two', value: 2 } ] - mountQSelect({ - props: { - options, - modelValue: 1 - }, - slots: { - 'selected-item': () => 'Test' - } - }) - getHostElement() - .should('not.contain', options[ 0 ].value) - .should('contain', 'Test') - }) - - it('should pass the selected option index to the slot scope', () => { - const options = [ { label: 'Option one', value: 1 }, { label: 'Option two', value: 2 } ] - mountQSelect({ - props: { - options, - modelValue: 1 - }, - slots: { - 'selected-item': (scope) => 'Test' + scope.index - } - }) - getHostElement() - .should('contain', 'Test0') - }) - - it('should pass the selected option value to the slot scope', () => { - const options = [ { label: 'Option one', value: 1 }, { label: 'Option two', value: 2 } ] - mountQSelect({ - props: { - options, - modelValue: 1 - }, - slots: { - 'selected-item': (scope) => 'Test' + scope.opt - } - }) - getHostElement() - .should('contain', 'Test1') - }) - - it('should pass a removeAtIndex function to the slot scope', () => { - const options = [ { label: 'Option one', value: 1 }, { label: 'Option two', value: 2 } ] - const model = ref(1) - mountQSelect({ - props: { - ...vModelAdapter(model), - options - }, - slots: { - 'selected-item': (scope) => h('button', { class: 'remove', onClick: () => scope.removeAtIndex(scope.index) }, 'Remove') - } - }) - getHostElement() - .get('button.remove') - .click() - getHostElement() - .get('button.remove') - .should('not.exist') - }) - - it('should pass a toggleOption function to the slot scope', () => { - const options = [ { label: 'Option one', value: 1 }, { label: 'Option two', value: 2 } ] - const model = ref(1) - mountQSelect({ - props: { - ...vModelAdapter(model), - options - }, - slots: { - 'selected-item': (scope) => h('button', { class: 'toggle', onClick: () => scope.toggleOption(2) }, 'Toggle' + scope.opt) - } - }) - getHostElement() - .get('button.toggle') - .should('contain', 'Toggle1') - .click() - getHostElement() - .get('button.toggle') - .should('contain', 'Toggle2') - }) - }) - - describe('(slot): option', () => { - it('should render a list of the provided slot as options', () => { - const options = [ '1', '2', '3' ] - mountQSelect({ - props: { - options - }, - slots: { - option: (scope) => h('div', { class: 'custom-option' }, scope.opt) - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .get('.custom-option') - .should('have.length', options.length) - }) - - it('should have a selected property in the scope', () => { - const options = [ '1', '2', '3' ] - mountQSelect({ - props: { - modelValue: '1', - options - }, - slots: { - option: (scope) => h('div', { class: `custom-option-${ scope.selected }` }, scope.opt + scope.selected) - } - }) - getHostElement() - .click() - cy.get('.q-menu') - .get('.custom-option-true') - .should('have.length', 1) - .should('contain', options[ 0 ]) - }) - }) - }) - - describe('Events', () => { - describe('(event): update:model-value', () => { - it('should emit event when model value changes', () => { - const fn = cy.stub() - mountQSelect({ - props: { - options: [ '1', '2', '3' ], - modelValue: null, - 'onUpdate:modelValue': fn - } - }) - - expect(fn).not.to.be.called - getHostElement() - .click() - cy.get('.q-menu') - .get('[role="option"]') - .first() - .should('be.visible') - .as('clicked') - .click() - cy.get('@clicked') - .then(() => { - expect(fn).to.be.calledWith('1') - }) - }) - }) - - describe('(event): input-value', () => { - it('should emit event when text input changes', () => { - const fn = cy.stub() - mountQSelect({ - props: { - modelValue: null, - onInputValue: fn, - useInput: true, - inputDebounce: 0 - } - }) - - expect(fn).not.to.be.called - getHostElement() - .get('input') - .type('h') - .then(() => { - expect(fn).to.be.calledWith('h') - }) - }) - }) - - describe('(event): remove', () => { - it('should emit event when a selected item is removed from selection', () => { - const fn = cy.stub() - const model = ref([ '2', '3' ]) - mountQSelect({ - props: { - ...vModelAdapter(model), - onRemove: fn, - multiple: true, - options: [ '1', '2', '3' ] - } - }) - - expect(fn).not.to.be.called - getHostElement() - .click() - cy.get('.q-menu') - .get('[role="option"]') - .first() - .should('be.visible') - .as('clicked') - .click() - cy.get('@clicked') - .then(() => { - expect(fn).not.to.be.called - }) - cy.get('.q-menu') - .get('[role="option"]') - .first() - .should('be.visible') - .as('clicked') - .click() - cy.get('@clicked') - .then(() => { - // Item is added in the previous step at the end of the array, so at index 2 - expect(fn).to.be.calledWith({ index: 2, value: '1' }) - }) - }) - }) - - describe('(event): add', () => { - it('should emit event when an option is added to the selection', () => { - const fn = cy.stub() - const model = ref([ '2' ]) - mountQSelect({ - props: { - ...vModelAdapter(model), - onAdd: fn, - multiple: true, - options: [ '1', '2', '3' ] - } - }) - - expect(fn).not.to.be.called - getHostElement() - .click() - cy.get('.q-menu') - .get('[role="option"]') - .first() - .should('be.visible') - .as('clicked') - .click() - cy.get('@clicked') - .then(() => { - // Item is added in the previous step at the end of the array, so at index 2 - expect(fn).to.be.calledWith({ index: 1, value: '1' }) - }) - }) - }) - - describe('(event): new-value', () => { - it('should emit event when something is typed into the input field and enter is pressed', () => { - const fn = cy.stub() - const model = ref([ '2' ]) - mountQSelect({ - props: { - ...vModelAdapter(model), - onNewValue: fn, - multiple: true, - useInput: true, - hideDropdownIcon: true - } - }) - - expect(fn).not.to.be.called - getHostElement() - .get('input') - .type('100') - .then(() => { - expect(fn).not.to.be.called - }) - .type('{enter}') - .then(() => { - expect(fn).to.be.calledWith('100') - }) - }) - - it('should add the value to the model when the doneFn is called', () => { - const model = ref([ '2' ]) - mountQSelect({ - props: { - ...vModelAdapter(model), - onNewValue: (val, doneFn) => { - doneFn(val) - }, - multiple: true, - useInput: true, - hideDropdownIcon: true - } - }) - - getHostElement() - .get('input') - .type('100') - .type('{enter}') - .then(() => { - expect(model.value).includes('100') - }) - }) - }) - - describe('(event): filter', () => { - it('should emit event when something is typed into the input field', () => { - const fn = cy.stub() - mountQSelect({ - props: { - onFilter: fn, - useInput: true, - inputDebounce: 0 - } - }) - - expect(fn).not.to.be.called - getHostElement() - .get('input') - .type('h') - .then(() => { - expect(fn).to.be.calledWith('h') - }) - }) - }) - - describe('(event): filter-abort', () => { - it('should emit event when the filterFn has not called the doneFn yet and a new filter is requested', () => { - const fn = cy.stub() - const filterFn = cy.stub() - mountQSelect({ - props: { - onFilter: filterFn, - onFilterAbort: fn, - useInput: true, - inputDebounce: 0 - } - }) - - expect(fn).not.to.be.called - getHostElement() - .get('input') - .click() - .then(() => { - expect(filterFn).to.be.calledOnce - expect(fn).not.to.be.called - }) - .type('h') - .then(() => { - expect(fn).to.be.calledOnce - }) - }) - - it('should not emit event when the filter has called its doneFn', () => { - const fn = cy.stub() - mountQSelect({ - props: { - onFilter: (val, doneFn) => { - doneFn() - }, - onFilterAbort: fn, - useInput: true, - inputDebounce: 0 - } - }) - - expect(fn).not.to.be.called - getHostElement() - .get('input') - .click() - .then(() => { - expect(fn).not.to.be.called - }) - .type('h') - .then(() => { - expect(fn).not.to.be.called - }) - }) - }) - - describe('(event): popup-show', () => { - it('should emit event when the options are shown', () => { - const fn = cy.stub() - mountQSelect({ - props: { - onPopupShow: fn, - options: [ '1', '2', '3' ] - } - }) - - expect(fn).not.to.be.called - getHostElement() - .click() - .then(() => { - expect(fn).to.be.called - }) - }) - }) - - describe('(event): popup-hide', () => { - it('should emit event when the options are hidden', () => { - const fn = cy.stub() - mountQSelect({ - props: { - onPopupHide: fn, - options: [ '1', '2', '3' ] - } - }) - - expect(fn).not.to.be.called - getHostElement() - .click() - .then(() => { - expect(fn).not.to.be.called - }) - .type('{esc}') - .then(() => { - expect(fn).to.be.called - }) - }) - }) - - describe('(event): virtual-scroll', () => { - it.skip('', () => { - // The virtual scroll code is tested as a composable - // The property is included in the QSelect.json to add typings - // for the QSelect ref passed in this event. - // I think testing the component ref that is passed is of type QSelect is out of scope for unit tests. - }) - }) - }) - - describe('Methods', () => { - describe('(method): focus', () => { - it('should focus the component', () => { - mountQSelect() - - getHostElement() - .get('[tabindex="0"]') - .should('not.have.focus') - getHostElement() - .then(() => { - Cypress.vueWrapper.vm.focus() - }) - getHostElement() - .get('[tabindex="0"]') - .should('have.focus') - }) - }) - - describe('(method): showPopup', () => { - it('should open the popup and focus the component', () => { - mountQSelect({ - props: { - options: [ '1', '2' ] - } - }) - - getHostElement() - cy.get('.q-menu') - .should('not.exist') - .then(() => { - Cypress.vueWrapper.vm.showPopup() - }) - cy.get('.q-menu') - .should('be.visible') - getHostElement() - .get('[tabindex="0"]') - .should('have.focus') - }) - }) - - describe('(method): hidePopup', () => { - it('should hide the popup', () => { - mountQSelect({ - props: { - options: [ '1', '2' ] - } - }) - - getHostElement() - .click() - cy.get('.q-menu') - .should('be.visible') - .then(() => { - Cypress.vueWrapper.vm.hidePopup() - }) - cy.get('.q-menu') - .should('not.exist') - }) - }) - - describe('(method): removeAtIndex', () => { - it('should remove a selected option at the correct index', () => { - const options = [ '1', '2', '3', '4' ] - const model = ref([ '1', '2', '4' ]) - mountQSelect({ - props: { - ...vModelAdapter(model), - multiple: true, - options - } - }) - .then(() => { - expect(model.value.includes('4')).to.be.true - Cypress.vueWrapper.vm.removeAtIndex(2) - expect(model.value.includes('4')).to.be.false - }) - }) - }) - - describe('(method): add', () => { - it('should add a selected option', () => { - const model = ref([ '1', '2' ]) - mountQSelect({ - props: { - ...vModelAdapter(model), - multiple: true - } - }) - .then(() => { - expect(model.value.includes('100')).to.be.false - Cypress.vueWrapper.vm.add('100') - expect(model.value.includes('100')).to.be.true - }) - }) - - it('should not add a duplicate option when unique is true', () => { - const model = ref([ '1', '2' ]) - mountQSelect({ - props: { - ...vModelAdapter(model), - multiple: true - } - }) - .then(() => { - expect(model.value.length).to.be.equal(2) - Cypress.vueWrapper.vm.add('2', true) - expect(model.value.length).to.be.equal(2) - Cypress.vueWrapper.vm.add('2') - expect(model.value.length).to.be.equal(3) - }) - }) - }) - - describe('(method): toggleOption', () => { - it('should toggle an option', () => { - const model = ref([ '1', '2' ]) - mountQSelect({ - props: { - ...vModelAdapter(model), - multiple: true - } - }) - .then(() => { - expect(model.value.length).to.be.equal(2) - Cypress.vueWrapper.vm.toggleOption('2') - expect(model.value.length).to.be.equal(1) - }) - // When not using this wait this test will succeed on `open-ct` but fail on `run-ct` - .wait(50) - .then(() => { - Cypress.vueWrapper.vm.toggleOption('2') - expect(model.value.length).to.be.equal(2) - }) - }) - - // Todo: toggleOption argument keepOpen only does something when using single select. This is not clear from the docs. - // should this be consistent? E.g. use `true` as argument when multiple is true by default but make sure it can be overridden. - it('should close the menu and clear the filter', () => { - const model = ref('1') - mountQSelect({ - props: { - ...vModelAdapter(model), - options: [ '1', '2' ], - useInput: true - } - }) - - getHostElement() - .click() - .get('input') - .type('h') - cy.get('.q-menu') - .should('be.visible') - .then(() => { - Cypress.vueWrapper.vm.toggleOption('2') - }) - cy.get('.q-menu') - .should('not.exist') - cy.get('input') - .should('have.value', '') - }) - - it('should not close the menu and clear the filter when keepOpen is true', () => { - const model = ref('1') - mountQSelect({ - props: { - ...vModelAdapter(model), - options: [ '1', '2' ], - useInput: true - } - }) - - getHostElement() - .click() - .get('input') - .type('h') - cy.get('.q-menu') - .should('be.visible') - .then(() => { - Cypress.vueWrapper.vm.toggleOption('2', true) - }) - cy.get('.q-menu') - .should('be.visible') - cy.get('input') - .should('have.value', 'h') - }) - }) - - describe('(method): setOptionIndex', () => { - it('should set an option from the menu dropdown as focused', () => { - const options = [ '1', '2', '3', '4' ] - mountQSelect({ - props: { - options - } - }) - getHostElement() - .click() - .then(() => { - Cypress.vueWrapper.vm.setOptionIndex(0) - }) - .get('[role="option"]') - .first() - .should('have.class', 'q-manual-focusable--focused') - }) - }) - - describe('(method): moveOptionSelection', () => { - it('should move the optionSelection by some index offset', () => { - const options = [ '1', '2', '3', '4' ] - mountQSelect({ - props: { - options - } - }) - getHostElement() - .click() - .then(() => { - Cypress.vueWrapper.vm.setOptionIndex(0) - }) - .get('[role="option"]') - .first() - .should('have.class', 'q-manual-focusable--focused') - .then(() => { - Cypress.vueWrapper.vm.moveOptionSelection(3) - }) - .get('[role="option"]') - .last() - .should('have.class', 'q-manual-focusable--focused') - }) - }) - - describe('(method): filter', () => { - it('should filter the options list', () => { - const options = [ '1', '2', '3', '4' ] - const fn = cy.stub() - const text = 'test' - mountQSelect({ - props: { - options, - useInput: true, - onFilter: fn - } - }) - getHostElement() - .click() - .then(() => { - expect(fn).not.to.be.calledWith(text) - Cypress.vueWrapper.vm.filter(text) - expect(fn).to.be.calledWith(text) - }) - }) - }) - - describe('(method): updateMenuPosition', () => { - it.skip(' ', () => { - // Not sure in what scenario this is needed, there is also some auto repositioning going on - }) - }) - - describe('(method): updateInputValue', () => { - it('should update the input value', () => { - const options = [ '1', '2', '3', '4' ] - const fn = cy.stub() - const text = 'test' - mountQSelect({ - props: { - options, - useInput: true, - onFilter: fn - } - }) - getHostElement() - .click() - .then(() => { - expect(fn).not.to.be.calledWith(text) - Cypress.vueWrapper.vm.updateInputValue(text) - expect(fn).to.be.calledWith(text) - }) - .get('input') - .should('have.value', text) - }) - - it('should not trigger the filter when specified', () => { - const options = [ '1', '2', '3', '4' ] - const fn = cy.stub() - const text = 'test' - mountQSelect({ - props: { - options, - useInput: true, - onFilter: fn - } - }) - getHostElement() - .click() - .then(() => { - expect(fn).not.to.be.calledWith(text) - Cypress.vueWrapper.vm.updateInputValue(text, true) - expect(fn).not.to.be.calledWith(text) - }) - .get('input') - .should('have.value', text) - }) - }) - - describe('(method): isOptionSelected', () => { - it('should tell when an option is selected', () => { - const options = [ '1', '2', '3', '4' ] - const modelValue = [ '1', '2', '4' ] - mountQSelect({ - props: { - modelValue, - multiple: true, - options - } - }) - .then(() => { - expect(Cypress.vueWrapper.vm.isOptionSelected(options[ 0 ])).to.be.true - expect(Cypress.vueWrapper.vm.isOptionSelected(options[ 2 ])).to.be.false - }) - }) - }) - - describe('(method): getEmittingOptionValue', () => { - it('should return the emit value with plain options', () => { - const options = [ '1', '2', '3', '4' ] - const modelValue = '1' - mountQSelect({ - props: { - modelValue, - options - } - }) - .then(() => { - expect(Cypress.vueWrapper.vm.getEmittingOptionValue(options[ 2 ])).to.equal(options[ 2 ]) - }) - }) - - it('should return the emit value with object options', () => { - const options = [ { label: '1', value: 1 }, { label: '2', value: 2 }, { label: '3', value: 3 } ] - const modelValue = options[ 0 ] - mountQSelect({ - props: { - modelValue, - options - } - }) - .then(() => { - expect(Cypress.vueWrapper.vm.getEmittingOptionValue(options[ 2 ])).to.equal(options[ 2 ]) - }) - }) - - it('should respect emit-value when using options', () => { - const options = [ { label: '1', value: 1 }, { label: '2', value: 2 }, { label: '3', value: 3 } ] - const modelValue = options[ 0 ] - mountQSelect({ - props: { - modelValue, - options, - emitValue: true - } - }) - .then(() => { - expect(Cypress.vueWrapper.vm.getEmittingOptionValue(options[ 2 ])).to.equal(options[ 2 ].value) - }) - }) - }) - - describe('(method): getOptionValue', () => { - it('should return the option value with plain options', () => { - const options = [ '1', '2', '3', '4' ] - const modelValue = '1' - mountQSelect({ - props: { - modelValue, - options - } - }) - .then(() => { - expect(Cypress.vueWrapper.vm.getOptionValue(options[ 2 ])).to.equal(options[ 2 ]) - }) - }) - - it('should return the option value with object options (value by default)', () => { - const options = [ { label: '1', value: 1 }, { label: '2', value: 2 }, { label: '3', value: 3 } ] - const modelValue = options[ 0 ] - mountQSelect({ - props: { - modelValue, - options - } - }) - .then(() => { - expect(Cypress.vueWrapper.vm.getOptionValue(options[ 2 ])).to.equal(options[ 2 ].value) - }) - }) - - it('should respect the option-value option', () => { - const options = [ { label: '1', test: 1 }, { label: '2', test: 2 }, { label: '3', test: 3 } ] - const modelValue = options[ 0 ] - mountQSelect({ - props: { - modelValue, - options, - optionValue: 'test' - } - }) - .then(() => { - expect(Cypress.vueWrapper.vm.getOptionValue(options[ 2 ])).to.equal(options[ 2 ].test) - }) - }) - }) - - describe('(method): getOptionLabel', () => { - it('should return the option label with plain options', () => { - const options = [ '1', '2', '3', '4' ] - const modelValue = '1' - mountQSelect({ - props: { - modelValue, - options - } - }) - .then(() => { - expect(Cypress.vueWrapper.vm.getOptionLabel(options[ 2 ])).to.equal(options[ 2 ]) - }) - }) - - it('should return the option label with object options (label by default)', () => { - const options = [ { label: '1', value: 1 }, { label: '2', value: 2 }, { label: '3', value: 3 } ] - const modelValue = options[ 0 ] - mountQSelect({ - props: { - modelValue, - options - } - }) - .then(() => { - expect(Cypress.vueWrapper.vm.getOptionLabel(options[ 2 ])).to.equal(options[ 2 ].label) - }) - }) - - it('should respect the option-value option', () => { - const options = [ { test: '1', value: 1 }, { test: '2', value: 2 }, { test: '3', value: 3 } ] - const modelValue = options[ 0 ] - mountQSelect({ - props: { - modelValue, - options, - optionLabel: 'test' - } - }) - .then(() => { - expect(Cypress.vueWrapper.vm.getOptionLabel(options[ 2 ])).to.equal(options[ 2 ].test) - }) - }) - }) - - describe('(method): isOptionDisabled', () => { - it('should return if an option is disabled correctly', () => { - const options = [ { label: '1', value: 1, disable: true }, { label: '2', value: 2 }, { label: '3', value: 3 } ] - const modelValue = options[ 0 ] - mountQSelect({ - props: { - modelValue, - options - } - }) - .then(() => { - expect(Cypress.vueWrapper.vm.isOptionDisabled(options[ 0 ])).to.be.true - // This currently fails: https://github.com/quasarframework/quasar/issues/12046 - // expect(Cypress.vueWrapper.vm.isOptionDisabled(options[ 1 ])).to.be.false - // expect(Cypress.vueWrapper.vm.isOptionDisabled(options[ 2 ])).to.be.false - }) - }) - }) - }) -}) diff --git a/ui/src/components/table/__tests__/QTable.cy.js b/ui/src/components/table/__tests__/QTable.cy.js deleted file mode 100644 index 0d228702aa0..00000000000 --- a/ui/src/components/table/__tests__/QTable.cy.js +++ /dev/null @@ -1,635 +0,0 @@ -describe('Table API', () => { - describe('Props', () => { - describe('Category: behavior', () => { - describe('(prop): grid', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: behavior|content', () => { - describe('(prop): grid-header', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): loading', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: column', () => { - describe('(prop): columns', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): visible-columns', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: content', () => { - describe('(prop): icon-first-page', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): icon-prev-page', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): icon-next-page', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): icon-last-page', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): title', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): hide-header', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): hide-bottom', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): hide-selected-banner', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): hide-no-data', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): hide-pagination', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): separator', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): wrap-cells', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): no-data-label', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): no-results-label', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): loading-label', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: expansion', () => { - describe('(prop): expanded', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: filter', () => { - describe('(prop): filter', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): filter-method', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: general', () => { - describe('(prop): rows', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): row-key', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: pagination', () => { - describe('(prop): rows-per-page-label', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): pagination-label', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): pagination', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): rows-per-page-options', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: selection', () => { - describe('(prop): selected-rows-label', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): selection', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): selected', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: sorting', () => { - describe('(prop): binary-state-sort', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): column-sort-order', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): sort-method', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: style', () => { - describe('(prop): color', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): dense', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): dark', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): flat', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): bordered', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): square', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): table-style', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): table-class', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): table-header-style', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): table-header-class', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): card-container-style', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): card-container-class', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): card-style', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): card-class', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): title-class', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: virtual-scroll', () => { - describe('(prop): virtual-scroll', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): virtual-scroll-slice-size', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): virtual-scroll-slice-ratio-before', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): virtual-scroll-slice-ratio-after', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): virtual-scroll-item-size', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): virtual-scroll-sticky-size-start', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): virtual-scroll-sticky-size-end', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: virtual-scroll|content', () => { - describe('(prop): table-colspan', () => { - it.skip(' ', () => { - // - }) - }) - }) - }) - - describe('Slots', () => { - describe('(slot): loading', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(slot): item', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(slot): body', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(slot): body-cell', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(slot): body-cell-[name]', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(slot): header', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(slot): header-cell', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(slot): header-cell-[name]', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(slot): body-selection', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(slot): header-selection', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(slot): top-row', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(slot): bottom-row', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(slot): top', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(slot): bottom', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(slot): pagination', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(slot): top-left', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(slot): top-right', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(slot): top-selection', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(slot): no-data', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Events', () => { - describe('(event): row-click', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(event): row-dblclick', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(event): row-contextmenu', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(event): request', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(event): selection', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(event): update:pagination', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(event): update:selected', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(event): update:expanded', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(event): virtual-scroll', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Methods', () => { - describe('(method): requestServerInteraction', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): setPagination', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): firstPage', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): prevPage', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): nextPage', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): lastPage', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): isRowSelected', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): clearSelection', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): isRowExpanded', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): setExpanded', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): sort', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): toggleFullscreen', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): resetVirtualScroll', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): scrollTo', () => { - it.skip(' ', () => { - // - }) - }) - }) -}) diff --git a/ui/src/components/table/__tests__/QTd.cy.js b/ui/src/components/table/__tests__/QTd.cy.js deleted file mode 100644 index 6b5f3a6558f..00000000000 --- a/ui/src/components/table/__tests__/QTd.cy.js +++ /dev/null @@ -1,35 +0,0 @@ -describe('Td API', () => { - describe('Props', () => { - describe('Category: content', () => { - describe('(prop): auto-width', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: general', () => { - describe('(prop): props', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: style', () => { - describe('(prop): no-hover', () => { - it.skip(' ', () => { - // - }) - }) - }) - }) - - describe('Slots', () => { - describe('(slot): default', () => { - it.skip(' ', () => { - // - }) - }) - }) -}) diff --git a/ui/src/components/table/__tests__/QTh.cy.js b/ui/src/components/table/__tests__/QTh.cy.js deleted file mode 100644 index 59ebaf586a6..00000000000 --- a/ui/src/components/table/__tests__/QTh.cy.js +++ /dev/null @@ -1,27 +0,0 @@ -describe('Th API', () => { - describe('Props', () => { - describe('Category: content', () => { - describe('(prop): auto-width', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: general', () => { - describe('(prop): props', () => { - it.skip(' ', () => { - // - }) - }) - }) - }) - - describe('Slots', () => { - describe('(slot): default', () => { - it.skip(' ', () => { - // - }) - }) - }) -}) diff --git a/ui/src/components/table/__tests__/QTr.cy.js b/ui/src/components/table/__tests__/QTr.cy.js deleted file mode 100644 index 04c6c8db2e2..00000000000 --- a/ui/src/components/table/__tests__/QTr.cy.js +++ /dev/null @@ -1,27 +0,0 @@ -describe('Tr API', () => { - describe('Props', () => { - describe('Category: general', () => { - describe('(prop): props', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: style', () => { - describe('(prop): no-hover', () => { - it.skip(' ', () => { - // - }) - }) - }) - }) - - describe('Slots', () => { - describe('(slot): default', () => { - it.skip(' ', () => { - // - }) - }) - }) -}) diff --git a/ui/src/components/tabs/__tests__/QTab.cy.js b/ui/src/components/tabs/__tests__/QTab.cy.js deleted file mode 100644 index aec94afe889..00000000000 --- a/ui/src/components/tabs/__tests__/QTab.cy.js +++ /dev/null @@ -1,79 +0,0 @@ -describe('Tab API', () => { - describe('Props', () => { - describe('Category: content', () => { - describe('(prop): icon', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): label', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): alert', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): alert-icon', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): no-caps', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: general', () => { - describe('(prop): name', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): tabindex', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: state', () => { - describe('(prop): disable', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: style', () => { - describe('(prop): content-class', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): ripple', () => { - it.skip(' ', () => { - // - }) - }) - }) - }) - - describe('Slots', () => { - describe('(slot): default', () => { - it.skip(' ', () => { - // - }) - }) - }) -}) diff --git a/ui/src/components/tabs/__tests__/QTabs.cy.js b/ui/src/components/tabs/__tests__/QTabs.cy.js deleted file mode 100644 index f55a3e8d1b8..00000000000 --- a/ui/src/components/tabs/__tests__/QTabs.cy.js +++ /dev/null @@ -1,147 +0,0 @@ -describe('Tabs API', () => { - describe('Props', () => { - describe('Category: content', () => { - describe('(prop): vertical', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): outside-arrows', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): mobile-arrows', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): align', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): left-icon', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): right-icon', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): stretch', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): shrink', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): switch-indicator', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): narrow-indicator', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): inline-label', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): no-caps', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: content|behavior', () => { - describe('(prop): breakpoint', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: model', () => { - describe('(prop): model-value', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: style', () => { - describe('(prop): active-color', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): active-bg-color', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): indicator-color', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): content-class', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): active-class', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): dense', () => { - it.skip(' ', () => { - // - }) - }) - }) - }) - - describe('Slots', () => { - describe('(slot): default', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Events', () => { - describe('(event): update:model-value', () => { - it.skip(' ', () => { - // - }) - }) - }) -}) diff --git a/ui/src/components/uploader/__tests__/QUploader.cy.js b/ui/src/components/uploader/__tests__/QUploader.cy.js deleted file mode 100644 index 2e3ceb7dc20..00000000000 --- a/ui/src/components/uploader/__tests__/QUploader.cy.js +++ /dev/null @@ -1,191 +0,0 @@ -import QUploader from '../QUploader' - -describe('Uploader API', () => { - describe('Props', () => { - describe('Category: behavior', () => { - describe('(prop): auto-upload', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): hide-upload-btn', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: content', () => { - describe('(prop): label', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): no-thumbnails', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: state', () => { - describe('(prop): disable', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): readonly', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: style', () => { - describe('(prop): color', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): text-color', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): dark', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): square', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): flat', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): bordered', () => { - it.skip(' ', () => { - // - }) - }) - }) - }) - - describe('Slots', () => { - describe('(slot): header', () => { - it('renders correctly using the scope', () => { - // Taken from ui/dev/src/pages/form/uploader.vue - const headerSlot = ` - -` - - cy.mount(QUploader, { - slots: { - header: headerSlot - } - }) - }) - }) - - describe('(slot): list', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Events', () => { - describe('(event): added', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(event): removed', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(event): start', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(event): finish', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Methods', () => { - describe('(method): upload', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): abort', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): reset', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): removeUploadedFiles', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): removeQueuedFiles', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): removeFile', () => { - it.skip(' ', () => { - // - }) - }) - }) -}) diff --git a/ui/src/composables/__tests__/FieldWrapper.vue b/ui/src/composables/__tests__/FieldWrapper.vue deleted file mode 100644 index bf7fb667a9b..00000000000 --- a/ui/src/composables/__tests__/FieldWrapper.vue +++ /dev/null @@ -1,54 +0,0 @@ - - - diff --git a/ui/src/composables/__tests__/use-anchor.cy.js b/ui/src/composables/__tests__/use-anchor.cy.js deleted file mode 100644 index 807114957ad..00000000000 --- a/ui/src/composables/__tests__/use-anchor.cy.js +++ /dev/null @@ -1,98 +0,0 @@ -import WrapperOne from '../../../components/menu/__tests__/WrapperOne.vue' - -describe('use-anchor API', () => { - describe('Props', () => { - describe('Category: behavior', () => { - describe('(prop): target', () => { - it('should use another target using a CSS selector', () => { - cy.mount(WrapperOne, { - props: { - target: '.other-target' - } - }) - - // Click on parent element should not show menu - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .should('not.exist') - - // Click on other target should show menu - cy.dataCy('other-target') - .click() - cy.dataCy('menu') - .should('exist') - }) - - it('should not show when target is false', () => { - cy.mount(WrapperOne, { - props: { - target: false - } - }) - - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .should('not.exist') - }) - }) - - describe('(prop): no-parent-event', () => { - it('should not show when clicking parent with no-parent-event true', () => { - cy.mount(WrapperOne, { - props: { - 'no-parent-event': true - } - }) - - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .should('not.exist') - }) - - it('should show when clicking parent with no-parent-event false', () => { - cy.mount(WrapperOne, { - props: { - 'no-parent-event': false - } - }) - - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .should('exist') - }) - }) - - describe('(prop): context-menu', () => { - it('should not show when left clicking parent', () => { - cy.mount(WrapperOne, { - props: { - 'context-menu': true - } - }) - - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .should('not.exist') - }) - - it('should show when right clicking parent', () => { - cy.mount(WrapperOne, { - props: { - 'context-menu': true - } - }) - - cy.dataCy('wrapper') - .rightclick() - cy.dataCy('menu') - .should('exist') - }) - }) - }) - }) -}) diff --git a/ui/src/composables/__tests__/use-field.cy.js b/ui/src/composables/__tests__/use-field.cy.js deleted file mode 100644 index cd391296b17..00000000000 --- a/ui/src/composables/__tests__/use-field.cy.js +++ /dev/null @@ -1,547 +0,0 @@ -import FieldWrapper from './FieldWrapper.vue' -import { ref } from 'vue' -import { vModelAdapter } from '@quasar/quasar-app-extension-testing-e2e-cypress' -import Icons from '../../../../icon-set/material-icons.mjs' - -function mountQFieldWrapper (options) { - return cy.mount(FieldWrapper, options) -} - -function getHostElement () { - return cy.get('.select-root') -} - -const selectOptions = [ 'Option 1', 'Option 2' ] - -describe('use-field API', () => { - describe('Props', () => { - describe('Category: behavior', () => { - describe('(prop): autofocus', () => { - it('should autofocus on component', () => { - mountQFieldWrapper({ - props: { - autofocus: true - } - }) - - getHostElement() - .get('.q-field--focused') - .should('exist') - .get('input') - .should('have.focus') - }) - }) - - describe('(prop): for', () => { - it('should set the for attribute for the value and the id of the control', () => { - const forProp = 'hello-there' - - mountQFieldWrapper({ - props: { - for: forProp - } - }) - - getHostElement().should('have.attr', 'for', forProp) - getHostElement().get('input').should('have.id', forProp) - }) - }) - - describe('(prop): name', () => { - it('should use the value of the for prop as control name if name is not set', () => { - const forProp = 'hello-there' - const model = ref(selectOptions[ 0 ]) - - mountQFieldWrapper({ - props: { - ...vModelAdapter(model), - options: selectOptions, - for: forProp - } - }) - - getHostElement().get('select').should('have.attr', 'name', forProp) - }) - - it('should set the name of the control', () => { - const name = 'hello-there' - const model = ref(selectOptions[ 0 ]) - - mountQFieldWrapper({ - props: { - ...vModelAdapter(model), - options: selectOptions, - name - } - }) - - getHostElement().get('select').should('have.attr', 'name', name) - }) - }) - }) - - describe('Category: behavior|content', () => { - describe('(prop): loading', () => { - it('should set the component into a loading state', () => { - mountQFieldWrapper({ - props: { - loading: true - } - }) - - getHostElement().get('.q-spinner').should('exist') - }) - }) - - describe('(prop): clearable', () => { - it('should append a clear icon', () => { - const model = ref(selectOptions[ 0 ]) - - mountQFieldWrapper({ - props: { - ...vModelAdapter(model), - clearable: true, - options: selectOptions - } - }) - - getHostElement().find('input').should('exist').should('have.value', model.value) - getHostElement().get('button[type="button"]').contains(Icons.field.clear).click() - getHostElement().get('input').should('have.value', '') - }) - }) - }) - - describe('Category: content', () => { - describe('(prop): label', () => { - it('should show the label when supplied', () => { - const label = 'Select something' - cy.mount(FieldWrapper, { - props: { - label - } - }) - cy.get('.select-root') - .should('contain.text', label) - }) - - it('should show the label centered when not focused', () => { - const label = 'Select something' - cy.mount(FieldWrapper, { - props: { - label - } - }) - cy.get('.select-root') - .get(`div:contains(${ label })`) - .should('exist') - .should('not.have.class', 'q-field--float') - }) - - it('should show the label stacked when focused', () => { - const label = 'Select something' - cy.mount(FieldWrapper, { - props: { - label - } - }) - cy.get('.select-root [tabindex="0"]') - .focus() - cy.get(`.select-root:contains(${ label })`) - .should('exist') - .should('have.class', 'q-field--float') - }) - }) - - describe('(prop): stack-label', () => { - it('should show the label stacked', () => { - const label = 'Select something' - cy.mount(FieldWrapper, { - props: { - label, - stackLabel: true - } - }) - cy.get(`.select-root:contains(${ label })`) - .should('exist') - .should('have.class', 'q-field--float') - }) - }) - - describe('(prop): hint', () => { - it('should show a hint text', () => { - const hint = 'Select something' - cy.mount(FieldWrapper, { - props: { - hint - } - }) - cy.get(`.select-root:contains(${ hint })`) - .should('exist') - }) - }) - - describe('(prop): hide-hint', () => { - it('should not show a hint text when not focused', () => { - const hint = 'Select something' - cy.mount(FieldWrapper, { - props: { - hint, - hideHint: true - } - }) - cy.get(`.select-root:contains(${ hint })`) - .should('not.exist') - }) - it('should show a hint text when focused', () => { - const hint = 'Select something' - cy.mount(FieldWrapper, { - props: { - hint, - hideHint: true - } - }) - cy.get('.select-root [tabindex="0"]') - .focus() - cy.get(`.select-root:contains(${ hint })`) - .should('exist') - }) - }) - - describe('(prop): prefix', () => { - it('should display a prefix', () => { - const prefix = 'Hello there!' - mountQFieldWrapper({ - props: { - prefix - } - }) - - getHostElement().get('.q-field__prefix').should('exist').should('contain', prefix) - }) - }) - - describe('(prop): suffix', () => { - it('should display a suffix', () => { - const suffix = 'Hello there!' - mountQFieldWrapper({ - props: { - suffix - } - }) - - getHostElement().get('.q-field__suffix').should('exist').should('contain', suffix) - }) - }) - - describe('(prop): clear-icon', () => { - it('should display custom clear-icon when one is set', () => { - const model = ref(selectOptions[ 0 ]) - const clearIcon = 'custom-clear-icon' - mountQFieldWrapper({ - props: { - ...vModelAdapter(model), - options: selectOptions, - clearable: true, - clearIcon - } - }) - - getHostElement().get('.q-field__append').get('button').should('contain', clearIcon) - }) - }) - - describe('(prop): label-slot', () => { - it('should force the use of the label slot even when a label prop is set', () => { - const model = ref(selectOptions[ 0 ]) - const labelSlot = 'Hello world' - - mountQFieldWrapper({ - props: { - ...vModelAdapter(model), - options: selectOptions, - labelSlot: true, - label: 'Hello there' - }, - slots: { - label: () => labelSlot - } - }) - - getHostElement().find('.q-field__label').should('contain.text', labelSlot) - }) - }) - - describe('(prop): bottom-slots', () => { - it('should use a bottom error slot', () => { - const model = ref(selectOptions[ 0 ]) - const bottomSlot = 'Hello there' - - mountQFieldWrapper({ - props: { - ...vModelAdapter(model), - options: selectOptions, - bottomSlotSlots: true, - error: true - }, - slots: { - error: () => bottomSlot - } - }) - - getHostElement().find('.q-field__bottom') - .should('contain.text', bottomSlot) - }) - - it('should use a bottom hint slot', () => { - const model = ref(selectOptions[ 0 ]) - const bottomSlot = 'Hello there' - - mountQFieldWrapper({ - props: { - ...vModelAdapter(model), - options: selectOptions, - bottomSlots: true - }, - slots: { - hint: () => bottomSlot - } - }) - - getHostElement().find('.q-field__bottom').should('contain.text', bottomSlot) - }) - - it('should use a bottom counter slot', () => { - const model = ref(selectOptions[ 0 ]) - - mountQFieldWrapper({ - props: { - ...vModelAdapter(model), - options: selectOptions, - counter: true - } - }) - - getHostElement().find('.q-field__bottom').should('contain.text', model.value.length) - }) - }) - - describe('(prop): counter', () => { - it('should show an automatic counter on bottom right', () => { - const model = ref(selectOptions[ 0 ]) - mountQFieldWrapper({ - props: { - ...vModelAdapter(model), - options: selectOptions, - counter: true - } - }) - - getHostElement().get('.q-field__counter').should('contain', model.value.length) - }) - }) - }) - - describe('Category: state', () => { - describe('(prop): disable', () => { - it('should put the component on disable state', () => { - mountQFieldWrapper({ - props: { - disable: true - } - }) - - getHostElement().should('have.class', 'q-field--disabled') - }) - }) - - describe('(prop): readonly', () => { - it('should put the component on readonly state', () => { - mountQFieldWrapper({ - props: { - readonly: true - } - }) - - getHostElement().should('have.class', 'q-field--readonly') - }) - }) - }) - - describe('Category: style', () => { - describe('(prop): label-color', () => { - it('should display a label color', () => { - const label = 'Hello there!' - mountQFieldWrapper({ - props: { - label, - labelColor: 'red' - } - }) - - getHostElement().get('.q-field__label.text-red').should('contain', label) - }) - }) - - describe('(prop): color', () => { - it('should set a color on the component', () => { - mountQFieldWrapper() - getHostElement().get('.q-field__control.text-red').should('not.exist') - - getHostElement().get('input').then(() => { - Cypress.vueWrapper.setProps({ color: 'red' }) - - getHostElement().get('.q-field__control.text-red').should('exist') - }) - }) - }) - - describe('(prop): bg-color', () => { - it('should display a background color', () => { - mountQFieldWrapper({ - props: { - bgColor: 'red' - } - }) - - getHostElement().get('.q-field__control.bg-red').should('exist') - }) - }) - - describe('(prop): hide-bottom-space', () => { - it.skip(' ', () => { - // - }) - }) - - const fieldLooks = [ 'item-aligned', 'dark', 'filled', 'outlined', 'borderless', 'standout', 'rounded', 'square', 'dense' ] - for (const style of fieldLooks) { - describe(`(prop): ${ style }`, () => { - it(`should apply ${ style } design style`, () => { - mountQFieldWrapper({ - props: { - [ style ]: true - } - }) - - getHostElement().get(`.q-field--${ style }`).should('exist') - }) - }) - } - }) - }) - - describe('Slots', () => { - const slots = [ 'prepend', 'append', 'before', 'after', 'label' ] - - describe('(slot): default', () => { - it('should use the default slot', () => { - const model = ref(selectOptions[ 0 ]) - const labelSlot = 'Hello world' - - mountQFieldWrapper({ - props: { - ...vModelAdapter(model), - options: selectOptions, - labelSlot: true - }, - slots: { - default: () => labelSlot - } - }) - - getHostElement().should('contain.text', labelSlot) - }) - }) - - for (const slot of slots) { - describe(`(slot): ${ slot }`, () => { - it(`should append a '${ slot }' slot`, () => { - const model = ref(selectOptions[ 0 ]) - const labelSlot = 'Hello world' - - mountQFieldWrapper({ - props: { - ...vModelAdapter(model), - options: selectOptions, - labelSlot: true - }, - slots: { - [ slot ]: () => labelSlot - } - }) - - getHostElement().get(`.q-field__${ slot }`).should('contain.text', labelSlot) - }) - }) - } - }) - - describe('Events', () => { - describe('(event): clear', () => { - it('should emit the clear event when the clear button is clicked', () => { - const model = ref(selectOptions[ 0 ]) - const fn = cy.stub() - - mountQFieldWrapper({ - props: { - ...vModelAdapter(model), - clearable: true, - options: selectOptions, - onClear: fn - } - }) - - getHostElement().get('button[type="button"]') - .contains(Icons.field.clear).click() - .then(() => { - expect(fn).to.be.calledWith() - }) - }) - }) - }) - - describe('Methods', () => { - describe('(method): focus', () => { - it('should focus the component', () => { - mountQFieldWrapper() - - getHostElement() - .get('input') - .should('not.have.focus') - getHostElement() - .then(() => { - Cypress.vueWrapper.vm.focusMethod() - }) - getHostElement() - .get('input') - .should('have.focus') - }) - }) - - describe('(method): blur', () => { - it('should blur the component', () => { - mountQFieldWrapper() - - getHostElement() - .get('input').focus() - getHostElement() - .get('.q-field--focused') - .as('focused-element') - .should('exist') - - cy.get('@focused-element') - .get('input') - .should('have.focus') - - getHostElement() - .then(() => { - Cypress.vueWrapper.vm.blur() - }) - - cy.get('@focused-element').should('not.exist') - getHostElement().get('input').should('not.have.focus') - }) - }) - }) -}) diff --git a/ui/src/composables/__tests__/use-file.cy.js b/ui/src/composables/__tests__/use-file.cy.js deleted file mode 100644 index 6e1a691598c..00000000000 --- a/ui/src/composables/__tests__/use-file.cy.js +++ /dev/null @@ -1,69 +0,0 @@ -describe('use-file API', () => { - describe('Props', () => { - describe('Category: behavior', () => { - describe('(prop): multiple', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): accept', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): capture', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): max-file-size', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): max-total-size', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): max-files', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): filter', () => { - it.skip(' ', () => { - // - }) - }) - }) - }) - - describe('Events', () => { - describe('(event): rejected', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Methods', () => { - describe('(method): pickFiles', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): addFiles', () => { - it.skip(' ', () => { - // - }) - }) - }) -}) diff --git a/ui/src/composables/__tests__/use-fullscreen.cy.js b/ui/src/composables/__tests__/use-fullscreen.cy.js deleted file mode 100644 index f397b839eef..00000000000 --- a/ui/src/composables/__tests__/use-fullscreen.cy.js +++ /dev/null @@ -1,37 +0,0 @@ -describe('use-fullscreen API', () => { - describe('Props', () => { - describe('Category: behavior', () => { - describe('(prop): fullscreen', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): no-route-fullscreen-exit', () => { - it.skip(' ', () => { - // - }) - }) - }) - }) - - describe('Methods', () => { - describe('(method): toggleFullscreen', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): setFullscreen', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(method): exitFullscreen', () => { - it.skip(' ', () => { - // - }) - }) - }) -}) diff --git a/ui/src/composables/__tests__/use-model-toggle.cy.js b/ui/src/composables/__tests__/use-model-toggle.cy.js deleted file mode 100644 index 7dc701c39b5..00000000000 --- a/ui/src/composables/__tests__/use-model-toggle.cy.js +++ /dev/null @@ -1,350 +0,0 @@ -/* eslint-disable no-unused-expressions */ -import { vModelAdapter } from '@quasar/quasar-app-extension-testing-e2e-cypress' -import { ref } from 'vue' -import WrapperOne from '../../../components/menu/__tests__/WrapperOne.vue' -import WrapperTwo from '../../../components/menu/__tests__/WrapperTwo.vue' - -describe('use-model-toggle API', () => { - describe('Props', () => { - describe('Category: model', () => { - describe('(prop): model-value', () => { - it('should open the dialog when modifying the model-value', () => { - const model = ref(false) - cy.mount(WrapperOne, { - props: { - ...vModelAdapter(model) - } - }) - cy.dataCy('wrapper') - cy.dataCy('menu') - .should('not.exist') - .then(() => { - model.value = true - cy.dataCy('menu') - .should('exist') - }) - }) - - it('should close the dialog when modifying the model-value', () => { - const model = ref(true) - cy.mount(WrapperOne, { - props: { - ...vModelAdapter(model) - } - }) - cy.dataCy('wrapper') - cy.dataCy('menu') - .should('exist') - .then(() => { - model.value = false - cy.dataCy('menu') - .should('not.exist') - }) - }) - }) - }) - }) - - describe('Events', () => { - describe('(event): update:model-value', () => { - it('should emit @update:model-value event when state changes', () => { - const fn = cy.stub() - cy.mount(WrapperOne, { - props: { - 'onUpdate:modelValue': fn - } - }) - - expect(fn).not.to.be.called - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .should('exist') - .then(() => { - expect(fn).to.be.called - }) - }) - }) - - describe('(event): show', () => { - it('should emit @show event when menu is triggered by parent', () => { - const fn = cy.stub() - cy.mount(WrapperOne, { - props: { - onShow: fn - } - }) - - expect(fn).not.to.be.called - cy.dataCy('wrapper') - .click() - // eslint-disable-next-line cypress/no-unnecessary-waiting - cy.dataCy('menu') - .should('exist') - .wait(300) // Await menu animation - .then(() => { - expect(fn).to.be.called - }) - }) - - it('should emit @show event when component is triggered with the show() method', () => { - const fn = cy.stub() - cy.mount(WrapperOne, { - props: { - onShow: fn - } - }) - - expect(fn).not.to.be.called - cy.dataCy('wrapper') - cy.dataCy('method-show') - .click({ force: true }) // Element is hidden to prevent clogging the window - // eslint-disable-next-line cypress/no-unnecessary-waiting - cy.dataCy('menu') - .should('exist') - .wait(300) // Await menu animation - .then(() => { - expect(fn).to.be.called - }) - }) - }) - - describe('(event): before-show', () => { - it('should emit @before-show event when menu is triggered by parent', () => { - const fn = cy.stub() - cy.mount(WrapperOne, { - props: { - onBeforeShow: fn - } - }) - - expect(fn).not.to.be.called - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .should('exist') - .then(() => { - expect(fn).to.be.called - }) - }) - - it('should emit @before-show event when component is triggered with the show() method', () => { - const fn = cy.stub() - cy.mount(WrapperOne, { - props: { - onBeforeShow: fn - } - }) - - expect(fn).not.to.be.called - cy.dataCy('wrapper') - cy.dataCy('method-show') - .click({ force: true }) // Element is hidden to prevent clogging the window - cy.dataCy('menu') - .should('exist') - .then(() => { - expect(fn).to.be.called - }) - }) - }) - - describe('(event): hide', () => { - it('should emit @hide event when menu is triggered by parent', () => { - const fn = cy.stub() - cy.mount(WrapperOne, { - props: { - onHide: fn, - // After the dialog has been closed, the hide event is fired only after transitionDuration. - // The default value for transitionDuration is 300ms. Hence, we cannot depend on the dialog closing - // to conclude that the event has been fired. So let's set it to 0 so that it fires immediately. - transitionDuration: 0 - } - }) - - expect(fn).not.to.be.called - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .should('exist') - .then(() => { - expect(fn).not.to.be.called - }) - - // We are clicking x distance from the left to ensure it always clicks on the body - // For some reason it some times does not work without it - cy.get('body') - .click(499, 0) - cy.dataCy('menu') - .should('not.exist') - .then(() => { - expect(fn).to.be.called - }) - }) - - it('should emit @hide event when component is triggered with the hide() method', () => { - const fn = cy.stub() - cy.mount(WrapperOne, { - props: { - onHide: fn, - // After the dialog has been closed, the hide event is fired only after transitionDuration. - // The default value for transitionDuration is 300ms. Hence, we cannot depend on the dialog closing - // to conclude that the event has been fired. So let's set it to 0 so that it fires immediately. - transitionDuration: 0 - } - }) - - expect(fn).not.to.be.called - cy.dataCy('wrapper') - cy.dataCy('method-show') - .click({ force: true }) // Element is hidden to prevent clogging the window - cy.dataCy('menu') - .should('exist') - .then(() => { - expect(fn).not.to.be.called - }) - cy.dataCy('method-hide') - .click({ force: true }) - cy.dataCy('menu') - .should('not.exist') // Element is hidden to prevent clogging the window - .then(() => { - expect(fn).to.be.called - }) - }) - - it('should emit @hide event after transition duration', () => { - const fn = cy.stub() - cy.mount(WrapperOne, { - props: { - onHide: fn, - transitionDuration: 500 - } - }) - - expect(fn).not.to.be.called - cy.dataCy('method-show') - .click({ force: true }) - - cy.dataCy('wrapper') - cy.dataCy('method-hide') - .click({ force: true }) - - // eslint-disable-next-line cypress/no-unnecessary-waiting - cy.dataCy('menu') - .wait(300) - .then(() => { - expect(fn).not.to.be.called - }) - .wait(500) - .then(() => { - expect(fn).to.be.called - }) - }) - }) - - describe('(event): before-hide', () => { - it('should emit @before-hide event when menu is triggered by parent', () => { - const fn = cy.stub() - cy.mount(WrapperOne, { - props: { - onBeforeHide: fn - } - }) - - expect(fn).not.to.be.called - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .should('exist') - .then(() => { - expect(fn).not.to.be.called - }) - cy.get('body') - .click(499, 0) - cy.get('body') - .then(() => { - expect(fn).to.be.called - }) - cy.dataCy('menu') - .should('not.exist') - }) - - it('should emit @before-hide event when component is triggered with the show() method', () => { - const fn = cy.stub() - cy.mount(WrapperOne, { - props: { - onBeforeHide: fn - } - }) - - expect(fn).not.to.be.called - cy.dataCy('wrapper') - cy.dataCy('method-show') - .click({ force: true }) // Element is hidden to prevent clogging the window - cy.dataCy('menu') - .should('exist') - .then(() => { - expect(fn).not.to.be.called - }) - cy.dataCy('method-hide') - .click({ force: true }) - cy.dataCy('method-hide') - .then(() => { - expect(fn).to.be.called - }) - cy.dataCy('menu') - .should('not.exist') - }) - }) - }) - - describe('Methods', () => { - describe('(method): show', () => { - it('should trigger the menu to show', () => { - cy.mount(WrapperOne) - - cy.dataCy('wrapper') - cy.dataCy('method-show') - .click({ force: true }) - cy.dataCy('menu') - .should('exist') - }) - }) - - describe('(method): hide', () => { - it('should trigger the menu to hide', () => { - cy.mount(WrapperOne) - - cy.dataCy('wrapper') - .click() - cy.dataCy('menu') - .should('exist') - cy.dataCy('method-hide') - .click({ force: true }) - cy.dataCy('menu') - .should('not.exist') - }) - }) - - describe('(method): toggle', () => { - it('should toggle the menu', () => { - cy.mount(WrapperTwo) - - cy.dataCy('wrapper') - .then(() => { - // Need to call method from wrapper - // Click a button closes the menu - Cypress.vueWrapper.vm.toggle() - }) - cy.dataCy('menu') - .should('exist') - .then(() => { - // Need to call method from wrapper - // Click a button closes the menu - Cypress.vueWrapper.vm.toggle() - }) - cy.dataCy('menu') - .should('not.exist') - }) - }) - }) -}) diff --git a/ui/src/composables/__tests__/use-portal.cy.js b/ui/src/composables/__tests__/use-portal.cy.js deleted file mode 100644 index f5160569561..00000000000 --- a/ui/src/composables/__tests__/use-portal.cy.js +++ /dev/null @@ -1,4 +0,0 @@ -describe('use-portal API', () => { - describe('Props', () => { - }) -}) diff --git a/ui/src/composables/__tests__/use-router-link.cy.js b/ui/src/composables/__tests__/use-router-link.cy.js deleted file mode 100644 index 12a052fae5a..00000000000 --- a/ui/src/composables/__tests__/use-router-link.cy.js +++ /dev/null @@ -1,55 +0,0 @@ -describe('use-router-link API', () => { - describe('Props', () => { - describe('Category: navigation', () => { - describe('(prop): to', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): exact', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): replace', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): active-class', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): exact-active-class', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): href', () => { - it.skip(' ', () => { - // - }) - }) - - describe('(prop): target', () => { - it.skip(' ', () => { - // - }) - }) - }) - - describe('Category: state', () => { - describe('(prop): disable', () => { - it.skip(' ', () => { - // - }) - }) - }) - }) -}) diff --git a/ui/src/composables/__tests__/use-validate.cy.js b/ui/src/composables/__tests__/use-validate.cy.js deleted file mode 100644 index 8c4bbef979e..00000000000 --- a/ui/src/composables/__tests__/use-validate.cy.js +++ /dev/null @@ -1,257 +0,0 @@ -import FieldWrapper from './FieldWrapper.vue' -import Icons from '../../../../icon-set/material-icons.mjs' -import { ref } from 'vue' -import { vModelAdapter } from '@quasar/quasar-app-extension-testing-e2e-cypress' - -function mountQFieldWrapper (options) { - return cy.mount(FieldWrapper, options) -} - -function getHostElement () { - return cy.get('.select-root') -} - -describe('use-validate API', () => { - describe('Props', () => { - describe('Category: behavior', () => { - describe('(prop): error', () => { - it('should mark the field as having an error', () => { - mountQFieldWrapper() - getHostElement().should('not.have.class', 'q-field--error') - - getHostElement().then(() => { - Cypress.vueWrapper.setProps({ error: true }) - }) - - getHostElement().should('have.class', 'q-field--error') - }) - }) - - describe('(prop): rules', () => { - it('should validate value using custom validation logic', () => { - const errorMessage = 'Selected value should be greater than 10 characters' - const model = ref('Option 1') - const options = [ 'Option 1', 'Option 2' ] - - mountQFieldWrapper({ - props: { - ...vModelAdapter(model), - options, - rules: [ val => val.length > 10 || errorMessage ] - } - }) - - getHostElement().then(() => { - model.value = 'Option 2' - }) - getHostElement().get('.q-field__messages').should('contain.text', errorMessage) - }) - - it('should validate email using inbuilt validation logic', () => { - const errorMessage = 'Enter a valid email address' - const model = ref('Option 1') - const options = [ 'Option 1', 'Option 2' ] - mountQFieldWrapper({ - props: { - ...vModelAdapter(model), - options, - rules: [ (val, rules) => rules.email(val) || errorMessage ] - } - }) - getHostElement().then(() => { - model.value = 'Option 2' - }) - getHostElement().get('.q-field__messages').should('contain.text', errorMessage) - }) - }) - - describe('(prop): reactive-rules', () => { - it('should trigger validation when there\'s a change of rules', () => { - const errorMessage = 'Error message' - const model = ref('Option 1') - const options = [ 'Option 1', 'Option 2' ] - mountQFieldWrapper({ - props: { - ...vModelAdapter(model), - options - } - }) - - getHostElement().then(() => { - Cypress.vueWrapper.setProps({ rules: [ (value) => value.length < 3 || errorMessage ] }) - - Cypress.vueWrapper.vm.focusMethod() - Cypress.vueWrapper.vm.blur() - }) - getHostElement().get('.q-field__messages').should('not.exist') - - getHostElement().then(() => { - Cypress.vueWrapper.setProps({ reactiveRules: true, rules: [ (value, rules) => rules.email(value) || errorMessage ] }) - }) - getHostElement().get('.q-field__messages').should('contain.text', errorMessage) - }) - }) - - describe('(prop): lazy-rules', () => { - it('should validate the input only when component loses focus', () => { - const errorMessage = 'Use a max 3 of characters' - const model = ref('Option 1') - const options = [ 'Option 1', 'Option 2' ] - mountQFieldWrapper({ - props: { - ...vModelAdapter(model), - options, - lazyRules: true, - rules: [ () => errorMessage ] - } - }) - - getHostElement().select('Option 2') - getHostElement().get('.q-field__messages').should('not.contain', errorMessage) - - getHostElement().then(() => { - Cypress.vueWrapper.vm.blur() - getHostElement().get('.q-field__messages').should('contain.text', errorMessage) - }) - }) - }) - }) - - describe('Category: content', () => { - describe('(prop): error-message', () => { - it('should show an error-message when error is true', () => { - const message = 'Please select something' - cy.mount(FieldWrapper, { - props: { - error: true, - errorMessage: message - } - }) - cy.get('.select-root') - .should('include.text', message) - }) - - it('should not show an error-message when error is false', () => { - const message = 'Please select something' - cy.mount(FieldWrapper, { - props: { - error: false, - errorMessage: message - } - }) - cy.get('.select-root') - .should('not.include.text', message) - }) - }) - - describe('(prop): no-error-icon', () => { - it('should not show an error icon when error is true', () => { - cy.mount(FieldWrapper, { - props: { - error: true, - noErrorIcon: true - } - }) - cy.get('.select-root') - .get(`i:contains(${ Icons.field.error })`) - .should('not.exist') - }) - - it('should show an error icon when error is true an no-error-icon is false', () => { - cy.mount(FieldWrapper, { - props: { - error: true, - noErrorIcon: false - } - }) - cy.get('.select-root') - .get(`i:contains(${ Icons.field.error })`) - .should('exist') - }) - }) - }) - - describe('Category: model', () => { - describe('(prop): model-value', () => { - it('should correctly update the model value', () => { - const model = ref() - const options = [ 'Option 1', 'Option 2' ] - mountQFieldWrapper({ - props: { - ...vModelAdapter(model), - options - } - }) - getHostElement().get('input').should('not.have.value', options[ 0 ]) - - getHostElement().then(() => { - model.value = options[ 0 ] - }) - - getHostElement().get('input').should('have.value', options[ 0 ]) - }) - }) - }) - }) - - describe('Methods', () => { - describe('(method): resetValidation', () => { - it('should reset validation', () => { - const errorMessage = 'Use a max 3 of characters' - const model = ref('Option 1') - const options = [ 'Option 1', 'Option 2' ] - mountQFieldWrapper({ - props: { - ...vModelAdapter(model), - options, - rules: [ val => val.length <= 3 || errorMessage ], - lazyRules: 'ondemand' - } - }) - - getHostElement().then(() => { - model.value = 'Option 2' - }) - getHostElement().get('.q-field__messages').should('not.contain', errorMessage) - - getHostElement().then(() => { - Cypress.vueWrapper.vm.validate() - getHostElement().get('.q-field__messages').should('contain', errorMessage) - }) - - getHostElement().then(() => { - Cypress.vueWrapper.vm.resetValidation() - getHostElement().get('.q-field__messages').should('not.contain', errorMessage) - }) - }) - }) - - describe('(method): validate', () => { - it('should validate the input only when component\'s validate() method is called', () => { - const errorMessage = 'Use a max 3 of characters' - const model = ref('Option 1') - const options = [ 'Option 1', 'Option 2' ] - mountQFieldWrapper({ - props: { - ...vModelAdapter(model), - options, - rules: [ val => val.length <= 3 || errorMessage ], - lazyRules: 'ondemand' - } - }) - - getHostElement().then(() => { - model.value = 'Option 2' - }) - getHostElement().get('.q-field__messages').should('not.contain', errorMessage) - - getHostElement().get('input').then(() => { - Cypress.vueWrapper.vm.blur() - getHostElement().get('.q-field__messages').should('not.contain', errorMessage) - Cypress.vueWrapper.vm.validate() - getHostElement().get('.q-field__messages').should('contain', errorMessage) - }) - }) - }) - }) -}) From 131637034919a6ab699978f820282e416a8f1b6e Mon Sep 17 00:00:00 2001 From: Jean Claveau <1556489+jclaveau@users.noreply.github.com> Date: Thu, 27 Feb 2025 14:38:45 +0100 Subject: [PATCH 12/32] feat(QBtnDropdown): no-(re)focus properties passed to the QMenu of QBtnDropdown (#17802) * feat(ui): no-focus / no-refocus properties passed to the dropdown's QMenu * Update QBtnDropdown.json --------- Co-authored-by: Razvan Stoenescu --- ui/src/components/btn-dropdown/QBtnDropdown.js | 4 ++++ ui/src/components/btn-dropdown/QBtnDropdown.json | 14 ++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/ui/src/components/btn-dropdown/QBtnDropdown.js b/ui/src/components/btn-dropdown/QBtnDropdown.js index 8ef7dc19b13..ce01e08e282 100644 --- a/ui/src/components/btn-dropdown/QBtnDropdown.js +++ b/ui/src/components/btn-dropdown/QBtnDropdown.js @@ -43,6 +43,8 @@ export default createComponent({ persistent: Boolean, noRouteDismiss: Boolean, autoClose: Boolean, + noRefocus: Boolean, + noFocus: Boolean, menuAnchor: { type: String, @@ -177,6 +179,8 @@ export default createComponent({ persistent: props.persistent, noRouteDismiss: props.noRouteDismiss, autoClose: props.autoClose, + noFocus: props.noFocus, + noRefocus: props.noRefocus, anchor: props.menuAnchor, self: props.menuSelf, offset: props.menuOffset, diff --git a/ui/src/components/btn-dropdown/QBtnDropdown.json b/ui/src/components/btn-dropdown/QBtnDropdown.json index 91d25aa3118..4539ed289c7 100644 --- a/ui/src/components/btn-dropdown/QBtnDropdown.json +++ b/ui/src/components/btn-dropdown/QBtnDropdown.json @@ -85,6 +85,20 @@ "category": "behavior" }, + "no-refocus": { + "type": "Boolean", + "desc": "(Accessibility) When the dropdown gets hidden, do not refocus on the DOM element that previously had focus", + "category": "behavior", + "addedIn": "v2.18" + }, + + "no-focus": { + "type": "Boolean", + "desc": "(Accessibility) When the dropdown gets shown, do not switch focus on it", + "category": "behavior", + "addedIn": "v2.18" + }, + "menu-anchor": { "type": "String", "desc": "Two values setting the starting position or anchor point of the menu relative to its target", From 9d0ae55bc7bedb0de89a074f85efea09c5d95319 Mon Sep 17 00:00:00 2001 From: Razvan Stoenescu Date: Thu, 27 Feb 2025 15:42:26 +0200 Subject: [PATCH 13/32] feat(QBtnDropdown): add noEscDismiss prop (#17784) --- ui/src/components/btn-dropdown/QBtnDropdown.js | 2 ++ ui/src/components/btn-dropdown/QBtnDropdown.json | 7 +++++++ ui/src/components/menu/QMenu.json | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/ui/src/components/btn-dropdown/QBtnDropdown.js b/ui/src/components/btn-dropdown/QBtnDropdown.js index ce01e08e282..4fce18e5909 100644 --- a/ui/src/components/btn-dropdown/QBtnDropdown.js +++ b/ui/src/components/btn-dropdown/QBtnDropdown.js @@ -41,6 +41,7 @@ export default createComponent({ cover: Boolean, persistent: Boolean, + noEscDismiss: Boolean, noRouteDismiss: Boolean, autoClose: Boolean, noRefocus: Boolean, @@ -177,6 +178,7 @@ export default createComponent({ cover: props.cover, fit: true, persistent: props.persistent, + noEscDismiss: props.noEscDismiss, noRouteDismiss: props.noRouteDismiss, autoClose: props.autoClose, noFocus: props.noFocus, diff --git a/ui/src/components/btn-dropdown/QBtnDropdown.json b/ui/src/components/btn-dropdown/QBtnDropdown.json index 4539ed289c7..b9be32d0376 100644 --- a/ui/src/components/btn-dropdown/QBtnDropdown.json +++ b/ui/src/components/btn-dropdown/QBtnDropdown.json @@ -73,6 +73,13 @@ "category": "behavior" }, + "no-esc-dismiss": { + "type": "Boolean", + "desc": "User cannot dismiss the popup by hitting ESC key; No need to set it if 'persistent' prop is also set", + "category": "behavior", + "addedIn": "v2.18" + }, + "no-route-dismiss": { "type": "Boolean", "desc": "Changing route app won't dismiss the popup; No need to set it if 'persistent' prop is also set", diff --git a/ui/src/components/menu/QMenu.json b/ui/src/components/menu/QMenu.json index 523b4ec26d4..86ac43c8268 100644 --- a/ui/src/components/menu/QMenu.json +++ b/ui/src/components/menu/QMenu.json @@ -71,7 +71,7 @@ "type": "Boolean", "desc": "User cannot dismiss the popup by hitting ESC key; No need to set it if 'persistent' prop is also set", "category": "behavior", - "addedIn": "v2.18.0" + "addedIn": "v2.18" }, "no-route-dismiss": { From 07e8af5554a834c9eefed0fa2bb254bf6332061f Mon Sep 17 00:00:00 2001 From: Minktong <98688524+Minktong@users.noreply.github.com> Date: Thu, 27 Feb 2025 17:15:48 +0330 Subject: [PATCH 14/32] feat(ui/lang): Add support for Urdu language from Pakistan (#17771) --- ui/lang/ur-PK.js | 116 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 ui/lang/ur-PK.js diff --git a/ui/lang/ur-PK.js b/ui/lang/ur-PK.js new file mode 100644 index 00000000000..fc360afbb52 --- /dev/null +++ b/ui/lang/ur-PK.js @@ -0,0 +1,116 @@ +/* + * Urdu (Pakistan) Language Pack for Quasar Framework + * ISO code: ur-PK + * Author: [Your Name or Team] + */ + +const days = 'اتوار_پیر_منگل_بدھ_جمعرات_جمعہ_ہفتہ'.split('_') +const daysShort = 'ات_پیر_منگ_بدھ_جمعر_جمعہ_ہف'.split('_') +// You can refine abbreviations if needed; e.g. +// 'ات_پیر_من_بد_جم_جمع_ہف' or any shorter versions that make sense. + +const months = 'جنوری_فروری_مارچ_اپریل_مئی_جون_جولائی_اگست_ستمبر_اکتوبر_نومبر_دسمبر'.split('_') +// Short forms below can also be adjusted if you prefer more concise abbreviations. +const monthsShort = 'جن._فر._مار._اپر._مئی_جون_جول._اگ._ستم._اکت._نوم._دسم.'.split('_') + +export default { + isoName: 'ur-PK', + nativeName: 'اردو (پاکستان)', + rtl: true, + + label: { + clear: 'صاف کریں', + ok: 'ٹھیک ہے', + cancel: 'منسوخ کریں', + close: 'بند کریں', + set: 'سیٹ کریں', + select: 'منتخب کریں', + reset: 'ری سیٹ کریں', + remove: 'ہٹا دیں', + update: 'اپ ڈیٹ کریں', + create: 'بنائیں', + search: 'تلاش کریں', + filter: 'فلٹر', + refresh: 'تازہ کریں', + expand: label => (label ? `"${ label }" کو وسیع کریں` : 'وسیع کریں'), + collapse: label => (label ? `"${ label }" کو سکیڑیں` : 'سکیڑیں') + }, + + date: { + days, + daysShort, + months, + monthsShort, + // Shown on the Quasar date picker’s title bar; you can adjust the format if needed + headerTitle: (date, model) => + `${ days[ date.getDay() ] }، ${ model.day } ${ months[ model.month - 1 ] }`, + firstDayOfWeek: 0, // 0-6; 0 = اتوار, 1 = پیر, etc. + format24h: true, + pluralDay: 'دن' + }, + + table: { + noData: 'کوئی ڈیٹا دستیاب نہیں', + noResults: 'کوئی نتیجہ نہیں ملا', + loading: 'لوڈ ہو رہا ہے...', + selectedRecords: rows => + rows === 0 + ? 'کوئی ریکارڈ منتخب نہیں ہوا' + : `${ rows } ریکارڈ منتخب ہوئے`, + recordsPerPage: 'ریکارڈز فی صفحہ:', + allRows: 'سب', + pagination: (start, end, total) => `${ start }-${ end } / ${ total }`, + columns: 'کالم' + }, + + editor: { + url: 'یو آر ایل', + bold: 'موٹا', + italic: 'ترچھا', + strikethrough: 'کاٹا ہوا', + underline: 'انڈر لائن', + unorderedList: 'غیر ترتیب شدہ فہرست', + orderedList: 'ترتیب شدہ فہرست', + subscript: 'ذیلی', + superscript: 'بالائی', + hyperlink: 'ہائپر لنک', + toggleFullscreen: 'مکمل اسکرین', + quote: 'اقتباس', + left: 'بائیں جانب', + center: 'درمیان میں', + right: 'دائیں جانب', + justify: 'برابر پھیلائیں', + print: 'پرنٹ کریں', + outdent: 'انڈینٹ کم کریں', + indent: 'انڈینٹ زیادہ کریں', + removeFormat: 'فارمیٹنگ ختم کریں', + formatting: 'فارمیٹنگ', + fontSize: 'فونٹ سائز', + align: 'سیدھ کریں', + hr: 'افقی لکیر داخل کریں', + undo: 'پچھلا قدم', + redo: 'دوبارہ کریں', + heading1: 'سرخی 1', + heading2: 'سرخی 2', + heading3: 'سرخی 3', + heading4: 'سرخی 4', + heading5: 'سرخی 5', + heading6: 'سرخی 6', + paragraph: 'پیراگراف', + code: 'کوڈ', + size1: 'بہت چھوٹا', + size2: 'چھوٹا', + size3: 'معمولی', + size4: 'درمیانہ', + size5: 'بڑا', + size6: 'بہت بڑا', + size7: 'سب سے بڑا', + defaultFont: 'طے شدہ فونٹ', + viewSource: 'ماخذ دیکھیں' + }, + + tree: { + noNodes: 'کوئی نوڈ دستیاب نہیں', + noResults: 'کوئی نوڈ نہیں ملا' + } +} From 8c1683a93628ac37026669c05163ee15f59e0296 Mon Sep 17 00:00:00 2001 From: Razvan Stoenescu Date: Thu, 27 Feb 2025 15:48:12 +0200 Subject: [PATCH 15/32] feat(ui/lang): add pagination translation (was added after previous PR) #17771 --- ui/lang/ur-PK.js | 233 ++++++++++++++++++++++++----------------------- 1 file changed, 117 insertions(+), 116 deletions(-) diff --git a/ui/lang/ur-PK.js b/ui/lang/ur-PK.js index fc360afbb52..fb65f51aaf4 100644 --- a/ui/lang/ur-PK.js +++ b/ui/lang/ur-PK.js @@ -1,116 +1,117 @@ -/* - * Urdu (Pakistan) Language Pack for Quasar Framework - * ISO code: ur-PK - * Author: [Your Name or Team] - */ - -const days = 'اتوار_پیر_منگل_بدھ_جمعرات_جمعہ_ہفتہ'.split('_') -const daysShort = 'ات_پیر_منگ_بدھ_جمعر_جمعہ_ہف'.split('_') -// You can refine abbreviations if needed; e.g. -// 'ات_پیر_من_بد_جم_جمع_ہف' or any shorter versions that make sense. - -const months = 'جنوری_فروری_مارچ_اپریل_مئی_جون_جولائی_اگست_ستمبر_اکتوبر_نومبر_دسمبر'.split('_') -// Short forms below can also be adjusted if you prefer more concise abbreviations. -const monthsShort = 'جن._فر._مار._اپر._مئی_جون_جول._اگ._ستم._اکت._نوم._دسم.'.split('_') - -export default { - isoName: 'ur-PK', - nativeName: 'اردو (پاکستان)', - rtl: true, - - label: { - clear: 'صاف کریں', - ok: 'ٹھیک ہے', - cancel: 'منسوخ کریں', - close: 'بند کریں', - set: 'سیٹ کریں', - select: 'منتخب کریں', - reset: 'ری سیٹ کریں', - remove: 'ہٹا دیں', - update: 'اپ ڈیٹ کریں', - create: 'بنائیں', - search: 'تلاش کریں', - filter: 'فلٹر', - refresh: 'تازہ کریں', - expand: label => (label ? `"${ label }" کو وسیع کریں` : 'وسیع کریں'), - collapse: label => (label ? `"${ label }" کو سکیڑیں` : 'سکیڑیں') - }, - - date: { - days, - daysShort, - months, - monthsShort, - // Shown on the Quasar date picker’s title bar; you can adjust the format if needed - headerTitle: (date, model) => - `${ days[ date.getDay() ] }، ${ model.day } ${ months[ model.month - 1 ] }`, - firstDayOfWeek: 0, // 0-6; 0 = اتوار, 1 = پیر, etc. - format24h: true, - pluralDay: 'دن' - }, - - table: { - noData: 'کوئی ڈیٹا دستیاب نہیں', - noResults: 'کوئی نتیجہ نہیں ملا', - loading: 'لوڈ ہو رہا ہے...', - selectedRecords: rows => - rows === 0 - ? 'کوئی ریکارڈ منتخب نہیں ہوا' - : `${ rows } ریکارڈ منتخب ہوئے`, - recordsPerPage: 'ریکارڈز فی صفحہ:', - allRows: 'سب', - pagination: (start, end, total) => `${ start }-${ end } / ${ total }`, - columns: 'کالم' - }, - - editor: { - url: 'یو آر ایل', - bold: 'موٹا', - italic: 'ترچھا', - strikethrough: 'کاٹا ہوا', - underline: 'انڈر لائن', - unorderedList: 'غیر ترتیب شدہ فہرست', - orderedList: 'ترتیب شدہ فہرست', - subscript: 'ذیلی', - superscript: 'بالائی', - hyperlink: 'ہائپر لنک', - toggleFullscreen: 'مکمل اسکرین', - quote: 'اقتباس', - left: 'بائیں جانب', - center: 'درمیان میں', - right: 'دائیں جانب', - justify: 'برابر پھیلائیں', - print: 'پرنٹ کریں', - outdent: 'انڈینٹ کم کریں', - indent: 'انڈینٹ زیادہ کریں', - removeFormat: 'فارمیٹنگ ختم کریں', - formatting: 'فارمیٹنگ', - fontSize: 'فونٹ سائز', - align: 'سیدھ کریں', - hr: 'افقی لکیر داخل کریں', - undo: 'پچھلا قدم', - redo: 'دوبارہ کریں', - heading1: 'سرخی 1', - heading2: 'سرخی 2', - heading3: 'سرخی 3', - heading4: 'سرخی 4', - heading5: 'سرخی 5', - heading6: 'سرخی 6', - paragraph: 'پیراگراف', - code: 'کوڈ', - size1: 'بہت چھوٹا', - size2: 'چھوٹا', - size3: 'معمولی', - size4: 'درمیانہ', - size5: 'بڑا', - size6: 'بہت بڑا', - size7: 'سب سے بڑا', - defaultFont: 'طے شدہ فونٹ', - viewSource: 'ماخذ دیکھیں' - }, - - tree: { - noNodes: 'کوئی نوڈ دستیاب نہیں', - noResults: 'کوئی نوڈ نہیں ملا' - } -} +/* + * Urdu (Pakistan) Language Pack for Quasar Framework + * ISO code: ur-PK + * Author: [Your Name or Team] + */ + +const days = 'اتوار_پیر_منگل_بدھ_جمعرات_جمعہ_ہفتہ'.split('_') +const daysShort = 'ات_پیر_منگ_بدھ_جمعر_جمعہ_ہف'.split('_') +// You can refine abbreviations if needed; e.g. +// 'ات_پیر_من_بد_جم_جمع_ہف' or any shorter versions that make sense. + +const months = 'جنوری_فروری_مارچ_اپریل_مئی_جون_جولائی_اگست_ستمبر_اکتوبر_نومبر_دسمبر'.split('_') +// Short forms below can also be adjusted if you prefer more concise abbreviations. +const monthsShort = 'جن._فر._مار._اپر._مئی_جون_جول._اگ._ستم._اکت._نوم._دسم.'.split('_') + +export default { + isoName: 'ur-PK', + nativeName: 'اردو (پاکستان)', + rtl: true, + label: { + clear: 'صاف کریں', + ok: 'ٹھیک ہے', + cancel: 'منسوخ کریں', + close: 'بند کریں', + set: 'سیٹ کریں', + select: 'منتخب کریں', + reset: 'ری سیٹ کریں', + remove: 'ہٹا دیں', + update: 'اپ ڈیٹ کریں', + create: 'بنائیں', + search: 'تلاش کریں', + filter: 'فلٹر', + refresh: 'تازہ کریں', + expand: label => (label ? `"${ label }" کو وسیع کریں` : 'وسیع کریں'), + collapse: label => (label ? `"${ label }" کو سکیڑیں` : 'سکیڑیں') + }, + date: { + days, + daysShort, + months, + monthsShort, + // Shown on the Quasar date picker’s title bar; you can adjust the format if needed + headerTitle: (date, model) => + `${ days[ date.getDay() ] }، ${ model.day } ${ months[ model.month - 1 ] }`, + firstDayOfWeek: 0, // 0-6; 0 = اتوار, 1 = پیر, etc. + format24h: true, + pluralDay: 'دن' + }, + table: { + noData: 'کوئی ڈیٹا دستیاب نہیں', + noResults: 'کوئی نتیجہ نہیں ملا', + loading: 'لوڈ ہو رہا ہے...', + selectedRecords: rows => + rows === 0 + ? 'کوئی ریکارڈ منتخب نہیں ہوا' + : `${ rows } ریکارڈ منتخب ہوئے`, + recordsPerPage: 'ریکارڈز فی صفحہ:', + allRows: 'سب', + pagination: (start, end, total) => `${ start }-${ end } / ${ total }`, + columns: 'کالم' + }, + pagination: { + first: 'لومړی مخ', + prev: 'مخکینۍ پاڼه', + next: 'بل مخ', + last: 'وروستۍ پاڼه' + }, + editor: { + url: 'یو آر ایل', + bold: 'موٹا', + italic: 'ترچھا', + strikethrough: 'کاٹا ہوا', + underline: 'انڈر لائن', + unorderedList: 'غیر ترتیب شدہ فہرست', + orderedList: 'ترتیب شدہ فہرست', + subscript: 'ذیلی', + superscript: 'بالائی', + hyperlink: 'ہائپر لنک', + toggleFullscreen: 'مکمل اسکرین', + quote: 'اقتباس', + left: 'بائیں جانب', + center: 'درمیان میں', + right: 'دائیں جانب', + justify: 'برابر پھیلائیں', + print: 'پرنٹ کریں', + outdent: 'انڈینٹ کم کریں', + indent: 'انڈینٹ زیادہ کریں', + removeFormat: 'فارمیٹنگ ختم کریں', + formatting: 'فارمیٹنگ', + fontSize: 'فونٹ سائز', + align: 'سیدھ کریں', + hr: 'افقی لکیر داخل کریں', + undo: 'پچھلا قدم', + redo: 'دوبارہ کریں', + heading1: 'سرخی 1', + heading2: 'سرخی 2', + heading3: 'سرخی 3', + heading4: 'سرخی 4', + heading5: 'سرخی 5', + heading6: 'سرخی 6', + paragraph: 'پیراگراف', + code: 'کوڈ', + size1: 'بہت چھوٹا', + size2: 'چھوٹا', + size3: 'معمولی', + size4: 'درمیانہ', + size5: 'بڑا', + size6: 'بہت بڑا', + size7: 'سب سے بڑا', + defaultFont: 'طے شدہ فونٹ', + viewSource: 'ماخذ دیکھیں' + }, + tree: { + noNodes: 'کوئی نوڈ دستیاب نہیں', + noResults: 'کوئی نوڈ نہیں ملا' + } +} From e0f22206dfedc11f14642cceb2fee242fc7ef521 Mon Sep 17 00:00:00 2001 From: Razvan Stoenescu Date: Thu, 27 Feb 2025 15:49:06 +0200 Subject: [PATCH 16/32] chore(ui): update lang index --- ui/lang/index.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/lang/index.json b/ui/lang/index.json index 64e052eed49..8b787b2b8ed 100644 --- a/ui/lang/index.json +++ b/ui/lang/index.json @@ -1 +1 @@ -[{"isoName":"ar-TN","nativeName":"العربية (تونس)"},{"isoName":"ar","nativeName":"العربية"},{"isoName":"az-Latn","nativeName":"Azerbaijani (latin)"},{"isoName":"bg","nativeName":"български език"},{"isoName":"bn","nativeName":"বাংলা"},{"isoName":"bs-BA","nativeName":"Bosanski jezik"},{"isoName":"ca","nativeName":"Català"},{"isoName":"cs","nativeName":"Čeština"},{"isoName":"da","nativeName":"Dansk"},{"isoName":"de-CH","nativeName":"Deutsch (CH)"},{"isoName":"de-DE","nativeName":"Deutsch (DE)"},{"isoName":"de","nativeName":"Deutsch"},{"isoName":"el","nativeName":"ελληνικά"},{"isoName":"en-GB","nativeName":"English (UK)"},{"isoName":"en-US","nativeName":"English (US)"},{"isoName":"eo","nativeName":"Esperanto"},{"isoName":"es","nativeName":"Español"},{"isoName":"et","nativeName":"Eesti"},{"isoName":"eu","nativeName":"Euskara"},{"isoName":"fa-IR","nativeName":"فارسی"},{"isoName":"fa","nativeName":"فارسی"},{"isoName":"fi","nativeName":"Suomi"},{"isoName":"fr","nativeName":"Français"},{"isoName":"gn","nativeName":"Avañe'ẽ"},{"isoName":"he","nativeName":"עברית"},{"isoName":"hi","nativeName":"अमेरिकी अंग्रेज़ी"},{"isoName":"hr","nativeName":"Hrvatski jezik"},{"isoName":"hu","nativeName":"Magyar"},{"isoName":"id","nativeName":"Bahasa Indonesia"},{"isoName":"is","nativeName":"Íslenska"},{"isoName":"it","nativeName":"Italiano"},{"isoName":"ja","nativeName":"日本語 (にほんご)"},{"isoName":"kk","nativeName":"Қазақша"},{"isoName":"km","nativeName":"ខ្មែរ"},{"isoName":"ko-KR","nativeName":"한국어"},{"isoName":"kur-CKB","nativeName":"کوردی سۆرانی"},{"isoName":"lt","nativeName":"Lithuanian"},{"isoName":"lu","nativeName":"Kiluba"},{"isoName":"lv","nativeName":"Latviešu valoda"},{"isoName":"mk","nativeName":"Македонски"},{"isoName":"ml","nativeName":"മലയാളം"},{"isoName":"mm","nativeName":"မြန်မာ(ဗမာ)"},{"isoName":"ms-MY","nativeName":"Malaysia"},{"isoName":"ms","nativeName":"Bahasa Melayu"},{"isoName":"my","nativeName":"Malaysia"},{"isoName":"nb-NO","nativeName":"Norsk"},{"isoName":"nl","nativeName":"Nederlands"},{"isoName":"pl","nativeName":"Polski"},{"isoName":"pt-BR","nativeName":"Português (BR)"},{"isoName":"pt","nativeName":"Português"},{"isoName":"ro","nativeName":"Română"},{"isoName":"ru","nativeName":"русский"},{"isoName":"sk","nativeName":"Slovenčina"},{"isoName":"sl","nativeName":"Slovenski Jezik"},{"isoName":"sm","nativeName":"Fa'asāmoa"},{"isoName":"sr-CYR","nativeName":"српски језик"},{"isoName":"sr","nativeName":"srpski jezik"},{"isoName":"sv","nativeName":"Svenska"},{"isoName":"ta","nativeName":"தமிழ்"},{"isoName":"th","nativeName":"ไทย"},{"isoName":"tl","nativeName":"Tagalog"},{"isoName":"tr","nativeName":"Türkçe"},{"isoName":"ug","nativeName":"ئۇيغۇرچە"},{"isoName":"uk","nativeName":"Українська"},{"isoName":"uz-Cyrl","nativeName":"Ўзбекча (Кирил)"},{"isoName":"uz-Latn","nativeName":"O'zbekcha (Lotin)"},{"isoName":"vi","nativeName":"Tiếng Việt"},{"isoName":"zh-CN","nativeName":"中文(简体)"},{"isoName":"zh-TW","nativeName":"中文(繁體)"}] \ No newline at end of file +[{"isoName":"ar-TN","nativeName":"العربية (تونس)"},{"isoName":"ar","nativeName":"العربية"},{"isoName":"az-Latn","nativeName":"Azerbaijani (latin)"},{"isoName":"bg","nativeName":"български език"},{"isoName":"bn","nativeName":"বাংলা"},{"isoName":"bs-BA","nativeName":"Bosanski jezik"},{"isoName":"ca","nativeName":"Català"},{"isoName":"cs","nativeName":"Čeština"},{"isoName":"da","nativeName":"Dansk"},{"isoName":"de-CH","nativeName":"Deutsch (CH)"},{"isoName":"de-DE","nativeName":"Deutsch (DE)"},{"isoName":"de","nativeName":"Deutsch"},{"isoName":"el","nativeName":"ελληνικά"},{"isoName":"en-GB","nativeName":"English (UK)"},{"isoName":"en-US","nativeName":"English (US)"},{"isoName":"eo","nativeName":"Esperanto"},{"isoName":"es","nativeName":"Español"},{"isoName":"et","nativeName":"Eesti"},{"isoName":"eu","nativeName":"Euskara"},{"isoName":"fa-IR","nativeName":"فارسی"},{"isoName":"fa","nativeName":"فارسی"},{"isoName":"fi","nativeName":"Suomi"},{"isoName":"fr","nativeName":"Français"},{"isoName":"gn","nativeName":"Avañe'ẽ"},{"isoName":"he","nativeName":"עברית"},{"isoName":"hi","nativeName":"अमेरिकी अंग्रेज़ी"},{"isoName":"hr","nativeName":"Hrvatski jezik"},{"isoName":"hu","nativeName":"Magyar"},{"isoName":"id","nativeName":"Bahasa Indonesia"},{"isoName":"is","nativeName":"Íslenska"},{"isoName":"it","nativeName":"Italiano"},{"isoName":"ja","nativeName":"日本語 (にほんご)"},{"isoName":"kk","nativeName":"Қазақша"},{"isoName":"km","nativeName":"ខ្មែរ"},{"isoName":"ko-KR","nativeName":"한국어"},{"isoName":"kur-CKB","nativeName":"کوردی سۆرانی"},{"isoName":"lt","nativeName":"Lithuanian"},{"isoName":"lu","nativeName":"Kiluba"},{"isoName":"lv","nativeName":"Latviešu valoda"},{"isoName":"mk","nativeName":"Македонски"},{"isoName":"ml","nativeName":"മലയാളം"},{"isoName":"mm","nativeName":"မြန်မာ(ဗမာ)"},{"isoName":"ms-MY","nativeName":"Malaysia"},{"isoName":"ms","nativeName":"Bahasa Melayu"},{"isoName":"my","nativeName":"Malaysia"},{"isoName":"nb-NO","nativeName":"Norsk"},{"isoName":"nl","nativeName":"Nederlands"},{"isoName":"pl","nativeName":"Polski"},{"isoName":"pt-BR","nativeName":"Português (BR)"},{"isoName":"pt","nativeName":"Português"},{"isoName":"ro","nativeName":"Română"},{"isoName":"ru","nativeName":"русский"},{"isoName":"sk","nativeName":"Slovenčina"},{"isoName":"sl","nativeName":"Slovenski Jezik"},{"isoName":"sm","nativeName":"Fa'asāmoa"},{"isoName":"sr-CYR","nativeName":"српски језик"},{"isoName":"sr","nativeName":"srpski jezik"},{"isoName":"sv","nativeName":"Svenska"},{"isoName":"ta","nativeName":"தமிழ்"},{"isoName":"th","nativeName":"ไทย"},{"isoName":"tl","nativeName":"Tagalog"},{"isoName":"tr","nativeName":"Türkçe"},{"isoName":"ug","nativeName":"ئۇيغۇرچە"},{"isoName":"uk","nativeName":"Українська"},{"isoName":"ur-PK","nativeName":"اردو (پاکستان)"},{"isoName":"uz-Cyrl","nativeName":"Ўзбекча (Кирил)"},{"isoName":"uz-Latn","nativeName":"O'zbekcha (Lotin)"},{"isoName":"vi","nativeName":"Tiếng Việt"},{"isoName":"zh-CN","nativeName":"中文(简体)"},{"isoName":"zh-TW","nativeName":"中文(繁體)"}] \ No newline at end of file From ad45b46e8c15bca1677f58bd3ac1e0aa7e9eeb66 Mon Sep 17 00:00:00 2001 From: Razvan Stoenescu Date: Thu, 27 Feb 2025 16:15:02 +0200 Subject: [PATCH 17/32] fix(QPagination): aria-label on RTL #17148 --- ui/src/components/pagination/QPagination.js | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/ui/src/components/pagination/QPagination.js b/ui/src/components/pagination/QPagination.js index 6a75c839fef..bacf3fb0cfd 100644 --- a/ui/src/components/pagination/QPagination.js +++ b/ui/src/components/pagination/QPagination.js @@ -172,17 +172,6 @@ export default createComponent({ return $q.lang.rtl === true ? ico.reverse() : ico }) - const ariaLabels = computed(() => { - const labels = [ - $q.lang.pagination.first, - $q.lang.pagination.prev, - $q.lang.pagination.next, - $q.lang.pagination.last - ] - - return $q.lang.rtl === true ? labels.reverse() : labels - }) - const attrs = computed(() => ({ 'aria-disabled': props.disable === true ? 'true' : 'false', role: 'navigation' @@ -329,7 +318,7 @@ export default createComponent({ key: 'bls', disable: props.disable || props.modelValue <= minProp.value, icon: icons.value[ 0 ], - 'aria-label': ariaLabels.value[ 0 ] + 'aria-label': $q.lang.pagination.first }, minProp.value) ) @@ -338,7 +327,7 @@ export default createComponent({ key: 'ble', disable: props.disable || props.modelValue >= maxProp.value, icon: icons.value[ 3 ], - 'aria-label': ariaLabels.value[ 3 ] + 'aria-label': $q.lang.pagination.last }, maxProp.value) ) } @@ -349,7 +338,7 @@ export default createComponent({ key: 'bdp', disable: props.disable || props.modelValue <= minProp.value, icon: icons.value[ 1 ], - 'aria-label': ariaLabels.value[ 1 ] + 'aria-label': $q.lang.pagination.prev }, props.modelValue - 1) ) @@ -358,7 +347,7 @@ export default createComponent({ key: 'bdn', disable: props.disable || props.modelValue >= maxProp.value, icon: icons.value[ 2 ], - 'aria-label': ariaLabels.value[ 2 ] + 'aria-label': $q.lang.pagination.next }, props.modelValue + 1) ) } From 5e4d6dc7c7b842f1232337c576f40477c66b3227 Mon Sep 17 00:00:00 2001 From: John Franey <1728528+johnfraney@users.noreply.github.com> Date: Thu, 27 Feb 2025 13:09:20 -0400 Subject: [PATCH 18/32] typo: "works" -> "work" in QSelect.json (#17865) --- ui/src/components/select/QSelect.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/components/select/QSelect.json b/ui/src/components/select/QSelect.json index 65c39d49c19..4e282fff5f1 100644 --- a/ui/src/components/select/QSelect.json +++ b/ui/src/components/select/QSelect.json @@ -260,7 +260,7 @@ "fill-input": { "type": "Boolean", - "desc": "Fills the input with current value; Useful along with 'hide-selected'; Does NOT works along with 'multiple' selection", + "desc": "Fills the input with current value; Useful along with 'hide-selected'; Does NOT work along with 'multiple' selection", "category": "behavior" }, From b953dc95afe6223fa6454e1c88852ebe283e4bde Mon Sep 17 00:00:00 2001 From: Yusuf Kandemir Date: Fri, 28 Feb 2025 13:01:16 +0300 Subject: [PATCH 19/32] fix(ui): add missing filter prop to QTable #no-data JSON API --- ui/src/components/table/QTable.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ui/src/components/table/QTable.json b/ui/src/components/table/QTable.json index 6e3e6029c4f..0addceb0773 100644 --- a/ui/src/components/table/QTable.json +++ b/ui/src/components/table/QTable.json @@ -1838,7 +1838,11 @@ "type": "String", "desc": "The suggested icon name (following Quasar convention)", "examples": [ "'warning'" ] - } + }, + "filter": { + "type": [ "String", "Object" ], + "desc": "String/Object to filter table with (the 'filter' prop)" + }, } } }, From f19f75b48acf6d9b05b50fafd1e44c2b24327c86 Mon Sep 17 00:00:00 2001 From: Yusuf Kandemir Date: Fri, 28 Feb 2025 13:02:55 +0300 Subject: [PATCH 20/32] fix(ui): remove trailing comma in JSON silly mistake... --- ui/src/components/table/QTable.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/components/table/QTable.json b/ui/src/components/table/QTable.json index 0addceb0773..5ff147b80ff 100644 --- a/ui/src/components/table/QTable.json +++ b/ui/src/components/table/QTable.json @@ -1842,7 +1842,7 @@ "filter": { "type": [ "String", "Object" ], "desc": "String/Object to filter table with (the 'filter' prop)" - }, + } } } }, From cb4866a3f23b1e2107da165fc580c2579b43e3da Mon Sep 17 00:00:00 2001 From: Razvan Stoenescu Date: Fri, 28 Feb 2025 15:06:50 +0200 Subject: [PATCH 21/32] feat(QTable): new props -> table-row-style-fn, table-row-class-fn, grid-style-fn, grid-class-fn #8311 --- .../src/pages/components/data-table-part1.vue | 2 + .../src/pages/components/data-table-part4.vue | 13 +++ ui/src/components/table/QTable.js | 107 ++++++++++++------ ui/src/components/table/QTable.json | 94 +++++++++++++++ ui/src/components/table/QTr.js | 5 +- 5 files changed, 185 insertions(+), 36 deletions(-) diff --git a/ui/playground/src/pages/components/data-table-part1.vue b/ui/playground/src/pages/components/data-table-part1.vue index 5d03e69b9c4..0ac32988b90 100644 --- a/ui/playground/src/pages/components/data-table-part1.vue +++ b/ui/playground/src/pages/components/data-table-part1.vue @@ -146,6 +146,8 @@ :pagination="{rowsPerPage: 3}" :rows-per-page-options="[1, 2, 3, 4, 6]" row-key="name" + :table-row-class-fn="row => row.calories % 2 === 0 ? 'bg-red-1' : 'bg-yellow-1'" + :table-row-style-fn="row => row.calories % 2 === 0 ? 'color:blue' : 'color:green'" > diff --git a/ui/src/components/table/QTable.js b/ui/src/components/table/QTable.js index 9db5f14832c..2ec68be792a 100644 --- a/ui/src/components/table/QTable.js +++ b/ui/src/components/table/QTable.js @@ -91,10 +91,14 @@ export default createComponent({ tableClass: [ String, Array, Object ], tableHeaderStyle: [ String, Array, Object ], tableHeaderClass: [ String, Array, Object ], + tableRowStyleFn: Function, + tableRowClassFn: Function, cardContainerClass: [ String, Array, Object ], cardContainerStyle: [ String, Array, Object ], cardStyle: [ String, Array, Object ], cardClass: [ String, Array, Object ], + cardStyleFn: Function, + cardClassFn: Function, hideBottom: Boolean, hideSelectedBanner: Boolean, @@ -148,7 +152,7 @@ export default createComponent({ + (props.bordered === true ? ' q-table--bordered' : '') ) - const __containerClass = computed(() => + const containerClass = computed(() => `q-table__container q-table--${ props.separator }-separator column no-wrap` + (props.grid === true ? ' q-table--grid' : cardDefaultClass.value) + (isDark.value === true ? ' q-table--dark' : '') @@ -157,12 +161,12 @@ export default createComponent({ + (inFullscreen.value === true ? ' fullscreen scroll' : '') ) - const containerClass = computed(() => - __containerClass.value + (props.loading === true ? ' q-table--loading' : '') + const rootContainerClass = computed(() => + containerClass.value + (props.loading === true ? ' q-table--loading' : '') ) watch( - () => props.tableStyle + props.tableClass + props.tableHeaderStyle + props.tableHeaderClass + __containerClass.value, + () => props.tableStyle + props.tableClass + props.tableHeaderStyle + props.tableHeaderClass + containerClass.value, () => { hasVirtScroll.value === true && virtScrollRef.value !== null && virtScrollRef.value.reset() } ) @@ -382,13 +386,26 @@ export default createComponent({ selected = isRowSelected(key) if (bodySlot !== void 0) { + const cfg = { + key, + row, + pageIndex, + __trClass: selected ? 'selected' : '' + } + + if (props.tableRowStyleFn !== void 0) { + cfg.__trStyle = props.tableRowStyleFn(row) + } + + if (props.tableRowClassFn !== void 0) { + const cls = props.tableRowClassFn(row) + if (cls) { + cfg.__trClass = `${ cls } ${ cfg.__trClass }` + } + } + return bodySlot( - getBodyScope({ - key, - row, - pageIndex, - __trClass: selected ? 'selected' : '' - }) + getBodyScope(cfg) ) } @@ -451,6 +468,17 @@ export default createComponent({ } } + if (props.tableRowStyleFn !== void 0) { + data.style = props.tableRowStyleFn(row) + } + + if (props.tableRowClassFn !== void 0) { + const cls = props.tableRowClassFn(row) + if (cls) { + data.class[ cls ] = true + } + } + return h('tr', data, child) } @@ -792,30 +820,28 @@ export default createComponent({ h('div', { class: 'q-table__separator col' }) ) - if (hasOpts === true) { - child.push( - h('div', { class: 'q-table__control' }, [ - h('span', { class: 'q-table__bottom-item' }, [ - props.rowsPerPageLabel || $q.lang.table.recordsPerPage - ]), - h(QSelect, { - class: 'q-table__select inline q-table__bottom-item', - color: props.color, - modelValue: rowsPerPage, - options: computedRowsPerPageOptions.value, - displayValue: rowsPerPage === 0 - ? $q.lang.table.allRows - : rowsPerPage, - dark: isDark.value, - borderless: true, - dense: true, - optionsDense: true, - optionsCover: true, - 'onUpdate:modelValue': onPagSelection - }) - ]) - ) - } + hasOpts === true && child.push( + h('div', { class: 'q-table__control' }, [ + h('span', { class: 'q-table__bottom-item' }, [ + props.rowsPerPageLabel || $q.lang.table.recordsPerPage + ]), + h(QSelect, { + class: 'q-table__select inline q-table__bottom-item', + color: props.color, + modelValue: rowsPerPage, + options: computedRowsPerPageOptions.value, + displayValue: rowsPerPage === 0 + ? $q.lang.table.allRows + : rowsPerPage, + dark: isDark.value, + borderless: true, + dense: true, + optionsDense: true, + optionsCover: true, + 'onUpdate:modelValue': onPagSelection + }) + ]) + ) if (paginationSlot !== void 0) { control = paginationSlot(marginalsScope.value) @@ -949,6 +975,17 @@ export default createComponent({ style: props.cardStyle } + if (props.cardStyleFn !== void 0) { + data.style = [ data.style, props.cardStyleFn(scope.row) ] + } + + if (props.cardClassFn !== void 0) { + const cls = props.cardClassFn(scope.row) + if (cls) { + data.class[ 0 ] += ` ${ cls }` + } + } + if ( props.onRowClick !== void 0 || props.onRowDblclick !== void 0 @@ -1024,7 +1061,7 @@ export default createComponent({ return () => { const child = [ getTopDiv() ] - const data = { ref: rootRef, class: containerClass.value } + const data = { ref: rootRef, class: rootContainerClass.value } if (props.grid === true) { child.push(getGridHeader()) diff --git a/ui/src/components/table/QTable.json b/ui/src/components/table/QTable.json index 5ff147b80ff..659cf94a256 100644 --- a/ui/src/components/table/QTable.json +++ b/ui/src/components/table/QTable.json @@ -563,6 +563,50 @@ "category": "style" }, + "table-row-style-fn": { + "type": "Function", + "desc": "CSS style to apply to the table rows (which are TR elements)", + "params": { + "row": { + "type": "Object", + "desc": "The current row being processed", + "examples": [ "{ name: 'Frozen Yogurt', calories: 159 }" ] + } + }, + "returns": { + "type": "String", + "desc": "CSS style to apply to the row", + "examples": [ + "'color: blue'", + "'background-color: #ff0000; color: green'" + ] + }, + "category": "style", + "addedIn": "v2.18" + }, + + "table-row-class-fn": { + "type": "Function", + "desc": "CSS class(es) to apply the table rows (which are TR elements)", + "params": { + "row": { + "type": "Object", + "desc": "The current row being processed", + "examples": [ "{ name: 'Frozen Yogurt', calories: 159 }" ] + } + }, + "returns": { + "type": "String", + "desc": "CSS class(es) to apply to the row, space separated", + "examples": [ + "'my-special-class'", + "'my-class my-second-class'" + ] + }, + "category": "style", + "addedIn": "v2.18" + }, + "card-container-style": { "type": [ "String", "Array", "Object" ], "tsType": "VueStyleProp", @@ -608,6 +652,50 @@ "category": "style" }, + "card-style-fn": { + "type": "Function", + "desc": "(Grid mode only) CSS style to apply to the row/record card; Has no effect when the 'item' slot is used", + "params": { + "row": { + "type": "Object", + "desc": "The current row/record being processed", + "examples": [ "{ name: 'Frozen Yogurt', calories: 159 }" ] + } + }, + "returns": { + "type": "String", + "desc": "CSS style to apply to the row/record", + "examples": [ + "'color: blue'", + "'background-color: #ff0000; color: green'" + ] + }, + "category": "style", + "addedIn": "v2.18" + }, + + "card-class-fn": { + "type": "Function", + "desc": "(Grid mode only) CSS class(es) to apply the row/record card; Has no effect when the 'item' slot is used", + "params": { + "row": { + "type": "Object", + "desc": "The current row/record being processed", + "examples": [ "{ name: 'Frozen Yogurt', calories: 159 }" ] + } + }, + "returns": { + "type": "String", + "desc": "CSS class(es) to apply to the row, space separated", + "examples": [ + "'my-special-class'", + "'my-class my-second-class'" + ] + }, + "category": "style", + "addedIn": "v2.18" + }, + "title-class": { "type": [ "String", "Array", "Object" ], "tsType": "VueClassProp", @@ -896,6 +984,12 @@ "__trClass": { "type": "String", "desc": "Internal prop passed down to QTr (if used)" + }, + "__trStyle": { + "type": "String", + "required": false, + "desc": "Internal prop passed down to QTr (if used)", + "addedIn": "v2.18" } } }, diff --git a/ui/src/components/table/QTr.js b/ui/src/components/table/QTr.js index 6aa5bbf376c..8f58b8f0dc0 100644 --- a/ui/src/components/table/QTr.js +++ b/ui/src/components/table/QTr.js @@ -18,6 +18,9 @@ export default createComponent({ + (props.noHover === true ? ' q-tr--no-hover' : '') ) - return () => h('tr', { class: classes.value }, hSlot(slots.default)) + return () => h('tr', { + style: props.props?.__trStyle, + class: classes.value + }, hSlot(slots.default)) } }) From 7833afedb538909754d315a844ffe429c6847b98 Mon Sep 17 00:00:00 2001 From: Razvan Stoenescu Date: Fri, 28 Feb 2025 15:54:45 +0200 Subject: [PATCH 22/32] feat(docs): add the new QTable props (table-row-style-fn & table-row-class-fn) to one of the styling examples #8311 --- docs/src/examples/QTable/CustomColor.vue | 16 +++++++++++++--- docs/src/pages/vue-components/table.md | 4 ++++ ui/src/components/table/QTable.json | 8 ++++---- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/docs/src/examples/QTable/CustomColor.vue b/docs/src/examples/QTable/CustomColor.vue index fb510de1465..f47499c46d6 100644 --- a/docs/src/examples/QTable/CustomColor.vue +++ b/docs/src/examples/QTable/CustomColor.vue @@ -2,9 +2,11 @@
+::: tip +For all the styling component properties, please check the API card at the top of the page. +::: + diff --git a/ui/src/components/table/QTable.json b/ui/src/components/table/QTable.json index 659cf94a256..4ee04c697b8 100644 --- a/ui/src/components/table/QTable.json +++ b/ui/src/components/table/QTable.json @@ -565,7 +565,7 @@ "table-row-style-fn": { "type": "Function", - "desc": "CSS style to apply to the table rows (which are TR elements)", + "desc": "CSS style to apply to the table rows (which are TR elements); For best performance, reference it from your scope and do not define it inline", "params": { "row": { "type": "Object", @@ -587,7 +587,7 @@ "table-row-class-fn": { "type": "Function", - "desc": "CSS class(es) to apply the table rows (which are TR elements)", + "desc": "CSS class(es) to apply the table rows (which are TR elements); For best performance, reference it from your scope and do not define it inline", "params": { "row": { "type": "Object", @@ -654,7 +654,7 @@ "card-style-fn": { "type": "Function", - "desc": "(Grid mode only) CSS style to apply to the row/record card; Has no effect when the 'item' slot is used", + "desc": "(Grid mode only) CSS style to apply to the row/record card; Has no effect when the 'item' slot is used; For best performance, reference it from your scope and do not define it inline", "params": { "row": { "type": "Object", @@ -676,7 +676,7 @@ "card-class-fn": { "type": "Function", - "desc": "(Grid mode only) CSS class(es) to apply the row/record card; Has no effect when the 'item' slot is used", + "desc": "(Grid mode only) CSS class(es) to apply the row/record card; Has no effect when the 'item' slot is used; For best performance, reference it from your scope and do not define it inline", "params": { "row": { "type": "Object", From ed0d977ee92bf50ee870de4503693b8c30869677 Mon Sep 17 00:00:00 2001 From: RamonDonadeu <43962562+RamonDonadeu@users.noreply.github.com> Date: Fri, 28 Feb 2025 15:04:09 +0100 Subject: [PATCH 23/32] feat(docs): Add Custom Options example in QOptionGroup #17787 (#17869) * feat(docs): Add Custom Options example in QOptionGroup #17787 * chore: improve warning box --- .../CustomOptionPropsWithFunction.vue | 38 +++++++++++++++++++ .../CustomOptionPropsWithString.vue | 36 ++++++++++++++++++ docs/src/pages/vue-components/option-group.md | 13 +++++++ 3 files changed, 87 insertions(+) create mode 100644 docs/src/examples/QOptionGroup/CustomOptionPropsWithFunction.vue create mode 100644 docs/src/examples/QOptionGroup/CustomOptionPropsWithString.vue diff --git a/docs/src/examples/QOptionGroup/CustomOptionPropsWithFunction.vue b/docs/src/examples/QOptionGroup/CustomOptionPropsWithFunction.vue new file mode 100644 index 00000000000..218d0af629c --- /dev/null +++ b/docs/src/examples/QOptionGroup/CustomOptionPropsWithFunction.vue @@ -0,0 +1,38 @@ + + + diff --git a/docs/src/examples/QOptionGroup/CustomOptionPropsWithString.vue b/docs/src/examples/QOptionGroup/CustomOptionPropsWithString.vue new file mode 100644 index 00000000000..4143fcd7fe9 --- /dev/null +++ b/docs/src/examples/QOptionGroup/CustomOptionPropsWithString.vue @@ -0,0 +1,36 @@ + + + diff --git a/docs/src/pages/vue-components/option-group.md b/docs/src/pages/vue-components/option-group.md index 5e586c54929..a82d02ba917 100644 --- a/docs/src/pages/vue-components/option-group.md +++ b/docs/src/pages/vue-components/option-group.md @@ -62,6 +62,19 @@ The objects within the `options` array can hold any of the props found in QToggl +### Custom Label, Value and Disable props + +By default, QOptionGroup looks at `label`, `value`, `disable` props of each option from the options array Objects. But you can override those: + + + + +::: warning +If you use functions for custom props always check if the option is null. +::: + + + ### Force dark mode From 67048301a30526f7a0b649a55eb427bc3b6cc5b9 Mon Sep 17 00:00:00 2001 From: Razvan Stoenescu Date: Fri, 28 Feb 2025 16:28:54 +0200 Subject: [PATCH 24/32] feat(docs): simplify examples for QOptionGroup custom option props #17787 (#17869) --- .../QOptionGroup/CustomOptionProps.vue | 59 +++++++++++++++++++ .../CustomOptionPropsWithFunction.vue | 38 ------------ .../CustomOptionPropsWithString.vue | 36 ----------- docs/src/pages/vue-components/option-group.md | 9 +-- 4 files changed, 60 insertions(+), 82 deletions(-) create mode 100644 docs/src/examples/QOptionGroup/CustomOptionProps.vue delete mode 100644 docs/src/examples/QOptionGroup/CustomOptionPropsWithFunction.vue delete mode 100644 docs/src/examples/QOptionGroup/CustomOptionPropsWithString.vue diff --git a/docs/src/examples/QOptionGroup/CustomOptionProps.vue b/docs/src/examples/QOptionGroup/CustomOptionProps.vue new file mode 100644 index 00000000000..b83e0b3f99a --- /dev/null +++ b/docs/src/examples/QOptionGroup/CustomOptionProps.vue @@ -0,0 +1,59 @@ + + + diff --git a/docs/src/examples/QOptionGroup/CustomOptionPropsWithFunction.vue b/docs/src/examples/QOptionGroup/CustomOptionPropsWithFunction.vue deleted file mode 100644 index 218d0af629c..00000000000 --- a/docs/src/examples/QOptionGroup/CustomOptionPropsWithFunction.vue +++ /dev/null @@ -1,38 +0,0 @@ - - - diff --git a/docs/src/examples/QOptionGroup/CustomOptionPropsWithString.vue b/docs/src/examples/QOptionGroup/CustomOptionPropsWithString.vue deleted file mode 100644 index 4143fcd7fe9..00000000000 --- a/docs/src/examples/QOptionGroup/CustomOptionPropsWithString.vue +++ /dev/null @@ -1,36 +0,0 @@ - - - diff --git a/docs/src/pages/vue-components/option-group.md b/docs/src/pages/vue-components/option-group.md index a82d02ba917..6c5d29c72d1 100644 --- a/docs/src/pages/vue-components/option-group.md +++ b/docs/src/pages/vue-components/option-group.md @@ -66,14 +66,7 @@ The objects within the `options` array can hold any of the props found in QToggl By default, QOptionGroup looks at `label`, `value`, `disable` props of each option from the options array Objects. But you can override those: - - - -::: warning -If you use functions for custom props always check if the option is null. -::: - - + ### Force dark mode From 401a630ca2aca6702496363a8c86ed95003bfaba Mon Sep 17 00:00:00 2001 From: Razvan Stoenescu Date: Fri, 28 Feb 2025 18:05:16 +0200 Subject: [PATCH 25/32] feat(ui): modernize codebase with optional chaining operator --- ui/src/components/breadcrumbs/QBreadcrumbs.js | 4 ++-- ui/src/components/btn-dropdown/QBtnDropdown.js | 8 ++++---- ui/src/components/btn/QBtn.js | 11 +++++------ ui/src/components/color/QColor.js | 2 +- ui/src/components/date/QDate.js | 2 +- ui/src/components/dialog/QDialog.js | 2 +- ui/src/components/drawer/QDrawer.js | 4 ++-- ui/src/components/editor/QEditor.js | 2 +- ui/src/components/editor/editor-caret.js | 2 +- ui/src/components/editor/editor-utils.js | 2 +- .../expansion-item/QExpansionItem.js | 6 +++--- ui/src/components/form/QForm.js | 4 ++-- ui/src/components/form/QFormChildMixin.js | 10 ++++++---- .../infinite-scroll/QInfiniteScroll.js | 6 ++---- ui/src/components/input/QInput.js | 6 +++--- ui/src/components/parallax/QParallax.js | 2 +- ui/src/components/popup-edit/QPopupEdit.js | 4 ++-- .../scroll-observer/QScrollObserver.js | 4 ++-- ui/src/components/select/QSelect.js | 10 +++++----- .../slide-transition/QSlideTransition.js | 2 +- ui/src/components/stepper/StepHeader.js | 2 +- ui/src/components/table/QTable.js | 2 +- ui/src/components/table/table-sort.js | 2 +- ui/src/components/tabs/QTabs.js | 4 ++-- ui/src/components/tabs/use-tab.js | 4 ++-- ui/src/components/time/QTime.js | 2 +- .../virtual-scroll/use-virtual-scroll.js | 4 ++-- .../composables/private.use-field/use-field.js | 6 +++--- .../composables/private.use-file/use-file.js | 2 +- .../use-model-toggle.js | 4 ++-- .../use-refocus-target.js | 9 ++++----- .../private.use-validate/use-validate.js | 2 +- ui/src/directives/intersection/Intersection.js | 4 ++-- ui/src/directives/morph/Morph.js | 4 ++-- ui/src/directives/mutation/Mutation.js | 4 ++-- ui/src/directives/touch-hold/TouchHold.js | 4 ++-- ui/src/directives/touch-pan/TouchPan.js | 4 ++-- ui/src/directives/touch-repeat/TouchRepeat.js | 6 +++--- ui/src/directives/touch-swipe/TouchSwipe.js | 6 +++--- ui/src/plugins/loading/Loading.js | 2 +- ui/src/plugins/notify/Notify.js | 2 +- ui/src/plugins/private.body/Body.js | 2 +- ui/src/plugins/private.history/History.js | 2 +- ui/src/plugins/screen/Screen.js | 2 +- ui/src/utils/morph/morph.js | 18 +++++++++--------- ui/src/utils/open-url/open-url.js | 4 ++-- ui/src/utils/private.dialog/create-dialog.js | 2 +- ui/src/utils/private.portal/portal.js | 2 +- 48 files changed, 101 insertions(+), 103 deletions(-) diff --git a/ui/src/components/breadcrumbs/QBreadcrumbs.js b/ui/src/components/breadcrumbs/QBreadcrumbs.js index bbf13425de9..1ccf7b17880 100644 --- a/ui/src/components/breadcrumbs/QBreadcrumbs.js +++ b/ui/src/components/breadcrumbs/QBreadcrumbs.js @@ -55,13 +55,13 @@ export default createComponent({ const child = [], - len = vnodes.filter(c => c.type !== void 0 && c.type.name === 'QBreadcrumbsEl').length, + len = vnodes.filter(c => c.type?.name === 'QBreadcrumbsEl').length, separator = slots.separator !== void 0 ? slots.separator : () => props.separator vnodes.forEach(comp => { - if (comp.type !== void 0 && comp.type.name === 'QBreadcrumbsEl') { + if (comp.type?.name === 'QBreadcrumbsEl') { const middle = els < len const disabled = comp.props !== null && disabledValues.includes(comp.props.disable) const cls = (middle === true ? '' : ' q-breadcrumbs--last') diff --git a/ui/src/components/btn-dropdown/QBtnDropdown.js b/ui/src/components/btn-dropdown/QBtnDropdown.js index 4fce18e5909..3d653ee4671 100644 --- a/ui/src/components/btn-dropdown/QBtnDropdown.js +++ b/ui/src/components/btn-dropdown/QBtnDropdown.js @@ -105,7 +105,7 @@ export default createComponent({ const btnProps = computed(() => passBtnProps(props)) watch(() => props.modelValue, val => { - menuRef.value !== null && menuRef.value[ val ? 'show' : 'hide' ]() + menuRef.value?.[ val ? 'show' : 'hide' ]() }) watch(() => props.split, hide) @@ -141,15 +141,15 @@ export default createComponent({ } function toggle (evt) { - menuRef.value !== null && menuRef.value.toggle(evt) + menuRef.value?.toggle(evt) } function show (evt) { - menuRef.value !== null && menuRef.value.show(evt) + menuRef.value?.show(evt) } function hide (evt) { - menuRef.value !== null && menuRef.value.hide(evt) + menuRef.value?.hide(evt) } // expose public methods diff --git a/ui/src/components/btn/QBtn.js b/ui/src/components/btn/QBtn.js index 76a6bff70ba..4f957ae4970 100644 --- a/ui/src/components/btn/QBtn.js +++ b/ui/src/components/btn/QBtn.js @@ -135,7 +135,7 @@ export default createComponent({ const onClickCleanup = () => { document.removeEventListener('keydown', stopAndPrevent, true) document.removeEventListener('keyup', onClickCleanup, passiveCapture) - rootRef.value !== null && rootRef.value.removeEventListener('blur', onClickCleanup, passiveCapture) + rootRef.value?.removeEventListener('blur', onClickCleanup, passiveCapture) } document.addEventListener('keydown', stopAndPrevent, true) @@ -218,12 +218,11 @@ export default createComponent({ // needed for IE (because it emits blur when focusing button from focus helper) if ( - e !== void 0 - && e.type === 'blur' + e?.type === 'blur' && document.activeElement === rootRef.value ) return - if (e !== void 0 && e.type === 'keyup') { + if (e?.type === 'keyup') { if (keyboardTarget === rootRef.value && isKeyCode(e, [ 13, 32 ]) === true) { // for click trigger const evt = new MouseEvent('click', e) @@ -272,11 +271,11 @@ export default createComponent({ if (keyboardTarget === rootRef.value) { document.removeEventListener('keyup', onPressEnd, true) - rootRef.value !== null && rootRef.value.removeEventListener('blur', onPressEnd, passiveCapture) + rootRef.value?.removeEventListener('blur', onPressEnd, passiveCapture) keyboardTarget = null } - rootRef.value !== null && rootRef.value.classList.remove('q-btn--active') + rootRef.value?.classList.remove('q-btn--active') } function onLoadingEvt (evt) { diff --git a/ui/src/components/color/QColor.js b/ui/src/components/color/QColor.js index 007e9f3c90b..ce9aec00e94 100644 --- a/ui/src/components/color/QColor.js +++ b/ui/src/components/color/QColor.js @@ -340,7 +340,7 @@ export default createComponent({ updateModel(rgb, change) - if (evt !== void 0 && change !== true && evt.target.selectionEnd !== void 0) { + if (change !== true && evt?.target.selectionEnd !== void 0) { const index = evt.target.selectionEnd nextTick(() => { evt.target.setSelectionRange(index, index) diff --git a/ui/src/components/date/QDate.js b/ui/src/components/date/QDate.js index a603a063090..8f46d883b18 100644 --- a/ui/src/components/date/QDate.js +++ b/ui/src/components/date/QDate.js @@ -906,7 +906,7 @@ export default createComponent({ function toggleDate (date, monthHash) { const month = daysMap.value[ monthHash ] - const fn = month !== void 0 && month.includes(date.day) === true + const fn = month?.includes(date.day) === true ? removeFromModel : addToModel diff --git a/ui/src/components/dialog/QDialog.js b/ui/src/components/dialog/QDialog.js index 5823d3717df..3a68764c99c 100644 --- a/ui/src/components/dialog/QDialog.js +++ b/ui/src/components/dialog/QDialog.js @@ -180,7 +180,7 @@ export default createComponent({ animating.value = true if (props.noFocus !== true) { - document.activeElement !== null && document.activeElement.blur() + document.activeElement?.blur() registerTick(focus) } else { diff --git a/ui/src/components/drawer/QDrawer.js b/ui/src/components/drawer/QDrawer.js index e40ad117c36..46f35e21f91 100644 --- a/ui/src/components/drawer/QDrawer.js +++ b/ui/src/components/drawer/QDrawer.js @@ -120,7 +120,7 @@ export default createComponent({ if (belowBreakpoint.value === true) { const otherInstance = $layout.instances[ otherSide.value ] - if (otherInstance !== void 0 && otherInstance.belowBreakpoint === true) { + if (otherInstance?.belowBreakpoint === true) { otherInstance.hide(false) } @@ -622,7 +622,7 @@ export default createComponent({ }) onBeforeUnmount(() => { - layoutTotalWidthWatcher !== void 0 && layoutTotalWidthWatcher() + layoutTotalWidthWatcher?.() if (timerMini !== null) { clearTimeout(timerMini) diff --git a/ui/src/components/editor/QEditor.js b/ui/src/components/editor/QEditor.js index c8e5189506e..e296e7403ff 100644 --- a/ui/src/components/editor/QEditor.js +++ b/ui/src/components/editor/QEditor.js @@ -451,7 +451,7 @@ export default createComponent({ function focus () { addFocusFn(() => { - contentRef.value !== null && contentRef.value.focus({ preventScroll: true }) + contentRef.value?.focus({ preventScroll: true }) }) } diff --git a/ui/src/components/editor/editor-caret.js b/ui/src/components/editor/editor-caret.js index 0e48e292e4d..d4fff99c2f5 100644 --- a/ui/src/components/editor/editor-caret.js +++ b/ui/src/components/editor/editor-caret.js @@ -91,7 +91,7 @@ export default class Caret { get range () { const sel = this.selection - if (sel !== null && sel.rangeCount) { + if (sel?.rangeCount) { return sel.getRangeAt(0) } diff --git a/ui/src/components/editor/editor-utils.js b/ui/src/components/editor/editor-utils.js index f97ae7dca44..31f721f2fa9 100644 --- a/ui/src/components/editor/editor-utils.js +++ b/ui/src/components/editor/editor-utils.js @@ -120,7 +120,7 @@ function getDropdown (eVm, btn) { dense: true, onClick (e) { closeDropdown() - eVm.contentRef.value !== null && eVm.contentRef.value.focus() + eVm.contentRef.value?.focus() eVm.caret.restore() run(e, btn, eVm) } diff --git a/ui/src/components/expansion-item/QExpansionItem.js b/ui/src/components/expansion-item/QExpansionItem.js index 6403be74e82..1c0f1d90313 100644 --- a/ui/src/components/expansion-item/QExpansionItem.js +++ b/ui/src/components/expansion-item/QExpansionItem.js @@ -150,7 +150,7 @@ export default createComponent({ }) watch(() => props.group, name => { - exitGroup !== void 0 && exitGroup() + exitGroup?.() name !== void 0 && enterGroup() }) @@ -164,7 +164,7 @@ export default createComponent({ } function toggleIcon (e, keyboard) { - keyboard !== true && blurTargetRef.value !== null && blurTargetRef.value.focus() + keyboard !== true && blurTargetRef.value?.focus() toggle(e) stopAndPrevent(e) } @@ -360,7 +360,7 @@ export default createComponent({ props.group !== void 0 && enterGroup() onBeforeUnmount(() => { - exitGroup !== void 0 && exitGroup() + exitGroup?.() }) return () => h('div', { class: classes.value }, [ diff --git a/ui/src/components/form/QForm.js b/ui/src/components/form/QForm.js index 7b3cbd77a82..87a9a327f79 100644 --- a/ui/src/components/form/QForm.js +++ b/ui/src/components/form/QForm.js @@ -114,7 +114,7 @@ export default createComponent({ if (props.onSubmit !== void 0) { emit('submit', evt) } - else if (evt !== void 0 && evt.target !== void 0 && typeof evt.target.submit === 'function') { + else if (evt?.target !== void 0 && typeof evt.target.submit === 'function') { evt.target.submit() } } @@ -143,7 +143,7 @@ export default createComponent({ || rootRef.value.querySelector('[autofocus], [data-autofocus]') || Array.prototype.find.call(rootRef.value.querySelectorAll('[tabindex]'), el => el.tabIndex !== -1) - target !== null && target !== void 0 && target.focus({ preventScroll: true }) + target?.focus({ preventScroll: true }) }) } diff --git a/ui/src/components/form/QFormChildMixin.js b/ui/src/components/form/QFormChildMixin.js index 6f1e78b7239..001805e6584 100644 --- a/ui/src/components/form/QFormChildMixin.js +++ b/ui/src/components/form/QFormChildMixin.js @@ -30,13 +30,15 @@ export default { mounted () { // register to parent QForm - const $form = this.$.provides[ formKey ] - $form !== void 0 && this.disable !== true && $form.bindComponent(this) + if (this.disable !== true) { + this.$.provides[ formKey ]?.bindComponent(this) + } }, beforeUnmount () { // un-register from parent QForm - const $form = this.$.provides[ formKey ] - $form !== void 0 && this.disable !== true && $form.unbindComponent(this) + if (this.disable !== true) { + this.$.provides[ formKey ]?.unbindComponent(this) + } } } diff --git a/ui/src/components/infinite-scroll/QInfiniteScroll.js b/ui/src/components/infinite-scroll/QInfiniteScroll.js index 9b29cf55e8a..fb9e8c218dc 100644 --- a/ui/src/components/infinite-scroll/QInfiniteScroll.js +++ b/ui/src/components/infinite-scroll/QInfiniteScroll.js @@ -126,9 +126,7 @@ export default createComponent({ isWorking.value = false isFetching.value = false localScrollTarget.removeEventListener('scroll', poll, passive) - if (poll !== void 0 && poll.cancel !== void 0) { - poll.cancel() - } + poll?.cancel?.() } } @@ -240,7 +238,7 @@ export default createComponent({ // expose public methods const vm = getCurrentInstance() Object.assign(vm.proxy, { - poll: () => { poll !== void 0 && poll() }, + poll: () => { poll?.() }, trigger, stop, reset, resume, setIndex, updateScrollTarget }) diff --git a/ui/src/components/input/QInput.js b/ui/src/components/input/QInput.js index 9355ba587f4..49ceea2130d 100644 --- a/ui/src/components/input/QInput.js +++ b/ui/src/components/input/QInput.js @@ -203,7 +203,7 @@ export default createComponent({ } function select () { - inputRef.value !== null && inputRef.value.select() + inputRef.value?.select() } function onPaste (e) { @@ -340,7 +340,7 @@ export default createComponent({ emitTimer = null } - emitValueFn !== void 0 && emitValueFn() + emitValueFn?.() emit('change', e.target.value) } @@ -353,7 +353,7 @@ export default createComponent({ emitTimer = null } - emitValueFn !== void 0 && emitValueFn() + emitValueFn?.() typedNumber = false stopValueWatcher = false diff --git a/ui/src/components/parallax/QParallax.js b/ui/src/components/parallax/QParallax.js index c9ec464cea7..48a58df42b2 100644 --- a/ui/src/components/parallax/QParallax.js +++ b/ui/src/components/parallax/QParallax.js @@ -134,7 +134,7 @@ export default createComponent({ onBeforeUnmount(() => { stop() - observer !== void 0 && observer.disconnect() + observer?.disconnect() mediaEl.onload = mediaEl.onloadstart = mediaEl.loadedmetadata = null }) diff --git a/ui/src/components/popup-edit/QPopupEdit.js b/ui/src/components/popup-edit/QPopupEdit.js index c6a8f9a854b..9efc783ab3b 100644 --- a/ui/src/components/popup-edit/QPopupEdit.js +++ b/ui/src/components/popup-edit/QPopupEdit.js @@ -163,8 +163,8 @@ export default createComponent({ Object.assign(proxy, { set, cancel, - show (e) { menuRef.value !== null && menuRef.value.show(e) }, - hide (e) { menuRef.value !== null && menuRef.value.hide(e) }, + show (e) { menuRef.value?.show(e) }, + hide (e) { menuRef.value?.hide(e) }, updatePosition }) diff --git a/ui/src/components/scroll-observer/QScrollObserver.js b/ui/src/components/scroll-observer/QScrollObserver.js index 5d49315a3e9..a011386f411 100644 --- a/ui/src/components/scroll-observer/QScrollObserver.js +++ b/ui/src/components/scroll-observer/QScrollObserver.js @@ -53,7 +53,7 @@ export default createComponent({ }) function emitEvent () { - clearTimer !== null && clearTimer() + clearTimer?.() const top = Math.max(0, getVerticalScrollPosition(localScrollTarget)) const left = getHorizontalScrollPosition(localScrollTarget) @@ -123,7 +123,7 @@ export default createComponent({ }) onBeforeUnmount(() => { - clearTimer !== null && clearTimer() + clearTimer?.() unconfigureScrollTarget() }) diff --git a/ui/src/components/select/QSelect.js b/ui/src/components/select/QSelect.js index 42e4446d797..142c33319d1 100644 --- a/ui/src/components/select/QSelect.js +++ b/ui/src/components/select/QSelect.js @@ -273,7 +273,7 @@ export default createComponent({ const needsHtmlFn = computed(() => ( props.optionsHtml === true ? () => true - : opt => opt !== void 0 && opt !== null && opt.html === true + : opt => opt?.html === true )) const valueAsHtml = computed(() => ( @@ -533,7 +533,7 @@ export default createComponent({ hidePopup() } - targetRef.value !== null && targetRef.value.focus() + targetRef.value?.focus() if ( innerValue.value.length === 0 @@ -894,7 +894,7 @@ export default createComponent({ fn(val, mode === 'add-unique') if (props.multiple !== true) { - targetRef.value !== null && targetRef.value.focus() + targetRef.value?.focus() hidePopup() } } @@ -1214,7 +1214,7 @@ export default createComponent({ function onDialogFieldFocus (e) { stop(e) - targetRef.value !== null && targetRef.value.focus() + targetRef.value?.focus() dialogFieldFocused.value = true window.scrollTo(window.pageXOffset || window.scrollX || document.body.scrollLeft || 0, 0) } @@ -1511,7 +1511,7 @@ export default createComponent({ if (hasDialog !== true && menu.value === true) { closeMenu() - targetRef.value !== null && targetRef.value.focus() + targetRef.value?.focus() return } diff --git a/ui/src/components/slide-transition/QSlideTransition.js b/ui/src/components/slide-transition/QSlideTransition.js index 34b22a011ce..6b31805ba45 100644 --- a/ui/src/components/slide-transition/QSlideTransition.js +++ b/ui/src/components/slide-transition/QSlideTransition.js @@ -34,7 +34,7 @@ export default createComponent({ timerFallback = null } - element !== void 0 && element.removeEventListener('transitionend', animListener) + element?.removeEventListener('transitionend', animListener) animListener = null } diff --git a/ui/src/components/stepper/StepHeader.js b/ui/src/components/stepper/StepHeader.js index f0e2958a977..b7f69a2ba10 100644 --- a/ui/src/components/stepper/StepHeader.js +++ b/ui/src/components/stepper/StepHeader.js @@ -119,7 +119,7 @@ export default createComponent({ )) function onActivate () { - blurRef.value !== null && blurRef.value.focus() + blurRef.value?.focus() isActive.value === false && props.goToPanel(props.step.name) } diff --git a/ui/src/components/table/QTable.js b/ui/src/components/table/QTable.js index 2ec68be792a..2d7bf6fa7d2 100644 --- a/ui/src/components/table/QTable.js +++ b/ui/src/components/table/QTable.js @@ -167,7 +167,7 @@ export default createComponent({ watch( () => props.tableStyle + props.tableClass + props.tableHeaderStyle + props.tableHeaderClass + containerClass.value, - () => { hasVirtScroll.value === true && virtScrollRef.value !== null && virtScrollRef.value.reset() } + () => { hasVirtScroll.value === true && virtScrollRef.value?.reset() } ) const { diff --git a/ui/src/components/table/table-sort.js b/ui/src/components/table/table-sort.js index ccd3ce2100d..c1df1b0d412 100644 --- a/ui/src/components/table/table-sort.js +++ b/ui/src/components/table/table-sort.js @@ -87,7 +87,7 @@ export function useTableSort (props, computedPagination, colList, setPagination) } else { const def = colList.value.find(def => def.name === col) - if (def !== void 0 && def.sortOrder) { + if (def?.sortOrder) { sortOrder = def.sortOrder } } diff --git a/ui/src/components/tabs/QTabs.js b/ui/src/components/tabs/QTabs.js index 1b619fec210..e9c51f786ea 100644 --- a/ui/src/components/tabs/QTabs.js +++ b/ui/src/components/tabs/QTabs.js @@ -424,7 +424,7 @@ export default createComponent({ function updateActiveRoute () { let name = null, bestScore = { matchedLen: 0, queryDiff: 9999, hrefLen: 0 } - const list = tabDataList.filter(tab => tab.routeData !== void 0 && tab.routeData.hasRouterLink.value === true) + const list = tabDataList.filter(tab => tab.routeData?.hasRouterLink.value === true) const { hash: currentHash, query: currentQuery } = proxy.$route const currentQueryLen = Object.keys(currentQuery).length @@ -632,7 +632,7 @@ export default createComponent({ function cleanup () { animateTimer !== null && clearTimeout(animateTimer) stopAnimScroll() - unwatchRoute !== void 0 && unwatchRoute() + unwatchRoute?.() } let hadRouteWatcher, hadActivated diff --git a/ui/src/components/tabs/use-tab.js b/ui/src/components/tabs/use-tab.js index 0e3d9782b05..282e041a656 100644 --- a/ui/src/components/tabs/use-tab.js +++ b/ui/src/components/tabs/use-tab.js @@ -105,7 +105,7 @@ export default function (props, slots, emit, routeData) { if (props.disable === true) { // we should hinder native navigation though - if (routeData !== void 0 && routeData.hasRouterLink.value === true) { + if (routeData?.hasRouterLink.value === true) { stopAndPrevent(e) } return @@ -140,7 +140,7 @@ export default function (props, slots, emit, routeData) { if ( hardError === void 0 && ( softError === void 0 - || (softError.message !== void 0 && softError.message.startsWith('Avoided redundant navigation') === true) + || (softError.message?.startsWith('Avoided redundant navigation') === true) ) ) { $tabs.updateModel({ name: props.name }) diff --git a/ui/src/components/time/QTime.js b/ui/src/components/time/QTime.js index aade80786ec..37fb083342e 100644 --- a/ui/src/components/time/QTime.js +++ b/ui/src/components/time/QTime.js @@ -264,7 +264,7 @@ export default createComponent({ for (let val = start, index = start; val <= end; val += step, index++) { const actualVal = val + offset, - disable = values !== void 0 && values.includes(actualVal) === false, + disable = values?.includes(actualVal) === false, label = view.value === 'hour' && val === 0 ? (computedFormat24h.value === true ? '00' : '12') : val diff --git a/ui/src/components/virtual-scroll/use-virtual-scroll.js b/ui/src/components/virtual-scroll/use-virtual-scroll.js index 87376495d4c..29db10c03f4 100644 --- a/ui/src/components/virtual-scroll/use-virtual-scroll.js +++ b/ui/src/components/virtual-scroll/use-virtual-scroll.js @@ -422,7 +422,7 @@ export function useVirtualScroll ({ contentEl.addEventListener('focusout', onBlurRefocusFn) setTimeout(() => { - contentEl !== null && contentEl.removeEventListener('focusout', onBlurRefocusFn) + contentEl?.removeEventListener('focusout', onBlurRefocusFn) }) } @@ -533,7 +533,7 @@ export function useVirtualScroll ({ } function onBlurRefocusFn () { - contentRef.value !== null && contentRef.value !== void 0 && contentRef.value.focus() + contentRef.value?.focus() } function localResetVirtualScroll (toIndex, fullReset) { diff --git a/ui/src/composables/private.use-field/use-field.js b/ui/src/composables/private.use-field/use-field.js index 04e0e98a3c5..f63c2c1a6e9 100644 --- a/ui/src/composables/private.use-field/use-field.js +++ b/ui/src/composables/private.use-field/use-field.js @@ -265,7 +265,7 @@ export default function (state) { function focusHandler () { const el = document.activeElement - let target = state.targetRef !== void 0 && state.targetRef.value + let target = state.targetRef?.value if (target && (el === null || el.id !== state.targetUid.value)) { target.hasAttribute('tabindex') === true || (target = target.querySelector('[tabindex]')) @@ -318,7 +318,7 @@ export default function (state) { emit('blur', e) } - then !== void 0 && then() + then?.() }) } @@ -327,7 +327,7 @@ export default function (state) { stopAndPrevent(e) if ($q.platform.is.mobile !== true) { - const el = (state.targetRef !== void 0 && state.targetRef.value) || state.rootRef.value + const el = state.targetRef?.value || state.rootRef.value el.focus() } else if (state.rootRef.value.contains(document.activeElement) === true) { diff --git a/ui/src/composables/private.use-file/use-file.js b/ui/src/composables/private.use-file/use-file.js index 8cbe4d1c123..afcffac2dc8 100644 --- a/ui/src/composables/private.use-file/use-file.js +++ b/ui/src/composables/private.use-file/use-file.js @@ -69,7 +69,7 @@ export default function ({ e = { target: null } } - if (e.target !== null && e.target.matches('input[type="file"]') === true) { + if (e.target?.matches('input[type="file"]') === true) { // stop propagation if it's not a real pointer event e.clientX === 0 && e.clientY === 0 && stop(e) } diff --git a/ui/src/composables/private.use-model-toggle/use-model-toggle.js b/ui/src/composables/private.use-model-toggle/use-model-toggle.js index c2f2c7e868b..672ff183796 100644 --- a/ui/src/composables/private.use-model-toggle/use-model-toggle.js +++ b/ui/src/composables/private.use-model-toggle/use-model-toggle.js @@ -41,8 +41,8 @@ export default function ({ function show (evt) { if ( - props.disable === true - || (evt !== void 0 && evt.qAnchorHandled === true) + (props.disable === true) + || (evt?.qAnchorHandled === true) || (canShow !== void 0 && canShow(evt) !== true) ) return diff --git a/ui/src/composables/private.use-refocus-target/use-refocus-target.js b/ui/src/composables/private.use-refocus-target/use-refocus-target.js index cbfd52f7ba6..acd077ba9ab 100644 --- a/ui/src/composables/private.use-refocus-target/use-refocus-target.js +++ b/ui/src/composables/private.use-refocus-target/use-refocus-target.js @@ -18,18 +18,17 @@ export default function (props, rootRef) { function refocusTarget (e) { const root = rootRef.value - if (e !== void 0 && e.type.indexOf('key') === 0) { + if (e?.type.indexOf('key') === 0) { if ( - root !== null - && document.activeElement !== root - && root.contains(document.activeElement) === true + document.activeElement !== root + && root?.contains(document.activeElement) === true ) { root.focus() } } else if ( refocusRef.value !== null - && (e === void 0 || (root !== null && root.contains(e.target) === true)) + && ((e === void 0) || (root?.contains(e.target) === true)) ) { refocusRef.value.focus() } diff --git a/ui/src/composables/private.use-validate/use-validate.js b/ui/src/composables/private.use-validate/use-validate.js index 6d52d7703e7..00a23c8079f 100644 --- a/ui/src/composables/private.use-validate/use-validate.js +++ b/ui/src/composables/private.use-validate/use-validate.js @@ -201,7 +201,7 @@ export default function (focused, innerLoading) { const debouncedValidate = debounce(validate, 0) onBeforeUnmount(() => { - unwatchRules !== void 0 && unwatchRules() + unwatchRules?.() debouncedValidate.cancel() }) diff --git a/ui/src/directives/intersection/Intersection.js b/ui/src/directives/intersection/Intersection.js index 56e358fe394..c7997685f84 100644 --- a/ui/src/directives/intersection/Intersection.js +++ b/ui/src/directives/intersection/Intersection.js @@ -28,7 +28,7 @@ function update (el, ctx, value) { if (changed === true) { ctx.cfg = cfg - ctx.observer !== void 0 && ctx.observer.unobserve(el) + ctx.observer?.unobserve(el) ctx.observer = new IntersectionObserver(([ entry ]) => { if (typeof ctx.handler === 'function') { @@ -62,7 +62,7 @@ function destroy (el) { const ctx = el.__qvisible if (ctx !== void 0) { - ctx.observer !== void 0 && ctx.observer.unobserve(el) + ctx.observer?.unobserve(el) delete el.__qvisible } } diff --git a/ui/src/directives/morph/Morph.js b/ui/src/directives/morph/Morph.js index 51a2b65abac..5d0fc939c91 100644 --- a/ui/src/directives/morph/Morph.js +++ b/ui/src/directives/morph/Morph.js @@ -44,7 +44,7 @@ function trigger (group) { }, ...to.opts, onEnd (dir, aborted) { - to.opts.onEnd !== void 0 && to.opts.onEnd(dir, aborted) + to.opts.onEnd?.(dir, aborted) if (aborted === true) return @@ -206,7 +206,7 @@ export default createDirective(__QUASAR_SSR_SERVER__ group.queue = group.queue.filter(item => item !== ctx) if (group.queue.length === 0) { - group.cancel !== void 0 && group.cancel() + group.cancel?.() delete morphGroups[ ctx.group ] } } diff --git a/ui/src/directives/mutation/Mutation.js b/ui/src/directives/mutation/Mutation.js index c0d767faa28..82f5e7d80f0 100644 --- a/ui/src/directives/mutation/Mutation.js +++ b/ui/src/directives/mutation/Mutation.js @@ -12,7 +12,7 @@ const defaultCfg = { function update (el, ctx, value) { ctx.handler = value - ctx.observer !== void 0 && ctx.observer.disconnect() + ctx.observer?.disconnect() ctx.observer = new MutationObserver(list => { if (typeof ctx.handler === 'function') { @@ -30,7 +30,7 @@ function destroy (el) { const ctx = el.__qmutation if (ctx !== void 0) { - ctx.observer !== void 0 && ctx.observer.disconnect() + ctx.observer?.disconnect() delete el.__qmutation } } diff --git a/ui/src/directives/touch-hold/TouchHold.js b/ui/src/directives/touch-hold/TouchHold.js index 60e834c84b6..07ddec134b9 100644 --- a/ui/src/directives/touch-hold/TouchHold.js +++ b/ui/src/directives/touch-hold/TouchHold.js @@ -106,7 +106,7 @@ export default createDirective(__QUASAR_SSR_SERVER__ cleanEvt(ctx, 'temp') // delay needed otherwise selection still occurs - ctx.styleCleanup !== void 0 && ctx.styleCleanup(ctx.triggered) + ctx.styleCleanup?.(ctx.triggered) if (ctx.triggered === true) { evt !== void 0 && stopAndPrevent(evt) @@ -166,7 +166,7 @@ export default createDirective(__QUASAR_SSR_SERVER__ cleanEvt(ctx, 'temp') ctx.timer !== void 0 && clearTimeout(ctx.timer) - ctx.styleCleanup !== void 0 && ctx.styleCleanup() + ctx.styleCleanup?.() delete el.__qtouchhold } diff --git a/ui/src/directives/touch-pan/TouchPan.js b/ui/src/directives/touch-pan/TouchPan.js index e47a5be9333..f13989ad4ae 100644 --- a/ui/src/directives/touch-pan/TouchPan.js +++ b/ui/src/directives/touch-pan/TouchPan.js @@ -355,7 +355,7 @@ export default createDirective(__QUASAR_SSR_SERVER__ client.is.firefox === true && preventDraggable(el, false) if (abort === true) { - ctx.styleCleanup !== void 0 && ctx.styleCleanup() + ctx.styleCleanup?.() if (ctx.event.detected !== true && ctx.initialEvent !== void 0) { ctx.initialEvent.target.dispatchEvent(ctx.initialEvent.event) @@ -426,7 +426,7 @@ export default createDirective(__QUASAR_SSR_SERVER__ cleanEvt(ctx, 'temp') client.is.firefox === true && preventDraggable(el, false) - ctx.styleCleanup !== void 0 && ctx.styleCleanup() + ctx.styleCleanup?.() delete el.__qtouchpan } diff --git a/ui/src/directives/touch-repeat/TouchRepeat.js b/ui/src/directives/touch-repeat/TouchRepeat.js index 0c86adc9e55..4357d91b829 100644 --- a/ui/src/directives/touch-repeat/TouchRepeat.js +++ b/ui/src/directives/touch-repeat/TouchRepeat.js @@ -186,8 +186,8 @@ export default createDirective(__QUASAR_SSR_SERVER__ end (evt) { if (ctx.event === void 0) return - ctx.styleCleanup !== void 0 && ctx.styleCleanup(true) - evt !== void 0 && ctx.event.repeatCount > 0 && stopAndPrevent(evt) + ctx.styleCleanup?.(true) + if ((evt !== void 0) && (ctx.event.repeatCount > 0)) stopAndPrevent(evt) cleanEvt(ctx, 'temp') @@ -248,7 +248,7 @@ export default createDirective(__QUASAR_SSR_SERVER__ cleanEvt(ctx, 'main') cleanEvt(ctx, 'temp') - ctx.styleCleanup !== void 0 && ctx.styleCleanup() + ctx.styleCleanup?.() delete el.__qtouchrepeat } diff --git a/ui/src/directives/touch-swipe/TouchSwipe.js b/ui/src/directives/touch-swipe/TouchSwipe.js index e012b670a0f..1183b7a86c5 100644 --- a/ui/src/directives/touch-swipe/TouchSwipe.js +++ b/ui/src/directives/touch-swipe/TouchSwipe.js @@ -221,8 +221,8 @@ export default createDirective(__QUASAR_SSR_SERVER__ cleanEvt(ctx, 'temp') client.is.firefox === true && preventDraggable(el, false) - ctx.styleCleanup !== void 0 && ctx.styleCleanup(true) - evt !== void 0 && ctx.event.dir !== false && stopAndPrevent(evt) + ctx.styleCleanup?.(true) + if ((evt !== void 0) && (ctx.event.dir !== false)) stopAndPrevent(evt) ctx.event = void 0 } @@ -268,7 +268,7 @@ export default createDirective(__QUASAR_SSR_SERVER__ cleanEvt(ctx, 'temp') client.is.firefox === true && preventDraggable(el, false) - ctx.styleCleanup !== void 0 && ctx.styleCleanup() + ctx.styleCleanup?.() delete el.__qtouchswipe } diff --git a/ui/src/plugins/loading/Loading.js b/ui/src/plugins/loading/Loading.js index bd5940f225f..d8f3431135a 100644 --- a/ui/src/plugins/loading/Loading.js +++ b/ui/src/plugins/loading/Loading.js @@ -34,7 +34,7 @@ const originalDefaults = { const defaults = { ...originalDefaults } function registerProps (opts) { - if (opts && opts.group !== void 0 && activeGroups[ opts.group ] !== void 0) { + if (opts?.group !== void 0 && activeGroups[ opts.group ] !== void 0) { return Object.assign(activeGroups[ opts.group ], opts) } diff --git a/ui/src/plugins/notify/Notify.js b/ui/src/plugins/notify/Notify.js index 5396d7e2ed4..541861a6b84 100644 --- a/ui/src/plugins/notify/Notify.js +++ b/ui/src/plugins/notify/Notify.js @@ -149,7 +149,7 @@ function addNotification (config, $q, originalApi) { ? defaults.actions : [] ).concat( - notifTypes[ config.type ] !== void 0 && Array.isArray(notifTypes[ config.type ].actions) === true + Array.isArray(notifTypes[ config.type ]?.actions) === true ? notifTypes[ config.type ].actions : [] ) diff --git a/ui/src/plugins/private.body/Body.js b/ui/src/plugins/private.body/Body.js index fc9e9c90a49..b8cceb32641 100644 --- a/ui/src/plugins/private.body/Body.js +++ b/ui/src/plugins/private.body/Body.js @@ -100,7 +100,7 @@ export default { const { $q, ssrContext } = opts const cls = getBodyClasses($q.platform, $q.config) - if ($q.config.screen !== void 0 && $q.config.screen.bodyClass === true) { + if ($q.config.screen?.bodyClass === true) { cls.push('screen--xs') } diff --git a/ui/src/plugins/private.history/History.js b/ui/src/plugins/private.history/History.js index 100264b0a80..58f7cec3578 100644 --- a/ui/src/plugins/private.history/History.js +++ b/ui/src/plugins/private.history/History.js @@ -51,7 +51,7 @@ export default { const qConf = $q.config[ cordova === true ? 'cordova' : 'capacitor' ] - if (qConf !== void 0 && qConf.backButton === false) return + if (qConf?.backButton === false) return // if the '@capacitor/app' plugin is not installed // then we got nothing to do diff --git a/ui/src/plugins/screen/Screen.js b/ui/src/plugins/screen/Screen.js index ccf6262eea8..84e60a4a707 100644 --- a/ui/src/plugins/screen/Screen.js +++ b/ui/src/plugins/screen/Screen.js @@ -71,7 +71,7 @@ export default createReactivePlugin({ visualViewport.height * visualViewport.scale + window.innerHeight - scrollingElement.clientHeight ] - const classes = $q.config.screen !== void 0 && $q.config.screen.bodyClasses === true + const classes = $q.config.screen?.bodyClasses === true this.__update = force => { const [ w, h ] = getSize() diff --git a/ui/src/utils/morph/morph.js b/ui/src/utils/morph/morph.js index 68bd7b11881..68586b36b3d 100644 --- a/ui/src/utils/morph/morph.js +++ b/ui/src/utils/morph/morph.js @@ -256,7 +256,7 @@ export default function morph (_options) { // we clean the clone of the initial element elFromClone.remove() - elFromTween !== void 0 && elFromTween.remove() + elFromTween?.remove() options.hideFromClone === true && elFromClone.classList.remove('q-morph--internal') @@ -301,7 +301,7 @@ export default function morph (_options) { // we clean the clone of the initial element elFromClone.remove() - elFromTween !== void 0 && elFromTween.remove() + elFromTween?.remove() options.hideFromClone === true && elFromClone.classList.remove('q-morph--internal') @@ -521,7 +521,7 @@ export default function morph (_options) { // we clean the spacers elFromClone.remove() elToClone.remove() - elFromTween !== void 0 && elFromTween.remove() + elFromTween?.remove() // cancel will be no longer available cancel = () => false @@ -677,9 +677,9 @@ export default function morph (_options) { }) const cleanup = abort => { - animationFromClone !== void 0 && animationFromClone.cancel() - animationFromTween !== void 0 && animationFromTween.cancel() - animationToClone !== void 0 && animationToClone.cancel() + animationFromClone?.cancel() + animationFromTween?.cancel() + animationToClone?.cancel() animationTo.cancel() animationTo.removeEventListener('finish', cleanup) @@ -721,9 +721,9 @@ export default function morph (_options) { endElementTo = endElementTo !== true - animationFromClone !== void 0 && animationFromClone.reverse() - animationFromTween !== void 0 && animationFromTween.reverse() - animationToClone !== void 0 && animationToClone.reverse() + animationFromClone?.reverse() + animationFromTween?.reverse() + animationToClone?.reverse() animationTo.reverse() return true diff --git a/ui/src/utils/open-url/open-url.js b/ui/src/utils/open-url/open-url.js index 4a1541fe4e2..d5862ee920b 100644 --- a/ui/src/utils/open-url/open-url.js +++ b/ui/src/utils/open-url/open-url.js @@ -22,10 +22,10 @@ function openWindow (url, reject, windowFeatures) { let open = window.open if (Platform.is.cordova === true) { - if (cordova !== void 0 && cordova.InAppBrowser !== void 0 && cordova.InAppBrowser.open !== void 0) { + if (cordova?.InAppBrowser?.open !== void 0) { open = cordova.InAppBrowser.open } - else if (navigator !== void 0 && navigator.app !== void 0) { + else if (navigator?.app !== void 0) { return navigator.app.loadUrl(url, { openExternal: true }) diff --git a/ui/src/utils/private.dialog/create-dialog.js b/ui/src/utils/private.dialog/create-dialog.js index ff6ef516f10..b6143455306 100644 --- a/ui/src/utils/private.dialog/create-dialog.js +++ b/ui/src/utils/private.dialog/create-dialog.js @@ -57,7 +57,7 @@ export default function (DefaultComponent, supportsCustomComponent, parentApp) { const el = createGlobalNode(false, 'dialog') const applyState = cmd => { - if (dialogRef.value !== null && dialogRef.value[ cmd ] !== void 0) { + if (dialogRef.value?.[ cmd ] !== void 0) { dialogRef.value[ cmd ]() return } diff --git a/ui/src/utils/private.portal/portal.js b/ui/src/utils/private.portal/portal.js index 4023a60c584..6e8801cc49a 100644 --- a/ui/src/utils/private.portal/portal.js +++ b/ui/src/utils/private.portal/portal.js @@ -25,7 +25,7 @@ export function closePortalMenus (proxy, evt) { // and hide it too const parent = getParentProxy(proxy) - if (parent !== void 0 && parent.$options.name === 'QPopupProxy') { + if (parent?.$options.name === 'QPopupProxy') { proxy.hide(evt) return parent } From 98d2b9930c5318967f0f5073e6b36a0697dee009 Mon Sep 17 00:00:00 2001 From: Razvan Stoenescu Date: Sat, 1 Mar 2025 17:38:40 +0200 Subject: [PATCH 26/32] feat(ui): further modernize codebase with optional chaining operator --- ui/src/components/dialog/QDialog.js | 2 +- ui/src/components/drawer/QDrawer.js | 4 +--- ui/src/components/editor/editor-utils.js | 2 +- ui/src/components/menu/QMenu.js | 5 +++-- ui/src/components/pull-to-refresh/QPullToRefresh.js | 2 +- ui/src/components/resize-observer/QResizeObserver.js | 2 +- ui/src/components/select/QSelect.js | 4 +++- ui/src/components/slide-transition/QSlideTransition.js | 2 +- ui/src/components/tree/QTree.js | 5 ++--- ui/src/components/virtual-scroll/use-virtual-scroll.js | 2 +- ui/src/composables/private.use-field/use-field.js | 4 ++-- ui/src/composables/private.use-file/use-file.js | 7 +++++-- ui/src/utils/morph/morph.js | 5 ++++- ui/src/utils/open-url/open-url.js | 2 +- ui/src/utils/private.dialog/create-dialog.js | 2 +- 15 files changed, 28 insertions(+), 22 deletions(-) diff --git a/ui/src/components/dialog/QDialog.js b/ui/src/components/dialog/QDialog.js index 3a68764c99c..74a9b949534 100644 --- a/ui/src/components/dialog/QDialog.js +++ b/ui/src/components/dialog/QDialog.js @@ -230,7 +230,7 @@ export default createComponent({ hidePortal() if (refocusTarget !== null) { - ((evt && evt.type.indexOf('key') === 0 + ((evt?.type.indexOf('key') === 0 ? refocusTarget.closest('[tabindex]:not([tabindex^="-"])') : void 0 ) || refocusTarget).focus() diff --git a/ui/src/components/drawer/QDrawer.js b/ui/src/components/drawer/QDrawer.js index 46f35e21f91..725db89ea77 100644 --- a/ui/src/components/drawer/QDrawer.js +++ b/ui/src/components/drawer/QDrawer.js @@ -472,9 +472,7 @@ export default createComponent({ timerMini = setTimeout(() => { timerMini = null flagMiniAnimate.value = false - if (vm && vm.proxy && vm.proxy.$el) { - vm.proxy.$el.classList.remove('q-drawer--mini-animate') - } + vm?.proxy?.$el?.classList.remove('q-drawer--mini-animate') }, 150) } diff --git a/ui/src/components/editor/editor-utils.js b/ui/src/components/editor/editor-utils.js index 31f721f2fa9..80cf0d9dffc 100644 --- a/ui/src/components/editor/editor-utils.js +++ b/ui/src/components/editor/editor-utils.js @@ -54,7 +54,7 @@ function getBtn (eVm, btn, clickHandler, active = false) { disable: btn.disable ? (typeof btn.disable === 'function' ? btn.disable(eVm) : true) : false, size: 'sm', onClick (e) { - clickHandler && clickHandler() + clickHandler?.() run(e, btn, eVm) } }, () => child) diff --git a/ui/src/components/menu/QMenu.js b/ui/src/components/menu/QMenu.js index d2f0fe34dfd..a3a358ea3df 100644 --- a/ui/src/components/menu/QMenu.js +++ b/ui/src/components/menu/QMenu.js @@ -177,7 +177,7 @@ export default createComponent({ addFocusFn(() => { let node = innerRef.value - if (node && node.contains(document.activeElement) !== true) { + if (node && (node.contains(document.activeElement) !== true)) { node = node.querySelector('[autofocus][tabindex], [data-autofocus][tabindex]') || node.querySelector('[autofocus] [tabindex], [data-autofocus] [tabindex]') || node.querySelector('[autofocus], [data-autofocus]') @@ -256,10 +256,11 @@ export default createComponent({ || evt.qClickOutside !== true ) ) { - ((evt && evt.type.indexOf('key') === 0 + ((evt?.type.indexOf('key') === 0 ? refocusTarget.closest('[tabindex]:not([tabindex^="-"])') : void 0 ) || refocusTarget).focus() + refocusTarget = null } diff --git a/ui/src/components/pull-to-refresh/QPullToRefresh.js b/ui/src/components/pull-to-refresh/QPullToRefresh.js index 0bef55d5ca9..2ac570043f1 100644 --- a/ui/src/components/pull-to-refresh/QPullToRefresh.js +++ b/ui/src/components/pull-to-refresh/QPullToRefresh.js @@ -149,7 +149,7 @@ export default createComponent({ timer = setTimeout(() => { timer = null animating.value = false - done && done() + done?.() }, 300) } diff --git a/ui/src/components/resize-observer/QResizeObserver.js b/ui/src/components/resize-observer/QResizeObserver.js index c889c61820b..8b97cbe4cac 100644 --- a/ui/src/components/resize-observer/QResizeObserver.js +++ b/ui/src/components/resize-observer/QResizeObserver.js @@ -117,7 +117,7 @@ export default createComponent({ function onObjLoad () { cleanup() - if (targetEl && targetEl.contentDocument) { + if (targetEl?.contentDocument) { curDocView = targetEl.contentDocument.defaultView curDocView.addEventListener('resize', trigger, listenOpts.passive) emitEvent() diff --git a/ui/src/components/select/QSelect.js b/ui/src/components/select/QSelect.js index 142c33319d1..2f06dcc4724 100644 --- a/ui/src/components/select/QSelect.js +++ b/ui/src/components/select/QSelect.js @@ -545,7 +545,9 @@ export default createComponent({ return } - (hasDialog !== true || dialogFieldFocused.value === true) && state.focus() + if (hasDialog !== true || dialogFieldFocused.value === true) { + state.focus() + } selectInputText() diff --git a/ui/src/components/slide-transition/QSlideTransition.js b/ui/src/components/slide-transition/QSlideTransition.js index 6b31805ba45..cf2af63ab73 100644 --- a/ui/src/components/slide-transition/QSlideTransition.js +++ b/ui/src/components/slide-transition/QSlideTransition.js @@ -20,7 +20,7 @@ export default createComponent({ let timer = null, timerFallback = null, animListener, lastEvent function cleanup () { - doneFn && doneFn() + doneFn?.() doneFn = null animating = false diff --git a/ui/src/components/tree/QTree.js b/ui/src/components/tree/QTree.js index 0d8b23cbe6f..a9266198957 100644 --- a/ui/src/components/tree/QTree.js +++ b/ui/src/components/tree/QTree.js @@ -334,7 +334,7 @@ export default createComponent({ node[ props.childrenKey ] = Array.isArray(children) === true ? children : [] nextTick(() => { const localMeta = meta.value[ key ] - if (localMeta && localMeta.isParent === true) { + if (localMeta?.isParent === true) { localSetExpanded(key, true) } }) @@ -627,8 +627,7 @@ export default createComponent({ } function blur (key) { - const blurTarget = blurTargets[ key ] - blurTarget && blurTarget.focus() + blurTargets[ key ]?.focus() } function onClick (node, meta, e, keyboard) { diff --git a/ui/src/components/virtual-scroll/use-virtual-scroll.js b/ui/src/components/virtual-scroll/use-virtual-scroll.js index 29db10c03f4..673545ab589 100644 --- a/ui/src/components/virtual-scroll/use-virtual-scroll.js +++ b/ui/src/components/virtual-scroll/use-virtual-scroll.js @@ -40,7 +40,7 @@ const setOverflowAnchor = __QUASAR_SSR__ || window.getComputedStyle(document.bod const el = children[ index ] - if (el && el.dataset) { + if (el?.dataset) { el.dataset.qVsAnchor = '' } }) diff --git a/ui/src/composables/private.use-field/use-field.js b/ui/src/composables/private.use-field/use-field.js index f63c2c1a6e9..a2e015b4a64 100644 --- a/ui/src/composables/private.use-field/use-field.js +++ b/ui/src/composables/private.use-field/use-field.js @@ -269,8 +269,8 @@ export default function (state) { if (target && (el === null || el.id !== state.targetUid.value)) { target.hasAttribute('tabindex') === true || (target = target.querySelector('[tabindex]')) - if (target && target !== el) { - target.focus({ preventScroll: true }) + if (target !== el) { + target?.focus({ preventScroll: true }) } } } diff --git a/ui/src/composables/private.use-file/use-file.js b/ui/src/composables/private.use-file/use-file.js index afcffac2dc8..4926f356752 100644 --- a/ui/src/composables/private.use-file/use-file.js +++ b/ui/src/composables/private.use-file/use-file.js @@ -19,7 +19,10 @@ function filterFiles (files, rejectedFiles, failedPropValidation, filterFn) { } function stopAndPreventDrag (e) { - e && e.dataTransfer && (e.dataTransfer.dropEffect = 'copy') + if (e?.dataTransfer) { + e.dataTransfer.dropEffect = 'copy' + } + stopAndPrevent(e) } @@ -75,7 +78,7 @@ export default function ({ } else { const input = getFileInput() - input && input !== e.target && input.click(e) + if (input !== e.target) input?.click(e) } } } diff --git a/ui/src/utils/morph/morph.js b/ui/src/utils/morph/morph.js index 68586b36b3d..9e3111c5360 100644 --- a/ui/src/utils/morph/morph.js +++ b/ui/src/utils/morph/morph.js @@ -516,7 +516,10 @@ export default function morph (_options) { elTo.style.cssText = elToStyleSaved elTo.className = elToClassSaved } - elToClone.parentNode === elToParent && elToParent.insertBefore(elTo, elToClone) + + if (elToClone.parentNode === elToParent) { + elToParent.insertBefore(elTo, elToClone) + } // we clean the spacers elFromClone.remove() diff --git a/ui/src/utils/open-url/open-url.js b/ui/src/utils/open-url/open-url.js index d5862ee920b..52832c6b220 100644 --- a/ui/src/utils/open-url/open-url.js +++ b/ui/src/utils/open-url/open-url.js @@ -39,7 +39,7 @@ function openWindow (url, reject, windowFeatures) { return win } else { - reject && reject() + reject?.() } } diff --git a/ui/src/utils/private.dialog/create-dialog.js b/ui/src/utils/private.dialog/create-dialog.js index b6143455306..8a0f4b33799 100644 --- a/ui/src/utils/private.dialog/create-dialog.js +++ b/ui/src/utils/private.dialog/create-dialog.js @@ -64,7 +64,7 @@ export default function (DefaultComponent, supportsCustomComponent, parentApp) { const target = vm.$.subTree - if (target && target.component) { + if (target?.component) { // account for "script setup" way of declaring component if (target.component.proxy && target.component.proxy[ cmd ]) { target.component.proxy[ cmd ]() From 6780026a789a045dc788d74b18860eaa7d3fb68e Mon Sep 17 00:00:00 2001 From: Razvan Stoenescu Date: Sat, 1 Mar 2025 17:43:06 +0200 Subject: [PATCH 27/32] feat(ui): Ability to avoid Quasar changing focused element by defining evt.qAvoidFocus flag to true #17803 --- ui/src/components/btn/QBtn.js | 4 ++-- ui/src/components/editor/editor-utils.js | 2 +- ui/src/components/expansion-item/QExpansionItem.js | 5 ++++- ui/src/components/fab/QFab.js | 4 ++-- ui/src/components/item/QItem.js | 2 +- ui/src/components/tabs/use-tab.js | 4 ++-- .../private.use-refocus-target/use-refocus-target.js | 2 ++ 7 files changed, 14 insertions(+), 9 deletions(-) diff --git a/ui/src/components/btn/QBtn.js b/ui/src/components/btn/QBtn.js index 4f957ae4970..c494236ea13 100644 --- a/ui/src/components/btn/QBtn.js +++ b/ui/src/components/btn/QBtn.js @@ -130,7 +130,7 @@ export default createComponent({ // required for iOS and desktop Safari && el.contains(rootRef.value) === false ) { - rootRef.value.focus() + e.qAvoidFocus !== true && rootRef.value.focus() const onClickCleanup = () => { document.removeEventListener('keydown', stopAndPrevent, true) @@ -158,7 +158,7 @@ export default createComponent({ if (e.defaultPrevented !== true) { // focus external button if the focus helper was focused before - rootRef.value.focus() + e.qAvoidFocus !== true && rootRef.value.focus() keyboardTarget = rootRef.value rootRef.value.classList.add('q-btn--active') diff --git a/ui/src/components/editor/editor-utils.js b/ui/src/components/editor/editor-utils.js index 80cf0d9dffc..137d0a33bc8 100644 --- a/ui/src/components/editor/editor-utils.js +++ b/ui/src/components/editor/editor-utils.js @@ -120,7 +120,7 @@ function getDropdown (eVm, btn) { dense: true, onClick (e) { closeDropdown() - eVm.contentRef.value?.focus() + e?.qAvoidFocus !== true && eVm.contentRef.value?.focus() eVm.caret.restore() run(e, btn, eVm) } diff --git a/ui/src/components/expansion-item/QExpansionItem.js b/ui/src/components/expansion-item/QExpansionItem.js index 1c0f1d90313..4cd090be73c 100644 --- a/ui/src/components/expansion-item/QExpansionItem.js +++ b/ui/src/components/expansion-item/QExpansionItem.js @@ -164,7 +164,10 @@ export default createComponent({ } function toggleIcon (e, keyboard) { - keyboard !== true && blurTargetRef.value?.focus() + if (keyboard !== true && e.qAvoidFocus !== true) { + blurTargetRef.value?.focus() + } + toggle(e) stopAndPrevent(e) } diff --git a/ui/src/components/fab/QFab.js b/ui/src/components/fab/QFab.js index b8d1ac461e6..06ee6f6963a 100644 --- a/ui/src/components/fab/QFab.js +++ b/ui/src/components/fab/QFab.js @@ -128,8 +128,8 @@ export default createComponent({ onChildClick (evt) { hide(evt) - if (triggerRef.value !== null) { - triggerRef.value.$el.focus() + if (evt?.qAvoidFocus !== true) { + triggerRef.value?.$el.focus() } } }) diff --git a/ui/src/components/item/QItem.js b/ui/src/components/item/QItem.js index aa7f64da77c..05c3228b47e 100644 --- a/ui/src/components/item/QItem.js +++ b/ui/src/components/item/QItem.js @@ -92,7 +92,7 @@ export default createComponent({ function onClick (e) { if (isClickable.value === true) { - if (blurTargetRef.value !== null) { + if (blurTargetRef.value !== null && e.qAvoidFocus !== true) { if (e.qKeyEvent !== true && document.activeElement === rootRef.value) { blurTargetRef.value.focus() } diff --git a/ui/src/components/tabs/use-tab.js b/ui/src/components/tabs/use-tab.js index 282e041a656..cbd8e4a4fa5 100644 --- a/ui/src/components/tabs/use-tab.js +++ b/ui/src/components/tabs/use-tab.js @@ -99,8 +99,8 @@ export default function (props, slots, emit, routeData) { )) function onClick (e, keyboard) { - if (keyboard !== true && blurTargetRef.value !== null) { - blurTargetRef.value.focus() + if (keyboard !== true && e?.qAvoidFocus !== true) { + blurTargetRef.value?.focus() } if (props.disable === true) { diff --git a/ui/src/composables/private.use-refocus-target/use-refocus-target.js b/ui/src/composables/private.use-refocus-target/use-refocus-target.js index acd077ba9ab..07f3a14ca6a 100644 --- a/ui/src/composables/private.use-refocus-target/use-refocus-target.js +++ b/ui/src/composables/private.use-refocus-target/use-refocus-target.js @@ -18,6 +18,8 @@ export default function (props, rootRef) { function refocusTarget (e) { const root = rootRef.value + if (e?.qAvoidFocus === true) return + if (e?.type.indexOf('key') === 0) { if ( document.activeElement !== root From 17f8a47f58e473f3176a404caa8a7d46e6c86c13 Mon Sep 17 00:00:00 2001 From: Stefan van Herwijnen <5968971+stefanvanherwijnen@users.noreply.github.com> Date: Sat, 1 Mar 2025 17:01:37 +0100 Subject: [PATCH 28/32] feat(ui): add pure CSS icons support (#17762) * feat(ui): add pure CSS icons support * Update QIcon.js * Update QIcon.js --------- Co-authored-by: Razvan Stoenescu --- ui/src/components/icon/QIcon.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ui/src/components/icon/QIcon.js b/ui/src/components/icon/QIcon.js index de97ee20a77..645a583a613 100644 --- a/ui/src/components/icon/QIcon.js +++ b/ui/src/components/icon/QIcon.js @@ -20,7 +20,8 @@ const libMap = { 'ion-logo': ionFn, 'iconfont ': sameFn, 'ti-': i => `themify-icon ${ i }`, - 'bi-': i => `bootstrap-icons ${ i }` + 'bi-': i => `bootstrap-icons ${ i }`, + 'i-': sameFn // UnoCSS pure icons } const matMap = { From 3500f4e7f6d1065680b2033b0e28056ee7fa809e Mon Sep 17 00:00:00 2001 From: Razvan Stoenescu Date: Sat, 1 Mar 2025 18:15:07 +0200 Subject: [PATCH 29/32] chore(ui/lang): remove comments from ur-PK language pack --- ui/lang/ur-PK.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/ui/lang/ur-PK.js b/ui/lang/ur-PK.js index fb65f51aaf4..8802476c286 100644 --- a/ui/lang/ur-PK.js +++ b/ui/lang/ur-PK.js @@ -1,9 +1,3 @@ -/* - * Urdu (Pakistan) Language Pack for Quasar Framework - * ISO code: ur-PK - * Author: [Your Name or Team] - */ - const days = 'اتوار_پیر_منگل_بدھ_جمعرات_جمعہ_ہفتہ'.split('_') const daysShort = 'ات_پیر_منگ_بدھ_جمعر_جمعہ_ہف'.split('_') // You can refine abbreviations if needed; e.g. From 5611ef9a7e04d708628940d7efa1ec5a8efddd34 Mon Sep 17 00:00:00 2001 From: Razvan Stoenescu Date: Sat, 1 Mar 2025 18:17:33 +0200 Subject: [PATCH 30/32] chore(ui): Bump version --- ui/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/package.json b/ui/package.json index f48cadc2a80..b0dde0af43c 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,6 @@ { "name": "quasar", - "version": "2.17.7", + "version": "2.18.0", "description": "Build high-performance VueJS user interfaces (SPA, PWA, SSR, Mobile and Desktop) in record time", "type": "module", "module": "dist/quasar.client.js", From dd4300bb38cef1dbceb9924765548e4336cbeb38 Mon Sep 17 00:00:00 2001 From: Razvan Stoenescu Date: Mon, 3 Mar 2025 12:23:33 +0200 Subject: [PATCH 31/32] fix(docs): mdi v7 on QIcon page #17866 --- docs/src/pages/vue-components/icon.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/src/pages/vue-components/icon.md b/docs/src/pages/vue-components/icon.md index 3d5416df7cf..f07d38671d5 100644 --- a/docs/src/pages/vue-components/icon.md +++ b/docs/src/pages/vue-components/icon.md @@ -61,7 +61,7 @@ If you are using webfont-based icons, make sure that you [installed the icon lib | fontawesome-v6 | fa-[solid,regular,brands] fa- | "fa-solid fa-ambulance" | QIcon "name" property is same as "class" attribute value in Fontawesome docs examples (where they show `` tags) | | fontawesome-v6 Pro| fa-[solid,regular,brands,thin,light,duotone] fa- | "fa-solid fa-ambulance" | Note: a license must be purchased from Fontawesome for this functionality) | | fontawesome-v5 | fa[s,r,l,d,b] fa- | "fas fa-ambulance" | QIcon "name" property is same as "class" attribute value in Fontawesome docs examples (where they show `` tags) | -| mdi-v6/v5/v4/v3 | mdi- | mdi-alert-circle-outline | Notice the use of dash characters; Use only one of mdi-v6, mdi-v5, mdi-v4 or mdi-v3 | +| mdi-v7/v6/v5/v4/v3 | mdi- | mdi-alert-circle-outline | Notice the use of dash characters; Use only one of mdi-v7, mdi-v6, mdi-v5, mdi-v4 or mdi-v3 | | eva-icons | eva- | eva-shield-outline, eva-activity-outline | Notice the use of dash characters | | themify | ti- | ti-hand-point-up | Notice the use of dash characters | | line-awesome | la[s,r,l,d,b] la- | "las la-atom" | QIcon "name" property is same as "class" attribute value in Line Awesome docs examples (where they show `` tags); **@quasar/extras v1.5+** | @@ -204,8 +204,7 @@ If you are only using svg icons (and have configured a [Quasar Icon Set](/option | Material Symbols Outlined (Google) | svg-material-symbols-outlined | @quasar/extras/material-symbols-outlined | @quasar/extras v1.14+; | | Material Symbols Sharp (Google) | svg-material-symbols-sharp | @quasar/extras/material-symbols-sharp | @quasar/extras v1.14+ | | Material Symbols Round (Google) | svg-material-symbols-rounded | @quasar/extras/material-symbols-rounded | @quasar/extras v1.14+ | -| MDI (Material Design Icons) (v3-v5) | svg-mdi-v5 | @quasar/extras/mdi-v5 | | -| MDI (Material Design Icons) v6 | svg-mdi-v7 | @quasar/extras/mdi-v6 | @quasar/extras v1.11+ | +| MDI (Material Design Icons) v3-v7 | svg-mdi-v7 | @quasar/extras/mdi-v7 etc | @quasar/extras v1.11+ | | Font Awesome v6 | svg-fontawesome-v6 | @quasar/extras/fontawesome-v6 | @quasar/extras v1.13+ | | Font Awesome | svg-fontawesome-v5 | @quasar/extras/fontawesome-v5 | | | Ionicons v6 | svg-ionicons-v6 | @quasar/extras/ionicons-v6 | @quasar/extras v1.12+ | From 39f8a63290edaa86e85b150611f197a16f1725e6 Mon Sep 17 00:00:00 2001 From: Razvan Stoenescu Date: Mon, 3 Mar 2025 12:33:28 +0200 Subject: [PATCH 32/32] feat(docs): replace mdi-v6 with mdi-v7 #17866 --- docs/src/assets/docs-homepage.js | 2 +- docs/src/assets/links.header.js | 2 +- docs/src/assets/links.integrations.js | 2 +- docs/src/assets/links.social.js | 6 +++--- docs/src/components/DocApi.vue | 2 +- docs/src/components/DocApiEntry.js | 2 +- docs/src/components/DocLink.vue | 2 +- docs/src/components/DocTree.vue | 2 +- docs/src/components/SurveyCountdown.vue | 2 +- docs/src/layouts/builder/LayoutBuilder.vue | 2 +- docs/src/layouts/doc-layout/DocDrawerMenu.vue | 2 +- docs/src/layouts/doc-layout/DocDrawerToc.vue | 2 +- docs/src/layouts/doc-layout/DocHeader.vue | 2 +- docs/src/layouts/doc-layout/DocHeaderMenu.js | 2 +- docs/src/layouts/doc-layout/DocHeaderTextLinks.vue | 2 +- docs/src/layouts/doc-layout/DocLayout.vue | 2 +- docs/src/layouts/doc-layout/DocPage.vue | 2 +- docs/src/layouts/doc-layout/DocPageMenu.js | 2 +- docs/src/layouts/doc-layout/search/ResultEmpty.vue | 2 +- docs/src/layouts/doc-layout/search/ResultError.vue | 2 +- docs/src/layouts/doc-layout/search/SearchResults.vue | 2 +- docs/src/layouts/gallery/LayoutGalleryPage.vue | 2 +- docs/src/pages/layout/gallery/LayoutGallery.vue | 2 +- docs/src/pages/layout/grid/flex-playground/FlexChild.vue | 2 +- .../layout/grid/flex-playground/FlexPlaygroundDemo.vue | 2 +- docs/src/pages/start/release-notes/PackageReleases.vue | 2 +- docs/src/pages/style/theme-builder/ThemePicker.vue | 2 +- docs/src/pages/vue-components/icon.md | 8 ++++---- 28 files changed, 33 insertions(+), 33 deletions(-) diff --git a/docs/src/assets/docs-homepage.js b/docs/src/assets/docs-homepage.js index a77aff158f8..45142aff97a 100644 --- a/docs/src/assets/docs-homepage.js +++ b/docs/src/assets/docs-homepage.js @@ -8,7 +8,7 @@ import { mdiImageSizeSelectSmall, mdiTable, mdiHumanMaleBoard -} from '@quasar/extras/mdi-v6' +} from '@quasar/extras/mdi-v7' export const mostUsedPages = [ { diff --git a/docs/src/assets/links.header.js b/docs/src/assets/links.header.js index b68fa218d17..52a6eaa71a4 100644 --- a/docs/src/assets/links.header.js +++ b/docs/src/assets/links.header.js @@ -5,7 +5,7 @@ import { fasCubes, fasBolt } from '@quasar/extras/fontawesome-v5' import { mdiBug, mdiClipboardText, mdiCodepen, mdiFlare, mdiFlask, mdiGithub, mdiJsfiddle, mdiPaletteSwatch, mdiPuzzle, mdiShoppingMusic, mdiStarCircle, mdiThemeLightDark, mdiViewDashboard, mdiSecurity, mdiMathIntegralBox -} from '@quasar/extras/mdi-v6' +} from '@quasar/extras/mdi-v7' import { socialLinks } from './links.social.js' diff --git a/docs/src/assets/links.integrations.js b/docs/src/assets/links.integrations.js index 46765f85573..4fd39054bb1 100644 --- a/docs/src/assets/links.integrations.js +++ b/docs/src/assets/links.integrations.js @@ -10,7 +10,7 @@ import { mdiServer, mdiStarCircle, mdiSvg, mdiTelevisionPlay, mdiTestTube -} from '@quasar/extras/mdi-v6' +} from '@quasar/extras/mdi-v7' export const platformIcons = [ 'img:https://cdn.quasar.dev/img/custom-svg-icons/chrome.svg', diff --git a/docs/src/assets/links.social.js b/docs/src/assets/links.social.js index 5d7d46a47bc..73a49f5358d 100644 --- a/docs/src/assets/links.social.js +++ b/docs/src/assets/links.social.js @@ -1,12 +1,12 @@ -import { fabFacebook, fabGithub, fabXTwitter } from '@quasar/extras/fontawesome-v6' -import { mdiDiscord, mdiForum } from '@quasar/extras/mdi-v6' +import { fabFacebook, fabGithub, fabXTwitter, fabDiscord } from '@quasar/extras/fontawesome-v6' +import { mdiForum } from '@quasar/extras/mdi-v7' export const socialLinks = { name: 'Social', mq: 1310, children: [ { name: 'Github', icon: fabGithub, path: 'https://github.quasar.dev', external: true }, - { name: 'Discord', icon: mdiDiscord, path: 'https://chat.quasar.dev', external: true }, + { name: 'Discord', icon: fabDiscord, path: 'https://chat.quasar.dev', external: true }, { name: 'Forum', icon: mdiForum, path: 'https://forum.quasar.dev', external: true }, { name: 'X (Twitter)', icon: fabXTwitter, path: 'https://twitter.quasar.dev', external: true }, { name: 'Facebook', icon: fabFacebook, path: 'https://facebook.quasar.dev', external: true } diff --git a/docs/src/components/DocApi.vue b/docs/src/components/DocApi.vue index 6a82c3bf48f..93b31776358 100644 --- a/docs/src/components/DocApi.vue +++ b/docs/src/components/DocApi.vue @@ -81,7 +81,7 @@ diff --git a/docs/src/layouts/doc-layout/search/ResultError.vue b/docs/src/layouts/doc-layout/search/ResultError.vue index f9c10dbdb08..71b2d129430 100644 --- a/docs/src/layouts/doc-layout/search/ResultError.vue +++ b/docs/src/layouts/doc-layout/search/ResultError.vue @@ -7,5 +7,5 @@ diff --git a/docs/src/layouts/doc-layout/search/SearchResults.vue b/docs/src/layouts/doc-layout/search/SearchResults.vue index fdfcc47ca0e..784e8437a6b 100644 --- a/docs/src/layouts/doc-layout/search/SearchResults.vue +++ b/docs/src/layouts/doc-layout/search/SearchResults.vue @@ -24,7 +24,7 @@ diff --git a/docs/src/pages/layout/grid/flex-playground/FlexChild.vue b/docs/src/pages/layout/grid/flex-playground/FlexChild.vue index b2754a1cb22..882b9bfd9b4 100644 --- a/docs/src/pages/layout/grid/flex-playground/FlexChild.vue +++ b/docs/src/pages/layout/grid/flex-playground/FlexChild.vue @@ -28,7 +28,7 @@ ``` @@ -154,7 +154,7 @@ import { fasFont } from '@quasar/extras/fontawesome-v5' ```html