@dative-gpi/foundation-shared-components 0.0.40 → 0.0.42

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.
@@ -163,7 +163,7 @@
163
163
  import { $createParagraphNode, $getSelection, $isElementNode, $isRangeSelection, $setSelection, CAN_UNDO_COMMAND, createEditor, ElementNode, FORMAT_ELEMENT_COMMAND, FORMAT_TEXT_COMMAND, ParagraphNode, UNDO_COMMAND } from "lexical";
164
164
  import { $createHeadingNode, HeadingNode, HeadingTagType, registerRichText } from "@lexical/rich-text";
165
165
  import { createEmptyHistoryState, registerHistory } from "@lexical/history";
166
- import { computed, defineComponent, onMounted, PropType, ref } from "vue";
166
+ import { computed, defineComponent, onMounted, PropType, ref, watch } from "vue";
167
167
  import { $createLinkNode, $isLinkNode, LinkNode } from "@lexical/link";
168
168
  import { $wrapNodes } from "@lexical/selection";
169
169
 
@@ -243,9 +243,11 @@ export default defineComponent({
243
243
  const isStrikethrough = ref(false);
244
244
 
245
245
  const id = `${Math.random()}-editor`;
246
+ const emptyState = "{\"root\":{\"children\":[{\"children\":[],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"paragraph\",\"version\":1}],\"direction\":null,\"format\":\"\",\"indent\":0,\"type\":\"root\",\"version\":1}}";
246
247
 
247
- const linkUrl = ref("https://");
248
248
 
249
+ const linkUrl = ref("https://");
250
+
249
251
  const config = {
250
252
  namespace: "MyEditor",
251
253
  theme: {
@@ -285,6 +287,11 @@ export default defineComponent({
285
287
  editor.setEditorState(editor.parseEditorState(props.modelValue));
286
288
  });
287
289
  }
290
+ else {
291
+ editor.update((): void => {
292
+ editor.setEditorState(editor.parseEditorState(emptyState));
293
+ });
294
+ }
288
295
  });
289
296
 
290
297
  const readonly = computed((): boolean => {
@@ -533,6 +540,21 @@ export default defineComponent({
533
540
  isLink.value = false;
534
541
  }
535
542
 
543
+ watch(() => props.modelValue, () => {
544
+ if (props.modelValue != JSON.stringify(editor.getEditorState().toJSON())) {
545
+ if (props.modelValue != null) {
546
+ editor.update(() => {
547
+ editor.setEditorState(editor.parseEditorState(props.modelValue));
548
+ });
549
+ }
550
+ else {
551
+ editor.update(() => {
552
+ editor.setEditorState(editor.parseEditorState(emptyState));
553
+ });
554
+ }
555
+ }
556
+ });
557
+
536
558
  return {
537
559
  readonly,
538
560
  style,
@@ -2,7 +2,7 @@
2
2
  <FSCard
3
3
  class="fs-data-iterator-item"
4
4
  padding="12px"
5
- width="50%"
5
+ width="100%"
6
6
  >
7
7
  <FSCol>
8
8
  <slot name="item.top" v-bind="{ item: $props.item }" />
@@ -17,7 +17,7 @@
17
17
  />
18
18
  <slot name="toolbar" />
19
19
  <v-spacer />
20
- <FSToggleSet
20
+ <FSOptionGroup
21
21
  v-if="!$props.disableTable && !$props.disableIterator"
22
22
  :values="modeOptions"
23
23
  :required="true"
@@ -36,7 +36,10 @@
36
36
  @update:filter="(value) => toggleFilter(header.value, value)"
37
37
  >
38
38
  <template #default="{ filter }">
39
- <slot :name="filterSlot(header)" v-bind="{ filter }" />
39
+ <slot
40
+ :name="filterSlot(header)"
41
+ v-bind="{ filter }"
42
+ />
40
43
  </template>
41
44
  </FSFilterButton>
42
45
  <FSChip
@@ -68,7 +71,7 @@
68
71
  :items="innerItems"
69
72
  :fixedHeader="true"
70
73
  :multiSort="false"
71
- :hover="true"
74
+ :hover="!$props.sortDraggable"
72
75
  :style="style"
73
76
  :class="classes"
74
77
  :page="innerPage"
@@ -77,6 +80,10 @@
77
80
  @auxclick:row="onClickRow"
78
81
  @click:row="onClickRow"
79
82
  @update:sortBy="innerSortBy = $event ? $event[0] : null"
83
+ @dragover.prevent
84
+ @drop:row="(event, row) => onDrop(event, row, 'tr.v-data-table__tr')"
85
+ @dragover="onDragOver($event, 'tr.v-data-table__tr', 'tbody')"
86
+ @dragleave="onDragLeave"
80
87
  >
81
88
  <template #no-data>
82
89
  <FSText
@@ -111,11 +118,37 @@
111
118
  />
112
119
  </FSRow>
113
120
  </template>
121
+ <template #[`item.data-table-draggable`]="props">
122
+ <FSDraggable
123
+ elementSelector="tr.v-data-table__tr"
124
+ :disabled="draggableDisabled"
125
+ :item="props"
126
+ @update:dragend="(event, dragged) => onDragEnd(event, dragged, 'tbody')"
127
+ >
128
+ <FSRow
129
+ class="fs-data-table-draggable"
130
+ align="bottom-center"
131
+ width="hug"
132
+ >
133
+ <FSIcon
134
+ size="l"
135
+ >
136
+ mdi-drag-vertical
137
+ </FSIcon>
138
+ </FSRow>
139
+ </FSDraggable>
140
+ </template>
114
141
  <template #[`header.data-table-group`]="props">
115
- <slot name="header.data-table-group" v-bind="props" />
142
+ <slot
143
+ name="header.data-table-group"
144
+ v-bind="props"
145
+ />
116
146
  </template>
117
147
  <template #[`item.data-table-group`]="props">
118
- <slot name="item.data-table-group" v-bind="props" />
148
+ <slot
149
+ name="item.data-table-group"
150
+ v-bind="props"
151
+ />
119
152
  </template>
120
153
  <template #group-header="props">
121
154
  <template :ref="() => { if (!props.isGroupOpen(props.item)) { props.toggleGroup(props.item) } }" />
@@ -125,27 +158,39 @@
125
158
  class="fs-data-table-group-header"
126
159
  :colspan="extraHeaders.concat(innerHeaders).length + 1"
127
160
  >
128
- <slot name="group-header" v-bind="props">
129
- <FSCard
130
- padding="12px 16px"
161
+ <slot
162
+ name="group-header"
163
+ v-bind="props"
131
164
  >
132
- <FSRow
133
- align="center-left"
134
- width="hug"
165
+ <FSCard
166
+ padding="12px 16px"
135
167
  >
136
- <FSText>
137
- <slot name="group-header-title" v-bind="props">
138
- {{ props.item.value }}
139
- </slot>
140
- </FSText>
141
- </FSRow>
142
- </FSCard>
168
+ <FSRow
169
+ align="center-left"
170
+ width="hug"
171
+ >
172
+ <FSText>
173
+ <slot
174
+ name="group-header-title"
175
+ v-bind="props"
176
+ >
177
+ {{ props.item.value }}
178
+ </slot>
179
+ </FSText>
180
+ </FSRow>
181
+ </FSCard>
143
182
  </slot>
144
183
  </td>
145
184
  </tr>
146
185
  </template>
147
- <template v-for="(header, index) in headersSlots" #[header.slotName]="props">
148
- <slot :name="header.slotName" v-bind="props">
186
+ <template
187
+ v-for="(header, index) in headersSlots"
188
+ #[header.slotName]="props"
189
+ >
190
+ <slot
191
+ :name="header.slotName"
192
+ v-bind="props"
193
+ >
149
194
  <FSRow
150
195
  align="center-left"
151
196
  :wrap="false"
@@ -177,8 +222,14 @@
177
222
  </FSRow>
178
223
  </slot>
179
224
  </template>
180
- <template v-for="(item, index) in itemsSlots" #[item.slotName]="props">
181
- <slot :name="item.slotName" v-bind="props">
225
+ <template
226
+ v-for="(item, index) in itemsSlots"
227
+ #[item.slotName]="props"
228
+ >
229
+ <slot
230
+ :name="item.slotName"
231
+ v-bind="props"
232
+ >
182
233
  <FSRow
183
234
  align="center-left"
184
235
  :key="index"
@@ -249,8 +300,14 @@
249
300
  />
250
301
  </FSRow>
251
302
  </template>
252
- <template v-for="(_, name) in innerSlots" #[name]="props">
253
- <slot :name="name" v-bind="props" />
303
+ <template
304
+ v-for="(_, name) in innerSlots"
305
+ #[name]="props"
306
+ >
307
+ <slot
308
+ :name="name"
309
+ v-bind="props"
310
+ />
254
311
  </template>
255
312
  </v-data-table>
256
313
  <v-data-iterator
@@ -263,25 +320,49 @@
263
320
  <template #default="{ items }">
264
321
  <FSCol
265
322
  width="fill"
323
+ class="fs-data-iterator-container"
266
324
  >
267
- <template v-for="(item, index) in items">
268
- <slot name="item.iterator" v-bind="{ item, index }">
325
+ <FSDraggable
326
+ v-for="(item, index) in items"
327
+ elementSelector=".fs-draggable-item"
328
+ :disabled="draggableDisabled"
329
+ :item="item"
330
+ :key="index"
331
+ @update:dragend="(event, dragged) => onDragEnd(event, dragged, '.fs-data-iterator-container')"
332
+ @dragover="onDragOver($event, '.fs-draggable-item', '.fs-data-iterator-container')"
333
+ @drop="(event) => onDrop(event, item, '.fs-draggable-item')"
334
+ @dragleave="onDragLeave"
335
+ @dragover.prevent
336
+ >
337
+ <slot
338
+ name="item.iterator"
339
+ v-bind="{ item, index }"
340
+ >
269
341
  <FSDataIteratorItem
270
342
  v-if="item.type === 'item'"
271
- :key="index"
272
- :item="item.raw"
273
- :color="$props.color"
274
- :itemTo="$props.itemTo"
275
- :showSelect="$props.showSelect"
276
343
  :headers="innerHeaders.filter(h => !$props.sneakyHeaders.includes(h.value))"
277
344
  :modelValue="innerValue.includes(item.raw[$props.itemValue])"
345
+ :showSelect="$props.showSelect"
346
+ :itemTo="$props.itemTo"
347
+ :color="$props.color"
348
+ :item="item.raw"
349
+ :key="index"
278
350
  @update:modelValue="toggleSelect"
279
351
  >
280
352
  <template #[`item.top`]="props">
281
- <slot name="item.top" v-bind="props" />
353
+ <slot
354
+ name="item.top"
355
+ v-bind="props"
356
+ />
282
357
  </template>
283
- <template v-for="(item, index) in itemsSlots" #[item.slotName]="props">
284
- <slot :name="item.slotName" v-bind="props">
358
+ <template
359
+ v-for="(item, index) in itemsSlots"
360
+ #[item.slotName]="props"
361
+ >
362
+ <slot
363
+ :name="item.slotName"
364
+ v-bind="props"
365
+ >
285
366
  <FSText
286
367
  :key="index"
287
368
  >
@@ -290,11 +371,14 @@
290
371
  </slot>
291
372
  </template>
292
373
  <template #[`item.bottom`]="props">
293
- <slot name="item.bottom" v-bind="props" />
374
+ <slot
375
+ name="item.bottom"
376
+ v-bind="props"
377
+ />
294
378
  </template>
295
379
  </FSDataIteratorItem>
296
380
  </slot>
297
- </template>
381
+ </FSDraggable>
298
382
  </FSCol>
299
383
  </template>
300
384
  <template #footer>
@@ -368,24 +452,48 @@
368
452
  <template #default="{ items }">
369
453
  <FSRow
370
454
  width="hug"
455
+ class="fs-data-iterator-container"
371
456
  >
372
- <template v-for="(item, index) in items.filter((item) => item.type === 'item')">
373
- <slot name="item.tile" v-bind="{ index, item: item.raw, toggleSelect }">
457
+ <FSDraggable
458
+ v-for="(item, index) in items.filter((item) => item.type === 'item')"
459
+ elementSelector=".fs-draggable-item"
460
+ :disabled="draggableDisabled"
461
+ :item="item"
462
+ :key="index"
463
+ @update:dragend="(event, dragged) => onDragEnd(event, dragged, '.fs-data-iterator-container')"
464
+ @dragover="onDragOver($event, '.fs-draggable-item', '.fs-data-iterator-container')"
465
+ @drop="(event) => onDrop(event, item, '.fs-draggable-item')"
466
+ @dragleave="onDragLeave"
467
+ @dragover.prevent
468
+ >
469
+ <slot
470
+ name="item.tile"
471
+ v-bind="{ index, item: item.raw, toggleSelect }"
472
+ >
374
473
  <FSDataIteratorItem
375
- :key="index"
376
- :item="item.raw"
377
- :color="$props.color"
378
- :itemTo="$props.itemTo"
379
- :showSelect="$props.showSelect"
380
474
  :headers="innerHeaders.filter(h => !$props.sneakyHeaders.includes(h.value))"
381
475
  :modelValue="innerValue.includes(item.raw[$props.itemValue])"
476
+ :showSelect="$props.showSelect"
477
+ :itemTo="$props.itemTo"
478
+ :color="$props.color"
479
+ :item="item.raw"
480
+ :key="index"
382
481
  @update:modelValue="toggleSelect"
383
482
  >
384
483
  <template #[`item.top`]="props">
385
- <slot name="item.top" v-bind="props" />
484
+ <slot
485
+ name="item.top"
486
+ v-bind="props"
487
+ />
386
488
  </template>
387
- <template v-for="(item, index) in itemsSlots" #[item.slotName]="props">
388
- <slot :name="item.slotName" v-bind="props">
489
+ <template
490
+ v-for="(item, index) in itemsSlots"
491
+ #[item.slotName]="props"
492
+ >
493
+ <slot
494
+ :name="item.slotName"
495
+ v-bind="props"
496
+ >
389
497
  <FSText
390
498
  :key="index"
391
499
  >
@@ -394,11 +502,14 @@
394
502
  </slot>
395
503
  </template>
396
504
  <template #[`item.bottom`]="props">
397
- <slot name="item.bottom" v-bind="props" />
505
+ <slot
506
+ name="item.bottom"
507
+ v-bind="props"
508
+ />
398
509
  </template>
399
510
  </FSDataIteratorItem>
400
511
  </slot>
401
- </template>
512
+ </FSDraggable>
402
513
  </FSRow>
403
514
  </template>
404
515
  </v-data-iterator>
@@ -421,11 +532,13 @@ import FSSelectField from "../fields/FSSelectField.vue";
421
532
  import FSFilterButton from "./FSFilterButton.vue";
422
533
  import FSHiddenButton from "./FSHiddenButton.vue";
423
534
  import FSHeaderButton from "./FSHeaderButton.vue";
424
- import FSContainer from "../FSContainer.vue";
535
+ import FSOptionGroup from "../FSOptionGroup.vue";
425
536
  import FSToggleSet from "../FSToggleSet.vue";
537
+ import FSDraggable from "./FSDraggable.vue";
426
538
  import FSCheckbox from "../FSCheckbox.vue";
427
539
  import FSCard from "../FSCard.vue";
428
540
  import FSChip from "../FSChip.vue";
541
+ import FSIcon from "../FSIcon.vue";
429
542
  import FSText from "../FSText.vue";
430
543
  import FSRow from "../FSRow.vue";
431
544
  import FSCol from "../FSCol.vue";
@@ -439,11 +552,13 @@ export default defineComponent({
439
552
  FSHeaderButton,
440
553
  FSSearchField,
441
554
  FSSelectField,
442
- FSContainer,
555
+ FSOptionGroup,
556
+ FSDraggable,
443
557
  FSToggleSet,
444
558
  FSCheckbox,
445
559
  FSCard,
446
560
  FSChip,
561
+ FSIcon,
447
562
  FSText,
448
563
  FSRow,
449
564
  FSCol
@@ -541,9 +656,19 @@ export default defineComponent({
541
656
  type: Number,
542
657
  required: false,
543
658
  default: 20
659
+ },
660
+ sortDraggable: {
661
+ type: Boolean,
662
+ required: false,
663
+ default: false
664
+ },
665
+ includeDraggable: {
666
+ type: Boolean,
667
+ required: false,
668
+ default: false
544
669
  }
545
670
  },
546
- emits: ["update:modelValue", "update:headers", "update:filters", "update:mode", "update:sortBy", "update:rowsPerPage", "update:page", "click:row"],
671
+ emits: ["update:modelValue", "update:headers", "update:filters", "update:mode", "update:sortBy", "update:rowsPerPage", "update:page", "update:include", "update:items", "click:row"],
547
672
  setup(props, { emit }) {
548
673
  const { isExtraSmall } = useBreakpoints();
549
674
  const { $tr } = useTranslationsProvider();
@@ -583,6 +708,8 @@ export default defineComponent({
583
708
  delete slots["no-data"];
584
709
  delete slots["header.data-table-select"];
585
710
  delete slots["item.data-table-select"];
711
+ delete slots["header.data-table-draggable"];
712
+ delete slots["item.data-table-draggable"];
586
713
  delete slots["header.data-table-group"];
587
714
  delete slots["item.data-table-group"];
588
715
  delete slots["group-header"];
@@ -606,8 +733,8 @@ export default defineComponent({
606
733
 
607
734
  const style = computed((): { [code: string]: string } & Partial<CSSStyleDeclaration> => {
608
735
  return {
609
- "--fs-data-table-background-color" : backgrounds.base,
610
- "--fs-data-table-border-color" : lights.base
736
+ "--fs-data-table-background-color": backgrounds.base,
737
+ "--fs-data-table-border-color": lights.base
611
738
  };
612
739
  });
613
740
 
@@ -620,13 +747,19 @@ export default defineComponent({
620
747
  });
621
748
 
622
749
  const extraHeaders = computed((): any[] => {
623
- const extra = [];
750
+ const extra: { key: string, width: string }[] = [];
624
751
  if (props.groupBy) {
625
752
  extra.push({
626
753
  key: "data-table-group",
627
754
  width: "0%"
628
755
  });
629
756
  }
757
+ if (props.sortDraggable || props.includeDraggable) {
758
+ extra.push({
759
+ key: "data-table-draggable",
760
+ width: "0%"
761
+ });
762
+ }
630
763
  if (props.showSelect) {
631
764
  extra.push({
632
765
  key: "data-table-select",
@@ -716,6 +849,31 @@ export default defineComponent({
716
849
  }
717
850
  });
718
851
 
852
+ const onClickRow = computed(() => {
853
+ if (!!getCurrentInstance()?.vnode.props?.["onClick:row"] || props.itemTo) {
854
+ return (event: PointerEvent, row: any) => {
855
+ if (props.itemTo && router) {
856
+ if (event.metaKey || event.ctrlKey || event.button === 1) {
857
+ window.open(router.resolve(props.itemTo(row.item)).href, "_blank");
858
+ }
859
+ else {
860
+ router.push(props.itemTo(row.item));
861
+ }
862
+ }
863
+ else {
864
+ emit("click:row", row.item);
865
+ }
866
+ };
867
+ }
868
+ else {
869
+ return null;
870
+ }
871
+ });
872
+
873
+ const draggableDisabled = computed(() => {
874
+ return (!props.sortDraggable && !props.includeDraggable) || !(innerSortBy.value === null || innerSortBy.value === undefined);
875
+ });
876
+
719
877
  const toggleSelectAll = (allSelected: boolean): void => {
720
878
  if (allSelected) {
721
879
  innerValue.value = [];
@@ -875,11 +1033,11 @@ export default defineComponent({
875
1033
  if (!intersectionObserver.value) {
876
1034
  intersectionObserver.value = new IntersectionObserver(entries => {
877
1035
  entries.forEach((entry) => {
878
- if (entry.boundingClientRect.bottom < window.innerHeight * 1.25) {
879
- if (innerItems.value.length > size.value) {
880
- size.value = Math.min(size.value + props.sizeIterator, innerItems.value.length);
881
- }
1036
+ if (entry.boundingClientRect.bottom < window.innerHeight * 1.25) {
1037
+ if (innerItems.value.length > size.value) {
1038
+ size.value = Math.min(size.value + props.sizeIterator, innerItems.value.length);
882
1039
  }
1040
+ }
883
1041
  });
884
1042
  }, { threshold: [0.9] });
885
1043
  }
@@ -890,26 +1048,111 @@ export default defineComponent({
890
1048
  }
891
1049
  }
892
1050
 
893
- const onClickRow = computed(() => {
894
- if (!!getCurrentInstance()?.vnode.props?.['onClick:row'] || props.itemTo) {
895
- return (event: PointerEvent, row: any) => {
896
- if (props.itemTo && router) {
897
- if (event.metaKey || event.ctrlKey || event.button === 1) {
898
- window.open(router.resolve(props.itemTo(row.item)).href, "_blank");
1051
+ const changeIndex = (oldIndex: number, newIndex: number) => {
1052
+ if (oldIndex === newIndex) {
1053
+ return;
1054
+ }
1055
+ const items = innerItems.value.slice();
1056
+ const itemToMove = items.splice(oldIndex, 1)[0];
1057
+ items.splice(newIndex, 0, itemToMove);
1058
+ return items;
1059
+ };
1060
+
1061
+ const resetRowIndex = (initialIndex: number, currentIndex: number, draggedElement: HTMLElement, tbodyElement: HTMLElement) => {
1062
+ if (initialIndex > currentIndex) {
1063
+ tbodyElement.children[initialIndex].insertAdjacentElement("afterend", draggedElement);
1064
+ }
1065
+ else {
1066
+ tbodyElement.children[initialIndex].insertAdjacentElement("beforebegin", draggedElement);
1067
+ }
1068
+ };
1069
+
1070
+ const onDragOver = (event: DragEvent, elementSelector: string, elementContainerSelector: string) => {
1071
+ const dragged = document.querySelector(".fs-draggable-dragging") as HTMLElement;
1072
+
1073
+ if (dragged != null) {
1074
+ const target = (event.target as HTMLElement)?.closest(elementSelector);
1075
+
1076
+ if (target != null) {
1077
+ if (props.includeDraggable) {
1078
+ if (!props.sortDraggable) {
1079
+ target.classList.add("fs-dropzone-include");
899
1080
  }
900
1081
  else {
901
- router.push(props.itemTo(row.item));
1082
+ const rowHeight = target.clientHeight;
1083
+ const y = event.clientY - target.getBoundingClientRect().top;
1084
+
1085
+ if (y > rowHeight * (3 / 4)) {
1086
+ target.insertAdjacentElement("afterend", dragged);
1087
+ target.classList.remove("fs-dropzone-include");
1088
+ }
1089
+ else if (y < rowHeight * (1 / 4)) {
1090
+ target.insertAdjacentElement("beforebegin", dragged);
1091
+ target.classList.remove("fs-dropzone-include");
1092
+ }
1093
+ else if (dragged?.getAttribute("data-initial-index") !== null) {
1094
+ target.classList.add("fs-dropzone-include");
1095
+ const tbodyElement = (event.target as HTMLElement)?.closest(elementContainerSelector) as HTMLElement;
1096
+ resetRowIndex(+dragged?.getAttribute('data-initial-index'), Array.from(tbodyElement.children).indexOf(dragged), dragged, tbodyElement);
1097
+ }
902
1098
  }
903
1099
  }
904
- else {
905
- emit("click:row", row.item);
1100
+ else if (props.sortDraggable) {
1101
+ const rowHeight = target.clientHeight;
1102
+ const y = event.clientY - target.getBoundingClientRect().top;
1103
+ if (y > rowHeight / 2) {
1104
+ target.insertAdjacentElement("afterend", dragged);
1105
+ }
1106
+ else {
1107
+ target.insertAdjacentElement("beforebegin", dragged);
1108
+ }
906
1109
  }
907
- };
1110
+ }
1111
+
908
1112
  }
909
- else {
910
- return null;
1113
+ };
1114
+
1115
+ const onDragLeave = (event: DragEvent) => {
1116
+ const dropzone = (event.target as HTMLElement)?.closest(".fs-dropzone-include");
1117
+ if (dropzone && !(event.relatedTarget as HTMLElement)?.closest(".fs-dropzone-include")) {
1118
+ dropzone.classList.remove("fs-dropzone-include");
911
1119
  }
912
- });
1120
+ };
1121
+
1122
+ const onDragEnd = (event: DragEvent, draggedElement: HTMLElement, elementContainerSelector: string) => {
1123
+ const initialIndex = +(draggedElement.getAttribute("data-initial-index") ?? -1);
1124
+
1125
+ if (draggedElement != null && initialIndex !== -1) {
1126
+ if (props.sortDraggable) {
1127
+ const tbodyElement = (event.target as HTMLElement)?.closest(elementContainerSelector) as HTMLElement;
1128
+ const currentIndex = Array.from(tbodyElement.children).indexOf(draggedElement);
1129
+ const newItems = changeIndex(initialIndex, currentIndex);
1130
+
1131
+ if (newItems !== null && newItems !== undefined) {
1132
+ emit("update:items", newItems);
1133
+ }
1134
+ resetRowIndex(initialIndex, currentIndex, draggedElement, tbodyElement);
1135
+ }
1136
+ }
1137
+ };
1138
+
1139
+ const onDrop = (event: DragEvent, row: any, elementSelector: string) => {
1140
+ const draggedElement = document.querySelector(".fs-draggable-dragging");
1141
+
1142
+ if (draggedElement != null) {
1143
+ const target = (event.target as HTMLElement)?.closest(elementSelector);
1144
+ const draggedData = JSON.parse(event.dataTransfer?.getData("text/plain") ?? "");
1145
+ const itemsData = draggedData.item ?? draggedData.raw;
1146
+ const rowData = row.item ?? row.raw;
1147
+
1148
+ if (target != null) {
1149
+ if (props.includeDraggable && itemsData[props.itemValue] != rowData[props.itemValue]) {
1150
+ emit("update:include", { draggedItem: itemsData, targetItem: rowData })
1151
+ }
1152
+ target.closest(".fs-dropzone-include")?.classList.remove("fs-dropzone-include");
1153
+ }
1154
+ }
1155
+ };
913
1156
 
914
1157
  onMounted(() => {
915
1158
  computeFilters();
@@ -975,6 +1218,7 @@ export default defineComponent({
975
1218
  size,
976
1219
  onClickRow,
977
1220
  isExtraSmall,
1221
+ draggableDisabled,
978
1222
  toggleSelectAll,
979
1223
  toggleSelect,
980
1224
  updateHeader,
@@ -983,6 +1227,10 @@ export default defineComponent({
983
1227
  filterSlot,
984
1228
  sortColor,
985
1229
  sortIcon,
1230
+ onDrop,
1231
+ onDragOver,
1232
+ onDragLeave,
1233
+ onDragEnd
986
1234
  };
987
1235
  }
988
1236
  })
@@ -0,0 +1,177 @@
1
+ <template>
2
+ <div
3
+ :draggable="!$props.disabled"
4
+ :class="classes"
5
+ @touchstart="onTouchStart"
6
+ @touchmove="onTouchMove"
7
+ @touchend="onTouchEnd"
8
+ @dragstart="onDragStart"
9
+ @dragend="onDragEnd"
10
+ @dragover.prevent
11
+ >
12
+ <slot />
13
+ </div>
14
+ </template>
15
+
16
+ <script lang="ts">
17
+ import { computed, defineComponent, ref } from "vue";
18
+
19
+ export default defineComponent({
20
+ name: "FSDraggable",
21
+ props: {
22
+ elementSelector: {
23
+ type: String,
24
+ default: null,
25
+ },
26
+ item: {
27
+ type: Object,
28
+ default: null,
29
+ },
30
+ disabled: {
31
+ type: Boolean,
32
+ default: false,
33
+ },
34
+ },
35
+ emits: ["update:dragstart", "update:dragend"],
36
+ setup(props, { emit }) {
37
+ let prevDragOverTarget: EventTarget | null = null;
38
+
39
+ const draggedElementCopy = ref<HTMLElement|null>(null);
40
+ const touchStartX = ref(0);
41
+ const touchStartY = ref(0);
42
+ const touchEndX = ref(0);
43
+ const touchEndY = ref(0);
44
+
45
+ const classes = computed((): string[] => {
46
+ const classNames = ["fs-draggable-item"];
47
+ if (!props.disabled) {
48
+ classNames.push("fs-draggable-enabled");
49
+ }
50
+ return classNames;
51
+ });
52
+
53
+ const onTouchStart = (event: TouchEvent) => {
54
+ if (props.disabled) {
55
+ return;
56
+ }
57
+ event.preventDefault();
58
+ const touch = event.touches[0];
59
+ touchStartX.value = touch.clientX;
60
+ touchStartY.value = touch.clientY;
61
+
62
+ const dragged = (event.target as HTMLElement)?.closest(props.elementSelector) as HTMLElement;
63
+ dragged.classList.add("fs-draggable-dragging");
64
+ dragged.dataset.initialIndex = props.item?.index ?? props.item?.value;
65
+
66
+ draggedElementCopy.value = dragged.cloneNode(true) as HTMLElement;
67
+ draggedElementCopy.value.style.position = "fixed";
68
+ draggedElementCopy.value.style.left = `${touchStartX.value - 25}px`;
69
+ draggedElementCopy.value.style.top = `${touchStartY.value - 25}px`;
70
+ draggedElementCopy.value.style.zIndex = "1000";
71
+ draggedElementCopy.value.style.pointerEvents = "none";
72
+ };
73
+
74
+ const onTouchMove = (event: TouchEvent) => {
75
+ if (props.disabled) {
76
+ return;
77
+ }
78
+ event.preventDefault();
79
+ const touch = event.touches[0];
80
+ touchEndX.value = touch.clientX;
81
+ touchEndY.value = touch.clientY;
82
+
83
+ draggedElementCopy.value.style.left = `${touchEndX.value - 25}px`;
84
+ draggedElementCopy.value.style.top = `${touchEndY.value - 25}px`;
85
+ document.body.appendChild(draggedElementCopy.value);
86
+ const dragOverTarget = document.elementFromPoint(touchEndX.value, touchEndY.value)?.closest(props.elementSelector);
87
+
88
+ if (dragOverTarget) {
89
+ const dragOverEvent = new Event("dragover", {
90
+ bubbles: true,
91
+ cancelable: true
92
+ });
93
+ dragOverTarget?.dispatchEvent(dragOverEvent);
94
+ if (dragOverTarget !== prevDragOverTarget) {
95
+ const dragLeaveEvent = new Event("dragleave", {
96
+ bubbles: true,
97
+ cancelable: true
98
+ });
99
+ prevDragOverTarget?.dispatchEvent(dragLeaveEvent);
100
+ }
101
+ prevDragOverTarget = dragOverTarget;
102
+ }
103
+ else if (prevDragOverTarget) {
104
+ const dragLeaveEvent = new Event("dragleave", {
105
+ bubbles: true,
106
+ cancelable: true
107
+ });
108
+ prevDragOverTarget?.dispatchEvent(dragLeaveEvent);
109
+ prevDragOverTarget = null;
110
+ }
111
+ };
112
+
113
+ const onTouchEnd = (event: TouchEvent) => {
114
+ if (props.disabled) {
115
+ return;
116
+ }
117
+ event.preventDefault();
118
+ const dragged = (event.target as HTMLElement)?.closest(props.elementSelector);
119
+ draggedElementCopy.value.remove();
120
+ draggedElementCopy.value = null;
121
+
122
+ const dropTarget = document.elementFromPoint(touchEndX.value, touchEndY.value);
123
+ const dragEndEvent = new Event("dragend");
124
+ Object.defineProperty(dragEndEvent, "srcElement", {
125
+ get: function () { return event.target; }
126
+ });
127
+ emit("update:dragend", dragEndEvent, dragged);
128
+
129
+ const dropEvent = new DragEvent("drop", {
130
+ bubbles: true,
131
+ cancelable: true,
132
+ });
133
+ dropEvent.dataTransfer?.setData("text/plain", JSON.stringify(props.item));
134
+ dropTarget?.dispatchEvent(dropEvent);
135
+
136
+ touchStartX.value = 0;
137
+ touchStartY.value = 0;
138
+ touchEndX.value = 0;
139
+ touchEndY.value = 0;
140
+ dragged.classList.remove("fs-draggable-dragging");
141
+ };
142
+
143
+ const onDragStart = (event: DragEvent) => {
144
+ if (props.disabled) {
145
+ event.preventDefault();
146
+ return;
147
+ }
148
+ const dragged = (event.target as HTMLElement)?.closest(props.elementSelector) as HTMLElement;
149
+ dragged.dataset.initialIndex = props.item?.index ?? props.item?.value;
150
+ event.dataTransfer?.setDragImage(dragged, 25, 25);
151
+
152
+ if (event.dataTransfer) {
153
+ event.dataTransfer.dropEffect = "move";
154
+ event.dataTransfer.effectAllowed = "move";
155
+ }
156
+ dragged?.classList.add("fs-draggable-dragging");
157
+ event.dataTransfer?.setData("text/plain", JSON.stringify(props.item));
158
+ emit("update:dragstart", event);
159
+ };
160
+
161
+ const onDragEnd = (event: DragEvent) => {
162
+ const dragged = (event.target as HTMLElement)?.closest(props.elementSelector);
163
+ dragged?.classList.remove("fs-draggable-dragging");
164
+ emit("update:dragend", event, dragged);
165
+ };
166
+
167
+ return {
168
+ classes,
169
+ onTouchStart,
170
+ onTouchMove,
171
+ onTouchEnd,
172
+ onDragStart,
173
+ onDragEnd
174
+ };
175
+ },
176
+ });
177
+ </script>
@@ -71,7 +71,7 @@ export const useColors = () => {
71
71
  soft: getSoft(seed).hex(),
72
72
  softContrast: getContrast(seed, dark).hex(),
73
73
  base: seed.hex(),
74
- baseContrast: getContrast(seed, base).hex(),
74
+ baseContrast: getContrast(seed, dark).hex(),
75
75
  dark: dark.hex(),
76
76
  darkContrast: getContrast(dark, light).hex()
77
77
  };
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.40",
4
+ "version": "0.0.42",
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.40",
14
- "@dative-gpi/foundation-shared-services": "0.0.40",
13
+ "@dative-gpi/foundation-shared-domain": "0.0.42",
14
+ "@dative-gpi/foundation-shared-services": "0.0.42",
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": "330530dc723ccb77b6bae29eca20fbafcc16895c"
35
+ "gitHead": "b04bea74796441bbb7176bcdd7d1a23317faa2b8"
36
36
  }
@@ -99,4 +99,4 @@
99
99
  margin-top: -8px;
100
100
  height: 10px;
101
101
  width: 100%;
102
- }
102
+ }
@@ -0,0 +1,25 @@
1
+ .fs-draggable-dragging {
2
+ opacity: 0.4;
3
+ filter: blur(1px);
4
+ }
5
+
6
+ .fs-dropzone-include {
7
+ transition: all 0.28s cubic-bezier(0.4, 0, 0.2, 1);
8
+ filter: brightness(0.85) contrast(1.1);
9
+ }
10
+
11
+ .fs-draggable-item {
12
+ transition: all 0.28s cubic-bezier(0.4, 0, 0.2, 1);
13
+ }
14
+
15
+ div.fs-dropzone-include {
16
+ transform: scale(1.04);
17
+ }
18
+
19
+ .fs-draggable-enabled {
20
+ cursor: move ;
21
+ cursor: grab ;
22
+ cursor: -moz-grab ;
23
+ cursor: -webkit-grab ;
24
+ user-select: none;
25
+ }
@@ -19,6 +19,7 @@
19
19
  @import "fs_date_field.scss";
20
20
  @import "fs_dialog.scss";
21
21
  @import "fs_divider.scss";
22
+ @import "fs_draggable.scss";
22
23
  @import "fs_error_toast.scss";
23
24
  @import "fs_fade_out.scss";
24
25
  @import "fs_filter_button.scss";