quasar-ui-danx 0.4.10 → 0.4.13
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/danx.es.js +12389 -7677
- package/dist/danx.es.js.map +1 -1
- package/dist/danx.umd.js +137 -7
- package/dist/danx.umd.js.map +1 -1
- package/dist/style.css +1 -1
- package/index.d.ts +7 -0
- package/index.ts +1 -0
- package/package.json +10 -4
- package/src/components/ActionTable/ActionMenu.vue +26 -31
- package/src/components/ActionTable/ActionTable.vue +4 -1
- package/src/components/ActionTable/Columns/ActionTableColumn.vue +14 -6
- package/src/components/ActionTable/Columns/ActionTableHeaderColumn.vue +63 -42
- package/src/components/ActionTable/Form/ActionForm.vue +55 -0
- package/src/components/ActionTable/Form/Fields/EditOnClickTextField.vue +11 -5
- package/src/components/ActionTable/Form/Fields/FileUploadButton.vue +1 -0
- package/src/components/ActionTable/Form/Fields/MultiFileField.vue +1 -1
- package/src/components/ActionTable/Form/Fields/NumberField.vue +0 -1
- package/src/components/ActionTable/Form/RenderedForm.vue +57 -50
- package/src/components/ActionTable/Form/index.ts +1 -0
- package/src/components/ActionTable/Layouts/ActionTableLayout.vue +3 -3
- package/src/components/ActionTable/TableSummaryRow.vue +48 -37
- package/src/components/ActionTable/Toolbars/ActionToolbar.vue +2 -2
- package/src/components/ActionTable/listControls.ts +3 -2
- package/src/components/PanelsDrawer/PanelsDrawer.vue +15 -5
- package/src/components/PanelsDrawer/PanelsDrawerPanels.vue +3 -1
- package/src/components/PanelsDrawer/PanelsDrawerTabs.vue +17 -4
- package/src/components/Utility/Dialogs/FullscreenCarouselDialog.vue +30 -5
- package/src/components/Utility/Files/FilePreview.vue +72 -12
- package/src/components/Utility/Popovers/PopoverMenu.vue +34 -29
- package/src/components/Utility/Tools/RenderVnode.vue +5 -1
- package/src/config/index.ts +2 -1
- package/src/helpers/FileUpload.ts +59 -8
- package/src/helpers/actions.ts +27 -27
- package/src/helpers/date.ts +2 -2
- package/src/helpers/download.ts +8 -2
- package/src/helpers/formats.ts +52 -5
- package/src/helpers/multiFileUpload.ts +6 -4
- package/src/helpers/objectStore.ts +14 -17
- package/src/helpers/request.ts +12 -0
- package/src/helpers/singleFileUpload.ts +63 -55
- package/src/helpers/utils.ts +12 -3
- package/src/index.ts +1 -0
- package/src/styles/danx.scss +5 -0
- package/src/styles/index.scss +1 -0
- package/src/styles/themes/danx/action-table.scss +24 -13
- package/src/types/actions.d.ts +16 -7
- package/src/types/controls.d.ts +4 -4
- package/src/types/files.d.ts +10 -5
- package/src/types/forms.d.ts +19 -1
- package/src/types/index.d.ts +0 -1
- package/src/types/requests.d.ts +2 -0
- package/src/types/tables.d.ts +28 -22
- package/src/{vue-plugin.js → vue-plugin.ts} +5 -4
- package/tsconfig.json +1 -0
- package/types/index.d.ts +2 -0
package/index.d.ts
ADDED
package/index.ts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
export * from "./src";
|
package/package.json
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
{
|
2
2
|
"name": "quasar-ui-danx",
|
3
|
-
"version": "0.4.
|
3
|
+
"version": "0.4.13",
|
4
4
|
"author": "Dan <dan@flytedesk.com>",
|
5
5
|
"description": "DanX Vue / Quasar component library",
|
6
6
|
"license": "MIT",
|
7
7
|
"type": "module",
|
8
|
-
"main": "dist/danx.
|
9
|
-
"module": "dist/danx.es.js",
|
8
|
+
"main": "./dist/danx.umd.js",
|
9
|
+
"module": "./dist/danx.es.js",
|
10
|
+
"types": "types/index.d.ts",
|
10
11
|
"scripts": {
|
11
12
|
"dev": "cd dev && quasar dev && cd ..",
|
12
13
|
"build": "vite build",
|
@@ -17,6 +18,10 @@
|
|
17
18
|
"type": "git",
|
18
19
|
"url": "https://github.com/flytedan/quasar-ui-danx"
|
19
20
|
},
|
21
|
+
"peerDependencies": {
|
22
|
+
"quasar": "^2.0.0",
|
23
|
+
"vue": "^3.4.21"
|
24
|
+
},
|
20
25
|
"devDependencies": {
|
21
26
|
"@quasar/extras": "^1.16.4",
|
22
27
|
"@types/luxon": "^3.4.2",
|
@@ -52,7 +57,8 @@
|
|
52
57
|
"danx-icon": "^1.0.2",
|
53
58
|
"exifreader": "^4.21.1",
|
54
59
|
"gsap": "^3.12.5",
|
55
|
-
"luxon": "^3.4.4"
|
60
|
+
"luxon": "^3.4.4",
|
61
|
+
"yaml": "^2.4.5"
|
56
62
|
},
|
57
63
|
"browserslist": [
|
58
64
|
"last 4 Chrome versions",
|
@@ -3,53 +3,48 @@
|
|
3
3
|
class="px-2 flex dx-action-button"
|
4
4
|
:items="activeActions"
|
5
5
|
:disabled="!hasTarget"
|
6
|
-
:tooltip="!hasTarget ? tooltip :
|
7
|
-
:loading="isSaving || loading"
|
6
|
+
:tooltip="!hasTarget ? tooltip : ''"
|
7
|
+
:loading="isSaving || !!loading"
|
8
8
|
:loading-component="loadingComponent"
|
9
9
|
@action-item="onAction"
|
10
10
|
/>
|
11
11
|
</template>
|
12
|
-
<script setup>
|
12
|
+
<script setup lang="ts">
|
13
13
|
import { computed, ref } from "vue";
|
14
|
+
import { ActionTarget, ResourceAction } from "../../types";
|
14
15
|
import { PopoverMenu } from "../Utility";
|
15
16
|
|
16
|
-
const
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
default: "First select records to perform a batch action"
|
28
|
-
},
|
29
|
-
loading: Boolean,
|
30
|
-
loadingComponent: {
|
31
|
-
type: [Function, Object],
|
32
|
-
default: undefined
|
33
|
-
}
|
17
|
+
const emit = defineEmits(["success"]);
|
18
|
+
const props = withDefaults(defineProps<{
|
19
|
+
actions: ResourceAction[],
|
20
|
+
target?: ActionTarget,
|
21
|
+
tooltip?: string,
|
22
|
+
loading?: boolean,
|
23
|
+
loadingComponent?: any
|
24
|
+
}>(), {
|
25
|
+
target: () => [],
|
26
|
+
tooltip: "First select records to perform a batch action",
|
27
|
+
loadingComponent: undefined
|
34
28
|
});
|
35
29
|
|
36
30
|
const hasTarget = computed(() => Array.isArray(props.target) ? props.target.length > 0 : !!props.target);
|
37
31
|
|
38
32
|
const activeActions = computed(() => props.actions.filter(action => {
|
39
|
-
|
40
|
-
|
41
|
-
|
33
|
+
if (Array.isArray(props.target)) {
|
34
|
+
return action.batchEnabled ? action.batchEnabled(props.target) : true;
|
35
|
+
}
|
42
36
|
|
43
|
-
|
37
|
+
return action.enabled ? action.enabled(props.target) : true;
|
44
38
|
}));
|
45
39
|
|
46
40
|
const isSaving = ref(false);
|
47
41
|
async function onAction(action) {
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
42
|
+
if (!action.trigger) {
|
43
|
+
throw new Error("Action must have a trigger function! Make sure you are using useActions() or implement your own trigger function.");
|
44
|
+
}
|
45
|
+
isSaving.value = true;
|
46
|
+
await action.trigger(props.target);
|
47
|
+
isSaving.value = false;
|
48
|
+
emit("success", action);
|
54
49
|
}
|
55
50
|
</script>
|
@@ -31,6 +31,7 @@
|
|
31
31
|
:label="label"
|
32
32
|
:item-count="summary?.count || 0"
|
33
33
|
:selected-count="selectedRows.length"
|
34
|
+
:sticky-colspan="summaryColSpan"
|
34
35
|
:loading="loadingSummary"
|
35
36
|
:summary="summary"
|
36
37
|
:columns="tableColumns"
|
@@ -87,6 +88,7 @@ export interface Props {
|
|
87
88
|
summary: any;
|
88
89
|
columns: TableColumn[];
|
89
90
|
rowsPerPageOptions?: number[];
|
91
|
+
summaryColSpan?: number;
|
90
92
|
}
|
91
93
|
|
92
94
|
const props = withDefaults(defineProps<Props>(), {
|
@@ -94,7 +96,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|
94
96
|
pagedItems: null,
|
95
97
|
summary: null,
|
96
98
|
loadingSummary: false,
|
97
|
-
rowsPerPageOptions: () => [10, 25, 50, 100]
|
99
|
+
rowsPerPageOptions: () => [10, 25, 50, 100],
|
100
|
+
summaryColSpan: null
|
98
101
|
});
|
99
102
|
|
100
103
|
const actionTable = ref(null);
|
@@ -3,13 +3,18 @@
|
|
3
3
|
:key="rowProps.key"
|
4
4
|
:props="rowProps"
|
5
5
|
:style="columnStyle"
|
6
|
+
class="dx-column"
|
7
|
+
:class="column.columnClass"
|
6
8
|
>
|
7
9
|
<div :style="columnStyle">
|
8
10
|
<div
|
9
11
|
class="flex items-center flex-nowrap"
|
10
|
-
:class="
|
12
|
+
:class="wrapClass"
|
11
13
|
>
|
12
|
-
<div
|
14
|
+
<div
|
15
|
+
v-if="!column.hideContent"
|
16
|
+
class="flex-grow overflow-hidden"
|
17
|
+
>
|
13
18
|
<a
|
14
19
|
v-if="column.onClick"
|
15
20
|
:class="column.innerClass"
|
@@ -25,6 +30,7 @@
|
|
25
30
|
<div
|
26
31
|
v-else
|
27
32
|
:class="column.innerClass"
|
33
|
+
class="dx-column-text"
|
28
34
|
>
|
29
35
|
<RenderVnode
|
30
36
|
v-if="column.vnode"
|
@@ -42,7 +48,8 @@
|
|
42
48
|
</div>
|
43
49
|
<div
|
44
50
|
v-if="column.actionMenu"
|
45
|
-
class="flex flex-shrink-0
|
51
|
+
class="flex flex-shrink-0"
|
52
|
+
:class="{'ml-2': !column.hideContent}"
|
46
53
|
>
|
47
54
|
<ActionMenu
|
48
55
|
class="dx-column-action-menu"
|
@@ -55,12 +62,13 @@
|
|
55
62
|
</div>
|
56
63
|
</QTd>
|
57
64
|
</template>
|
58
|
-
<script setup>
|
65
|
+
<script setup lang="ts">
|
59
66
|
import { QTd } from "quasar";
|
60
67
|
import { computed } from "vue";
|
61
68
|
import { RenderVnode } from "../../Utility";
|
62
69
|
import ActionMenu from "../ActionMenu";
|
63
70
|
import { TitleColumnFormat } from "./";
|
71
|
+
import { TableColumn } from "./../../../types";
|
64
72
|
|
65
73
|
const props = defineProps({
|
66
74
|
rowProps: {
|
@@ -74,7 +82,7 @@ const props = defineProps({
|
|
74
82
|
});
|
75
83
|
|
76
84
|
const row = computed(() => props.rowProps.row);
|
77
|
-
const column = computed(() => props.rowProps.col);
|
85
|
+
const column = computed<TableColumn>(() => props.rowProps.col);
|
78
86
|
const value = computed(() => props.rowProps.value);
|
79
87
|
const isSaving = computed(() => row.value.isSaving?.value);
|
80
88
|
|
@@ -86,7 +94,7 @@ const columnStyle = computed(() => {
|
|
86
94
|
};
|
87
95
|
});
|
88
96
|
|
89
|
-
const
|
97
|
+
const wrapClass = computed(() => ({
|
90
98
|
[column.value.class || ""]: true,
|
91
99
|
"is-saving": isSaving.value,
|
92
100
|
"justify-end": column.value.align === "right",
|
@@ -3,7 +3,7 @@
|
|
3
3
|
:key="rowProps.key"
|
4
4
|
:props="rowProps"
|
5
5
|
:data-drop-zone="isResizeable && `resize-column-` + column.name"
|
6
|
-
:class="
|
6
|
+
:class="columnClass"
|
7
7
|
:style="columnStyle"
|
8
8
|
>
|
9
9
|
{{ column.label }}
|
@@ -17,67 +17,88 @@
|
|
17
17
|
</HandleDraggable>
|
18
18
|
</QTh>
|
19
19
|
</template>
|
20
|
-
<script setup>
|
20
|
+
<script setup lang="ts">
|
21
21
|
import { QTh } from "quasar";
|
22
|
-
import { computed } from "vue";
|
22
|
+
import { computed, useCssModule } from "vue";
|
23
23
|
import { DragHandleIcon as RowResizeIcon } from "../../../svg";
|
24
|
+
import { TableColumn } from "../../../types";
|
24
25
|
import { HandleDraggable } from "../../DragAndDrop";
|
25
26
|
|
26
27
|
const emit = defineEmits(["update:model-value"]);
|
27
28
|
const props = defineProps({
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
29
|
+
modelValue: {
|
30
|
+
type: Object,
|
31
|
+
required: true
|
32
|
+
},
|
33
|
+
name: {
|
34
|
+
type: String,
|
35
|
+
required: true
|
36
|
+
},
|
37
|
+
rowProps: {
|
38
|
+
type: Object,
|
39
|
+
required: true
|
40
|
+
}
|
40
41
|
});
|
41
42
|
|
42
|
-
const column = computed(() => props.rowProps.col);
|
43
|
+
const column = computed<TableColumn>(() => props.rowProps.col);
|
43
44
|
const isResizeable = computed(() => column.value.resizeable);
|
44
45
|
|
45
46
|
const columnStyle = computed(() => {
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
47
|
+
const width = props.settings?.width || column.value.width;
|
48
|
+
return {
|
49
|
+
width: width ? `${width}px` : undefined,
|
50
|
+
minWidth: column.value.minWidth ? `${column.value.minWidth}px` : undefined,
|
51
|
+
...(column.value.headerStyle || {})
|
52
|
+
};
|
52
53
|
});
|
53
54
|
|
55
|
+
const clsModule = useCssModule("cls");
|
56
|
+
const columnClass = computed(() => {
|
57
|
+
const colCls = {
|
58
|
+
[clsModule["handle-drop-zone"]]: isResizeable.value,
|
59
|
+
"dx-column-shrink": column.value.shrink
|
60
|
+
};
|
61
|
+
|
62
|
+
const headerClass = column.value.headerClass;
|
63
|
+
if (headerClass) {
|
64
|
+
if (typeof headerClass === "string") {
|
65
|
+
colCls[headerClass] = true;
|
66
|
+
} else {
|
67
|
+
Object.keys(headerClass).forEach((key) => {
|
68
|
+
colCls[key] = headerClass[key];
|
69
|
+
});
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
return colCls;
|
74
|
+
});
|
54
75
|
|
55
76
|
function onResizeColumn(val) {
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
77
|
+
const settings = {
|
78
|
+
...props.modelValue,
|
79
|
+
[column.value.name]: {
|
80
|
+
width: Math.max(Math.min(val.distance + val.startDropZoneSize, column.value.maxWidth || 500), column.value.minWidth || 80)
|
81
|
+
}
|
82
|
+
};
|
83
|
+
emit("update:model-value", settings);
|
63
84
|
}
|
64
85
|
</script>
|
65
86
|
|
66
87
|
<style lang="scss" module="cls">
|
67
88
|
.handle-drop-zone {
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
89
|
+
.resize-handle {
|
90
|
+
position: absolute;
|
91
|
+
top: 0;
|
92
|
+
right: -.45em;
|
93
|
+
width: .9em;
|
94
|
+
opacity: 0;
|
95
|
+
transition: all .3s;
|
96
|
+
}
|
76
97
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
98
|
+
&:hover {
|
99
|
+
.resize-handle {
|
100
|
+
opacity: 1;
|
101
|
+
}
|
102
|
+
}
|
82
103
|
}
|
83
104
|
</style>
|
@@ -0,0 +1,55 @@
|
|
1
|
+
<template>
|
2
|
+
<div>
|
3
|
+
<RenderedForm
|
4
|
+
v-bind="renderedFormProps"
|
5
|
+
v-model:values="input"
|
6
|
+
empty-value=""
|
7
|
+
:saving="action.isApplying"
|
8
|
+
@update:values="action.trigger(target, input)"
|
9
|
+
/>
|
10
|
+
</div>
|
11
|
+
</template>
|
12
|
+
<script setup lang="ts">
|
13
|
+
import { ref, watch } from "vue";
|
14
|
+
import { ActionTargetItem, AnyObject, Form, ResourceAction } from "../../../types";
|
15
|
+
import RenderedForm from "./RenderedForm.vue";
|
16
|
+
|
17
|
+
interface ActionFormProps {
|
18
|
+
action: ResourceAction;
|
19
|
+
target: ActionTargetItem;
|
20
|
+
form: Form;
|
21
|
+
noLabel?: boolean;
|
22
|
+
showName?: boolean;
|
23
|
+
disable?: boolean;
|
24
|
+
readonly?: boolean;
|
25
|
+
clearable?: boolean;
|
26
|
+
fieldClass?: string;
|
27
|
+
savingClass?: string;
|
28
|
+
}
|
29
|
+
|
30
|
+
const props = defineProps<ActionFormProps>();
|
31
|
+
const renderedFormProps = {
|
32
|
+
form: props.form,
|
33
|
+
noLabel: props.noLabel,
|
34
|
+
showName: props.showName,
|
35
|
+
disable: props.disable,
|
36
|
+
readonly: props.readonly,
|
37
|
+
clearable: props.clearable,
|
38
|
+
fieldClass: props.fieldClass,
|
39
|
+
savingClass: props.savingClass
|
40
|
+
};
|
41
|
+
|
42
|
+
const input: AnyObject = ref({ ...props.target });
|
43
|
+
const fieldStatus: AnyObject = {};
|
44
|
+
|
45
|
+
// Only update field values from target changes when the field is not already being saved
|
46
|
+
watch(() => props.target, (target: ActionTargetItem) => {
|
47
|
+
if (!target) return;
|
48
|
+
|
49
|
+
for (let field of props.form.fields) {
|
50
|
+
if (!fieldStatus[field.name]?.isSaving) {
|
51
|
+
input.value[field.name] = target[field.name];
|
52
|
+
}
|
53
|
+
}
|
54
|
+
});
|
55
|
+
</script>
|
@@ -3,12 +3,13 @@
|
|
3
3
|
class="danx-edit-on-click-text-field flex flex-nowrap items-center rounded overflow-ellipsis"
|
4
4
|
:class="{[props.class]: true, 'is-readonly': readonly, 'cursor-pointer': !isEditing && !readonly}"
|
5
5
|
@click="onEdit"
|
6
|
+
@focusout="isEditing = false"
|
6
7
|
>
|
7
8
|
<div
|
8
9
|
ref="editableBox"
|
9
10
|
:contenteditable="!readonly && isEditing"
|
10
11
|
class="flex-grow p-2 rounded outline-none border-none"
|
11
|
-
:class="{[editingClass]: isEditing}"
|
12
|
+
:class="{[editingClass]: isEditing, [inputClass]: true}"
|
12
13
|
@input="text = $event.target.innerText"
|
13
14
|
>
|
14
15
|
{{ text }}
|
@@ -33,15 +34,20 @@
|
|
33
34
|
import { FaSolidCheck as DoneIcon, FaSolidPencil as EditIcon } from "danx-icon";
|
34
35
|
import { nextTick, ref } from "vue";
|
35
36
|
|
36
|
-
export interface
|
37
|
-
class?:
|
38
|
-
editingClass?:
|
37
|
+
export interface EditOnClickTextFieldProps {
|
38
|
+
class?: string;
|
39
|
+
editingClass?: string;
|
40
|
+
inputClass?: string;
|
39
41
|
readonly?: boolean;
|
40
42
|
}
|
41
43
|
|
42
44
|
const editableBox = ref<HTMLElement | null>(null);
|
43
45
|
const text = defineModel({ type: String });
|
44
|
-
const props = defineProps<
|
46
|
+
const props = withDefaults(defineProps<EditOnClickTextFieldProps>(), {
|
47
|
+
class: "hover:bg-slate-300",
|
48
|
+
editingClass: "bg-slate-500",
|
49
|
+
inputClass: "whitespace-normal"
|
50
|
+
});
|
45
51
|
const isEditing = ref(false);
|
46
52
|
function onEdit() {
|
47
53
|
if (props.readonly) return;
|
@@ -62,6 +62,7 @@ function upload() {
|
|
62
62
|
async function onAttachFiles({ target: { files } }) {
|
63
63
|
emit("uploading", files);
|
64
64
|
let fileUpload = new FileUpload(files)
|
65
|
+
.prepare()
|
65
66
|
.onProgress(({ file, progress }) => {
|
66
67
|
file.progress = progress;
|
67
68
|
emit("file-progress", file);
|
@@ -62,32 +62,36 @@
|
|
62
62
|
</QTab>
|
63
63
|
</QTabs>
|
64
64
|
</div>
|
65
|
-
<
|
65
|
+
<template
|
66
66
|
v-for="(field, index) in mappedFields"
|
67
67
|
:key="field.id"
|
68
|
-
:class="{ 'mt-4': index > 0, [fieldClass]: true }"
|
69
68
|
>
|
70
|
-
<
|
71
|
-
v-
|
72
|
-
:
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
69
|
+
<div
|
70
|
+
v-show="isFieldEnabled(field)"
|
71
|
+
:class="{ 'mt-4': index > 0, [fieldClass]: true }"
|
72
|
+
>
|
73
|
+
<RenderVnode
|
74
|
+
v-if="field.vnode"
|
75
|
+
:vnode="field.vnode"
|
76
|
+
:props="getVnodeProps(field)"
|
77
|
+
:params="fieldInputs"
|
78
|
+
@update:model-value="onInput(field.name, $event)"
|
79
|
+
/>
|
80
|
+
<Component
|
81
|
+
:is="field.component"
|
82
|
+
:key="field.name + '-' + currentVariation"
|
83
|
+
:model-value="getFieldValue(field.name)"
|
84
|
+
:field="field"
|
85
|
+
:label="field.label || undefined"
|
86
|
+
:no-label="noLabel"
|
87
|
+
:show-name="showName"
|
88
|
+
:clearable="field.clearable || clearable"
|
89
|
+
:disable="disable"
|
90
|
+
:readonly="readonly"
|
91
|
+
@update:model-value="onInput(field.name, $event)"
|
92
|
+
/>
|
93
|
+
</div>
|
94
|
+
</template>
|
91
95
|
<div
|
92
96
|
v-if="savedAt"
|
93
97
|
:class="savingClass"
|
@@ -136,7 +140,7 @@ import { ExclamationCircleIcon as MissingIcon, PencilIcon as EditIcon } from "@h
|
|
136
140
|
import { computed, ref } from "vue";
|
137
141
|
import { fDateTime, FlashMessages, incrementName, replace } from "../../../helpers";
|
138
142
|
import { TrashIcon as RemoveIcon } from "../../../svg";
|
139
|
-
import {
|
143
|
+
import { AnyObject, FormFieldValue, RenderedFormProps } from "../../../types";
|
140
144
|
import { ConfirmDialog, RenderVnode } from "../../Utility";
|
141
145
|
import {
|
142
146
|
BooleanField,
|
@@ -150,28 +154,12 @@ import {
|
|
150
154
|
WysiwygField
|
151
155
|
} from "./Fields";
|
152
156
|
|
153
|
-
export interface RenderedFormProps {
|
154
|
-
values?: FormFieldValue[] | object;
|
155
|
-
form: Form;
|
156
|
-
noLabel?: boolean;
|
157
|
-
showName?: boolean;
|
158
|
-
disable?: boolean;
|
159
|
-
readonly?: boolean;
|
160
|
-
saving?: boolean;
|
161
|
-
clearable?: boolean;
|
162
|
-
emptyValue?: string | number | boolean;
|
163
|
-
canModifyVariations?: boolean;
|
164
|
-
fieldClass?: string;
|
165
|
-
savingClass?: string;
|
166
|
-
savedAt?: string;
|
167
|
-
}
|
168
|
-
|
169
157
|
const props = withDefaults(defineProps<RenderedFormProps>(), {
|
170
158
|
values: null,
|
171
159
|
emptyValue: undefined,
|
172
160
|
fieldClass: "",
|
173
161
|
savingClass: "text-sm text-slate-500 justify-end mt-4",
|
174
|
-
savedAt:
|
162
|
+
savedAt: undefined
|
175
163
|
});
|
176
164
|
|
177
165
|
const emit = defineEmits(["update:values"]);
|
@@ -190,9 +178,9 @@ const FORM_FIELD_MAP = {
|
|
190
178
|
|
191
179
|
const mappedFields = props.form.fields.map((field) => ({
|
192
180
|
placeholder: `Enter ${field.label}`,
|
181
|
+
default: field.type === "BOOLEAN" ? false : "",
|
193
182
|
...field,
|
194
|
-
component: field.component || FORM_FIELD_MAP[field.type]
|
195
|
-
default: field.type === "BOOLEAN" ? false : ""
|
183
|
+
component: field.component || FORM_FIELD_MAP[field.type]
|
196
184
|
}));
|
197
185
|
|
198
186
|
const fieldResponses = computed(() => {
|
@@ -201,6 +189,24 @@ const fieldResponses = computed(() => {
|
|
201
189
|
return Object.entries(props.values).map(([name, value]) => ({ name, value, variation: "" }));
|
202
190
|
});
|
203
191
|
|
192
|
+
const fieldInputs = computed(() => {
|
193
|
+
const inputs: AnyObject = {};
|
194
|
+
for (const field of mappedFields) {
|
195
|
+
inputs[field.name] = getFieldValue(field.name);
|
196
|
+
}
|
197
|
+
return inputs;
|
198
|
+
});
|
199
|
+
|
200
|
+
function isFieldEnabled(field) {
|
201
|
+
if (field.enabled === undefined) return true;
|
202
|
+
|
203
|
+
if (typeof field.enabled === "function") {
|
204
|
+
return field.enabled(fieldInputs.value);
|
205
|
+
}
|
206
|
+
|
207
|
+
return field.enabled;
|
208
|
+
}
|
209
|
+
|
204
210
|
function getVnodeProps(field) {
|
205
211
|
return {
|
206
212
|
modelValue: getFieldValue(field.name),
|
@@ -226,16 +232,16 @@ const currentVariation = ref(variationNames.value[0] || "");
|
|
226
232
|
const newVariationName = ref("");
|
227
233
|
const variationToEdit = ref<boolean | string>(false);
|
228
234
|
const variationToDelete = ref("");
|
229
|
-
const canAddVariation = computed(() => props.canModifyVariations && !props.readonly && !props.disable && variationNames.value.length < props.form.variations);
|
235
|
+
const canAddVariation = computed(() => props.canModifyVariations && !props.readonly && !props.disable && variationNames.value.length < (props.form.variations || 0));
|
230
236
|
|
231
|
-
function getFieldResponse(name, variation
|
237
|
+
function getFieldResponse(name: string, variation?: string) {
|
232
238
|
if (!fieldResponses.value) return undefined;
|
233
239
|
return fieldResponses.value.find((fr: FormFieldValue) => fr.variation === (variation !== undefined ? variation : currentVariation.value) && fr.name === name);
|
234
240
|
}
|
235
|
-
function getFieldValue(name) {
|
241
|
+
function getFieldValue(name: string) {
|
236
242
|
return getFieldResponse(name)?.value;
|
237
243
|
}
|
238
|
-
function onInput(name, value) {
|
244
|
+
function onInput(name: string, value: any) {
|
239
245
|
const fieldResponse = getFieldResponse(name);
|
240
246
|
const newFieldResponse = {
|
241
247
|
name,
|
@@ -291,11 +297,12 @@ function updateValues(values: FormFieldValue[]) {
|
|
291
297
|
let updatedValues: FormFieldValue[] | object = values;
|
292
298
|
|
293
299
|
if (!Array.isArray(props.values)) {
|
294
|
-
updatedValues = values.reduce((acc, v) => {
|
300
|
+
updatedValues = values.reduce((acc: AnyObject, v) => {
|
295
301
|
acc[v.name] = v.value;
|
296
302
|
return acc;
|
297
303
|
}, {});
|
298
304
|
}
|
305
|
+
|
299
306
|
emit("update:values", updatedValues);
|
300
307
|
}
|
301
308
|
|
@@ -311,8 +318,8 @@ function onRemoveVariation(name: string) {
|
|
311
318
|
variationToDelete.value = "";
|
312
319
|
}
|
313
320
|
|
314
|
-
function isVariationFormComplete(variation) {
|
315
|
-
const requiredGroups = {};
|
321
|
+
function isVariationFormComplete(variation: string) {
|
322
|
+
const requiredGroups: AnyObject = {};
|
316
323
|
return props.form.fields.filter(r => r.required || r.required_group).every((field) => {
|
317
324
|
const fieldResponse = getFieldResponse(field.name, variation);
|
318
325
|
const hasValue = !!fieldResponse && fieldResponse.value !== null;
|