@nexdom/uimed-vue 2.0.0-beta.28 → 2.0.0-beta.29

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
@@ -174,7 +174,14 @@ Use an existing composable (e.g. `use-toast`) as the reference, and deliver in t
174
174
  - Never write CSS or `<style>` blocks, and expose styling to consumers only through props.
175
175
  - Adjustments that Vuetify exposes as Sass variables (e.g. a font family or a line height) go in `src/styles/settings.scss`, which `vitePluginUimed()` compiles into Vuetify's stylesheets in the apps. That file only configures variables: still no CSS rules.
176
176
  - Don't add spacing to Vuetify components: no padding or margin utility classes (`pa-*`, `pt-6`, `px-4`, `ma-2`, ...) and no spacing props overriding the defaults. Vuetify already applies Material Design 3 spacing, and overriding it breaks what it handles on its own, such as button paddings and the scrollbar of scrollable content. If the default spacing looks wrong, stop and ask instead of adjusting it. The one accepted layout margin is `mt-auto` on `v-card-actions` (as in `USection`), which pushes the actions to the bottom of a full-height card without setting any spacing value.
177
- - Prefer a Vuetify component's own props to building its parts by hand (e.g. `title`/`text` on `v-card` instead of `v-card-item` > `v-card-title` > `v-card-text`). A non-spacing utility class is acceptable only when no prop covers the need (e.g. `text-wrap` on a card title), with a comment saying why.
177
+ - Prefer a Vuetify component's own props to building its parts by hand (e.g. `title`/`text` on `v-card` instead of `v-card-item` > `v-card-title` > `v-card-text`). A non-spacing utility class is acceptable only when no prop covers the need (e.g. `text-wrap` on a card title), with a comment saying why. Props covered by the shared composables below are the exception: they always go through them.
178
+ - Reuse the shared internal composables for props that recur across components, so every component maps them to Vuetify the same way, instead of mapping them again:
179
+ - `fullWidth`/`fullHeight`: `useFullWidth`/`useFullHeight` (`src/composables/dimensions/dimensions.ts`), applying Vuetify's `w-100`/`h-100` classes even where the component has `width`/`height` props.
180
+ - `alignX`/`alignY`: `useAlignX`/`useAlignY` (`src/composables/alignment/alignment.ts`), with the `AlignX`/`AlignY` types from the group's `types.ts`.
181
+ - `color`: `useVuetifyColor` (`src/composables/colors/use-vuetify-color.ts`), with the `ColorVariant` type from the group's `types.ts`.
182
+
183
+ A new prop that does the same as one of these takes its name and type. When a second component needs a mapping that only one component has, move it into a shared composable first.
184
+
178
185
  - Keep components thin: a component wires props, slots and events to Vuetify. Logic beyond that (focus management, queues, DOM lookups, comparisons) goes into an internal composable with its own unit tests.
179
186
  - Path aliases: `@/*` → `src/*`, `@e2e/*` → `e2e/*`.
180
187
  - Every public component follows this pattern to block access to internals and give it an editor-hover description:
@@ -1,4 +1,4 @@
1
- import { t as ButtonProps } from "./types-Zex9kf7C.js";
1
+ import { t as ButtonProps } from "./types-DNT_odv7.js";
2
2
  import { RouteLocationRaw } from "vue-router";
3
3
  //#region src/components/app-bar/types.d.ts
4
4
  /**
@@ -621,6 +621,12 @@ declare const __VLS_export$12: import("vue", { with: { "resolution-mode": "impor
621
621
  * Props exposed by the {@link Container} component.
622
622
  */
623
623
  type ContainerProps = {
624
+ /**
625
+ * Stretches the container to the whole height of its parent, which must have a height of its
626
+ * own, e.g. the content area of `UMain`.
627
+ * @default false
628
+ */
629
+ fullHeight?: boolean;
624
630
  /**
625
631
  * Component id to use on automated tests.
626
632
  */
@@ -652,6 +658,18 @@ type __VLS_WithSlots$8<T, S> = T & {
652
658
  };
653
659
  };
654
660
  //#endregion
