@nexdom/uimed-vue 2.0.0-beta.25 → 2.0.0-beta.27
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 +1 -1
- package/dist/components.d.ts +2 -2
- package/dist/components.js +124 -10
- package/dist/composables.d.ts +1 -1
- package/dist/{types-HPMP8IrV.d.ts → types-Zex9kf7C.d.ts} +2 -0
- package/package.json +1 -1
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
|
- Keep temporary files (scripts, specs, configs) outside the repo, e.g. in the system's temp folder. Vitest collects test files from every folder except the excluded ones in `vite.config.ts`, so a temporary spec in a gitignored folder such as `reports/` still runs, and fails, with the unit tests.
|
|
217
217
|
- Never run Stryker at the same time as another test command: they share build output and Stryker fails with `ENOENT`.
|
|
218
218
|
- Unit tests that mount several apps at once (e.g. nested dialogs) need a distinct `global.config.idPrefix` per mount, since `useId()` restarts in each app.
|
|
219
|
-
- In E2E, a paused `page.clock` also freezes transitions, so overlays never finish leaving. Use `page.
|
|
219
|
+
- In E2E, a paused `page.clock` also freezes transitions, so overlays never finish leaving. Use `pauseClock(page)` from `e2e/utils.ts` to hold time-based work (e.g. a demo action with `setTimeout`), `page.clock.fastForward` to complete it, then `page.clock.resume()` before expecting the overlay to be hidden (see `e2e/composables/use-confirm.spec.ts`). Don't call `page.clock.pauseAt` with the test runner's time (`new Date()`, `Date.now()`): the page clock can be ahead of it, and the pause then fails with "Cannot fast-forward to the past". `pauseClock` pauses from the page's own time instead.
|
|
220
220
|
|
|
221
221
|
## Public API design
|
|
222
222
|
|
package/dist/components.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as ButtonProps } from "./types-
|
|
1
|
+
import { t as ButtonProps } from "./types-Zex9kf7C.js";
|
|
2
2
|
import { RouteLocationRaw } from "vue-router";
|
|
3
3
|
//#region src/components/app-bar/types.d.ts
|
|
4
4
|
/**
|
|
@@ -433,8 +433,8 @@ declare const __VLS_export$14: import("vue", { with: { "resolution-mode": "impor
|
|
|
433
433
|
"onUpdate:modelValue"?: ((value: string) => any) | undefined;
|
|
434
434
|
}>, {
|
|
435
435
|
variant: TextFieldVariant;
|
|
436
|
-
clearable: boolean;
|
|
437
436
|
type: TextFieldType;
|
|
437
|
+
clearable: boolean;
|
|
438
438
|
}, {}, {}, {}, string, import("vue", { with: { "resolution-mode": "import" } }).ComponentProvideOptions, false, {}, any>;
|
|
439
439
|
//#endregion
|
|
440
440
|
//#region src/components/inputs/checkbox/types.d.ts
|
package/dist/components.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { a as messages, n as owners, r as requests, s as useVuetifyColor } from "./use-dialog-Ck2_ZW7T.js";
|
|
2
|
-
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, createTextVNode, createVNode, defineComponent, guardReactiveProps, inject, isRef, mergeModels, mergeProps, normalizeClass, normalizeProps, onMounted, onScopeDispose, onUnmounted, openBlock, provide, ref, renderList, renderSlot, resolveDynamicComponent, shallowRef, toDisplayString, toValue, unref, useId, useModel, useSlots, useTemplateRef, watch, withCtx, withModifiers } from "vue";
|
|
1
|
+
import { a as messages, n as owners$1, r as requests, s as useVuetifyColor } from "./use-dialog-Ck2_ZW7T.js";
|
|
2
|
+
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, createTextVNode, createVNode, defineComponent, guardReactiveProps, inject, isRef, mergeModels, mergeProps, nextTick, normalizeClass, normalizeProps, onMounted, onScopeDispose, onUnmounted, openBlock, provide, ref, renderList, renderSlot, resolveDynamicComponent, shallowRef, toDisplayString, toValue, unref, useId, useModel, useSlots, useTemplateRef, watch, withCtx, withKeys, withModifiers } from "vue";
|
|
3
3
|
import { pt } from "vuetify/locale";
|
|
4
|
-
import { VApp, VAppBar, VAppBarNavIcon, VAppBarTitle, VAutocomplete, VAvatar, VBadge, VBtn, VCard, VCardActions, VCardText, VCheckboxBtn, VCol, VCombobox, VContainer, VDataIterator, VDatePicker, VDialog, VDivider, VEmptyState, VExpansionPanel, VExpansionPanelText, VExpansionPanelTitle, VExpansionPanels, VForm, VIcon, VIconBtn, VImg, VList, VListGroup, VListItem, VListItemAction, VListSubheader, VLocaleProvider, VMain, VMenu, VNavigationDrawer, VPagination, VProgressLinear, VRow, VSkeletonLoader, VSnackbarQueue, VTable, VTextField, VTimePicker } from "vuetify/components";
|
|
4
|
+
import { VApp, VAppBar, VAppBarNavIcon, VAppBarTitle, VAutocomplete, VAvatar, VBadge, VBtn, VCard, VCardActions, VCardText, VCheckboxBtn, VCol, VCombobox, VContainer, VDataIterator, VDatePicker, VDialog, VDivider, VEmptyState, VExpansionPanel, VExpansionPanelText, VExpansionPanelTitle, VExpansionPanels, VForm, VIcon, VIconBtn, VImg, VList, VListGroup, VListItem, VListItemAction, VListSubheader, VLocaleProvider, VMain, VMenu, VNavigationDrawer, VPagination, VProgressCircular, VProgressLinear, VRow, VSkeletonLoader, VSnackbarQueue, VTable, VTextField, VTimePicker } from "vuetify/components";
|
|
5
5
|
import { formatDateTime, isEmail, isEmpty, isPhone, isUrl } from "@nexdom/shared/utils";
|
|
6
6
|
//#region src/composables/navigation/use-route-props.ts
|
|
7
7
|
function useRouteProps(route) {
|
|
@@ -15,6 +15,101 @@ function isExternalRoute(route) {
|
|
|
15
15
|
return typeof route === "string" && route.startsWith("http");
|
|
16
16
|
}
|
|
17
17
|
//#endregion
|
|
18
|
+
//#region src/composables/navigation/use-navigation-search-shortcut.ts
|
|
19
|
+
/**
|
|
20
|
+
* Mounted navigation menus, in mounting order. Only the first one answers the shortcut, so pages
|
|
21
|
+
* with more than one `UMain` (like the docs) don't open every menu at once.
|
|
22
|
+
*/
|
|
23
|
+
const owners = ref([]);
|
|
24
|
+
/**
|
|
25
|
+
* Focus targets where the shortcut is left to the page: overlays (dialogs, confirmations and
|
|
26
|
+
* menus) and other modals, the activators of open menus (which keep the focus when opened by a
|
|
27
|
+
* click, and would pull it back from the navigation menu) and rich text editors, which usually
|
|
28
|
+
* take Ctrl+K to insert links.
|
|
29
|
+
*/
|
|
30
|
+
const IGNORED_TARGETS = [
|
|
31
|
+
".v-overlay",
|
|
32
|
+
"dialog",
|
|
33
|
+
"[aria-modal='true']",
|
|
34
|
+
"[aria-haspopup][aria-expanded='true']",
|
|
35
|
+
"[contenteditable]:not([contenteditable='false'])"
|
|
36
|
+
].join(", ");
|
|
37
|
+
/** Accessible name of the search field, whose placeholder also shows the shortcut. */
|
|
38
|
+
const SEARCH_NAME = "Buscar";
|
|
39
|
+
/**
|
|
40
|
+
* Shortcut keys for the current platform. Starts with the non-macOS keys and switches on mount,
|
|
41
|
+
* so server-rendered pages (the docs) hydrate without mismatches.
|
|
42
|
+
*/
|
|
43
|
+
function useNavigationShortcutKeys() {
|
|
44
|
+
const isMac = ref(false);
|
|
45
|
+
onMounted(() => isMac.value = isMacOS());
|
|
46
|
+
return {
|
|
47
|
+
label: computed(() => isMac.value ? "⌘K" : "Ctrl+K"),
|
|
48
|
+
ariaKeyshortcuts: computed(() => isMac.value ? "Meta+K" : "Control+K")
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Listens to Ctrl+K (⌘K on macOS) on `window` while mounted: opens the menu, waits for it to
|
|
53
|
+
* render (the drawer is `inert` while closed) and focuses the search field (a disabled field, as
|
|
54
|
+
* while loading, can't take the focus). When the menu opened by the shortcut closes with the focus
|
|
55
|
+
* inside it, the focus goes back to where it was.
|
|
56
|
+
*/
|
|
57
|
+
function useNavigationSearchShortcut(options) {
|
|
58
|
+
const { open, field } = options;
|
|
59
|
+
const owner = Symbol("navigation-search-shortcut");
|
|
60
|
+
/** Element focused before the shortcut opened the menu, which gets the focus back on close. */
|
|
61
|
+
let origin = null;
|
|
62
|
+
onMounted(() => {
|
|
63
|
+
owners.value.push(owner);
|
|
64
|
+
globalThis.addEventListener("keydown", onKeydown, { capture: true });
|
|
65
|
+
findInput().setAttribute("aria-label", SEARCH_NAME);
|
|
66
|
+
});
|
|
67
|
+
onUnmounted(() => {
|
|
68
|
+
owners.value = owners.value.filter((item) => item !== owner);
|
|
69
|
+
globalThis.removeEventListener("keydown", onKeydown, { capture: true });
|
|
70
|
+
});
|
|
71
|
+
watch(open, (isOpen) => {
|
|
72
|
+
if (isOpen) return;
|
|
73
|
+
const target = origin;
|
|
74
|
+
origin = null;
|
|
75
|
+
if (!findInput().closest(".v-navigation-drawer").contains(document.activeElement)) return;
|
|
76
|
+
if (target instanceof HTMLElement || target instanceof SVGElement) target.focus();
|
|
77
|
+
});
|
|
78
|
+
function onKeydown(event) {
|
|
79
|
+
if (owners.value[0] !== owner || isIgnoredTarget(event)) return;
|
|
80
|
+
if (isShortcut(event)) openAndFocus(event);
|
|
81
|
+
else if (event.key === "Escape" && origin) close();
|
|
82
|
+
}
|
|
83
|
+
async function openAndFocus(event) {
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
if (!open.value) origin = document.activeElement;
|
|
86
|
+
open.value = true;
|
|
87
|
+
await nextTick();
|
|
88
|
+
const input = findInput();
|
|
89
|
+
input.focus({ preventScroll: true });
|
|
90
|
+
input.select();
|
|
91
|
+
}
|
|
92
|
+
function findInput() {
|
|
93
|
+
return field.value.$el.querySelector("input");
|
|
94
|
+
}
|
|
95
|
+
function close() {
|
|
96
|
+
open.value = false;
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
...useNavigationShortcutKeys(),
|
|
100
|
+
close
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function isMacOS() {
|
|
104
|
+
return navigator.userAgent.includes("Macintosh");
|
|
105
|
+
}
|
|
106
|
+
function isShortcut(event) {
|
|
107
|
+
return (isMacOS() ? event.metaKey : event.ctrlKey) && !event.shiftKey && !event.altKey && !event.isComposing && event.key.toLowerCase() === "k";
|
|
108
|
+
}
|
|
109
|
+
function isIgnoredTarget(event) {
|
|
110
|
+
return event.target instanceof Element && event.target.closest(IGNORED_TARGETS) !== null;
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
18
113
|
//#region src/components/app-bar/app-bar.vue?vue&type=script&setup=true&lang.ts
|
|
19
114
|
const _hoisted_1$3 = { class: "opacity-60" };
|
|
20
115
|
//#endregion
|
|
@@ -69,6 +164,7 @@ var app_bar_default = /* @__PURE__ */ defineComponent({
|
|
|
69
164
|
const props = __props;
|
|
70
165
|
const notificationsOpen = useModel(__props, "notificationsOpen");
|
|
71
166
|
const navigationOpen = useModel(__props, "navigationOpen");
|
|
167
|
+
const { ariaKeyshortcuts: navigationKeyshortcuts } = useNavigationShortcutKeys();
|
|
72
168
|
const helpBtnDataTestid = computed(() => `${props.dataTestid}-help`);
|
|
73
169
|
const notificationsBtnDataTestid = computed(() => `${props.dataTestid}-notifications`);
|
|
74
170
|
const navigationBtnDataTestid = computed(() => `${props.dataTestid}-navigation`);
|
|
@@ -221,8 +317,13 @@ var app_bar_default = /* @__PURE__ */ defineComponent({
|
|
|
221
317
|
"data-testid": navigationBtnDataTestid.value,
|
|
222
318
|
"aria-expanded": navigationOpen.value,
|
|
223
319
|
"aria-label": "Menu de navegação",
|
|
320
|
+
"aria-keyshortcuts": unref(navigationKeyshortcuts),
|
|
224
321
|
onClick: _cache[0] || (_cache[0] = ($event) => navigationOpen.value = !navigationOpen.value)
|
|
225
|
-
}, null, 8, [
|
|
322
|
+
}, null, 8, [
|
|
323
|
+
"data-testid",
|
|
324
|
+
"aria-expanded",
|
|
325
|
+
"aria-keyshortcuts"
|
|
326
|
+
])) : createCommentVNode("v-if", true),
|
|
226
327
|
__props.logo ? (openBlock(), createBlock(unref(VImg), {
|
|
227
328
|
key: 1,
|
|
228
329
|
src: props.logo,
|
|
@@ -488,6 +589,10 @@ var navigation_menu_default = /* @__PURE__ */ defineComponent({
|
|
|
488
589
|
const props = __props;
|
|
489
590
|
const open = useModel(__props, "modelValue");
|
|
490
591
|
const search = ref("");
|
|
592
|
+
const { label: shortcutLabel, close } = useNavigationSearchShortcut({
|
|
593
|
+
open,
|
|
594
|
+
field: useTemplateRef("searchField")
|
|
595
|
+
});
|
|
491
596
|
const filteredItems = computed(() => filterItems());
|
|
492
597
|
const searchDataTestid = computed(() => `${props.dataTestid}-search`);
|
|
493
598
|
const query = computed(() => search.value.toLowerCase());
|
|
@@ -518,17 +623,20 @@ var navigation_menu_default = /* @__PURE__ */ defineComponent({
|
|
|
518
623
|
"data-testid": props.dataTestid,
|
|
519
624
|
"aria-busy": props.loading || void 0,
|
|
520
625
|
absolute: "",
|
|
521
|
-
temporary: ""
|
|
626
|
+
temporary: "",
|
|
627
|
+
onKeydown: withKeys(unref(close), ["esc"])
|
|
522
628
|
}, {
|
|
523
629
|
prepend: withCtx(() => [createElementVNode("div", _hoisted_1$2, [createVNode(text_field_default, {
|
|
630
|
+
ref: "searchField",
|
|
524
631
|
modelValue: search.value,
|
|
525
632
|
"onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => search.value = $event),
|
|
526
633
|
type: "search",
|
|
527
|
-
|
|
634
|
+
placeholder: `Buscar (${unref(shortcutLabel)})`,
|
|
528
635
|
"data-testid": searchDataTestid.value,
|
|
529
636
|
disabled: props.loading
|
|
530
637
|
}, null, 8, [
|
|
531
638
|
"modelValue",
|
|
639
|
+
"placeholder",
|
|
532
640
|
"data-testid",
|
|
533
641
|
"disabled"
|
|
534
642
|
])])]),
|
|
@@ -561,7 +669,8 @@ var navigation_menu_default = /* @__PURE__ */ defineComponent({
|
|
|
561
669
|
}, 8, [
|
|
562
670
|
"modelValue",
|
|
563
671
|
"data-testid",
|
|
564
|
-
"aria-busy"
|
|
672
|
+
"aria-busy",
|
|
673
|
+
"onKeydown"
|
|
565
674
|
]);
|
|
566
675
|
};
|
|
567
676
|
}
|
|
@@ -694,6 +803,11 @@ var button_default = /* @__PURE__ */ defineComponent({
|
|
|
694
803
|
form: unref(form),
|
|
695
804
|
onClick
|
|
696
805
|
}, {
|
|
806
|
+
loader: withCtx(() => [createVNode(unref(VProgressCircular), {
|
|
807
|
+
indeterminate: "",
|
|
808
|
+
width: "2",
|
|
809
|
+
"aria-hidden": "true"
|
|
810
|
+
})]),
|
|
697
811
|
default: withCtx(() => [renderSlot(_ctx.$slots, "default")]),
|
|
698
812
|
_: 3
|
|
699
813
|
}, 8, [
|
|
@@ -892,7 +1006,7 @@ var dialog_host_default = /* @__PURE__ */ defineComponent({
|
|
|
892
1006
|
__name: "dialog-host",
|
|
893
1007
|
setup(__props) {
|
|
894
1008
|
const owner = Symbol("dialog");
|
|
895
|
-
const isOwner = computed(() => owners.value[0] === owner);
|
|
1009
|
+
const isOwner = computed(() => owners$1.value[0] === owner);
|
|
896
1010
|
const next = computed(() => isOwner.value ? requests.value[0] : void 0);
|
|
897
1011
|
/** Dialog on display, kept until its leave transition ends. */
|
|
898
1012
|
const request = shallowRef();
|
|
@@ -921,8 +1035,8 @@ var dialog_host_default = /* @__PURE__ */ defineComponent({
|
|
|
921
1035
|
persistent: () => isRunning.value,
|
|
922
1036
|
afterLeave: showNext
|
|
923
1037
|
});
|
|
924
|
-
onMounted(() => owners.value.push(owner));
|
|
925
|
-
onUnmounted(() => owners.value = owners.value.filter((item) => item !== owner));
|
|
1038
|
+
onMounted(() => owners$1.value.push(owner));
|
|
1039
|
+
onUnmounted(() => owners$1.value = owners$1.value.filter((item) => item !== owner));
|
|
926
1040
|
watch(next, show);
|
|
927
1041
|
function show() {
|
|
928
1042
|
if (request.value) return;
|
package/dist/composables.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as FeedbackColorVariant, n as ButtonVariant, r as ColorVariant } from "./types-
|
|
1
|
+
import { i as FeedbackColorVariant, n as ButtonVariant, r as ColorVariant } from "./types-Zex9kf7C.js";
|
|
2
2
|
//#region src/composables/dialogs/types.d.ts
|
|
3
3
|
interface ToastOptions {
|
|
4
4
|
/**
|
|
@@ -33,6 +33,8 @@ type ButtonProps = {
|
|
|
33
33
|
disabled?: boolean;
|
|
34
34
|
/**
|
|
35
35
|
* Displays a loading indicator on the button, disabling interaction.
|
|
36
|
+
* The button keeps its text as accessible name and is marked as busy, and
|
|
37
|
+
* assistive technologies ignore the indicator.
|
|
36
38
|
* @default false
|
|
37
39
|
*/
|
|
38
40
|
loading?: boolean;
|