@nexdom/uimed-vue 2.0.0-beta.61 → 2.0.0-beta.62

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/AGENTS.md CHANGED
@@ -216,7 +216,7 @@ Use an existing composable (e.g. `use-toast`) as the reference, and deliver in t
216
216
  - Public components/composables are re-exported from `src/components/index.ts` and `src/composables/index.ts`. Prop types stay internal (`types.ts` is not re-exported); consumers derive them with `ComponentProps` (see [Usage](#usage)).
217
217
  - Every publicly exported component must use the `U` prefix on its export identifier (e.g., `UButton`, `UMain`), while internal file names and component names remain unprefixed.
218
218
  - Use [JSDoc](https://jsdoc.app/about-getting-started) on every method/prop/type intended to be part of the public API — it's the primary documentation surface and supports markdown/code examples.
219
- - Known workarounds (see CONTRIBUTING.md before touching related config): `stryker-vue-ignorer` patches a Stryker/Vue macro-hoisting issue; `vue-tsc` is used for type-check instead of Vite+'s built-in one due to an oxlint/Vue support gap; the internal `SkeletonLoader` wraps Vuetify's skeleton loader so its loading announcement gets valid ARIA attributes in server rendering. All are meant to be removed once fixed upstream — don't build further on top of them without checking if they're still needed.
219
+ - Known workarounds (see CONTRIBUTING.md before touching related config): `stryker-vue-ignorer` patches a Stryker/Vue macro-hoisting issue; `vue-tsc` is used for type-check instead of Vite+'s built-in one due to an oxlint/Vue support gap; the internal `SkeletonLoader` wraps Vuetify's skeleton loader so its loading announcement gets valid ARIA attributes in server rendering; the navigation menu's rail (`useNavigationRail`) dispatches synthetic `mouseenter`/`mouseleave` events on Vuetify's navigation drawer, which only expands its rail on mouse events, to expand it on keyboard focus and keep it expanded when the mouse leaves with the focus inside. All are meant to be removed once fixed upstream — don't build further on top of them without checking if they're still needed.
220
220
 
221
221
  ## Testing conventions
222
222
 
@@ -23,8 +23,8 @@ declare const __VLS_export: import("vue", { with: { "resolution-mode": "import"
23
23
  "onUpdate:modelValue"?: ((value: string) => any) | undefined;
24
24
  }>, {
25
25
  variant: TextFieldVariant;
26
- type: TextFieldType;
27
26
  clearable: boolean;
27
+ type: TextFieldType;
28
28
  }, {}, {}, {}, string, import("vue", { with: { "resolution-mode": "import" } }).ComponentProvideOptions, false, {}, any>;
29
29
  //#endregion
30
30
  export { _default as default };
@@ -1,4 +1,5 @@
1
1
  import app_bar_default from "../app-bar/app-bar.js";
2
+ import { useMobileNavigation } from "../../composables/navigation/use-navigation-rail.js";
2
3
  import navigation_menu_default from "../navigation-menu/navigation-menu.js";
3
4
  import footer_default from "../footer/footer.js";
4
5
  import container_default from "../grid/container/container.js";
@@ -50,10 +51,11 @@ var main_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/ defineCompo
50
51
  setup(__props) {
51
52
  const props = __props;
52
53
  const slots = useSlots();
54
+ const mobile = useMobileNavigation();
53
55
  const appBarProps = computed(() => props.appBar ? {
54
56
  ...props.appBar,
55
57
  logo: props.logo,
56
- navigation: props.navigationMenu !== void 0,
58
+ navigation: props.navigationMenu !== void 0 && mobile.value,
57
59
  loading: props.loading
58
60
  } : void 0);
59
61
  const backgroundClass = useVuetifyBackgroundColor(() => props.background);
@@ -3,7 +3,8 @@ import { routeToListItemProps } from "../../composables/navigation/use-route-pro
3
3
  import { useNavigationSearchShortcut } from "../../composables/navigation/use-navigation-search-shortcut.js";
4
4
  import skeleton_loader_default from "../skeleton-loader/skeleton-loader.js";
5
5
  import text_field_default from "../inputs/text-field/text-field.js";
6
- import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, mergeModels, mergeProps, openBlock, ref, renderList, unref, useModel, useTemplateRef, withCtx, withKeys } from "vue";
6
+ import { isParentItem, useNavigationRail } from "../../composables/navigation/use-navigation-rail.js";
7
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, isRef, mergeModels, mergeProps, openBlock, ref, renderList, unref, useModel, useTemplateRef, vShow, withCtx, withDirectives, withKeys } from "vue";
7
8
  import { VDivider, VList, VListGroup, VListItem, VNavigationDrawer } from "vuetify/components";
8
9
  //#region src/components/navigation-menu/navigation-menu.vue?vue&type=script&setup=true&lang.ts
9
10
  const _hoisted_1 = { class: "px-4 pt-2" };
@@ -35,11 +36,22 @@ var navigation_menu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
35
36
  const props = __props;
36
37
  const open = useModel(__props, "modelValue");
37
38
  const search = ref("");
38
- const { label: shortcutLabel, close } = useNavigationSearchShortcut({
39
+ const field = useTemplateRef("searchField");
40
+ const { mobile, collapsed, drawerOpen, menuOpen, opened, isActiveGroup, dismiss, isRestoringFocus, onFocusin, onFocusout, onMouseenter, onMouseleave } = useNavigationRail({
39
41
  open,
40
- field: useTemplateRef("searchField")
42
+ field,
43
+ searchItem: useTemplateRef("searchItem"),
44
+ items: () => props.items,
45
+ search
46
+ });
47
+ const { label: shortcutLabel, ariaKeyshortcuts, openSearch } = useNavigationSearchShortcut({
48
+ open: menuOpen,
49
+ field
41
50
  });
42
51
  const filteredItems = computed(() => filterItems());
52
+ function onSearchItemFocus(event) {
53
+ if (!isRestoringFocus()) openSearch(event.relatedTarget);
54
+ }
43
55
  const searchDataTestid = computed(() => `${props.dataTestid}-search`);
44
56
  const query = computed(() => search.value.toLowerCase());
45
57
  function filterItems() {
@@ -58,49 +70,91 @@ var navigation_menu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
58
70
  function matches(item) {
59
71
  return item.description.toLowerCase().includes(query.value);
60
72
  }
61
- function isParentItem(item) {
62
- return "items" in item;
63
- }
64
73
  return (_ctx, _cache) => {
65
74
  return openBlock(), createBlock(unref(VNavigationDrawer), {
66
- modelValue: open.value,
67
- "onUpdate:modelValue": _cache[1] || (_cache[1] = ($event) => open.value = $event),
75
+ modelValue: unref(drawerOpen),
76
+ "onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => isRef(drawerOpen) ? drawerOpen.value = $event : null),
68
77
  color: "primary",
69
78
  "data-testid": props.dataTestid,
70
79
  "aria-busy": props.loading || void 0,
80
+ mobile: unref(mobile),
81
+ permanent: !unref(mobile),
82
+ rail: !unref(mobile),
83
+ "expand-on-hover": "",
71
84
  absolute: "",
72
85
  temporary: "",
73
- onKeydown: withKeys(unref(close), ["esc"])
86
+ onFocusin: unref(onFocusin),
87
+ onFocusout: unref(onFocusout),
88
+ onMouseenter: unref(onMouseenter),
89
+ onMouseleave: unref(onMouseleave),
90
+ onKeydown: withKeys(unref(dismiss), ["esc"])
74
91
  }, {
75
- prepend: withCtx(() => [createElementVNode("div", _hoisted_1, [createVNode(text_field_default, {
76
- ref: "searchField",
77
- modelValue: search.value,
78
- "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => search.value = $event),
79
- type: "search",
80
- placeholder: `Buscar (${unref(shortcutLabel)})`,
81
- "data-testid": searchDataTestid.value,
82
- disabled: props.loading
83
- }, null, 8, [
84
- "modelValue",
85
- "placeholder",
86
- "data-testid",
87
- "disabled"
88
- ])])]),
92
+ prepend: withCtx(() => [
93
+ withDirectives(createElementVNode("div", _hoisted_1, [createVNode(text_field_default, {
94
+ ref: "searchField",
95
+ modelValue: search.value,
96
+ "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => search.value = $event),
97
+ type: "search",
98
+ placeholder: `Buscar (${unref(shortcutLabel)})`,
99
+ "data-testid": searchDataTestid.value,
100
+ disabled: props.loading
101
+ }, null, 8, [
102
+ "modelValue",
103
+ "placeholder",
104
+ "data-testid",
105
+ "disabled"
106
+ ])], 512), [[vShow, !unref(collapsed)]]),
107
+ createCommentVNode(" Vuetify's list is focusable and passes the focus on to its first item: out of the tab\n order, the item gets the focus straight from the page, which then gets it back on `Esc` "),
108
+ withDirectives(createVNode(unref(VList), {
109
+ nav: "",
110
+ role: "group",
111
+ tabindex: "-1"
112
+ }, {
113
+ default: withCtx(() => [createCommentVNode(" Rendered as a native button, as the groups' headers (see below). Vuetify only styles\n a disabled item, and its button would still take the focus "), createVNode(unref(VListItem), {
114
+ ref: "searchItem",
115
+ title: "Buscar",
116
+ "prepend-icon": unref(icons).search,
117
+ disabled: props.loading,
118
+ tabindex: props.loading ? -1 : void 0,
119
+ "aria-disabled": props.loading || void 0,
120
+ "aria-keyshortcuts": unref(ariaKeyshortcuts),
121
+ role: void 0,
122
+ tag: "button",
123
+ type: "button",
124
+ class: "w-100 text-start",
125
+ onFocus: onSearchItemFocus
126
+ }, null, 8, [
127
+ "prepend-icon",
128
+ "disabled",
129
+ "tabindex",
130
+ "aria-disabled",
131
+ "aria-keyshortcuts"
132
+ ])]),
133
+ _: 1
134
+ }, 512), [[vShow, unref(collapsed)]])
135
+ ]),
89
136
  default: withCtx(() => [createVNode(unref(VDivider)), createVNode(skeleton_loader_default, {
90
137
  loading: props.loading,
91
138
  type: "list-item@6",
92
139
  color: "primary"
93
140
  }, {
94
141
  default: withCtx(() => [createCommentVNode(" Vuetify's `list` role only allows list items, but the items are links "), createVNode(unref(VList), {
142
+ opened: unref(opened),
143
+ "onUpdate:opened": _cache[1] || (_cache[1] = ($event) => isRef(opened) ? opened.value = $event : null),
95
144
  nav: "",
96
145
  role: "group"
97
146
  }, {
98
147
  default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(filteredItems.value, (item, index) => {
99
- return openBlock(), createElementBlock(Fragment, { key: index }, [isParentItem(item) ? (openBlock(), createBlock(unref(VListGroup), { key: 0 }, {
148
+ return openBlock(), createElementBlock(Fragment, { key: index }, [unref(isParentItem)(item) ? (openBlock(), createBlock(unref(VListGroup), {
149
+ key: 0,
150
+ value: item.description
151
+ }, {
100
152
  activator: withCtx(({ props: activatorProps, isOpen }) => [createVNode(unref(VListItem), mergeProps({ ref_for: true }, activatorProps, {
101
153
  title: item.description,
102
- "prepend-icon": item.icon && unref(icons)[item.icon],
154
+ "prepend-icon": unref(icons)[item.icon],
155
+ active: unref(isActiveGroup)(item),
103
156
  "aria-expanded": isOpen,
157
+ "aria-selected": void 0,
104
158
  role: void 0,
105
159
  tag: "button",
106
160
  type: "button",
@@ -108,6 +162,7 @@ var navigation_menu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
108
162
  }), null, 16, [
109
163
  "title",
110
164
  "prepend-icon",
165
+ "active",
111
166
  "aria-expanded"
112
167
  ])]),
113
168
  default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(item.items, (childItem, childIndex) => {
@@ -117,10 +172,10 @@ var navigation_menu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
117
172
  }), null, 16, ["title", "onClick"]);
118
173
  }), 128))]),
119
174
  _: 2
120
- }, 1024)) : (openBlock(), createBlock(unref(VListItem), mergeProps({
175
+ }, 1032, ["value"])) : (openBlock(), createBlock(unref(VListItem), mergeProps({
121
176
  key: 1,
122
177
  title: item.description,
123
- "prepend-icon": item.icon && unref(icons)[item.icon]
178
+ "prepend-icon": unref(icons)[item.icon]
124
179
  }, { ref_for: true }, unref(routeToListItemProps)(item.route), { onClick: item.action }), null, 16, [
125
180
  "title",
126
181
  "prepend-icon",
@@ -128,7 +183,7 @@ var navigation_menu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
128
183
  ]))], 64);
129
184
  }), 128))]),
130
185
  _: 1
131
- })]),
186
+ }, 8, ["opened"])]),
132
187
  _: 1
133
188
  }, 8, ["loading"])]),
134
189
  _: 1
@@ -136,6 +191,13 @@ var navigation_menu_vue_vue_type_script_setup_true_lang_default = /*@__PURE__*/
136
191
  "modelValue",
137
192
  "data-testid",
138
193
  "aria-busy",
194
+ "mobile",
195
+ "permanent",
196
+ "rail",
197
+ "onFocusin",
198
+ "onFocusout",
199
+ "onMouseenter",
200
+ "onMouseleave",
139
201
  "onKeydown"
140
202
  ]);