661
+ //#region src/composables/alignment/types.d.ts
662
+ /**
663
+ * Horizontal alignment of a component's items: to the start, to the center or to the end,
664
+ * following the text direction.
665
+ */
666
+ type AlignX = "start" | "center" | "end";
667
+ /**
668
+ * Vertical alignment of a component's items: to the top, to the center, to the bottom, or
669
+ * stretched to the whole height available.
670
+ */
671
+ type AlignY = "start" | "center" | "end" | "stretch";
672
+ //#endregion
655
673
  //#region src/components/grid/row/types.d.ts
656
674
  /**
657
675
  * Props exposed by the {@link Row} component.
@@ -672,7 +690,7 @@ type RowProps = {
672
690
  * whole line, so the alignment has no visible effect.
673
691
  * @default "start"
674
692
  */
675
- alignX?: RowAlignX;
693
+ alignX?: AlignX;
676
694
  /**
677
695
  * Vertical alignment of the row's columns, relative to the tallest column on the same line.
678
696
  * Available options are:
@@ -685,10 +703,21 @@ type RowProps = {
685
703
  * `USection`s with `fullHeight`.
686
704
  *
687
705
  * Columns sit side by side only from the `sm` breakpoint up. Below it, each column takes a
688
- * whole line, so the alignment has no visible effect.
706
+ * whole line, so the alignment has no visible effect, unless the row has `fullHeight`: then
707
+ * the columns, as a group, are aligned within the row's height.
689
708
  * @default "start"
690
709
  */
691
- alignY?: RowAlignY;
710
+ alignY?: AlignY;
711
+ /**
712
+ * Stretches the row to the whole height of its parent: the visible content area of `UMain`
713
+ * (below its app bar) or a `UContainer` with `fullHeight`.
714
+ *
715
+ * Combine it with `alignY` to place the columns within that height, e.g. `alignY="center"`
716
+ * and `alignX="center"` to center a login form on the screen. A row with `fullHeight` takes
717
+ * the whole height by itself, so other rows next to it are pushed below the visible area.
718
+ * @default false
719
+ */
720
+ fullHeight?: boolean;
692
721
  /**
693
722
  * Renders the row as a list (`<ul>`), without the list's default indentation
694
723
  * and bullets, so assistive technologies announce its columns as the items
@@ -702,8 +731,6 @@ type RowProps = {
702
731
  */
703
732
  dataTestid?: string;
704
733
  };
705
- type RowAlignX = "start" | "center" | "end";
706
- type RowAlignY = "start" | "center" | "end" | "stretch";
707
734
  //#endregion
708
735
  //#region src/components/grid/row/row.vue.d.ts
709
736
  /**
@@ -723,10 +750,7 @@ type RowAlignY = "start" | "center" | "end" | "stretch";
723
750
  * @see {@link https://nexdom-healthtech.github.io/uimed-vue/guide/components/layout | Row Guide}
724
751
  */
725
752
  declare const _default$13: typeof __VLS_export$10;
726
- declare const __VLS_export$10: __VLS_WithSlots$7<import("vue", { with: { "resolution-mode": "import" } }).DefineComponent<RowProps, {}, {}, {}, {}, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, {}, string, import("vue", { with: { "resolution-mode": "import" } }).PublicProps, Readonly<RowProps> & Readonly<{}>, {
727
- alignX: RowAlignX;
728
- alignY: RowAlignY;
729
- }, {}, {}, {}, string, import("vue", { with: { "resolution-mode": "import" } }).ComponentProvideOptions, false, {}, any>, {
753
+ declare const __VLS_export$10: __VLS_WithSlots$7<import("vue", { with: { "resolution-mode": "import" } }).DefineComponent<RowProps, {}, {}, {}, {}, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, import("vue", { with: { "resolution-mode": "import" } }).ComponentOptionsMixin, {}, string, import("vue", { with: { "resolution-mode": "import" } }).PublicProps, Readonly<RowProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue", { with: { "resolution-mode": "import" } }).ComponentProvideOptions, false, {}, any>, {
730
754
  default?: (props: {}) => any;
731
755
  }>;
