@bagelink/vue 0.0.427 → 0.0.435
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/components/Alert.vue.d.ts +4 -1
- package/dist/components/Alert.vue.d.ts.map +1 -1
- package/dist/components/TableSchema.vue.d.ts +2 -5
- package/dist/components/TableSchema.vue.d.ts.map +1 -1
- package/dist/components/form/BglField.vue.d.ts.map +1 -1
- package/dist/components/form/BglForm.vue.d.ts +4 -1
- package/dist/components/form/BglForm.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/DatePicker.vue.d.ts +4 -0
- package/dist/components/form/inputs/DatePicker.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/FileUpload.vue.d.ts +6 -2
- package/dist/components/form/inputs/FileUpload.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RadioPillsInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/TextInput.vue.d.ts +0 -3
- package/dist/components/form/inputs/TextInput.vue.d.ts.map +1 -1
- package/dist/components/layout/Tabs.vue.d.ts +6 -2
- package/dist/components/layout/Tabs.vue.d.ts.map +1 -1
- package/dist/components/layout/TabsNav.vue.d.ts.map +1 -1
- package/dist/index.cjs +239 -137
- package/dist/index.mjs +240 -138
- package/dist/plugins/bagel.d.ts +3 -0
- package/dist/plugins/bagel.d.ts.map +1 -1
- package/dist/style.css +109 -91
- package/dist/utils/BagelFormUtils.d.ts +1 -0
- package/dist/utils/BagelFormUtils.d.ts.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/lang.d.ts +7 -0
- package/dist/utils/lang.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/Alert.vue +4 -2
- package/src/components/TableSchema.vue +19 -9
- package/src/components/form/BglField.vue +5 -3
- package/src/components/form/BglForm.vue +13 -9
- package/src/components/form/inputs/DatePicker.vue +8 -2
- package/src/components/form/inputs/FileUpload.vue +54 -23
- package/src/components/form/inputs/RadioPillsInput.vue +9 -9
- package/src/components/form/inputs/SelectInput.vue +36 -31
- package/src/components/form/inputs/TextInput.vue +1 -1
- package/src/components/layout/Tabs.vue +14 -5
- package/src/components/layout/TabsNav.vue +34 -14
- package/src/plugins/bagel.ts +13 -4
- package/src/utils/BagelFormUtils.ts +44 -10
- package/src/utils/index.ts +2 -0
- package/src/utils/lang.ts +39 -0
|
@@ -14,13 +14,15 @@
|
|
|
14
14
|
bindAttrs({ options: field.options }, fieldData, modelValue).options
|
|
15
15
|
"
|
|
16
16
|
:helptext="field.helptext"
|
|
17
|
-
@update:modelValue="($event: any) => field?.onUpdate?.($event, formData)"
|
|
17
|
+
@update:modelValue="($event: any) => field?.onUpdate?.($event, formData)"
|
|
18
|
+
>
|
|
18
19
|
{{ field.transform?.(fieldData, modelValue) || fieldData || '' }}
|
|
19
20
|
<BglField
|
|
20
21
|
v-model="formData"
|
|
21
22
|
v-for="(child, ii) in field.children"
|
|
22
23
|
:key="child.id || ii"
|
|
23
|
-
:field="child"
|
|
24
|
+
:field="child"
|
|
25
|
+
/>
|
|
24
26
|
</component>
|
|
25
27
|
</template>
|
|
26
28
|
|
|
@@ -43,7 +45,7 @@ const props = withDefaults(
|
|
|
43
45
|
}>(),
|
|
44
46
|
{
|
|
45
47
|
modelValue: () => ({}),
|
|
46
|
-
}
|
|
48
|
+
},
|
|
47
49
|
);
|
|
48
50
|
|
|
49
51
|
const is = $computed(() => {
|
|
@@ -4,19 +4,22 @@
|
|
|
4
4
|
v-for="(field, i) in schema"
|
|
5
5
|
:key="field.id || `${i}p`"
|
|
6
6
|
:field="field"
|
|
7
|
-
v-model="data"
|
|
7
|
+
v-model="data"
|
|
8
|
+
/>
|
|
8
9
|
</template>
|
|
9
10
|
<form
|
|
10
11
|
v-else-if="!slots.success || status !== 'success'"
|
|
11
12
|
ref="form"
|
|
12
|
-
@submit.prevent="runSubmit"
|
|
13
|
+
@submit.prevent="runSubmit"
|
|
14
|
+
>
|
|
13
15
|
<Title tag="h4" :label="label" v-if="label" />
|
|
14
16
|
<BglField
|
|
15
17
|
v-for="(field, i) in schema"
|
|
16
18
|
:key="field.id || `${i}p`"
|
|
17
19
|
:field="field"
|
|
18
|
-
v-model="data"
|
|
19
|
-
|
|
20
|
+
v-model="data"
|
|
21
|
+
/>
|
|
22
|
+
<slot name="submit" :submit="runSubmit" :isDirty />
|
|
20
23
|
</form>
|
|
21
24
|
<slot v-if="status === 'success'" name="success" />
|
|
22
25
|
<slot v-if="status === 'error'" name="error" />
|
|
@@ -44,7 +47,7 @@ const props = withDefaults(
|
|
|
44
47
|
}>(),
|
|
45
48
|
{
|
|
46
49
|
modelValue: () => ({}),
|
|
47
|
-
}
|
|
50
|
+
},
|
|
48
51
|
);
|
|
49
52
|
|
|
50
53
|
const instAt = new Date();
|
|
@@ -61,7 +64,7 @@ let data = $computed({
|
|
|
61
64
|
formData = val;
|
|
62
65
|
emit('update:modelValue', val);
|
|
63
66
|
},
|
|
64
|
-
get: () =>
|
|
67
|
+
get: () => formData,
|
|
65
68
|
});
|
|
66
69
|
const form = $ref<HTMLFormElement>();
|
|
67
70
|
|
|
@@ -79,14 +82,15 @@ watch(
|
|
|
79
82
|
clearForm();
|
|
80
83
|
}
|
|
81
84
|
},
|
|
82
|
-
{ immediate: true }
|
|
85
|
+
{ immediate: true },
|
|
83
86
|
);
|
|
84
87
|
|
|
85
88
|
const runSubmit = () => {
|
|
86
89
|
const isValid = validateForm();
|
|
87
90
|
if (!isValid) return;
|
|
88
91
|
// props.onSubmit?.(data);
|
|
89
|
-
emit('submit',
|
|
92
|
+
emit('submit', { ...formData });
|
|
93
|
+
isDirty = false;
|
|
90
94
|
};
|
|
91
95
|
|
|
92
96
|
const i18nT = (val: string) => val;
|
|
@@ -105,7 +109,7 @@ const deleteItem = () => {
|
|
|
105
109
|
{ value: 'Cancel', color: 'gray' },
|
|
106
110
|
],
|
|
107
111
|
},
|
|
108
|
-
{ default: i18nT('form.deleteMessage') }
|
|
112
|
+
{ default: i18nT('form.deleteMessage') },
|
|
109
113
|
);
|
|
110
114
|
};
|
|
111
115
|
|
|
@@ -5,11 +5,14 @@
|
|
|
5
5
|
inline
|
|
6
6
|
week-start="0"
|
|
7
7
|
v-model="selectedDate"
|
|
8
|
+
:allowed-dates="allowedDates"
|
|
9
|
+
:disabled-dates="disabledDates"
|
|
8
10
|
:auto-apply="true"
|
|
9
11
|
:highlight-week-days="[6]"
|
|
10
12
|
:enable-time-picker="false"
|
|
11
13
|
:month-change-on-scroll="false"
|
|
12
|
-
v-bind="options"
|
|
14
|
+
v-bind="options"
|
|
15
|
+
>
|
|
13
16
|
<template #action-buttons />
|
|
14
17
|
<template #action-preview />
|
|
15
18
|
</VDatepicker>
|
|
@@ -21,7 +24,8 @@
|
|
|
21
24
|
:id="`${hr}_${id}`"
|
|
22
25
|
:name="label"
|
|
23
26
|
:value="hr"
|
|
24
|
-
v-model="selectedHour"
|
|
27
|
+
v-model="selectedHour"
|
|
28
|
+
/>
|
|
25
29
|
<label :for="`${hr}_${id}`">{{ hr }}</label>
|
|
26
30
|
</template>
|
|
27
31
|
</div>
|
|
@@ -38,6 +42,8 @@ const props = defineProps<{
|
|
|
38
42
|
options?: Record<string, any>;
|
|
39
43
|
showTimeWrap?: boolean;
|
|
40
44
|
modelValue?: Date;
|
|
45
|
+
allowedDates?: string[] | Date[];
|
|
46
|
+
disabledDates?: string[] | Date[];
|
|
41
47
|
}>();
|
|
42
48
|
|
|
43
49
|
const emit = defineEmits(['update:modelValue']);
|
|
@@ -12,18 +12,14 @@
|
|
|
12
12
|
:class="{
|
|
13
13
|
fileDropZone: !storageFiles.length && !fileQueue.length,
|
|
14
14
|
dragover: isDragOver,
|
|
15
|
-
}"
|
|
15
|
+
}"
|
|
16
|
+
>
|
|
16
17
|
<template v-for="file in storageFiles" :key="file.id">
|
|
17
18
|
<div class="imagePreviewWrap" v-if="!multiple">
|
|
18
19
|
<img class="preview" :src="file.url" alt="" />
|
|
19
20
|
</div>
|
|
20
21
|
<div class="previewName">
|
|
21
|
-
<img
|
|
22
|
-
height="60"
|
|
23
|
-
v-if="multiple"
|
|
24
|
-
class="preview"
|
|
25
|
-
:src="file.url"
|
|
26
|
-
alt="" />
|
|
22
|
+
<img v-if="multiple" class="preview" :src="file.url" alt="" />
|
|
27
23
|
<p class="no-margin">
|
|
28
24
|
{{ file.name }}
|
|
29
25
|
</p>
|
|
@@ -32,7 +28,8 @@
|
|
|
32
28
|
@click.stop="removeFile(file)"
|
|
33
29
|
flat
|
|
34
30
|
icon="delete"
|
|
35
|
-
color="red"
|
|
31
|
+
color="red"
|
|
32
|
+
/>
|
|
36
33
|
</div>
|
|
37
34
|
</template>
|
|
38
35
|
<template v-for="fileQ in fileQueue" :key="fileQ.file">
|
|
@@ -41,11 +38,12 @@
|
|
|
41
38
|
</div>
|
|
42
39
|
<div class="previewName">
|
|
43
40
|
<img
|
|
44
|
-
|
|
41
|
+
:width="width || '220'"
|
|
45
42
|
v-if="multiple"
|
|
46
43
|
class="preview"
|
|
47
44
|
:src="fileToUrl(fileQ.file)"
|
|
48
|
-
alt=""
|
|
45
|
+
alt=""
|
|
46
|
+
/>
|
|
49
47
|
<p class="no-margin">
|
|
50
48
|
{{ fileQ.name }}
|
|
51
49
|
</p>
|
|
@@ -53,7 +51,8 @@
|
|
|
53
51
|
class="pie"
|
|
54
52
|
:style="`--p:${fileQ.progress}`"
|
|
55
53
|
style="--b: 2px"
|
|
56
|
-
:class="{ complete: fileQ.progress === 100 }"
|
|
54
|
+
:class="{ complete: fileQ.progress === 100 }"
|
|
55
|
+
>
|
|
57
56
|
<span class="progress" v-if="fileQ.progress < 100">
|
|
58
57
|
{{ `${fileQ.progress.toFixed(0)}` }}
|
|
59
58
|
</span>
|
|
@@ -66,7 +65,7 @@
|
|
|
66
65
|
</template>
|
|
67
66
|
|
|
68
67
|
<script setup lang="ts">
|
|
69
|
-
import { watch } from 'vue';
|
|
68
|
+
import { onMounted, watch } from 'vue';
|
|
70
69
|
import { Btn, type StorageFile, useBagel, MaterialIcon } from '@bagelink/vue';
|
|
71
70
|
|
|
72
71
|
const bagel = useBagel();
|
|
@@ -84,11 +83,12 @@ type StrKey = keyof StorageFile;
|
|
|
84
83
|
type FSValue = string[] | string | number;
|
|
85
84
|
|
|
86
85
|
const props = defineProps<{
|
|
87
|
-
label
|
|
86
|
+
label?: string;
|
|
88
87
|
multiple?: boolean;
|
|
89
88
|
files?: StorageFile | StorageFile[];
|
|
90
89
|
deleteEndpoint?: string;
|
|
91
90
|
bindkey?: StrKey;
|
|
91
|
+
width?: string;
|
|
92
92
|
}>();
|
|
93
93
|
|
|
94
94
|
const bindKey: StrKey = props.bindkey || 'id';
|
|
@@ -99,12 +99,36 @@ const storageFiles = $ref<StorageFile[]>([]);
|
|
|
99
99
|
const compareIds = (v1?: FSValue, v2?: FSValue) =>
|
|
100
100
|
[v1].flat().join(',') === [v2].flat().join(',');
|
|
101
101
|
|
|
102
|
+
onMounted(() => {
|
|
103
|
+
if (!props.files && [file_bindkeys.value].flat().length) {
|
|
104
|
+
const ids = [file_bindkeys.value].flat();
|
|
105
|
+
if (props.multiple) {
|
|
106
|
+
ids.forEach((id) => {
|
|
107
|
+
void bagel.get<StorageFile>(`/files/${id}`).then((file) => {
|
|
108
|
+
storageFiles.push(file);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
} else {
|
|
112
|
+
void bagel.get<StorageFile>(`/files/${ids[0]}`).then((file) => {
|
|
113
|
+
storageFiles.push(file);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
102
119
|
watch(
|
|
103
120
|
() => props.files,
|
|
104
121
|
(newFiles) => {
|
|
105
|
-
if (newFiles)
|
|
122
|
+
if (newFiles) {
|
|
123
|
+
const filesToAdd = [newFiles]
|
|
124
|
+
.flat()
|
|
125
|
+
.filter((f) => !storageFiles.find((sf) => sf[bindKey] === f[bindKey]));
|
|
126
|
+
for (const file of filesToAdd) {
|
|
127
|
+
storageFiles.push(file);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
106
130
|
},
|
|
107
|
-
{ immediate: true }
|
|
131
|
+
{ immediate: true },
|
|
108
132
|
);
|
|
109
133
|
|
|
110
134
|
watch(
|
|
@@ -119,7 +143,7 @@ watch(
|
|
|
119
143
|
file_bindkeys.value = idValue;
|
|
120
144
|
}
|
|
121
145
|
},
|
|
122
|
-
{ deep: true }
|
|
146
|
+
{ deep: true },
|
|
123
147
|
);
|
|
124
148
|
|
|
125
149
|
const fileQueue = $ref<QueueFile[]>([]);
|
|
@@ -159,7 +183,7 @@ const browse = () => {
|
|
|
159
183
|
const target = e?.target as HTMLInputElement;
|
|
160
184
|
if (target?.files) {
|
|
161
185
|
Array.from(target.files).forEach((file: File) =>
|
|
162
|
-
fileQueue.push({ name: file.name, file, progress: 0 })
|
|
186
|
+
fileQueue.push({ name: file.name, file, progress: 0 }),
|
|
163
187
|
);
|
|
164
188
|
}
|
|
165
189
|
flushQueue();
|
|
@@ -183,7 +207,7 @@ const drop = (e: DragEvent) => {
|
|
|
183
207
|
preventDefault(e);
|
|
184
208
|
if (e.dataTransfer) {
|
|
185
209
|
Array.from(e.dataTransfer.files).forEach((file: File) =>
|
|
186
|
-
fileQueue.push({ name: file.name, file, progress: 0 })
|
|
210
|
+
fileQueue.push({ name: file.name, file, progress: 0 }),
|
|
187
211
|
);
|
|
188
212
|
}
|
|
189
213
|
isDragOver = false;
|
|
@@ -193,14 +217,18 @@ const drop = (e: DragEvent) => {
|
|
|
193
217
|
|
|
194
218
|
<style scoped>
|
|
195
219
|
.bagel-input .fileUploadWrap {
|
|
220
|
+
--width: 320px;
|
|
196
221
|
outline: 1px solid var(--border-color);
|
|
197
222
|
border-radius: var(--input-border-radius);
|
|
198
223
|
text-align: center;
|
|
199
224
|
cursor: pointer;
|
|
200
225
|
transition: var(--bgl-transition);
|
|
201
226
|
position: relative;
|
|
202
|
-
height: 132px;
|
|
227
|
+
min-height: 132px;
|
|
228
|
+
max-height: 500px;
|
|
203
229
|
font-size: var(--input-font-size);
|
|
230
|
+
overflow-y: auto;
|
|
231
|
+
background: var(--input-bg);
|
|
204
232
|
}
|
|
205
233
|
|
|
206
234
|
.previewName {
|
|
@@ -208,7 +236,8 @@ const drop = (e: DragEvent) => {
|
|
|
208
236
|
text-align: start;
|
|
209
237
|
color: var(--input-color);
|
|
210
238
|
display: grid;
|
|
211
|
-
grid-template-columns: 1fr 22px;
|
|
239
|
+
grid-template-columns: auto 1fr 22px;
|
|
240
|
+
gap: 1rem;
|
|
212
241
|
align-items: center;
|
|
213
242
|
padding-inline: 14px;
|
|
214
243
|
}
|
|
@@ -222,14 +251,16 @@ const drop = (e: DragEvent) => {
|
|
|
222
251
|
.imagePreviewWrap {
|
|
223
252
|
background: var(--input-bg);
|
|
224
253
|
border-radius: var(--input-border-radius);
|
|
225
|
-
height: 90px;
|
|
226
254
|
padding: 5px;
|
|
255
|
+
min-height: 90px;
|
|
227
256
|
}
|
|
228
257
|
|
|
229
258
|
img.preview {
|
|
230
|
-
|
|
259
|
+
width: var(--width);
|
|
260
|
+
/* max-width: calc(var(--width) * 2); */
|
|
231
261
|
border-radius: var(--input-border-radius);
|
|
232
|
-
object-fit:
|
|
262
|
+
object-fit: cover;
|
|
263
|
+
background: var(--bgl-white);
|
|
233
264
|
}
|
|
234
265
|
|
|
235
266
|
.fileUploadWrap.dragover,
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="bagel-input">
|
|
3
|
-
<label class="pb-025">
|
|
4
|
-
{{ label }}
|
|
5
|
-
</label>
|
|
3
|
+
<label class="pb-025"> {{ label }}</label>
|
|
6
4
|
<div class="flex gap-05 flex-wrap">
|
|
7
5
|
<div class="radio-pill" v-for="(option, index) in options" :key="index">
|
|
8
6
|
<input
|
|
@@ -10,8 +8,9 @@
|
|
|
10
8
|
:id="`${id}-${getValue(option)}`"
|
|
11
9
|
:name="id"
|
|
12
10
|
:value="getValue(option)"
|
|
13
|
-
:checked="selectedValue
|
|
14
|
-
@change="handleSelect"
|
|
11
|
+
:checked="selectedValue == getValue(option)"
|
|
12
|
+
@change="handleSelect"
|
|
13
|
+
/>
|
|
15
14
|
<label :for="`${id}-${getValue(option)}`">{{ getLabel(option) }}</label>
|
|
16
15
|
</div>
|
|
17
16
|
</div>
|
|
@@ -50,25 +49,26 @@ const props = withDefaults(
|
|
|
50
49
|
}>(),
|
|
51
50
|
{
|
|
52
51
|
modelValue: '',
|
|
53
|
-
id: () => `radio-pill-${Math.random().toString(36).
|
|
52
|
+
id: () => `radio-pill-${Math.random().toString(36).substring(2, 9)}`,
|
|
54
53
|
label: '',
|
|
55
|
-
}
|
|
54
|
+
},
|
|
56
55
|
);
|
|
57
56
|
|
|
58
|
-
let selectedValue = $ref(
|
|
57
|
+
let selectedValue = $ref(props.modelValue);
|
|
59
58
|
|
|
60
59
|
function handleSelect(e: Event) {
|
|
61
60
|
const newVal = (e.target as any)?.value;
|
|
62
61
|
emits('update:modelValue', newVal);
|
|
63
62
|
selectedValue = newVal;
|
|
64
63
|
}
|
|
64
|
+
|
|
65
65
|
watch(
|
|
66
66
|
() => props.modelValue,
|
|
67
67
|
(newVal, oldVal) => {
|
|
68
68
|
if (newVal === oldVal || oldVal === undefined) return;
|
|
69
69
|
if (selectedValue !== newVal) selectedValue = newVal;
|
|
70
70
|
},
|
|
71
|
-
{ immediate: true }
|
|
71
|
+
{ immediate: true },
|
|
72
72
|
);
|
|
73
73
|
|
|
74
74
|
onMounted(() => {
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
@hide="updateOpen(false)"
|
|
5
5
|
ref="dropdown"
|
|
6
6
|
placement="bottom-start"
|
|
7
|
-
class="bagel-input selectinput"
|
|
7
|
+
class="bagel-input selectinput"
|
|
8
|
+
>
|
|
8
9
|
<label>
|
|
9
10
|
{{ label }}
|
|
10
11
|
<button
|
|
@@ -13,11 +14,13 @@
|
|
|
13
14
|
:disabled="disabled"
|
|
14
15
|
type="button"
|
|
15
16
|
class="selectinput-btn"
|
|
16
|
-
:class="{ isEmpty: selectedItems.length === 0 }"
|
|
17
|
+
:class="{ isEmpty: selectedItems.length === 0 }"
|
|
18
|
+
>
|
|
17
19
|
<p>{{ selectedLabel }}</p>
|
|
18
20
|
<Icon
|
|
19
21
|
v-if="!disabled"
|
|
20
|
-
v-bind="{ icon: open ? 'unfold_less' : 'unfold_more' }"
|
|
22
|
+
v-bind="{ icon: open ? 'unfold_less' : 'unfold_more' }"
|
|
23
|
+
/>
|
|
21
24
|
</button>
|
|
22
25
|
<input
|
|
23
26
|
tabindex="-1"
|
|
@@ -25,30 +28,35 @@
|
|
|
25
28
|
v-if="required"
|
|
26
29
|
@input="updateOpen(true)"
|
|
27
30
|
:value="selectedItems"
|
|
28
|
-
required
|
|
31
|
+
required
|
|
32
|
+
>
|
|
29
33
|
</label>
|
|
30
34
|
<template #popper>
|
|
31
35
|
<Card
|
|
32
36
|
class="selectinput-options p-05"
|
|
33
|
-
:style="{ width: fullWidth ? '100%' : 'auto' }"
|
|
37
|
+
:style="{ width: fullWidth ? '100%' : 'auto' }"
|
|
38
|
+
>
|
|
34
39
|
<TextInput
|
|
35
40
|
v-if="searchable"
|
|
36
41
|
ref="searchInput"
|
|
37
42
|
dense
|
|
38
43
|
:placeholder="'Search'"
|
|
39
44
|
icon="search"
|
|
40
|
-
v-model="search"
|
|
45
|
+
v-model="search"
|
|
46
|
+
/>
|
|
41
47
|
<div
|
|
42
48
|
class="selectinput-option hover gap-1"
|
|
43
49
|
v-for="(option, i) in filteredOptions"
|
|
44
50
|
:key="`${option}${i}`"
|
|
45
51
|
@click="select(option)"
|
|
46
|
-
:class="{ selected: isSelected(option) }"
|
|
52
|
+
:class="{ selected: isSelected(option) }"
|
|
53
|
+
>
|
|
47
54
|
<Icon v-if="isSelected(option)" icon="check" />
|
|
48
55
|
<Icon
|
|
49
56
|
class="opacity-3"
|
|
50
57
|
v-if="!isSelected(option)"
|
|
51
|
-
icon="fiber_manual_record"
|
|
58
|
+
icon="fiber_manual_record"
|
|
59
|
+
/>
|
|
52
60
|
<span>
|
|
53
61
|
{{ getLabel(option) }}
|
|
54
62
|
</span>
|
|
@@ -103,7 +111,7 @@ function updateOpen(visible: boolean) {
|
|
|
103
111
|
else {
|
|
104
112
|
setTimeout(
|
|
105
113
|
() => (searchInput as any)?.$el?.querySelector('input')?.focus(),
|
|
106
|
-
100
|
|
114
|
+
100,
|
|
107
115
|
);
|
|
108
116
|
}
|
|
109
117
|
}
|
|
@@ -129,33 +137,30 @@ const getLabel = (option: Option) => {
|
|
|
129
137
|
return option.label;
|
|
130
138
|
};
|
|
131
139
|
|
|
132
|
-
const getValue = (option
|
|
133
|
-
if (!option) return
|
|
140
|
+
const getValue = (option?: Option) => {
|
|
141
|
+
if (!option) return undefined;
|
|
134
142
|
if (typeof option === 'string') return option;
|
|
135
143
|
if (typeof option === 'number') return option;
|
|
136
144
|
return option.value;
|
|
137
145
|
};
|
|
138
146
|
|
|
139
|
-
const isSelected = (option: Option) =>
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
return (
|
|
149
|
-
Boolean(option) &&
|
|
147
|
+
const isSelected = (option: Option) => !!selectedItems.find((item) => getValue(option) === getValue(item));
|
|
148
|
+
|
|
149
|
+
const filteredOptions = $computed(() => props.options.filter((option) => {
|
|
150
|
+
const searchTerm = search
|
|
151
|
+
.split(/\s+/)
|
|
152
|
+
.filter(Boolean)
|
|
153
|
+
.map((t) => new RegExp(t, 'gi'));
|
|
154
|
+
return (
|
|
155
|
+
Boolean(option) &&
|
|
150
156
|
(searchTerm.every((s) => getLabel(option).match(s)) ||
|
|
151
157
|
searchTerm.length === 0)
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
);
|
|
158
|
+
);
|
|
159
|
+
}));
|
|
155
160
|
|
|
156
161
|
const select = (option: Option) => {
|
|
157
162
|
const existingIndex = selectedItems.findIndex(
|
|
158
|
-
(item) => getValue(item) === getValue(option)
|
|
163
|
+
(item) => getValue(item) === getValue(option),
|
|
159
164
|
);
|
|
160
165
|
if (existingIndex > -1) selectedItems.splice(existingIndex, 1);
|
|
161
166
|
else if (props.multiselect) {
|
|
@@ -173,9 +178,9 @@ function emitUpdate() {
|
|
|
173
178
|
if (props.multiselect) {
|
|
174
179
|
emit('update:modelValue', selectedItems.map(getValue).filter(Boolean));
|
|
175
180
|
} else {
|
|
176
|
-
selectedItems.splice(1, selectedItems.length - 1);
|
|
181
|
+
// selectedItems.splice(1, selectedItems.length - 1);
|
|
177
182
|
const [item] = selectedItems;
|
|
178
|
-
emit('update:modelValue',
|
|
183
|
+
emit('update:modelValue', getValue(item));
|
|
179
184
|
}
|
|
180
185
|
}
|
|
181
186
|
|
|
@@ -200,7 +205,7 @@ watch(
|
|
|
200
205
|
}
|
|
201
206
|
}
|
|
202
207
|
},
|
|
203
|
-
{ immediate: true }
|
|
208
|
+
{ immediate: true },
|
|
204
209
|
);
|
|
205
210
|
|
|
206
211
|
watch(
|
|
@@ -208,7 +213,7 @@ watch(
|
|
|
208
213
|
() => {
|
|
209
214
|
selectedItems.forEach((option, i) => {
|
|
210
215
|
const exists = props.options.find(
|
|
211
|
-
(o) => getValue(o) === getValue(option)
|
|
216
|
+
(o) => getValue(o) === getValue(option),
|
|
212
217
|
);
|
|
213
218
|
if (!exists) selectedItems.splice(i, 1);
|
|
214
219
|
else selectedItems.splice(i, 1, exists);
|
|
@@ -217,7 +222,7 @@ watch(
|
|
|
217
222
|
// const newSelection = JSON.stringify(selectedItems.map(getValue));
|
|
218
223
|
// if (original !== newSelection) emitUpdate();
|
|
219
224
|
},
|
|
220
|
-
{ deep: true, immediate: true }
|
|
225
|
+
{ deep: true, immediate: true },
|
|
221
226
|
);
|
|
222
227
|
</script>
|
|
223
228
|
|
|
@@ -96,6 +96,7 @@ const props = withDefaults(
|
|
|
96
96
|
autoheight?: boolean;
|
|
97
97
|
code?: boolean;
|
|
98
98
|
lines?: number;
|
|
99
|
+
// eslint-disable-next-line max-len
|
|
99
100
|
autocomplete?: string;
|
|
100
101
|
autofocus?: boolean;
|
|
101
102
|
}>(),
|
|
@@ -103,7 +104,6 @@ const props = withDefaults(
|
|
|
103
104
|
type: 'text',
|
|
104
105
|
toggleEdit: false,
|
|
105
106
|
modelValue: '',
|
|
106
|
-
autocomplete: 'off',
|
|
107
107
|
},
|
|
108
108
|
);
|
|
109
109
|
let inputVal = $ref<string | number>();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<TabsNav :tabs="tabs" :group="group" class="mb-05" />
|
|
2
|
+
<TabsNav v-model="slctTab" :tabs="tabs" :group="group" class="mb-05" />
|
|
3
3
|
<div v-if="currentTab">
|
|
4
4
|
<template v-if="slots[currentTab]">
|
|
5
5
|
<slot :name="currentTab" />
|
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
</template>
|
|
10
10
|
</div>
|
|
11
11
|
</template>
|
|
12
|
-
|
|
12
|
+
z
|
|
13
13
|
<script setup lang="ts">
|
|
14
14
|
import { TabsNav, type Tab } from '@bagelink/vue';
|
|
15
|
-
import { defineProps, useSlots, h, defineComponent } from 'vue';
|
|
15
|
+
import { defineProps, useSlots, h, type VNode, defineComponent } from 'vue';
|
|
16
|
+
|
|
16
17
|
import { useTabs } from './tabsManager';
|
|
17
18
|
|
|
18
19
|
const props = defineProps<{
|
|
@@ -26,13 +27,21 @@ const { currentTab } = useTabs(group);
|
|
|
26
27
|
|
|
27
28
|
const tabValue = (tab: Tab) => (typeof tab === 'string' ? tab : tab.id);
|
|
28
29
|
|
|
30
|
+
const emit = defineEmits(['update:modelValue']);
|
|
31
|
+
|
|
32
|
+
const slctTab = $computed({
|
|
33
|
+
get: () => props.modelValue || tabValue(props.tabs[0]),
|
|
34
|
+
set: (value) => emit('update:modelValue', value),
|
|
35
|
+
});
|
|
36
|
+
|
|
29
37
|
const tabComponent = defineComponent({
|
|
30
38
|
render() {
|
|
31
39
|
const currentTabIndex = props.tabs.findIndex(
|
|
32
|
-
(tab) => tabValue(tab) === currentTab.value
|
|
40
|
+
(tab) => tabValue(tab) === currentTab.value,
|
|
33
41
|
);
|
|
34
42
|
const slotChildren = slots?.default?.()?.[1]?.children;
|
|
35
|
-
|
|
43
|
+
|
|
44
|
+
return h('div', (slotChildren as VNode[])[currentTabIndex]);
|
|
36
45
|
},
|
|
37
46
|
});
|
|
38
47
|
</script>
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
@click="selectTab(tab)"
|
|
7
7
|
class="bgl_tab"
|
|
8
8
|
v-for="(tab, i) in tabs"
|
|
9
|
-
:key="i"
|
|
9
|
+
:key="i"
|
|
10
|
+
>
|
|
10
11
|
<Icon v-if="typeof tab !== 'string' && tab.icon" :icon="tab.icon" />
|
|
11
12
|
{{ tabLabel(tab) }}
|
|
12
13
|
</button>
|
|
@@ -15,6 +16,7 @@
|
|
|
15
16
|
|
|
16
17
|
<script setup lang="ts">
|
|
17
18
|
import { type Tab, Icon } from '@bagelink/vue';
|
|
19
|
+
import { watch } from 'vue';
|
|
18
20
|
import { useTabs } from './tabsManager';
|
|
19
21
|
|
|
20
22
|
const props = defineProps<{
|
|
@@ -41,39 +43,57 @@ const isActive = (tab: Tab) => {
|
|
|
41
43
|
if (typeof tab === 'string') return currentTab.value === tab;
|
|
42
44
|
return currentTab.value === tab.id;
|
|
43
45
|
};
|
|
46
|
+
|
|
44
47
|
const tabLabel = (tab: Tab) => {
|
|
45
48
|
if (typeof tab === 'string') return tab;
|
|
46
49
|
return tab.label;
|
|
47
50
|
};
|
|
51
|
+
|
|
52
|
+
watch(
|
|
53
|
+
() => props.modelValue,
|
|
54
|
+
(value) => {
|
|
55
|
+
if (value && !isActive(value)) currentTab.value = value;
|
|
56
|
+
},
|
|
57
|
+
{ immediate: true },
|
|
58
|
+
);
|
|
48
59
|
</script>
|
|
49
60
|
|
|
50
61
|
<style scoped>
|
|
51
62
|
.bgl_tabs_wrap {
|
|
63
|
+
background: var(--input-bg);
|
|
64
|
+
border-radius: calc(var(--input-border-radius) * 1.4);
|
|
65
|
+
padding-inline: calc(var(--btn-padding) / 8);
|
|
66
|
+
padding-block: calc(var(--btn-padding) / 8);
|
|
67
|
+
box-shadow: inset 0 0 10px #00000012;
|
|
68
|
+
gap: 0.25rem;
|
|
52
69
|
}
|
|
53
70
|
.bgl_tab {
|
|
54
71
|
border: none;
|
|
55
|
-
border-bottom: 2px solid var(--border-color);
|
|
56
72
|
background: transparent;
|
|
73
|
+
cursor: pointer;
|
|
57
74
|
font-size: var(--input-font-size);
|
|
58
75
|
font-family: inherit;
|
|
59
|
-
cursor: pointer;
|
|
60
76
|
transition: var(--bgl-transition);
|
|
61
|
-
padding-inline
|
|
62
|
-
padding-
|
|
63
|
-
|
|
77
|
+
padding-inline: calc(var(--btn-padding) / 2);
|
|
78
|
+
padding-block: calc(var(--btn-padding) / 8);
|
|
79
|
+
border-radius: var(--input-border-radius);
|
|
64
80
|
}
|
|
65
|
-
|
|
66
|
-
.bgl_tab.currentTab {
|
|
67
|
-
border-bottom: 2px solid var(--primary-color);
|
|
68
|
-
}
|
|
69
|
-
|
|
81
|
+
.bgl_tab.currentTab,
|
|
70
82
|
.bgl_tab.active {
|
|
71
|
-
|
|
72
|
-
|
|
83
|
+
background: var(--bgl-white);
|
|
84
|
+
box-shadow: 0 0 10px #00000011;
|
|
85
|
+
position: relative;
|
|
73
86
|
}
|
|
74
87
|
|
|
75
88
|
.bgl_tab:hover {
|
|
76
|
-
|
|
89
|
+
background: var(--input-bg);
|
|
90
|
+
filter: brightness(95%);
|
|
91
|
+
}
|
|
92
|
+
.bgl_tab.currentTab:hover,
|
|
93
|
+
.bgl_tab.active:hover {
|
|
94
|
+
background: var(--bgl-white);
|
|
95
|
+
filter: brightness(100%);
|
|
96
|
+
box-shadow: 0 0 10px #00000022;
|
|
77
97
|
}
|
|
78
98
|
|
|
79
99
|
.bgl_tab:active {
|