@vc-shell/framework 1.0.93 → 1.0.94

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 +14 -1
  2. package/core/utilities/camelize.ts +5 -0
  3. package/core/utilities/generateId.ts +3 -0
  4. package/core/utilities/index.ts +2 -0
  5. package/core/utilities/kebabToCamel.ts +1 -1
  6. package/dist/core/utilities/camelize.d.ts +2 -0
  7. package/dist/core/utilities/camelize.d.ts.map +1 -0
  8. package/dist/core/utilities/generateId.d.ts +2 -0
  9. package/dist/core/utilities/generateId.d.ts.map +1 -0
  10. package/dist/core/utilities/index.d.ts +2 -0
  11. package/dist/core/utilities/index.d.ts.map +1 -1
  12. package/dist/core/utilities/kebabToCamel.d.ts +1 -0
  13. package/dist/core/utilities/kebabToCamel.d.ts.map +1 -1
  14. package/dist/framework.mjs +6293 -6076
  15. package/dist/index.css +1 -1
  16. package/dist/tsconfig.tsbuildinfo +1 -1
  17. package/dist/ui/components/molecules/index.d.ts +1 -0
  18. package/dist/ui/components/molecules/index.d.ts.map +1 -1
  19. package/dist/ui/components/molecules/vc-multivalue/index.d.ts +42 -0
  20. package/dist/ui/components/molecules/vc-multivalue/index.d.ts.map +1 -0
  21. package/dist/ui/components/molecules/vc-multivalue/vc-multivalue.vue.d.ts +66 -0
  22. package/dist/ui/components/molecules/vc-multivalue/vc-multivalue.vue.d.ts.map +1 -0
  23. package/dist/ui/components/organisms/vc-dynamic-property/index.d.ts +16 -12
  24. package/dist/ui/components/organisms/vc-dynamic-property/index.d.ts.map +1 -1
  25. package/dist/ui/components/organisms/vc-dynamic-property/vc-dynamic-property.vue.d.ts +16 -12
  26. package/dist/ui/components/organisms/vc-dynamic-property/vc-dynamic-property.vue.d.ts.map +1 -1
  27. package/dist/ui/types/index.d.ts +1 -0
  28. package/dist/ui/types/index.d.ts.map +1 -1
  29. package/package.json +3 -3
  30. package/ui/components/molecules/index.ts +1 -0
  31. package/ui/components/molecules/vc-multivalue/index.ts +3 -0
  32. package/ui/components/molecules/vc-multivalue/vc-multivalue.vue +396 -0
  33. package/ui/components/organisms/vc-dynamic-property/vc-dynamic-property.vue +17 -15
  34. package/ui/types/index.ts +1 -0
