@dative-gpi/foundation-shared-components 0.0.144 → 0.0.145

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.
@@ -5,6 +5,7 @@
5
5
  v-bind="$attrs"
6
6
  >
7
7
  <FSCard
8
+ width="calc(100vw - 48px)"
8
9
  padding="8px"
9
10
  gap="24px"
10
11
  :color="$props.color"
@@ -27,29 +27,23 @@
27
27
  </FSRow>
28
28
  </template>
29
29
  <template
30
- #autocomplete-item="{ props, item }"
30
+ #item-label="{ item, font }"
31
31
  >
32
- <v-list-item
33
- v-bind="{ ...props, title: '' }"
32
+ <FSRow
33
+ align="center-left"
34
+ :wrap="false"
34
35
  >
35
- <FSRow
36
- align="center-left"
36
+ <FSIcon
37
+ v-if="item.raw.icon"
38
+ >
39
+ {{ item.raw.icon }}
40
+ </FSIcon>
41
+ <FSSpan
42
+ :font="font"
37
43
  >
38
- <FSCheckbox
39
- v-if="$props.multiple"
40
- :modelValue="$props.modelValue?.includes(item.value)"
41
- @click="props.onClick"
42
- />
43
- <FSIcon
44
- v-if="item.raw.icon"
45
- >
46
- {{ item.raw.icon }}
47
- </FSIcon>
48
- <FSSpan>
49
- {{ item.raw.label }}
50
- </FSSpan>
51
- </FSRow>
52
- </v-list-item>
44
+ {{ item.raw.label }}
45
+ </FSSpan>
46
+ </FSRow>
53
47
  </template>
54
48
  </FSAutocompleteField>
55
49
  </template>
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <FSAutocompleteField
3
+ itemTitle="id"
3
4
  :toggleSet="!$props.toggleSetDisabled && toggleSet"
4
- :customFilter="customFilter"
5
5
  :multiple="$props.multiple"
6
6
  :loading="loading"
7
7
  :items="timeZones"
@@ -26,27 +26,21 @@
26
26
  </FSRow>
27
27
  </template>
28
28
  <template
29
- #autocomplete-item="{ props, item }"
29
+ #item-label="{ item, font }"
30
30
  >
31
- <v-list-item
32
- v-bind="{ ...props, title: '' }"
31
+ <FSRow
32
+ align="center-left"
33
+ :wrap="false"
33
34
  >
34
- <FSRow
35
- align="center-left"
35
+ <FSChip
36
+ :label="item.raw.offset"
37
+ />
38
+ <FSSpan
39
+ :font="font"
36
40
  >
37
- <FSCheckbox
38
- v-if="$props.multiple"
39
- :modelValue="$props.modelValue?.includes(item.value)"
40
- @click="props.onClick"
41
- />
42
- <FSChip
43
- :label="item.raw.offset"
44
- />
45
- <FSSpan>
46
- {{ item.raw.id }}
47
- </FSSpan>
48
- </FSRow>
49
- </v-list-item>
41
+ {{ item.raw.id }}
42
+ </FSSpan>
43
+ </FSRow>
50
44
  </template>
51
45
  <template
52
46
  #toggle-set-item="props"
@@ -76,9 +70,9 @@ import { computed, defineComponent, PropType } from "vue";
76
70
  import { TimeZoneFilters, TimeZoneInfos } from "@dative-gpi/foundation-shared-domain/models";
77
71
  import { useAutocomplete } from "@dative-gpi/foundation-shared-components/composables";
78
72
  import { useTimeZones } from "@dative-gpi/foundation-shared-services/composables";
73
+ import { ColorEnum } from "@dative-gpi/foundation-shared-components/models";
79
74
 
80
75
  import FSAutocompleteField from "../fields/FSAutocompleteField.vue";
81
- import FSCheckbox from "../FSCheckbox.vue"
82
76
  import FSButton from "../FSButton.vue";
83
77
  import FSChip from "../FSChip.vue";
84
78
  import FSSpan from "../FSSpan.vue";
