aloha-vue 2.81.0 → 2.82.0

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.
Files changed (34) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/aloha-vue.es.js +314 -10
  3. package/dist/aloha-vue.umd.js +15 -15
  4. package/package.json +1 -1
  5. package/src/ui/ACheckbox/ACheckbox.js +6 -0
  6. package/src/ui/ACheckbox/ACheckboxItem/ACheckboxItem.js +6 -0
  7. package/src/ui/ADatepicker/ADatepicker.js +6 -0
  8. package/src/ui/ADatepickerRange/ADatepickerRange.js +6 -1
  9. package/src/ui/AFieldset/AFieldset.js +7 -1
  10. package/src/ui/AForm/AForm.js +6 -0
  11. package/src/ui/AFormElement/AFormElement.js +5 -0
  12. package/src/ui/AFormReadonly/AFormReadonly.js +1 -1
  13. package/src/ui/AGroup/AGroup.js +8 -1
  14. package/src/ui/AInput/AInput.js +6 -0
  15. package/src/ui/AInputCurrency/AInputCurrency.js +6 -0
  16. package/src/ui/AInputFile/AInputFile.js +40 -5
  17. package/src/ui/AInputNumber/AInputNumber.js +6 -0
  18. package/src/ui/AInputNumberRange/AInputNumberRange.js +6 -1
  19. package/src/ui/AJson/AJson.js +6 -0
  20. package/src/ui/AMultiselectOrdered/AMultiselectOrdered.js +30 -24
  21. package/src/ui/AOneCheckbox/AOneCheckbox.js +6 -0
  22. package/src/ui/ARadio/ARadio.js +6 -0
  23. package/src/ui/ARadio/ARadioItem/ARadioItem.js +6 -0
  24. package/src/ui/ARouterLinkConfig/ARouterLinkConfig.js +7 -0
  25. package/src/ui/ASelect/ASelect.js +6 -0
  26. package/src/ui/ASelectIcon/ASelectIcon.js +16 -11
  27. package/src/ui/ASelectStyle/ASelectStyle.js +16 -11
  28. package/src/ui/ASlider/ASlider.js +6 -0
  29. package/src/ui/ASwitch/ASwitch.js +11 -5
  30. package/src/ui/ATemplate/ATemplate.js +7 -2
  31. package/src/ui/ATextarea/ATextarea.js +6 -0
  32. package/src/ui/AValidatedJson/AValidatedJson.js +10 -2
  33. package/src/ui/AValidatedJson/AValidatedJsonItem/AValidatedJsonItem.js +6 -0
  34. package/src/ui/__tests__/ReadonlyValueParentClass.test.js +217 -0
@@ -192,6 +192,11 @@ export default {
192
192
  type: Boolean,
193
193
  required: false,
194
194
  },
