@bagelink/vue 0.0.374 → 0.0.384

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.
@@ -1,17 +1,27 @@
1
1
  <template>
2
2
  <div
3
- class="bg-dark" :class="{ 'is-side': side, 'is-active': isVisible, 'bg-lignt': false }"
4
- @click="() => (dismissable ? closeModal() : '')" @keydown.esc="closeModal"
5
- >
6
- <Card class="modal" @click.stop>
3
+ class="bg-dark"
4
+ :class="{ 'is-side': side, 'is-active': isVisible, 'bg-lignt': false }"
5
+ @click="() => (dismissable ? closeModal() : '')"
6
+ @keydown.esc="closeModal">
7
+ <Card class="modal" @click.stop :style="{ ...maxWidth }">
7
8
  <header class="tool-bar">
8
9
  <slot name="toolbar" />
9
- <Btn :style="{ float: side ? 'left' : 'right' }" flat icon="close" @click="closeModal" />
10
+ <Btn
11
+ :style="{ float: side ? 'left' : 'right' }"
12
+ flat
13
+ icon="close"
14
+ @click="closeModal" />
10
15
  <Title class="modal-title" tag="h3" v-if="title" :label="title" />
11
16
  </header>
12
17
  <slot />
13
18
  <footer class="modal-footer mt-3">
14
- <Btn v-for="(action, i) in actions" :key="i" @click="closeModal" color="gray" v-bind="action" />
19
+ <Btn
20
+ v-for="(action, i) in actions"
21
+ :key="i"
22
+ @click="closeModal"
23
+ color="gray"
24
+ v-bind="action" />
15
25
  <slot name="footer" />
16
26
  </footer>
17
27
  </Card>
@@ -20,26 +30,36 @@
20
30
 
21
31
  <script lang="ts" setup>
22
32
  import { onMounted, onUnmounted, watch } from 'vue';
23
- import {
24
- type BtnOptions, Btn, useEscape, Title, Card,
25
- } from '@bagelink/vue';
33
+ import { type BtnOptions, Btn, useEscape, Title, Card } from '@bagelink/vue';
26
34
  import '../styles/modal.css';
27
35
 
28
36
  const props = defineProps<{
29
- side?: boolean;
30
- title?: string;
31
- dismissable?: boolean;
32
- actions?: BtnOptions[];
33
- visible?: boolean;
37
+ side?: boolean;
38
+ title?: string;
39
+ width?: string;
40
+ dismissable?: boolean;
41
+ actions?: BtnOptions[];
42
+ visible?: boolean;
34
43
  }>();
35
44
 
36
45
  let isVisible = $ref<boolean>(false);
37
46
 
38
- watch(() => props.visible, (val) => {
39
- if (val === isVisible || val === undefined) return;
40
- if (val) openModal();
41
- else closeModal();
42
- }, { immediate: true });
47
+ watch(
48
+ () => props.visible,
49
+ (val) => {
50
+ if (val === isVisible || val === undefined) return;
51
+ if (val) openModal();
52
+ else closeModal();
53
+ },
54
+ { immediate: true }
55
+ );
56
+
57
+ const maxWidth = $computed(() => {
58
+ const { width } = props;
59
+ if (width?.match(/px|em|rem|vw|vh|%/)) return { 'max-width': width };
60
+ if (width?.match(/\d+/)) return { 'max-width': `${width}px` };
61
+ return { 'max-width': '720px' };
62
+ });
43
63
 
44
64
  const emit = defineEmits(['update:visible']);
45
65
 
