@flux-ui/components 3.0.0-next.57 → 3.0.0-next.58
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/dist/component/FluxKanban.vue.d.ts +17 -5
- package/dist/component/FluxKanbanCard.vue.d.ts +1 -0
- package/dist/component/FluxKanbanColumn.vue.d.ts +13 -2
- package/dist/composable/private/useAsyncFilterOptions.d.ts +1 -1
- package/dist/composable/private/useKanban.d.ts +13 -3
- package/dist/data/di.d.ts +29 -0
- package/dist/index.css +152 -32
- package/dist/index.js +802 -177
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/component/FluxKanban.vue +83 -5
- package/src/component/FluxKanbanCard.vue +107 -13
- package/src/component/FluxKanbanColumn.vue +200 -13
- package/src/composable/private/useAsyncFilterOptions.ts +8 -14
- package/src/composable/private/useFilterOption.ts +1 -1
- package/src/composable/private/useKanban.ts +593 -13
- package/src/css/component/FluxKanban.module.scss +205 -0
- package/src/data/di.ts +33 -0
- package/src/data/filter.ts +1 -1
- package/src/css/component/FluxKanbanCard.module.scss +0 -35
- package/src/css/component/FluxKanbanColumn.module.scss +0 -49
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flux-ui/components",
|
|
3
3
|
"description": "A set of opiniated UI components.",
|
|
4
|
-
"version": "3.0.0-next.
|
|
4
|
+
"version": "3.0.0-next.58",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"funding": "https://github.com/sponsors/basmilius",
|
|
@@ -49,8 +49,8 @@
|
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@basmilius/common": "^3.21.0",
|
|
51
51
|
"@basmilius/utils": "^3.21.0",
|
|
52
|
-
"@flux-ui/internals": "3.0.0-next.
|
|
53
|
-
"@flux-ui/types": "3.0.0-next.
|
|
52
|
+
"@flux-ui/internals": "3.0.0-next.58",
|
|
53
|
+
"@flux-ui/types": "3.0.0-next.58",
|
|
54
54
|
"@fortawesome/fontawesome-common-types": "^7.2.0",
|
|
55
55
|
"clsx": "^2.1.1",
|
|
56
56
|
"imask": "^7.6.1",
|
|
@@ -1,25 +1,103 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
2
|
+
<div
|
|
3
|
+
ref="root"
|
|
4
|
+
role="application"
|
|
5
|
+
aria-roledescription="Kanban board"
|
|
6
|
+
:aria-label="ariaLabel"
|
|
7
|
+
:class="[$style.kanban, isDragging && $style.isBoardDragging]">
|
|
3
8
|
<slot/>
|
|
9
|
+
|
|
10
|
+
<div
|
|
11
|
+
aria-live="polite"
|
|
12
|
+
aria-atomic="true"
|
|
13
|
+
:class="$style.kanbanLiveRegion">
|
|
14
|
+
{{ liveMessage }}
|
|
15
|
+
</div>
|
|
4
16
|
</div>
|
|
5
17
|
</template>
|
|
6
18
|
|
|
7
19
|
<script
|
|
8
20
|
lang="ts"
|
|
9
21
|
setup>
|
|
10
|
-
import type { FluxKanbanMoveEvent } from '@flux-ui/types';
|
|
11
|
-
import { provide } from 'vue';
|
|
12
|
-
import { FluxKanbanInjectionKey } from '$flux/data/di';
|
|
22
|
+
import type { FluxKanbanMoveColumnEvent, FluxKanbanMoveEvent } from '@flux-ui/types';
|
|
23
|
+
import { computed, onBeforeUnmount, onMounted, provide, ref, toRef, useTemplateRef, watch } from 'vue';
|
|
24
|
+
import { FluxDisabledInjectionKey, FluxKanbanInjectionKey } from '$flux/data/di';
|
|
13
25
|
import { useKanban } from '$flux/composable/private/useKanban';
|
|
14
26
|
import $style from '$flux/css/component/FluxKanban.module.scss';
|
|
15
27
|
|
|
28
|
+
const {
|
|
29
|
+
ariaLabel,
|
|
30
|
+
canMove,
|
|
31
|
+
disabled = false,
|
|
32
|
+
reorderableColumns = false
|
|
33
|
+
} = defineProps<{
|
|
34
|
+
readonly ariaLabel?: string;
|
|
35
|
+
readonly canMove?: (event: FluxKanbanMoveEvent) => boolean;
|
|
36
|
+
readonly disabled?: boolean;
|
|
37
|
+
readonly reorderableColumns?: boolean;
|
|
38
|
+
}>();
|
|
39
|
+
|
|
16
40
|
const emit = defineEmits<{
|
|
17
41
|
move: [FluxKanbanMoveEvent];
|
|
42
|
+
moveColumn: [FluxKanbanMoveColumnEvent];
|
|
18
43
|
}>();
|
|
19
44
|
|
|
20
45
|
defineSlots<{
|
|
21
46
|
default?(): any;
|
|
22
47
|
}>();
|
|
23
48
|
|
|
24
|
-
|
|
49
|
+
const root = useTemplateRef('root');
|
|
50
|
+
const liveMessage = ref('');
|
|
51
|
+
|
|
52
|
+
const disabledRef = toRef(() => disabled);
|
|
53
|
+
const reorderableColumnsRef = toRef(() => reorderableColumns);
|
|
54
|
+
const canMoveRef = toRef(() => canMove);
|
|
55
|
+
|
|
56
|
+
const kanban = useKanban({
|
|
57
|
+
disabled: disabledRef,
|
|
58
|
+
reorderableColumns: reorderableColumnsRef,
|
|
59
|
+
canMove: canMoveRef,
|
|
60
|
+
onMove: event => emit('move', event),
|
|
61
|
+
onMoveColumn: event => emit('moveColumn', event),
|
|
62
|
+
onAnnounce: message => {
|
|
63
|
+
liveMessage.value = '';
|
|
64
|
+
requestAnimationFrame(() => {
|
|
65
|
+
liveMessage.value = message;
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const isDragging = computed(() => kanban.dragState.value !== null || kanban.columnDragState.value !== null);
|
|
71
|
+
|
|
72
|
+
function onPointerMove(evt: DragEvent | PointerEvent): void {
|
|
73
|
+
kanban.onPointerMove(evt.clientX, evt.clientY);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function onWindowDrop(): void {
|
|
77
|
+
kanban.endDrag();
|
|
78
|
+
kanban.endColumnDrag();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
onMounted(() => {
|
|
82
|
+
kanban.setBoardElement(root.value);
|
|
83
|
+
window.addEventListener('drag', onPointerMove);
|
|
84
|
+
window.addEventListener('dragend', onWindowDrop);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
onBeforeUnmount(() => {
|
|
88
|
+
kanban.setBoardElement(null);
|
|
89
|
+
window.removeEventListener('drag', onPointerMove);
|
|
90
|
+
window.removeEventListener('dragend', onWindowDrop);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
watch(() => disabled, value => {
|
|
94
|
+
if (value) {
|
|
95
|
+
kanban.endDrag();
|
|
96
|
+
kanban.endColumnDrag();
|
|
97
|
+
kanban.cancelKeyboardDrop();
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
provide(FluxKanbanInjectionKey, kanban);
|
|
102
|
+
provide(FluxDisabledInjectionKey, disabledRef);
|
|
25
103
|
</script>
|
|
@@ -2,15 +2,25 @@
|
|
|
2
2
|
<div
|
|
3
3
|
ref="root"
|
|
4
4
|
data-kanban-card
|
|
5
|
+
role="listitem"
|
|
6
|
+
aria-roledescription="Kanban card"
|
|
7
|
+
:aria-disabled="disabledState ? true : undefined"
|
|
8
|
+
:aria-grabbed="isGrabbed ? true : undefined"
|
|
5
9
|
:class="[
|
|
6
10
|
$style.kanbanCard,
|
|
7
11
|
isDragging && $style.isDragging,
|
|
8
|
-
|
|
12
|
+
isGrabbed && $style.isGrabbed,
|
|
13
|
+
isDropBefore && $style.isDropBefore,
|
|
14
|
+
isDropBefore && !kanban.isDropAllowed.value && $style.isDropBeforeDisallowed,
|
|
15
|
+
disabledState && $style.isDisabled
|
|
9
16
|
]"
|
|
10
|
-
draggable="
|
|
17
|
+
:draggable="!disabledState"
|
|
18
|
+
:tabindex="disabledState ? -1 : 0"
|
|
11
19
|
@dragstart="onDragStart"
|
|
12
20
|
@dragend="onDragEnd"
|
|
13
|
-
@dragover.
|
|
21
|
+
@dragover.stop="onDragOver"
|
|
22
|
+
@focus="onFocus"
|
|
23
|
+
@keydown="onKeyDown">
|
|
14
24
|
<slot/>
|
|
15
25
|
</div>
|
|
16
26
|
</template>
|
|
@@ -18,13 +28,19 @@
|
|
|
18
28
|
<script
|
|
19
29
|
lang="ts"
|
|
20
30
|
setup>
|
|
21
|
-
import { computed, inject, onMounted,
|
|
31
|
+
import { computed, inject, onBeforeUnmount, onMounted, toRef, unref, useTemplateRef, watch } from 'vue';
|
|
22
32
|
import { FluxKanbanInjectionKey } from '$flux/data/di';
|
|
23
|
-
import
|
|
24
|
-
|
|
25
|
-
|
|
33
|
+
import useDisabled from '$flux/composable/useDisabled';
|
|
34
|
+
import $style from '$flux/css/component/FluxKanban.module.scss';
|
|
35
|
+
|
|
36
|
+
const {
|
|
37
|
+
cardId,
|
|
38
|
+
columnId,
|
|
39
|
+
disabled = false
|
|
40
|
+
} = defineProps<{
|
|
26
41
|
readonly cardId: string | number;
|
|
27
42
|
readonly columnId: string | number;
|
|
43
|
+
readonly disabled?: boolean;
|
|
28
44
|
}>();
|
|
29
45
|
|
|
30
46
|
defineSlots<{
|
|
@@ -33,8 +49,10 @@
|
|
|
33
49
|
|
|
34
50
|
const kanban = inject(FluxKanbanInjectionKey)!;
|
|
35
51
|
const root = useTemplateRef('root');
|
|
52
|
+
const disabledState = useDisabled(toRef(() => disabled));
|
|
36
53
|
|
|
37
|
-
const isDragging = computed(() => unref(kanban.dragState)?.cardId === cardId);
|
|
54
|
+
const isDragging = computed(() => unref(kanban.dragState)?.cardId === cardId && unref(kanban.dragState)?.mode === 'pointer');
|
|
55
|
+
const isGrabbed = computed(() => kanban.isCardGrabbed(cardId));
|
|
38
56
|
|
|
39
57
|
const isDropBefore = computed(() => {
|
|
40
58
|
const state = unref(kanban.dragState);
|
|
@@ -50,19 +68,42 @@
|
|
|
50
68
|
if (root.value) {
|
|
51
69
|
kanban.registerCard(root.value, cardId);
|
|
52
70
|
}
|
|
71
|
+
|
|
72
|
+
if (kanban.isCardGrabbed(cardId)) {
|
|
73
|
+
root.value?.focus();
|
|
74
|
+
}
|
|
53
75
|
});
|
|
54
76
|
|
|
55
|
-
|
|
77
|
+
onBeforeUnmount(() => {
|
|
56
78
|
if (root.value) {
|
|
57
79
|
kanban.unregisterCard(root.value);
|
|
58
80
|
}
|
|
59
81
|
});
|
|
60
82
|
|
|
83
|
+
watch(() => cardId, (newId, oldId) => {
|
|
84
|
+
if (root.value && oldId !== undefined) {
|
|
85
|
+
kanban.unregisterCard(root.value);
|
|
86
|
+
kanban.registerCard(root.value, newId);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
61
90
|
function onDragStart(evt: DragEvent): void {
|
|
91
|
+
if (unref(disabledState)) {
|
|
92
|
+
evt.preventDefault();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
62
96
|
kanban.startDrag(cardId, columnId);
|
|
63
97
|
|
|
64
98
|
if (evt.dataTransfer) {
|
|
65
99
|
evt.dataTransfer.effectAllowed = 'move';
|
|
100
|
+
evt.dataTransfer.setData('text/plain', `card:${String(cardId)}`);
|
|
101
|
+
|
|
102
|
+
if (root.value) {
|
|
103
|
+
const offsetX = evt.offsetX || root.value.getBoundingClientRect().width / 2;
|
|
104
|
+
const offsetY = evt.offsetY || 12;
|
|
105
|
+
evt.dataTransfer.setDragImage(root.value, offsetX, offsetY);
|
|
106
|
+
}
|
|
66
107
|
}
|
|
67
108
|
}
|
|
68
109
|
|
|
@@ -70,21 +111,25 @@
|
|
|
70
111
|
kanban.endDrag();
|
|
71
112
|
}
|
|
72
113
|
|
|
114
|
+
function onFocus(): void {
|
|
115
|
+
root.value?.scrollIntoView({block: 'nearest', inline: 'nearest', behavior: 'smooth'});
|
|
116
|
+
}
|
|
117
|
+
|
|
73
118
|
function onDragOver(evt: DragEvent): void {
|
|
74
119
|
const state = unref(kanban.dragState);
|
|
75
120
|
|
|
76
|
-
if (!state) {
|
|
121
|
+
if (!state || unref(disabledState)) {
|
|
77
122
|
return;
|
|
78
123
|
}
|
|
79
124
|
|
|
125
|
+
evt.preventDefault();
|
|
126
|
+
|
|
80
127
|
const cardEl = evt.currentTarget as Element;
|
|
81
128
|
const rect = cardEl.getBoundingClientRect();
|
|
82
129
|
|
|
83
130
|
if (evt.clientY < rect.top + rect.height / 2) {
|
|
84
|
-
// Drop before this card
|
|
85
131
|
kanban.updateDropTarget(columnId, cardId);
|
|
86
132
|
} else {
|
|
87
|
-
// Drop after this card = before the next sibling card
|
|
88
133
|
let next = cardEl.nextElementSibling;
|
|
89
134
|
|
|
90
135
|
while (next) {
|
|
@@ -98,8 +143,57 @@
|
|
|
98
143
|
next = next.nextElementSibling;
|
|
99
144
|
}
|
|
100
145
|
|
|
101
|
-
// No next sibling → append to end of column
|
|
102
146
|
kanban.updateDropTarget(columnId, null);
|
|
103
147
|
}
|
|
104
148
|
}
|
|
149
|
+
|
|
150
|
+
function onKeyDown(evt: KeyboardEvent): void {
|
|
151
|
+
if (unref(disabledState)) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const state = unref(kanban.dragState);
|
|
156
|
+
const grabbed = state !== null && state.mode === 'keyboard' && state.cardId === cardId;
|
|
157
|
+
|
|
158
|
+
if (!grabbed) {
|
|
159
|
+
if (evt.key === ' ' || evt.key === 'Enter') {
|
|
160
|
+
evt.preventDefault();
|
|
161
|
+
kanban.grabCard(cardId, columnId);
|
|
162
|
+
}
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
switch (evt.key) {
|
|
167
|
+
case 'ArrowUp':
|
|
168
|
+
evt.preventDefault();
|
|
169
|
+
kanban.moveKeyboard('up');
|
|
170
|
+
break;
|
|
171
|
+
|
|
172
|
+
case 'ArrowDown':
|
|
173
|
+
evt.preventDefault();
|
|
174
|
+
kanban.moveKeyboard('down');
|
|
175
|
+
break;
|
|
176
|
+
|
|
177
|
+
case 'ArrowLeft':
|
|
178
|
+
evt.preventDefault();
|
|
179
|
+
kanban.moveKeyboard('left');
|
|
180
|
+
break;
|
|
181
|
+
|
|
182
|
+
case 'ArrowRight':
|
|
183
|
+
evt.preventDefault();
|
|
184
|
+
kanban.moveKeyboard('right');
|
|
185
|
+
break;
|
|
186
|
+
|
|
187
|
+
case ' ':
|
|
188
|
+
case 'Enter':
|
|
189
|
+
evt.preventDefault();
|
|
190
|
+
kanban.commitKeyboardDrop();
|
|
191
|
+
break;
|
|
192
|
+
|
|
193
|
+
case 'Escape':
|
|
194
|
+
evt.preventDefault();
|
|
195
|
+
kanban.cancelKeyboardDrop();
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
105
199
|
</script>
|
|
@@ -1,8 +1,27 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
ref="root"
|
|
4
|
+
role="list"
|
|
5
|
+
aria-roledescription="Kanban column"
|
|
6
|
+
:aria-label="label"
|
|
7
|
+
:aria-disabled="disabledState ? true : undefined"
|
|
8
|
+
:class="[
|
|
9
|
+
$style.kanbanColumn,
|
|
10
|
+
isOver && $style.isOver,
|
|
11
|
+
isOver && !kanban.isDropAllowed.value && $style.isDropDisallowed,
|
|
12
|
+
isReorderable && $style.isReorderable,
|
|
13
|
+
isColumnDragging && $style.isColumnDragging,
|
|
14
|
+
isColumnDropBefore && $style.isColumnDropBefore,
|
|
15
|
+
disabledState && $style.isDisabled
|
|
16
|
+
]">
|
|
17
|
+
<div
|
|
18
|
+
:class="$style.kanbanColumnHeader"
|
|
19
|
+
:draggable="isReorderable && !disabledState"
|
|
20
|
+
:tabindex="isReorderable && !disabledState ? 0 : undefined"
|
|
21
|
+
@dragstart="onColumnDragStart"
|
|
22
|
+
@dragend="onColumnDragEnd"
|
|
23
|
+
@dragover="onColumnDragOver"
|
|
24
|
+
@drop="onColumnDrop">
|
|
6
25
|
<slot name="header">
|
|
7
26
|
<span :class="$style.kanbanColumnLabel">{{ label }}</span>
|
|
8
27
|
</slot>
|
|
@@ -11,16 +30,32 @@
|
|
|
11
30
|
</div>
|
|
12
31
|
|
|
13
32
|
<div
|
|
33
|
+
ref="body"
|
|
14
34
|
:class="$style.kanbanColumnBody"
|
|
15
35
|
@dragenter="onDragEnter"
|
|
16
36
|
@dragleave="onDragLeave"
|
|
17
|
-
@dragover
|
|
18
|
-
@drop
|
|
37
|
+
@dragover="onDragOver"
|
|
38
|
+
@drop="onDrop">
|
|
19
39
|
<slot/>
|
|
20
40
|
|
|
21
41
|
<div
|
|
22
|
-
v-if="
|
|
23
|
-
:class="$style.
|
|
42
|
+
v-if="isEmpty"
|
|
43
|
+
:class="$style.kanbanColumnEmpty">
|
|
44
|
+
<slot name="empty"/>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<div
|
|
48
|
+
:class="[
|
|
49
|
+
$style.kanbanDropIndicator,
|
|
50
|
+
isDropEnd && $style.isDropEndActive,
|
|
51
|
+
isDropEnd && !kanban.isDropAllowed.value && $style.isDropEndDisallowed
|
|
52
|
+
]"/>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<div
|
|
56
|
+
v-if="hasFooter"
|
|
57
|
+
:class="$style.kanbanColumnFooter">
|
|
58
|
+
<slot name="footer"/>
|
|
24
59
|
</div>
|
|
25
60
|
</div>
|
|
26
61
|
</template>
|
|
@@ -28,12 +63,19 @@
|
|
|
28
63
|
<script
|
|
29
64
|
lang="ts"
|
|
30
65
|
setup>
|
|
31
|
-
import { computed, inject, ref, unref } from 'vue';
|
|
32
|
-
import {
|
|
33
|
-
import
|
|
66
|
+
import { Comment, Text, computed, inject, onBeforeUnmount, onMounted, provide, ref, toRef, unref, useSlots, useTemplateRef, watch } from 'vue';
|
|
67
|
+
import { flattenVNodeTree } from '@flux-ui/internals';
|
|
68
|
+
import { FluxDisabledInjectionKey, FluxKanbanInjectionKey } from '$flux/data/di';
|
|
69
|
+
import useDisabled from '$flux/composable/useDisabled';
|
|
70
|
+
import $style from '$flux/css/component/FluxKanban.module.scss';
|
|
34
71
|
|
|
35
|
-
const {
|
|
72
|
+
const {
|
|
73
|
+
columnId,
|
|
74
|
+
disabled = false,
|
|
75
|
+
label
|
|
76
|
+
} = defineProps<{
|
|
36
77
|
readonly columnId: string | number;
|
|
78
|
+
readonly disabled?: boolean;
|
|
37
79
|
readonly label: string;
|
|
38
80
|
}>();
|
|
39
81
|
|
|
@@ -41,19 +83,62 @@
|
|
|
41
83
|
default?(): any;
|
|
42
84
|
header?(): any;
|
|
43
85
|
actions?(): any;
|
|
86
|
+
empty?(): any;
|
|
87
|
+
footer?(): any;
|
|
44
88
|
}>();
|
|
45
89
|
|
|
46
90
|
const kanban = inject(FluxKanbanInjectionKey)!;
|
|
91
|
+
const root = useTemplateRef('root');
|
|
92
|
+
const body = useTemplateRef('body');
|
|
93
|
+
const slots = useSlots();
|
|
94
|
+
|
|
95
|
+
const disabledState = useDisabled(toRef(() => disabled));
|
|
96
|
+
provide(FluxDisabledInjectionKey, disabledState);
|
|
47
97
|
|
|
48
98
|
let dragEnterCount = 0;
|
|
49
99
|
const isOver = ref(false);
|
|
50
100
|
|
|
101
|
+
const isReorderable = computed(() => unref(kanban.reorderableColumns) && !unref(disabledState));
|
|
102
|
+
|
|
51
103
|
const isDropEnd = computed(() => {
|
|
52
104
|
const state = unref(kanban.dragState);
|
|
53
105
|
return state !== null && state.dropColumnId === columnId && state.beforeCardId === null;
|
|
54
106
|
});
|
|
55
107
|
|
|
108
|
+
const isColumnDragging = computed(() => unref(kanban.columnDragState)?.columnId === columnId);
|
|
109
|
+
|
|
110
|
+
const isColumnDropBefore = computed(() => {
|
|
111
|
+
const state = unref(kanban.columnDragState);
|
|
112
|
+
|
|
113
|
+
if (!state || state.dropBeforeColumnId !== columnId || state.columnId === columnId) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return true;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
const isEmpty = computed(() => {
|
|
121
|
+
if (!slots.empty) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const defaultSlot = slots.default?.();
|
|
126
|
+
|
|
127
|
+
if (!defaultSlot || defaultSlot.length === 0) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const hasContent = flattenVNodeTree(defaultSlot).some(vnode => vnode.type !== Comment && vnode.type !== Text);
|
|
132
|
+
return !hasContent;
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const hasFooter = computed(() => !!slots.footer);
|
|
136
|
+
|
|
56
137
|
function onDragEnter(): void {
|
|
138
|
+
if (unref(disabledState)) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
57
142
|
dragEnterCount++;
|
|
58
143
|
isOver.value = true;
|
|
59
144
|
}
|
|
@@ -67,7 +152,12 @@
|
|
|
67
152
|
}
|
|
68
153
|
|
|
69
154
|
function onDragOver(evt: DragEvent): void {
|
|
70
|
-
|
|
155
|
+
if (unref(disabledState) || !unref(kanban.dragState)) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
evt.preventDefault();
|
|
160
|
+
|
|
71
161
|
const target = evt.target as Element;
|
|
72
162
|
const isOverCard = !!target.closest('[data-kanban-card]');
|
|
73
163
|
|
|
@@ -76,9 +166,106 @@
|
|
|
76
166
|
}
|
|
77
167
|
}
|
|
78
168
|
|
|
79
|
-
function onDrop(): void {
|
|
169
|
+
function onDrop(evt: DragEvent): void {
|
|
80
170
|
dragEnterCount = 0;
|
|
81
171
|
isOver.value = false;
|
|
172
|
+
|
|
173
|
+
if (unref(disabledState) || !unref(kanban.dragState)) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
evt.preventDefault();
|
|
82
178
|
kanban.commitDrop();
|
|
83
179
|
}
|
|
180
|
+
|
|
181
|
+
function onColumnDragStart(evt: DragEvent): void {
|
|
182
|
+
if (!unref(isReorderable)) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (evt.dataTransfer) {
|
|
187
|
+
evt.dataTransfer.effectAllowed = 'move';
|
|
188
|
+
evt.dataTransfer.setData('text/plain', `column:${String(columnId)}`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
kanban.startColumnDrag(columnId);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function onColumnDragEnd(): void {
|
|
195
|
+
kanban.endColumnDrag();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function onColumnDragOver(evt: DragEvent): void {
|
|
199
|
+
const state = unref(kanban.columnDragState);
|
|
200
|
+
|
|
201
|
+
if (!state || state.columnId === columnId) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
evt.preventDefault();
|
|
206
|
+
|
|
207
|
+
const headerEl = evt.currentTarget as Element;
|
|
208
|
+
const rect = headerEl.getBoundingClientRect();
|
|
209
|
+
|
|
210
|
+
if (evt.clientX < rect.left + rect.width / 2) {
|
|
211
|
+
kanban.updateColumnDropTarget(columnId);
|
|
212
|
+
} else {
|
|
213
|
+
let next = root.value?.nextElementSibling ?? null;
|
|
214
|
+
|
|
215
|
+
while (next) {
|
|
216
|
+
const info = kanban.getColumnInfo(next);
|
|
217
|
+
|
|
218
|
+
if (info) {
|
|
219
|
+
kanban.updateColumnDropTarget(info.columnId);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
next = next.nextElementSibling;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
kanban.updateColumnDropTarget(null);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function onColumnDrop(evt: DragEvent): void {
|
|
231
|
+
if (!unref(kanban.columnDragState)) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
evt.preventDefault();
|
|
236
|
+
kanban.commitColumnDrop();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
onMounted(() => {
|
|
240
|
+
if (root.value) {
|
|
241
|
+
kanban.registerColumn(root.value, columnId);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (body.value) {
|
|
245
|
+
kanban.setColumnBodyElement(columnId, body.value);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
onBeforeUnmount(() => {
|
|
250
|
+
if (root.value) {
|
|
251
|
+
kanban.unregisterColumn(root.value);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
kanban.setColumnBodyElement(columnId, null);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
watch(() => columnId, (newId, oldId) => {
|
|
258
|
+
if (oldId !== undefined) {
|
|
259
|
+
kanban.setColumnBodyElement(oldId, null);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (root.value) {
|
|
263
|
+
kanban.unregisterColumn(root.value);
|
|
264
|
+
kanban.registerColumn(root.value, newId);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (body.value) {
|
|
268
|
+
kanban.setColumnBodyElement(newId, body.value);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
84
271
|
</script>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useDebouncedRef, useLoaded } from '@basmilius/common';
|
|
2
2
|
import type { FluxFilterOptionRow, FluxFilterValue } from '@flux-ui/types';
|
|
3
|
-
import { type ComputedRef, type ModelRef,
|
|
3
|
+
import { computed, type ComputedRef, type ModelRef, type Ref, ref, unref, watch } from 'vue';
|
|
4
4
|
import { isFluxFilterOptionItem } from '$flux/data';
|
|
5
5
|
|
|
6
6
|
type UseAsyncFilterOptionsParams = {
|
|
@@ -11,25 +11,19 @@ type UseAsyncFilterOptionsParams = {
|
|
|
11
11
|
fetchSearch(searchQuery: string): Promise<FluxFilterOptionRow[]>;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
export default function ({
|
|
15
|
-
currentValueIds,
|
|
16
|
-
modelSearch,
|
|
17
|
-
fetchOptions: fetchOptionsProp,
|
|
18
|
-
fetchRelevant: fetchRelevantProp,
|
|
19
|
-
fetchSearch: fetchSearchProp
|
|
20
|
-
}: UseAsyncFilterOptionsParams) {
|
|
14
|
+
export default function (params: UseAsyncFilterOptionsParams) {
|
|
21
15
|
const {isLoading, loaded} = useLoaded();
|
|
22
|
-
const debouncedModelSearch = useDebouncedRef(modelSearch, 150) as unknown as Ref<string>;
|
|
23
|
-
const fetchOptions = computed(() => loaded(
|
|
24
|
-
const fetchRelevant = computed(() => loaded(
|
|
25
|
-
const fetchSearch = computed(() => loaded(
|
|
16
|
+
const debouncedModelSearch = useDebouncedRef(params.modelSearch, 150) as unknown as Ref<string>;
|
|
17
|
+
const fetchOptions = computed(() => loaded(params.fetchOptions));
|
|
18
|
+
const fetchRelevant = computed(() => loaded(params.fetchRelevant));
|
|
19
|
+
const fetchSearch = computed(() => loaded(params.fetchSearch));
|
|
26
20
|
|
|
27
21
|
const selectedOptions = ref<FluxFilterOptionRow[]>([]);
|
|
28
22
|
const visibleOptions = ref<FluxFilterOptionRow[]>([]);
|
|
29
23
|
|
|
30
24
|
const options = computed(() => {
|
|
31
25
|
const options: FluxFilterOptionRow[] = [];
|
|
32
|
-
const search = unref(modelSearch);
|
|
26
|
+
const search = unref(params.modelSearch);
|
|
33
27
|
const selected = unref(selectedOptions);
|
|
34
28
|
const visible = unref(visibleOptions);
|
|
35
29
|
|
|
@@ -50,7 +44,7 @@ export default function ({
|
|
|
50
44
|
return options;
|
|
51
45
|
});
|
|
52
46
|
|
|
53
|
-
watch(currentValueIds, async ids => {
|
|
47
|
+
watch(params.currentValueIds, async ids => {
|
|
54
48
|
if (ids.length === 0) {
|
|
55
49
|
return;
|
|
56
50
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FluxFilterValue, FluxFilterValueSingle } from '@flux-ui/types';
|
|
2
|
-
import { type ComputedRef,
|
|
2
|
+
import { computed, type ComputedRef, unref } from 'vue';
|
|
3
3
|
import { useFilterInjection } from '$flux/composable';
|
|
4
4
|
|
|
5
5
|
export type FilterOptionSingle = {
|