@@ -88,7 +82,6 @@ export default defineComponent({
88
82
  name: "FSAutocompleteTimeZone",
89
83
  components: {
90
84
  FSAutocompleteField,
91
- FSCheckbox,
92
85
  FSButton,
93
86
  FSChip,
94
87
  FSSpan,
@@ -128,10 +121,6 @@ export default defineComponent({
128
121
  return getManyTimeZones({ ...props.timeZoneFilters, search: search ?? undefined });
129
122
  };
130
123
 
131
- const customFilter = (_: any, search: string, item: any): boolean => {
132
- return item.raw.id.toLowerCase().includes(search.toLowerCase());
133
- };
134
-
135
124
  const { toggleSet, search, init, onUpdate } = useAutocomplete(
136
125
  timeZones,
137
126
  [() => props.timeZoneFilters],
@@ -143,11 +132,11 @@ export default defineComponent({
143
132
  );
144
133
 
145
134
  return {
135
+ ColorEnum,
146
136
  timeZones,
147
137
  toggleSet,
148
138
  loading,
149
139
  search,
150
- customFilter,
151
140
  onUpdate
152
141
  };
153
142
  }
@@ -77,6 +77,7 @@
77
77
  #body
78
78
  >
79
79
  <FSSearchField
80
+ :hideHeader="true"
80
81
  v-model="search"
81
82
  />
82
83
  <FSFadeOut
@@ -229,9 +230,14 @@
229
230
  >
230
231
  <slot
231
232
  name="item-label"
232
- v-bind="{ item }"
233
+ v-bind="{
234
+ item,
235
+ font: $props.modelValue === item.raw[$props.itemTitle] ? 'text-button' : 'text-body'
236
+ }"
233
237
  >
234
- <FSSpan>
238
+ <FSSpan
239
+ :font="$props.modelValue === item.raw[$props.itemTitle] ? 'text-button' : 'text-body'"
240
+ >
235
241
  {{ item.raw[$props.itemTitle] }}
236
242
  </FSSpan>
237
243
  </slot>
@@ -553,7 +559,7 @@ export default defineComponent({
553
559
  emit("update:modelValue", [...props.modelValue, value]);
554
560
  }
555
561
  }
556
- else {
562
+ else if (props.modelValue != null) {
557
563
  if (props.modelValue === value) {
558
564
  emit("update:modelValue", []);
559
565
  }
@@ -561,6 +567,9 @@ export default defineComponent({
561
567
  emit("update:modelValue", [props.modelValue, value]);
562
568
  }
563
569
  }
570
+ else {
571
+ emit("update:modelValue", [value]);
572
+ }
564
573
  };
565
574
 
566
575
  const onClick = () => {
@@ -457,7 +457,7 @@ export default defineComponent({
457
457
  emit("update:modelValue", [...props.modelValue, value]);
458
458
  }
459
459
  }
460
- else {
460
+ else if (props.modelValue != null) {
461
461
  if (props.modelValue === value) {
462
462
  emit("update:modelValue", []);
463
463
  }
@@ -465,6 +465,9 @@ export default defineComponent({
465
465
  emit("update:modelValue", [props.modelValue, value]);
466
466
  }
467
467
  }
468
+ else {
469
+ emit("update:modelValue", [value]);
470
+ }
468
471
  };
469
472
 
470
473
  return {
@@ -0,0 +1,519 @@
1
+ <template>
2
+ <template
3
+ v-if="$props.loading"
4
+ >
5
+ <FSCol>
6
+ <FSLoader
7
+ v-if="!$props.hideHeader"
8
+ variant="text-overline"
9
+ />
10
+ <FSLoader
11
+ v-if="$props.loading"
12
+ width="100%"
13
+ :height="['40px', '36px']"
14
+ />
15
+ </FSCol>
16
+ </template>
17
+ <template
18
+ v-else
19
+ >
20
+ <FSCol>
21
+ <template
22
+ v-if="isExtraSmall"
23
+ >
24
+ <FSTextField
25
+ class="fs-tree-view-field"
26
+ :validationValue="$props.modelValue"
27
+ :description="$props.description"
28
+ :hideHeader="$props.hideHeader"
29
+ :clearable="$props.clearable"
30
+ :editable="$props.editable"
31
+ :required="$props.required"
32
+ :validateOn="validateOn"
33
+ :label="$props.label"
34
+ :rules="$props.rules"
35
+ :messages="messages"
36
+ :readonly="true"
37
+ :style="style"
38
+ :modelValue="innerValue"
39
+ @update:modelValue="$emit('update:modelValue', $event)"
40
+ @click="openMobileOverlay"
41
+ v-bind="$attrs"
42
+ >
43
+ <template
44
+ v-for="(_, name) in fieldSlots"
45
+ v-slot:[name]="slotData"
46
+ >
47
+ <slot
48
+ :name="name"
49
+ v-bind="slotData"
50
+ />
51
+ </template>
52
+ </FSTextField>
53
+ <FSDialogMenu
54
+ v-model="dialog"
55
+ >
56
+ <template
57
+ #body
58
+ >
59
+ <FSFadeOut
60
+ :height="height"
61
+ >
62
+ <v-treeview
63
+ :itemTitle="$props.itemTitle"
64
+ :itemValue="$props.itemValue"
65
+ :items="treeItems"
66
+ >
67
+ <template
68
+ v-for="(_, name) in menuSlots"
69
+ v-slot:[name]="slotData"
70
+ >
71
+ <slot
72
+ :name="name"
73
+ v-bind="slotData"
74
+ />
75
+ </template>
76
+ <template
77
+ #prepend="{ item }"
78
+ >
79
+ <FSCheckbox
80
+ v-if="$props.multiple"
81
+ :class="listItemClass(item[$props.itemValue])"
82
+ :editable="$props.editable"
83
+ :modelValue="$props.modelValue?.includes(item[$props.itemValue])"
84
+ @update:modelValue="() => onCheckboxChange(item[$props.itemValue])"
85
+ >
86
+ <template
87
+ #label="{ font }"
88
+ >
89
+ <slot
90
+ name="menu-prepend"
91
+ :item="item"
92
+ />
93
+ <FSSpan
94
+ :font="font"
95
+ >
96
+ {{ item[$props.itemTitle] }}
97
+ </FSSpan>
98
+ </template>
99
+ </FSCheckbox>
100
+ <FSRadio
101
+ v-if="!$props.multiple"
102
+ :selected="$props.modelValue === item[$props.itemValue]"
103
+ :class="listItemClass(item[$props.itemValue])"
104
+ :editable="$props.editable"
105
+ :modelValue="item[$props.itemValue]"
106
+ @update:modelValue="onRadioChange"
107
+ >
108
+ <template
109
+ #label="{ font }"
110
+ >
111
+ <slot
112
+ name="menu-prepend"
113
+ :item="item"
114
+ />
115
+ <FSSpan
116
+ :font="font"
117
+ >
118
+ {{ item[$props.itemTitle] }}
119
+ </FSSpan>
120
+ </template>
121
+ </FSRadio>
122
+ </template>
123
+ <template
124
+ #title
125
+ />
126
+ </v-treeview>
127
+ </FSFadeOut>
128
+ </template>
129
+ </FSDialogMenu>
130
+ </template>
131
+ <template
132
+ v-else
133
+ >
134
+ <v-menu
135
+ :closeOnContentClick="false"
136
+ :modelValue="menu && $props.editable"
137
+ @update:modelValue="menu = $event"
138
+ >
139
+ <template
140
+ #activator="{ props }"
141
+ >
142
+ <FSTextField
143
+ class="fs-tree-view-field"
144
+ :validationValue="$props.modelValue"
145
+ :description="$props.description"
146
+ :hideHeader="$props.hideHeader"
147
+ :clearable="$props.clearable"
148
+ :editable="$props.editable"
149
+ :required="$props.required"
150
+ :validateOn="validateOn"
151
+ :label="$props.label"
152
+ :rules="$props.rules"
153
+ :messages="messages"
154
+ :readonly="true"
155
+ :style="style"
156
+ :modelValue="innerValue"
157
+ @update:modelValue="$emit('update:modelValue', $event)"
158
+ v-bind="{ ...$attrs, ...props }"
159
+ >
160
+ <template
161
+ v-for="(_, name) in fieldSlots"
162
+ v-slot:[name]="slotData"
163
+ >
164
+ <slot
165
+ :name="name"
166
+ v-bind="slotData"
167
+ />
168
+ </template>
169
+ </FSTextField>
170
+ </template>
171
+ <v-treeview
172
+ :itemTitle="$props.itemTitle"
173
+ :itemValue="$props.itemValue"
174
+ :items="treeItems"
175
+ >
176
+ <template
177
+ v-for="(_, name) in menuSlots"
178
+ v-slot:[name]="slotData"
179
+ >
180
+ <slot
181
+ :name="name"
182
+ v-bind="slotData"
183
+ />
184
+ </template>
185
+ <template
186
+ #prepend="{ item }"
187
+ >
188
+ <FSCheckbox
189
+ v-if="$props.multiple"
190
+ :class="listItemClass(item[$props.itemValue])"
191
+ :editable="$props.editable"
192
+ :modelValue="$props.modelValue?.includes(item[$props.itemValue])"
193
+ @update:modelValue="() => onCheckboxChange(item[$props.itemValue])"
194
+ >
195
+ <template
196
+ #label="{ font }"
197
+ >
198
+ <slot
199
+ name="menu-prepend"
200
+ :item="item"
201
+ />
202
+ <FSSpan
203
+ :font="font"
204
+ >
205
+ {{ item[$props.itemTitle] }}
206
+ </FSSpan>
207
+ </template>
208
+ </FSCheckbox>
209
+ <FSRadio
210
+ v-if="!$props.multiple"
211
+ :selected="$props.modelValue === item[$props.itemValue]"
212
+ :class="listItemClass(item[$props.itemValue])"
213
+ :editable="$props.editable"
214
+ :modelValue="item[$props.itemValue]"
215
+ @update:modelValue="onRadioChange"
216
+ >
217
+ <template
218
+ #label="{ font }"
219
+ >
220
+ <slot
221
+ name="menu-prepend"
222
+ :item="item"
223
+ />
224
+ <FSSpan
225
+ :font="font"
226
+ >
227
+ {{ item[$props.itemTitle] }}
228
+ </FSSpan>
229
+ </template>
230
+ </FSRadio>
231
+ </template>
232
+ <template
233
+ #title
234
+ />
235
+ </v-treeview>
236
+ </v-menu>
237
+ </template>
238
+ </FSCol>
239
+ </template>
240
+ </template>
241
+
242
+ <script lang="ts">
243
+ import { computed, defineComponent, PropType, ref } from "vue";
244
+
245
+ import { useBreakpoints, useColors, useRules, useSlots } from "@dative-gpi/foundation-shared-components/composables";
246
+ import { ColorEnum } from "@dative-gpi/foundation-shared-components/models";
247
+
248
+ import { VTreeview } from "vuetify/labs/VTreeview";
249
+
250
+ import FSDialogMenu from "../FSDialogMenu.vue";
251
+ import FSTextField from "./FSTextField.vue";
252
+ import FSCheckbox from "../FSCheckbox.vue";
253
+ import FSFadeOut from "../FSFadeOut.vue";
254
+ import FSLoader from "../FSLoader.vue";
255
+ import FSRadio from "../FSRadio.vue";
256
+ import FSSpan from "../FSSpan.vue";
257
+ import FSCol from "../FSCol.vue";
258
+
259
+ export default defineComponent({
260
+ name: "FSTreeViewField",
261
+ components: {
262
+ VTreeview,
263
+ FSDialogMenu,
264
+ FSTextField,
265
+ FSCheckbox,
266
+ FSFadeOut,
267
+ FSLoader,
268
+ FSRadio,
269
+ FSSpan,
270
+ FSCol,
271
+ },
272
+ props: {
273
+ label: {
274
+ type: String as PropType<string | null>,
275
+ required: false,
276
+ default: null
277
+ },
278
+ description: {
279
+ type: String as PropType<string | null>,
280
+ required: false,
281
+ default: null
282
+ },
283
+ items: {
284
+ type: Array as PropType<any[]>,
285
+ required: true
286
+ },
287
+ itemValue: {
288
+ type: String,
289
+ required: false,
290
+ default: "id"
291
+ },
292
+ itemTitle: {
293
+ type: String,
294
+ required: false,
295
+ default: "label"
296
+ },
297
+ itemParent: {
298
+ type: String,
299
+ required: false,
300
+ default: "parentId"
301
+ },
302
+ exclude: {
303
+ type: Array as PropType<string[]>,
304
+ required: false,
305
+ default: () => []
306
+ },
307
+ modelValue: {
308
+ type: [Array, String, Number] as PropType<(string | number)[] | string | number | null>,
309
+ required: false,
310
+ default: null
311
+ },
312
+ hideHeader: {
313
+ type: Boolean,
314
+ required: false,
315
+ default: false
316
+ },
317
+ required: {
318
+ type: Boolean,
319
+ required: false,
320
+ default: false
321
+ },
322
+ multiple: {
323
+ type: Boolean,
324
+ required: false,
325
+ default: false
326
+ },
327
+ rules: {
328
+ type: Array as PropType<any[]>,
329
+ required: false,
330
+ default: () => []
331
+ },
332
+ messages: {
333
+ type: Array as PropType<string[]>,
334
+ required: false,
335
+ default: null
336
+ },
337
+ clearable: {
338
+ type: Boolean,
339
+ required: false,
340
+ default: true
341
+ },
342
+ editable: {
343
+ type: Boolean,
344
+ required: false,
345
+ default: true
346
+ },
347
+ loading: {
348
+ type: Boolean,
349
+ required: false,
350
+ default: false
351
+ }
352
+ },
353
+ emits: ["update:modelValue"],
354
+ setup(props, { emit }) {
355
+ const { validateOn, getMessages } = useRules();
356
+ const { isExtraSmall } = useBreakpoints();
357
+ const { getColors } = useColors();
358
+ const { slots } = useSlots();
359
+
360
+ delete slots.label;
361
+ delete slots.description;
362
+ delete slots["menu-prepend"];
363
+
364
+ const backgrounds = getColors(ColorEnum.Background);
365
+
366
+ const dialog = ref(false);
367
+ const menu = ref(false);
368
+
369
+ const style = computed((): { [key: string]: string | undefined | null } => {
370
+ if (!props.editable) {
371
+ return {
372
+ "--fs-tree-view-field-cursor": "default"
373
+ };
374
+ }
375
+ else {
376
+ return {
377
+ "--fs-tree-view-field-cursor" : "pointer",
378
+ "--fs-tree-view-field-background-color": backgrounds.base
379
+ };
380
+ }
381
+ });
382
+
383
+ const messages = computed((): string[] => props.messages ?? getMessages(props.modelValue, props.rules));
384
+
385
+ const height = computed(() => {
386
+ const other = 8 + 8; // Paddings
387
+ return `calc(100vh - 40px - ${other}px)`;
388
+ });
389
+
390
+ const fieldSlots = computed((): any => {
391
+ return Object.keys(slots).filter(k => !k.startsWith("menu-")).reduce((acc, key) => {
392
+ acc[key] = slots[key];
393
+ return acc;
394
+ }, {});
395
+ });
396
+
397
+ const menuSlots = computed((): any => {
398
+ return Object.keys(slots).filter(k => k.startsWith("menu-")).reduce((acc, key) => {
399
+ acc[key.substring("menu-".length)] = slots[key];
400
+ return acc;
401
+ }, {});
402
+ });
403
+
404
+ const innerValue = computed((): string | null => {
405
+ if (props.multiple) {
406
+ if (Array.isArray(props.modelValue)) {
407
+ return props.modelValue.map((value: any) => {
408
+ const item = props.items.find((item: Object) => item[props.itemValue] === value);
409
+ if (item) {
410
+ return item[props.itemTitle];
411
+ }
412
+ }).filter(value => !!value).join(", ");
413
+ }
414
+ }
415
+ if (props.modelValue) {
416
+ const item = props.items.find((item: Object) => item[props.itemValue] === props.modelValue);
417
+ if (item) {
418
+ return item[props.itemTitle];
419
+ }
420
+ }
421
+ return null;
422
+ });
423
+
424
+ const treeItems = computed((): any[] => {
425
+ const filter = ((parentId: string | null) => {
426
+ return props.items.filter((item: any) => {
427
+ if (props.exclude.includes(item[props.itemValue])) {
428
+ return false;
429
+ }
430
+ return item[props.itemParent] == parentId;
431
+ });
432
+ });
433
+ const process = ((item: any) => {
434
+ if (props.items.some((child: any) => child[props.itemParent] === item[props.itemValue])) {
435
+ return {
436
+ ...item,
437
+ children: filter(item[props.itemValue]).map(process)
438
+ };
439
+ }
440
+ return item;
441
+ });
442
+ return filter(null).map(process);
443
+ });
444
+
445
+ const listItemClass = (value: string): string[] => {
446
+ const classNames: string[] = [];
447
+ if (props.multiple) {
448
+ if (Array.isArray(props.modelValue)) {
449
+ if (props.modelValue.includes(value)) {
450
+ classNames.push("fs-tree-view-item-selected");
451
+ }
452
+ }
453
+ else if (props.modelValue === value) {
454
+ classNames.push("fs-tree-view-item-selected");
455
+ }
456
+ }
457
+ else {
458
+ if (props.modelValue === value) {
459
+ classNames.push("fs-tree-view-item-selected");
460
+ }
461
+ }
462
+ return classNames;
463
+ };
464
+
465
+ const openMobileOverlay = () => {
466
+ if (!props.editable) {
467
+ return;
468
+ }
469
+ dialog.value = true;
470
+ };
471
+
472
+ const onRadioChange = (value: any): void => {
473
+ emit("update:modelValue", value);
474
+ dialog.value = false;
475
+ menu.value = false;
476
+ };
477
+
478
+ const onCheckboxChange = (value: string) => {
479
+ if (Array.isArray(props.modelValue)) {
480
+ if (props.modelValue.includes(value)) {
481
+ emit("update:modelValue", props.modelValue.filter((item: any) => item !== value));
482
+ }
483
+ else {
484
+ emit("update:modelValue", [...props.modelValue, value]);
485
+ }
486
+ }
487
+ else if (props.modelValue != null) {
488
+ if (props.modelValue === value) {
489
+ emit("update:modelValue", []);
490
+ }
491
+ else {
492
+ emit("update:modelValue", [props.modelValue, value]);
493
+ }
494
+ }
495
+ else {
496
+ emit("update:modelValue", [value]);
497
+ }
498
+ };
499
+
500
+ return {
501
+ isExtraSmall,
502
+ innerValue,
503
+ fieldSlots,
504
+ validateOn,
505
+ menuSlots,
506
+ treeItems,
507
+ messages,
508
+ dialog,
509
+ height,
510
+ style,
511
+ menu,
512
+ openMobileOverlay,
513
+ onCheckboxChange,
514
+ onRadioChange,
515
+ listItemClass
516
+ };
517
+ }
518
+ });
519
+ </script>
@@ -112,6 +112,7 @@
112
112
  >
113
113
  <v-data-table
114
114
  v-if="!isExtraSmall"
115
+ loadingText=""
115
116
  :selectStrategy="$props.singleSelect ? 'single' : 'all'"
116
117
  :groupBy="$props.groupBy ? [$props.groupBy] : []"
117
118
  :headers="extraHeaders.concat(innerHeaders)"
@@ -120,6 +121,7 @@
120
121
  :showSelect="$props.showSelect"
121
122
  :hover="!$props.sortDraggable"
122
123
  :itemValue="$props.itemValue"
124
+ :loading="$props.loading"
123
125
  :rowProps="rowProps"
124
126
  :fixedHeader="true"
125
127
  :items="innerItems"
@@ -408,9 +410,27 @@
408
410
  v-else
409
411
  class="fs-data-table-iterator"
410
412
  :itemsPerPage="innerRowsPerPage"
413
+ :loading="$props.loading"
411
414
  :items="innerItems"
412
415
  :page="innerPage"
413
416
  >
417
+ <template
418
+ #loader
419
+ >
420
+ <v-progress-linear
421
+ height="2"
422
+ :indeterminate="true"
423
+ />
424
+ </template>
425
+ <template
426
+ #no-data
427
+ >
428
+ <FSText
429
+ font="text-overline"
430
+ >
431
+ {{ $tr("ui.data-table.empty", "No data") }}
432
+ </FSText>
433
+ </template>
414
434
  <template
415
435
  #default="{ items }"
416
436
  >
@@ -560,9 +580,27 @@
560
580
  >
561
581
  <v-data-iterator
562
582
  class="fs-data-table-iterator"
563
- :items="innerItems"
583
+ :loading="$props.loading"
564
584
  :itemsPerPage="size"
585
+ :items="innerItems"
565
586
  >
587
+ <template
588
+ #loader
589
+ >
590
+ <v-progress-linear
591
+ height="2"
592
+ :indeterminate="true"
593
+ />
594
+ </template>
595
+ <template
596
+ #no-data
597
+ >
598
+ <FSText
599
+ font="text-overline"
600
+ >
601
+ {{ $tr("ui.data-table.empty", "No data") }}
602
+ </FSText>
603
+ </template>
566
604
  <template
567
605
  #default="{ items }"
568
606
  >
@@ -782,6 +820,11 @@ export default defineComponent({
782
820
  required: false,
783
821
  default: false
784
822
  },
823
+ loading: {
824
+ type: Boolean,
825
+ required: false,
826
+ default: false
827
+ },
785
828
  mode: {
786
829
  type: String as PropType<"table" | "iterator">,
787
830
  required: false,
@@ -4,4 +4,5 @@ export * from "./useColors";
4
4
  export * from "./useDebounce";
5
5
  export * from "./useRules";
6
6
  export * from "./useSlots";
7
- export * from "./useTables";
7
+ export * from "./useTables";
8
+ export * from "./useTreeView";
@@ -0,0 +1,47 @@
1
+ import { onMounted, Ref, watch } from "vue";
2
+ import _ from "lodash";
3
+
4
+ import { useDebounce } from "./useDebounce";
5
+
6
+ export const useTreeView = <TInfos>(
7
+ entities: Ref<TInfos[]>,
8
+ filters: (() => any)[],
9
+ emit: (event: "update:modelValue", value: string[] | string | null) => void,
10
+ customFetch: () => Promise<any>,
11
+ customUpdate: ((item: TInfos[] | TInfos | null) => void) | null = null,
12
+ toId: (item: TInfos) => string | null = (item: TInfos) => (item as any).id,
13
+ debounceInterval: number = 1000
14
+ ) => {
15
+ const { debounce } = useDebounce();
16
+
17
+ const debouncedFetch = () => debounce(customFetch, debounceInterval);
18
+
19
+ const onUpdate = (value: string[] | string | null) => {
20
+ if (customUpdate) {
21
+ if (Array.isArray(value)) {
22
+ const items = value.map(v => entities.value.find(e => toId(e) === v) || null).filter(e => e !== null) as TInfos[];
23
+ customUpdate(items);
24
+ }
25
+ else {
26
+ customUpdate((value && entities.value.find(e => toId(e) === value)) || null);
27
+ }
28
+ }
29
+ else {
30
+ emit("update:modelValue", value);
31
+ }
32
+ };
33
+
34
+ watch(filters, (newValue, oldValue) => {
35
+ if (!_.isEqual(newValue, oldValue)) {
36
+ debouncedFetch();
37
+ }
38
+ });
39
+
40
+ onMounted((): void => {
41
+ customFetch();
42
+ });
43
+
44
+ return {
45
+ onUpdate
46
+ };
47
+ }
package/models/rules.ts CHANGED
@@ -64,4 +64,10 @@ export const TimeRules = {
64
64
 
65
65
  export const ToggleRules = {
66
66
  required: (message: string | undefined = undefined) => (value: boolean) => value || (message ?? $tr("ui.rules.required", "Required"))
67
+ };
68
+
69
+ export const TreeViewRules = {
70
+ required: (message: string | undefined = undefined) => (value: string) => !!value || (message ?? $tr("ui.rules.required", "Required")),
71
+ min: (min: number, message: string | undefined = undefined) => (value: string[]) => { console.log(value); return (Array.isArray(value) && value.length >= min) || (message ?? $tr("ui.rules.tree-view-min", "Must select at least {0} elements", min.toString())); },
72
+ max: (max: number, message: string | undefined = undefined) => (value: string[]) => (Array.isArray(value) && value.length <= max) || (message ?? $tr("ui.rules.tree-view-max", "Must select at most {0} elements", max.toString()))
67
73
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dative-gpi/foundation-shared-components",
3
3
  "sideEffects": false,
4
- "version": "0.0.144",
4
+ "version": "0.0.145",
5
5
  "description": "",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -10,8 +10,8 @@
10
10
  "author": "",
11
11
  "license": "ISC",
12
12
  "dependencies": {
13
- "@dative-gpi/foundation-shared-domain": "0.0.144",
14
- "@dative-gpi/foundation-shared-services": "0.0.144",
13
+ "@dative-gpi/foundation-shared-domain": "0.0.145",
14
+ "@dative-gpi/foundation-shared-services": "0.0.145",
15
15
  "@fontsource/montserrat": "^5.0.16",
16
16
  "@lexical/clipboard": "^0.12.5",
17
17
  "@lexical/history": "^0.12.5",
@@ -32,5 +32,5 @@
32
32
  "sass": "^1.69.5",
33
33
  "sass-loader": "^13.3.2"
34
34
  },
35
- "gitHead": "e894b5045c2f95bea5635cc4eb3c17dd0934ff2e"
35
+ "gitHead": "3d11321eaa64d0fa150cab22ff5256f28e4cf93e"
36
36
  }
@@ -11,4 +11,9 @@
11
11
  &:has(.fs-button-load) {
12
12
  cursor: default !important;
13
13
  }
14
+
15
+ & > a, a:hover, a:visited, a:active {
16
+ color: inherit;
17
+ text-decoration: none;
18
+ }
14
19
  }
@@ -3,8 +3,7 @@
3
3
  color: var(--fs-checkbox-checkbox-color) !important;
4
4
  }
5
5
 
6
- .fs-checkbox-label,
7
- .fs-checkbox-label :not(.fs-checkbox) {
6
+ .fs-checkbox-label {
8
7
  cursor: var(--fs-checkbox-cursor) !important;
9
8
  color: var(--fs-checkbox-color) !important;
10
9
  user-select: none;
@@ -3,8 +3,7 @@
3
3
  color: var(--fs-radio-radio-color) !important;
4
4
  }
5
5
 
6
- .fs-radio-label,
7
- .fs-radio-label :not(.fs-radio) {
6
+ .fs-radio-label {
8
7
  cursor: var(--fs-radio-cursor) !important;
9
8
  color: var(--fs-radio-color) !important;
10
9
  user-select: none;
@@ -33,21 +33,15 @@
33
33
  color: var(--fs-select-field-color);
34
34
  }
35
35
 
36
- .v-overlay-container > .v-overlay > .v-select__content {
37
- box-shadow: 0px 5px 8px 0px #00000029;
38
- max-width: none !important;
39
- border-radius: 4px;
40
-
41
- & > .v-list {
42
- @extend .fs-hide-y-scrollbar;
36
+ .v-overlay-container > .v-overlay > .v-select__content > .v-list {
37
+ @extend .fs-hide-y-scrollbar;
43
38
 
44
- background-color: var(--fs-select-field-background-color);
45
- padding: 2px 8px 2px 16px;
46
- border-radius: 4px;
39
+ background-color: var(--fs-select-field-background-color);
40
+ padding: 2px 8px 2px 16px;
41
+ border-radius: 4px;
47
42
 
48
- & .v-list-item {
49
- border-radius: 4px !important;
50
- margin-top: 2px !important;
51
- }
43
+ & .v-list-item {
44
+ border-radius: 4px !important;
45
+ margin-top: 2px !important;
52
46
  }
53
47
  }
@@ -1,5 +1,5 @@
1
1
  .fs-text {
2
- color: var(--fs-text-color);
2
+ color: var(--fs-text-color) !important;
3
3
 
4
4
  @extend .fs-span;
5
5
  }
@@ -0,0 +1,53 @@
1
+ .fs-tree-view-field > .v-input__control > .v-field > .v-field__field > .v-field__input {
2
+ cursor: var(--fs-tree-view-field-cursor) !important;
3
+ }
4
+
5
+ .v-overlay__content > .v-treeview {
6
+ max-height: 310px !important;
7
+ }
8
+
9
+ .v-treeview.v-list {
10
+ @extend .fs-hide-y-scrollbar;
11
+
12
+ background-color: var(--fs-select-field-background-color);
13
+ padding: 2px 8px 2px 16px;
14
+ border-radius: 4px;
15
+
16
+ & .v-list-item {
17
+ border-radius: 4px !important;
18
+ margin-top: 2px;
19
+ }
20
+ }
21
+
22
+ .v-treeview-item:has(.v-list-item__prepend > .fs-tree-view-item-selected) > .v-list-item__overlay {
23
+ opacity: calc(var(--v-activated-opacity) * var(--v-theme-overlay-multiplier)) !important;
24
+ }
25
+
26
+ .v-treeview-item:not(:has(.v-list-item__prepend > .fs-tree-view-item-selected)) > .v-list-item__overlay {
27
+ display: none;
28
+ }
29
+
30
+ .fs-dialog-menu {
31
+ .v-treeview.v-list {
32
+ display: flex;
33
+ flex-direction: column;
34
+ padding: 0px !important;
35
+ gap: 12px !important;
36
+ }
37
+
38
+ .v-treeview-item {
39
+ cursor: default;
40
+ padding-inline: 0px !important;
41
+ min-height: 20px !important;
42
+ padding: 0px !important;
43
+ margin: 0px !important;
44
+
45
+ & > .v-list-item__overlay {
46
+ display: none;
47
+ }
48
+
49
+ & > .v-ripple__container {
50
+ display: none;
51
+ }
52
+ }
53
+ }
@@ -58,5 +58,6 @@
58
58
  @import "fs_timeslot_field.scss";
59
59
  @import "fs_tooltip.scss";
60
60
  @import "fs_translate_field.scss";
61
+ @import "fs_tree_view_field.scss";
61
62
  @import "fs_window.scss";
62
63
  @import "fs_wrap_group.scss";
@@ -12,7 +12,7 @@
12
12
 
13
13
  @include clickscreen {
14
14
  .fs-hide-x-scrollbar {
15
- overflow-x: scroll;
15
+ overflow-x: scroll !important;
16
16
 
17
17
  &::-webkit-scrollbar {
18
18
  height: 8px;
@@ -28,7 +28,7 @@
28
28
  }
29
29
 
30
30
  .fs-hide-y-scrollbar {
31
- overflow-y: scroll;
31
+ overflow-y: scroll !important;
32
32
 
33
33
  &::-webkit-scrollbar {
34
34
  width: 8px;