@nexdom/uimed-vue 2.0.0-beta.8 → 2.0.0-beta.9
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 +85 -12
- package/README.md +1 -1
- package/dist/components.d.ts +1 -45
- package/dist/components.js +196 -69
- package/dist/composables.d.ts +130 -4
- package/dist/composables.js +55 -3
- package/dist/types-HPMP8IrV.d.ts +49 -0
- package/dist/use-dialog-Ck2_ZW7T.js +124 -0
- package/package.json +1 -1
- package/dist/types-BPXXtbmW.d.ts +0 -5
- package/dist/use-toast-CACuYhK_.js +0 -53
package/AGENTS.md
CHANGED
|
@@ -30,7 +30,7 @@ Add the Vite plugin:
|
|
|
30
30
|
|
|
31
31
|
```ts
|
|
32
32
|
// vite.config.ts
|
|
33
|
-
import { vitePluginUimed } from "@nexdom/uimed-vue/plugins
|
|
33
|
+
import { vitePluginUimed } from "@nexdom/uimed-vue/plugins";
|
|
34
34
|
|
|
35
35
|
// ...
|
|
36
36
|
|
|
@@ -55,18 +55,18 @@ createApp(App).use(uimed).mount("#app");
|
|
|
55
55
|
|
|
56
56
|
### Usage
|
|
57
57
|
|
|
58
|
-
Place the `
|
|
58
|
+
Place the `UMain` component at the top of `App.vue`, then add other components as needed:
|
|
59
59
|
|
|
60
60
|
```vue
|
|
61
61
|
<!-- App.vue -->
|
|
62
62
|
<template>
|
|
63
|
-
<
|
|
63
|
+
<u-main>
|
|
64
64
|
<!-- ... -->
|
|
65
|
-
</
|
|
65
|
+
</u-main>
|
|
66
66
|
</template>
|
|
67
67
|
|
|
68
68
|
<script setup lang="ts">
|
|
69
|
-
import {
|
|
69
|
+
import { UMain } from "@nexdom/uimed-vue/components";
|
|
70
70
|
</script>
|
|
71
71
|
```
|
|
72
72
|
|
|
@@ -102,24 +102,70 @@ vp env setup # create shims like vpr and vpx
|
|
|
102
102
|
vp install # install dependencies
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
+
On Windows, prefer VSCode's "Dev Containers: Clone Repository in Container Volume" over opening a folder cloned on the host:
|
|
106
|
+
|
|
107
|
+
- A host clone with `core.autocrlf=true` checks files out with CRLF, and `vpr check` then reports formatting issues on every file. `.gitattributes` enforces LF, but clones made before it was added need `git rm -rq --cached . && git reset --hard` to be re-normalized (commit or stash local changes first, since `reset --hard` discards them).
|
|
108
|
+
- A host folder bind-mounted into the container is roughly 10x slower (unit tests alone go from seconds to minutes), which makes Stryker's initial test run time out.
|
|
109
|
+
|
|
105
110
|
## Commands
|
|
106
111
|
|
|
107
|
-
- `vpr check` — lint, formatter, and type-check (
|
|
112
|
+
- `vpr check` — lint, formatter, and type-check (builds the library first, since the type-check reads `dist`)
|
|
108
113
|
- `vp test --coverage` — unit tests with coverage (Vitest, jsdom, 100% coverage threshold enforced)
|
|
109
|
-
- `vpr test:mutations` — mutation tests (Stryker; thresholds: high 100, low
|
|
110
|
-
- `vpr test:e2e` — E2E tests (Playwright, runs against the built docs preview site)
|
|
114
|
+
- `vpr test:mutations` — mutation tests (Stryker; thresholds: high 100, low 100, break 100). It starts one test runner per CPU core; on machines with limited memory, run `vpx stryker run --concurrency 4` instead
|
|
115
|
+
- `vpr test:e2e` — E2E tests (Playwright, runs against the built docs preview site). Outside CI it reuses any server already on port 4173, which is also `vpr docs:dev`'s port: stop the dev server first, or the tests run against it instead of the built site
|
|
111
116
|
- `vpr depcruise` — architecture/dependency rules (dependency-cruiser)
|
|
112
117
|
- `vp pack` / `vpr build` — build the library
|
|
113
118
|
- `vpr docs` / `vpr docs:dev` — run docs site (imports the lib from `dist`, not `src` — run `vpr dev` in a second terminal to keep `dist` updated while iterating)
|
|
114
119
|
|
|
115
120
|
CI (`.github/workflows/ci.yml`) runs, in order: commitlint on PR commits, `vp pack`, `vpr check`, `vpr depcruise`, `vp test --coverage`, `vpr test:mutations`, `vpr test:e2e`. Match this locally before opening a PR.
|
|
116
121
|
|
|
122
|
+
### Focused runs while iterating
|
|
123
|
+
|
|
124
|
+
The full suite takes minutes; while working on a single component or composable, scope each step to it (`button` below is an example) and run the full suite only before opening a PR:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# Unit tests of one or more folders
|
|
128
|
+
vp test src/components/button
|
|
129
|
+
|
|
130
|
+
# Same, with coverage limited to the files you changed. Without `--coverage.include`, files that
|
|
131
|
+
# are only imported (e.g. the children of `main.vue`) count as uncovered and fail the 100% threshold
|
|
132
|
+
vp test src/components/button --coverage --coverage.include="src/components/button/**"
|
|
133
|
+
|
|
134
|
+
# Mutation tests of specific files. List several files in a single comma-separated `--mutate`
|
|
135
|
+
# (repeating the flag keeps only the last one)
|
|
136
|
+
vpx stryker run --mutate "src/components/button/button.vue,src/composables/button/button.ts"
|
|
137
|
+
|
|
138
|
+
# One E2E spec
|
|
139
|
+
vpr test:e2e e2e/components/button.spec.ts
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Adding a new component
|
|
143
|
+
|
|
144
|
+
Use an existing component (e.g. `button`) as the reference, and deliver all of the following in the same PR:
|
|
145
|
+
|
|
146
|
+
1. `src/components/<name>/<name>.vue` (following the pattern in [Code conventions](#code-conventions)), `types.ts` and `__tests__/<name>.test.ts`.
|
|
147
|
+
2. The `U`-prefixed export in `src/components/index.ts`.
|
|
148
|
+
3. A usage guide at `docs/guide/components/<name>.md`, with `<demo>` examples (and a `<playground>` section when the component has configurable props), and an API reference at `docs/api/components/<name>.md` (props, events and slots tables). Both in Portuguese.
|
|
149
|
+
4. Both pages registered in the sidebar at `docs/.vitepress/config.ts`.
|
|
150
|
+
5. `e2e/components/<name>.spec.ts` covering the guide's interactive examples and a "UI consistency" screenshot check (plus accessible snapshots where relevant). Commit the generated files under `__snapshots__`/`__screenshot__`.
|
|
151
|
+
6. The full CI sequence passing locally.
|
|
152
|
+
|
|
153
|
+
## Adding a new composable
|
|
154
|
+
|
|
155
|
+
Use an existing composable (e.g. `use-toast`) as the reference, and deliver in the same PR:
|
|
156
|
+
|
|
157
|
+
1. `src/composables/<group>/<name>.ts` and `__tests__/<name>.test.ts`, plus the types in the group's `types.ts`. If it relies on an internal component (as `useToast` relies on the internal `Toast` rendered by `UMain`), that component follows the component layout but isn't exported.
|
|
158
|
+
2. The export in `src/composables/index.ts`, and its assertion in `src/composables/__tests__/index.test.ts`.
|
|
159
|
+
3. A usage guide at `docs/guide/composables/<name>.md` and an API reference at `docs/api/composables/<name>.md`, both in Portuguese, registered in both sidebars at `docs/.vitepress/config.ts` and listed in `docs/api/index.md` and in the table of `docs/guide/index.md`. If it relies on `UMain` (like `useToast`), also mention it in `UMain`'s guide and API pages.
|
|
160
|
+
4. `e2e/composables/<name>.spec.ts` covering the guide's examples and a "UI consistency" screenshot check.
|
|
161
|
+
5. The full CI sequence passing locally.
|
|
162
|
+
|
|
117
163
|
## Code conventions
|
|
118
164
|
|
|
119
165
|
- All `src` code is written in English. `docs` content is written in Portuguese (aimed at Brazilian users), even though file/dir names stay in English.
|
|
120
166
|
- `docs` must not mention Vuetify
|
|
121
167
|
- A new component/composable must be listed in its guide page (`docs/guide/…`), its API page (`docs/api/…`), both sidebars in `docs/.vitepress/config.ts`, the guide index table (`docs/guide/index.md`) and the API index list (`docs/api/index.md`).
|
|
122
|
-
- Never write CSS
|
|
168
|
+
- Never write CSS or `<style>` blocks, and expose styling to consumers only through props. Inside the library's own components, a Vuetify utility class is acceptable when no Vuetify prop covers the need (e.g. `text-wrap` on a card title), with a comment saying why.
|
|
123
169
|
- Path aliases: `@/*` → `src/*`, `@e2e/*` → `e2e/*`.
|
|
124
170
|
- Every public component follows this pattern to block access to internals and give it an editor-hover description:
|
|
125
171
|
|
|
@@ -139,8 +185,9 @@ CI (`.github/workflows/ci.yml`) runs, in order: commitlint on PR commits, `vp pa
|
|
|
139
185
|
</script>
|
|
140
186
|
```
|
|
141
187
|
|
|
142
|
-
- Component layout: `src/components/<name>/<name>.vue`, `types.ts` for prop/option types, `__tests__/<name>.test.ts` for unit tests.
|
|
143
|
-
-
|
|
188
|
+
- Component layout: `src/components/<name>/<name>.vue`, `types.ts` for prop/option types, `__tests__/<name>.test.ts` for unit tests. Components that belong to a group live in the group's folder (e.g. the internal `src/components/dialogs/toast.vue`).
|
|
189
|
+
- Composables live in a group folder: `src/composables/<group>/<name>.ts`, with `types.ts` and `__tests__/` in the group folder (e.g. `src/composables/dialogs/use-toast.ts`).
|
|
190
|
+
- 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)).
|
|
144
191
|
- 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.
|
|
145
192
|
- 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.
|
|
146
193
|
- 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. Both are meant to be removed once their upstream issues are fixed — don't build further on top of them without checking if they're still needed.
|
|
@@ -149,8 +196,34 @@ CI (`.github/workflows/ci.yml`) runs, in order: commitlint on PR commits, `vp pa
|
|
|
149
196
|
|
|
150
197
|
- Unit tests use Vitest + `@vue/test-utils`, with `vueTestUtilsPluginUimed()` from `@/unit-test.ts` to mount a Vuetify instance.
|
|
151
198
|
- Global unit test setup (`src/__tests__/setup.ts`) stubs `visualViewport`, uses fake timers, and silences `console.error/warn/log`.
|
|
152
|
-
-
|
|
199
|
+
- With fake timers in jsdom, Vuetify transitions (e.g. of `VDialog`, `VMenu`) don't finish on their own. Emit the transition events on the Vuetify component (`wrapper.findComponent(VDialog).vm.$emit("afterLeave")`) or advance the timers with `await vi.runAllTimersAsync()`.
|
|
200
|
+
- Coverage threshold is 100%; mutation testing threshold is 100% (break at 100). Don't add code paths without covering tests. Mutants that don't compile are reported as compile errors and don't count toward the score.
|
|
153
201
|
- E2E tests (Playwright, `e2e/`) run against the built docs preview (`http://localhost:4173/uimed-vue/`). Snapshots/screenshots live under `__snapshots__`/`__screenshot__` next to each spec.
|
|
202
|
+
- In E2E, a paused `page.clock` also freezes transitions, so overlays never finish leaving. Use `page.clock.pauseAt` 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`).
|
|
203
|
+
|
|
204
|
+
## Library documentation and dependencies
|
|
205
|
+
|
|
206
|
+
The stack is newer than most AI models' training data (Vue 3.5, Vuetify 4, Vue Router 5, Vite+ 0.2, Vitest 4, TypeScript 6, VitePress 2 alpha, Stryker 10, Playwright 1.62), so don't rely on memory for library APIs.
|
|
207
|
+
|
|
208
|
+
- Before using an API this repo doesn't use yet, or implementing something from scratch, look it up in the version declared in `package.json`. The project's `.mcp.json` provides two servers for that:
|
|
209
|
+
- `context7`, with these library IDs: Vue `/websites/vuejs`, Vuetify `/websites/vuetifyjs_en`, Vue Router `/websites/router_vuejs`, Vite+ `/websites/viteplus_dev`, Vitest `/vitest-dev/vitest`, Vue Test Utils `/vuejs/test-utils`, Playwright `/microsoft/playwright`, VitePress `/vuejs/vitepress`, Stryker `/stryker-mutator/stryker-js`.
|
|
210
|
+
- `vuetify`, Vuetify's own server, for component and composable APIs and release notes. Its tools that create or update bins, links, playgrounds or bug reports publish content outside the repo and are denied in `.claude/settings.json`.
|
|
211
|
+
- Queries to both servers leave your machine: describe what you need in generic terms and never include source code or business rules.
|
|
212
|
+
- Before adding a dependency, check whether Vuetify, `@nexdom/shared` or the current dependencies already cover the need. If not, confirm with the requester, check the package's docs, maintenance and license, and declare runtime dependencies as `peerDependencies` (enforced by dependency-cruiser's `use-peer-deps` rule).
|
|
213
|
+
- Vuetify is an internal detail: consumers use the library's props, and `docs` never mention Vuetify.
|
|
214
|
+
|
|
215
|
+
## AI agents
|
|
216
|
+
|
|
217
|
+
Besides this file, the repo ships Claude Code subagents in `.claude/agents/`:
|
|
218
|
+
|
|
219
|
+
- `issue-planner`: turns an issue into an API proposal plus open questions, before any code is written.
|
|
220
|
+
- `issue-implementer`: implements an issue end to end (source, tests, docs, E2E).
|
|
221
|
+
- `code-reviewer`: reviews a branch or PR against these conventions, without changing code.
|
|
222
|
+
- `dependency-updater`: evaluates and applies dependency updates, such as Dependabot PRs.
|
|
223
|
+
|
|
224
|
+
Issues and PRs live in `nexdom-healthtech/uimed-vue`. Pass `-R nexdom-healthtech/uimed-vue` to `gh`, since clones from forks have issues disabled, and read issues with `gh issue view <number> -R nexdom-healthtech/uimed-vue --json title,body,comments` (`--comments` prints nothing in non-interactive shells).
|
|
225
|
+
|
|
226
|
+
To resolve an issue, run `/resolve-issue <number>` (`.claude/skills/resolve-issue/`). It chains planner, implementer and reviewer, asks you about open questions and waits for your approval of the public API before any code is written.
|
|
154
227
|
|
|
155
228
|
## Git workflow
|
|
156
229
|
|
package/README.md
CHANGED
package/dist/components.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as
|
|
1
|
+
import { t as ButtonProps } from "./types-HPMP8IrV.js";
|
|
2
2
|
import { RouteLocationRaw } from "vue-router";
|
|
3
3
|
//#region src/components/app-bar/types.d.ts
|
|
4
4
|
/**
|
|
@@ -189,50 +189,6 @@ type __VLS_WithSlots$8<T, S> = T & {
|
|
|
189
189
|
};
|
|
190
190
|
};
|
|
191
191
|
//#endregion
|
|
192
|
-
//#region src/components/button/types.d.ts
|
|
193
|
-
type ButtonVariant = "primary" | "secondary" | "ghost";
|
|
194
|
-
/**
|
|
195
|
-
* Props exposed by the {@link Button} component.
|
|
196
|
-
*/
|
|
197
|
-
type ButtonProps = {
|
|
198
|
-
/**
|
|
199
|
-
* Applies a distinct style variation to the button.
|
|
200
|
-
* One of `primary`, `secondary`, or `ghost`.
|
|
201
|
-
* @default "primary"
|
|
202
|
-
*/
|
|
203
|
-
variant?: ButtonVariant;
|
|
204
|
-
/**
|
|
205
|
-
* Applies a distinct behavior to the button.
|
|
206
|
-
* One of `button` or `submit`.
|
|
207
|
-
* @default "button"
|
|
208
|
-
*/
|
|
209
|
-
type?: "submit" | "button";
|
|
210
|
-
/**
|
|
211
|
-
* Applies a color to the button.
|
|
212
|
-
* One of `primary`, `secondary`, `positive`, `informative`, `caution`, or `danger`.
|
|
213
|
-
* @default "primary"
|
|
214
|
-
*/
|
|
215
|
-
color?: ColorVariant;
|
|
216
|
-
/**
|
|
217
|
-
* Removes the ability to click or target the button.
|
|
218
|
-
* @default false
|
|
219
|
-
*/
|
|
220
|
-
disabled?: boolean;
|
|
221
|
-
/**
|
|
222
|
-
* Displays a loading indicator on the button, disabling interaction.
|
|
223
|
-
* @default false
|
|
224
|
-
*/
|
|
225
|
-
loading?: boolean;
|
|
226
|
-
/**
|
|
227
|
-
* Associated [form](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/form) id.
|
|
228
|
-
*/
|
|
229
|
-
form?: string;
|
|
230
|
-
/**
|
|
231
|
-
* Component id to use on automated tests.
|
|
232
|
-
*/
|
|
233
|
-
dataTestid?: string;
|
|
234
|
-
};
|
|
235
|
-
//#endregion
|
|
236
192
|
//#region src/components/button/button.vue.d.ts
|
|
237
193
|
/**
|
|
238
194
|
* Button component to be used throughout the application.
|
package/dist/components.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { r as
|
|
2
|
-
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, createTextVNode, createVNode, defineComponent, isRef, mergeModels, mergeProps, normalizeClass, openBlock, ref, renderList, renderSlot, resolveDynamicComponent, toDisplayString, toValue, unref, useModel, withCtx, withModifiers } from "vue";
|
|
3
|
-
import { VApp, VAppBar, VAppBarNavIcon, VAppBarTitle, VAutocomplete, VAvatar, VBadge, VBtn, VCard, VCardActions, VCardText, VCheckboxBtn, VCol, VCombobox, VContainer, VDivider, VExpansionPanel, VExpansionPanelText, VExpansionPanelTitle, VExpansionPanels, VForm, VIcon, VIconBtn, VImg, VList, VListGroup, VListItem, VListItemAction, VListSubheader, VMain, VMenu, VNavigationDrawer, VRow, VSkeletonLoader, VSnackbarQueue, VTable, VTextField } from "vuetify/components";
|
|
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, isRef, mergeModels, mergeProps, normalizeClass, onMounted, onUnmounted, openBlock, ref, renderList, renderSlot, resolveDynamicComponent, shallowRef, toDisplayString, toValue, unref, useId, useModel, useTemplateRef, watch, withCtx, withModifiers } from "vue";
|
|
3
|
+
import { VApp, VAppBar, VAppBarNavIcon, VAppBarTitle, VAutocomplete, VAvatar, VBadge, VBtn, VCard, VCardActions, VCardItem, VCardText, VCardTitle, VCheckboxBtn, VCol, VCombobox, VContainer, VDialog, VDivider, VExpansionPanel, VExpansionPanelText, VExpansionPanelTitle, VExpansionPanels, VForm, VIcon, VIconBtn, VImg, VList, VListGroup, VListItem, VListItemAction, VListSubheader, VMain, VMenu, VNavigationDrawer, VRow, VSkeletonLoader, VSnackbarQueue, VTable, VTextField } from "vuetify/components";
|
|
4
4
|
import { formatDateTime, isEmail, isEmpty, isPhone, isUrl, toPeriodInterval } from "@nexdom/shared/utils";
|
|
5
5
|
//#region src/composables/navigation/use-route-props.ts
|
|
6
6
|
function useRouteProps(route) {
|
|
@@ -549,71 +549,6 @@ var toast_default = /* @__PURE__ */ defineComponent({
|
|
|
549
549
|
}
|
|
550
550
|
});
|
|
551
551
|
//#endregion
|
|
552
|
-
//#region src/components/main/main.vue
|
|
553
|
-
var main_default = /* @__PURE__ */ defineComponent({
|
|
554
|
-
inheritAttrs: false,
|
|
555
|
-
__name: "main",
|
|
556
|
-
props: /*@__PURE__*/ mergeModels({
|
|
557
|
-
dataTestid: {
|
|
558
|
-
type: String,
|
|
559
|
-
required: false
|
|
560
|
-
},
|
|
561
|
-
logo: {
|
|
562
|
-
type: String,
|
|
563
|
-
required: false
|
|
564
|
-
},
|
|
565
|
-
appBar: {
|
|
566
|
-
type: Object,
|
|
567
|
-
required: false
|
|
568
|
-
},
|
|
569
|
-
navigationMenu: {
|
|
570
|
-
type: Object,
|
|
571
|
-
required: false
|
|
572
|
-
}
|
|
573
|
-
}, {
|
|
574
|
-
"notificationsOpen": {
|
|
575
|
-
type: Boolean,
|
|
576
|
-
default: false
|
|
577
|
-
},
|
|
578
|
-
"notificationsOpenModifiers": {}
|
|
579
|
-
}),
|
|
580
|
-
emits: ["update:notificationsOpen"],
|
|
581
|
-
setup(__props) {
|
|
582
|
-
const props = __props;
|
|
583
|
-
const appBarProps = computed(() => props.appBar ? {
|
|
584
|
-
...props.appBar,
|
|
585
|
-
logo: props.logo,
|
|
586
|
-
navigation: props.navigationMenu !== void 0
|
|
587
|
-
} : void 0);
|
|
588
|
-
const notificationsOpen = useModel(__props, "notificationsOpen");
|
|
589
|
-
const navigationMenuOpen = ref(false);
|
|
590
|
-
return (_ctx, _cache) => {
|
|
591
|
-
return openBlock(), createBlock(unref(VApp), { "data-testid": props.dataTestid }, {
|
|
592
|
-
default: withCtx(() => [
|
|
593
|
-
appBarProps.value ? (openBlock(), createBlock(app_bar_default, mergeProps({ key: 0 }, appBarProps.value, {
|
|
594
|
-
"notifications-open": notificationsOpen.value,
|
|
595
|
-
"onUpdate:notificationsOpen": _cache[0] || (_cache[0] = ($event) => notificationsOpen.value = $event),
|
|
596
|
-
"navigation-open": navigationMenuOpen.value,
|
|
597
|
-
"onUpdate:navigationOpen": _cache[1] || (_cache[1] = ($event) => navigationMenuOpen.value = $event)
|
|
598
|
-
}), null, 16, ["notifications-open", "navigation-open"])) : createCommentVNode("v-if", true),
|
|
599
|
-
props.navigationMenu ? (openBlock(), createBlock(navigation_menu_default, mergeProps({ key: 1 }, props.navigationMenu, {
|
|
600
|
-
modelValue: navigationMenuOpen.value,
|
|
601
|
-
"onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => navigationMenuOpen.value = $event)
|
|
602
|
-
}), null, 16, ["modelValue"])) : createCommentVNode("v-if", true),
|
|
603
|
-
createVNode(unref(VMain), null, {
|
|
604
|
-
default: withCtx(() => [createVNode(container_default, null, {
|
|
605
|
-
default: withCtx(() => [renderSlot(_ctx.$slots, "default"), createVNode(toast_default)]),
|
|
606
|
-
_: 3
|
|
607
|
-
})]),
|
|
608
|
-
_: 3
|
|
609
|
-
})
|
|
610
|
-
]),
|
|
611
|
-
_: 3
|
|
612
|
-
}, 8, ["data-testid"]);
|
|
613
|
-
};
|
|
614
|
-
}
|
|
615
|
-
});
|
|
616
|
-
//#endregion
|
|
617
552
|
//#region src/composables/button/button.ts
|
|
618
553
|
const buttonVariantToVuetifyVariant = {
|
|
619
554
|
primary: "elevated",
|
|
@@ -706,6 +641,198 @@ var button_default = /* @__PURE__ */ defineComponent({
|
|
|
706
641
|
}
|
|
707
642
|
});
|
|
708
643
|
//#endregion
|
|
644
|
+
//#region src/components/dialogs/dialog.vue
|
|
645
|
+
var dialog_default = /* @__PURE__ */ defineComponent({
|
|
646
|
+
inheritAttrs: false,
|
|
647
|
+
__name: "dialog",
|
|
648
|
+
setup(__props) {
|
|
649
|
+
const owner = Symbol("dialog");
|
|
650
|
+
const titleId = useId();
|
|
651
|
+
const messageId = useId();
|
|
652
|
+
const actions = useTemplateRef("actions");
|
|
653
|
+
const isOwner = computed(() => owners.value[0] === owner);
|
|
654
|
+
const next = computed(() => isOwner.value ? requests.value[0] : void 0);
|
|
655
|
+
/** Dialog on display, kept until its leave transition ends. */
|
|
656
|
+
const request = shallowRef();
|
|
657
|
+
const isOpen = ref(false);
|
|
658
|
+
const selected = shallowRef();
|
|
659
|
+
const isRunning = computed(() => selected.value !== void 0);
|
|
660
|
+
/** Element focused before the dialog opened, which gets the focus back when it closes. */
|
|
661
|
+
let origin = null;
|
|
662
|
+
onMounted(() => owners.value.push(owner));
|
|
663
|
+
onUnmounted(() => owners.value = owners.value.filter((item) => item !== owner));
|
|
664
|
+
watch(next, show);
|
|
665
|
+
function show() {
|
|
666
|
+
if (request.value) return;
|
|
667
|
+
origin = document.activeElement;
|
|
668
|
+
request.value = next.value;
|
|
669
|
+
isOpen.value = next.value !== void 0;
|
|
670
|
+
}
|
|
671
|
+
function showNext() {
|
|
672
|
+
request.value = void 0;
|
|
673
|
+
show();
|
|
674
|
+
}
|
|
675
|
+
async function select(current, action) {
|
|
676
|
+
if (isRunning.value) return;
|
|
677
|
+
requests.value.shift();
|
|
678
|
+
selected.value = action;
|
|
679
|
+
await current.select(action?.value);
|
|
680
|
+
selected.value = void 0;
|
|
681
|
+
isOpen.value = false;
|
|
682
|
+
if (origin instanceof HTMLElement || origin instanceof SVGElement) origin.focus();
|
|
683
|
+
}
|
|
684
|
+
function dismiss() {
|
|
685
|
+
if (request.value) select(request.value);
|
|
686
|
+
}
|
|
687
|
+
function focusFirstAction() {
|
|
688
|
+
actions.value?.$el.querySelector("button").focus();
|
|
689
|
+
}
|
|
690
|
+
return (_ctx, _cache) => {
|
|
691
|
+
return openBlock(), createBlock(unref(VDialog), {
|
|
692
|
+
"model-value": isOpen.value,
|
|
693
|
+
persistent: isRunning.value,
|
|
694
|
+
role: request.value?.role,
|
|
695
|
+
"aria-labelledby": request.value?.title ? unref(titleId) : unref(messageId),
|
|
696
|
+
"aria-describedby": unref(messageId),
|
|
697
|
+
"max-width": "560",
|
|
698
|
+
"onUpdate:modelValue": dismiss,
|
|
699
|
+
onAfterEnter: focusFirstAction,
|
|
700
|
+
onAfterLeave: showNext
|
|
701
|
+
}, {
|
|
702
|
+
default: withCtx(() => [createCommentVNode("\n MD3 dialogs have a 24px padding, 16px between title and message, 24px between message and\n actions, and 8px between the end-aligned actions. Inside a dialog, the card parts already\n have 24px sides, 16px below the title, 24px below the message, and end-aligned actions 8px\n apart, but no prop sets the remaining paddings, hence the spacing classes.\n "), request.value ? (openBlock(), createBlock(unref(VCard), { key: 0 }, {
|
|
703
|
+
default: withCtx(() => [
|
|
704
|
+
request.value.title ? (openBlock(), createBlock(unref(VCardItem), {
|
|
705
|
+
key: 0,
|
|
706
|
+
class: "pt-6"
|
|
707
|
+
}, {
|
|
708
|
+
default: withCtx(() => [createCommentVNode(" Titles wrap instead of being truncated, since they're part of the message "), createVNode(unref(VCardTitle), {
|
|
709
|
+
id: unref(titleId),
|
|
710
|
+
class: "text-wrap"
|
|
711
|
+
}, {
|
|
712
|
+
default: withCtx(() => [createTextVNode(toDisplayString(request.value.title), 1)]),
|
|
713
|
+
_: 1
|
|
714
|
+
}, 8, ["id"])]),
|
|
715
|
+
_: 1
|
|
716
|
+
})) : createCommentVNode("v-if", true),
|
|
717
|
+
createCommentVNode(" Below a title, the card already removes the message's top padding "),
|
|
718
|
+
createVNode(unref(VCardText), {
|
|
719
|
+
id: unref(messageId),
|
|
720
|
+
class: normalizeClass({ "pt-6": !request.value.title })
|
|
721
|
+
}, {
|
|
722
|
+
default: withCtx(() => [createTextVNode(toDisplayString(request.value.message), 1)]),
|
|
723
|
+
_: 1
|
|
724
|
+
}, 8, ["id", "class"]),
|
|
725
|
+
request.value.actions.length > 0 ? (openBlock(), createBlock(unref(VCardActions), {
|
|
726
|
+
key: 1,
|
|
727
|
+
ref_key: "actions",
|
|
728
|
+
ref: actions,
|
|
729
|
+
class: "px-6 pt-0 pb-6"
|
|
730
|
+
}, {
|
|
731
|
+
default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(request.value.actions, (action, index) => {
|
|
732
|
+
return openBlock(), createBlock(button_default, {
|
|
733
|
+
key: index,
|
|
734
|
+
variant: action.variant,
|
|
735
|
+
color: action.color,
|
|
736
|
+
loading: selected.value === action,
|
|
737
|
+
disabled: isRunning.value && selected.value !== action,
|
|
738
|
+
onClick: ($event) => select(request.value, action)
|
|
739
|
+
}, {
|
|
740
|
+
default: withCtx(() => [createTextVNode(toDisplayString(action.text), 1)]),
|
|
741
|
+
_: 2
|
|
742
|
+
}, 1032, [
|
|
743
|
+
"variant",
|
|
744
|
+
"color",
|
|
745
|
+
"loading",
|
|
746
|
+
"disabled",
|
|
747
|
+
"onClick"
|
|
748
|
+
]);
|
|
749
|
+
}), 128))]),
|
|
750
|
+
_: 1
|
|
751
|
+
}, 512)) : createCommentVNode("v-if", true)
|
|
752
|
+
]),
|
|
753
|
+
_: 1
|
|
754
|
+
})) : createCommentVNode("v-if", true)]),
|
|
755
|
+
_: 1
|
|
756
|
+
}, 8, [
|
|
757
|
+
"model-value",
|
|
758
|
+
"persistent",
|
|
759
|
+
"role",
|
|
760
|
+
"aria-labelledby",
|
|
761
|
+
"aria-describedby"
|
|
762
|
+
]);
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
//#endregion
|
|
767
|
+
//#region src/components/main/main.vue
|
|
768
|
+
var main_default = /* @__PURE__ */ defineComponent({
|
|
769
|
+
inheritAttrs: false,
|
|
770
|
+
__name: "main",
|
|
771
|
+
props: /*@__PURE__*/ mergeModels({
|
|
772
|
+
dataTestid: {
|
|
773
|
+
type: String,
|
|
774
|
+
required: false
|
|
775
|
+
},
|
|
776
|
+
logo: {
|
|
777
|
+
type: String,
|
|
778
|
+
required: false
|
|
779
|
+
},
|
|
780
|
+
appBar: {
|
|
781
|
+
type: Object,
|
|
782
|
+
required: false
|
|
783
|
+
},
|
|
784
|
+
navigationMenu: {
|
|
785
|
+
type: Object,
|
|
786
|
+
required: false
|
|
787
|
+
}
|
|
788
|
+
}, {
|
|
789
|
+
"notificationsOpen": {
|
|
790
|
+
type: Boolean,
|
|
791
|
+
default: false
|
|
792
|
+
},
|
|
793
|
+
"notificationsOpenModifiers": {}
|
|
794
|
+
}),
|
|
795
|
+
emits: ["update:notificationsOpen"],
|
|
796
|
+
setup(__props) {
|
|
797
|
+
const props = __props;
|
|
798
|
+
const appBarProps = computed(() => props.appBar ? {
|
|
799
|
+
...props.appBar,
|
|
800
|
+
logo: props.logo,
|
|
801
|
+
navigation: props.navigationMenu !== void 0
|
|
802
|
+
} : void 0);
|
|
803
|
+
const notificationsOpen = useModel(__props, "notificationsOpen");
|
|
804
|
+
const navigationMenuOpen = ref(false);
|
|
805
|
+
return (_ctx, _cache) => {
|
|
806
|
+
return openBlock(), createBlock(unref(VApp), { "data-testid": props.dataTestid }, {
|
|
807
|
+
default: withCtx(() => [
|
|
808
|
+
appBarProps.value ? (openBlock(), createBlock(app_bar_default, mergeProps({ key: 0 }, appBarProps.value, {
|
|
809
|
+
"notifications-open": notificationsOpen.value,
|
|
810
|
+
"onUpdate:notificationsOpen": _cache[0] || (_cache[0] = ($event) => notificationsOpen.value = $event),
|
|
811
|
+
"navigation-open": navigationMenuOpen.value,
|
|
812
|
+
"onUpdate:navigationOpen": _cache[1] || (_cache[1] = ($event) => navigationMenuOpen.value = $event)
|
|
813
|
+
}), null, 16, ["notifications-open", "navigation-open"])) : createCommentVNode("v-if", true),
|
|
814
|
+
props.navigationMenu ? (openBlock(), createBlock(navigation_menu_default, mergeProps({ key: 1 }, props.navigationMenu, {
|
|
815
|
+
modelValue: navigationMenuOpen.value,
|
|
816
|
+
"onUpdate:modelValue": _cache[2] || (_cache[2] = ($event) => navigationMenuOpen.value = $event)
|
|
817
|
+
}), null, 16, ["modelValue"])) : createCommentVNode("v-if", true),
|
|
818
|
+
createVNode(unref(VMain), null, {
|
|
819
|
+
default: withCtx(() => [createVNode(container_default, null, {
|
|
820
|
+
default: withCtx(() => [
|
|
821
|
+
renderSlot(_ctx.$slots, "default"),
|
|
822
|
+
createVNode(toast_default),
|
|
823
|
+
createVNode(dialog_default)
|
|
824
|
+
]),
|
|
825
|
+
_: 3
|
|
826
|
+
})]),
|
|
827
|
+
_: 3
|
|
828
|
+
})
|
|
829
|
+
]),
|
|
830
|
+
_: 3
|
|
831
|
+
}, 8, ["data-testid"]);
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
});
|
|
835
|
+
//#endregion
|
|
709
836
|
//#region src/composables/inputs/autocomplete-field.ts
|
|
710
837
|
function createAutocompleteRequiredRule(options) {
|
|
711
838
|
return createRequiredRule(options.required, (value) => {
|
|
@@ -1086,7 +1213,7 @@ var section_default = /* @__PURE__ */ defineComponent({
|
|
|
1086
1213
|
}, {
|
|
1087
1214
|
default: withCtx(() => [renderSlot(_ctx.$slots, "default"), showActions.value ? (openBlock(), createBlock(unref(VCardActions), {
|
|
1088
1215
|
key: 0,
|
|
1089
|
-
class: "mt-auto"
|
|
1216
|
+
class: "mt-auto justify-end"
|
|
1090
1217
|
}, {
|
|
1091
1218
|
default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(props.actions, ({ label, onClick, ...action }, index) => {
|
|
1092
1219
|
return openBlock(), createBlock(button_default, mergeProps({ key: index }, { ref_for: true }, action, { onClick }), {
|
package/dist/composables.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as FeedbackColorVariant, n as ButtonVariant, r as ColorVariant } from "./types-HPMP8IrV.js";
|
|
2
2
|
//#region src/composables/dialogs/types.d.ts
|
|
3
3
|
interface ToastOptions {
|
|
4
4
|
/**
|
|
@@ -6,12 +6,84 @@ interface ToastOptions {
|
|
|
6
6
|
*/
|
|
7
7
|
message: string;
|
|
8
8
|
/**
|
|
9
|
-
* Applies a color to the
|
|
9
|
+
* Applies a color to the toast.
|
|
10
10
|
* One of `positive`, `informative`, `caution`, or `danger`.
|
|
11
11
|
* @default "informative"
|
|
12
12
|
*/
|
|
13
13
|
color?: FeedbackColorVariant;
|
|
14
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
|
+
}
|
|
15
87
|
//#endregion
|
|
16
88
|
//#region src/composables/dialogs/use-toast.d.ts
|
|
17
89
|
/**
|
|
@@ -37,7 +109,7 @@ declare function useToast(): {
|
|
|
37
109
|
*/
|
|
38
110
|
declare function toast(options: ToastOptions): void;
|
|
39
111
|
//#endregion
|
|
40
|
-
//#region src/composables/dialogs/use-run-or-toast
|
|
112
|
+
//#region src/composables/dialogs/use-run-or-toast.d.ts
|
|
41
113
|
/**
|
|
42
114
|
* Generate `run` method and a computed property `isRunning`.
|
|
43
115
|
* Calling `run` will reactively update `isRunning`.
|
|
@@ -64,4 +136,58 @@ declare function useRunOrToast(): {
|
|
|
64
136
|
isRunning: import("vue").ComputedRef<boolean>;
|
|
65
137
|
};
|
|
66
138
|
//#endregion
|
|
67
|
-
|
|
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 };
|
package/dist/composables.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as useDialog, o as useToast, t as openDialog } from "./use-dialog-Ck2_ZW7T.js";
|
|
2
2
|
import { computed, ref } from "vue";
|
|
3
|
-
//#region src/composables/dialogs/use-run-or-toast
|
|
3
|
+
//#region src/composables/dialogs/use-run-or-toast.ts
|
|
4
4
|
const { toast } = useToast();
|
|
5
5
|
/**
|
|
6
6
|
* Generate `run` method and a computed property `isRunning`.
|
|
@@ -62,4 +62,56 @@ function useRunOrToast() {
|
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
64
|
//#endregion
|
|
65
|
-
|
|
65
|
+
//#region src/composables/dialogs/use-confirm.ts
|
|
66
|
+
/**
|
|
67
|
+
* Composable which returns a function to run actions only after the user confirms them in a
|
|
68
|
+
* dialog, and a computed property `isRunning`.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```ts
|
|
72
|
+
* const { confirm, isRunning } = useConfirm();
|
|
73
|
+
*
|
|
74
|
+
* const removed = await confirm(
|
|
75
|
+
* { title: "Excluir paciente", message: "Esta ação não pode ser desfeita.", confirmText: "Excluir", color: "danger" },
|
|
76
|
+
* () => api.deletePatient(id),
|
|
77
|
+
* );
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
function useConfirm() {
|
|
81
|
+
const { run, isRunning } = useRunOrToast();
|
|
82
|
+
/**
|
|
83
|
+
* Displays a confirmation dialog and, if the user confirms, runs the given function while the
|
|
84
|
+
* dialog shows the confirm button loading. If the function throws an error, the dialog closes
|
|
85
|
+
* and a toast shows the error message.
|
|
86
|
+
* @returns the function returned value, or `false` if the user cancels or the function fails
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* const { confirm } = useConfirm();
|
|
91
|
+
*
|
|
92
|
+
* // Call like this:
|
|
93
|
+
* const result1 = await confirm({ message: "Salvar alterações?" }, () => save(form));
|
|
94
|
+
*
|
|
95
|
+
* // Or this:
|
|
96
|
+
* const result2 = await confirm({ message: "Salvar alterações?" }, save, form);
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
function confirm(options, action, ...params) {
|
|
100
|
+
return openDialog({
|
|
101
|
+
title: options.title,
|
|
102
|
+
message: options.message,
|
|
103
|
+
role: "alertdialog",
|
|
104
|
+
actions: [{ text: options.cancelText ?? "Cancelar" }, {
|
|
105
|
+
text: options.confirmText ?? "Confirmar",
|
|
106
|
+
value: "confirm",
|
|
107
|
+
color: options.color
|
|
108
|
+
}]
|
|
109
|
+
}, (value) => value === "confirm" ? run(action, ...params) : false);
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
confirm,
|
|
113
|
+
isRunning
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
export { useConfirm, useDialog, useRunOrToast, useToast };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
//#region src/composables/colors/types.d.ts
|
|
2
|
+
type ColorVariant = "primary" | "secondary" | FeedbackColorVariant;
|
|
3
|
+
type FeedbackColorVariant = "positive" | "informative" | "caution" | "danger";
|
|
4
|
+
//#endregion
|
|
5
|
+
//#region src/components/button/types.d.ts
|
|
6
|
+
type ButtonVariant = "primary" | "secondary" | "ghost";
|
|
7
|
+
/**
|
|
8
|
+
* Props exposed by the {@link Button} component.
|
|
9
|
+
*/
|
|
10
|
+
type ButtonProps = {
|
|
11
|
+
/**
|
|
12
|
+
* Applies a distinct style variation to the button.
|
|
13
|
+
* One of `primary`, `secondary`, or `ghost`.
|
|
14
|
+
* @default "primary"
|
|
15
|
+
*/
|
|
16
|
+
variant?: ButtonVariant;
|
|
17
|
+
/**
|
|
18
|
+
* Applies a distinct behavior to the button.
|
|
19
|
+
* One of `button` or `submit`.
|
|
20
|
+
* @default "button"
|
|
21
|
+
*/
|
|
22
|
+
type?: "submit" | "button";
|
|
23
|
+
/**
|
|
24
|
+
* Applies a color to the button.
|
|
25
|
+
* One of `primary`, `secondary`, `positive`, `informative`, `caution`, or `danger`.
|
|
26
|
+
* @default "primary"
|
|
27
|
+
*/
|
|
28
|
+
color?: ColorVariant;
|
|
29
|
+
/**
|
|
30
|
+
* Removes the ability to click or target the button.
|
|
31
|
+
* @default false
|
|
32
|
+
*/
|
|
33
|
+
disabled?: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Displays a loading indicator on the button, disabling interaction.
|
|
36
|
+
* @default false
|
|
37
|
+
*/
|
|
38
|
+
loading?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Associated [form](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/form) id.
|
|
41
|
+
*/
|
|
42
|
+
form?: string;
|
|
43
|
+
/**
|
|
44
|
+
* Component id to use on automated tests.
|
|
45
|
+
*/
|
|
46
|
+
dataTestid?: string;
|
|
47
|
+
};
|
|
48
|
+
//#endregion
|
|
49
|
+
export { FeedbackColorVariant as i, ButtonVariant as n, ColorVariant as r, ButtonProps as t };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { computed, ref, toValue } from "vue";
|
|
2
|
+
const colorToVuetifyColor = {
|
|
3
|
+
primary: "primary",
|
|
4
|
+
secondary: "secondary",
|
|
5
|
+
positive: "success",
|
|
6
|
+
informative: "info",
|
|
7
|
+
caution: "warning",
|
|
8
|
+
danger: "error"
|
|
9
|
+
};
|
|
10
|
+
//#endregion
|
|
11
|
+
//#region src/composables/colors/use-vuetify-color.ts
|
|
12
|
+
function useVuetifyColor(color) {
|
|
13
|
+
return computed(() => {
|
|
14
|
+
const colorValue = toValue(color);
|
|
15
|
+
return colorValue ? colorToVuetifyColor[colorValue] : colorToVuetifyColor.primary;
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/composables/dialogs/use-toast.ts
|
|
20
|
+
const messages = ref([]);
|
|
21
|
+
/**
|
|
22
|
+
* Composable which returns a function to display toast messages.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* const { toast } = useToast();
|
|
27
|
+
* toast({ message: "Hello, World!" });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
function useToast() {
|
|
31
|
+
return { toast };
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Displays a toast message.
|
|
35
|
+
* @param {ToastOptions} options - Options for the toast message.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```ts
|
|
39
|
+
* toast({ message: "Hello, World!" });
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
function toast(options) {
|
|
43
|
+
const text = options.message;
|
|
44
|
+
const color = useVuetifyColor(options.color ?? "informative");
|
|
45
|
+
const toastMessage = {
|
|
46
|
+
text,
|
|
47
|
+
color: color.value,
|
|
48
|
+
prependIcon: `$${color.value}`
|
|
49
|
+
};
|
|
50
|
+
messages.value.push(toastMessage);
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/composables/dialogs/use-dialog.ts
|
|
54
|
+
/**
|
|
55
|
+
* Dialogs waiting to be displayed, shared by `useDialog` and `useConfirm`. The first one is the
|
|
56
|
+
* dialog on display (or about to be).
|
|
57
|
+
*/
|
|
58
|
+
const requests = ref([]);
|
|
59
|
+
/**
|
|
60
|
+
* Mounted `Dialog` components, in mounting order. Only the first one displays dialogs, so pages
|
|
61
|
+
* with more than one `UMain` don't display duplicates.
|
|
62
|
+
*/
|
|
63
|
+
const owners = ref([]);
|
|
64
|
+
/**
|
|
65
|
+
* Queues a dialog and resolves with what `onSelect` returns for the clicked action's value
|
|
66
|
+
* (`undefined` when the dialog is dismissed).
|
|
67
|
+
*/
|
|
68
|
+
function openDialog(options, onSelect) {
|
|
69
|
+
const { actions, ...content } = options;
|
|
70
|
+
const lastIndex = actions.length - 1;
|
|
71
|
+
return new Promise((resolve) => {
|
|
72
|
+
requests.value.push({
|
|
73
|
+
...content,
|
|
74
|
+
actions: actions.map((action, index) => ({
|
|
75
|
+
...action,
|
|
76
|
+
variant: action.variant ?? (index === lastIndex ? "primary" : "ghost")
|
|
77
|
+
})),
|
|
78
|
+
async select(value) {
|
|
79
|
+
resolve(await onSelect(value));
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Composable which returns a function to display dialogs.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* const { dialog } = useDialog();
|
|
90
|
+
*
|
|
91
|
+
* const answer = await dialog({
|
|
92
|
+
* title: "Sessão expirando",
|
|
93
|
+
* message: "Deseja continuar conectado?",
|
|
94
|
+
* actions: [
|
|
95
|
+
* { text: "Sair", value: "leave" },
|
|
96
|
+
* { text: "Continuar", value: "stay" },
|
|
97
|
+
* ],
|
|
98
|
+
* });
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
function useDialog() {
|
|
102
|
+
return { dialog };
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Displays a dialog. Dialogs are displayed one at a time, in the order they were requested.
|
|
106
|
+
* @param {DialogOptions} options - Options for the dialog.
|
|
107
|
+
* @returns the `value` of the clicked action, or `undefined` when the dialog is closed by the
|
|
108
|
+
* default action, the `Esc` key, a click outside it or the browser's back button.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* await dialog({ title: "Aviso", message: "Seu cadastro foi enviado para análise." });
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
function dialog(options) {
|
|
116
|
+
return openDialog({
|
|
117
|
+
title: options.title,
|
|
118
|
+
message: options.message,
|
|
119
|
+
role: "dialog",
|
|
120
|
+
actions: options.actions ?? [{ text: "Fechar" }]
|
|
121
|
+
}, (value) => value);
|
|
122
|
+
}
|
|
123
|
+
//#endregion
|
|
124
|
+
export { messages as a, useDialog as i, owners as n, useToast as o, requests as r, useVuetifyColor as s, openDialog as t };
|
package/package.json
CHANGED
package/dist/types-BPXXtbmW.d.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { computed, ref, toValue } from "vue";
|
|
2
|
-
const colorToVuetifyColor = {
|
|
3
|
-
primary: "primary",
|
|
4
|
-
secondary: "secondary",
|
|
5
|
-
positive: "success",
|
|
6
|
-
informative: "info",
|
|
7
|
-
caution: "warning",
|
|
8
|
-
danger: "error"
|
|
9
|
-
};
|
|
10
|
-
//#endregion
|
|
11
|
-
//#region src/composables/colors/use-vuetify-color.ts
|
|
12
|
-
function useVuetifyColor(color) {
|
|
13
|
-
return computed(() => {
|
|
14
|
-
const colorValue = toValue(color);
|
|
15
|
-
return colorValue ? colorToVuetifyColor[colorValue] : colorToVuetifyColor.primary;
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
//#endregion
|
|
19
|
-
//#region src/composables/dialogs/use-toast.ts
|
|
20
|
-
const messages = ref([]);
|
|
21
|
-
/**
|
|
22
|
-
* Composable which returns a function to display toast messages.
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* ```ts
|
|
26
|
-
* const { toast } = useToast();
|
|
27
|
-
* toast({ message: "Hello, World!" });
|
|
28
|
-
* ```
|
|
29
|
-
*/
|
|
30
|
-
function useToast() {
|
|
31
|
-
return { toast };
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Displays a toast message.
|
|
35
|
-
* @param {ToastOptions} options - Options for the toast message.
|
|
36
|
-
*
|
|
37
|
-
* @example
|
|
38
|
-
* ```ts
|
|
39
|
-
* toast({ message: "Hello, World!" });
|
|
40
|
-
* ```
|
|
41
|
-
*/
|
|
42
|
-
function toast(options) {
|
|
43
|
-
const text = options.message;
|
|
44
|
-
const color = useVuetifyColor(options.color ?? "informative");
|
|
45
|
-
const toastMessage = {
|
|
46
|
-
text,
|
|
47
|
-
color: color.value,
|
|
48
|
-
prependIcon: `$${color.value}`
|
|
49
|
-
};
|
|
50
|
-
messages.value.push(toastMessage);
|
|
51
|
-
}
|
|
52
|
-
//#endregion
|
|
53
|
-
export { useToast as n, useVuetifyColor as r, messages as t };
|