@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.
Files changed (45) hide show
  1. package/dist/components/Alert.vue.d.ts +4 -1
  2. package/dist/components/Alert.vue.d.ts.map +1 -1
  3. package/dist/components/TableSchema.vue.d.ts +2 -5
  4. package/dist/components/TableSchema.vue.d.ts.map +1 -1
  5. package/dist/components/form/BglField.vue.d.ts.map +1 -1
  6. package/dist/components/form/BglForm.vue.d.ts +4 -1
  7. package/dist/components/form/BglForm.vue.d.ts.map +1 -1
  8. package/dist/components/form/inputs/DatePicker.vue.d.ts +4 -0
  9. package/dist/components/form/inputs/DatePicker.vue.d.ts.map +1 -1
  10. package/dist/components/form/inputs/FileUpload.vue.d.ts +6 -2
  11. package/dist/components/form/inputs/FileUpload.vue.d.ts.map +1 -1
  12. package/dist/components/form/inputs/RadioPillsInput.vue.d.ts.map +1 -1
  13. package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
  14. package/dist/components/form/inputs/TextInput.vue.d.ts +0 -3
  15. package/dist/components/form/inputs/TextInput.vue.d.ts.map +1 -1
  16. package/dist/components/layout/Tabs.vue.d.ts +6 -2
  17. package/dist/components/layout/Tabs.vue.d.ts.map +1 -1
  18. package/dist/components/layout/TabsNav.vue.d.ts.map +1 -1
  19. package/dist/index.cjs +239 -137
  20. package/dist/index.mjs +240 -138
  21. package/dist/plugins/bagel.d.ts +3 -0
  22. package/dist/plugins/bagel.d.ts.map +1 -1
  23. package/dist/style.css +109 -91
  24. package/dist/utils/BagelFormUtils.d.ts +1 -0
  25. package/dist/utils/BagelFormUtils.d.ts.map +1 -1
  26. package/dist/utils/index.d.ts +1 -0
  27. package/dist/utils/index.d.ts.map +1 -1
  28. package/dist/utils/lang.d.ts +7 -0
  29. package/dist/utils/lang.d.ts.map +1 -0
  30. package/package.json +1 -1
  31. package/src/components/Alert.vue +4 -2
  32. package/src/components/TableSchema.vue +19 -9
  33. package/src/components/form/BglField.vue +5 -3
  34. package/src/components/form/BglForm.vue +13 -9
  35. package/src/components/form/inputs/DatePicker.vue +8 -2
  36. package/src/components/form/inputs/FileUpload.vue +54 -23
  37. package/src/components/form/inputs/RadioPillsInput.vue +9 -9
  38. package/src/components/form/inputs/SelectInput.vue +36 -31
  39. package/src/components/form/inputs/TextInput.vue +1 -1
  40. package/src/components/layout/Tabs.vue +14 -5
  41. package/src/components/layout/TabsNav.vue +34 -14
  42. package/src/plugins/bagel.ts +13 -4
  43. package/src/utils/BagelFormUtils.ts +44 -10
  44. package/src/utils/index.ts +2 -0
  45. 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
- <slot name="submit" />
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: () => props.modelValue || formData,
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', data);
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
- height="60"
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: string;
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) storageFiles.push(...[newFiles].flat());
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
- height: 80px;
259
+ width: var(--width);
260
+ /* max-width: calc(var(--width) * 2); */
231
261
  border-radius: var(--input-border-radius);
232
- object-fit: contain;
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 === getValue(option)"
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).substr(2, 9)}`,
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: 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
- !!selectedItems.find((item) => getValue(option) === getValue(item));
141
-
142
- const filteredOptions = $computed(() =>
143
- props.options.filter((option) => {
144
- const searchTerm = search
145
- .split(/\s+/)
146
- .filter(Boolean)
147
- .map((t) => new RegExp(t, 'gi'));
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', item ? getValue(item) : null);
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
- return h('div', slotChildren?.[currentTabIndex]);
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-start: calc(var(--btn-padding) / 2);
62
- padding-inline-end: calc(var(--btn-padding) / 2);
63
- padding-bottom: calc(var(--btn-padding) / 4);
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
- color: var(--bgl-primary);
72
- border-bottom: 2px solid var(--bgl-primary);
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
- color: var(--bgl-primary);
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 {