@nexdom/uimed-vue 2.0.0-beta.21 → 2.0.0-beta.23

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
@@ -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:
@@ -205,6 +211,9 @@ Use an existing composable (e.g. `use-toast`) as the reference, and deliver in t
205
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.
206
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.
207
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.
208
217
  - Never run Stryker at the same time as another test command: they share build output and Stryker fails with `ENOENT`.
209
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.
210
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`).
@@ -254,7 +254,9 @@ type TextFieldProps = {
254
254
  */
255
255
  disabled?: boolean;
256
256
  /**
257
- * Displays a loading indicator on the input.
257
+ * Displays a loading indicator on the input. The field keeps its label as
258
+ * accessible name and is marked as busy, and assistive technologies ignore
259
+ * the indicator.
258
260
  * @default false
259
261
  */
260
262
  loading?: boolean;
@@ -317,7 +319,9 @@ type AutocompleteFieldProps<T = string, Multiple extends boolean = false> = {
317
319
  */
318
320
  disabled?: boolean;
319
321
  /**
320
- * Displays a loading indicator on the input.
322
+ * Displays a loading indicator on the input. The field keeps its label as
323
+ * accessible name and is marked as busy, and assistive technologies ignore
324
+ * the indicator.
321
325
  * @default false
322
326
  */
323
327
  loading?: boolean;
@@ -541,7 +545,9 @@ type DateTimeFieldProps = {
541
545
  */
542
546
  disabled?: boolean;
543
547
  /**
544
- * Displays a loading indicator on the input.
548
+ * Displays a loading indicator on the input. The field keeps its label as
549
+ * accessible name and is marked as busy, and assistive technologies ignore
550
+ * the indicator.
545
551
  * @default false
546
552
  */
547
553
  loading?: boolean;
@@ -651,6 +657,22 @@ type __VLS_WithSlots$8<T, S> = T & {
651
657
  * Props exposed by the {@link Row} component.
652
658
  */
653
659
  type RowProps = {
660
+ /**
661
+ * Horizontal alignment of the row's columns on each line.
662
+ * Available options are:
663
+ * - `"start"`: aligns the columns to the start of the line;
664
+ * - `"center"`: centers the columns horizontally;
665
+ * - `"end"`: aligns the columns to the end of the line.
666
+ *
667
+ * `"start"` and `"end"` follow the text direction. It only has a visible effect when the
668
+ * columns of a line don't fill it, e.g. with `cols="auto"` or sizes adding up to less than 12.
669
+ * When the columns wrap into several lines, each line is aligned on its own.
670
+ *
671
+ * Columns sit side by side only from the `sm` breakpoint up. Below it, each column takes a
672
+ * whole line, so the alignment has no visible effect.
673
+ * @default "start"
674
+ */
675
+ alignX?: RowAlignX;
654
676
  /**
655
677
  * Vertical alignment of the row's columns, relative to the tallest column on the same line.
656
678
  * Available options are:
@@ -666,7 +688,7 @@ type RowProps = {
666
688
  * whole line, so the alignment has no visible effect.
667
689
  * @default "start"
668
690
  */
669
- align?: RowAlign;
691
+ alignY?: RowAlignY;
670
692
  /**
671
693
  * Renders the row as a list (`<ul>`), without the list's default indentation
672
694
  * and bullets, so assistive technologies announce its columns as the items
@@ -680,7 +702,8 @@ type RowProps = {
680
702
  */
681
703
  dataTestid?: string;
682
704
  };
683
- type RowAlign = "start" | "center" | "end" | "stretch";
705
+ type RowAlignX = "start" | "center" | "end";
706
+ type RowAlignY = "start" | "center" | "end" | "stretch";
684
707
  //#endregion
685
708
  //#region src/components/grid/row/row.vue.d.ts
686
709
  /**
@@ -690,7 +713,7 @@ type RowAlign = "start" | "center" | "end" | "stretch";
690
713
  * ```vue
691
714
  * <template>
