@nexdom/uimed-vue 2.0.0-beta.42 → 2.0.0-beta.44

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.
@@ -1,4 +1,4 @@
1
- import { t as ButtonProps } from "./types-CjaJ2WAC.js";
1
+ import { t as ButtonProps } from "./types-BbWo6RIy.js";
2
2
  import { RouteLocationRaw } from "vue-router";
3
3
  //#region src/components/app-bar/types.d.ts
4
4
  /**
@@ -652,7 +652,9 @@ type MaskFieldVariant = TextFieldVariant;
652
652
  * Masks the library provides:
653
653
  *
654
654
  * - `cpf`: `###.###.###-##` (e.g. `123.456.789-09`);
655
- * - `cnpj`: `##.###.###/####-##` (e.g. `12.345.678/0001-95`);
655
+ * - `cnpj`: `##.###.###/####-##`, where the first 12 positions also accept
656
+ * letters, typed in uppercase or lowercase and kept in uppercase (e.g.
657
+ * `12.345.678/0001-95` or the alphanumeric `12.ABC.345/01DE-35`);
656
658
  * - `cep`: `#####-###` (e.g. `01310-100`);
657
659
  * - `phone`: `(##) #####-####` (e.g. `(11) 91234-5678`);
658
660
  * - `date`: `##/##/####` (e.g. `31/12/2026`), formatting only.
@@ -691,14 +693,21 @@ type MaskFieldProps = {
691
693
  * inserted automatically (e.g. `####-##/####` or `Nº ####`). A custom mask
692
694
  * must contain at least one `#`.
693
695
  *
694
- * Only digits are accepted, up to the number of `#` in the mask, and the
695
- * `v-model` holds only the digits (e.g. `12345678909` for `cpf`). A literal
696
- * the user could also type, such as a fixed digit (`0800 ###-####`), isn't
697
- * supported: where the literal is expected, the same character is read as
698
- * the literal.
696
+ * Only digits are accepted, up to the number of `#` in the mask, except in
697
+ * the first 12 positions of `cnpj`, which also accept letters (A–Z, without
698
+ * accents, typed in uppercase or lowercase) for the alphanumeric CNPJ (only
699
+ * with `cnpj`: the custom mask `##.###.###/####-##` accepts only digits). The
700
+ * `v-model` holds only these characters, with letters in uppercase (e.g.
701
+ * `12345678909` for `cpf`, and `12345678000195` or `12ABC34501DE35` for
702
+ * `cnpj`). A literal the user could also type, such as a fixed digit
703
+ * (`0800 ###-####`), isn't supported: where the literal is expected, the
704
+ * same character is read as the literal.
699
705
  *
700
- * The `cpf` and `cnpj` masks don't validate check digits, and `cnpj` only
701
- * accepts digits (not the alphanumeric CNPJ).
706
+ * Editing in the middle of the value shifts the characters after it, and one
707
+ * that doesn't fit its new position (e.g. a letter of `cnpj` shifted into
708
+ * the last 2 positions) is discarded.
709
+ *
710
+ * The `cpf` and `cnpj` masks don't validate check digits.
702
711
  *
703
712
  * Changing it keeps the `v-model`: a value that doesn't fit the new mask
704
713
  * (e.g. longer than it) is displayed cut and fails validation with "Valor
@@ -738,7 +747,8 @@ type MaskFieldProps = {
738
747
  label?: string;
739
748
  /**
740
749
  * Field placeholder. Defaults to the mask, with `0` in place of each `#`,
741
- * e.g. `000.000.000-00` for `cpf` and `Nº 0000` for `Nº ####`.
750
+ * e.g. `000.000.000-00` for `cpf`, `00.000.000/0000-00` for `cnpj` and
751
+ * `Nº 0000` for `Nº ####`.
742
752
  */
743
753
  placeholder?: string;