@@ -0,0 +1,396 @@
1
+ <template>
2
+ <div
3
+ class="vc-multivalue"
4
+ :class="[
5
+ `vc-multivalue_${type}`,
6
+ {
7
+ 'vc-multivalue_opened': isOpened,
8
+ 'vc-multivalue_error tw-pb-[20px]': error,
9
+ 'vc-multivalue_disabled': disabled,
10
+ },
11
+ ]"
12
+ >
13
+ <!-- Input label -->
14
+ <VcLabel
15
+ v-if="label"
16
+ class="tw-mb-2"
17
+ :required="required"
18
+ >
19
+ <span>{{ label }}</span>
20
+ <template
21
+ v-if="tooltip"
22
+ #tooltip
23
+ >{{ tooltip }}</template
24
+ >
25
+ </VcLabel>
26
+
27
+ <!-- Input field -->
28
+ <div
29
+ ref="dropdownToggleRef"
30
+ class="vc-multivalue__field-wrapper"
31
+ >
32
+ <div
33
+ v-for="(item, i) in modelValue"
34
+ :key="`${item.id}_${generateId()}`"
35
+ class="vc-multivalue__field-value-wrapper"
36
+ >
37
+ <div class="vc-multivalue__field-value">
38
+ <span class="tw-truncate">{{
39
+ type === "number" ? Number(item[props.emitLabel]).toFixed(3) : item[props.emitLabel]
40
+ }}</span>
41
+ <VcIcon
42
+ v-if="!disabled"
43
+ class="vc-multivalue__field-value-clear"
44
+ icon="fas fa-times"
45
+ size="s"
46
+ @click="onDelete(i)"
47
+ ></VcIcon>
48
+ </div>
49
+ </div>
50
+
51
+ <template v-if="multivalue">
52
+ <div class="vc-multivalue__field vc-multivalue__field_dictionary tw-grow tw-basis-0 tw-p-2">
53
+ <VcButton
54
+ small
55
+ @click.stop="toggleDropdown"
56
+ >Add +</VcButton
57
+ >
58
+ <teleport to="#app">
59
+ <div
60
+ v-if="isOpened"
61
+ ref="dropdownRef"
62
+ v-on-click-outside="[toggleDropdown, { ignore: [dropdownToggleRef] }]"
63
+ class="vc-multivalue__dropdown"
64
+ :style="dropdownStyle"
65
+ >
66
+ <input
67
+ ref="searchRef"
68
+ class="vc-multivalue__search"
69
+ @input="onSearch"
70
+ />
71
+
72
+ <VcContainer
73
+ ref="root"
74
+ :no-padding="true"
75
+ >
76
+ <div
77
+ v-for="(item, i) in slicedDictionary"
78
+ :key="i"
79
+ class="vc-multivalue__item"
80
+ @click="onItemSelect(item)"
81
+ >
82
+ <slot
83
+ name="item"
84
+ :item="item"
85
+ >{{ item[optionLabel] }}</slot
86
+ >
87
+ </div>
88
+ </VcContainer>
89
+ </div>
90
+ </teleport>
91
+ </div>
92
+ </template>
93
+ <template v-else>
94
+ <input
95
+ v-model="value"
96
+ class="vc-multivalue__field tw-grow tw-basis-0 tw-pl-3"
97
+ :placeholder="placeholder"
98
+ :type="type"
99
+ :disabled="disabled"
100
+ @keypress.enter.stop.prevent="onInput"
101
+ />
102
+ </template>
103
+ </div>
104
+
105
+ <slot
106
+ v-if="errorMessage"
107
+ name="error"
108
+ >
109
+ <VcHint class="vc-multivalue__error tw-mt-1">
110
+ {{ errorMessage }}
111
+ </VcHint>
112
+ </slot>
113
+ </div>
114
+ </template>
115
+
116
+ <script lang="ts" setup generic="T extends {id?: string; alias?: string}">
117
+ import { unref, nextTick, ref, computed } from "vue";
118
+ import { vOnClickOutside } from "@vueuse/components";
119
+ import { useFloating, UseFloatingReturn, offset, flip, shift, autoUpdate } from "@floating-ui/vue";
120
+ import { generateId } from "../../../../core/utilities";
121
+
122
+ export interface Props<T> {
123
+ placeholder?: string;
124
+ modelValue?: T[];
125
+ required?: boolean;
126
+ disabled?: boolean;
127
+ type?: "text" | "number";
128
+ label?: string;
129
+ tooltip?: string;
130
+ name?: string;
131
+ options?: T[];
132
+ optionValue?: string;
133
+ optionLabel?: string;
134
+ emitValue?: string;
135
+ emitLabel?: string;
136
+ multivalue?: boolean;
137
+ error?: boolean;
138
+ errorMessage?: string;
139
+ }
140
+
141
+ export interface Emits<T> {
142
+ (event: "update:model-value", value: T[]): void;
143
+ (event: "close"): void;
144
+ (event: "search", value: string): void;
145
+ }
146
+
147
+ type FloatingInstanceType = UseFloatingReturn & {
148
+ middlewareData: {
149
+ sameWidthChangeBorders: {
150
+ borderTop?: string;
151
+ borderBottom?: string;
152
+ borderRadius?: string;
153
+ width?: string;
154
+ };
155
+ };
156
+ };
157
+
158
+ const props = withDefaults(defineProps<Props<T>>(), {
159
+ modelValue: () => [],
160
+ type: "text",
161
+ name: "Field",
162
+ options: () => [],
163
+ optionValue: "id",
164
+ optionLabel: "title",
165
+ emitValue: "valueId",
166
+ emitLabel: "value",
167
+ });
168
+
169
+ const emit = defineEmits<Emits<T>>();
170
+
171
+ const dropdownToggleRef = ref();
172
+ const dropdownRef = ref();
173
+ const root = ref();
174
+ const searchRef = ref();
175
+ const isOpened = ref(false);
176
+ const value = ref();
177
+
178
+ const popper = useFloating(dropdownToggleRef, dropdownRef, {
179
+ placement: "bottom",
180
+ whileElementsMounted: autoUpdate,
181
+ middleware: [
182
+ flip({ fallbackPlacements: ["top", "bottom"] }),
183
+ shift({ mainAxis: false }),
184
+ sameWidthChangeBorders(),
185
+ offset(-2),
186
+ ],
187
+ }) as FloatingInstanceType;
188
+
189
+ const dropdownStyle = computed(() => {
190
+ return {
191
+ top: `${popper.y.value ?? 0}px`,
192
+ left: `${popper.x.value ?? 0}px`,
193
+ ...popper.middlewareData.value.sameWidthChangeBorders,
194
+ };
195
+ });
196
+
197
+ const slicedDictionary = computed(() => {
198
+ return props.options?.filter((x) => {
199
+ return !props.modelValue?.find((item) => item[props.emitValue] === x[props.optionValue]);
200
+ });
201
+ });
202
+
203
+ // Handle input event to propertly validate value and emit changes
204
+ function onInput(e: KeyboardEvent) {
205
+ const newValue = (e.target as HTMLInputElement).value;
206
+ emit("update:model-value", [...props.modelValue, { [props.emitLabel]: newValue } as T]);
207
+ value.value = undefined;
208
+ }
209
+
210
+ function onItemSelect(item: T) {
211
+ emit("update:model-value", [
212
+ ...props.modelValue,
213
+ { [props.emitValue]: item[props.optionValue], [props.emitLabel]: item[props.optionLabel] } as T,
214
+ ]);
215
+ emit("close");
216
+ closeDropdown();
217
+ }
218
+
219
+ // Handle event to propertly remove particular value and emit changes
220
+ function onDelete(i: number) {
221
+ const result = unref(props.modelValue);
222
+ result.splice(i, 1);
223
+ emit("update:model-value", [...result]);
224
+ }
225
+
226
+ function sameWidthChangeBorders() {
227
+ return {
228
+ name: "sameWidthChangeBorders",
229
+ fn: ({ rects, placement, x, y }) => {
230
+ let borderTop;
231
+ let borderBottom;
232
+ let borderRadius;
233
+ if (placement === "top") {
234
+ borderTop = "1px solid var(--select-border-color)";
235
+ borderBottom = "1px solid var(--select-background-color)";
236
+ borderRadius = "var(--select-border-radius) var(--select-border-radius) 0 0";
237
+ } else {
238
+ borderBottom = "1px solid var(--select-border-color)";
239
+ borderTop = "1px solid var(--select-background-color)";
240
+ borderRadius = "0 0 var(--select-border-radius) var(--select-border-radius)";
241
+ }
242
+
243
+ const width = `${rects.reference.width}px`;
244
+
245
+ return {
246
+ x,
247
+ y,
248
+ data: {
249
+ borderTop,
250
+ borderBottom,
251
+ borderRadius,
252
+ width,
253
+ },
254
+ };
255
+ },
256
+ };
257
+ }
258
+
259
+ async function toggleDropdown() {
260
+ if (!props.disabled) {
261
+ if (isOpened.value) {
262
+ closeDropdown();
263
+ } else {
264
+ isOpened.value = true;
265
+
266
+ nextTick(() => {
267
+ searchRef?.value?.focus();
268
+ });
269
+ }
270
+ }
271
+ }
272
+
273
+ function closeDropdown() {
274
+ isOpened.value = false;
275
+ emit("close");
276
+ }
277
+
278
+ function onSearch(event: InputEvent) {
279
+ emit("search", (event.target as HTMLInputElement).value);
280
+ }
281
+ </script>
282
+
283
+ <style lang="scss">
284
+ :root {
285
+ --multivalue-height: 38px;
286
+ --multivalue-border-radius: 3px;
287
+ --multivalue-border-color: #d3dbe9;
288
+ --multivalue-border-color-error: #f14e4e;
289
+ --multivalue-background-color: #ffffff;
290
+ --multivalue-placeholder-color: #a5a5a5;
291
+
292
+ --select-height: 38px;
293
+ --select-border-radius: 3px;
294
+ --select-border-color: #d3dbe9;
295
+ --select-border-color-error: #f14e4e;
296
+ --select-background-color: #ffffff;
297
+ --select-background-color-disabled: #fafafa;
298
+ --select-placeholder-color: #a5a5a5;
299
+ --select-chevron-color: #43b0e6;
300
+ --select-chevron-color-hover: #319ed4;
301
+ }
302
+
303
+ .vc-multivalue {
304
+ @apply tw-overflow-hidden;
305
+
306
+ &_date,
307
+ &_datetime-local {
308
+ @apply tw-max-w-[220px];
309
+
310
+ .vc-app_mobile & {
311
+ @apply tw-max-w-full;
312
+ }
313
+ }
314
+
315
+ &__field-wrapper {
316
+ @apply tw-border tw-border-solid
317
+ tw-border-[color:var(--multivalue-border-color)]
318
+ tw-rounded-[var(--multivalue-border-radius)]
319
+ tw-bg-[color:var(--multivalue-background-color)]
320
+ tw-items-center
321
+ tw-flex
322
+ tw-flex-wrap;
323
+ }
324
+
325
+ &__dropdown {
326
+ @apply tw-flex tw-flex-col tw-box-border
327
+ tw-max-h-[300px] tw-z-10 tw-overflow-hidden
328
+ tw-absolute tw-bg-[color:var(--select-background-color)]
329
+ tw-border tw-border-solid tw-border-[color:var(--select-border-color)]
330
+ tw-border-t-[color:var(--select-background-color)]
331
+ tw-rounded-b-[var(--select-border-radius)]
332
+ tw-p-2;
333
+ }
334
+
335
+ &__search {
336
+ @apply tw-w-full tw-box-border tw-border tw-border-solid tw-border-[#eaecf2]
337
+ tw-rounded-[4px] tw-h-8 tw-leading-[32px]
338
+ tw-outline-none tw-mb-3 tw-px-2;
339
+ }
340
+
341
+ &__item {
342
+ @apply tw-flex tw-items-center tw-min-h-[36px] tw-px-2 tw-rounded-[3px] tw-cursor-pointer hover:tw-bg-[#eff7fc];
343
+ }
344
+
345
+ &_opened &__field-wrapper {
346
+ @apply tw-rounded-t-[var(--select-border-radius)];
347
+ }
348
+
349
+ &_error &__field-wrapper {
350
+ @apply tw-border tw-border-solid tw-border-[color:var(--multivalue-border-color-error)];
351
+ }
352
+
353
+ &__error {
354
+ @apply tw-text-[color:var(--multivalue-border-color-error)];
355
+ }
356
+
357
+ &__field {
358
+ @apply tw-border-none tw-outline-none tw-h-[var(--multivalue-height)]
359
+ tw-min-w-[120px] tw-box-border placeholder:tw-text-[color:var(--multivalue-placeholder-color)];
360
+
361
+ &::-webkit-input-placeholder {
362
+ @apply tw-text-[color:var(--multivalue-placeholder-color)];
363
+ }
364
+
365
+ &::-moz-placeholder {
366
+ @apply tw-text-[color:var(--multivalue-placeholder-color)];
367
+ }
368
+
369
+ &::-ms-placeholder {
370
+ @apply tw-text-[color:var(--multivalue-placeholder-color)];
371
+ }
372
+
373
+ &-value-wrapper {
374
+ @apply tw-h-[var(--multivalue-height)] tw-ml-2 tw-flex tw-items-center;
375
+ }
376
+
377
+ &-value {
378
+ @apply tw-bg-[#fbfdfe] tw-border tw-border-solid tw-border-[color:#bdd1df] tw-rounded-[2px]
379
+ tw-flex tw-items-center tw-h-[28px] tw-box-border tw-px-2 tw-max-w-[150px];
380
+
381
+ &-clear {
382
+ @apply tw-text-[#a9bfd2] tw-ml-2 tw-cursor-pointer;
383
+ }
384
+ }
385
+
386
+ &_dictionary {
387
+ @apply tw-h-auto tw-min-w-[auto];
388
+ }
389
+ }
390
+
391
+ &_disabled &__field-wrapper,
392
+ &_disabled &__field {
393
+ @apply tw-bg-[#fafafa] tw-text-[#424242];
394
+ }
395
+ }
396
+ </style>
@@ -14,7 +14,7 @@
14
14
  :error-message="errorMessage"
15
15
  :label="computedProperty.displayName"
16
16
  :required="computedProperty.required"
17
- :placeholder="computedProperty.displayName"
17
+ :placeholder="computedProperty.placeholder"
18
18
  :options="items"
19
19
  :option-value="computedProperty.optionValue"
20
20
  :option-label="computedProperty.optionLabel"
@@ -155,7 +155,7 @@
155
155
  clearable
156
156
  type="number"
157
157
  :required="computedProperty.required"
158
- :placeholder="computedProperty.displayName"
158
+ :placeholder="computedProperty.placeholder"
159
159
  :disabled="disabled"
160
160
  ></VcInput>
161
161
  </Field>
@@ -178,7 +178,7 @@
178
178
  type="number"
179
179
  step="1"
180
180
  :required="computedProperty.required"
181
- :placeholder="computedProperty.displayName"
181
+ :placeholder="computedProperty.placeholder"
182
182
  :disabled="disabled"
183
183
  ></VcInput>
184
184
  </Field>
@@ -199,7 +199,7 @@
199
199
  :label="computedProperty.displayName"
200
200
  type="datetime-local"
201
201
  :required="computedProperty.required"
202
- :placeholder="computedProperty.displayName"
202
+ :placeholder="computedProperty.placeholder"
203
203
  :disabled="disabled"
204
204
  ></VcInput>
205
205
  </Field>
@@ -218,7 +218,7 @@
218
218
  :error-message="errorMessage"
219
219
  :label="computedProperty.displayName"
220
220
  :required="computedProperty.required"
221
- :placeholder="computedProperty.displayName"
221
+ :placeholder="computedProperty.placeholder"
222
222
  :disabled="disabled"
223
223
  ></VcTextarea>
224
224
  </Field>
@@ -261,11 +261,11 @@ const props = withDefaults(
261
261
  defineProps<{
262
262
  property: T;
263
263
  modelValue: any;
264
- optionsGetter: (property: T, keyword?: string) => Promise<unknown[]>;
264
+ optionsGetter: (property: T, keyword?: string) => Promise<unknown[]> | unknown[];
265
265
  required: boolean;
266
- multivalue: boolean;
266
+ multivalue?: boolean;
267
267
  valueType: string;
268
- dictionary: boolean;
268
+ dictionary?: boolean;
269
269
  name: string;
270
270
  optionsValue?: string;
271
271
  optionsLabel?: string;
@@ -279,6 +279,7 @@ const props = withDefaults(
279
279
  regex: string;
280
280
  };
281
281
  disabled?: boolean;
282
+ placeholder?: string;
282
283
  }>(),
283
284
  {
284
285
  optionsValue: "id",
@@ -300,19 +301,19 @@ const computedProperty = computed(() => {
300
301
  if (props.required) {
301
302
  rules["required"] = true;
302
303
  }
303
- if (props.rules.min) {
304
+ if (props.rules?.min) {
304
305
  rules["min"] = Number(props.rules.min);
305
306
  }
306
- if (props.rules.max) {
307
+ if (props.rules?.max) {
307
308
  rules["max"] = Number(props.rules.max);
308
309
  }
309
- if (props.rules.regex) {
310
+ if (props.rules?.regex) {
310
311
  rules["regex"] = new RegExp(props.rules.regex);
311
312
  }
312
313
 
313
- const propertyDisplayName = props.displayNames?.find((displayName) =>
314
- displayName.languageCode?.startsWith(locale.value as string)
315
- )?.name;
314
+ const propertyDisplayName =
315
+ props.displayNames?.find((displayName) => displayName.languageCode?.startsWith(locale.value as string))?.name ||
316
+ props.name;
316
317
  const propertyDisplayNameLocalized =
317
318
  propertyDisplayName && te(propertyDisplayName.toUpperCase())
318
319
  ? t(propertyDisplayName.toUpperCase())
@@ -324,10 +325,11 @@ const computedProperty = computed(() => {
324
325
  dictionary: props.dictionary || false,
325
326
  multivalue: props.multivalue || false,
326
327
  name: props.name,
327
- displayName: propertyDisplayNameLocalized || props.name, //|| setting?.displayName || setting?.defaultValue,
328
+ displayName: propertyDisplayNameLocalized, //|| setting?.displayName || setting?.defaultValue,
328
329
  optionValue: props.optionsValue,
329
330
  optionLabel: props.optionsLabel,
330
331
  required: props.required,
332
+ placeholder: props.placeholder || propertyDisplayNameLocalized,
331
333
  };
332
334
  });
333
335
 
package/ui/types/index.ts CHANGED
@@ -38,6 +38,7 @@ declare module "@vue/runtime-core" {
38
38
  VcSelect: (typeof VcShellComponents)["VcSelect"];
39
39
  VcSlider: (typeof VcShellComponents)["VcSlider"];
40
40
  VcTextarea: (typeof VcShellComponents)["VcTextarea"];
41
+ VcMultivalue: (typeof VcShellComponents)["VcMultivalue"];
41
42
 
42
43
  // organisms
43
44
  VcApp: (typeof VcShellComponents)["VcApp"];