692
715
  * <u-container>
693
- * <u-row align="center">
716
+ * <u-row align-x="center" align-y="center">
694
717
  * <!-- uimed-column -->
695
718
  * </u-row>
696
719
  * </u-container>
@@ -701,7 +724,8 @@ type RowAlign = "start" | "center" | "end" | "stretch";
701
724
  */
702
725
  declare const _default$12: typeof __VLS_export$9;
703
726
  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;
727
+ alignX: RowAlignX;
728
+ alignY: RowAlignY;
705
729
  }, {}, {}, {}, string, import("vue", { with: { "resolution-mode": "import" } }).ComponentProvideOptions, false, {}, any>, {
706
730
  default?: (props: {}) => any;
707
731
  }>;
@@ -888,7 +912,7 @@ type SectionProps = {
888
912
  /**
889
913
  * Makes the section occupy 100% of its parent's height.
890
914
  *
891
- * Inside a `URow`, set its `align` to `"stretch"` so the section takes the height of the
915
+ * Inside a `URow`, set its `alignY` to `"stretch"` so the section takes the height of the
892
916
  * tallest column on the same line.
893
917
  * @default false
894
918
  */
@@ -1,7 +1,7 @@
1
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, inject, isRef, mergeModels, mergeProps, normalizeClass, onMounted, onScopeDispose, onUnmounted, openBlock, provide, ref, renderList, renderSlot, resolveDynamicComponent, shallowRef, toDisplayString, toValue, unref, useId, useModel, useSlots, useTemplateRef, watch, withCtx, withModifiers } from "vue";
2
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createSlots, createTextVNode, createVNode, defineComponent, guardReactiveProps, inject, isRef, mergeModels, mergeProps, normalizeClass, normalizeProps, onMounted, onScopeDispose, onUnmounted, openBlock, provide, ref, renderList, renderSlot, resolveDynamicComponent, shallowRef, toDisplayString, toValue, unref, useId, useModel, useSlots, useTemplateRef, watch, withCtx, withModifiers } from "vue";
3
3
  import { pt } from "vuetify/locale";
4
- import { VApp, VAppBar, VAppBarNavIcon, VAppBarTitle, VAutocomplete, VAvatar, VBadge, VBtn, VCard, VCardActions, VCardText, VCheckboxBtn, VCol, VCombobox, VContainer, VDataIterator, VDatePicker, VDialog, VDivider, VEmptyState, VExpansionPanel, VExpansionPanelText, VExpansionPanelTitle, VExpansionPanels, VForm, VIcon, VIconBtn, VImg, VList, VListGroup, VListItem, VListItemAction, VListSubheader, VLocaleProvider, VMain, VMenu, VNavigationDrawer, VPagination, VRow, VSkeletonLoader, VSnackbarQueue, VTable, VTextField, VTimePicker } from "vuetify/components";
4
+ import { VApp, VAppBar, VAppBarNavIcon, VAppBarTitle, VAutocomplete, VAvatar, VBadge, VBtn, VCard, VCardActions, VCardText, VCheckboxBtn, VCol, VCombobox, VContainer, VDataIterator, VDatePicker, VDialog, VDivider, VEmptyState, VExpansionPanel, VExpansionPanelText, VExpansionPanelTitle, VExpansionPanels, VForm, VIcon, VIconBtn, VImg, VList, VListGroup, VListItem, VListItemAction, VListSubheader, VLocaleProvider, VMain, VMenu, VNavigationDrawer, VPagination, VProgressLinear, VRow, VSkeletonLoader, VSnackbarQueue, VTable, VTextField, VTimePicker } from "vuetify/components";
5
5
  import { formatDateTime, isEmail, isEmpty, isPhone, isUrl } from "@nexdom/shared/utils";
6
6
  //#region src/composables/navigation/use-route-props.ts
