@nexdom/uimed-vue 2.0.0-beta.9 → 2.1.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 (220) hide show
  1. package/dist/_virtual/_rolldown/runtime.js +7 -0
  2. package/dist/components/app-bar/app-bar.js +5 -0
  3. package/dist/components/app-bar/app-bar.vue_vue_type_script_setup_true_lang.js +286 -0
  4. package/dist/components/app-bar/types.d.ts +96 -0
  5. package/dist/components/button/button.js +5 -0
  6. package/dist/components/button/button.vue.d.ts +31 -0
  7. package/dist/components/button/button.vue_vue_type_script_setup_true_lang.js +97 -0
  8. package/dist/components/button/types.d.ts +65 -0
  9. package/dist/components/data-sets/data-set/data-set.js +5 -0
  10. package/dist/components/data-sets/data-set/data-set.vue.d.ts +47 -0
  11. package/dist/components/data-sets/data-set/data-set.vue_vue_type_script_setup_true_lang.js +157 -0
  12. package/dist/components/data-sets/data-set/types.d.ts +93 -0
  13. package/dist/components/data-sets/data-set-bar.js +5 -0
  14. package/dist/components/data-sets/data-set-bar.vue_vue_type_script_setup_true_lang.js +99 -0
  15. package/dist/components/data-sets/data-set-item/data-set-item.js +5 -0
  16. package/dist/components/data-sets/data-set-item/data-set-item.vue.d.ts +35 -0
  17. package/dist/components/data-sets/data-set-item/data-set-item.vue_vue_type_script_setup_true_lang.js +57 -0
  18. package/dist/components/data-sets/data-set-item/types.d.ts +42 -0
  19. package/dist/components/details/details.js +5 -0
  20. package/dist/components/details/details.vue.d.ts +28 -0
  21. package/dist/components/details/details.vue_vue_type_script_setup_true_lang.js +56 -0
  22. package/dist/components/details/types.d.ts +29 -0
  23. package/dist/components/dialogs/dialog/dialog-host-key.js +10 -0
  24. package/dist/components/dialogs/dialog/dialog.js +5 -0
  25. package/dist/components/dialogs/dialog/dialog.vue.d.ts +46 -0
  26. package/dist/components/dialogs/dialog/dialog.vue_vue_type_script_setup_true_lang.js +148 -0
  27. package/dist/components/dialogs/dialog/types.d.ts +49 -0
  28. package/dist/components/dialogs/dialog-host.js +5 -0
  29. package/dist/components/dialogs/dialog-host.vue_vue_type_script_setup_true_lang.js +81 -0
  30. package/dist/components/dialogs/toast.js +5 -0
  31. package/dist/components/dialogs/toast.vue_vue_type_script_setup_true_lang.js +35 -0
  32. package/dist/components/footer/footer.js +5 -0
  33. package/dist/components/footer/footer.vue_vue_type_script_setup_true_lang.js +31 -0
  34. package/dist/components/footer/types.d.ts +16 -0
  35. package/dist/components/form/form.js +5 -0
  36. package/dist/components/form/form.vue.d.ts +32 -0
  37. package/dist/components/form/form.vue_vue_type_script_setup_true_lang.js +37 -0
  38. package/dist/components/form/types.d.ts +15 -0
  39. package/dist/components/grid/column/column.js +5 -0
  40. package/dist/components/grid/column/column.vue.d.ts +33 -0
  41. package/dist/components/grid/column/column.vue_vue_type_script_setup_true_lang.js +45 -0
  42. package/dist/components/grid/column/types.d.ts +27 -0
  43. package/dist/components/grid/container/container.js +5 -0
  44. package/dist/components/grid/container/container.vue.d.ts +27 -0
  45. package/dist/components/grid/container/container.vue_vue_type_script_setup_true_lang.js +33 -0
  46. package/dist/components/grid/container/types.d.ts +17 -0
  47. package/dist/components/grid/row/row.js +5 -0
  48. package/dist/components/grid/row/row.vue.d.ts +29 -0
  49. package/dist/components/grid/row/row.vue_vue_type_script_setup_true_lang.js +61 -0
  50. package/dist/components/grid/row/types.d.ts +63 -0
  51. package/dist/components/icon-button/icon-button.js +5 -0
  52. package/dist/components/icon-button/icon-button.vue.d.ts +23 -0
  53. package/dist/components/icon-button/icon-button.vue_vue_type_script_setup_true_lang.js +51 -0
  54. package/dist/components/icon-button/types.d.ts +32 -0
  55. package/dist/components/img/img.js +5 -0
  56. package/dist/components/img/img.vue.d.ts +23 -0
  57. package/dist/components/img/img.vue_vue_type_script_setup_true_lang.js +61 -0
  58. package/dist/components/img/types.d.ts +43 -0
  59. package/dist/components/inputs/autocomplete-field/autocomplete-field.js +5 -0
  60. package/dist/components/inputs/autocomplete-field/autocomplete-field.vue.d.ts +33 -0
  61. package/dist/components/inputs/autocomplete-field/autocomplete-field.vue_vue_type_script_setup_true_lang.js +161 -0
  62. package/dist/components/inputs/autocomplete-field/types.d.ts +84 -0
  63. package/dist/components/inputs/checkbox/checkbox.js +5 -0
  64. package/dist/components/inputs/checkbox/checkbox.vue.d.ts +33 -0
  65. package/dist/components/inputs/checkbox/checkbox.vue_vue_type_script_setup_true_lang.js +71 -0
  66. package/dist/components/inputs/checkbox/types.d.ts +35 -0
  67. package/dist/components/inputs/date-time-field/date-time-field.js +5 -0
  68. package/dist/components/inputs/date-time-field/date-time-field.vue.d.ts +32 -0
  69. package/dist/components/inputs/date-time-field/date-time-field.vue_vue_type_script_setup_true_lang.js +205 -0
  70. package/dist/components/inputs/date-time-field/types.d.ts +84 -0
  71. package/dist/components/inputs/field-loader.js +5 -0
  72. package/dist/components/inputs/field-loader.vue_vue_type_script_setup_true_lang.js +30 -0
  73. package/dist/components/inputs/mask-field/mask-field.js +5 -0
  74. package/dist/components/inputs/mask-field/mask-field.vue.d.ts +32 -0
  75. package/dist/components/inputs/mask-field/mask-field.vue_vue_type_script_setup_true_lang.js +120 -0
  76. package/dist/components/inputs/mask-field/types.d.ts +118 -0
  77. package/dist/components/inputs/text-field/text-field.js +5 -0
  78. package/dist/components/inputs/text-field/text-field.vue.d.ts +30 -0
  79. package/dist/components/inputs/text-field/text-field.vue_vue_type_script_setup_true_lang.js +119 -0
  80. package/dist/components/inputs/text-field/types.d.ts +70 -0
  81. package/dist/components/link/link.js +5 -0
  82. package/dist/components/link/link.vue.d.ts +27 -0
  83. package/dist/components/link/link.vue_vue_type_script_setup_true_lang.js +32 -0
  84. package/dist/components/link/types.d.ts +18 -0
  85. package/dist/components/main/main.js +5 -0
  86. package/dist/components/main/main.vue.d.ts +33 -0
  87. package/dist/components/main/main.vue_vue_type_script_setup_true_lang.js +104 -0
  88. package/dist/components/main/types.d.ts +75 -0
  89. package/dist/components/navigation-menu/navigation-menu.js +5 -0
  90. package/dist/components/navigation-menu/navigation-menu.vue_vue_type_script_setup_true_lang.js +207 -0
  91. package/dist/components/navigation-menu/types.d.ts +62 -0
  92. package/dist/components/page-transition/page-transition.js +5 -0
  93. package/dist/components/page-transition/page-transition.vue.d.ts +40 -0
  94. package/dist/components/page-transition/page-transition.vue_vue_type_script_setup_true_lang.js +27 -0
  95. package/dist/components/page-transition/types.d.ts +18 -0
  96. package/dist/components/sections/section/section.js +5 -0
  97. package/dist/components/sections/section/section.vue.d.ts +28 -0
  98. package/dist/components/sections/section/section.vue_vue_type_script_setup_true_lang.js +108 -0
  99. package/dist/components/sections/section/types.d.ts +80 -0
  100. package/dist/components/sections/section-content/section-content.js +5 -0
  101. package/dist/components/sections/section-content/section-content.vue.d.ts +27 -0
  102. package/dist/components/sections/section-content/section-content.vue_vue_type_script_setup_true_lang.js +16 -0
  103. package/dist/components/skeleton-loader/skeleton-loader.js +5 -0
  104. package/dist/components/skeleton-loader/skeleton-loader.vue_vue_type_script_setup_true_lang.js +65 -0
  105. package/dist/components/table/table.js +5 -0
  106. package/dist/components/table/table.vue.d.ts +19 -0
  107. package/dist/components/table/table.vue_vue_type_script_setup_true_lang.js +84 -0
  108. package/dist/components/table/types.d.ts +51 -0
  109. package/dist/components.d.ts +23 -896
  110. package/dist/components.js +23 -1381
  111. package/dist/composables/alignment/alignment.js +41 -0
  112. package/dist/composables/alignment/types.d.ts +12 -0
  113. package/dist/composables/button/button.js +54 -0
  114. package/dist/composables/colors/constants.js +27 -0
  115. package/dist/composables/colors/types.d.ts +4 -0
  116. package/dist/composables/colors/use-vuetify-background-color.js +16 -0
  117. package/dist/composables/colors/use-vuetify-color.js +11 -0
  118. package/dist/composables/colors/use-vuetify-text-color.js +12 -0
  119. package/dist/composables/data-set/data-set.js +128 -0
  120. package/dist/composables/dialogs/types.d.ts +88 -0
  121. package/dist/composables/dialogs/use-confirm.d.ts +21 -0
  122. package/dist/composables/dialogs/use-confirm.js +55 -0
  123. package/dist/composables/dialogs/use-decorative-timer.js +25 -0
  124. package/dist/composables/dialogs/use-dialog-focus.js +78 -0
  125. package/dist/composables/dialogs/use-dialog-loading.js +13 -0
  126. package/dist/composables/dialogs/use-dialog.d.ts +37 -0
  127. package/dist/{use-dialog-Ck2_ZW7T.js → composables/dialogs/use-dialog.js} +4 -54
  128. package/dist/composables/dialogs/use-run-or-toast.d.ts +27 -0
  129. package/dist/composables/dialogs/use-run-or-toast.js +65 -0
  130. package/dist/composables/dialogs/use-toast.d.ts +25 -0
  131. package/dist/composables/dialogs/use-toast.js +39 -0
  132. package/dist/composables/dimensions/dimensions.js +18 -0
  133. package/dist/composables/inputs/autocomplete-field.js +39 -0
  134. package/dist/composables/inputs/date-time-field.js +109 -0
  135. package/dist/composables/inputs/fields.js +28 -0
  136. package/dist/composables/inputs/mask-field.js +133 -0
  137. package/dist/composables/inputs/rules.js +16 -0
  138. package/dist/composables/inputs/text-field.js +81 -0
  139. package/dist/composables/navigation/types.d.ts +54 -0
  140. package/dist/composables/navigation/use-navigation-rail.js +163 -0
  141. package/dist/composables/navigation/use-navigation-search-shortcut.js +96 -0
  142. package/dist/composables/navigation/use-route-props.js +21 -0
  143. package/dist/composables/navigation/use-unsaved-changes.d.ts +35 -0
  144. package/dist/composables/navigation/use-unsaved-changes.js +88 -0
  145. package/dist/composables/section/section.js +34 -0
  146. package/dist/composables.d.ts +6 -193
  147. package/dist/composables.js +6 -117
  148. package/dist/consts/icons.d.ts +67 -0
  149. package/dist/consts/icons.js +69 -0
  150. package/dist/consts/locale.js +12 -0
  151. package/dist/consts/types.d.ts +12 -0
  152. package/dist/index.d.ts +1 -13
  153. package/dist/index.js +39 -14
  154. package/dist/plugins.d.ts +15 -4
  155. package/dist/plugins.js +54 -3
  156. package/dist/styles/fonts/UnimedSlab-Regular.woff2 +0 -0
  157. package/dist/styles/fonts/fonts.css +5 -0
  158. package/dist/styles/settings.scss +154 -0
  159. package/dist/unit-test.js +11 -7
  160. package/package.json +11 -11
  161. package/skills/uimed-vue/SKILL.md +113 -0
  162. package/skills/uimed-vue/references/api/components/autocomplete-field.md +45 -0
  163. package/skills/uimed-vue/references/api/components/button.md +48 -0
  164. package/skills/uimed-vue/references/api/components/checkbox.md +36 -0
  165. package/skills/uimed-vue/references/api/components/data-sets/data-set-item.md +61 -0
  166. package/skills/uimed-vue/references/api/components/data-sets/data-set.md +67 -0
  167. package/skills/uimed-vue/references/api/components/date-time-field.md +44 -0
  168. package/skills/uimed-vue/references/api/components/details.md +33 -0
  169. package/skills/uimed-vue/references/api/components/dialog.md +70 -0
  170. package/skills/uimed-vue/references/api/components/form.md +43 -0
  171. package/skills/uimed-vue/references/api/components/grid/column.md +41 -0
  172. package/skills/uimed-vue/references/api/components/grid/container.md +32 -0
  173. package/skills/uimed-vue/references/api/components/grid/row.md +37 -0
  174. package/skills/uimed-vue/references/api/components/icon-button.md +37 -0
  175. package/skills/uimed-vue/references/api/components/img.md +34 -0
  176. package/skills/uimed-vue/references/api/components/link.md +31 -0
  177. package/skills/uimed-vue/references/api/components/main.md +174 -0
  178. package/skills/uimed-vue/references/api/components/mask-field.md +60 -0
  179. package/skills/uimed-vue/references/api/components/page-transition.md +37 -0
  180. package/skills/uimed-vue/references/api/components/sections/section-content.md +23 -0
  181. package/skills/uimed-vue/references/api/components/sections/section.md +61 -0
  182. package/skills/uimed-vue/references/api/components/table.md +35 -0
  183. package/skills/uimed-vue/references/api/components/text-field.md +42 -0
  184. package/skills/uimed-vue/references/api/composables/use-confirm.md +96 -0
  185. package/skills/uimed-vue/references/api/composables/use-dialog.md +96 -0
  186. package/skills/uimed-vue/references/api/composables/use-run-or-toast.md +58 -0
  187. package/skills/uimed-vue/references/api/composables/use-toast.md +48 -0
  188. package/skills/uimed-vue/references/api/composables/use-unsaved-changes.md +106 -0
  189. package/skills/uimed-vue/references/api/index.md +1 -0
  190. package/skills/uimed-vue/references/guide/components/autocomplete-field.md +256 -0
  191. package/skills/uimed-vue/references/guide/components/button.md +155 -0
  192. package/skills/uimed-vue/references/guide/components/checkbox.md +113 -0
  193. package/skills/uimed-vue/references/guide/components/data-set.md +370 -0
  194. package/skills/uimed-vue/references/guide/components/date-time-field.md +202 -0
  195. package/skills/uimed-vue/references/guide/components/details.md +76 -0
  196. package/skills/uimed-vue/references/guide/components/dialog.md +269 -0
  197. package/skills/uimed-vue/references/guide/components/form.md +96 -0
  198. package/skills/uimed-vue/references/guide/components/icon-button.md +76 -0
  199. package/skills/uimed-vue/references/guide/components/img.md +128 -0
  200. package/skills/uimed-vue/references/guide/components/layout.md +323 -0
  201. package/skills/uimed-vue/references/guide/components/link.md +47 -0
  202. package/skills/uimed-vue/references/guide/components/main.md +435 -0
  203. package/skills/uimed-vue/references/guide/components/mask-field.md +256 -0
  204. package/skills/uimed-vue/references/guide/components/page-transition.md +111 -0
  205. package/skills/uimed-vue/references/guide/components/section.md +232 -0
  206. package/skills/uimed-vue/references/guide/components/table.md +102 -0
  207. package/skills/uimed-vue/references/guide/components/text-field.md +172 -0
  208. package/skills/uimed-vue/references/guide/composables/use-confirm.md +123 -0
  209. package/skills/uimed-vue/references/guide/composables/use-dialog.md +102 -0
  210. package/skills/uimed-vue/references/guide/composables/use-run-or-toast.md +55 -0
  211. package/skills/uimed-vue/references/guide/composables/use-toast.md +57 -0
  212. package/skills/uimed-vue/references/guide/composables/use-unsaved-changes.md +129 -0
  213. package/skills/uimed-vue/references/guide/getting-started.md +135 -0
  214. package/skills/uimed-vue/references/guide/icons.md +71 -0
  215. package/skills/uimed-vue/references/guide/index.md +51 -0
  216. package/skills/uimed-vue/references/guide/migration-v1-to-v2.md +191 -0
  217. package/skills/uimed-vue/references/guide/unit-testing.md +85 -0
  218. package/AGENTS.md +0 -233
  219. package/CLAUDE.md +0 -1
  220. package/dist/types-HPMP8IrV.d.ts +0 -49
