@bagelink/vue 1.4.109 → 1.4.115
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/bin/generateFormSchema.ts +12 -12
- package/dist/components/Card.vue.d.ts.map +1 -1
- package/dist/components/ImportData.vue.d.ts.map +1 -1
- package/dist/components/ListItem.vue.d.ts +6 -1
- package/dist/components/ListItem.vue.d.ts.map +1 -1
- package/dist/components/analytics/BarChart.vue.d.ts +39 -0
- package/dist/components/analytics/BarChart.vue.d.ts.map +1 -0
- package/dist/components/analytics/KpiCard.vue.d.ts +24 -0
- package/dist/components/analytics/KpiCard.vue.d.ts.map +1 -0
- package/dist/components/analytics/LineChart.vue.d.ts +26 -0
- package/dist/components/analytics/LineChart.vue.d.ts.map +1 -0
- package/dist/components/analytics/PieChart.vue.d.ts +24 -0
- package/dist/components/analytics/PieChart.vue.d.ts.map +1 -0
- package/dist/components/analytics/index.d.ts +5 -0
- package/dist/components/analytics/index.d.ts.map +1 -0
- package/dist/components/calendar/Index.vue.d.ts.map +1 -1
- package/dist/components/calendar/index.d.ts +2 -0
- package/dist/components/calendar/index.d.ts.map +1 -0
- package/dist/components/calendar/views/MonthView.vue.d.ts.map +1 -1
- package/dist/components/calendar/views/WeekView.vue.d.ts.map +1 -1
- package/dist/components/dataTable/DataTable.vue.d.ts.map +1 -1
- package/dist/components/form/BagelForm.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/DatePicker.vue.d.ts +2 -0
- package/dist/components/form/inputs/DatePicker.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RadioGroup.vue.d.ts +6 -10
- package/dist/components/form/inputs/RadioGroup.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/utils/media.d.ts.map +1 -1
- package/dist/components/form/inputs/SelectInput.vue.d.ts +2 -2
- package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
- package/dist/components/layout/AppContent.vue.d.ts +34 -0
- package/dist/components/layout/AppContent.vue.d.ts.map +1 -0
- package/dist/components/layout/AppLayout.vue.d.ts +27 -0
- package/dist/components/layout/AppLayout.vue.d.ts.map +1 -0
- package/dist/components/layout/AppSidebar.vue.d.ts +44 -0
- package/dist/components/layout/AppSidebar.vue.d.ts.map +1 -0
- package/dist/components/layout/index.d.ts +3 -0
- package/dist/components/layout/index.d.ts.map +1 -1
- package/dist/composables/useFormField.d.ts.map +1 -1
- package/dist/composables/useSchemaField.d.ts +2 -2
- package/dist/composables/useSchemaField.d.ts.map +1 -1
- package/dist/index.cjs +19 -19
- package/dist/index.mjs +10 -10
- package/dist/style.css +1 -1
- package/dist/types/BagelForm.d.ts +25 -13
- package/dist/types/BagelForm.d.ts.map +1 -1
- package/dist/utils/BagelFormUtils.d.ts +11 -8
- package/dist/utils/BagelFormUtils.d.ts.map +1 -1
- package/dist/utils/calendar/dateUtils.d.ts +21 -0
- package/dist/utils/calendar/dateUtils.d.ts.map +1 -1
- package/dist/utils/elementUtils.d.ts +5 -0
- package/dist/utils/elementUtils.d.ts.map +1 -1
- package/dist/utils/useSearch.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/Card.vue +1 -2
- package/src/components/DataPreview.vue +1 -1
- package/src/components/ImportData.vue +94 -88
- package/src/components/ListItem.vue +32 -24
- package/src/components/analytics/BarChart.vue +153 -0
- package/src/components/analytics/KpiCard.vue +84 -0
- package/src/components/analytics/LineChart.vue +267 -0
- package/src/components/analytics/PieChart.vue +183 -0
- package/src/components/analytics/index.ts +4 -0
- package/src/components/calendar/Index.vue +15 -35
- package/src/components/calendar/views/MonthView.vue +84 -88
- package/src/components/calendar/views/WeekView.vue +143 -89
- package/src/components/dataTable/DataTable.vue +2 -3
- package/src/components/form/BagelForm.vue +27 -6
- package/src/components/form/inputs/DateInput.vue +2 -2
- package/src/components/form/inputs/DatePicker.vue +42 -48
- package/src/components/form/inputs/RadioGroup.vue +60 -35
- package/src/components/form/inputs/RichText/utils/media.ts +1 -2
- package/src/components/form/inputs/SelectInput.vue +94 -101
- package/src/components/form/inputs/Upload/upload.css +135 -138
- package/src/components/layout/AppContent.vue +125 -0
- package/src/components/layout/AppLayout.vue +124 -0
- package/src/components/layout/AppSidebar.vue +271 -0
- package/src/components/layout/index.ts +5 -0
- package/src/composables/useFormField.ts +6 -0
- package/src/composables/useSchemaField.ts +38 -10
- package/src/styles/inputs.css +9 -0
- package/src/styles/theme.css +2 -2
- package/src/types/BagelForm.ts +68 -13
- package/src/utils/BagelFormUtils.ts +49 -52
- package/src/utils/calendar/dateUtils.ts +71 -17
- package/src/utils/elementUtils.ts +23 -4
- package/src/utils/useSearch.ts +14 -7
- /package/src/components/{dialog → calendar}/index.ts +0 -0
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
import type { IconType, Option } from '@bagelink/vue'
|
|
3
3
|
import type { AlignedPlacement } from '../../Dropdown.vue'
|
|
4
|
-
import { Btn, Card,
|
|
4
|
+
import { Btn, Card, Skeleton, Dropdown, Icon, TextInput, useSearch } from '@bagelink/vue'
|
|
5
5
|
import { onMounted, watch } from 'vue'
|
|
6
6
|
import 'floating-vue/style.css'
|
|
7
7
|
|
|
8
|
+
type OptionsSource = Option[] | ((query: string) => Promise<Option[]>)
|
|
9
|
+
const props = withDefaults(defineProps<PropTypes>(), {
|
|
10
|
+
placeholder: 'Select',
|
|
11
|
+
placement: 'bottom-start',
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
const emit = defineEmits(['update:modelValue'])
|
|
15
|
+
|
|
16
|
+
const isAsyncSource = (src: OptionsSource): src is (q: string) => Promise<Option[]> => typeof src === 'function'
|
|
17
|
+
|
|
18
|
+
type Primitive = string | number | boolean
|
|
19
|
+
|
|
8
20
|
interface PropTypes {
|
|
9
|
-
options:
|
|
21
|
+
options: OptionsSource
|
|
10
22
|
placeholder?: string
|
|
11
23
|
disabled?: boolean
|
|
12
24
|
modelValue?: Option
|
|
@@ -21,16 +33,8 @@ interface PropTypes {
|
|
|
21
33
|
clearable?: boolean
|
|
22
34
|
placement?: AlignedPlacement
|
|
23
35
|
searchPlaceholder?: string
|
|
24
|
-
onSearch?: (search: string) => Promise<Option[]>
|
|
25
36
|
}
|
|
26
37
|
|
|
27
|
-
const props = withDefaults(defineProps<PropTypes>(), {
|
|
28
|
-
placeholder: 'Select',
|
|
29
|
-
placement: 'bottom-start',
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
const emit = defineEmits(['update:modelValue']) // Add 'search' event
|
|
33
|
-
|
|
34
38
|
const searchInput = $ref<HTMLElement | undefined>()
|
|
35
39
|
|
|
36
40
|
let selectedItems = $ref<Option[]>([])
|
|
@@ -55,7 +59,12 @@ const selectedLabel = $computed((): string => {
|
|
|
55
59
|
})
|
|
56
60
|
const searchPlaceholder = $computed(() => props.searchPlaceholder ?? selectedLabel ?? 'Search')
|
|
57
61
|
|
|
58
|
-
const { results, isLoading } = useSearch<Option>({
|
|
62
|
+
const { results, isLoading } = useSearch<Option>({
|
|
63
|
+
searchTerm: () => searchTerm,
|
|
64
|
+
serverSearch: isAsyncSource(props.options) ? props.options : undefined,
|
|
65
|
+
items: () => (Array.isArray(props.options) ? props.options : []),
|
|
66
|
+
minChars: isAsyncSource(props.options) ? 0 : 2,
|
|
67
|
+
})
|
|
59
68
|
|
|
60
69
|
let highlightedIndex = $ref(-1)
|
|
61
70
|
|
|
@@ -81,20 +90,35 @@ function navigate(direction: 'up' | 'down') {
|
|
|
81
90
|
|
|
82
91
|
const isSelected = (option: Option) => selectedItems.find(item => getValue(option) === getValue(item)) !== undefined
|
|
83
92
|
|
|
93
|
+
function scrollToSelectedItem() {
|
|
94
|
+
if (!selectOptions || selectedItemCount === 0) return
|
|
95
|
+
|
|
96
|
+
// Find the first selected item in the results
|
|
97
|
+
const selectedIndex = results.value.findIndex(option => isSelected(option))
|
|
98
|
+
if (selectedIndex === -1) return
|
|
99
|
+
|
|
100
|
+
// Get the selected option element
|
|
101
|
+
const selectedElement = selectOptions.children[selectedIndex] as HTMLElement
|
|
102
|
+
if (!selectedElement) return
|
|
103
|
+
|
|
104
|
+
// Scroll the selected item into view
|
|
105
|
+
selectedElement.scrollIntoView({
|
|
106
|
+
block: 'center',
|
|
107
|
+
behavior: 'instant'
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
|
|
84
111
|
function getLabel(option: Option) {
|
|
85
|
-
if (
|
|
86
|
-
if (typeof option === '
|
|
87
|
-
if (typeof option === 'number') return `${option}`
|
|
112
|
+
if (option == null) return ''
|
|
113
|
+
if (typeof option === 'object') return option.label ?? String((option as any).value ?? '')
|
|
88
114
|
if (typeof option === 'boolean') return option ? 'Yes' : 'No'
|
|
89
|
-
return option
|
|
115
|
+
return String(option)
|
|
90
116
|
}
|
|
91
117
|
|
|
92
|
-
function getValue(option?: Option) {
|
|
93
|
-
if (
|
|
94
|
-
if (typeof option === '
|
|
95
|
-
|
|
96
|
-
if (typeof option === 'boolean') return option ? 'Yes' : 'No'
|
|
97
|
-
return option.value
|
|
118
|
+
function getValue(option?: Option): Primitive | undefined {
|
|
119
|
+
if (option == null) return
|
|
120
|
+
if (typeof option === 'object') return option.value as Primitive
|
|
121
|
+
return option as Primitive
|
|
98
122
|
}
|
|
99
123
|
|
|
100
124
|
function focusInput() {
|
|
@@ -109,14 +133,16 @@ function select(option: Option) {
|
|
|
109
133
|
const existingIndex = selectedItems.findIndex(
|
|
110
134
|
item => getValue(item) === getValue(option),
|
|
111
135
|
)
|
|
112
|
-
if (existingIndex > -1) {
|
|
136
|
+
if (existingIndex > -1) {
|
|
137
|
+
selectedItems.splice(existingIndex, 1)
|
|
113
138
|
}
|
|
114
139
|
else if (props.multiselect) {
|
|
115
140
|
const current = [...selectedItems]
|
|
116
141
|
current.push(option)
|
|
117
142
|
|
|
118
143
|
selectedItems = current
|
|
119
|
-
} else {
|
|
144
|
+
} else {
|
|
145
|
+
selectedItems.splice(0, selectedItemCount, option)
|
|
120
146
|
}
|
|
121
147
|
|
|
122
148
|
if (!props.multiselect) open = false
|
|
@@ -135,17 +161,18 @@ function emitUpdate() {
|
|
|
135
161
|
|
|
136
162
|
function compareArrays(arr1: Option[], arr2: Option[]) {
|
|
137
163
|
if (arr1.length !== arr2.length) return false
|
|
138
|
-
const
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
return isSame
|
|
164
|
+
const s1 = arr1.map(getValue).filter(Boolean).map(String).sort()
|
|
165
|
+
const s2 = arr2.map(getValue).filter(Boolean).map(String).sort()
|
|
166
|
+
return s1.every((v, i) => v === s2[i])
|
|
142
167
|
}
|
|
143
168
|
|
|
144
169
|
watch(
|
|
145
170
|
() => props.modelValue,
|
|
146
171
|
(newVal: Option | Option[]) => {
|
|
147
172
|
if (!props.multiselect) {
|
|
148
|
-
const newOption = props.options
|
|
173
|
+
const newOption = Array.isArray(props.options)
|
|
174
|
+
? (props.options.find(o => getValue(o) === newVal) ?? newVal)
|
|
175
|
+
: newVal
|
|
149
176
|
if (newOption && !isSelected(newOption)) selectedItems = [newOption]
|
|
150
177
|
} else {
|
|
151
178
|
const newData = [newVal].flat()
|
|
@@ -161,15 +188,19 @@ watch(
|
|
|
161
188
|
watch(() => open, (value) => {
|
|
162
189
|
if (value) {
|
|
163
190
|
highlightedIndex = -1
|
|
191
|
+
// Scroll to selected item when dropdown opens
|
|
192
|
+
setTimeout(() => { scrollToSelectedItem() }, 10)
|
|
164
193
|
}
|
|
165
194
|
})
|
|
166
195
|
|
|
167
196
|
watch(
|
|
168
197
|
() => props.options,
|
|
169
198
|
() => {
|
|
199
|
+
const opts = props.options
|
|
200
|
+
if (!Array.isArray(opts)) return
|
|
170
201
|
selectedItems.forEach((option, i) => {
|
|
171
|
-
const exists =
|
|
172
|
-
o => getValue(o) === getValue(option),
|
|
202
|
+
const exists = opts.find(
|
|
203
|
+
(o: Option) => getValue(o) === getValue(option),
|
|
173
204
|
)
|
|
174
205
|
if (exists === undefined) selectedItems.splice(i, 1)
|
|
175
206
|
else selectedItems.splice(i, 1, exists)
|
|
@@ -182,9 +213,10 @@ watch(
|
|
|
182
213
|
)
|
|
183
214
|
|
|
184
215
|
onMounted(() => {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
216
|
+
const opts = props.options
|
|
217
|
+
if (props.defaultValue !== undefined && Array.isArray(opts)) {
|
|
218
|
+
const defaultOption = opts.find(
|
|
219
|
+
(o: Option) => getValue(o) === getValue(props.defaultValue),
|
|
188
220
|
)
|
|
189
221
|
|
|
190
222
|
if (defaultOption === undefined) return
|
|
@@ -196,11 +228,7 @@ onMounted(() => {
|
|
|
196
228
|
|
|
197
229
|
<template>
|
|
198
230
|
<Dropdown
|
|
199
|
-
ref="dropdown"
|
|
200
|
-
v-model:shown="open"
|
|
201
|
-
:placement="placement"
|
|
202
|
-
class="bagel-input selectinput"
|
|
203
|
-
no-auto-focus
|
|
231
|
+
ref="dropdown" v-model:shown="open" :placement="placement" class="bagel-input selectinput" no-auto-focus
|
|
204
232
|
@click.stop
|
|
205
233
|
>
|
|
206
234
|
<template #trigger>
|
|
@@ -208,24 +236,13 @@ onMounted(() => {
|
|
|
208
236
|
{{ label }} <span v-if="required && label">*</span>
|
|
209
237
|
<div class="flex gap-05">
|
|
210
238
|
<TextInput
|
|
211
|
-
v-if="searchable && open"
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
class="mb-0"
|
|
215
|
-
:placeholder="searchPlaceholder"
|
|
216
|
-
icon="search"
|
|
217
|
-
@input="selected = false"
|
|
218
|
-
@click="focusInput"
|
|
219
|
-
@keydown.down.prevent="navigate('down')"
|
|
220
|
-
@keydown.up.prevent="navigate('up')"
|
|
239
|
+
v-if="searchable && open" ref="searchInput" v-model="searchTerm" class="mb-0"
|
|
240
|
+
:placeholder="searchPlaceholder" icon="search" @input="selected = false" @click="focusInput"
|
|
241
|
+
@keydown.down.prevent="navigate('down')" @keydown.up.prevent="navigate('up')"
|
|
221
242
|
/>
|
|
222
243
|
<button
|
|
223
|
-
v-else
|
|
224
|
-
:
|
|
225
|
-
type="button"
|
|
226
|
-
class="selectinput-btn"
|
|
227
|
-
:class="{ isEmpty: selectedItemCount === 0 }"
|
|
228
|
-
@keydown.down.prevent="navigate('down')"
|
|
244
|
+
v-else :disabled="disabled" type="button" class="selectinput-btn"
|
|
245
|
+
:class="{ isEmpty: selectedItemCount === 0 }" @keydown.down.prevent="navigate('down')"
|
|
229
246
|
@keydown.up.prevent="navigate('up')"
|
|
230
247
|
>
|
|
231
248
|
<Icon v-if="icon" :icon="icon" />
|
|
@@ -234,61 +251,31 @@ onMounted(() => {
|
|
|
234
251
|
</p>
|
|
235
252
|
|
|
236
253
|
<div v-if="clearable && selectedItemCount > 0" class="ms-auto ps-05 me-05">
|
|
237
|
-
<Btn
|
|
238
|
-
flat
|
|
239
|
-
thin
|
|
240
|
-
icon="clear"
|
|
241
|
-
class="color-gray"
|
|
242
|
-
@click="selectedItems = []; emitUpdate()"
|
|
243
|
-
/>
|
|
254
|
+
<Btn flat thin icon="clear" class="color-gray" @click="selectedItems = []; emitUpdate()" />
|
|
244
255
|
</div>
|
|
245
|
-
<Icon
|
|
246
|
-
v-if="!disabled"
|
|
247
|
-
thin
|
|
248
|
-
v-bind="{ icon: open ? 'unfold_less' : 'unfold_more' }"
|
|
249
|
-
/>
|
|
256
|
+
<Icon v-if="!disabled" thin v-bind="{ icon: open ? 'unfold_less' : 'unfold_more' }" />
|
|
250
257
|
</button>
|
|
251
258
|
<input
|
|
252
|
-
v-if="required"
|
|
253
|
-
|
|
254
|
-
style="width: 0; height: 0; position: absolute; opacity: 0; z-index: -1"
|
|
255
|
-
:value="selectedItems"
|
|
259
|
+
v-if="required" tabindex="-1"
|
|
260
|
+
style="width: 0; height: 0; position: absolute; opacity: 0; z-index: -1" :value="selectedItems"
|
|
256
261
|
required
|
|
257
262
|
>
|
|
258
263
|
</div>
|
|
259
264
|
</label>
|
|
260
265
|
</template>
|
|
261
266
|
<Skeleton v-if="isLoading" :count="6" width="200px" height="25px" class="mx-1 my-1" />
|
|
262
|
-
<Card
|
|
263
|
-
class="p-05"
|
|
264
|
-
:style="{ width: fullWidth ? '100%' : 'auto' }"
|
|
265
|
-
@click.stop
|
|
266
|
-
>
|
|
267
|
+
<Card class="p-05" :style="{ width: fullWidth ? '100%' : 'auto' }" @click.stop>
|
|
267
268
|
<div ref="selectOptions" class="selectinput-options" :class="{ multiselect }">
|
|
268
269
|
<div
|
|
269
|
-
v-for="(option, i) in results"
|
|
270
|
-
:
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
role="option"
|
|
274
|
-
tabindex="0"
|
|
275
|
-
:aria-selected="isSelected(option)"
|
|
276
|
-
@click="select(option)"
|
|
277
|
-
@keydown.enter="select(option)"
|
|
278
|
-
@keydown.down.prevent="navigate('down')"
|
|
270
|
+
v-for="(option, i) in results" :key="`${String(getValue(option))}-${i}`"
|
|
271
|
+
class="selectinput-option hover gap-1 align-items-center" :class="{ selected: isSelected(option) }"
|
|
272
|
+
role="option" tabindex="0" :aria-selected="isSelected(option)" @click="select(option)"
|
|
273
|
+
@keydown.enter="select(option)" @keydown.down.prevent="navigate('down')"
|
|
279
274
|
@keydown.up.prevent="navigate('up')"
|
|
280
275
|
>
|
|
281
276
|
<template v-if="multiselect">
|
|
282
|
-
<Icon
|
|
283
|
-
|
|
284
|
-
icon="select_check_box"
|
|
285
|
-
/>
|
|
286
|
-
<Icon
|
|
287
|
-
v-if="!isSelected(option)"
|
|
288
|
-
class="opacity-3"
|
|
289
|
-
icon="check_box_outline_blank"
|
|
290
|
-
:size="1.1"
|
|
291
|
-
/>
|
|
277
|
+
<Icon v-if="isSelected(option)" :size="1.1" icon="select_check_box" />
|
|
278
|
+
<Icon v-if="!isSelected(option)" class="opacity-3" icon="check_box_outline_blank" :size="1.1" />
|
|
292
279
|
</template>
|
|
293
280
|
<span class="block">
|
|
294
281
|
{{ getLabel(option) }}
|
|
@@ -311,15 +298,17 @@ onMounted(() => {
|
|
|
311
298
|
border-radius: 5px;
|
|
312
299
|
transition: all 0.2s;
|
|
313
300
|
display: grid;
|
|
314
|
-
grid-template-columns:1fr;
|
|
301
|
+
grid-template-columns: 1fr;
|
|
315
302
|
justify-content: space-between;
|
|
316
303
|
width: 100%;
|
|
317
304
|
font-size: var(--input-font-size);
|
|
318
305
|
margin-block: 0.15rem;
|
|
319
306
|
}
|
|
320
|
-
|
|
307
|
+
|
|
308
|
+
.selectinput-option .bgl_icon-font {
|
|
321
309
|
line-height: normal;
|
|
322
310
|
}
|
|
311
|
+
|
|
323
312
|
.selectinput-options.multiselect .selectinput-option {
|
|
324
313
|
grid-template-columns: 10px 1fr;
|
|
325
314
|
|
|
@@ -330,14 +319,17 @@ onMounted(() => {
|
|
|
330
319
|
overflow-y: auto;
|
|
331
320
|
}
|
|
332
321
|
|
|
333
|
-
.selectinput-option:hover,
|
|
322
|
+
.selectinput-option:hover,
|
|
323
|
+
.highlight {
|
|
334
324
|
background: var(--bgl-gray-tint);
|
|
335
325
|
}
|
|
326
|
+
|
|
336
327
|
.isEmpty p {
|
|
337
328
|
opacity: 0.3;
|
|
338
329
|
}
|
|
339
|
-
|
|
340
|
-
|
|
330
|
+
|
|
331
|
+
.selected {
|
|
332
|
+
color: var(--bgl-primary);
|
|
341
333
|
background: var(--bgl-selected);
|
|
342
334
|
}
|
|
343
335
|
</style>
|
|
@@ -361,7 +353,8 @@ onMounted(() => {
|
|
|
361
353
|
font-family: inherit;
|
|
362
354
|
font-size: var(--input-font-size);
|
|
363
355
|
}
|
|
364
|
-
|
|
356
|
+
|
|
357
|
+
.selectinput-btn p {
|
|
365
358
|
white-space: nowrap;
|
|
366
359
|
overflow: hidden;
|
|
367
360
|
text-overflow: ellipsis;
|