quasar-ui-danx 0.4.27 → 0.4.28
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/danx.es.js +24536 -24082
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +109 -109
- package/dist/danx.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/ActionTable/ActionTable.vue +29 -7
- package/src/components/ActionTable/Filters/FilterableField.vue +14 -2
- package/src/components/ActionTable/Form/ActionForm.vue +17 -12
- package/src/components/ActionTable/Form/Fields/DateField.vue +24 -20
- package/src/components/ActionTable/Form/Fields/DateRangeField.vue +57 -53
- package/src/components/ActionTable/Form/Fields/EditOnClickTextField.vue +9 -2
- package/src/components/ActionTable/Form/Fields/EditableDiv.vue +12 -12
- package/src/components/ActionTable/Form/Fields/FieldLabel.vue +1 -1
- package/src/components/ActionTable/Form/Fields/SelectField.vue +27 -6
- package/src/components/ActionTable/Form/Fields/SelectOrCreateField.vue +56 -0
- package/src/components/ActionTable/Form/Fields/index.ts +1 -0
- package/src/components/ActionTable/Layouts/ActionTableLayout.vue +20 -23
- package/src/components/ActionTable/Toolbars/ActionToolbar.vue +44 -36
- package/src/components/ActionTable/listControls.ts +3 -3
- package/src/components/DragAndDrop/ListItemDraggable.vue +38 -28
- package/src/components/DragAndDrop/dragAndDrop.ts +220 -220
- package/src/components/DragAndDrop/listDragAndDrop.ts +256 -227
- package/src/components/PanelsDrawer/PanelsDrawer.vue +7 -7
- package/src/components/PanelsDrawer/PanelsDrawerTabs.vue +3 -3
- package/src/components/Utility/Buttons/ShowHideButton.vue +86 -0
- package/src/components/Utility/Buttons/index.ts +1 -0
- package/src/components/Utility/Dialogs/ActionFormDialog.vue +30 -0
- package/src/components/Utility/Dialogs/CreateNewWithNameDialog.vue +26 -0
- package/src/components/Utility/Dialogs/RenderedFormDialog.vue +50 -0
- package/src/components/Utility/Dialogs/index.ts +3 -0
- package/src/helpers/actions.ts +84 -20
- package/src/helpers/formats.ts +23 -20
- package/src/helpers/objectStore.ts +24 -12
- package/src/types/actions.d.ts +12 -6
- package/src/types/controls.d.ts +23 -6
- package/types/vue-shims.d.ts +3 -2
package/package.json
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
class="dx-action-table overflow-hidden"
|
4
4
|
:class="{'dx-no-data': !hasData, 'dx-is-loading': loadingList || loadingSummary, 'dx-is-loading-list': loadingList}"
|
5
5
|
>
|
6
|
-
<ActionVnode />
|
7
6
|
<QTable
|
8
7
|
ref="actionTable"
|
9
8
|
:selected="selectedRows"
|
@@ -67,8 +66,7 @@
|
|
67
66
|
import { QTable } from "quasar";
|
68
67
|
import { computed, ref } from "vue";
|
69
68
|
import { getItem, setItem } from "../../helpers";
|
70
|
-
import { ActionTargetItem, ListControlsPagination, TableColumn } from "../../types";
|
71
|
-
import { ActionVnode } from "../Utility";
|
69
|
+
import { ActionTargetItem, ListControlsPagination, ResourceAction, TableColumn } from "../../types";
|
72
70
|
import { ActionTableColumn, ActionTableHeaderColumn } from "./Columns";
|
73
71
|
import EmptyTableState from "./EmptyTableState.vue";
|
74
72
|
import { mapSortBy, registerStickyScrolling } from "./listHelpers";
|
@@ -86,6 +84,7 @@ export interface Props {
|
|
86
84
|
loadingSummary?: boolean;
|
87
85
|
pagedItems?: any;
|
88
86
|
summary: any;
|
87
|
+
menuActions?: ResourceAction[];
|
89
88
|
columns: TableColumn[];
|
90
89
|
rowsPerPageOptions?: number[];
|
91
90
|
summaryColSpan?: number;
|
@@ -105,10 +104,33 @@ const props = withDefaults(defineProps<Props>(), {
|
|
105
104
|
const actionTable = ref(null);
|
106
105
|
registerStickyScrolling(actionTable);
|
107
106
|
|
108
|
-
const tableColumns = computed<TableColumn[]>(() =>
|
109
|
-
...column
|
110
|
-
|
111
|
-
|
107
|
+
const tableColumns = computed<TableColumn[]>(() => {
|
108
|
+
const columns = [...props.columns].map((column: TableColumn) => ({
|
109
|
+
...column,
|
110
|
+
field: column.field || column.name
|
111
|
+
}));
|
112
|
+
|
113
|
+
// Inject the Action Menu column if there are menu actions
|
114
|
+
if (props.menuActions?.length) {
|
115
|
+
const menuColumn = columns.find((column) => column.name === "menu");
|
116
|
+
const menuColumnOptions: TableColumn = {
|
117
|
+
name: "menu",
|
118
|
+
label: "",
|
119
|
+
required: true,
|
120
|
+
hideContent: true,
|
121
|
+
shrink: true,
|
122
|
+
actionMenu: props.menuActions
|
123
|
+
};
|
124
|
+
|
125
|
+
if (menuColumn) {
|
126
|
+
Object.assign(menuColumn, menuColumnOptions);
|
127
|
+
} else {
|
128
|
+
columns.unshift(menuColumnOptions);
|
129
|
+
}
|
130
|
+
}
|
131
|
+
|
132
|
+
return columns;
|
133
|
+
});
|
112
134
|
const hasData = computed(() => props.pagedItems?.data?.length);
|
113
135
|
const COLUMN_SETTINGS_KEY = `column-settings-${props.name}`;
|
114
136
|
const columnSettings = ref(getItem(COLUMN_SETTINGS_KEY) || {});
|
@@ -1,6 +1,15 @@
|
|
1
1
|
<template>
|
2
2
|
<div>
|
3
|
-
<template v-if="field.type === '
|
3
|
+
<template v-if="field.type === 'text'">
|
4
|
+
<TextField
|
5
|
+
:model-value="modelValue"
|
6
|
+
:label="field.label"
|
7
|
+
:placeholder="field.placeholder"
|
8
|
+
:debounce="1000"
|
9
|
+
@update:model-value="onUpdate"
|
10
|
+
/>
|
11
|
+
</template>
|
12
|
+
<template v-else-if="field.type === 'multi-select'">
|
4
13
|
<SelectField
|
5
14
|
v-if="field.options?.length > 0 || loading"
|
6
15
|
:model-value="modelValue"
|
@@ -41,6 +50,7 @@
|
|
41
50
|
v-else-if="field.type === 'date'"
|
42
51
|
:model-value="modelValue"
|
43
52
|
:label="field.label"
|
53
|
+
:clearable="field.clearable === undefined ? true : field.clearable"
|
44
54
|
class="mt-2"
|
45
55
|
@update:model-value="onUpdate"
|
46
56
|
/>
|
@@ -49,6 +59,7 @@
|
|
49
59
|
:model-value="modelValue"
|
50
60
|
:label="field.label"
|
51
61
|
:inline="!!field.inline"
|
62
|
+
:clearable="field.clearable === undefined ? true : field.clearable"
|
52
63
|
with-time
|
53
64
|
class="mt-2 reactive"
|
54
65
|
@update:model-value="onUpdate"
|
@@ -117,7 +128,8 @@ import {
|
|
117
128
|
MultiKeywordField,
|
118
129
|
NumberRangeField,
|
119
130
|
SelectField,
|
120
|
-
SelectWithChildrenField
|
131
|
+
SelectWithChildrenField,
|
132
|
+
TextField
|
121
133
|
} from "../Form/Fields";
|
122
134
|
|
123
135
|
const emit = defineEmits(["update:model-value"]);
|
@@ -1,16 +1,14 @@
|
|
1
1
|
<template>
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
</RenderedForm>
|
13
|
-
</div>
|
2
|
+
<RenderedForm
|
3
|
+
v-bind="renderedFormProps"
|
4
|
+
v-model:values="input"
|
5
|
+
empty-value=""
|
6
|
+
:saved-at="hideSavedAt ? undefined : target.updated_at"
|
7
|
+
:saving="action.isApplying"
|
8
|
+
@update:values="onUpdate"
|
9
|
+
>
|
10
|
+
<slot />
|
11
|
+
</RenderedForm>
|
14
12
|
</template>
|
15
13
|
<script setup lang="ts">
|
16
14
|
import { Ref, ref, watch } from "vue";
|
@@ -28,8 +26,10 @@ interface ActionFormProps {
|
|
28
26
|
clearable?: boolean;
|
29
27
|
fieldClass?: string;
|
30
28
|
savingClass?: string;
|
29
|
+
hideSavedAt?: boolean;
|
31
30
|
}
|
32
31
|
|
32
|
+
const emit = defineEmits(["saved"]);
|
33
33
|
const props = withDefaults(defineProps<ActionFormProps>(), {
|
34
34
|
fieldClass: "",
|
35
35
|
savingClass: undefined
|
@@ -58,4 +58,9 @@ watch(() => props.target, (target: ActionTargetItem) => {
|
|
58
58
|
}
|
59
59
|
}
|
60
60
|
});
|
61
|
+
|
62
|
+
async function onUpdate() {
|
63
|
+
await props.action.trigger(props.target, input.value);
|
64
|
+
emit("saved");
|
65
|
+
}
|
61
66
|
</script>
|
@@ -7,8 +7,8 @@
|
|
7
7
|
{{ label }}
|
8
8
|
</div>
|
9
9
|
<div class="flex items-center cursor-pointer">
|
10
|
-
<DateIcon class="w-5
|
11
|
-
<div class="font-bold ml-3 hover:
|
10
|
+
<DateIcon class="w-5" />
|
11
|
+
<div class="flex-grow font-bold ml-3 hover:opacity-70">
|
12
12
|
<template v-if="date">
|
13
13
|
{{ formattedDate }}
|
14
14
|
</template>
|
@@ -16,6 +16,15 @@
|
|
16
16
|
- -
|
17
17
|
</template>
|
18
18
|
</div>
|
19
|
+
<div v-if="clearable && date">
|
20
|
+
<QBtn
|
21
|
+
icon="close"
|
22
|
+
size="sm"
|
23
|
+
round
|
24
|
+
flat
|
25
|
+
@click.stop.prevent="(date = null) || onSave()"
|
26
|
+
/>
|
27
|
+
</div>
|
19
28
|
</div>
|
20
29
|
<QPopupProxy>
|
21
30
|
<QDate
|
@@ -26,34 +35,29 @@
|
|
26
35
|
</div>
|
27
36
|
</template>
|
28
37
|
|
29
|
-
<script setup>
|
38
|
+
<script setup lang="ts">
|
30
39
|
import { CalendarIcon as DateIcon } from "@heroicons/vue/outline";
|
31
40
|
import { computed, ref, watch } from "vue";
|
32
|
-
import { fDate,
|
41
|
+
import { fDate, parseDateTime } from "../../../../helpers";
|
33
42
|
|
34
43
|
const emit = defineEmits(["update:model-value"]);
|
35
|
-
const props = defineProps
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
label: {
|
41
|
-
type: String,
|
42
|
-
default: null
|
43
|
-
}
|
44
|
-
});
|
44
|
+
const props = defineProps<{
|
45
|
+
modelValue?: string | null;
|
46
|
+
label: string | null;
|
47
|
+
clearable: boolean;
|
48
|
+
}>();
|
45
49
|
|
46
50
|
const formattedDate = computed(() => {
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
+
if (props.modelValue) {
|
52
|
+
return fDate(parseDateTime(props.modelValue || "0000-00-00"));
|
53
|
+
}
|
54
|
+
return "- -";
|
51
55
|
});
|
52
56
|
|
53
|
-
const date = ref(props.modelValue);
|
57
|
+
const date = ref(parseDateTime(props.modelValue));
|
54
58
|
watch(() => props.modelValue, val => date.value = val);
|
55
59
|
|
56
60
|
function onSave() {
|
57
|
-
|
61
|
+
emit("update:model-value", date.value);
|
58
62
|
}
|
59
63
|
</script>
|
@@ -13,8 +13,8 @@
|
|
13
13
|
</template>
|
14
14
|
<template v-else>
|
15
15
|
<div class="flex items-center cursor-pointer">
|
16
|
-
<DateIcon class="w-5
|
17
|
-
<div class="font-bold ml-3 hover:
|
16
|
+
<DateIcon class="w-5 py-2" />
|
17
|
+
<div class="flex-grow font-bold ml-3 hover:opacity-70">
|
18
18
|
<template v-if="dateRangeValue">
|
19
19
|
{{ formattedDates.from }} - {{ formattedDates.to }}
|
20
20
|
</template>
|
@@ -22,6 +22,15 @@
|
|
22
22
|
- -
|
23
23
|
</template>
|
24
24
|
</div>
|
25
|
+
<div v-if="clearable && dateRange">
|
26
|
+
<QBtn
|
27
|
+
icon="close"
|
28
|
+
size="sm"
|
29
|
+
round
|
30
|
+
flat
|
31
|
+
@click.stop.prevent="(dateRange = null) || onSave()"
|
32
|
+
/>
|
33
|
+
</div>
|
25
34
|
</div>
|
26
35
|
<QPopupProxy>
|
27
36
|
<QDate
|
@@ -34,80 +43,75 @@
|
|
34
43
|
</div>
|
35
44
|
</template>
|
36
45
|
|
37
|
-
<script setup>
|
46
|
+
<script setup lang="ts">
|
38
47
|
import { CalendarIcon as DateIcon } from "@heroicons/vue/outline";
|
39
48
|
import { computed, ref, watch } from "vue";
|
40
49
|
import { fDate, parseQDate, parseQDateTime } from "../../../../helpers";
|
41
50
|
import FieldLabel from "./FieldLabel";
|
42
51
|
|
43
52
|
const emit = defineEmits(["update:model-value"]);
|
44
|
-
const props = defineProps
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
default: null
|
52
|
-
},
|
53
|
-
inline: Boolean,
|
54
|
-
withTime: Boolean
|
55
|
-
});
|
53
|
+
const props = defineProps<{
|
54
|
+
modelValue?: { from: string; to: string } | null;
|
55
|
+
label: string | null;
|
56
|
+
inline: boolean;
|
57
|
+
clearable: boolean;
|
58
|
+
withTime: boolean;
|
59
|
+
}>();
|
56
60
|
|
57
61
|
const formattedDates = computed(() => {
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
62
|
+
if (dateRangeValue.value) {
|
63
|
+
if (props.withTime) {
|
64
|
+
return {
|
65
|
+
from: fDate(parseQDateTime(dateRangeValue.value.from || "0000-00-00")),
|
66
|
+
to: fDate(parseQDateTime(dateRangeValue.value.to || "9999-12-31"))
|
67
|
+
};
|
68
|
+
}
|
65
69
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
70
|
+
return {
|
71
|
+
from: fDate(parseQDate(dateRangeValue.value.from || "0000-00-00")),
|
72
|
+
to: fDate(parseQDate(dateRangeValue.value.to || "9999-12-31"))
|
73
|
+
};
|
74
|
+
}
|
75
|
+
return {
|
76
|
+
from: null,
|
77
|
+
to: null
|
78
|
+
};
|
75
79
|
});
|
76
80
|
|
77
81
|
const dateRange = ref(toQDateValue(props.modelValue));
|
78
82
|
watch(() => props.modelValue, val => dateRange.value = toQDateValue(val));
|
79
83
|
|
80
84
|
function toQDateValue(val) {
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
+
if (val?.from && val?.to) {
|
86
|
+
return fDate(val.from) === fDate(val.to) ? val.from : val;
|
87
|
+
}
|
88
|
+
return null;
|
85
89
|
}
|
86
90
|
|
87
91
|
const dateRangeValue = computed(() => {
|
88
|
-
|
92
|
+
let range;
|
89
93
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
94
|
+
if (typeof dateRange.value === "string") {
|
95
|
+
range = {
|
96
|
+
from: dateRange.value,
|
97
|
+
to: dateRange.value
|
98
|
+
};
|
99
|
+
} else if (dateRange.value) {
|
100
|
+
range = {
|
101
|
+
from: dateRange.value.from,
|
102
|
+
to: dateRange.value.to
|
103
|
+
};
|
104
|
+
}
|
101
105
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
+
if (range?.from && range?.to && props.withTime && !range.from.includes("00:00:00")) {
|
107
|
+
range.from += " 00:00:00";
|
108
|
+
range.to += " 23:59:59";
|
109
|
+
}
|
106
110
|
|
107
|
-
|
111
|
+
return range;
|
108
112
|
});
|
109
113
|
|
110
114
|
function onSave() {
|
111
|
-
|
115
|
+
emit("update:model-value", dateRangeValue.value);
|
112
116
|
}
|
113
117
|
</script>
|
@@ -10,9 +10,9 @@
|
|
10
10
|
:contenteditable="!readonly && isEditing"
|
11
11
|
class="flex-grow p-2 rounded outline-none border-none"
|
12
12
|
:class="{[editingClass]: isEditing, [inputClass]: true}"
|
13
|
-
@input="
|
13
|
+
@input="onUpdate($event.target.innerText)"
|
14
14
|
>
|
15
|
-
{{ text }}
|
15
|
+
{{ isEditing ? editingText : text }}
|
16
16
|
</div>
|
17
17
|
<div v-if="!readonly">
|
18
18
|
<QBtn
|
@@ -43,6 +43,7 @@ export interface EditOnClickTextFieldProps {
|
|
43
43
|
|
44
44
|
const editableBox = ref<HTMLElement | null>(null);
|
45
45
|
const text = defineModel({ type: String });
|
46
|
+
const editingText = ref(text.value);
|
46
47
|
const props = withDefaults(defineProps<EditOnClickTextFieldProps>(), {
|
47
48
|
class: "hover:bg-slate-300",
|
48
49
|
editingClass: "bg-slate-500",
|
@@ -52,11 +53,17 @@ const isEditing = ref(false);
|
|
52
53
|
function onEdit() {
|
53
54
|
if (props.readonly) return;
|
54
55
|
isEditing.value = true;
|
56
|
+
editingText.value = text.value;
|
55
57
|
nextTick(() => {
|
56
58
|
editableBox.value?.focus();
|
57
59
|
});
|
58
60
|
}
|
59
61
|
|
62
|
+
function onUpdate(newText: string) {
|
63
|
+
editingText.value = newText;
|
64
|
+
text.value = newText;
|
65
|
+
}
|
66
|
+
|
60
67
|
</script>
|
61
68
|
|
62
69
|
<style lang="scss" scoped>
|
@@ -14,26 +14,26 @@ import { ref } from "vue";
|
|
14
14
|
|
15
15
|
const emit = defineEmits(["update:model-value", "change"]);
|
16
16
|
const props = defineProps({
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
17
|
+
modelValue: {
|
18
|
+
type: String,
|
19
|
+
required: true
|
20
|
+
},
|
21
|
+
debounceDelay: {
|
22
|
+
type: Number,
|
23
|
+
default: 1000
|
24
|
+
}
|
25
25
|
});
|
26
26
|
|
27
27
|
const text = ref(props.modelValue);
|
28
28
|
|
29
29
|
const debouncedChange = useDebounceFn(() => {
|
30
|
-
|
31
|
-
|
30
|
+
emit("update:model-value", text.value);
|
31
|
+
emit("change", text.value);
|
32
32
|
}, props.debounceDelay);
|
33
33
|
|
34
34
|
function onInput(e) {
|
35
|
-
|
36
|
-
|
35
|
+
text.value = e.target.innerText;
|
36
|
+
debouncedChange();
|
37
37
|
}
|
38
38
|
|
39
39
|
</script>
|
@@ -86,6 +86,8 @@ export interface Props extends QSelectProps {
|
|
86
86
|
options?: unknown[];
|
87
87
|
filterable?: boolean;
|
88
88
|
filterFn?: (val: string) => void;
|
89
|
+
selectByObject?: boolean;
|
90
|
+
optionLabel?: string | ((option) => string);
|
89
91
|
}
|
90
92
|
|
91
93
|
const emit = defineEmits(["update:model-value", "search", "update"]);
|
@@ -97,7 +99,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|
97
99
|
inputClass: "",
|
98
100
|
selectionClass: "",
|
99
101
|
options: () => [],
|
100
|
-
filterFn: null
|
102
|
+
filterFn: null,
|
103
|
+
optionLabel: "label"
|
101
104
|
});
|
102
105
|
|
103
106
|
const selectField = ref(null);
|
@@ -146,7 +149,11 @@ const selectedValue = computed(() => {
|
|
146
149
|
return v === null ? "__null__" : v;
|
147
150
|
}) || [];
|
148
151
|
} else {
|
149
|
-
|
152
|
+
if (props.modelValue === null) return "__null__";
|
153
|
+
|
154
|
+
if (props.selectByObject) return props.modelValue.value || props.modelValue.id;
|
155
|
+
|
156
|
+
return props.modelValue;
|
150
157
|
}
|
151
158
|
});
|
152
159
|
|
@@ -159,8 +166,14 @@ const selectedOptions = computed(() => {
|
|
159
166
|
if (!props.multiple) {
|
160
167
|
values = (values || values === 0) ? [values] : [];
|
161
168
|
}
|
169
|
+
|
170
|
+
const comparableValues = values.map((v) => {
|
171
|
+
if (v === "__null__") return null;
|
172
|
+
if (typeof v === "object") return v.value || v.id;
|
173
|
+
return v;
|
174
|
+
});
|
162
175
|
return computedOptions.value.filter((o) => {
|
163
|
-
return
|
176
|
+
return comparableValues.includes(o.value);
|
164
177
|
});
|
165
178
|
});
|
166
179
|
|
@@ -175,6 +188,7 @@ const selectedLabel = computed(() => {
|
|
175
188
|
if (!selectedOptions.value || selectedOptions.value.length === 0) {
|
176
189
|
return props.placeholder || "(Select Option)";
|
177
190
|
}
|
191
|
+
|
178
192
|
return selectedOptions.value[0].selectionLabel;
|
179
193
|
});
|
180
194
|
|
@@ -220,7 +234,7 @@ function resolveSelectionLabel(option) {
|
|
220
234
|
if (typeof props.selectionLabel === "function") {
|
221
235
|
return props.selectionLabel(option);
|
222
236
|
}
|
223
|
-
return option?.selectionLabel || option?.label;
|
237
|
+
return option?.selectionLabel || option?.label || (option && option[props.optionLabel]);
|
224
238
|
}
|
225
239
|
|
226
240
|
/**
|
@@ -232,7 +246,7 @@ function resolveValue(option) {
|
|
232
246
|
if (!option || typeof option === "string") {
|
233
247
|
return option;
|
234
248
|
}
|
235
|
-
let value = option.value;
|
249
|
+
let value = option.value || option.id;
|
236
250
|
if (typeof props.optionValue === "string") {
|
237
251
|
value = option[props.optionValue];
|
238
252
|
} else if (typeof props.optionValue === "function") {
|
@@ -255,6 +269,13 @@ function onUpdate(value) {
|
|
255
269
|
|
256
270
|
value = value === "__null__" ? null : value;
|
257
271
|
|
272
|
+
if (props.selectByObject && value !== null && value !== undefined && typeof value !== "object") {
|
273
|
+
if (props.multiple) {
|
274
|
+
value = props.options.filter((o) => value.includes(resolveValue(o)));
|
275
|
+
} else {
|
276
|
+
value = props.options.find((o) => resolveValue(o) === value);
|
277
|
+
}
|
278
|
+
}
|
258
279
|
emit("update:model-value", value);
|
259
280
|
emit("update", value);
|
260
281
|
}
|
@@ -275,7 +296,7 @@ async function onFilter(val, update) {
|
|
275
296
|
await nextTick(update);
|
276
297
|
} else {
|
277
298
|
update();
|
278
|
-
if (shouldFilter.value
|
299
|
+
if (!shouldFilter.value) return;
|
279
300
|
if (val !== null && val !== filter.value) {
|
280
301
|
filter.value = val;
|
281
302
|
if (props.filterFn) {
|
@@ -0,0 +1,56 @@
|
|
1
|
+
<template>
|
2
|
+
<div class="flex items-stretch flex-nowrap gap-x-4">
|
3
|
+
<QBtn
|
4
|
+
class="bg-green-900 px-4"
|
5
|
+
:loading="loading"
|
6
|
+
@click="$emit('create')"
|
7
|
+
>
|
8
|
+
<CreateIcon
|
9
|
+
class="w-4"
|
10
|
+
:class="createText ? 'mr-2' : ''"
|
11
|
+
/>
|
12
|
+
{{ createText }}
|
13
|
+
</QBtn>
|
14
|
+
<SelectField
|
15
|
+
v-model="selected"
|
16
|
+
class="flex-grow"
|
17
|
+
:options="options"
|
18
|
+
:clearable="clearable"
|
19
|
+
:select-by-object="selectByObject"
|
20
|
+
:option-label="optionLabel"
|
21
|
+
/>
|
22
|
+
<ShowHideButton
|
23
|
+
v-if="showEdit"
|
24
|
+
v-model="editing"
|
25
|
+
:disable="!canEdit"
|
26
|
+
:label="editText"
|
27
|
+
class="bg-sky-800 w-1/5"
|
28
|
+
/>
|
29
|
+
</div>
|
30
|
+
</template>
|
31
|
+
<script setup lang="ts">
|
32
|
+
import { FaSolidPlus as CreateIcon } from "danx-icon";
|
33
|
+
import { QSelectOption } from "quasar";
|
34
|
+
import { ActionTargetItem } from "../../../../types";
|
35
|
+
import { ShowHideButton } from "../../../Utility/Buttons";
|
36
|
+
import SelectField from "./SelectField";
|
37
|
+
|
38
|
+
defineEmits(["create"]);
|
39
|
+
const selected = defineModel<string | number | object | null>("selected");
|
40
|
+
const editing = defineModel<boolean>("editing");
|
41
|
+
withDefaults(defineProps<{
|
42
|
+
options: QSelectOption[] | ActionTargetItem[];
|
43
|
+
showEdit?: boolean;
|
44
|
+
canEdit?: boolean;
|
45
|
+
loading?: boolean;
|
46
|
+
selectByObject?: boolean;
|
47
|
+
optionLabel?: string;
|
48
|
+
createText?: string;
|
49
|
+
editText?: string;
|
50
|
+
clearable?: boolean;
|
51
|
+
}>(), {
|
52
|
+
optionLabel: "label",
|
53
|
+
createText: "Create",
|
54
|
+
editText: "Edit"
|
55
|
+
});
|
56
|
+
</script>
|
@@ -19,6 +19,7 @@ export { default as NumberField } from "./NumberField.vue";
|
|
19
19
|
export { default as NumberRangeField } from "./NumberRangeField.vue";
|
20
20
|
export { default as SelectDrawer } from "./SelectDrawer.vue";
|
21
21
|
export { default as SelectField } from "./SelectField.vue";
|
22
|
+
export { default as SelectOrCreateField } from "./SelectOrCreateField.vue";
|
22
23
|
export { default as SelectWithChildrenField } from "./SelectWithChildrenField.vue";
|
23
24
|
export { default as SingleFileField } from "./SingleFileField.vue";
|
24
25
|
export { default as SliderNumberField } from "./SliderNumberField.vue";
|