@nexdom/uimed-vue 2.0.0-beta.8 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/_virtual/_rolldown/runtime.js +7 -0
- package/dist/components/app-bar/app-bar.js +5 -0
- package/dist/components/app-bar/app-bar.vue_vue_type_script_setup_true_lang.js +286 -0
- package/dist/components/app-bar/types.d.ts +96 -0
- package/dist/components/button/button.js +5 -0
- package/dist/components/button/button.vue.d.ts +31 -0
- package/dist/components/button/button.vue_vue_type_script_setup_true_lang.js +97 -0
- package/dist/components/button/types.d.ts +65 -0
- package/dist/components/data-sets/data-set/data-set.js +5 -0
- package/dist/components/data-sets/data-set/data-set.vue.d.ts +47 -0
- package/dist/components/data-sets/data-set/data-set.vue_vue_type_script_setup_true_lang.js +157 -0
- package/dist/components/data-sets/data-set/types.d.ts +93 -0
- package/dist/components/data-sets/data-set-bar.js +5 -0
- package/dist/components/data-sets/data-set-bar.vue_vue_type_script_setup_true_lang.js +99 -0
- package/dist/components/data-sets/data-set-item/data-set-item.js +5 -0
- package/dist/components/data-sets/data-set-item/data-set-item.vue.d.ts +35 -0
- package/dist/components/data-sets/data-set-item/data-set-item.vue_vue_type_script_setup_true_lang.js +57 -0
- package/dist/components/data-sets/data-set-item/types.d.ts +42 -0
- package/dist/components/details/details.js +5 -0
- package/dist/components/details/details.vue.d.ts +28 -0
- package/dist/components/details/details.vue_vue_type_script_setup_true_lang.js +56 -0
- package/dist/components/details/types.d.ts +29 -0
- package/dist/components/dialogs/dialog/dialog-host-key.js +10 -0
- package/dist/components/dialogs/dialog/dialog.js +5 -0
- package/dist/components/dialogs/dialog/dialog.vue.d.ts +36 -0
- package/dist/components/dialogs/dialog/dialog.vue_vue_type_script_setup_true_lang.js +112 -0
- package/dist/components/dialogs/dialog/types.d.ts +47 -0
- package/dist/components/dialogs/dialog-host.js +5 -0
- package/dist/components/dialogs/dialog-host.vue_vue_type_script_setup_true_lang.js +81 -0
- package/dist/components/dialogs/toast.js +5 -0
- package/dist/components/dialogs/toast.vue_vue_type_script_setup_true_lang.js +35 -0
- package/dist/components/footer/footer.js +5 -0
- package/dist/components/footer/footer.vue_vue_type_script_setup_true_lang.js +31 -0
- package/dist/components/footer/types.d.ts +16 -0
- package/dist/components/form/form.js +5 -0
- package/dist/components/form/form.vue.d.ts +32 -0
- package/dist/components/form/form.vue_vue_type_script_setup_true_lang.js +37 -0
- package/dist/components/form/types.d.ts +15 -0
- package/dist/components/grid/column/column.js +5 -0
- package/dist/components/grid/column/column.vue.d.ts +33 -0
- package/dist/components/grid/column/column.vue_vue_type_script_setup_true_lang.js +45 -0
- package/dist/components/grid/column/types.d.ts +27 -0
- package/dist/components/grid/container/container.js +5 -0
- package/dist/components/grid/container/container.vue.d.ts +27 -0
- package/dist/components/grid/container/container.vue_vue_type_script_setup_true_lang.js +33 -0
- package/dist/components/grid/container/types.d.ts +17 -0
- package/dist/components/grid/row/row.js +5 -0
- package/dist/components/grid/row/row.vue.d.ts +29 -0
- package/dist/components/grid/row/row.vue_vue_type_script_setup_true_lang.js +61 -0
- package/dist/components/grid/row/types.d.ts +63 -0
- package/dist/components/icon-button/icon-button.js +5 -0
- package/dist/components/icon-button/icon-button.vue.d.ts +23 -0
- package/dist/components/icon-button/icon-button.vue_vue_type_script_setup_true_lang.js +51 -0
- package/dist/components/icon-button/types.d.ts +32 -0
- package/dist/components/img/img.js +5 -0
- package/dist/components/img/img.vue.d.ts +23 -0
- package/dist/components/img/img.vue_vue_type_script_setup_true_lang.js +61 -0
- package/dist/components/img/types.d.ts +43 -0
- package/dist/components/inputs/autocomplete-field/autocomplete-field.js +5 -0
- package/dist/components/inputs/autocomplete-field/autocomplete-field.vue.d.ts +33 -0
- package/dist/components/inputs/autocomplete-field/autocomplete-field.vue_vue_type_script_setup_true_lang.js +161 -0
- package/dist/components/inputs/autocomplete-field/types.d.ts +84 -0
- package/dist/components/inputs/checkbox/checkbox.js +5 -0
- package/dist/components/inputs/checkbox/checkbox.vue.d.ts +33 -0
- package/dist/components/inputs/checkbox/checkbox.vue_vue_type_script_setup_true_lang.js +71 -0
- package/dist/components/inputs/checkbox/types.d.ts +35 -0
- package/dist/components/inputs/date-time-field/date-time-field.js +5 -0
- package/dist/components/inputs/date-time-field/date-time-field.vue.d.ts +32 -0
- package/dist/components/inputs/date-time-field/date-time-field.vue_vue_type_script_setup_true_lang.js +205 -0
- package/dist/components/inputs/date-time-field/types.d.ts +84 -0
- package/dist/components/inputs/field-loader.js +5 -0
- package/dist/components/inputs/field-loader.vue_vue_type_script_setup_true_lang.js +30 -0
- package/dist/components/inputs/mask-field/mask-field.js +5 -0
- package/dist/components/inputs/mask-field/mask-field.vue.d.ts +32 -0
- package/dist/components/inputs/mask-field/mask-field.vue_vue_type_script_setup_true_lang.js +120 -0
- package/dist/components/inputs/mask-field/types.d.ts +118 -0
- package/dist/components/inputs/text-field/text-field.js +5 -0
- package/dist/components/inputs/text-field/text-field.vue.d.ts +30 -0
- package/dist/components/inputs/text-field/text-field.vue_vue_type_script_setup_true_lang.js +119 -0
- package/dist/components/inputs/text-field/types.d.ts +70 -0
- package/dist/components/link/link.js +5 -0
- package/dist/components/link/link.vue.d.ts +27 -0
- package/dist/components/link/link.vue_vue_type_script_setup_true_lang.js +32 -0
- package/dist/components/link/types.d.ts +18 -0
- package/dist/components/main/main.js +5 -0
- package/dist/components/main/main.vue.d.ts +33 -0
- package/dist/components/main/main.vue_vue_type_script_setup_true_lang.js +104 -0
- package/dist/components/main/types.d.ts +75 -0
- package/dist/components/navigation-menu/navigation-menu.js +5 -0
- package/dist/components/navigation-menu/navigation-menu.vue_vue_type_script_setup_true_lang.js +207 -0
- package/dist/components/navigation-menu/types.d.ts +62 -0
- package/dist/components/page-transition/page-transition.js +5 -0
- package/dist/components/page-transition/page-transition.vue.d.ts +40 -0
- package/dist/components/page-transition/page-transition.vue_vue_type_script_setup_true_lang.js +27 -0
- package/dist/components/page-transition/types.d.ts +18 -0
- package/dist/components/sections/section/section.js +5 -0
- package/dist/components/sections/section/section.vue.d.ts +28 -0
- package/dist/components/sections/section/section.vue_vue_type_script_setup_true_lang.js +108 -0
- package/dist/components/sections/section/types.d.ts +80 -0
- package/dist/components/sections/section-content/section-content.js +5 -0
- package/dist/components/sections/section-content/section-content.vue.d.ts +27 -0
- package/dist/components/sections/section-content/section-content.vue_vue_type_script_setup_true_lang.js +16 -0
- package/dist/components/skeleton-loader/skeleton-loader.js +5 -0
- package/dist/components/skeleton-loader/skeleton-loader.vue_vue_type_script_setup_true_lang.js +65 -0
- package/dist/components/table/table.js +5 -0
- package/dist/components/table/table.vue.d.ts +19 -0
- package/dist/components/table/table.vue_vue_type_script_setup_true_lang.js +84 -0
- package/dist/components/table/types.d.ts +51 -0
- package/dist/components.d.ts +23 -940
- package/dist/components.js +23 -1254
- package/dist/composables/alignment/alignment.js +41 -0
- package/dist/composables/alignment/types.d.ts +12 -0
- package/dist/composables/button/button.js +54 -0
- package/dist/composables/colors/constants.js +27 -0
- package/dist/composables/colors/types.d.ts +4 -0
- package/dist/composables/colors/use-vuetify-background-color.js +16 -0
- package/dist/composables/colors/use-vuetify-color.js +11 -0
- package/dist/composables/colors/use-vuetify-text-color.js +12 -0
- package/dist/composables/data-set/data-set.js +128 -0
- package/dist/composables/dialogs/types.d.ts +88 -0
- package/dist/composables/dialogs/use-confirm.d.ts +21 -0
- package/dist/composables/dialogs/use-confirm.js +55 -0
- package/dist/composables/dialogs/use-decorative-timer.js +25 -0
- package/dist/composables/dialogs/use-dialog-focus.js +64 -0
- package/dist/composables/dialogs/use-dialog-loading.js +13 -0
- package/dist/composables/dialogs/use-dialog.d.ts +36 -0
- package/dist/composables/dialogs/use-dialog.js +73 -0
- package/dist/composables/dialogs/use-run-or-toast.d.ts +27 -0
- package/dist/composables/dialogs/use-run-or-toast.js +65 -0
- package/dist/composables/dialogs/use-toast.d.ts +25 -0
- package/dist/composables/dialogs/use-toast.js +39 -0
- package/dist/composables/dimensions/dimensions.js +18 -0
- package/dist/composables/inputs/autocomplete-field.js +39 -0
- package/dist/composables/inputs/date-time-field.js +109 -0
- package/dist/composables/inputs/fields.js +28 -0
- package/dist/composables/inputs/mask-field.js +133 -0
- package/dist/composables/inputs/rules.js +16 -0
- package/dist/composables/inputs/text-field.js +81 -0
- package/dist/composables/navigation/types.d.ts +54 -0
- package/dist/composables/navigation/use-navigation-rail.js +163 -0
- package/dist/composables/navigation/use-navigation-search-shortcut.js +96 -0
- package/dist/composables/navigation/use-route-props.js +21 -0
- package/dist/composables/navigation/use-unsaved-changes.d.ts +35 -0
- package/dist/composables/navigation/use-unsaved-changes.js +88 -0
- package/dist/composables/section/section.js +34 -0
- package/dist/composables.d.ts +6 -67
- package/dist/composables.js +6 -65
- package/dist/consts/icons.d.ts +67 -0
- package/dist/consts/icons.js +69 -0
- package/dist/consts/locale.js +12 -0
- package/dist/consts/types.d.ts +12 -0
- package/dist/index.d.ts +1 -13
- package/dist/index.js +39 -14
- package/dist/plugins.d.ts +15 -4
- package/dist/plugins.js +54 -3
- package/dist/styles/fonts/UnimedSlab-Regular.woff2 +0 -0
- package/dist/styles/fonts/fonts.css +5 -0
- package/dist/styles/settings.scss +155 -0
- package/dist/unit-test.js +11 -7
- package/package.json +11 -11
- package/skills/uimed-vue/SKILL.md +113 -0
- package/skills/uimed-vue/references/api/components/autocomplete-field.md +45 -0
- package/skills/uimed-vue/references/api/components/button.md +48 -0
- package/skills/uimed-vue/references/api/components/checkbox.md +36 -0
- package/skills/uimed-vue/references/api/components/data-sets/data-set-item.md +61 -0
- package/skills/uimed-vue/references/api/components/data-sets/data-set.md +67 -0
- package/skills/uimed-vue/references/api/components/date-time-field.md +44 -0
- package/skills/uimed-vue/references/api/components/details.md +33 -0
- package/skills/uimed-vue/references/api/components/dialog.md +70 -0
- package/skills/uimed-vue/references/api/components/form.md +43 -0
- package/skills/uimed-vue/references/api/components/grid/column.md +41 -0
- package/skills/uimed-vue/references/api/components/grid/container.md +32 -0
- package/skills/uimed-vue/references/api/components/grid/row.md +37 -0
- package/skills/uimed-vue/references/api/components/icon-button.md +37 -0
- package/skills/uimed-vue/references/api/components/img.md +34 -0
- package/skills/uimed-vue/references/api/components/link.md +31 -0
- package/skills/uimed-vue/references/api/components/main.md +174 -0
- package/skills/uimed-vue/references/api/components/mask-field.md +60 -0
- package/skills/uimed-vue/references/api/components/page-transition.md +37 -0
- package/skills/uimed-vue/references/api/components/sections/section-content.md +23 -0
- package/skills/uimed-vue/references/api/components/sections/section.md +61 -0
- package/skills/uimed-vue/references/api/components/table.md +35 -0
- package/skills/uimed-vue/references/api/components/text-field.md +42 -0
- package/skills/uimed-vue/references/api/composables/use-confirm.md +96 -0
- package/skills/uimed-vue/references/api/composables/use-dialog.md +95 -0
- package/skills/uimed-vue/references/api/composables/use-run-or-toast.md +58 -0
- package/skills/uimed-vue/references/api/composables/use-toast.md +48 -0
- package/skills/uimed-vue/references/api/composables/use-unsaved-changes.md +106 -0
- package/skills/uimed-vue/references/api/index.md +1 -0
- package/skills/uimed-vue/references/guide/components/autocomplete-field.md +256 -0
- package/skills/uimed-vue/references/guide/components/button.md +155 -0
- package/skills/uimed-vue/references/guide/components/checkbox.md +113 -0
- package/skills/uimed-vue/references/guide/components/data-set.md +370 -0
- package/skills/uimed-vue/references/guide/components/date-time-field.md +202 -0
- package/skills/uimed-vue/references/guide/components/details.md +76 -0
- package/skills/uimed-vue/references/guide/components/dialog.md +269 -0
- package/skills/uimed-vue/references/guide/components/form.md +96 -0
- package/skills/uimed-vue/references/guide/components/icon-button.md +76 -0
- package/skills/uimed-vue/references/guide/components/img.md +128 -0
- package/skills/uimed-vue/references/guide/components/layout.md +323 -0
- package/skills/uimed-vue/references/guide/components/link.md +47 -0
- package/skills/uimed-vue/references/guide/components/main.md +435 -0
- package/skills/uimed-vue/references/guide/components/mask-field.md +256 -0
- package/skills/uimed-vue/references/guide/components/page-transition.md +111 -0
- package/skills/uimed-vue/references/guide/components/section.md +232 -0
- package/skills/uimed-vue/references/guide/components/table.md +102 -0
- package/skills/uimed-vue/references/guide/components/text-field.md +172 -0
- package/skills/uimed-vue/references/guide/composables/use-confirm.md +123 -0
- package/skills/uimed-vue/references/guide/composables/use-dialog.md +102 -0
- package/skills/uimed-vue/references/guide/composables/use-run-or-toast.md +55 -0
- package/skills/uimed-vue/references/guide/composables/use-toast.md +57 -0
- package/skills/uimed-vue/references/guide/composables/use-unsaved-changes.md +129 -0
- package/skills/uimed-vue/references/guide/getting-started.md +135 -0
- package/skills/uimed-vue/references/guide/icons.md +71 -0
- package/skills/uimed-vue/references/guide/index.md +51 -0
- package/skills/uimed-vue/references/guide/migration-v1-to-v2.md +191 -0
- package/skills/uimed-vue/references/guide/unit-testing.md +85 -0
- package/AGENTS.md +0 -160
- package/CLAUDE.md +0 -1
- package/dist/types-BPXXtbmW.d.ts +0 -5
- package/dist/use-toast-CACuYhK_.js +0 -53
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { ref } from "vue";
|
|
2
|
+
//#region src/composables/dialogs/use-dialog.ts
|
|
3
|
+
/**
|
|
4
|
+
* Dialogs waiting to be displayed, shared by `useDialog` and `useConfirm`. The first one is the
|
|
5
|
+
* dialog on display (or about to be).
|
|
6
|
+
*/
|
|
7
|
+
const requests = ref([]);
|
|
8
|
+
/**
|
|
9
|
+
* Mounted `Dialog` components, in mounting order. Only the first one displays dialogs, so pages
|
|
10
|
+
* with more than one `UMain` don't display duplicates.
|
|
11
|
+
*/
|
|
12
|
+
const owners = ref([]);
|
|
13
|
+
/**
|
|
14
|
+
* Queues a dialog and resolves with what `onSelect` returns for the clicked action's value
|
|
15
|
+
* (`undefined` when the dialog is dismissed).
|
|
16
|
+
*/
|
|
17
|
+
function openDialog(options, onSelect) {
|
|
18
|
+
const { actions, ...content } = options;
|
|
19
|
+
const lastIndex = actions.length - 1;
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
requests.value.push({
|
|
22
|
+
...content,
|
|
23
|
+
actions: actions.map((action, index) => ({
|
|
24
|
+
...action,
|
|
25
|
+
variant: action.variant ?? (index === lastIndex ? "primary" : "ghost")
|
|
26
|
+
})),
|
|
27
|
+
async select(value) {
|
|
28
|
+
resolve(await onSelect(value));
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Composable which returns a function to display dialogs.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* const { dialog } = useDialog();
|
|
39
|
+
*
|
|
40
|
+
* const answer = await dialog({
|
|
41
|
+
* title: "Sessão expirando",
|
|
42
|
+
* message: "Deseja continuar conectado?",
|
|
43
|
+
* actions: [
|
|
44
|
+
* { text: "Sair", value: "leave" },
|
|
45
|
+
* { text: "Continuar", value: "stay" },
|
|
46
|
+
* ],
|
|
47
|
+
* });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
function useDialog() {
|
|
51
|
+
return { dialog };
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Displays a dialog. Dialogs are displayed one at a time, in the order they were requested.
|
|
55
|
+
* @param {DialogOptions} options - Options for the dialog.
|
|
56
|
+
* @returns the `value` of the clicked action, or `undefined` when the dialog is closed by the
|
|
57
|
+
* default action, the `Esc` key, a click outside it or the browser's back button.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* await dialog({ title: "Aviso", message: "Seu cadastro foi enviado para análise." });
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
function dialog(options) {
|
|
65
|
+
return openDialog({
|
|
66
|
+
title: options.title,
|
|
67
|
+
message: options.message,
|
|
68
|
+
role: "dialog",
|
|
69
|
+
actions: options.actions ?? [{ text: "Fechar" }]
|
|
70
|
+
}, (value) => value);
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
export { useDialog as default, openDialog, owners, requests };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/composables/dialogs/use-run-or-toast.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Generate `run` method and a computed property `isRunning`.
|
|
4
|
+
* Calling `run` will reactively update `isRunning`.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* const { run, isRunning } = useRunOrToast();
|
|
9
|
+
*
|
|
10
|
+
* // Logs: false
|
|
11
|
+
* console.log(isRunning.value);
|
|
12
|
+
*
|
|
13
|
+
* const promise = run(() => someFunction(param1, param2));
|
|
14
|
+
*
|
|
15
|
+
* // Logs: true
|
|
16
|
+
* console.log(isRunning.value);
|
|
17
|
+
*
|
|
18
|
+
* await promise;
|
|
19
|
+
* // Logs: false
|
|
20
|
+
* console.log(isRunning.value);
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export default function useRunOrToast(): {
|
|
24
|
+
run: <T extends (...args: Parameters<T>) => PromiseLike<Awaited<ReturnType<T>>>>(toRun: T, ...params: Parameters<T>) => Promise<Awaited<ReturnType<T>> | false>;
|
|
25
|
+
isRunning: import("vue").ComputedRef<boolean>;
|
|
26
|
+
};
|
|
27
|
+
//#endregion
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import useToast from "./use-toast.js";
|
|
2
|
+
import { computed, ref } from "vue";
|
|
3
|
+
//#region src/composables/dialogs/use-run-or-toast.ts
|
|
4
|
+
const { toast } = useToast();
|
|
5
|
+
/**
|
|
6
|
+
* Generate `run` method and a computed property `isRunning`.
|
|
7
|
+
* Calling `run` will reactively update `isRunning`.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* const { run, isRunning } = useRunOrToast();
|
|
12
|
+
*
|
|
13
|
+
* // Logs: false
|
|
14
|
+
* console.log(isRunning.value);
|
|
15
|
+
*
|
|
16
|
+
* const promise = run(() => someFunction(param1, param2));
|
|
17
|
+
*
|
|
18
|
+
* // Logs: true
|
|
19
|
+
* console.log(isRunning.value);
|
|
20
|
+
*
|
|
21
|
+
* await promise;
|
|
22
|
+
* // Logs: false
|
|
23
|
+
* console.log(isRunning.value);
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
function useRunOrToast() {
|
|
27
|
+
const running = ref(false);
|
|
28
|
+
const isRunning = computed(() => running.value);
|
|
29
|
+
/**
|
|
30
|
+
* Receives a function and its parameters, runs it, and if it throws an error, shows a toast with the error message.
|
|
31
|
+
* @returns the function returned value or `false` on error
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* const { run, isRunning } = useRunOrToast();
|
|
36
|
+
*
|
|
37
|
+
* // Call like this:
|
|
38
|
+
* const someFunctionResult1 = await run(() => someFunction(param1, param2));
|
|
39
|
+
*
|
|
40
|
+
* // Or this:
|
|
41
|
+
* const someFunctionResult2 = await run(someFunction, param1, param2);
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
async function run(toRun, ...params) {
|
|
45
|
+
try {
|
|
46
|
+
running.value = true;
|
|
47
|
+
return await toRun(...params);
|
|
48
|
+
} catch (error) {
|
|
49
|
+
if (error instanceof Error) toast({
|
|
50
|
+
message: error.message,
|
|
51
|
+
color: "danger"
|
|
52
|
+
});
|
|
53
|
+
else console.error(error);
|
|
54
|
+
return false;
|
|
55
|
+
} finally {
|
|
56
|
+
running.value = false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
run,
|
|
61
|
+
isRunning
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
//#endregion
|
|
65
|
+
export { useRunOrToast as default };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { ToastOptions } from "./types.js";
|
|
2
|
+
//#region src/composables/dialogs/use-toast.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Composable which returns a function to display toast messages.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* const { toast } = useToast();
|
|
9
|
+
* toast({ message: "Hello, World!" });
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export default function useToast(): {
|
|
13
|
+
toast: typeof toast;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Displays a toast message.
|
|
17
|
+
* @param {ToastOptions} options - Options for the toast message.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* toast({ message: "Hello, World!" });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
declare function toast(options: ToastOptions): void;
|
|
25
|
+
//#endregion
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { icons } from "../../consts/icons.js";
|
|
2
|
+
import { feedbackColorToVuetifyColor } from "../colors/constants.js";
|
|
3
|
+
import { ref } from "vue";
|
|
4
|
+
//#region src/composables/dialogs/use-toast.ts
|
|
5
|
+
const messages = ref([]);
|
|
6
|
+
/**
|
|
7
|
+
* Composable which returns a function to display toast messages.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* const { toast } = useToast();
|
|
12
|
+
* toast({ message: "Hello, World!" });
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
function useToast() {
|
|
16
|
+
return { toast };
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Displays a toast message.
|
|
20
|
+
* @param {ToastOptions} options - Options for the toast message.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* toast({ message: "Hello, World!" });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
function toast(options) {
|
|
28
|
+
const text = options.message;
|
|
29
|
+
const color = feedbackColorToVuetifyColor[options.color ?? "informative"];
|
|
30
|
+
const toastMessage = {
|
|
31
|
+
text,
|
|
32
|
+
color,
|
|
33
|
+
prependIcon: icons[color],
|
|
34
|
+
timerColor: `rgb(var(--v-theme-on-${color}))`
|
|
35
|
+
};
|
|
36
|
+
messages.value.push(toastMessage);
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
export { useToast as default, messages };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { computed, toValue } from "vue";
|
|
2
|
+
//#region src/composables/dimensions/dimensions.ts
|
|
3
|
+
/**
|
|
4
|
+
* Vuetify's utility class that stretches a component to the whole width of its parent, for
|
|
5
|
+
* components with a `fullWidth` prop.
|
|
6
|
+
*/
|
|
7
|
+
function useFullWidth(fullWidth) {
|
|
8
|
+
return computed(() => toValue(fullWidth) ? "w-100" : void 0);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Vuetify's utility class that stretches a component to the whole height of its parent, for
|
|
12
|
+
* components with a `fullHeight` prop. The parent must have a height of its own.
|
|
13
|
+
*/
|
|
14
|
+
function useFullHeight(fullHeight) {
|
|
15
|
+
return computed(() => toValue(fullHeight) ? "h-100" : void 0);
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { useFullHeight, useFullWidth };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createRequiredRule } from "./fields.js";
|
|
2
|
+
import { computed, toValue } from "vue";
|
|
3
|
+
import { isEmpty } from "@nexdom/shared/utils";
|
|
4
|
+
//#region src/composables/inputs/autocomplete-field.ts
|
|
5
|
+
function createAutocompleteRequiredRule(options) {
|
|
6
|
+
return createRequiredRule(options.required, (value) => {
|
|
7
|
+
if (options.multiple) return (Array.isArray(value) ? value : []).length > 0;
|
|
8
|
+
return !isEmpty(String(value ?? ""));
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
function useAutocompleteRules(options) {
|
|
12
|
+
return computed(() => {
|
|
13
|
+
const opts = toValue(options);
|
|
14
|
+
const rules = [];
|
|
15
|
+
if (opts.required) rules.push(createAutocompleteRequiredRule(opts));
|
|
16
|
+
return rules;
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Menu props that keep the combobox semantics only on the field's `<input>`.
|
|
21
|
+
*
|
|
22
|
+
* The field's container gets `role="combobox"` from the field, which passes its
|
|
23
|
+
* role to both the container and the input, and the menu's ARIA attributes,
|
|
24
|
+
* since it's the menu's activator. Without these props, the container is
|
|
25
|
+
* exposed as an unnamed combobox wrapping the input. The menu removes from its
|
|
26
|
+
* activator the attributes set to `undefined`, role included, and the field
|
|
27
|
+
* never sets that role again, since its value doesn't change. The input keeps
|
|
28
|
+
* its role, label, `aria-expanded` and `aria-controls`. This only happens in
|
|
29
|
+
* the browser: server-rendered HTML still has the role on the container.
|
|
30
|
+
*/
|
|
31
|
+
const autocompleteFieldMenuProps = { activatorProps: {
|
|
32
|
+
role: void 0,
|
|
33
|
+
"aria-haspopup": void 0,
|
|
34
|
+
"aria-expanded": void 0,
|
|
35
|
+
"aria-controls": void 0,
|
|
36
|
+
"aria-owns": void 0
|
|
37
|
+
} };
|
|
38
|
+
//#endregion
|
|
39
|
+
export { autocompleteFieldMenuProps, createAutocompleteRequiredRule, useAutocompleteRules };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { useRules } from "./fields.js";
|
|
2
|
+
import { computed, toValue, useId } from "vue";
|
|
3
|
+
//#region src/composables/inputs/date-time-field.ts
|
|
4
|
+
/**
|
|
5
|
+
* The menu's content is a dialog with the pickers. `aria-owns` is removed since the input can't
|
|
6
|
+
* own the content, and the menu still adds `aria-expanded` and `aria-controls`.
|
|
7
|
+
*/
|
|
8
|
+
const activatorProps = {
|
|
9
|
+
role: "combobox",
|
|
10
|
+
"aria-haspopup": "dialog",
|
|
11
|
+
"aria-owns": void 0
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Extracts the date part (`YYYY-MM-DD`) of a value, or an empty string when
|
|
15
|
+
* the type has no date.
|
|
16
|
+
*/
|
|
17
|
+
function getDatePart(value, type) {
|
|
18
|
+
return type === "time" ? "" : value.split("T")[0];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Extracts the time part (`HH:mm`) of a value, or an empty string when the
|
|
22
|
+
* value has no time.
|
|
23
|
+
*/
|
|
24
|
+
function getTimePart(value, type) {
|
|
25
|
+
if (type === "time") return value;
|
|
26
|
+
const [, time = ""] = value.split("T");
|
|
27
|
+
return time;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Builds a value for the given type, or an empty string while any required
|
|
31
|
+
* part is missing.
|
|
32
|
+
*/
|
|
33
|
+
function joinDateTime(type, date, time) {
|
|
34
|
+
if (type === "date") return date;
|
|
35
|
+
if (type === "time") return time;
|
|
36
|
+
return date && time ? `${date}T${time}` : "";
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Formats a value to be displayed in pt-BR, like `24/09/2026`, `14:30` or
|
|
40
|
+
* `24/09/2026 14:30`.
|
|
41
|
+
*/
|
|
42
|
+
function formatDateTime(value, type) {
|
|
43
|
+
return [getDatePart(value, type).split("-").reverse().join("/"), getTimePart(value, type)].filter(Boolean).join(" ");
|
|
44
|
+
}
|
|
45
|
+
function useDateTimeFieldRules(props) {
|
|
46
|
+
const defaultRules = useRules(props);
|
|
47
|
+
return computed(() => {
|
|
48
|
+
const rules = [...defaultRules.value];
|
|
49
|
+
if (props.min) rules.push(minRule(props.min, props.type));
|
|
50
|
+
if (props.max) rules.push(maxRule(props.max, props.type));
|
|
51
|
+
return rules;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Computes the limits of each picker. For `datetime`, time limits only apply
|
|
56
|
+
* when the selected date is the limit's own date.
|
|
57
|
+
*/
|
|
58
|
+
function useDateTimeFieldPickerBounds(props, selectedDate) {
|
|
59
|
+
return computed(() => {
|
|
60
|
+
const min = props.min ?? "";
|
|
61
|
+
const max = props.max ?? "";
|
|
62
|
+
const date = toValue(selectedDate);
|
|
63
|
+
return {
|
|
64
|
+
dateMin: getDatePart(min, props.type),
|
|
65
|
+
dateMax: getDatePart(max, props.type),
|
|
66
|
+
timeMin: getTimeBound(min, props.type, date),
|
|
67
|
+
timeMax: getTimeBound(max, props.type, date)
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Makes the field's input the activator of the pickers' menu, so the menu's attributes are on an
|
|
73
|
+
* element whose role accepts them.
|
|
74
|
+
* @param isOpen - Whether the menu is open.
|
|
75
|
+
* @param isDisabled - Whether the menu can't open, as when the field is disabled or readonly.
|
|
76
|
+
* @param hasLabel - Whether the field has a label, which names the menu's dialog.
|
|
77
|
+
*/
|
|
78
|
+
function useDateTimeFieldMenu(isOpen, isDisabled, hasLabel) {
|
|
79
|
+
const inputId = useId();
|
|
80
|
+
const activator = computed(() => toValue(isDisabled) ? void 0 : `#${inputId}`);
|
|
81
|
+
const contentProps = computed(() => ({
|
|
82
|
+
role: "dialog",
|
|
83
|
+
"aria-labelledby": toValue(hasLabel) ? `${inputId}-label` : void 0
|
|
84
|
+
}));
|
|
85
|
+
function open() {
|
|
86
|
+
if (!toValue(isDisabled)) isOpen.value = true;
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
inputId,
|
|
90
|
+
activator,
|
|
91
|
+
activatorProps,
|
|
92
|
+
contentProps,
|
|
93
|
+
open
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function getTimeBound(bound, type, selectedDate) {
|
|
97
|
+
if (type === "datetime" && getDatePart(bound, type) !== selectedDate) return "";
|
|
98
|
+
return getTimePart(bound, type);
|
|
99
|
+
}
|
|
100
|
+
function minRule(min, type) {
|
|
101
|
+
const message = `Valor anterior ao mínimo permitido (${formatDateTime(min, type)})`;
|
|
102
|
+
return (value) => !value || value >= min || message;
|
|
103
|
+
}
|
|
104
|
+
function maxRule(max, type) {
|
|
105
|
+
const message = `Valor posterior ao máximo permitido (${formatDateTime(max, type)})`;
|
|
106
|
+
return (value) => !value || value <= max || message;
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
109
|
+
export { formatDateTime, getDatePart, getTimePart, joinDateTime, useDateTimeFieldMenu, useDateTimeFieldPickerBounds, useDateTimeFieldRules };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { required } from "./rules.js";
|
|
2
|
+
import { computed, toValue } from "vue";
|
|
3
|
+
//#region src/composables/inputs/fields.ts
|
|
4
|
+
const textFieldVariantToVuetifyVariant = {
|
|
5
|
+
primary: "underlined",
|
|
6
|
+
secondary: "outlined"
|
|
7
|
+
};
|
|
8
|
+
function useTextFieldVariant(variant) {
|
|
9
|
+
return computed(() => {
|
|
10
|
+
const variantValue = toValue(variant);
|
|
11
|
+
return textFieldVariantToVuetifyVariant[variantValue];
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
function createRequiredRule(required, isPresent) {
|
|
15
|
+
return (value) => {
|
|
16
|
+
if (!required) return true;
|
|
17
|
+
return isPresent(value) || "Campo obrigatório";
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function useRules(options) {
|
|
21
|
+
return computed(() => {
|
|
22
|
+
const rules = [];
|
|
23
|
+
if (options.required) rules.push(required);
|
|
24
|
+
return rules;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
export { createRequiredRule, useRules, useTextFieldVariant };
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { useRules } from "./fields.js";
|
|
2
|
+
import { useMask } from "vuetify";
|
|
3
|
+
import { computed } from "vue";
|
|
4
|
+
//#region src/composables/inputs/mask-field.ts
|
|
5
|
+
/**
|
|
6
|
+
* Masks of the presets, as Vuetify reads them: `#` is a digit and `N` is a
|
|
7
|
+
* letter or a digit, with letters converted to uppercase. Our preset names
|
|
8
|
+
* always map to these masks, never to Vuetify's own presets (e.g. its American
|
|
9
|
+
* `phone`).
|
|
10
|
+
*/
|
|
11
|
+
const maskFieldPresets = {
|
|
12
|
+
cpf: "###.###.###-##",
|
|
13
|
+
cnpj: "NN.NNN.NNN/NNNN-##",
|
|
14
|
+
cep: "#####-###",
|
|
15
|
+
phone: "(##) #####-####",
|
|
16
|
+
date: "##/##/####"
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Returns the mask as Vuetify reads it: the mask of a preset, or the given mask
|
|
20
|
+
* itself, escaped, when it's a custom one.
|
|
21
|
+
*
|
|
22
|
+
* It takes any string, as the `mask` prop may get one through a cast or from
|
|
23
|
+
* JavaScript: a string that isn't a preset is taken as the mask.
|
|
24
|
+
*/
|
|
25
|
+
function resolveMask(mask) {
|
|
26
|
+
return isMaskFieldPreset(mask) ? maskFieldPresets[mask] : toVuetifyMask(mask);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Returns a custom mask as Vuetify reads it. `#` is the only token of custom
|
|
30
|
+
* masks, but Vuetify always adds its own tokens (`A`, `a`, `N`, `n` and `X`)
|
|
31
|
+
* and its escape (`\`), so these characters are escaped to stay literals.
|
|
32
|
+
*/
|
|
33
|
+
function toVuetifyMask(mask) {
|
|
34
|
+
return mask.replaceAll(/[AaNnX\\]/g, String.raw`\$&`);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Returns the tokens of a mask as Vuetify reads it (see {@link resolveMask}),
|
|
38
|
+
* in order, without its literals (e.g. `NNNNNNNNNNNN##` for `cnpj`).
|
|
39
|
+
*/
|
|
40
|
+
function getMaskTokens(mask) {
|
|
41
|
+
return mask.replaceAll(/\\.|[^#N]/g, "");
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Returns the default placeholder of a mask as Vuetify reads it (see
|
|
45
|
+
* {@link resolveMask}): the mask with `0` in place of each token, keeping its
|
|
46
|
+
* literals (e.g. `000.000.000-00` for `cpf` and `Nº 0000` for `Nº ####`).
|
|
47
|
+
*/
|
|
48
|
+
function getMaskPlaceholder(mask) {
|
|
49
|
+
return mask.replaceAll(/\\(.)|[#N]/g, (_token, literal) => literal ?? "0");
|
|
50
|
+
}
|
|
51
|
+
function isMaskFieldPreset(mask) {
|
|
52
|
+
return Object.hasOwn(maskFieldPresets, mask);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Keeps the input masked while the user edits it, emitting only the
|
|
56
|
+
* characters of the tokens (Vuetify's `unmask`), with letters in uppercase.
|
|
57
|
+
*
|
|
58
|
+
* A `v-model` out of the mask's format (masked, with lowercase letters, or with
|
|
59
|
+
* characters or length the mask doesn't accept) is displayed normalized,
|
|
60
|
+
* without being emitted, and validated as it is, so a value that doesn't fit
|
|
61
|
+
* the mask fails validation.
|
|
62
|
+
*/
|
|
63
|
+
function useMaskField(props, model) {
|
|
64
|
+
const mask = computed(() => resolveMask(props.mask));
|
|
65
|
+
const tokens = computed(() => getMaskTokens(mask.value));
|
|
66
|
+
const acceptsLetters = computed(() => tokens.value.includes("N"));
|
|
67
|
+
const vuetifyMask = useMask({ get mask() {
|
|
68
|
+
return {
|
|
69
|
+
mask: mask.value,
|
|
70
|
+
tokens: {}
|
|
71
|
+
};
|
|
72
|
+
} });
|
|
73
|
+
const displayValue = computed(() => vuetifyMask.mask(unmask(model.value)));
|
|
74
|
+
const placeholder = computed(() => props.placeholder ?? getMaskPlaceholder(mask.value));
|
|
75
|
+
const inputMode = computed(() => acceptsLetters.value ? "text" : "numeric");
|
|
76
|
+
const autocapitalize = computed(() => acceptsLetters.value ? "characters" : void 0);
|
|
77
|
+
const defaultRules = useRules(props);
|
|
78
|
+
const rules = computed(() => [
|
|
79
|
+
...defaultRules.value,
|
|
80
|
+
(value) => !value || !isValid(value) || isComplete(value) || "Valor incompleto",
|
|
81
|
+
(value) => !value || isValid(value) || "Valor inválido"
|
|
82
|
+
]);
|
|
83
|
+
function unmask(text) {
|
|
84
|
+
return vuetifyMask.unmask(text) ?? "";
|
|
85
|
+
}
|
|
86
|
+
function isValid(value) {
|
|
87
|
+
return unmask(value).toUpperCase() === unmask(vuetifyMask.mask(value));
|
|
88
|
+
}
|
|
89
|
+
function isComplete(value) {
|
|
90
|
+
return unmask(vuetifyMask.mask(value)).length === tokens.value.length;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Returns the position in a masked text right after the given number of
|
|
94
|
+
* characters of the value, skipping the literals after it unless the user
|
|
95
|
+
* deleted backwards (as Vuetify's mask input does).
|
|
96
|
+
*/
|
|
97
|
+
function findCaret(masked, valueLength, deletedBackwards) {
|
|
98
|
+
let caret = 0;
|
|
99
|
+
let found = 0;
|
|
100
|
+
while (found < valueLength) {
|
|
101
|
+
if (!vuetifyMask.isDelimiter(masked, caret)) found++;
|
|
102
|
+
caret++;
|
|
103
|
+
}
|
|
104
|
+
if (!deletedBackwards) while (vuetifyMask.isDelimiter(masked, caret)) caret++;
|
|
105
|
+
return caret;
|
|
106
|
+
}
|
|
107
|
+
function update(input, deletedBackwards) {
|
|
108
|
+
const caret = input.selectionStart ?? input.value.length;
|
|
109
|
+
const masked = vuetifyMask.mask(unmask(input.value));
|
|
110
|
+
const newCaret = findCaret(masked, unmask(vuetifyMask.mask(unmask(input.value.slice(0, caret)))).length, deletedBackwards);
|
|
111
|
+
const value = unmask(masked);
|
|
112
|
+
input.value = masked;
|
|
113
|
+
input.setSelectionRange(newCaret, newCaret);
|
|
114
|
+
if (value !== unmask(displayValue.value)) model.value = value;
|
|
115
|
+
}
|
|
116
|
+
function onInput(event) {
|
|
117
|
+
if (!event.isComposing) update(event.target, event.inputType.endsWith("Backward"));
|
|
118
|
+
}
|
|
119
|
+
function onCompositionEnd(event) {
|
|
120
|
+
update(event.target, false);
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
displayValue,
|
|
124
|
+
placeholder,
|
|
125
|
+
inputMode,
|
|
126
|
+
autocapitalize,
|
|
127
|
+
rules,
|
|
128
|
+
onInput,
|
|
129
|
+
onCompositionEnd
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
//#endregion
|
|
133
|
+
export { getMaskPlaceholder, getMaskTokens, maskFieldPresets, resolveMask, toVuetifyMask, useMaskField };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { isEmail, isEmpty, isPhone, isUrl } from "@nexdom/shared/utils";
|
|
2
|
+
//#region src/composables/inputs/rules.ts
|
|
3
|
+
function required(value) {
|
|
4
|
+
return !isEmpty(value) || "Campo obrigatório";
|
|
5
|
+
}
|
|
6
|
+
function phone(value) {
|
|
7
|
+
return isEmpty(value) || isPhone(value) || "Número inválido";
|
|
8
|
+
}
|
|
9
|
+
function email(value) {
|
|
10
|
+
return isEmpty(value) || isEmail(value) || "E-mail inválido";
|
|
11
|
+
}
|
|
12
|
+
function url(value) {
|
|
13
|
+
return isEmpty(value) || isUrl(value) || "URL inválida";
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { email, phone, required, url };
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { icons } from "../../consts/icons.js";
|
|
2
|
+
import { email, phone, url } from "./rules.js";
|
|
3
|
+
import { useRules } from "./fields.js";
|
|
4
|
+
import { computed, readonly, ref, toValue, watch } from "vue";
|
|
5
|
+
//#region src/composables/inputs/text-field.ts
|
|
6
|
+
const textFieldTypeToVuetifyType = {
|
|
7
|
+
text: "text",
|
|
8
|
+
phone: "tel",
|
|
9
|
+
email: "email",
|
|
10
|
+
url: "url",
|
|
11
|
+
password: "password",
|
|
12
|
+
search: "text"
|
|
13
|
+
};
|
|
14
|
+
const textFieldTypeToVuetifyRule = {
|
|
15
|
+
phone,
|
|
16
|
+
email,
|
|
17
|
+
url
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Maps the field type to the input type, showing as plain text a password
|
|
21
|
+
* the user chose to make visible.
|
|
22
|
+
*/
|
|
23
|
+
function useTextFieldType(type, isPasswordVisible) {
|
|
24
|
+
return computed(() => {
|
|
25
|
+
const typeValue = toValue(type);
|
|
26
|
+
if (typeValue === "password" && toValue(isPasswordVisible)) return "text";
|
|
27
|
+
return textFieldTypeToVuetifyType[typeValue];
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Controls the icon at the end of the field: a decorative magnifier on
|
|
32
|
+
* `search`, and the toggle that shows or hides the value on `password`. The
|
|
33
|
+
* toggle works with click, `Enter` and `Space`, does nothing while disabled,
|
|
34
|
+
* and the password hides again whenever the type stops being `password`.
|
|
35
|
+
*/
|
|
36
|
+
function useTextFieldAppendIcon(type, disabled) {
|
|
37
|
+
const isPasswordField = computed(() => toValue(type) === "password");
|
|
38
|
+
const isPasswordVisible = ref(false);
|
|
39
|
+
watch(isPasswordField, () => {
|
|
40
|
+
isPasswordVisible.value = false;
|
|
41
|
+
});
|
|
42
|
+
function toggle() {
|
|
43
|
+
if (toValue(disabled)) return;
|
|
44
|
+
isPasswordVisible.value = !isPasswordVisible.value;
|
|
45
|
+
}
|
|
46
|
+
function onKeydown(event) {
|
|
47
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
48
|
+
event.preventDefault();
|
|
49
|
+
event.stopPropagation();
|
|
50
|
+
toggle();
|
|
51
|
+
}
|
|
52
|
+
const iconProps = computed(() => {
|
|
53
|
+
if (toValue(type) === "search") return { icon: icons.search };
|
|
54
|
+
if (!isPasswordField.value) return void 0;
|
|
55
|
+
return {
|
|
56
|
+
icon: isPasswordVisible.value ? icons.hidePassword : icons.showPassword,
|
|
57
|
+
"aria-label": "Mostrar senha",
|
|
58
|
+
"aria-pressed": isPasswordVisible.value,
|
|
59
|
+
onClick: toggle,
|
|
60
|
+
onKeydown
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
isPasswordVisible: readonly(isPasswordVisible),
|
|
65
|
+
iconProps
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function useTextFieldRules(props) {
|
|
69
|
+
const defaultRules = useRules(props);
|
|
70
|
+
return computed(() => {
|
|
71
|
+
const rules = [...defaultRules.value];
|
|
72
|
+
const typeRule = associatedRuleType(props.type) ? textFieldTypeToVuetifyRule[props.type] : void 0;
|
|
73
|
+
if (typeRule) rules.push(typeRule);
|
|
74
|
+
return rules;
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function associatedRuleType(type) {
|
|
78
|
+
return type in textFieldTypeToVuetifyRule;
|
|
79
|
+
}
|
|
80
|
+
//#endregion
|
|
81
|
+
export { useTextFieldAppendIcon, useTextFieldRules, useTextFieldType };
|