@@ -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,193 +1,6 @@
1
- import { i as FeedbackColorVariant, n as ButtonVariant, r as ColorVariant } from "./types-HPMP8IrV.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 toast.
10
- * One of `positive`, `informative`, `caution`, or `danger`.
11
- * @default "informative"
12
- */
13
- color?: FeedbackColorVariant;
14
- }
15
- /**
16
- * A button displayed at the bottom of a dialog opened by `useDialog`'s `dialog`.
17
- */
18
- interface DialogAction<T extends string = string> {
19
- /**
20
- * Text displayed on the button.
21
- */
22
- text: string;
23
- /**
24
- * Value the dialog resolves with when the button is clicked.
25
- */
26
- value: T;
27
- /**
28
- * Applies a distinct style variation to the button.
29
- * One of `primary`, `secondary`, or `ghost`.
30
- * @default "ghost", or "primary" for the last action
31
- */
32
- variant?: ButtonVariant;
33
- /**
34
- * Applies a color to the button.
35
- * One of `primary`, `secondary`, `positive`, `informative`, `caution`, or `danger`.
36
- * @default "primary"
37
- */
38
- color?: ColorVariant;
39
- }
40
- /**
41
- * Options of a dialog opened by `useDialog`'s `dialog`.
42
- */
43
- interface DialogOptions<T extends string = string> {
44
- /**
45
- * Plain text displayed as the dialog's title. HTML isn't interpreted.
46
- */
47
- title?: string;
48
- /**
49
- * Plain text displayed as the dialog's message. HTML isn't interpreted.
50
- */
51
- message: string;
52
- /**
53
- * Buttons displayed at the bottom of the dialog, right-aligned, in the given order.
54
- * @default [{ text: "Fechar" }], which resolves `undefined`
55
- */
56
- actions?: DialogAction<T>[];
57
- }
58
- /**
59
- * Options of a dialog opened by `useConfirm`'s `confirm`.
60
- */
61
- interface ConfirmOptions {
62
- /**
63
- * Plain text displayed as the dialog's title. HTML isn't interpreted.
64
- */
65
- title?: string;
66
- /**
67
- * Plain text displayed as the dialog's message. HTML isn't interpreted.
68
- */
69
- message: string;
70
- /**
71
- * Text displayed on the confirm button.
72
- * @default "Confirmar"
73
- */
74
- confirmText?: string;
75
- /**
76
- * Text displayed on the cancel button.
77
- * @default "Cancelar"
78
- */
79
- cancelText?: string;
80
- /**
81
- * Applies a color to the confirm button.
82
- * One of `primary`, `secondary`, `positive`, `informative`, `caution`, or `danger`.
83
- * @default "primary"
84
- */
85
- color?: ColorVariant;
86
- }
87
- //#endregion
88
- //#region src/composables/dialogs/use-toast.d.ts
89
- /**
90
- * Composable which returns a function to display toast messages.
91
- *
92
- * @example
93
- * ```ts
94
- * const { toast } = useToast();
95
- * toast({ message: "Hello, World!" });
96
- * ```
97
- */
98
- declare function useToast(): {
99
- toast: typeof toast;
100
- };
101
- /**
102
- * Displays a toast message.
103
- * @param {ToastOptions} options - Options for the toast message.
104
- *
105
- * @example
106
- * ```ts
107
- * toast({ message: "Hello, World!" });
108
- * ```
109
- */
110
- declare function toast(options: ToastOptions): void;
111
- //#endregion
112
- //#region src/composables/dialogs/use-run-or-toast.d.ts
113
- /**
114
- * Generate `run` method and a computed property `isRunning`.
115
- * Calling `run` will reactively update `isRunning`.
116
- *
117
- * @example
118
- * ```ts
119
- * const { run, isRunning } = useRunOrToast();
120
- *
121
- * // Logs: false
122
- * console.log(isRunning.value);
123
- *
124
- * const promise = run(() => someFunction(param1, param2));
125
- *
126
- * // Logs: true
127
- * console.log(isRunning.value);
128
- *
129
- * await promise;
130
- * // Logs: false
131
- * console.log(isRunning.value);
132
- * ```
133
- */
134
- declare function useRunOrToast(): {
135
- run: <T extends (...args: Parameters<T>) => PromiseLike<Awaited<ReturnType<T>>>>(toRun: T, ...params: Parameters<T>) => Promise<Awaited<ReturnType<T>> | false>;
136
- isRunning: import("vue").ComputedRef<boolean>;
137
- };
138
- //#endregion
139
- //#region src/composables/dialogs/use-dialog.d.ts
140
- /**
141
- * Composable which returns a function to display dialogs.
142
- *
143
- * @example
144
- * ```ts
145
- * const { dialog } = useDialog();
146
- *
147
- * const answer = await dialog({
148
- * title: "Sessão expirando",
149
- * message: "Deseja continuar conectado?",
150
- * actions: [
151
- * { text: "Sair", value: "leave" },
152
- * { text: "Continuar", value: "stay" },
153
- * ],
154
- * });
155
- * ```
156
- */
157
- declare function useDialog(): {
158
- dialog: typeof dialog;
159
- };
160
- /**
161
- * Displays a dialog. Dialogs are displayed one at a time, in the order they were requested.
162
- * @param {DialogOptions} options - Options for the dialog.
163
- * @returns the `value` of the clicked action, or `undefined` when the dialog is closed by the
164
- * default action, the `Esc` key, a click outside it or the browser's back button.
165
- *
166
- * @example
167
- * ```ts
168
- * await dialog({ title: "Aviso", message: "Seu cadastro foi enviado para análise." });
169
- * ```
170
- */
171
- declare function dialog<T extends string = never>(options: DialogOptions<T>): Promise<T | undefined>;
172
- //#endregion
173
- //#region src/composables/dialogs/use-confirm.d.ts
174
- /**
175
- * Composable which returns a function to run actions only after the user confirms them in a
176
- * dialog, and a computed property `isRunning`.
177
- *
178
- * @example
179
- * ```ts
180
- * const { confirm, isRunning } = useConfirm();
181
- *
182
- * const removed = await confirm(
183
- * { title: "Excluir paciente", message: "Esta ação não pode ser desfeita.", confirmText: "Excluir", color: "danger" },
184
- * () => api.deletePatient(id),
185
- * );
186
- * ```
187
- */
188
- declare function useConfirm(): {
189
- confirm: <F extends (...args: Parameters<F>) => PromiseLike<Awaited<ReturnType<F>>>>(options: ConfirmOptions, action: F, ...params: Parameters<F>) => Promise<Awaited<ReturnType<F>> | false>;
190
- isRunning: import("vue").ComputedRef<boolean>;
191
- };
192
- //#endregion
193
- export { useConfirm, useDialog, 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 };