7
7
  function useRouteProps(route) {
@@ -315,6 +315,34 @@ function associatedRuleType(type) {
315
315
  return type in textFieldTypeToVuetifyRule;
316
316
  }
317
317
  //#endregion
318
+ //#region src/components/inputs/field-loader.vue
319
+ var field_loader_default = /* @__PURE__ */ defineComponent({
320
+ inheritAttrs: false,
321
+ __name: "field-loader",
322
+ props: {
323
+ color: {
324
+ type: String,
325
+ required: false
326
+ },
327
+ isActive: {
328
+ type: Boolean,
329
+ required: true
330
+ }
331
+ },
332
+ setup(__props) {
333
+ const props = __props;
334
+ return (_ctx, _cache) => {
335
+ return openBlock(), createBlock(unref(VProgressLinear), {
336
+ active: props.isActive,
337
+ color: props.color,
338
+ height: "2",
339
+ indeterminate: "",
340
+ "aria-hidden": "true"
341
+ }, null, 8, ["active", "color"]);
342
+ };
343
+ }
344
+ });
345
+ //#endregion
318
346
  //#region src/components/inputs/text-field/text-field.vue
319
347
  var text_field_default = /* @__PURE__ */ defineComponent({
320
348
  inheritAttrs: false,
@@ -398,13 +426,17 @@ var text_field_default = /* @__PURE__ */ defineComponent({
398
426
  "data-testid": props.dataTestid,
399
427
  disabled: props.disabled,
400
428
  loading: props.loading,
429
+ "aria-busy": props.loading || void 0,
401
430
  readonly: props.readonly,
402
431
  label: props.label,
403
432
  placeholder: props.placeholder,
404
433
  hint: props.hint,
405
434
  clearable: computedClearable.value,
406
435
  "onClick:clear": clear
407
- }, null, 8, [
436
+ }, {
437
+ loader: withCtx((loader) => [createVNode(field_loader_default, normalizeProps(guardReactiveProps(loader)), null, 16)]),
438
+ _: 1
439
+ }, 8, [
408
440
  "modelValue",
409
441
  "variant",
410
442
  "type",
@@ -413,6 +445,7 @@ var text_field_default = /* @__PURE__ */ defineComponent({
413
445
  "data-testid",
414
446
  "disabled",
415
447
  "loading",
448
+ "aria-busy",
416
449
  "readonly",
417
450
  "label",
418
451
  "placeholder",
@@ -1115,6 +1148,7 @@ var autocomplete_field_default = /* @__PURE__ */ defineComponent({
1115
1148
  "data-testid": props.dataTestid,
1116
1149
  disabled: props.disabled,
1117
1150
  loading: props.loading,
1151
+ "aria-busy": props.loading || void 0,
1118
1152
  readonly: props.readonly,
1119
1153
  label: props.label,
1120
1154
  placeholder: props.placeholder,
@@ -1124,13 +1158,17 @@ var autocomplete_field_default = /* @__PURE__ */ defineComponent({
1124
1158
  "item-title": "label",
1125
1159
  rules: unref(rules),
1126
1160
  chips: ""
1127
- }, null, 8, [
1161
+ }, {
1162
+ loader: withCtx((loader) => [createVNode(field_loader_default, normalizeProps(guardReactiveProps(loader)), null, 16)]),
1163
+ _: 1
1164
+ }, 8, [
1128
1165
  "modelValue",
1129
1166
  "variant",
1130
1167
  "items",
1131
1168
  "data-testid",
1132
1169
  "disabled",
1133
1170
  "loading",
1171
+ "aria-busy",
1134
1172
  "readonly",
1135
1173
  "label",
1136
1174
  "placeholder",
@@ -1396,6 +1434,7 @@ var date_time_field_default = /* @__PURE__ */ defineComponent({
1396
1434
  "data-testid": props.dataTestid,
1397
1435
  disabled: props.disabled,
1398
1436
  loading: props.loading,
1437
+ "aria-busy": props.loading || void 0,
1399
1438
  label: props.label,
1400
1439
  placeholder: props.placeholder,
1401
1440
  hint: props.hint,
@@ -1403,6 +1442,7 @@ var date_time_field_default = /* @__PURE__ */ defineComponent({
1403
1442
  readonly: "",
1404
1443
  "onClick:clear": clear
1405
1444
  }, {
1445
+ loader: withCtx((loader) => [createVNode(field_loader_default, normalizeProps(guardReactiveProps(loader)), null, 16)]),
1406
1446
  default: withCtx(() => [createVNode(unref(VMenu), {
1407
1447
  modelValue: isMenuOpen.value,
1408
1448
  "onUpdate:modelValue": _cache[0] || (_cache[0] = ($event) => isMenuOpen.value = $event),
@@ -1456,6 +1496,7 @@ var date_time_field_default = /* @__PURE__ */ defineComponent({
1456
1496
  "data-testid",
1457
1497
  "disabled",
1458
1498
  "loading",
1499
+ "aria-busy",
1459
1500
  "label",
1460
1501
  "placeholder",
1461
1502
  "hint",
@@ -1470,7 +1511,12 @@ var row_default = /* @__PURE__ */ defineComponent({
1470
1511
  inheritAttrs: false,
1471
1512
  __name: "row",
1472
1513
  props: {
1473
- align: {
1514
+ alignX: {
1515
+ type: String,
1516
+ required: false,
1517
+ default: "start"
1518
+ },
1519
+ alignY: {
1474
1520
  type: String,
1475
1521
  required: false,
1476
1522
  default: "start"
@@ -1486,19 +1532,29 @@ var row_default = /* @__PURE__ */ defineComponent({
1486
1532
  },
1487
1533
  setup(__props) {
1488
1534
  const props = __props;
1489
- const rowAlignToClass = {
1535
+ const rowAlignXToClass = {
1536
+ start: "justify-start",
1537
+ center: "justify-center",
1538
+ end: "justify-end"
1539
+ };
1540
+ const rowAlignYToClass = {
1490
1541
  start: "align-start",
1491
1542
  center: "align-center",
1492
1543
  end: "align-end",
1493
1544
  stretch: "align-stretch"
1494
1545
  };
1495
- const alignClass = computed(() => rowAlignToClass[props.align]);
1546
+ const alignXClass = computed(() => rowAlignXToClass[props.alignX]);
1547
+ const alignYClass = computed(() => rowAlignYToClass[props.alignY]);
1496
1548
  const tag = computed(() => props.list ? "ul" : void 0);
1497
1549
  const listClasses = computed(() => props.list ? "pa-0 ma-0" : void 0);
1498
1550
  return (_ctx, _cache) => {
1499
1551
  return openBlock(), createBlock(unref(VRow), {
1500
1552
  tag: tag.value,
1501
- class: normalizeClass([alignClass.value, listClasses.value]),
1553
+ class: normalizeClass([
1554
+ alignXClass.value,
1555
+ alignYClass.value,
1556
+ listClasses.value
1557
+ ]),
1502
1558
  "data-testid": props.dataTestid
1503
1559
  }, {
1504
1560
  default: withCtx(() => [renderSlot(_ctx.$slots, "default")]),
@@ -2077,7 +2133,7 @@ var data_set_default = /* @__PURE__ */ defineComponent({
2077
2133
  }, null, 8, ["modelValue"])) : createCommentVNode("v-if", true)]),
2078
2134
  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
2135
  list: "",
2080
- align: "stretch"
2136
+ "align-y": "stretch"
2081
2137
  }, {
2082
2138
  default: withCtx(() => [(openBlock(true), createElementBlock(Fragment, null, renderList(pageItems, (pageItem, index) => {
2083
2139
  return openBlock(), createBlock(column_default, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexdom/uimed-vue",
3
- "version": "2.0.0-beta.21",
3
+ "version": "2.0.0-beta.23",
4
4
  "description": "A ui framework for NEXDOM applications.",
5
5
  "keywords": [
6
6
  "lib",