@nexdom/uimed-vue 2.0.0-beta.20 → 2.0.0-beta.22

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
@@ -22,11 +22,11 @@ pnpm add @nexdom/uimed-vue
22
22
  yarn add @nexdom/uimed-vue
23
23
  ```
24
24
 
25
- Peer dependencies (`@fontsource/roboto`, `@mdi/font`, `@nexdom/shared`, `resize-observer-polyfill`, `vite-plugin-vuetify`, `vite-plus`, `vue`, `vue-router`, `vuetify`) must be installed alongside it — most package managers install these automatically, but confirm versions match what's declared in `package.json`'s `peerDependencies`.
25
+ Peer dependencies (`@mdi/font`, `@nexdom/shared`, `resize-observer-polyfill`, `sass-embedded`, `vite-plus`, `vue`, `vue-component-type-helpers`, `vue-router`, `vuetify`) must be installed alongside it — most package managers install these automatically, but confirm versions match what's declared in `package.json`'s `peerDependencies`.
26
26
 
27
27
  ### Setup
28
28
 
29
- Add the Vite plugin:
29
+ Add the Vite plugin, which compiles the components' styles with the library's font and Sass settings (without it, components lose the Unimed Slab font):
30
30
 
31
31
  ```ts
32
32
  // vite.config.ts
@@ -112,12 +112,12 @@ On Windows, prefer VSCode's "Dev Containers: Clone Repository in Container Volum
112
112
  - `vpr check` — lint, formatter, and type-check (builds the library first, since the type-check reads `dist`)
113
113
  - `vp test --coverage` — unit tests with coverage (Vitest, jsdom, 100% coverage threshold enforced)
114
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
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. A running `vitepress preview` keeps serving the build it started with, so restart it after rebuilding the docs
116
116
  - `vpr depcruise` — architecture/dependency rules (dependency-cruiser)
117
117
  - `vp pack` / `vpr build` — build the library
118
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)
119
119
 
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.
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`, and then the SonarQube analysis (`vpr sonar`), whose quality gate fails the PR on any issue or unreviewed security hotspot in `src/` (see [Static analysis](#static-analysis-sonarqube)). Match this locally before opening a PR.
121
121
 
122
122
  ### Focused runs while iterating
123
123
 
@@ -139,6 +139,12 @@ vpx stryker run --mutate "src/components/button/button.vue,src/composables/butto
139
139
  vpr test:e2e e2e/components/button.spec.ts
140
140
  ```
141
141
 
142
+ ### Static analysis (SonarQube)
143
+
144
+ The company's SonarQube runs only in CI, after the E2E tests. Write code that avoids the rules that have already failed PRs here: use `globalThis` instead of `window`; move inner functions that don't use anything from the outer function out of it (S7721); don't use deprecated APIs (e.g. `event.returnValue`); don't write template comments that look like code; don't add non-null assertions or casts that don't change the type (S4325); no regular expressions with super-linear backtracking, such as two overlapping quantifiers (S5852); and prefer native elements to `role` attributes (S6819).
145
+
146
+ Only if CI's SonarQube step fails and its dashboard doesn't show why, reproduce the analysis with a local SonarQube Community in the server's version (`sonarqube:26.3.0.120487-community`) and `vpx @sonar/scan` pointed at it. The goal is 0 issues and no new security hotspots in the files the PR changed.
147
+
142
148
  ## Adding a new component
143
149
 
144
150
  Use an existing component (e.g. `button`) as the reference, and deliver all of the following in the same PR:
@@ -166,6 +172,7 @@ Use an existing composable (e.g. `use-toast`) as the reference, and deliver in t
166
172
  - `docs` must not mention Vuetify
167
173
  - 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`).
168
174
  - Never write CSS or `<style>` blocks, and expose styling to consumers only through props.
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.
169
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.
170
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.
171
178
  - 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.
@@ -204,6 +211,9 @@ Use an existing composable (e.g. `use-toast`) as the reference, and deliver in t
204
211
  - 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.
205
212
  - 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.
206
213
  - Before keeping a new screenshot as a baseline, open it and check it against how plain Vuetify renders the same component: overflowing content shows its scrollbar, buttons and cards keep their native paddings, nothing touches the edges or gets clipped. A screenshot that only "looks fine" isn't enough: name what you checked in your report.
214
+ - Generate and update screenshots only on Linux, as CI does: in the Dev Container, or in the Playwright Docker image of the version in `package.json` (`mcr.microsoft.com/playwright:v<version>-noble`). Screenshots taken on Windows or macOS render fonts differently and fail in CI.
215
+ - Update baselines with `--update-snapshots=changed`, scoped to the spec you changed. `--update-snapshots=all` also rewrites accessible snapshots (`.aria.yml`) that didn't need to change.
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.
207
217
  - Never run Stryker at the same time as another test command: they share build output and Stryker fails with `ENOENT`.
208
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.
209
219
  - 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`).