744
754
  /**
@@ -755,9 +765,10 @@ type MaskFieldProps = {
755
765
  //#endregion
756
766
  //#region src/components/inputs/mask-field/mask-field.vue.d.ts
757
767
  /**
758
- * Text field that masks the value while the user types, such as CPF, CNPJ,
759
- * CEP, phone or a custom mask. Its value holds only the digits, without the
760
- * mask's literals.
768
+ * Text field that masks the value while the user types, such as CPF, CNPJ
769
+ * (numeric or alphanumeric), CEP, phone or a custom mask. Its value holds only
770
+ * the typed characters, without the mask's literals and with letters in
771
+ * uppercase.
761
772
  *
762
773
  * @example
763
774
  * ```vue
@@ -924,13 +924,31 @@ function useButtonVariant(variant) {
924
924
  return variantValue ? buttonVariantToVuetifyVariant[variantValue] : buttonVariantToVuetifyVariant.primary;
925
925
  });
926
926
  }
927
- function useButtonType(type) {
927
+ /**
928
+ * The route the button navigates to, or `undefined` while it's blocked, so a `disabled` or
929
+ * `loading` button is rendered as a disabled `<button>` instead of a link that the keyboard,
930
+ * assistive technologies or `element.click()` could still follow.
931
+ */
932
+ function useButtonRoute(route, blocked) {
933
+ return computed(() => toValue(blocked) ? void 0 : toValue(route));
934
+ }
935
+ /**
936
+ * The button's `type`. It doesn't apply to a button with a `route`, which is `undefined` while
937
+ * rendered as a link, and `"button"` while blocked, so the disabled `<button>` it's rendered as
938
+ * isn't a submit button.
939
+ */
940
+ function useButtonType(type, route, blocked) {
928
941
  return computed(() => {
929
- return toValue(type) ?? "button";
942
+ if (!toValue(route)) return toValue(type) ?? "button";
943
+ return toValue(blocked) ? "button" : void 0;
930
944
  });
931
945
  }
