@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.
Files changed (222) hide show
  1. package/README.md +1 -1
  2. package/dist/_virtual/_rolldown/runtime.js +7 -0
  3. package/dist/components/app-bar/app-bar.js +5 -0
  4. package/dist/components/app-bar/app-bar.vue_vue_type_script_setup_true_lang.js +286 -0
  5. package/dist/components/app-bar/types.d.ts +96 -0
  6. package/dist/components/button/button.js +5 -0
  7. package/dist/components/button/button.vue.d.ts +31 -0
  8. package/dist/components/button/button.vue_vue_type_script_setup_true_lang.js +97 -0
  9. package/dist/components/button/types.d.ts +65 -0
  10. package/dist/components/data-sets/data-set/data-set.js +5 -0
  11. package/dist/components/data-sets/data-set/data-set.vue.d.ts +47 -0
  12. package/dist/components/data-sets/data-set/data-set.vue_vue_type_script_setup_true_lang.js +157 -0
  13. package/dist/components/data-sets/data-set/types.d.ts +93 -0
  14. package/dist/components/data-sets/data-set-bar.js +5 -0
  15. package/dist/components/data-sets/data-set-bar.vue_vue_type_script_setup_true_lang.js +99 -0
  16. package/dist/components/data-sets/data-set-item/data-set-item.js +5 -0
  17. package/dist/components/data-sets/data-set-item/data-set-item.vue.d.ts +35 -0
  18. package/dist/components/data-sets/data-set-item/data-set-item.vue_vue_type_script_setup_true_lang.js +57 -0
  19. package/dist/components/data-sets/data-set-item/types.d.ts +42 -0
  20. package/dist/components/details/details.js +5 -0
  21. package/dist/components/details/details.vue.d.ts +28 -0
  22. package/dist/components/details/details.vue_vue_type_script_setup_true_lang.js +56 -0
  23. package/dist/components/details/types.d.ts +29 -0
  24. package/dist/components/dialogs/dialog/dialog-host-key.js +10 -0
  25. package/dist/components/dialogs/dialog/dialog.js +5 -0
  26. package/dist/components/dialogs/dialog/dialog.vue.d.ts +36 -0
  27. package/dist/components/dialogs/dialog/dialog.vue_vue_type_script_setup_true_lang.js +112 -0
  28. package/dist/components/dialogs/dialog/types.d.ts +47 -0
  29. package/dist/components/dialogs/dialog-host.js +5 -0
  30. package/dist/components/dialogs/dialog-host.vue_vue_type_script_setup_true_lang.js +81 -0
  31. package/dist/components/dialogs/toast.js +5 -0
  32. package/dist/components/dialogs/toast.vue_vue_type_script_setup_true_lang.js +35 -0
  33. package/dist/components/footer/footer.js +5 -0
  34. package/dist/components/footer/footer.vue_vue_type_script_setup_true_lang.js +31 -0
  35. package/dist/components/footer/types.d.ts +16 -0
  36. package/dist/components/form/form.js +5 -0
  37. package/dist/components/form/form.vue.d.ts +32 -0
  38. package/dist/components/form/form.vue_vue_type_script_setup_true_lang.js +37 -0
  39. package/dist/components/form/types.d.ts +15 -0
  40. package/dist/components/grid/column/column.js +5 -0
  41. package/dist/components/grid/column/column.vue.d.ts +33 -0
  42. package/dist/components/grid/column/column.vue_vue_type_script_setup_true_lang.js +45 -0
  43. package/dist/components/grid/column/types.d.ts +27 -0
  44. package/dist/components/grid/container/container.js +5 -0
  45. package/dist/components/grid/container/container.vue.d.ts +27 -0
  46. package/dist/components/grid/container/container.vue_vue_type_script_setup_true_lang.js +33 -0
  47. package/dist/components/grid/container/types.d.ts +17 -0
  48. package/dist/components/grid/row/row.js +5 -0
  49. package/dist/components/grid/row/row.vue.d.ts +29 -0
  50. package/dist/components/grid/row/row.vue_vue_type_script_setup_true_lang.js +61 -0
  51. package/dist/components/grid/row/types.d.ts +63 -0
  52. package/dist/components/icon-button/icon-button.js +5 -0
  53. package/dist/components/icon-button/icon-button.vue.d.ts +23 -0
  54. package/dist/components/icon-button/icon-button.vue_vue_type_script_setup_true_lang.js +51 -0
  55. package/dist/components/icon-button/types.d.ts +32 -0
  56. package/dist/components/img/img.js +5 -0
  57. package/dist/components/img/img.vue.d.ts +23 -0
  58. package/dist/components/img/img.vue_vue_type_script_setup_true_lang.js +61 -0
  59. package/dist/components/img/types.d.ts +43 -0
  60. package/dist/components/inputs/autocomplete-field/autocomplete-field.js +5 -0
  61. package/dist/components/inputs/autocomplete-field/autocomplete-field.vue.d.ts +33 -0
  62. package/dist/components/inputs/autocomplete-field/autocomplete-field.vue_vue_type_script_setup_true_lang.js +161 -0
  63. package/dist/components/inputs/autocomplete-field/types.d.ts +84 -0
  64. package/dist/components/inputs/checkbox/checkbox.js +5 -0
  65. package/dist/components/inputs/checkbox/checkbox.vue.d.ts +33 -0
  66. package/dist/components/inputs/checkbox/checkbox.vue_vue_type_script_setup_true_lang.js +71 -0
  67. package/dist/components/inputs/checkbox/types.d.ts +35 -0
  68. package/dist/components/inputs/date-time-field/date-time-field.js +5 -0
  69. package/dist/components/inputs/date-time-field/date-time-field.vue.d.ts +32 -0
  70. package/dist/components/inputs/date-time-field/date-time-field.vue_vue_type_script_setup_true_lang.js +205 -0
  71. package/dist/components/inputs/date-time-field/types.d.ts +84 -0
  72. package/dist/components/inputs/field-loader.js +5 -0
  73. package/dist/components/inputs/field-loader.vue_vue_type_script_setup_true_lang.js +30 -0
  74. package/dist/components/inputs/mask-field/mask-field.js +5 -0
  75. package/dist/components/inputs/mask-field/mask-field.vue.d.ts +32 -0
  76. package/dist/components/inputs/mask-field/mask-field.vue_vue_type_script_setup_true_lang.js +120 -0
  77. package/dist/components/inputs/mask-field/types.d.ts +118 -0
  78. package/dist/components/inputs/text-field/text-field.js +5 -0
  79. package/dist/components/inputs/text-field/text-field.vue.d.ts +30 -0
  80. package/dist/components/inputs/text-field/text-field.vue_vue_type_script_setup_true_lang.js +119 -0
  81. package/dist/components/inputs/text-field/types.d.ts +70 -0
  82. package/dist/components/link/link.js +5 -0
  83. package/dist/components/link/link.vue.d.ts +27 -0
  84. package/dist/components/link/link.vue_vue_type_script_setup_true_lang.js +32 -0
  85. package/dist/components/link/types.d.ts +18 -0
  86. package/dist/components/main/main.js +5 -0
  87. package/dist/components/main/main.vue.d.ts +33 -0
  88. package/dist/components/main/main.vue_vue_type_script_setup_true_lang.js +104 -0
  89. package/dist/components/main/types.d.ts +75 -0
  90. package/dist/components/navigation-menu/navigation-menu.js +5 -0
  91. package/dist/components/navigation-menu/navigation-menu.vue_vue_type_script_setup_true_lang.js +207 -0
  92. package/dist/components/navigation-menu/types.d.ts +62 -0
  93. package/dist/components/page-transition/page-transition.js +5 -0
  94. package/dist/components/page-transition/page-transition.vue.d.ts +40 -0
  95. package/dist/components/page-transition/page-transition.vue_vue_type_script_setup_true_lang.js +27 -0
  96. package/dist/components/page-transition/types.d.ts +18 -0
  97. package/dist/components/sections/section/section.js +5 -0
  98. package/dist/components/sections/section/section.vue.d.ts +28 -0
  99. package/dist/components/sections/section/section.vue_vue_type_script_setup_true_lang.js +108 -0
  100. package/dist/components/sections/section/types.d.ts +80 -0
  101. package/dist/components/sections/section-content/section-content.js +5 -0
  102. package/dist/components/sections/section-content/section-content.vue.d.ts +27 -0
  103. package/dist/components/sections/section-content/section-content.vue_vue_type_script_setup_true_lang.js +16 -0
  104. package/dist/components/skeleton-loader/skeleton-loader.js +5 -0
  105. package/dist/components/skeleton-loader/skeleton-loader.vue_vue_type_script_setup_true_lang.js +65 -0
  106. package/dist/components/table/table.js +5 -0
  107. package/dist/components/table/table.vue.d.ts +19 -0
  108. package/dist/components/table/table.vue_vue_type_script_setup_true_lang.js +84 -0
  109. package/dist/components/table/types.d.ts +51 -0
  110. package/dist/components.d.ts +23 -940
  111. package/dist/components.js +23 -1254
  112. package/dist/composables/alignment/alignment.js +41 -0
  113. package/dist/composables/alignment/types.d.ts +12 -0
  114. package/dist/composables/button/button.js +54 -0
  115. package/dist/composables/colors/constants.js +27 -0
  116. package/dist/composables/colors/types.d.ts +4 -0
  117. package/dist/composables/colors/use-vuetify-background-color.js +16 -0
  118. package/dist/composables/colors/use-vuetify-color.js +11 -0
  119. package/dist/composables/colors/use-vuetify-text-color.js +12 -0
  120. package/dist/composables/data-set/data-set.js +128 -0
  121. package/dist/composables/dialogs/types.d.ts +88 -0
  122. package/dist/composables/dialogs/use-confirm.d.ts +21 -0
  123. package/dist/composables/dialogs/use-confirm.js +55 -0
  124. package/dist/composables/dialogs/use-decorative-timer.js +25 -0
  125. package/dist/composables/dialogs/use-dialog-focus.js +64 -0
  126. package/dist/composables/dialogs/use-dialog-loading.js +13 -0
  127. package/dist/composables/dialogs/use-dialog.d.ts +36 -0
  128. package/dist/composables/dialogs/use-dialog.js +73 -0
  129. package/dist/composables/dialogs/use-run-or-toast.d.ts +27 -0
  130. package/dist/composables/dialogs/use-run-or-toast.js +65 -0
  131. package/dist/composables/dialogs/use-toast.d.ts +25 -0
  132. package/dist/composables/dialogs/use-toast.js +39 -0
  133. package/dist/composables/dimensions/dimensions.js +18 -0
  134. package/dist/composables/inputs/autocomplete-field.js +39 -0
  135. package/dist/composables/inputs/date-time-field.js +109 -0
  136. package/dist/composables/inputs/fields.js +28 -0
  137. package/dist/composables/inputs/mask-field.js +133 -0
  138. package/dist/composables/inputs/rules.js +16 -0
  139. package/dist/composables/inputs/text-field.js +81 -0
  140. package/dist/composables/navigation/types.d.ts +54 -0
  141. package/dist/composables/navigation/use-navigation-rail.js +163 -0
  142. package/dist/composables/navigation/use-navigation-search-shortcut.js +96 -0
  143. package/dist/composables/navigation/use-route-props.js +21 -0
  144. package/dist/composables/navigation/use-unsaved-changes.d.ts +35 -0
  145. package/dist/composables/navigation/use-unsaved-changes.js +88 -0
  146. package/dist/composables/section/section.js +34 -0
  147. package/dist/composables.d.ts +6 -67
  148. package/dist/composables.js +6 -65
  149. package/dist/consts/icons.d.ts +67 -0
  150. package/dist/consts/icons.js +69 -0
  151. package/dist/consts/locale.js +12 -0
  152. package/dist/consts/types.d.ts +12 -0
  153. package/dist/index.d.ts +1 -13
  154. package/dist/index.js +39 -14
  155. package/dist/plugins.d.ts +15 -4
  156. package/dist/plugins.js +54 -3
  157. package/dist/styles/fonts/UnimedSlab-Regular.woff2 +0 -0
  158. package/dist/styles/fonts/fonts.css +5 -0
  159. package/dist/styles/settings.scss +155 -0
  160. package/dist/unit-test.js +11 -7
  161. package/package.json +11 -11
  162. package/skills/uimed-vue/SKILL.md +113 -0
  163. package/skills/uimed-vue/references/api/components/autocomplete-field.md +45 -0
  164. package/skills/uimed-vue/references/api/components/button.md +48 -0
  165. package/skills/uimed-vue/references/api/components/checkbox.md +36 -0
  166. package/skills/uimed-vue/references/api/components/data-sets/data-set-item.md +61 -0
  167. package/skills/uimed-vue/references/api/components/data-sets/data-set.md +67 -0
  168. package/skills/uimed-vue/references/api/components/date-time-field.md +44 -0
  169. package/skills/uimed-vue/references/api/components/details.md +33 -0
  170. package/skills/uimed-vue/references/api/components/dialog.md +70 -0
  171. package/skills/uimed-vue/references/api/components/form.md +43 -0
  172. package/skills/uimed-vue/references/api/components/grid/column.md +41 -0
  173. package/skills/uimed-vue/references/api/components/grid/container.md +32 -0
  174. package/skills/uimed-vue/references/api/components/grid/row.md +37 -0
  175. package/skills/uimed-vue/references/api/components/icon-button.md +37 -0
  176. package/skills/uimed-vue/references/api/components/img.md +34 -0
  177. package/skills/uimed-vue/references/api/components/link.md +31 -0
  178. package/skills/uimed-vue/references/api/components/main.md +174 -0
  179. package/skills/uimed-vue/references/api/components/mask-field.md +60 -0
  180. package/skills/uimed-vue/references/api/components/page-transition.md +37 -0
  181. package/skills/uimed-vue/references/api/components/sections/section-content.md +23 -0
  182. package/skills/uimed-vue/references/api/components/sections/section.md +61 -0
  183. package/skills/uimed-vue/references/api/components/table.md +35 -0
  184. package/skills/uimed-vue/references/api/components/text-field.md +42 -0
  185. package/skills/uimed-vue/references/api/composables/use-confirm.md +96 -0
  186. package/skills/uimed-vue/references/api/composables/use-dialog.md +95 -0
  187. package/skills/uimed-vue/references/api/composables/use-run-or-toast.md +58 -0
  188. package/skills/uimed-vue/references/api/composables/use-toast.md +48 -0
  189. package/skills/uimed-vue/references/api/composables/use-unsaved-changes.md +106 -0
  190. package/skills/uimed-vue/references/api/index.md +1 -0
  191. package/skills/uimed-vue/references/guide/components/autocomplete-field.md +256 -0
  192. package/skills/uimed-vue/references/guide/components/button.md +155 -0
  193. package/skills/uimed-vue/references/guide/components/checkbox.md +113 -0
  194. package/skills/uimed-vue/references/guide/components/data-set.md +370 -0
  195. package/skills/uimed-vue/references/guide/components/date-time-field.md +202 -0
  196. package/skills/uimed-vue/references/guide/components/details.md +76 -0
  197. package/skills/uimed-vue/references/guide/components/dialog.md +269 -0
  198. package/skills/uimed-vue/references/guide/components/form.md +96 -0
  199. package/skills/uimed-vue/references/guide/components/icon-button.md +76 -0
  200. package/skills/uimed-vue/references/guide/components/img.md +128 -0
  201. package/skills/uimed-vue/references/guide/components/layout.md +323 -0
  202. package/skills/uimed-vue/references/guide/components/link.md +47 -0
  203. package/skills/uimed-vue/references/guide/components/main.md +435 -0
  204. package/skills/uimed-vue/references/guide/components/mask-field.md +256 -0
  205. package/skills/uimed-vue/references/guide/components/page-transition.md +111 -0
  206. package/skills/uimed-vue/references/guide/components/section.md +232 -0
  207. package/skills/uimed-vue/references/guide/components/table.md +102 -0
  208. package/skills/uimed-vue/references/guide/components/text-field.md +172 -0
  209. package/skills/uimed-vue/references/guide/composables/use-confirm.md +123 -0
  210. package/skills/uimed-vue/references/guide/composables/use-dialog.md +102 -0
  211. package/skills/uimed-vue/references/guide/composables/use-run-or-toast.md +55 -0
  212. package/skills/uimed-vue/references/guide/composables/use-toast.md +57 -0
  213. package/skills/uimed-vue/references/guide/composables/use-unsaved-changes.md +129 -0
  214. package/skills/uimed-vue/references/guide/getting-started.md +135 -0
  215. package/skills/uimed-vue/references/guide/icons.md +71 -0
  216. package/skills/uimed-vue/references/guide/index.md +51 -0
  217. package/skills/uimed-vue/references/guide/migration-v1-to-v2.md +191 -0
  218. package/skills/uimed-vue/references/guide/unit-testing.md +85 -0
  219. package/AGENTS.md +0 -160
  220. package/CLAUDE.md +0 -1
  221. package/dist/types-BPXXtbmW.d.ts +0 -5
  222. 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 };
@@ -1,67 +1,6 @@
1
- import { n as FeedbackColorVariant } from "./types-BPXXtbmW.js";
2
- //#region src/composables/dialogs/types.d.ts
3
- interface ToastOptions {
4
- /**
5
- * Text to be presented in the toast.
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 };
@@ -1,65 +1,6 @@
1
- import { n as useToast } from "./use-toast-CACuYhK_.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, 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 };