732
756
  type __VLS_WithSlots$7<T, S> = T & {
@@ -676,24 +676,48 @@ var navigation_menu_default = /* @__PURE__ */ defineComponent({
676
676
  }
677
677
  });
678
678
  //#endregion
679
+ //#region src/composables/dimensions/dimensions.ts
680
+ /**
681
+ * Vuetify's utility class that stretches a component to the whole width of its parent, for
682
+ * components with a `fullWidth` prop.
683
+ */
684
+ function useFullWidth(fullWidth) {
685
+ return computed(() => toValue(fullWidth) ? "w-100" : void 0);
686
+ }
687
+ /**
688
+ * Vuetify's utility class that stretches a component to the whole height of its parent, for
689
+ * components with a `fullHeight` prop. The parent must have a height of its own.
690
+ */
691
+ function useFullHeight(fullHeight) {
692
+ return computed(() => toValue(fullHeight) ? "h-100" : void 0);
693
+ }
694
+ //#endregion
679
695
  //#region src/components/grid/container/container.vue
680
696
  var container_default = /* @__PURE__ */ defineComponent({
681
697
  inheritAttrs: false,
682
698
  __name: "container",
683
- props: { dataTestid: {
684
- type: String,
685
- required: false
686
- } },
699
+ props: {
700
+ fullHeight: {
701
+ type: Boolean,
702
+ required: false
703
+ },
704
+ dataTestid: {
705
+ type: String,
706
+ required: false
707
+ }
708
+ },
687
709
  setup(__props) {
688
710
  const props = __props;
711
+ const heightClass = useFullHeight(() => props.fullHeight);
689
712
  return (_ctx, _cache) => {
690
713
  return openBlock(), createBlock(unref(VContainer), {
714
+ class: normalizeClass(unref(heightClass)),
691
715
  "data-testid": props.dataTestid,
692
716
  fluid: ""
693
717
  }, {
694
718
  default: withCtx(() => [renderSlot(_ctx.$slots, "default")]),
695
719
  _: 3
696
- }, 8, ["data-testid"]);
720
+ }, 8, ["class", "data-testid"]);
697
721
  };
698
722
  }
699
723
  });