932
- function useButtonForm(form) {
933
- return computed(() => toValue(form));
946
+ /**
947
+ * The button's `form`, or `undefined` when it has a `route`, even while blocked, since `form`
948
+ * doesn't apply to it.
949
+ */
950
+ function useButtonForm(form, route) {
951
+ return computed(() => toValue(route) ? void 0 : toValue(form));
934
952
  }
935
953
  //#endregion
936
954
  //#region src/components/button/button.vue
@@ -966,6 +984,10 @@ var button_default = /* @__PURE__ */ defineComponent({
966
984
  type: String,
967
985
  required: false
968
986
  },
987
+ route: {
988
+ type: null,
989
+ required: false
990
+ },
969
991
  dataTestid: {
970
992
  type: String,
971
993
  required: false
@@ -977,15 +999,16 @@ var button_default = /* @__PURE__ */ defineComponent({
977
999
  const emit = __emit;
978
1000
  const variant = useButtonVariant(() => props.variant);
979
1001
  const color = useVuetifyColor(() => props.color);
980
- const type = useButtonType(() => props.type);
981
- const form = useButtonForm(() => props.form);
982
1002
  const widthClass = useFullWidth(() => props.fullWidth);
983
1003
  const isDisabled = computed(() => props.disabled || props.loading);
1004
+ const routeProps = useRouteProps(useButtonRoute(() => props.route, isDisabled));
1005
+ const type = useButtonType(() => props.type, () => props.route, isDisabled);
1006
+ const form = useButtonForm(() => props.form, () => props.route);
984
1007
  function onClick(event) {
985
1008
  emit("click", event);
986
1009
  }
987
1010
  return (_ctx, _cache) => {
988
- return openBlock(), createBlock(unref(VBtn), {
1011
+ return openBlock(), createBlock(unref(VBtn), mergeProps({
989
1012
  disabled: isDisabled.value,
990
1013
  loading: props.loading,
991
1014
  "data-testid": props.dataTestid,
@@ -993,9 +1016,11 @@ var button_default = /* @__PURE__ */ defineComponent({
993
1016
  color: unref(color),
994
1017
  type: unref(type),
995
1018
  form: unref(form),
996
- class: normalizeClass(unref(widthClass)),
1019
+ class: unref(widthClass)
1020
+ }, unref(routeProps), {
1021
+ active: false,
997
1022
  onClick
998
- }, {
1023
+ }), {
999
1024
  loader: withCtx(() => [createVNode(unref(VProgressCircular), {
1000
1025
  indeterminate: "",
1001
1026
  width: "2",
@@ -1003,7 +1028,7 @@ var button_default = /* @__PURE__ */ defineComponent({
1003
1028
  })]),
1004
1029
  default: withCtx(() => [renderSlot(_ctx.$slots, "default")]),
1005
1030
  _: 3
1006
- }, 8, [
1031
+ }, 16, [
1007
1032
  "disabled",
1008
1033
  "loading",
1009
1034
  "data-testid",
@@ -1917,79 +1942,96 @@ var date_time_field_default = /* @__PURE__ */ defineComponent({
1917
1942
  //#endregion
1918
1943
  //#region src/composables/inputs/mask-field.ts
1919
1944
  /**
1920
- * Masks of the presets. Our preset names always map to these masks, never to
1921
- * Vuetify's own presets (e.g. its American `phone`).
1945
+ * Masks of the presets, as Vuetify reads them: `#` is a digit and `N` is a
1946
+ * letter or a digit, with letters converted to uppercase. Our preset names
1947
+ * always map to these masks, never to Vuetify's own presets (e.g. its American
1948
+ * `phone`).
1922
1949
  */
1923
1950
  const maskFieldPresets = {
1924
1951
  cpf: "###.###.###-##",
1925
- cnpj: "##.###.###/####-##",
1952
+ cnpj: "NN.NNN.NNN/NNNN-##",
1926
1953
  cep: "#####-###",
1927
1954
  phone: "(##) #####-####",
1928
1955
  date: "##/##/####"
1929
1956
  };
1930
1957
  /**
1931
- * Returns the mask of a preset, or the given mask itself when it's a custom
1932
- * one.
1958
+ * Returns the mask as Vuetify reads it: the mask of a preset, or the given mask
1959
+ * itself, escaped, when it's a custom one.
1933
1960
  *
1934
1961
  * It takes any string, as the `mask` prop may get one through a cast or from
1935
1962
  * JavaScript: a string that isn't a preset is taken as the mask.
1936
1963
  */
1937
1964
  function resolveMask(mask) {
1938
- return isMaskFieldPreset(mask) ? maskFieldPresets[mask] : mask;
1965
+ return isMaskFieldPreset(mask) ? maskFieldPresets[mask] : toVuetifyMask(mask);
1939
1966
  }
1940
1967
  /**
1941
- * Returns a mask as Vuetify reads it. `#` is our only token, but Vuetify always
1942
- * adds its own tokens (`A`, `a`, `N`, `n` and `X`) and its escape (`\`), so
1943
- * these characters are escaped to stay literals.
1968
+ * Returns a custom mask as Vuetify reads it. `#` is the only token of custom
1969
+ * masks, but Vuetify always adds its own tokens (`A`, `a`, `N`, `n` and `X`)
1970
+ * and its escape (`\`), so these characters are escaped to stay literals.
1944
1971
  */
1945
1972
  function toVuetifyMask(mask) {
1946
1973
  return mask.replaceAll(/[AaNnX\\]/g, String.raw`\$&`);
1947
1974
  }
1948
1975
  /**
1949
- * Returns the default placeholder of a mask: the mask with `0` in place of each
1950
- * `#`, keeping its literals (e.g. `000.000.000-00` for `cpf`).
1976
+ * Returns the tokens of a mask as Vuetify reads it (see {@link resolveMask}),
1977
+ * in order, without its literals (e.g. `NNNNNNNNNNNN##` for `cnpj`).
1978
+ */
1979
+ function getMaskTokens(mask) {
1980
+ return mask.replaceAll(/\\.|[^#N]/g, "");
1981
+ }
1982
+ /**
1983
+ * Returns the default placeholder of a mask as Vuetify reads it (see
1984
+ * {@link resolveMask}): the mask with `0` in place of each token, keeping its
1985
+ * literals (e.g. `000.000.000-00` for `cpf` and `Nº 0000` for `Nº ####`).
1951
1986
  */
1952
1987
  function getMaskPlaceholder(mask) {
1953
- return mask.replaceAll("#", "0");
1988
+ return mask.replaceAll(/\\(.)|[#N]/g, (_token, literal) => literal ?? "0");
1954
1989
  }
1955
1990
  function isMaskFieldPreset(mask) {
1956
1991
  return Object.hasOwn(maskFieldPresets, mask);
1957
1992
  }
1958
1993
  /**
1959
- * Keeps the input masked while the user edits it, emitting only the digits
1960
- * (Vuetify's `unmask`).
1994
+ * Keeps the input masked while the user edits it, emitting only the
1995
+ * characters of the tokens (Vuetify's `unmask`), with letters in uppercase.
1961
1996
  *
1962
- * A `v-model` out of the mask's format (masked, or with characters or length
1963
- * the mask doesn't accept) is displayed normalized, without being emitted, and
1964
- * validated as it is, so a value that doesn't fit the mask fails validation.
1997
+ * A `v-model` out of the mask's format (masked, with lowercase letters, or with
1998
+ * characters or length the mask doesn't accept) is displayed normalized,
1999
+ * without being emitted, and validated as it is, so a value that doesn't fit
2000
+ * the mask fails validation.
1965
2001
  */
1966
2002
  function useMaskField(props, model) {
1967
2003
  const mask = computed(() => resolveMask(props.mask));
1968
- const digitCount = computed(() => mask.value.split("#").length - 1);
2004
+ const tokens = computed(() => getMaskTokens(mask.value));
2005
+ const acceptsLetters = computed(() => tokens.value.includes("N"));
1969
2006
  const vuetifyMask = useMask({ get mask() {
1970
2007
  return {
1971
- mask: toVuetifyMask(mask.value),
2008
+ mask: mask.value,
1972
2009
  tokens: {}
1973
2010
  };
1974
2011
  } });
1975
2012
  const displayValue = computed(() => vuetifyMask.mask(unmask(model.value)));
1976
2013
  const placeholder = computed(() => props.placeholder ?? getMaskPlaceholder(mask.value));
2014
+ const inputMode = computed(() => acceptsLetters.value ? "text" : "numeric");
2015
+ const autocapitalize = computed(() => acceptsLetters.value ? "characters" : void 0);
1977
2016
  const defaultRules = useRules(props);
1978
2017
  const rules = computed(() => [
1979
2018
  ...defaultRules.value,
1980
- (value) => !value || !vuetifyMask.isValid(value) || isComplete(value) || "Valor incompleto",
1981
- (value) => !value || vuetifyMask.isValid(value) || "Valor inválido"
2019
+ (value) => !value || !isValid(value) || isComplete(value) || "Valor incompleto",
2020
+ (value) => !value || isValid(value) || "Valor inválido"
1982
2021
  ]);
1983
2022
  function unmask(text) {
1984
2023
  return vuetifyMask.unmask(text) ?? "";
1985
2024
  }
2025
+ function isValid(value) {
2026
+ return unmask(value).toUpperCase() === unmask(vuetifyMask.mask(value));
2027
+ }
1986
2028
  function isComplete(value) {
1987
- return unmask(vuetifyMask.mask(value)).length === digitCount.value;
2029
+ return unmask(vuetifyMask.mask(value)).length === tokens.value.length;
1988
2030
  }
1989
2031
  /**
1990
2032
  * Returns the position in a masked text right after the given number of
1991
- * digits, skipping the literals after it unless the user deleted backwards
1992
- * (as Vuetify's mask input does).
2033
+ * characters of the value, skipping the literals after it unless the user
2034
+ * deleted backwards (as Vuetify's mask input does).
1993
2035
  */
1994
2036
  function findCaret(masked, valueLength, deletedBackwards) {
1995
2037
  let caret = 0;
@@ -2019,6 +2061,8 @@ function useMaskField(props, model) {
2019
2061
  return {
2020
2062
  displayValue,
2021
2063
  placeholder,
2064
+ inputMode,
2065
+ autocapitalize,
2022
2066
  rules,
2023
2067
  onInput,
2024
2068
  onCompositionEnd
@@ -2087,7 +2131,7 @@ var mask_field_default = /* @__PURE__ */ defineComponent({
2087
2131
  const modelValue = useModel(__props, "modelValue");
2088
2132
  const props = __props;
2089
2133
  const vuetifyVariant = useTextFieldVariant(() => props.variant);
2090
- const { displayValue, placeholder, rules, onInput, onCompositionEnd } = useMaskField(props, modelValue);
2134
+ const { displayValue, placeholder, inputMode, autocapitalize, rules, onInput, onCompositionEnd } = useMaskField(props, modelValue);
2091
2135
  function keepFieldControlled() {}
2092
2136
  function clear() {
2093
2137
  modelValue.value = "";
@@ -2108,7 +2152,8 @@ var mask_field_default = /* @__PURE__ */ defineComponent({
2108
2152
  hint: props.hint,
2109
2153
  clearable: props.clearable,
2110
2154
  type: "text",
2111
- inputmode: "numeric",
2155
+ inputmode: unref(inputMode),
2156
+ autocapitalize: unref(autocapitalize),
2112
2157
  "onUpdate:modelValue": keepFieldControlled,
2113
2158
  onInput: unref(onInput),
2114
2159
  onCompositionend: unref(onCompositionEnd),
@@ -2130,6 +2175,8 @@ var mask_field_default = /* @__PURE__ */ defineComponent({
2130
2175
  "placeholder",
2131
2176
  "hint",
2132
2177
  "clearable",
2178
+ "inputmode",
2179
+ "autocapitalize",
2133
2180
  "onInput",
2134
2181
  "onCompositionend"
2135
2182
  ]);
@@ -1,4 +1,4 @@
1
- import { i as FeedbackColorVariant, n as ButtonVariant, r as ColorVariant } from "./types-CjaJ2WAC.js";
1
+ import { i as FeedbackColorVariant, n as ButtonVariant, r as ColorVariant } from "./types-BbWo6RIy.js";
2
2
  import { ComputedRef, MaybeRefOrGetter } from "vue";
3
3
  //#region src/composables/dialogs/types.d.ts
4
4
  interface ToastOptions {
@@ -1,3 +1,4 @@
1
+ import { RouteLocationRaw } from "vue-router";
1
2
  //#region src/composables/colors/types.d.ts
2
3
  type ColorVariant = "primary" | "secondary" | FeedbackColorVariant;
3
4
  type FeedbackColorVariant = "positive" | "informative" | "caution" | "danger";
@@ -16,7 +17,7 @@ type ButtonProps = {
16
17
  variant?: ButtonVariant;
17
18
  /**
18
19
  * Applies a distinct behavior to the button.
19
- * One of `button` or `submit`.
20
+ * One of `button` or `submit`. Ignored when `route` is set.
20
21
  * @default "button"
21
22
  */
22
23
  type?: "submit" | "button";
@@ -49,8 +50,16 @@ type ButtonProps = {
49
50
  fullWidth?: boolean;
50
51
  /**
51
52
  * Associated [form](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/form) id.
53
+ * Ignored when `route` is set.
52
54
  */
53
55
  form?: string;
56
+ /**
57
+ * Where the button goes when clicked, rendering it as a link with the button's look. A route
58
+ * inside the app (`"/patients"`, `{ name: "patient", params: { id } }`) navigates through the
59
+ * app's router; a URL starting with `http` navigates through the browser, in the same tab. While
60
+ * `disabled` or `loading`, the button doesn't navigate and is rendered as a disabled button.
61
+ */
62
+ route?: RouteLocationRaw;
54
63
  /**
55
64
  * Component id to use on automated tests.
56
65
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexdom/uimed-vue",
3
- "version": "2.0.0-beta.42",
3
+ "version": "2.0.0-beta.44",
4
4
  "description": "A ui framework for NEXDOM applications.",
5
5
  "keywords": [
6
6
  "lib",