@@ -50,10 +70,11 @@ const closeModal = () => {
50
70
 
51
71
  defineExpose({ closeModal });
52
72
 
53
- const escapeKeyClose = (e: KeyboardEvent) => props?.dismissable && useEscape(e, () => closeModal());
73
+ const escapeKeyClose = (e: KeyboardEvent) =>
74
+ props?.dismissable && useEscape(e, () => closeModal());
54
75
 
55
76
  function openModal() {
56
- setTimeout(() => isVisible = true, 1);
77
+ setTimeout(() => (isVisible = true), 1);
57
78
  }
58
79
 
59
80
  onMounted(() => document.addEventListener('keydown', escapeKeyClose));
@@ -65,35 +86,35 @@ onUnmounted(() => {
65
86
 
66
87
  <style>
67
88
  .modal-title {
68
- text-align: center;
69
- font-weight: 600;
70
- font-size: 20px;
71
- margin-top: 0.5rem;
72
- margin-bottom: 0 !important;
73
- width: 100%;
74
- -webkit-padding-end: 40px;
75
- padding-inline-end: 40px;
76
- line-height: 2;
77
- display: -webkit-box;
78
- max-width: 100%;
79
- -webkit-line-clamp: 1;
80
- -webkit-box-orient: vertical;
81
- overflow: hidden;
82
- text-overflow: ellipsis;
89
+ text-align: center;
90
+ font-weight: 600;
91
+ font-size: 20px;
92
+ margin-top: 0.5rem;
93
+ margin-bottom: 0 !important;
94
+ width: 100%;
95
+ -webkit-padding-end: 40px;
96
+ padding-inline-end: 40px;
97
+ line-height: 2;
98
+ display: -webkit-box;
99
+ max-width: 100%;
100
+ -webkit-line-clamp: 1;
101
+ -webkit-box-orient: vertical;
102
+ overflow: hidden;
103
+ text-overflow: ellipsis;
83
104
  }
84
105
 
85
106
  .modal-footer {
86
- gap: 1rem;
87
- display: flex;
88
- justify-content: space-between;
89
- align-items: center;
107
+ gap: 1rem;
108
+ display: flex;
109
+ justify-content: space-between;
110
+ align-items: center;
90
111
  }
91
112
 
92
- .modal-footer>div {
93
- gap: 1rem;
94
- display: flex;
95
- justify-content: space-between;
96
- align-items: center;
97
- gap: 0;
113
+ .modal-footer > div {
114
+ gap: 1rem;
115
+ display: flex;
116
+ justify-content: space-between;
117
+ align-items: center;
118
+ gap: 0;
98
119
  }
99
120
  </style>
@@ -1,13 +1,27 @@
1
1
  <template>
2
2
  <Modal
3
- @onUpdate:isModalVisible="props['onUpdate:isModalVisible']" :side="side" ref="modal" :dismissable="dismissable"
4
- :title="title"
5
- >
6
- <BagelForm @submit="runSubmit" ref="form" v-model="formData" :schema="computedFormSchema" />
3
+ @onUpdate:isModalVisible="props['onUpdate:isModalVisible']"
4
+ :side
5
+ ref="modal"
6
+ :width
7
+ :dismissable
8
+ :title="title">
9
+ <BagelForm
10
+ @submit="runSubmit"
11
+ ref="form"
12
+ v-model="formData"
13
+ :schema="computedFormSchema" />
7
14
  <template #footer v-if="onDelete || onSubmit">
8
15
  <div>
9
16
  <Btn thin flat value="Cancel" @click="closeModal" />
10
- <Btn thin icon="delete" v-if="onDelete" flat value="Delete" @click="runDelete" color="red" />
17
+ <Btn
18
+ thin
19
+ icon="delete"
20
+ v-if="onDelete"
21
+ flat
22
+ value="Delete"
23
+ @click="runDelete"
24
+ color="red" />
11
25
  </div>
12
26
  <Btn value="Submit" @click="runSubmit" />
13
27
  </template>
@@ -16,28 +30,33 @@
16
30
 
17
31
  <script lang="ts" setup>
18
32
  import {
19
- Modal, Btn, BagelForm, type BglFormSchemaT, type BtnOptions, useBagel,
33
+ Modal,
34
+ Btn,
35
+ BagelForm,
36
+ type BglFormSchemaT,
37
+ type BtnOptions,
38
+ useBagel,
20
39
  } from '@bagelink/vue';
21
40
 
22
41
  const bagel = useBagel();
23
42
 
24
43
  const props = defineProps<{
25
- side?: boolean;
26
- title?: string;
27
- dismissable?: boolean;
28
- actions?: BtnOptions[];
29
- schema: BglFormSchemaT | (() => BglFormSchemaT);
44
+ 'side'?: boolean;
45
+ 'title'?: string;
46
+ 'width'?: string;
47
+ 'dismissable'?: boolean;
48
+ 'actions'?: BtnOptions[];
49
+ 'schema': BglFormSchemaT | (() => BglFormSchemaT);
30
50
  // eslint-disable-next-line no-unused-vars
31
- onSubmit?: ((formData: any) => Promise<void>);
51
+ 'onSubmit'?: (formData: any) => Promise<void>;
32
52
  // eslint-disable-next-line no-unused-vars
33
- onDelete?: ((id: string) => void);
53
+ 'onDelete'?: (id: string) => void;
34
54
  // eslint-disable-next-line no-unused-vars
35
- 'onUpdate:isModalVisible'?: ((visible: boolean) => void);
55
+ 'onUpdate:isModalVisible'?: (visible: boolean) => void;
36
56
  // eslint-disable-next-line no-unused-vars
37
- onError?: ((err: any) => void);
38
-
39
- modelValue?: Record<string, any>;
57
+ 'onError'?: (err: any) => void;
40
58
 
59
+ 'modelValue'?: Record<string, any>;
41
60
  }>();
42
61
 
43
62
  const modal = $ref<InstanceType<typeof Modal>>();
@@ -47,7 +66,9 @@ const computedFormSchema = $computed(() => {
47
66
  return props.schema;
48
67
  });
49
68
 
50
- const formData = defineModel<Record<string, any>>('modelValue', { default: {} });
69
+ const formData = defineModel<Record<string, any>>('modelValue', {
70
+ default: {},
71
+ });
51
72
  const form = $ref<InstanceType<typeof BagelForm>>();
52
73
  const closeModal = () => modal?.closeModal();
53
74
 
@@ -9,21 +9,13 @@
9
9
  @drop="drop"
10
10
  @dragleave="dragleave"
11
11
  class="fileUploadWrap"
12
- :class="{ fileDropZone: !storageFiles.length && !fileQueue.length, dragover: isDragOver }"
13
- >
14
- <template
15
- v-for="file in storageFiles"
16
- :key="file.id"
17
- >
18
- <div
19
- class="imagePreviewWrap"
20
- v-if="!multiple"
21
- >
22
- <img
23
- class="preview"
24
- :src="file.url"
25
- alt=""
26
- >
12
+ :class="{
13
+ fileDropZone: !storageFiles.length && !fileQueue.length,
14
+ dragover: isDragOver,
15
+ }">
16
+ <template v-for="file in storageFiles" :key="file.id">
17
+ <div class="imagePreviewWrap" v-if="!multiple">
18
+ <img class="preview" :src="file.url" alt="" />
27
19
  </div>
28
20
  <div class="previewName">
29
21
  <img
@@ -31,8 +23,7 @@
31
23
  v-if="multiple"
32
24
  class="preview"
33
25
  :src="file.url"
34
- alt=""
35
- >
26
+ alt="" />
36
27
  <p class="no-margin">
37
28
  {{ file.name }}
38
29
  </p>
@@ -41,23 +32,12 @@
41
32
  @click.stop="removeFile(file)"
42
33
  flat
43
34
  icon="delete"
44
- color="red"
45
- />
35
+ color="red" />
46
36
  </div>
47
37
  </template>
48
- <template
49
- v-for="fileQ in fileQueue"
50
- :key="fileQ.file"
51
- >
52
- <div
53
- class="imagePreviewWrap"
54
- v-if="!multiple"
55
- >
56
- <img
57
- class="preview"
58
- :src="fileToUrl(fileQ.file)"
59
- alt=""
60
- >
38
+ <template v-for="fileQ in fileQueue" :key="fileQ.file">
39
+ <div class="imagePreviewWrap" v-if="!multiple">
40
+ <img class="preview" :src="fileToUrl(fileQ.file)" alt="" />
61
41
  </div>
62
42
  <div class="previewName">
63
43
  <img
@@ -65,8 +45,7 @@
65
45
  v-if="multiple"
66
46
  class="preview"
67
47
  :src="fileToUrl(fileQ.file)"
68
- alt=""
69
- >
48
+ alt="" />
70
49
  <p class="no-margin">
71
50
  {{ fileQ.name }}
72
51
  </p>
@@ -74,18 +53,11 @@
74
53
  class="pie"
75
54
  :style="`--p:${fileQ.progress}`"
76
55
  style="--b: 2px"
77
- :class="{ complete: fileQ.progress === 100 }"
78
- >
79
- <span
80
- class="progress"
81
- v-if="fileQ.progress < 100"
82
- >
56
+ :class="{ complete: fileQ.progress === 100 }">
57
+ <span class="progress" v-if="fileQ.progress < 100">
83
58
  {{ `${fileQ.progress.toFixed(0)}` }}
84
59
  </span>
85
- <MaterialIcon
86
- class="success"
87
- icon="check"
88
- />
60
+ <MaterialIcon class="success" icon="check" />
89
61
  </div>
90
62
  </div>
91
63
  </template>
@@ -95,9 +67,7 @@
95
67
 
96
68
  <script setup lang="ts">
97
69
  import { watch } from 'vue';
98
- import {
99
- Btn, type StorageFile, useBagel, MaterialIcon,
100
- } from '@bagelink/vue';
70
+ import { Btn, type StorageFile, useBagel, MaterialIcon } from '@bagelink/vue';
101
71
 
102
72
  const bagel = useBagel();
103
73
 
@@ -109,28 +79,48 @@ type QueueFile = {
109
79
  uploaded?: boolean;
110
80
  };
111
81
 
82
+ type StrKey = keyof StorageFile;
83
+
84
+ type FSValue = string[] | string | number;
85
+
112
86
  const props = defineProps<{
113
- label: string,
114
- multiple?: boolean,
115
- files?: StorageFile | StorageFile[],
87
+ label: string;
88
+ multiple?: boolean;
89
+ files?: StorageFile | StorageFile[];
116
90
  deleteEndpoint?: string;
91
+ bindkey?: StrKey;
117
92
  }>();
118
93
 
119
- const file_ids = defineModel<string[] | string>('modelValue');
120
- const storageFiles = $ref<StorageFile[]>([]);
121
-
122
- const compareIds = (v1?: string[] | string, v2?: string[] | string) => [v1].flat().join(',') === [v2].flat().join(',');
94
+ const bindKey: StrKey = props.bindkey || 'id';
123
95
 
124
- watch(() => props.files, (newFiles) => {
125
- if (newFiles) storageFiles.push(...[newFiles].flat());
126
- }, { immediate: true });
96
+ const file_bindkeys = defineModel<FSValue>('modelValue');
97
+ const storageFiles = $ref<StorageFile[]>([]);
127
98
 
128
- watch(() => storageFiles, (newFiles) => {
129
- let idValue: string[] | string = '';
130
- if (props.multiple) idValue = newFiles.map((f) => f.id || '');
131
- else idValue = newFiles[0]?.id || '';
132
- if (!compareIds(file_ids?.value, idValue)) file_ids.value = idValue;
133
- }, { deep: true });
99
+ const compareIds = (v1?: FSValue, v2?: FSValue) =>
100
+ [v1].flat().join(',') === [v2].flat().join(',');
101
+
102
+ watch(
103
+ () => props.files,
104
+ (newFiles) => {
105
+ if (newFiles) storageFiles.push(...[newFiles].flat());
106
+ },
107
+ { immediate: true }
108
+ );
109
+
110
+ watch(
111
+ () => storageFiles,
112
+ (newFiles) => {
113
+ let idValue: FSValue;
114
+ if (props.multiple) idValue = newFiles.map((f) => f[bindKey]) as FSValue;
115
+ else {
116
+ idValue = (newFiles[0]?.[bindKey] as string) || '';
117
+ }
118
+ if (!compareIds(file_bindkeys?.value, idValue)) {
119
+ file_bindkeys.value = idValue;
120
+ }
121
+ },
122
+ { deep: true }
123
+ );
134
124
 
135
125
  const fileQueue = $ref<QueueFile[]>([]);
136
126
  let isDragOver = $ref(false);
@@ -145,20 +135,20 @@ const removeFile = (file: StorageFile) => {
145
135
  const index = storageFiles.indexOf(file);
146
136
  storageFiles.splice(index, 1);
147
137
 
148
- if (props.deleteEndpoint) void bagel.delete(`${props.deleteEndpoint}/${file.id}`);
138
+ if (props.deleteEndpoint) {
139
+ void bagel.delete(`${props.deleteEndpoint}/${file.id}`);
140
+ }
149
141
  };
150
142
 
151
143
  const flushQueue = () => {
152
- fileQueue.forEach(
153
- async (file, i) => {
154
- if (!props.multiple) storageFiles.splice(0, 1);
155
- const serverFile = await bagel.uploadFile<StorageFile>(file.file, {
156
- onUploadProgress: (e: any) => (file.progress = e.progress * 100 - 1),
157
- });
158
- storageFiles.push(serverFile);
159
- fileQueue.splice(i, 1);
160
- },
161
- );
144
+ fileQueue.forEach(async (file, i) => {
145
+ if (!props.multiple) storageFiles.splice(0, 1);
146
+ const serverFile = await bagel.uploadFile<StorageFile>(file.file, {
147
+ onUploadProgress: (e: any) => (file.progress = e.progress * 100 - 1),
148
+ });
149
+ storageFiles.push(serverFile);
150
+ fileQueue.splice(i, 1);
151
+ });
162
152
  };
163
153
 
164
154
  const browse = () => {
@@ -168,7 +158,9 @@ const browse = () => {
168
158
  input.onchange = (e: Event) => {
169
159
  const target = e?.target as HTMLInputElement;
170
160
  if (target?.files) {
171
- Array.from(target.files).forEach((file: File) => fileQueue.push({ name: file.name, file, progress: 0 }));
161
+ Array.from(target.files).forEach((file: File) =>
162
+ fileQueue.push({ name: file.name, file, progress: 0 })
163
+ );
172
164
  }
173
165
  flushQueue();
174
166
  };
@@ -190,13 +182,9 @@ const dragover = (e: DragEvent) => {
190
182
  const drop = (e: DragEvent) => {
191
183
  preventDefault(e);
192
184
  if (e.dataTransfer) {
193
- Array
194
- .from(e.dataTransfer.files)
195
- .forEach(
196
- (file: File) => (
197
- fileQueue.push({ name: file.name, file, progress: 0 })
198
- ),
199
- );
185
+ Array.from(e.dataTransfer.files).forEach((file: File) =>
186
+ fileQueue.push({ name: file.name, file, progress: 0 })
187
+ );
200
188
  }
201
189
  isDragOver = false;
202
190
  flushQueue();
@@ -222,7 +210,7 @@ const drop = (e: DragEvent) => {
222
210
  display: grid;
223
211
  grid-template-columns: 1fr 22px;
224
212
  align-items: center;
225
-
213
+ padding-inline: 14px;
226
214
  }
227
215
 
228
216
  .previewName p {
@@ -258,7 +246,7 @@ img.preview {
258
246
  }
259
247
 
260
248
  .bagel-input .fileUploadWrap.fileDropZone:after {
261
- content: "Drop files here or click to upload";
249
+ content: 'Drop files here or click to upload';
262
250
  }
263
251
 
264
252
  .pie {
@@ -271,18 +259,22 @@ img.preview {
271
259
  }
272
260
 
273
261
  .pie:before {
274
- content: "";
262
+ content: '';
275
263
  position: absolute;
276
264
  border-radius: 50%;
277
265
  inset: 0;
278
266
  transition: all 0.2s ease-in-out;
279
267
  background: conic-gradient(var(--bgl-primary) calc(var(--p) * 1%), #0000 0);
280
- -webkit-mask: radial-gradient(farthest-side,
281
- #0000 calc(99% - var(--b)),
282
- #000 calc(100% - var(--b)));
283
- mask: radial-gradient(farthest-side,
284
- #0000 calc(99% - var(--b)),
285
- #000 calc(100% - var(--b)));
268
+ -webkit-mask: radial-gradient(
269
+ farthest-side,
270
+ #0000 calc(99% - var(--b)),
271
+ #000 calc(100% - var(--b))
272
+ );
273
+ mask: radial-gradient(
274
+ farthest-side,
275
+ #0000 calc(99% - var(--b)),
276
+ #000 calc(100% - var(--b))
277
+ );
286
278
  }
287
279
 
288
280
  .pie .success {