@@ -772,6 +796,10 @@ var button_default = /* @__PURE__ */ defineComponent({
772
796
  type: Boolean,
773
797
  required: false
774
798
  },
799
+ fullWidth: {
800
+ type: Boolean,
801
+ required: false
802
+ },
775
803
  form: {
776
804
  type: String,
777
805
  required: false
@@ -789,6 +817,7 @@ var button_default = /* @__PURE__ */ defineComponent({
789
817
  const color = useVuetifyColor(() => props.color);
790
818
  const type = useButtonType(() => props.type);
791
819
  const form = useButtonForm(() => props.form);
820
+ const widthClass = useFullWidth(() => props.fullWidth);
792
821
  function onClick(event) {
793
822
  emit("click", event);
794
823
  }
@@ -801,6 +830,7 @@ var button_default = /* @__PURE__ */ defineComponent({
801
830
  color: unref(color),
802
831
  type: unref(type),
803
832
  form: unref(form),
833
+ class: normalizeClass(unref(widthClass)),
804
834
  onClick
805
835
  }, {
806
836
  loader: withCtx(() => [createVNode(unref(VProgressCircular), {
@@ -817,7 +847,8 @@ var button_default = /* @__PURE__ */ defineComponent({
817
847
  "variant",
818
848
  "color",
819
849
  "type",
820
- "form"
850
+ "form",
851
+ "class"
821
852
  ]);
822
853
  };
823
854
  }
@@ -1137,7 +1168,7 @@ var main_default = /* @__PURE__ */ defineComponent({
1137
1168
  loading: props.loading
1138
1169
  }), null, 16, ["modelValue", "loading"])) : createCommentVNode("v-if", true),
1139
1170
  createVNode(unref(VMain), null, {
1140
- default: withCtx(() => [createVNode(container_default, null, {
1171
+ default: withCtx(() => [createCommentVNode(" Full height, so the page's rows and containers with `fullHeight` have a height to fill "), createVNode(container_default, { "full-height": "" }, {
1141
1172
  default: withCtx(() => [
1142
1173
  renderSlot(_ctx.$slots, "default"),
1143
1174
  createVNode(toast_default),
@@ -1668,6 +1699,45 @@ var date_time_field_default = /* @__PURE__ */ defineComponent({
1668
1699
  }
1669
1700
  });
1670
1701
  //#endregion
1702
+ //#region src/composables/alignment/alignment.ts
1703
+ const alignXToClass = {
1704
+ start: "justify-start",
1705
+ center: "justify-center",
1706
+ end: "justify-end"
1707
+ };
1708
+ const alignYToClasses = {
1709
+ start: {
1710
+ items: "align-start",
1711
+ content: "align-content-start"
1712
+ },
1713
+ center: {
1714
+ items: "align-center",
1715
+ content: "align-content-center"
1716
+ },
1717
+ end: {
1718
+ items: "align-end",
1719
+ content: "align-content-end"
1720
+ },
1721
+ stretch: {
1722
+ items: "align-stretch",
1723
+ content: "align-content-stretch"
1724
+ }
1725
+ };
1726
+ /**
1727
+ * Vuetify's `justify-*` utility class for components with an `alignX` prop, laid out with flexbox.
1728
+ * Falls back to `"start"`.
1729
+ */
1730
+ function useAlignX(alignX) {
1731
+ return computed(() => alignXToClass[toValue(alignX) ?? "start"]);
1732
+ }
1733
+ /**
1734
+ * Vuetify's `align-*` and `align-content-*` utility classes for components with an `alignY` prop,
1735
+ * laid out with flexbox. Falls back to `"start"`.
1736
+ */
1737
+ function useAlignY(alignY) {
1738
+ return computed(() => alignYToClasses[toValue(alignY) ?? "start"]);
1739
+ }
1740
+ //#endregion
1671
1741
  //#region src/components/grid/row/row.vue
1672
1742
  var row_default = /* @__PURE__ */ defineComponent({
1673
1743
  inheritAttrs: false,
@@ -1675,13 +1745,15 @@ var row_default = /* @__PURE__ */ defineComponent({
1675
1745
  props: {
1676
1746
  alignX: {
1677
1747
  type: String,
1678
- required: false,
1679
- default: "start"
1748
+ required: false
1680
1749
  },
1681
1750
  alignY: {
1682
1751
  type: String,
1683
- required: false,
1684
- default: "start"
1752
+ required: false
1753
+ },
1754
+ fullHeight: {
1755
+ type: Boolean,
1756
+ required: false
1685
1757
  },
1686
1758
  list: {
1687
1759
  type: Boolean,
@@ -1694,27 +1766,20 @@ var row_default = /* @__PURE__ */ defineComponent({
1694
1766
  },
1695
1767
  setup(__props) {
1696
1768
  const props = __props;
1697
- const rowAlignXToClass = {
1698
- start: "justify-start",
1699
- center: "justify-center",
1700
- end: "justify-end"
1701
- };
1702
- const rowAlignYToClass = {
1703
- start: "align-start",
1704
- center: "align-center",
1705
- end: "align-end",
1706
- stretch: "align-stretch"
1707
- };
1708
- const alignXClass = computed(() => rowAlignXToClass[props.alignX]);
1709
- const alignYClass = computed(() => rowAlignYToClass[props.alignY]);
1769
+ const alignXClass = useAlignX(() => props.alignX);
1770
+ const alignYClasses = useAlignY(() => props.alignY);
1771
+ const heightClass = useFullHeight(() => props.fullHeight);
1772
+ const alignContentClass = computed(() => props.fullHeight ? alignYClasses.value.content : void 0);
1710
1773
  const tag = computed(() => props.list ? "ul" : void 0);
1711
1774
  const listClasses = computed(() => props.list ? "pa-0 ma-0" : void 0);
1712
1775
  return (_ctx, _cache) => {
1713
1776
  return openBlock(), createBlock(unref(VRow), {
1714
1777
  tag: tag.value,
1715
1778
  class: normalizeClass([
1716
- alignXClass.value,
1717
- alignYClass.value,
1779
+ unref(alignXClass),
1780
+ unref(alignYClasses).items,
1781
+ unref(heightClass),
1782
+ alignContentClass.value,
1718
1783
  listClasses.value
1719
1784
  ]),
1720
1785
  "data-testid": props.dataTestid
@@ -1902,25 +1967,27 @@ var section_default = /* @__PURE__ */ defineComponent({
1902
1967
  const textAlignClasses = useSectionTextAlign(() => props.textAlign);
1903
1968
  const title = computed(() => props.title || void 0);
1904
1969
  const subtitle = computed(() => props.subtitle || void 0);
1905
- const width = computed(() => props.fullWidth ? "100%" : void 0);
1906
- const height = computed(() => props.fullHeight ? "100%" : void 0);
1970
+ const widthClass = useFullWidth(() => props.fullWidth);
1971
+ const heightClass = useFullHeight(() => props.fullHeight);
1907
1972
  const showActions = computed(() => !!props.actions?.length);
1908
1973
  const skeletonType = computed(() => showActions.value ? "image, actions" : "image");
1909
1974
  return (_ctx, _cache) => {
1910
1975
  return openBlock(), createBlock(unref(VSkeletonLoader), {
1911
1976
  loading: props.loading,
1912
1977
  type: skeletonType.value,
1913
- width: width.value,
1914
- height: height.value
1978
+ class: normalizeClass([unref(widthClass), unref(heightClass)])
1915
1979
  }, {
1916
1980
  default: withCtx(() => [createCommentVNode(" The card and its actions have no alignment props, so `textAlign` uses utility classes "), createVNode(unref(VCard), {
1917
1981
  title: title.value,
1918
1982
  subtitle: subtitle.value,
1919
1983
  variant: unref(variant),
1920
- width: width.value,
1921
- height: height.value,
1922
1984
  "data-testid": props.dataTestid,
1923
- class: normalizeClass(["d-flex flex-column", unref(textAlignClasses).content])
1985
+ class: normalizeClass([
1986
+ "d-flex flex-column",
1987
+ unref(widthClass),
1988
+ unref(heightClass),
1989
+ unref(textAlignClasses).content
1990
+ ])
1924
1991
  }, {
1925
1992
  default: withCtx(() => [renderSlot(_ctx.$slots, "default"), showActions.value ? (openBlock(), createBlock(unref(VCardActions), {
1926
1993
  key: 0,
@@ -1939,8 +2006,6 @@ var section_default = /* @__PURE__ */ defineComponent({
1939
2006
  "title",
1940
2007
  "subtitle",
1941
2008
  "variant",
1942
- "width",
1943
- "height",
1944
2009
  "data-testid",
1945
2010
  "class"
1946
2011
  ])]),
@@ -1948,8 +2013,7 @@ var section_default = /* @__PURE__ */ defineComponent({
1948
2013
  }, 8, [
1949
2014
  "loading",
1950
2015
  "type",
1951
- "width",
1952
- "height"
2016
+ "class"
1953
2017
  ]);
1954
2018
  };
1955
2019
  }
@@ -1,4 +1,4 @@
1
- import { i as FeedbackColorVariant, n as ButtonVariant, r as ColorVariant } from "./types-Zex9kf7C.js";
1
+ import { i as FeedbackColorVariant, n as ButtonVariant, r as ColorVariant } from "./types-DNT_odv7.js";
2
2
  //#region src/composables/dialogs/types.d.ts
3
3
  interface ToastOptions {
4
4
  /**
@@ -38,6 +38,12 @@ type ButtonProps = {
38
38
  * @default false
39
39
  */
40
40
  loading?: boolean;
41
+ /**
42
+ * Stretches the button to the whole width of its parent, e.g. the only action of a form on a
43
+ * small screen.
44
+ * @default false
45
+ */
46
+ fullWidth?: boolean;
41
47
  /**
42
48
  * Associated [form](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/form) id.
43
49
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexdom/uimed-vue",
3
- "version": "2.0.0-beta.28",
3
+ "version": "2.0.0-beta.29",
4
4
  "description": "A ui framework for NEXDOM applications.",
5
5
  "keywords": [
6
6
  "lib",