@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,54 @@
|
|
|
1
|
+
import { ColorVariant } from "../colors/types.js";
|
|
2
|
+
import { MaybeRefOrGetter } from "vue";
|
|
3
|
+
//#region src/composables/navigation/types.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Values compared by `useUnsavedChanges`.
|
|
6
|
+
*/
|
|
7
|
+
export interface UnsavedChangesValues<T> {
|
|
8
|
+
/**
|
|
9
|
+
* Original (last saved) value. To mark the form as saved, assign it a copy of the current value.
|
|
10
|
+
*/
|
|
11
|
+
previous: MaybeRefOrGetter<T>;
|
|
12
|
+
/**
|
|
13
|
+
* Current value of the form.
|
|
14
|
+
*/
|
|
15
|
+
current: MaybeRefOrGetter<T>;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Options of `useUnsavedChanges`.
|
|
19
|
+
*/
|
|
20
|
+
export interface UnsavedChangesOptions {
|
|
21
|
+
/**
|
|
22
|
+
* Plain text displayed as the confirmation dialog's title. HTML isn't interpreted.
|
|
23
|
+
* @default "Alterações não salvas"
|
|
24
|
+
*/
|
|
25
|
+
title?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Plain text displayed as the confirmation dialog's message. HTML isn't interpreted.
|
|
28
|
+
* @default "Existem alterações que ainda não foram salvas. Deseja sair sem salvar?"
|
|
29
|
+
*/
|
|
30
|
+
message?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Text displayed on the button which leaves the page.
|
|
33
|
+
* @default "Sair sem salvar"
|
|
34
|
+
*/
|
|
35
|
+
confirmText?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Text displayed on the button which cancels the navigation.
|
|
38
|
+
* @default "Continuar editando"
|
|
39
|
+
*/
|
|
40
|
+
cancelText?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Applies a color to the button which leaves the page.
|
|
43
|
+
* One of `primary`, `secondary`, `positive`, `informative`, `caution`, or `danger`.
|
|
44
|
+
* @default "danger"
|
|
45
|
+
*/
|
|
46
|
+
color?: ColorVariant;
|
|
47
|
+
/**
|
|
48
|
+
* Also asks for the browser's native confirmation on reload (F5), tab close or leaving the app
|
|
49
|
+
* through an external link.
|
|
50
|
+
* @default true
|
|
51
|
+
*/
|
|
52
|
+
beforeUnload?: boolean;
|
|
53
|
+
}
|
|
54
|
+
//#endregion
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { isExternalRoute } from "./use-route-props.js";
|
|
2
|
+
import { useDisplay } from "vuetify";
|
|
3
|
+
import { computed, inject, nextTick, onMounted, ref, toValue, watch } from "vue";
|
|
4
|
+
import { isDeepEqual } from "@nexdom/shared/utils";
|
|
5
|
+
import { routerKey } from "vue-router";
|
|
6
|
+
//#region src/composables/navigation/use-navigation-rail.ts
|
|
7
|
+
/**
|
|
8
|
+
* Mouse events the rail dispatches on the drawer to expand or collapse it, which its own listeners
|
|
9
|
+
* ignore.
|
|
10
|
+
*/
|
|
11
|
+
const syntheticEvents = /* @__PURE__ */ new WeakSet();
|
|
12
|
+
/**
|
|
13
|
+
* Whether the navigation menu is a temporary drawer, opened by the app bar's button, instead of a
|
|
14
|
+
* rail. Starts as the drawer and switches on mount, so server-rendered pages (the docs), rendered
|
|
15
|
+
* without a window width, hydrate without mismatches.
|
|
16
|
+
*/
|
|
17
|
+
function useMobileNavigation() {
|
|
18
|
+
const { mobile } = useDisplay();
|
|
19
|
+
const mounted = ref(false);
|
|
20
|
+
onMounted(() => mounted.value = true);
|
|
21
|
+
return computed(() => !mounted.value || mobile.value);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* State of the navigation menu as a rail, above the mobile breakpoint: collapsed to its icons, and
|
|
25
|
+
* expanded over the page while the mouse is over it, the keyboard focus is in it or the user is
|
|
26
|
+
* searching. `Esc` collapses it until the mouse enters it, the focus leaves and enters it again
|
|
27
|
+
* or the user searches.
|
|
28
|
+
*
|
|
29
|
+
* The drawer only expands its rail on its own mouse events, so the rail dispatches them itself to
|
|
30
|
+
* expand on focus, and to stay expanded when the mouse leaves with the focus inside.
|
|
31
|
+
*/
|
|
32
|
+
function useNavigationRail(options) {
|
|
33
|
+
const { open, field, searchItem, items, search } = options;
|
|
34
|
+
const mobile = useMobileNavigation();
|
|
35
|
+
const router = inject(routerKey, null);
|
|
36
|
+
const pointerInside = ref(false);
|
|
37
|
+
const focusInside = ref(false);
|
|
38
|
+
const searching = ref(false);
|
|
39
|
+
/** Collapsed by `Esc`: the focus doesn't expand it again until it leaves the rail. */
|
|
40
|
+
const dismissed = ref(false);
|
|
41
|
+
let restoringFocus = false;
|
|
42
|
+
const expandedGroups = ref([]);
|
|
43
|
+
const collapsed = computed(() => !mobile.value && !(pointerInside.value || focusInside.value || searching.value));
|
|
44
|
+
const drawerOpen = computed({
|
|
45
|
+
get: () => !mobile.value || open.value,
|
|
46
|
+
set: (value) => {
|
|
47
|
+
if (mobile.value) open.value = value;
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
/** The drawer's model below the mobile breakpoint, and the rail's search state above it. */
|
|
51
|
+
const menuState = computed(() => mobile.value ? open : searching);
|
|
52
|
+
const menuOpen = computed({
|
|
53
|
+
get: () => menuState.value.value,
|
|
54
|
+
set: (value) => {
|
|
55
|
+
menuState.value.value = value;
|
|
56
|
+
dismissed.value = false;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
const opened = computed({
|
|
60
|
+
get: () => collapsed.value ? [] : expandedGroups.value,
|
|
61
|
+
set: (value) => expandedGroups.value = value
|
|
62
|
+
});
|
|
63
|
+
watch(collapsed, (isCollapsed) => {
|
|
64
|
+
syncHover();
|
|
65
|
+
if (isCollapsed) search.value = "";
|
|
66
|
+
else expandedGroups.value = currentGroups();
|
|
67
|
+
});
|
|
68
|
+
watch(() => router?.currentRoute.value, () => searching.value = false);
|
|
69
|
+
function onFocusin(event) {
|
|
70
|
+
focusInside.value = !dismissed.value && event.target.matches(":focus-visible");
|
|
71
|
+
}
|
|
72
|
+
function onFocusout(event) {
|
|
73
|
+
if (isInside(event, event.relatedTarget)) return;
|
|
74
|
+
focusInside.value = false;
|
|
75
|
+
searching.value = false;
|
|
76
|
+
dismissed.value = false;
|
|
77
|
+
}
|
|
78
|
+
function onMouseenter(event) {
|
|
79
|
+
if (syntheticEvents.has(event)) return;
|
|
80
|
+
pointerInside.value = true;
|
|
81
|
+
dismissed.value = false;
|
|
82
|
+
}
|
|
83
|
+
function onMouseleave(event) {
|
|
84
|
+
if (syntheticEvents.has(event)) return;
|
|
85
|
+
pointerInside.value = false;
|
|
86
|
+
syncHover();
|
|
87
|
+
}
|
|
88
|
+
async function dismiss() {
|
|
89
|
+
menuOpen.value = false;
|
|
90
|
+
pointerInside.value = false;
|
|
91
|
+
focusInside.value = false;
|
|
92
|
+
dismissed.value = true;
|
|
93
|
+
await nextTick();
|
|
94
|
+
if (collapsed.value && isHidingFocus()) {
|
|
95
|
+
restoringFocus = true;
|
|
96
|
+
toValue(searchItem).$el.focus();
|
|
97
|
+
restoringFocus = false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Whether the focus is on an element the collapsed rail hides: the search field or an item of a
|
|
102
|
+
* group, which would leave the focus on the page's body.
|
|
103
|
+
*/
|
|
104
|
+
function isHidingFocus() {
|
|
105
|
+
const { activeElement } = document;
|
|
106
|
+
const groupsItems = [...findDrawer().querySelectorAll(".v-list-group__items")];
|
|
107
|
+
return toValue(field).$el.contains(activeElement) || groupsItems.some((items) => items.contains(activeElement));
|
|
108
|
+
}
|
|
109
|
+
function isRestoringFocus() {
|
|
110
|
+
return restoringFocus;
|
|
111
|
+
}
|
|
112
|
+
/** Expands or collapses the drawer's rail, through the mouse events it listens to. */
|
|
113
|
+
function syncHover() {
|
|
114
|
+
const event = new MouseEvent(collapsed.value ? "mouseleave" : "mouseenter");
|
|
115
|
+
syntheticEvents.add(event);
|
|
116
|
+
findDrawer().dispatchEvent(event);
|
|
117
|
+
}
|
|
118
|
+
function findDrawer() {
|
|
119
|
+
return toValue(field).$el.closest(".v-navigation-drawer");
|
|
120
|
+
}
|
|
121
|
+
function currentGroups() {
|
|
122
|
+
return (toValue(items) ?? []).filter((item) => isParentItem(item) && hasCurrentRoute(item)).map(({ description }) => description);
|
|
123
|
+
}
|
|
124
|
+
function isActiveGroup(item) {
|
|
125
|
+
return collapsed.value && hasCurrentRoute(item);
|
|
126
|
+
}
|
|
127
|
+
function hasCurrentRoute(item) {
|
|
128
|
+
return item.items.some(({ route }) => isCurrentRoute(route));
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Whether the route is the current page, or one of its parents, as the links highlight
|
|
132
|
+
* themselves: by the matched route record and the params the route sets.
|
|
133
|
+
*/
|
|
134
|
+
function isCurrentRoute(route) {
|
|
135
|
+
if (!router || route === void 0 || isExternalRoute(route)) return false;
|
|
136
|
+
const { matched, params } = router.resolve(route);
|
|
137
|
+
const current = router.currentRoute.value;
|
|
138
|
+
const record = matched.at(-1);
|
|
139
|
+
return current.matched.includes(record) && Object.entries(params).every(([key, value]) => isDeepEqual(value, current.params[key]));
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
mobile,
|
|
143
|
+
collapsed,
|
|
144
|
+
drawerOpen,
|
|
145
|
+
menuOpen,
|
|
146
|
+
opened,
|
|
147
|
+
isActiveGroup,
|
|
148
|
+
dismiss,
|
|
149
|
+
isRestoringFocus,
|
|
150
|
+
onFocusin,
|
|
151
|
+
onFocusout,
|
|
152
|
+
onMouseenter,
|
|
153
|
+
onMouseleave
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function isParentItem(item) {
|
|
157
|
+
return "items" in item;
|
|
158
|
+
}
|
|
159
|
+
function isInside(event, target) {
|
|
160
|
+
return target instanceof Node && event.currentTarget.contains(target);
|
|
161
|
+
}
|
|
162
|
+
//#endregion
|
|
163
|
+
export { isParentItem, useMobileNavigation, useNavigationRail };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { computed, nextTick, onMounted, onUnmounted, ref, toValue, watch } from "vue";
|
|
2
|
+
//#region src/composables/navigation/use-navigation-search-shortcut.ts
|
|
3
|
+
/**
|
|
4
|
+
* Mounted navigation menus, in mounting order. Only the first one answers the shortcut, so pages
|
|
5
|
+
* with more than one `UMain` (like the docs) don't open every menu at once.
|
|
6
|
+
*/
|
|
7
|
+
const owners = ref([]);
|
|
8
|
+
/**
|
|
9
|
+
* Focus targets where the shortcut is left to the page: overlays (dialogs, confirmations and
|
|
10
|
+
* menus) and other modals, the activators of open menus (which keep the focus when opened by a
|
|
11
|
+
* click, and would pull it back from the navigation menu) and rich text editors, which usually
|
|
12
|
+
* take Ctrl+K to insert links.
|
|
13
|
+
*/
|
|
14
|
+
const IGNORED_TARGETS = [
|
|
15
|
+
".v-overlay",
|
|
16
|
+
"dialog",
|
|
17
|
+
"[aria-modal='true']",
|
|
18
|
+
"[aria-haspopup][aria-expanded='true']",
|
|
19
|
+
"[contenteditable]:not([contenteditable='false'])"
|
|
20
|
+
].join(", ");
|
|
21
|
+
/** Accessible name of the search field, whose placeholder also shows the shortcut. */
|
|
22
|
+
const SEARCH_NAME = "Buscar";
|
|
23
|
+
/**
|
|
24
|
+
* Shortcut keys for the current platform. Starts with the non-macOS keys and switches on mount,
|
|
25
|
+
* so server-rendered pages (the docs) hydrate without mismatches.
|
|
26
|
+
*/
|
|
27
|
+
function useNavigationShortcutKeys() {
|
|
28
|
+
const isMac = ref(false);
|
|
29
|
+
onMounted(() => isMac.value = isMacOS());
|
|
30
|
+
return {
|
|
31
|
+
label: computed(() => isMac.value ? "⌘K" : "Ctrl+K"),
|
|
32
|
+
ariaKeyshortcuts: computed(() => isMac.value ? "Meta+K" : "Control+K")
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Listens to Ctrl+K (⌘K on macOS) on `window` while mounted: opens the menu, waits for it to
|
|
37
|
+
* render (the drawer is `inert` while closed, and the rail only shows the field while expanded)
|
|
38
|
+
* and focuses the search field (a disabled field, as while loading, can't take the focus). When
|
|
39
|
+
* the menu opened by the shortcut closes with the focus inside it, the focus goes back to where it
|
|
40
|
+
* was.
|
|
41
|
+
*/
|
|
42
|
+
function useNavigationSearchShortcut(options) {
|
|
43
|
+
const { open, field } = options;
|
|
44
|
+
const owner = Symbol("navigation-search-shortcut");
|
|
45
|
+
/** Element focused before the shortcut opened the menu, which gets the focus back on close. */
|
|
46
|
+
let origin = null;
|
|
47
|
+
onMounted(() => {
|
|
48
|
+
owners.value.push(owner);
|
|
49
|
+
globalThis.addEventListener("keydown", onKeydown, { capture: true });
|
|
50
|
+
findInput().setAttribute("aria-label", SEARCH_NAME);
|
|
51
|
+
});
|
|
52
|
+
onUnmounted(() => {
|
|
53
|
+
owners.value = owners.value.filter((item) => item !== owner);
|
|
54
|
+
globalThis.removeEventListener("keydown", onKeydown, { capture: true });
|
|
55
|
+
});
|
|
56
|
+
watch(open, (isOpen) => {
|
|
57
|
+
if (isOpen) return;
|
|
58
|
+
const target = origin;
|
|
59
|
+
origin = null;
|
|
60
|
+
if (!findInput().closest(".v-navigation-drawer").contains(document.activeElement)) return;
|
|
61
|
+
if (target instanceof HTMLElement || target instanceof SVGElement) target.focus();
|
|
62
|
+
});
|
|
63
|
+
function onKeydown(event) {
|
|
64
|
+
if (owners.value[0] !== owner || isIgnoredTarget(event)) return;
|
|
65
|
+
if (isShortcut(event)) {
|
|
66
|
+
event.preventDefault();
|
|
67
|
+
openSearch(document.activeElement);
|
|
68
|
+
} else if (event.key === "Escape" && origin) open.value = false;
|
|
69
|
+
}
|
|
70
|
+
async function openSearch(from) {
|
|
71
|
+
if (!open.value) origin = from;
|
|
72
|
+
open.value = true;
|
|
73
|
+
await nextTick();
|
|
74
|
+
const input = findInput();
|
|
75
|
+
input.focus({ preventScroll: true });
|
|
76
|
+
input.select();
|
|
77
|
+
}
|
|
78
|
+
function findInput() {
|
|
79
|
+
return toValue(field).$el.querySelector("input");
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
...useNavigationShortcutKeys(),
|
|
83
|
+
openSearch
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function isMacOS() {
|
|
87
|
+
return navigator.userAgent.includes("Macintosh");
|
|
88
|
+
}
|
|
89
|
+
function isShortcut(event) {
|
|
90
|
+
return (isMacOS() ? event.metaKey : event.ctrlKey) && !event.shiftKey && !event.altKey && !event.isComposing && event.key.toLowerCase() === "k";
|
|
91
|
+
}
|
|
92
|
+
function isIgnoredTarget(event) {
|
|
93
|
+
return event.target instanceof Element && event.target.closest(IGNORED_TARGETS) !== null;
|
|
94
|
+
}
|
|
95
|
+
//#endregion
|
|
96
|
+
export { owners, useNavigationSearchShortcut, useNavigationShortcutKeys };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { computed, toValue } from "vue";
|
|
2
|
+
//#region src/composables/navigation/use-route-props.ts
|
|
3
|
+
function useRouteProps(route) {
|
|
4
|
+
return computed(() => routeToProps(route));
|
|
5
|
+
}
|
|
6
|
+
function routeToProps(route) {
|
|
7
|
+
const location = toValue(route);
|
|
8
|
+
return isExternalRoute(location) ? { href: location } : { to: location };
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Maps a menu item's route to its list item's props. An item without a route only runs its action,
|
|
12
|
+
* so it's a button: Vuetify would make it a list item, which the menus' groups of links don't allow.
|
|
13
|
+
*/
|
|
14
|
+
function routeToListItemProps(route) {
|
|
15
|
+
return route ? routeToProps(route) : { role: "button" };
|
|
16
|
+
}
|
|
17
|
+
function isExternalRoute(route) {
|
|
18
|
+
return typeof route === "string" && route.startsWith("http");
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { useRouteProps as default, isExternalRoute, routeToListItemProps, routeToProps };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { UnsavedChangesOptions, UnsavedChangesValues } from "./types.js";
|
|
2
|
+
import { ComputedRef } from "vue";
|
|
3
|
+
//#region src/composables/navigation/use-unsaved-changes.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Composable which compares the original and the current values of a form, and asks the user to
|
|
6
|
+
* confirm before leaving the page while they differ.
|
|
7
|
+
*
|
|
8
|
+
* Must be called in the `setup` of a component. Leaving through vue-router is only guarded in
|
|
9
|
+
* components rendered by a `<RouterView>`, and the confirmation dialog is displayed by `UMain`.
|
|
10
|
+
*
|
|
11
|
+
* @param values - The original (`previous`) and current (`current`) values, as refs, getters or
|
|
12
|
+
* reactive objects. They're compared in depth: key order doesn't matter, a key holding `undefined`
|
|
13
|
+
* equals a missing key, dates are compared by their time and arrays item by item, in order.
|
|
14
|
+
* @param options - Texts and color of the confirmation dialog, and whether to ask for the
|
|
15
|
+
* browser's native confirmation before unloading the page.
|
|
16
|
+
* @returns `hasChanges`, a computed property which is `true` while the values differ
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* const saved = ref({ name: "Maria" });
|
|
21
|
+
* const form = ref(structuredClone(toRaw(saved.value)));
|
|
22
|
+
*
|
|
23
|
+
* const { hasChanges } = useUnsavedChanges({ previous: saved, current: form });
|
|
24
|
+
*
|
|
25
|
+
* async function save() {
|
|
26
|
+
* await api.save(form.value);
|
|
27
|
+
* // Marks the form as saved
|
|
28
|
+
* saved.value = structuredClone(toRaw(form.value));
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export default function useUnsavedChanges<T>(values: UnsavedChangesValues<T>, options?: UnsavedChangesOptions): {
|
|
33
|
+
hasChanges: ComputedRef<boolean>;
|
|
34
|
+
};
|
|
35
|
+
//#endregion
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import useConfirm from "../dialogs/use-confirm.js";
|
|
2
|
+
import { computed, onActivated, onDeactivated, onMounted, onUnmounted, toValue, watch } from "vue";
|
|
3
|
+
import { isDeepEqual } from "@nexdom/shared/utils";
|
|
4
|
+
import { onBeforeRouteLeave } from "vue-router";
|
|
5
|
+
//#region src/composables/navigation/use-unsaved-changes.ts
|
|
6
|
+
/**
|
|
7
|
+
* Calls whose page is on display with unsaved changes. The `beforeunload` listener is registered
|
|
8
|
+
* while at least one of them exists, so a call that stops guarding doesn't remove the guard of the
|
|
9
|
+
* others.
|
|
10
|
+
*/
|
|
11
|
+
const unloadGuards = /* @__PURE__ */ new Set();
|
|
12
|
+
function preventUnload(event) {
|
|
13
|
+
event.preventDefault();
|
|
14
|
+
}
|
|
15
|
+
function updateUnloadListener() {
|
|
16
|
+
if (unloadGuards.size > 0) globalThis.addEventListener("beforeunload", preventUnload);
|
|
17
|
+
else globalThis.removeEventListener("beforeunload", preventUnload);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Composable which compares the original and the current values of a form, and asks the user to
|
|
21
|
+
* confirm before leaving the page while they differ.
|
|
22
|
+
*
|
|
23
|
+
* Must be called in the `setup` of a component. Leaving through vue-router is only guarded in
|
|
24
|
+
* components rendered by a `<RouterView>`, and the confirmation dialog is displayed by `UMain`.
|
|
25
|
+
*
|
|
26
|
+
* @param values - The original (`previous`) and current (`current`) values, as refs, getters or
|
|
27
|
+
* reactive objects. They're compared in depth: key order doesn't matter, a key holding `undefined`
|
|
28
|
+
* equals a missing key, dates are compared by their time and arrays item by item, in order.
|
|
29
|
+
* @param options - Texts and color of the confirmation dialog, and whether to ask for the
|
|
30
|
+
* browser's native confirmation before unloading the page.
|
|
31
|
+
* @returns `hasChanges`, a computed property which is `true` while the values differ
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* const saved = ref({ name: "Maria" });
|
|
36
|
+
* const form = ref(structuredClone(toRaw(saved.value)));
|
|
37
|
+
*
|
|
38
|
+
* const { hasChanges } = useUnsavedChanges({ previous: saved, current: form });
|
|
39
|
+
*
|
|
40
|
+
* async function save() {
|
|
41
|
+
* await api.save(form.value);
|
|
42
|
+
* // Marks the form as saved
|
|
43
|
+
* saved.value = structuredClone(toRaw(form.value));
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
function useUnsavedChanges(values, options = {}) {
|
|
48
|
+
const hasChanges = computed(() => !isDeepEqual(toValue(values.previous), toValue(values.current)));
|
|
49
|
+
const { confirm } = useConfirm();
|
|
50
|
+
/** Answer of the dialog on display, shared by every navigation made while it's open. */
|
|
51
|
+
let pending;
|
|
52
|
+
onBeforeRouteLeave(() => {
|
|
53
|
+
if (!hasChanges.value) return true;
|
|
54
|
+
pending ??= confirm({
|
|
55
|
+
title: options.title ?? "Alterações não salvas",
|
|
56
|
+
message: options.message ?? "Existem alterações que ainda não foram salvas. Deseja sair sem salvar?",
|
|
57
|
+
confirmText: options.confirmText ?? "Sair sem salvar",
|
|
58
|
+
cancelText: options.cancelText ?? "Continuar editando",
|
|
59
|
+
color: options.color ?? "danger"
|
|
60
|
+
}, async () => {}).then((answer) => answer !== false).finally(() => pending = void 0);
|
|
61
|
+
return pending;
|
|
62
|
+
});
|
|
63
|
+
/** Whether the component is on display, which is false while `<KeepAlive>` keeps it deactivated. */
|
|
64
|
+
let isActive = true;
|
|
65
|
+
if (options.beforeUnload ?? true) {
|
|
66
|
+
onMounted(() => watch(hasChanges, updateBeforeUnload, { immediate: true }));
|
|
67
|
+
onUnmounted(deactivate);
|
|
68
|
+
onDeactivated(deactivate);
|
|
69
|
+
onActivated(() => {
|
|
70
|
+
isActive = true;
|
|
71
|
+
updateBeforeUnload();
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/** Identifies this call in `unloadGuards`. */
|
|
75
|
+
const unloadGuard = {};
|
|
76
|
+
function updateBeforeUnload() {
|
|
77
|
+
if (hasChanges.value && isActive) unloadGuards.add(unloadGuard);
|
|
78
|
+
else unloadGuards.delete(unloadGuard);
|
|
79
|
+
updateUnloadListener();
|
|
80
|
+
}
|
|
81
|
+
function deactivate() {
|
|
82
|
+
isActive = false;
|
|
83
|
+
updateBeforeUnload();
|
|
84
|
+
}
|
|
85
|
+
return { hasChanges };
|
|
86
|
+
}
|
|
87
|
+
//#endregion
|
|
88
|
+
export { useUnsavedChanges as default };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { computed, toValue } from "vue";
|
|
2
|
+
//#region src/composables/section/section.ts
|
|
3
|
+
const sectionVariantToVuetifyVariant = {
|
|
4
|
+
primary: "elevated",
|
|
5
|
+
secondary: "outlined"
|
|
6
|
+
};
|
|
7
|
+
function useSectionVariant(variant) {
|
|
8
|
+
return computed(() => {
|
|
9
|
+
const variantValue = toValue(variant);
|
|
10
|
+
return variantValue ? sectionVariantToVuetifyVariant[variantValue] : sectionVariantToVuetifyVariant.primary;
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
const sectionTextAlignToClasses = {
|
|
14
|
+
start: {
|
|
15
|
+
content: "text-start",
|
|
16
|
+
actions: "justify-end"
|
|
17
|
+
},
|
|
18
|
+
center: {
|
|
19
|
+
content: "text-center",
|
|
20
|
+
actions: "justify-center"
|
|
21
|
+
},
|
|
22
|
+
end: {
|
|
23
|
+
content: "text-end",
|
|
24
|
+
actions: "justify-end"
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
function useSectionTextAlign(textAlign) {
|
|
28
|
+
return computed(() => {
|
|
29
|
+
const textAlignValue = toValue(textAlign);
|
|
30
|
+
return textAlignValue ? sectionTextAlignToClasses[textAlignValue] : sectionTextAlignToClasses.start;
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
export { useSectionTextAlign, useSectionVariant };
|
package/dist/composables.d.ts
CHANGED
|
@@ -1,67 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
message: string;
|
|
8
|
-
/**
|
|
9
|
-
* Applies a color to the button.
|
|
10
|
-
* One of `positive`, `informative`, `caution`, or `danger`.
|
|
11
|
-
* @default "informative"
|
|
12
|
-
*/
|
|
13
|
-
color?: FeedbackColorVariant;
|
|
14
|
-
}
|
|
15
|
-
//#endregion
|
|
16
|
-
//#region src/composables/dialogs/use-toast.d.ts
|
|
17
|
-
/**
|
|
18
|
-
* Composable which returns a function to display toast messages.
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* ```ts
|
|
22
|
-
* const { toast } = useToast();
|
|
23
|
-
* toast({ message: "Hello, World!" });
|
|
24
|
-
* ```
|
|
25
|
-
*/
|
|
26
|
-
declare function useToast(): {
|
|
27
|
-
toast: typeof toast;
|
|
28
|
-
};
|
|
29
|
-
/**
|
|
30
|
-
* Displays a toast message.
|
|
31
|
-
* @param {ToastOptions} options - Options for the toast message.
|
|
32
|
-
*
|
|
33
|
-
* @example
|
|
34
|
-
* ```ts
|
|
35
|
-
* toast({ message: "Hello, World!" });
|
|
36
|
-
* ```
|
|
37
|
-
*/
|
|
38
|
-
declare function toast(options: ToastOptions): void;
|
|
39
|
-
//#endregion
|
|
40
|
-
//#region src/composables/dialogs/use-run-or-toast..d.ts
|
|
41
|
-
/**
|
|
42
|
-
* Generate `run` method and a computed property `isRunning`.
|
|
43
|
-
* Calling `run` will reactively update `isRunning`.
|
|
44
|
-
*
|
|
45
|
-
* @example
|
|
46
|
-
* ```ts
|
|
47
|
-
* const { run, isRunning } = useRunOrToast();
|
|
48
|
-
*
|
|
49
|
-
* // Logs: false
|
|
50
|
-
* console.log(isRunning.value);
|
|
51
|
-
*
|
|
52
|
-
* const promise = run(() => someFunction(param1, param2));
|
|
53
|
-
*
|
|
54
|
-
* // Logs: true
|
|
55
|
-
* console.log(isRunning.value);
|
|
56
|
-
*
|
|
57
|
-
* await promise;
|
|
58
|
-
* // Logs: false
|
|
59
|
-
* console.log(isRunning.value);
|
|
60
|
-
* ```
|
|
61
|
-
*/
|
|
62
|
-
declare function useRunOrToast(): {
|
|
63
|
-
run: <T extends (...args: Parameters<T>) => PromiseLike<Awaited<ReturnType<T>>>>(toRun: T, ...params: Parameters<T>) => Promise<Awaited<ReturnType<T>> | false>;
|
|
64
|
-
isRunning: import("vue").ComputedRef<boolean>;
|
|
65
|
-
};
|
|
66
|
-
//#endregion
|
|
67
|
-
export { useRunOrToast, useToast };
|
|
1
|
+
import useToast from "./composables/dialogs/use-toast.js";
|
|
2
|
+
import useRunOrToast from "./composables/dialogs/use-run-or-toast.js";
|
|
3
|
+
import useDialog from "./composables/dialogs/use-dialog.js";
|
|
4
|
+
import useConfirm from "./composables/dialogs/use-confirm.js";
|
|
5
|
+
import useUnsavedChanges from "./composables/navigation/use-unsaved-changes.js";
|
|
6
|
+
export { useConfirm, useDialog, useRunOrToast, useToast, useUnsavedChanges };
|
package/dist/composables.js
CHANGED
|
@@ -1,65 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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, useToast };
|
|
1
|
+
import useToast from "./composables/dialogs/use-toast.js";
|
|
2
|
+
import useDialog from "./composables/dialogs/use-dialog.js";
|
|
3
|
+
import useRunOrToast from "./composables/dialogs/use-run-or-toast.js";
|
|
4
|
+
import useConfirm from "./composables/dialogs/use-confirm.js";
|
|
5
|
+
import useUnsavedChanges from "./composables/navigation/use-unsaved-changes.js";
|
|
6
|
+
export { useConfirm, useDialog, useRunOrToast, useToast, useUnsavedChanges };
|