195
+ readonlyValueParentClass: {
196
+ type: [String, Object, Array],
197
+ required: false,
198
+ default: undefined,
199
+ },
195
200
  readonlyDefault: {
196
201
  type: String,
197
202
  required: false,
@@ -321,6 +326,7 @@ export default {
321
326
  if (this.readonly) {
322
327
  return h(AFormReadonly, {
323
328
  ...this.$attrs,
329
+ valueParentClass: this.readonlyValueParentClass,
324
330
  id: this.htmlIdLocal,
325
331
  alwaysTranslate: this.alwaysTranslate,
326
332
  excludeRenderAttributes: this.excludeRenderAttributes,
@@ -348,11 +354,11 @@ export default {
348
354
  }],
349
355
  }, [
350
356
  (this.label || this.labelScreenReader) ?
351
- h(ALabel, {
352
- id: this.htmlIdLocal,
353
- alwaysTranslate: this.alwaysTranslate,
354
- extra: this.extra,
355
- label: this.label,
357
+ h(ALabel, {
358
+ id: this.htmlIdLocal,
359
+ alwaysTranslate: this.alwaysTranslate,
360
+ extra: this.extra,
361
+ label: this.label,
356
362
  labelClass: this.labelClass,
357
363
  labelScreenReader: this.labelScreenReader,
358
364
  required: this.required,
@@ -105,6 +105,11 @@ export default {
105
105
  type: Boolean,
106
106
  required: false,
107
107
  },
108
+ readonlyValueParentClass: {
109
+ type: [String, Object, Array],
110
+ required: false,
111
+ default: undefined,
112
+ },
108
113
  readonlyDefault: {
109
114
  type: String,
110
115
  required: false,
@@ -148,7 +153,7 @@ export default {
148
153
  }, [
149
154
  this.$slots[this.slotName] ?
150
155
  h("div", {
151
- class: "a_template",
156
+ class: ["a_template", this.readonly && this.readonlyValueParentClass],
152
157
  }, this.$slots[this.slotName]({
153
158
  options: this.options,
154
159
  props: this.$props,
@@ -156,7 +161,7 @@ export default {
156
161
  "",
157
162
  this.html ?
158
163
  withDirectives(h("div", {
159
- class: "a_template",
164
+ class: ["a_template", this.readonly && this.readonlyValueParentClass],
160
165
  }), [
161
166
  [ASafeHtml, this.html],
162
167
  ]) :
@@ -186,6 +186,11 @@ export default {
186
186
  type: Boolean,
187
187
  required: false,
188
188
  },
189
+ readonlyValueParentClass: {
190
+ type: [String, Object, Array],
191
+ required: false,
192
+ default: undefined,
193
+ },
189
194
  readonlyDefault: {
190
195
  type: String,
191
196
  required: false,
@@ -314,6 +319,7 @@ export default {
314
319
  if (this.readonly) {
315
320
  return h(AFormReadonly, {
316
321
  ...this.$attrs,
322
+ valueParentClass: this.readonlyValueParentClass,
317
323
  id: this.htmlIdLocal,
318
324
  alwaysTranslate: this.alwaysTranslate,
319
325
  excludeRenderAttributes: this.excludeRenderAttributes,
@@ -194,6 +194,11 @@ export default {
194
194
  required: false,
195
195
  default: undefined,
196
196
  },
197
+ readonlyValueParentClass: {
198
+ type: [String, Object, Array],
199
+ required: false,
200
+ default: undefined,
201
+ },
197
202
  required: {
198
203
  type: Boolean,
199
204
  required: false,
@@ -385,6 +390,7 @@ export default {
385
390
  modelDependencies: this.modelDependencies,
386
391
  modelValue: this.modelValue,
387
392
  readonly: this.readonly,
393
+ readonlyValueParentClass: this.readonlyValueParentClass,
388
394
  required: this.required,
389
395
  skipOwnIdInModelPath: true,
390
396
  slotNamePrepend: `singlePrepend_${ this.id }`,
@@ -428,7 +434,7 @@ export default {
428
434
  class: "a_column a_column_12",
429
435
  }, [
430
436
  h("ul", {
431
- class: "a_list_group",
437
+ class: ["a_list_group", this.readonly && this.readonlyValueParentClass],
432
438
  }, [
433
439
  this.modelListMode.length > 0 ?
434
440
  this.listModeItems.map((item, index) => {
@@ -449,6 +455,7 @@ export default {
449
455
  parentHtmlId: this.htmlIdLocal,
450
456
  parentId: this.id,
451
457
  readonly: this.readonly,
458
+ readonlyValueParentClass: this.readonlyValueParentClass,
452
459
  typedBaseId: this.typedBaseId,
453
460
  typedChildren: this.typedChildren,
454
461
  uniqueChildrenIds: this.uniqueChildrenIds,
@@ -534,7 +541,7 @@ export default {
534
541
  class: "a_column a_column_12",
535
542
  }, [
536
543
  h("ul", {
537
- class: "a_list_group",
544
+ class: ["a_list_group", this.readonly && this.readonlyValueParentClass],
538
545
  }, [
539
546
  this.isModelJsonModeEmpty ?
540
547
  h(ATranslation, {
@@ -560,6 +567,7 @@ export default {
560
567
  parentHtmlId: this.htmlIdLocal,
561
568
  parentId: this.id,
562
569
  readonly: this.readonly,
570
+ readonlyValueParentClass: this.readonlyValueParentClass,
563
571
  typedBaseId: this.typedBaseId,
564
572
  typedChildren: this.typedChildren,
565
573
  uniqueChildrenIds: this.uniqueChildrenIds,
@@ -103,6 +103,11 @@ export default {
103
103
  required: false,
104
104
  default: undefined,
105
105
  },
106
+ readonlyValueParentClass: {
107
+ type: [String, Object, Array],
108
+ required: false,
109
+ default: undefined,
110
+ },
106
111
  typedBaseId: {
107
112
  type: String,
108
113
  required: false,
@@ -279,6 +284,7 @@ export default {
279
284
  idPrefix: this.parentHtmlId,
280
285
  modelValue: this.modelItem,
281
286
  readonly: true,
287
+ readonlyValueParentClass: this.readonlyValueParentClass,
282
288
  skipOwnIdInModelPath: true,
283
289
  slotNamePrepend: "singlePrepend",
284
290
  useFlatModel: this.useFlatModel,
@@ -0,0 +1,217 @@
1
+ import {
2
+ h,
3
+ nextTick,
4
+ } from "vue";
5
+ import {
6
+ afterEach,
7
+ beforeEach,
8
+ describe,
9
+ expect,
10
+ it,
11
+ } from "vitest";
12
+ import {
13
+ mount,
14
+ } from "@vue/test-utils";
15
+ import {
16
+ ACheckbox,
17
+ ADatepicker,
18
+ ADatepickerRange,
19
+ AFieldset,
20
+ AForm,
21
+ AFormElement,
22
+ AGroup,
23
+ AInput,
24
+ AInputCurrency,
25
+ AInputFile,
26
+ AInputNumber,
27
+ AInputNumberRange,
28
+ AJson,
29
+ AMultiselectOrdered,
30
+ AOneCheckbox,
31
+ ARadio,
32
+ ARouterLinkConfig,
33
+ ASelect,
34
+ ASelectIcon,
35
+ ASelectStyle,
36
+ ASlider,
37
+ ASwitch,
38
+ ATemplate,
39
+ ATextarea,
40
+ AValidatedJson,
41
+ } from "../../index";
42
+
43
+ import ACheckboxItem from "../ACheckbox/ACheckboxItem/ACheckboxItem";
44
+ import ARadioItem from "../ARadio/ARadioItem/ARadioItem";
45
+
46
+ import {
47
+ setI18n,
48
+ updateTranslation,
49
+ } from "../../ATranslation/compositionAPI/ATranslationAPI";
50
+
51
+ const wrappers = [];
52
+
53
+ beforeEach(async() => {
54
+ setI18n({ de: {} });
55
+ updateTranslation();
56
+ await nextTick();
57
+ });
58
+
59
+ afterEach(() => {
60
+ wrappers.forEach(wrapper => wrapper.unmount());
61
+ wrappers.length = 0;
62
+ });
63
+
64
+ function mountReadonly(component, props = {}, slots = {}) {
65
+ const wrapper = mount(component, {
66
+ props: {
67
+ id: "test",
68
+ readonly: true,
69
+ readonlyValueParentClass: ["custom_value", { custom_object: true }],
70
+ ...props,
71
+ },
72
+ slots,
73
+ });
74
+ wrappers.push(wrapper);
75
+ return wrapper;
76
+ }
77
+
78
+ describe("readonlyValueParentClass", () => {
79
+ it.each([
80
+ ACheckbox,
81
+ ADatepicker,
82
+ ADatepickerRange,
83
+ AFieldset,
84
+ AInput,
85
+ AInputCurrency,
86
+ AInputFile,
87
+ AInputNumber,
88
+ AInputNumberRange,
89
+ AJson,
90
+ AMultiselectOrdered,
91
+ AOneCheckbox,
92
+ ARadio,
93
+ ASelect,
94
+ ASelectIcon,
95
+ ASelectStyle,
96
+ ASlider,
97
+ ASwitch,
98
+ ATextarea,
99
+ ])("styles the value wrapper in $name and reacts to class/readonly changes", async component => {
100
+ const wrapper = mountReadonly(component);
101
+ await nextTick();
102
+ const value = wrapper.get(".a_form_element_readonly_value");
103
+ expect(value.classes()).toContain("custom_value");
104
+ expect(value.classes()).toContain("custom_object");
105
+ expect(wrapper.classes()).not.toContain("custom_value");
106
+ await wrapper.setProps({ readonlyValueParentClass: "updated_value" });
107
+ expect(value.classes()).toContain("updated_value");
108
+ expect(value.classes()).not.toContain("custom_value");
109
+ await wrapper.setProps({ readonly: false });
110
+ expect(wrapper.find(".updated_value").exists()).toBe(false);
111
+ });
112
+
113
+ it.each([ADatepickerRange, AInputNumberRange])("preserves range layout in $name", component => {
114
+ const wrapper = mountReadonly(component);
115
+ expect(wrapper.get(".custom_value").classes()).toContain("a_form_element_readonly_value_range");
116
+ });
117
+
118
+ it("preserves fieldset columns and propagates defaults with per-child overrides", () => {
119
+ const wrapper = mountReadonly(AFieldset, {
120
+ children: [
121
+ { id: "first", type: "text" },
122
+ { id: "second", type: "text", readonlyValueParentClass: "child_value" },
123
+ ],
124
+ modelValue: {},
125
+ });
126
+ const value = wrapper.get(".a_form_element_readonly_value");
127
+ expect(value.classes()).toContain("a_columns");
128
+ const inputs = wrapper.findAllComponents(AInput);
129
+ expect(inputs[0].get(".a_form_element_readonly_value").classes()).toContain("custom_value");
130
+ expect(inputs[1].get(".a_form_element_readonly_value").classes()).toContain("child_value");
131
+ expect(inputs[1].get(".a_form_element_readonly_value").classes()).not.toContain("custom_value");
132
+ });
133
+
134
+ it("propagates defaults through AForm and nested groups", () => {
135
+ const wrapper = mountReadonly(AForm, {
136
+ modelValue: { group: {} },
137
+ data: [{
138
+ id: "group",
139
+ type: "group",
140
+ children: [
141
+ { id: "first", type: "text" },
142
+ { id: "second", type: "text", readonlyValueParentClass: "child_value" },
143
+ ],
144
+ }],
145
+ });
146
+ const group = wrapper.findComponent(AGroup);
147
+ expect(group.get(".a_columns").classes()).toContain("custom_value");
148
+ const inputs = group.findAllComponents(AInput);
149
+ expect(inputs[0].get(".a_form_element_readonly_value").classes()).toContain("custom_value");
150
+ expect(inputs[1].get(".a_form_element_readonly_value").classes()).toContain("child_value");
151
+ });
152
+
153
+ it("passes the class through AFormElement", () => {
154
+ const wrapper = mountReadonly(AFormElement, { type: "text" });
155
+ expect(wrapper.get(".a_form_element_readonly_value").classes()).toContain("custom_value");
156
+ });
157
+
158
+ it.each(["routerLinkConfig", "selectRoute", "multiselectRoute"])("passes the class through ARouterLinkConfig (%s)", async type => {
159
+ const wrapper = mountReadonly(ARouterLinkConfig, { type });
160
+ await nextTick();
161
+ expect(wrapper.get(".a_form_element_readonly_value").classes()).toContain("custom_value");
162
+ });
163
+
164
+ it("styles template HTML and slot content only in readonly mode", async() => {
165
+ const wrapper = mountReadonly(ATemplate, { html: "Value", slotName: "content" }, {
166
+ content: () => h("span", "Slot"),
167
+ });
168
+ expect(wrapper.findAll(".a_template.custom_value")).toHaveLength(2);
169
+ await wrapper.setProps({ readonly: false });
170
+ expect(wrapper.find(".custom_value").exists()).toBe(false);
171
+ });
172
+
173
+ it.each(["list", "json"])("styles AValidatedJson values and previews in %s mode", async mode => {
174
+ const wrapper = mountReadonly(AValidatedJson, {
175
+ mode,
176
+ children: [{ id: "value", type: "text" }],
177
+ modelValue: mode === "list" ? [{ value: "Hello" }] : { first: { value: "Hello" } },
178
+ });
179
+ expect(wrapper.get("ul.a_list_group").classes()).toContain("custom_value");
180
+ await wrapper.get(".test_toggle_details").trigger("click");
181
+ expect(wrapper.get(".a_form_element_readonly_value").classes()).toContain("custom_value");
182
+ await wrapper.setProps({ readonly: false });
183
+ expect(wrapper.get("ul.a_list_group").classes()).not.toContain("custom_value");
184
+ });
185
+
186
+ it("styles AValidatedJson values in single mode", () => {
187
+ const wrapper = mountReadonly(AValidatedJson, {
188
+ mode: "single",
189
+ children: [{ id: "value", type: "text" }],
190
+ modelValue: { value: "Hello" },
191
+ });
192
+ expect(wrapper.get(".a_form_element_readonly_value").classes()).toContain("custom_value");
193
+ });
194
+
195
+ it.each([ACheckboxItem, ARadioItem])("styles standalone readonly $name", async component => {
196
+ const wrapper = mountReadonly(component, { itemIndex: 0, dataItem: { label: "Value", value: 1 } });
197
+ expect(wrapper.classes()).toContain("custom_value");
198
+ await wrapper.setProps({ readonly: false });
199
+ expect(wrapper.find(".custom_value").exists()).toBe(false);
200
+ });
201
+
202
+ it.each([false, true])("shows file names without file input (multiple: %s)", isMultiple => {
203
+ const files = [{ name: "<first>.txt" }, { name: "second.txt" }];
204
+ const wrapper = mountReadonly(AInputFile, {
205
+ isMultiple,
206
+ modelValue: isMultiple ? files : files[0],
207
+ });
208
+ expect(wrapper.findAll("li").map(item => item.text())).toEqual(isMultiple ? ["<first>.txt", "second.txt"] : ["<first>.txt"]);
209
+ expect(wrapper.find("input[type=file]").exists()).toBe(false);
210
+ expect(wrapper.find("first").exists()).toBe(false);
211
+ });
212
+
213
+ it("shows readonlyDefault for an empty file value", () => {
214
+ const wrapper = mountReadonly(AInputFile, { readonlyDefault: "No files" });
215
+ expect(wrapper.get(".a_form_element_readonly_value").text()).toBe("No files");
216
+ });
217
+ });