141
203
  };
@@ -22,12 +22,12 @@ export type NavigationMenuParentItem = {
22
22
  */
23
23
  description: string;
24
24
  /**
25
- * Icon shown before the group's description. One of the names listed on the "Ícones" guide
26
- * page.
25
+ * Icon shown before the group's description, and alone while the menu is collapsed to a rail.
26
+ * One of the names listed on the "Ícones" guide page.
27
27
  *
28
28
  * @see {@link https://nexdom-healthtech.github.io/uimed-vue/guide/icons | Icons Guide}
29
29
  */
30
- icon?: Icon;
30
+ icon: Icon;
31
31
  /**
32
32
  * Child {@link NavigationMenuChildItem} list. Unlike the first level items, they show no icon.
33
33
  */
@@ -44,12 +44,12 @@ export interface NavigationMenuItem {
44
44
  */
45
45
  description: string;
46
46
  /**
47
- * Icon shown before the item's description. One of the names listed on the "Ícones" guide
48
- * page.
47
+ * Icon shown before the item's description, and alone while the menu is collapsed to a rail.
48
+ * One of the names listed on the "Ícones" guide page.
49
49
  *
50
50
  * @see {@link https://nexdom-healthtech.github.io/uimed-vue/guide/icons | Icons Guide}
51
51
  */
52
- icon?: Icon;
52
+ icon: Icon;
53
53
  /**
54
54
  * Route to navigate when item is clicked.
55
55
  */
@@ -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 };
@@ -34,9 +34,10 @@ function useNavigationShortcutKeys() {
34
34
  }
35
35
  /**
36
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 focuses the search field (a disabled field, as
38
- * while loading, can't take the focus). When the menu opened by the shortcut closes with the focus
39
- * inside it, the focus goes back to where it was.
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.
40
41
  */
41
42
  function useNavigationSearchShortcut(options) {
42
43
  const { open, field } = options;
@@ -61,12 +62,13 @@ function useNavigationSearchShortcut(options) {
61
62
  });
62
63
  function onKeydown(event) {
63
64
  if (owners.value[0] !== owner || isIgnoredTarget(event)) return;
64
- if (isShortcut(event)) openAndFocus(event);
65
- else if (event.key === "Escape" && origin) close();
65
+ if (isShortcut(event)) {
66
+ event.preventDefault();
67
+ openSearch(document.activeElement);
68
+ } else if (event.key === "Escape" && origin) open.value = false;
66
69
  }
67
- async function openAndFocus(event) {
68
- event.preventDefault();
69
- if (!open.value) origin = document.activeElement;
70
+ async function openSearch(from) {
71
+ if (!open.value) origin = from;
70
72
  open.value = true;
71
73
  await nextTick();
72
74
  const input = findInput();
@@ -76,12 +78,9 @@ function useNavigationSearchShortcut(options) {
76
78
  function findInput() {
77
79
  return toValue(field).$el.querySelector("input");
78
80
  }
79
- function close() {
80
- open.value = false;
81
- }
82
81
  return {
83
82
  ...useNavigationShortcutKeys(),
84
- close
83
+ openSearch
85
84
  };
86
85
  }
87
86
  function isMacOS() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexdom/uimed-vue",
3
- "version": "2.0.0-beta.61",
3
+ "version": "2.0.0-beta.62",
4
4
  "description": "A ui framework for NEXDOM applications.",
5
5
  "keywords": [
6
6
  "lib",