@@ -651,6 +651,22 @@ type __VLS_WithSlots$8<T, S> = T & {
651
651
  * Props exposed by the {@link Row} component.
652
652
  */
653
653
  type RowProps = {
654
+ /**
655
+ * Horizontal alignment of the row's columns on each line.
656
+ * Available options are:
657
+ * - `"start"`: aligns the columns to the start of the line;
658
+ * - `"center"`: centers the columns horizontally;
659
+ * - `"end"`: aligns the columns to the end of the line.
660
+ *
661
+ * `"start"` and `"end"` follow the text direction. It only has a visible effect when the
662
+ * columns of a line don't fill it, e.g. with `cols="auto"` or sizes adding up to less than 12.
663
+ * When the columns wrap into several lines, each line is aligned on its own.
664
+ *
665
+ * Columns sit side by side only from the `sm` breakpoint up. Below it, each column takes a
666
+ * whole line, so the alignment has no visible effect.
667
+ * @default "start"
668
+ */
669
+ alignX?: RowAlignX;
654
670
  /**
655
671
  * Vertical alignment of the row's columns, relative to the tallest column on the same line.
656
672
  * Available options are:
@@ -666,7 +682,7 @@ type RowProps = {
666
682
  * whole line, so the alignment has no visible effect.
667
683
  * @default "start"
668
684
  */
669
- align?: RowAlign;
685
+ alignY?: RowAlignY;
670
686
  /**
671
687
  * Renders the row as a list (`<ul>`), without the list's default indentation
672
688
  * and bullets, so assistive technologies announce its columns as the items
@@ -680,7 +696,8 @@ type RowProps = {
680
696
  */
681
697
  dataTestid?: string;
682
698
  };
683
- type RowAlign = "start" | "center" | "end" | "stretch";
699
+ type RowAlignX = "start" | "center" | "end";
700
+ type RowAlignY = "start" | "center" | "end" | "stretch";
684
701
  //#endregion
685
702
  //#region src/components/grid/row/row.vue.d.ts
686
703
  /**
@@ -690,7 +707,7 @@ type RowAlign = "start" | "center" | "end" | "stretch";
690
707
  * ```vue
691
708
  * <template>
692
709
  * <u-container>
693
- * <u-row align="center">
710
+ * <u-row align-x="center" align-y="center">
694
711
  * <!-- uimed-column -->
695
712
  * </u-row>
696
713
  * </u-container>
@@ -701,7 +718,8 @@ type RowAlign = "start" | "center" | "end" | "stretch";
701
718
  */
702
719
  declare const _default$12: typeof __VLS_export$9;
703
720
  declare const __VLS_export$9: __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<{}>, {
704
- align: RowAlign;
721
+ alignX: RowAlignX;
722
+ alignY: RowAlignY;
705
723
  }, {}, {}, {}, string, import("vue", { with: { "resolution-mode": "import" } }).ComponentProvideOptions, false, {}, any>, {
706
724
  default?: (props: {}) => any;
707
725
  }>;
@@ -888,7 +906,7 @@ type SectionProps = {
888
906
  /**
889
907
  * Makes the section occupy 100% of its parent's height.
890
908
  *
891
- * Inside a `URow`, set its `align` to `"stretch"` so the section takes the height of the
909
+ * Inside a `URow`, set its `alignY` to `"stretch"` so the section takes the height of the
892
910
  * tallest column on the same line.
893
911
  * @default false
894
912
  */
@@ -1470,7 +1470,12 @@ var row_default = /* @__PURE__ */ defineComponent({
1470
1470
  inheritAttrs: false,
1471
1471
  __name: "row",
1472
1472
  props: {
1473
- align: {
1473
+ alignX: {
1474
+ type: String,
1475
+ required: false,
1476
+ default: "start"
1477
+ },
1478
+ alignY: {
1474
1479
  type: String,
1475
1480
  required: false,
1476
1481
  default: "start"
@@ -1486,19 +1491,29 @@ var row_default = /* @__PURE__ */ defineComponent({
1486
1491
  },
1487
1492
  setup(__props) {
1488
1493
  const props = __props;
1489
- const rowAlignToClass = {
1494
+ const rowAlignXToClass = {
1495
+ start: "justify-start",
1496
+ center: "justify-center",
1497
+ end: "justify-end"
1498
+ };
1499
+ const rowAlignYToClass = {
1490
1500
  start: "align-start",
1491
1501
  center: "align-center",
1492
1502
  end: "align-end",
1493
1503
  stretch: "align-stretch"
1494
1504
  };
1495
- const alignClass = computed(() => rowAlignToClass[props.align]);
1505
+ const alignXClass = computed(() => rowAlignXToClass[props.alignX]);
1506
+ const alignYClass = computed(() => rowAlignYToClass[props.alignY]);
1496
1507
  const tag = computed(() => props.list ? "ul" : void 0);
1497
1508
  const listClasses = computed(() => props.list ? "pa-0 ma-0" : void 0);
1498
1509
  return (_ctx, _cache) => {
1499
1510
  return openBlock(), createBlock(unref(VRow), {
1500
1511
  tag: tag.value,
1501
- class: normalizeClass([alignClass.value, listClasses.value]),
1512
+ class: normalizeClass([
1513
+ alignXClass.value,
1514
+ alignYClass.value,
1515
+ listClasses.value
1516
+ ]),
1502
1517
  "data-testid": props.dataTestid
1503
1518
  }, {
1504
1519
  default: withCtx(() => [renderSlot(_ctx.$slots, "default")]),
@@ -2077,7 +2092,7 @@ var data_set_default = /* @__PURE__ */ defineComponent({
2077
2092
  }, null, 8, ["modelValue"])) : createCommentVNode("v-if", true)]),
2078
2093
  default: withCtx(({ items: pageItems }) => [createCommentVNode(" Stretched, so the items of the same line (e.g. full-height cards) share its height "), createVNode(row_default, {
2079
2094
  list: "",
2080
- align: "stretch"
2095
+ "align-y": "stretch"
2081
2096
  }, {
2082
2097
  default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(pageItems, (pageItem, index) => {
2083
2098
  return openBlock(), createBlock(column_default, {
package/dist/index.js CHANGED
@@ -26,10 +26,7 @@ function createUimed() {
26
26
  error: "#D92D20",
27
27
  info: "#9ACBE5"
28
28
  },
29
- variables: {
30
- "border-color": "#D0D5DD",
31
- "font-body": "\"Unimed Slab\", sans-serif"
32
- }
29
+ variables: { "border-color": "#D0D5DD" }
33
30
  } }
34
31
  }
35
32
  });
package/dist/plugins.d.ts CHANGED
@@ -1,10 +1,21 @@
1
- import vitePluginVuetify from "vite-plugin-vuetify";
2
- import { UserConfig } from "vite-plus";
1
+ import { Plugin, UserConfig } from "vite-plus";
3
2
  //#region src/plugins.d.ts
4
3
  /**
5
- * Vite plugin for UIMed.
4
+ * Vite plugin that compiles the components' styles with the library's font and Sass settings.
5
+ * Required: without it, the components lose the Unimed Slab font. Needs `sass-embedded`, a peer
6
+ * dependency.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * // vite.config.ts
11
+ * import { vitePluginUimed } from "@nexdom/uimed-vue/plugins";
12
+ *
13
+ * export default defineConfig({
14
+ * plugins: [vue(), vitePluginUimed()],
15
+ * });
16
+ * ```
6
17
  */
7
- export declare function vitePluginUimed(): ReturnType<typeof vitePluginVuetify>;
18
+ export declare function vitePluginUimed(): Plugin[];
8
19
  /**
9
20
  * Vitest Server plugin for UIMed.
10
21
  */
package/dist/plugins.js CHANGED
@@ -1,10 +1,61 @@
1
- import vitePluginVuetify from "vite-plugin-vuetify";
1
+ import { existsSync } from "node:fs";
2
+ import { pathToFileURL } from "node:url";
2
3
  //#region src/plugins.ts
4
+ /** Matches the ids of CSS files, with or without a query */
5
+ const stylesheet = /\.css(?:\?|$)/;
6
+ /** Path of Vuetify's stylesheets folder, inside the `node_modules` folder where it's installed */
7
+ const vuetifyStylesheets = "/node_modules/vuetify/lib/";
3
8
  /**
4
- * Vite plugin for UIMed.
9
+ * Matches the queries that import a stylesheet's file as is (`?raw`) or its URL (`?url`), which
10
+ * Vite doesn't compile. The URL still serves the compiled stylesheet, which Vite loads separately
11
+ */
12
+ const uncompiledStylesheetQuery = /[?&](?:raw|url)\b/;
13
+ /**
14
+ * Vite plugin that compiles the components' styles with the library's font and Sass settings.
15
+ * Required: without it, the components lose the Unimed Slab font. Needs `sass-embedded`, a peer
16
+ * dependency.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * // vite.config.ts
21
+ * import { vitePluginUimed } from "@nexdom/uimed-vue/plugins";
22
+ *
23
+ * export default defineConfig({
24
+ * plugins: [vue(), vitePluginUimed()],
25
+ * });
26
+ * ```
5
27
  */
6
28
  function vitePluginUimed() {
7
- return vitePluginVuetify({ autoImport: false });
29
+ const settings = new URL("./styles/settings.scss", import.meta.url);
30
+ let compiler;
31
+ return [{
32
+ name: "uimed:vuetify-styles",
33
+ enforce: "pre",
34
+ load: {
35
+ filter: { id: {
36
+ include: stylesheet,
37
+ exclude: uncompiledStylesheetQuery
38
+ } },
39
+ async handler(id) {
40
+ const file = id.split("?")[0].replaceAll("\\", "/");
41
+ const folder = file.lastIndexOf(vuetifyStylesheets);
42
+ if (folder === -1) return null;
43
+ const nodeModules = `${file.slice(0, folder)}/node_modules`;
44
+ const source = [".sass", ".scss"].map((extension) => `${file.slice(0, -4)}${extension}`).find((path) => existsSync(path));
45
+ if (!source) return null;
46
+ compiler ??= import("sass-embedded").then((sass) => sass.initAsyncCompiler());
47
+ const sass = await compiler;
48
+ const entry = `@use "${settings.href}";\n@use "${pathToFileURL(source).href}";`;
49
+ const { css } = await sass.compileStringAsync(entry, { loadPaths: [nodeModules] });
50
+ return css;
51
+ }
52
+ },
53
+ async closeBundle() {
54
+ const current = compiler;
55
+ compiler = void 0;
56
+ await (await current)?.dispose();
57
+ }
58
+ }];
8
59
  }
9
60
  /**
10
61
  * Vitest Server plugin for UIMed.
@@ -0,0 +1,6 @@
1
+ @use "vuetify/settings" with (
2
+ $body-font-family: var(--v-font-body, "Unimed Slab", sans-serif),
3
+ // Unimed Slab's ascent and descent take 1.26em, more than the subtitles' default 1rem line
4
+ // height, which clipped its descenders and accented capitals
5
+ $list-item-subtitle-line-height: normal
6
+ );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexdom/uimed-vue",
3
- "version": "2.0.0-beta.20",
3
+ "version": "2.0.0-beta.22",
4
4
  "description": "A ui framework for NEXDOM applications.",
5
5
  "keywords": [
6
6
  "lib",
@@ -75,7 +75,6 @@
75
75
  "sass-embedded": "^1.103.1",
76
76
  "stryker-vue-ignorer": "github:davidlandais/stryker-vue-ignorer",
77
77
  "typescript": "^6.0.3",
78
- "vite-plugin-vuetify": "^2.1.3",
79
78
  "vite-plus": "^1.0.0",
80
79
  "vitepress": "2.0.0-alpha.19",
81
80
  "vitepress-plugin-group-icons": "^1.7.6",
@@ -90,7 +89,7 @@
90
89
  "@mdi/font": "^7.4.47",
91
90
  "@nexdom/shared": "^2.1.1",
92
91
  "resize-observer-polyfill": "^1.5.1",
93
- "vite-plugin-vuetify": "^2.1.3",
92
+ "sass-embedded": "^1.103.1",
94
93
  "vite-plus": "^1.0.0",
95
94
  "vue": "^3.5.40",
96
95
  "vue-component-type-helpers": "^3.3.11",