@bagelink/vue 1.15.112 → 1.15.114
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/form/inputs/DateInput.vue.d.ts +12 -4
- package/dist/components/form/inputs/DateInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/DatePicker.vue.d.ts +12 -1
- package/dist/components/form/inputs/DatePicker.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.map +1 -1
- package/dist/index.cjs +95 -95
- package/dist/index.mjs +10943 -10841
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/form/inputs/DateInput.vue +105 -18
- package/src/components/form/inputs/DatePicker.vue +124 -8
- package/src/components/form/inputs/SelectInput.vue +1 -1
- package/src/components/form/inputs/TextInput.vue +37 -11
- package/src/styles/dark.css +2 -4
package/package.json
CHANGED
|
@@ -5,6 +5,7 @@ import { onClickOutside } from '@vueuse/core'
|
|
|
5
5
|
import { ref, computed, onMounted, watch } from 'vue'
|
|
6
6
|
import { WEEK_START_DAY } from '../../../utils/calendar/time'
|
|
7
7
|
import DatePicker from './DatePicker.vue'
|
|
8
|
+
import type { DateRangeValue } from './DatePicker.vue'
|
|
8
9
|
import type { BagelInputShellProps } from './bagelInputShell'
|
|
9
10
|
import { useBagelInputShell } from './bagelInputShell'
|
|
10
11
|
|
|
@@ -19,7 +20,13 @@ export interface DateInputProps extends BagelInputShellProps {
|
|
|
19
20
|
* @default false
|
|
20
21
|
*/
|
|
21
22
|
enableTime?: boolean
|
|
22
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Select a start/end date range in a single input.
|
|
25
|
+
* When true, `modelValue` is a `{ start, end }` object.
|
|
26
|
+
* @default false
|
|
27
|
+
*/
|
|
28
|
+
range?: boolean
|
|
29
|
+
modelValue?: string | Date | DateRangeValue
|
|
23
30
|
min?: string | Date
|
|
24
31
|
max?: string | Date
|
|
25
32
|
mode?: ModeType
|
|
@@ -37,6 +44,7 @@ const props = withDefaults(
|
|
|
37
44
|
enableTime: false,
|
|
38
45
|
editMode: true,
|
|
39
46
|
small: false,
|
|
47
|
+
range: false,
|
|
40
48
|
mode: () => ({ mode: 'day' }),
|
|
41
49
|
firstDayOfWeek: WEEK_START_DAY.SUNDAY,
|
|
42
50
|
locale: ''
|
|
@@ -47,7 +55,9 @@ const { $t } = useI18n()
|
|
|
47
55
|
|
|
48
56
|
const { shellClass, shellStyle } = useBagelInputShell(props)
|
|
49
57
|
|
|
50
|
-
const selectedDate = defineModel<string | Date>()
|
|
58
|
+
const selectedDate = defineModel<string | Date | DateRangeValue>()
|
|
59
|
+
|
|
60
|
+
const RANGE_SEPARATOR = ' - '
|
|
51
61
|
const inputValue = ref('')
|
|
52
62
|
const isValid = ref(true)
|
|
53
63
|
const isTyping = ref(false)
|
|
@@ -58,11 +68,22 @@ const datePickerRef = ref<HTMLElement | null>(null)
|
|
|
58
68
|
const calendarRef = ref<HTMLElement | null>(null)
|
|
59
69
|
|
|
60
70
|
// Formatting utilities
|
|
61
|
-
function
|
|
71
|
+
function formatSingleDate(date: Date | string | undefined): string {
|
|
62
72
|
if (date === undefined || date === '') return ''
|
|
63
73
|
return formatDate(date, { format: props.enableTime ? 'DD.MM.YY HH:mm' : 'DD.MM.YY' })
|
|
64
74
|
}
|
|
65
75
|
|
|
76
|
+
function formatDisplayDate(value: Date | string | DateRangeValue | undefined): string {
|
|
77
|
+
if (props.range) {
|
|
78
|
+
const range = (value ?? {}) as DateRangeValue
|
|
79
|
+
const start = formatSingleDate(range.start)
|
|
80
|
+
const end = formatSingleDate(range.end)
|
|
81
|
+
if (!start && !end) return ''
|
|
82
|
+
return `${start}${RANGE_SEPARATOR}${end}`
|
|
83
|
+
}
|
|
84
|
+
return formatSingleDate(value as Date | string | undefined)
|
|
85
|
+
}
|
|
86
|
+
|
|
66
87
|
function parseUserInput(input: string): Date | null {
|
|
67
88
|
const date = new Date(input)
|
|
68
89
|
if (!Number.isNaN(date.getTime())) return date
|
|
@@ -88,25 +109,41 @@ function normalizeDate(date: Date): string {
|
|
|
88
109
|
return normalized.toISOString().split('T')[0]
|
|
89
110
|
}
|
|
90
111
|
|
|
112
|
+
function hasModelValue(): boolean {
|
|
113
|
+
if (props.range) {
|
|
114
|
+
const range = selectedDate.value as DateRangeValue | undefined
|
|
115
|
+
return !!(range && (range.start || range.end))
|
|
116
|
+
}
|
|
117
|
+
return selectedDate.value !== undefined
|
|
118
|
+
}
|
|
119
|
+
|
|
91
120
|
function syncInputValue() {
|
|
92
121
|
if (!isTyping.value) {
|
|
93
|
-
inputValue.value =
|
|
122
|
+
inputValue.value = hasModelValue() ? formatDisplayDate(selectedDate.value) : ''
|
|
94
123
|
}
|
|
95
124
|
}
|
|
96
125
|
|
|
126
|
+
// Format a single "DD.MM.YY" fragment as the user types (auto-insert dots).
|
|
127
|
+
function autoFormatDatePart(raw: string): string {
|
|
128
|
+
const cleaned = raw.replace(/[^\d.]/g, '')
|
|
129
|
+
const parts = cleaned.split('.')
|
|
130
|
+
let formatted = parts.slice(0, 3).join('.')
|
|
131
|
+
if (formatted.length === 2 && !formatted.includes('.')) formatted += '.'
|
|
132
|
+
else if (formatted.length === 5 && parts.length === 2) formatted += '.'
|
|
133
|
+
return formatted
|
|
134
|
+
}
|
|
135
|
+
|
|
97
136
|
// Event handlers
|
|
98
137
|
function handleInput(event: Event) {
|
|
99
138
|
const input = event.target as HTMLInputElement
|
|
100
139
|
isTyping.value = true
|
|
101
140
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (formatted.length === 2 && !formatted.includes('.')) formatted += '.'
|
|
108
|
-
else if (formatted.length === 5 && parts.length === 2) formatted += '.'
|
|
141
|
+
if (props.range) {
|
|
142
|
+
handleRangeInput(input)
|
|
143
|
+
return
|
|
144
|
+
}
|
|
109
145
|
|
|
146
|
+
const formatted = autoFormatDatePart(input.value)
|
|
110
147
|
inputValue.value = formatted
|
|
111
148
|
if (input.value !== formatted) input.value = formatted
|
|
112
149
|
|
|
@@ -123,6 +160,49 @@ function handleInput(event: Event) {
|
|
|
123
160
|
}
|
|
124
161
|
}
|
|
125
162
|
|
|
163
|
+
function handleRangeInput(input: HTMLInputElement) {
|
|
164
|
+
// Split on the separator (tolerate a bare "-" typed by the user)
|
|
165
|
+
const [rawStart = '', rawEnd = ''] = input.value.split(/\s*-\s*/, 2)
|
|
166
|
+
const startFmt = autoFormatDatePart(rawStart)
|
|
167
|
+
const endFmt = autoFormatDatePart(rawEnd)
|
|
168
|
+
|
|
169
|
+
// Reassemble display; only show separator once the user moved past the start
|
|
170
|
+
const typedSeparator = /-/.test(input.value)
|
|
171
|
+
const formatted = (endFmt || typedSeparator)
|
|
172
|
+
? `${startFmt}${RANGE_SEPARATOR}${endFmt}`
|
|
173
|
+
: startFmt
|
|
174
|
+
|
|
175
|
+
inputValue.value = formatted
|
|
176
|
+
if (input.value !== formatted) input.value = formatted
|
|
177
|
+
|
|
178
|
+
const startDate = rawStart ? parseUserInput(startFmt) : null
|
|
179
|
+
const endDate = rawEnd ? parseUserInput(endFmt) : null
|
|
180
|
+
|
|
181
|
+
// Invalid only if a non-empty part fails to parse
|
|
182
|
+
if ((rawStart && !startDate) || (rawEnd && !endDate)) {
|
|
183
|
+
isValid.value = false
|
|
184
|
+
return
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
isValid.value = true
|
|
188
|
+
if (!startDate && !endDate) {
|
|
189
|
+
selectedDate.value = undefined
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Order endpoints if both present
|
|
194
|
+
let lo = startDate
|
|
195
|
+
let hi = endDate
|
|
196
|
+
if (startDate && endDate && endDate < startDate) {
|
|
197
|
+
lo = endDate
|
|
198
|
+
hi = startDate
|
|
199
|
+
}
|
|
200
|
+
selectedDate.value = {
|
|
201
|
+
start: lo ? normalizeDate(lo) : undefined,
|
|
202
|
+
end: hi ? normalizeDate(hi) : undefined,
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
126
206
|
function handleFocus() {
|
|
127
207
|
if (!isClosing) isOpen.value = true
|
|
128
208
|
}
|
|
@@ -132,7 +212,7 @@ function handleBlur() {
|
|
|
132
212
|
if (inputValue.value.length === 0) {
|
|
133
213
|
selectedDate.value = undefined
|
|
134
214
|
isValid.value = true
|
|
135
|
-
} else if (
|
|
215
|
+
} else if (hasModelValue()) {
|
|
136
216
|
inputValue.value = formatDisplayDate(selectedDate.value)
|
|
137
217
|
isValid.value = true
|
|
138
218
|
} else {
|
|
@@ -153,15 +233,16 @@ function handleKeydown(event: KeyboardEvent) {
|
|
|
153
233
|
if (key === 'Escape') {
|
|
154
234
|
isOpen.value = false
|
|
155
235
|
isTyping.value = false
|
|
156
|
-
inputValue.value =
|
|
236
|
+
inputValue.value = hasModelValue() ? formatDisplayDate(selectedDate.value) : ''
|
|
157
237
|
isValid.value = true
|
|
158
238
|
} else if (key === 'Enter') {
|
|
159
239
|
isTyping.value = false
|
|
160
|
-
if (
|
|
240
|
+
if (hasModelValue()) inputValue.value = formatDisplayDate(selectedDate.value)
|
|
161
241
|
isOpen.value = false
|
|
162
|
-
} else if (key === 'ArrowUp' || key === 'ArrowDown') {
|
|
242
|
+
} else if (!props.range && (key === 'ArrowUp' || key === 'ArrowDown')) {
|
|
243
|
+
// Arrow-key increment only applies to single-date mode
|
|
163
244
|
event.preventDefault()
|
|
164
|
-
const date = selectedDate.value !== undefined ? new Date(selectedDate.value) : new Date()
|
|
245
|
+
const date = selectedDate.value !== undefined ? new Date(selectedDate.value as string | Date) : new Date()
|
|
165
246
|
const increment = key === 'ArrowUp' ? 1 : -1
|
|
166
247
|
|
|
167
248
|
if (shiftKey) date.setMonth(date.getMonth() + increment)
|
|
@@ -181,6 +262,11 @@ watch(() => selectedDate.value, syncInputValue, { immediate: true })
|
|
|
181
262
|
|
|
182
263
|
const hasValue = computed(() => inputValue.value.length > 0)
|
|
183
264
|
|
|
265
|
+
const defaultPlaceholder = computed(() => {
|
|
266
|
+
const single = props.enableTime ? 'DD.MM.YY HH:mm' : 'DD.MM.YY'
|
|
267
|
+
return props.range ? `${single}${RANGE_SEPARATOR}${single}` : single
|
|
268
|
+
})
|
|
269
|
+
|
|
184
270
|
onClickOutside(datePickerRef, () => {
|
|
185
271
|
isClosing = true
|
|
186
272
|
isOpen.value = false
|
|
@@ -188,7 +274,7 @@ onClickOutside(datePickerRef, () => {
|
|
|
188
274
|
}, { ignore: [calendarRef] })
|
|
189
275
|
|
|
190
276
|
onMounted(() => {
|
|
191
|
-
if (selectedDate.value === undefined && props.defaultValue !== undefined) {
|
|
277
|
+
if (!props.range && selectedDate.value === undefined && props.defaultValue !== undefined) {
|
|
192
278
|
selectedDate.value = props.defaultValue
|
|
193
279
|
}
|
|
194
280
|
})
|
|
@@ -208,7 +294,7 @@ onMounted(() => {
|
|
|
208
294
|
<div ref="datePickerRef" class="date-picker-container">
|
|
209
295
|
<TextInput
|
|
210
296
|
v-model="inputValue" iconStart="calendar"
|
|
211
|
-
:placeholder="underlined ? ' ' : (resolveI18n(placeholder) ||
|
|
297
|
+
:placeholder="underlined ? ' ' : (resolveI18n(placeholder) || defaultPlaceholder)"
|
|
212
298
|
:required="underlined ? false : required" :disabled="!editMode"
|
|
213
299
|
:error="!isValid ? $t('date.invalidFormat') : error" :class="{ 'txt-center': center }"
|
|
214
300
|
:underlined="underlined" :frame="frame" :outline="outline" :min-width="minWidth"
|
|
@@ -223,6 +309,7 @@ onMounted(() => {
|
|
|
223
309
|
<DatePicker
|
|
224
310
|
v-model="selectedDate" :min="min" :max="max" :mode="mode"
|
|
225
311
|
:firstDayOfWeek="firstDayOfWeek" :locale="locale" :enableTime="enableTime"
|
|
312
|
+
:range="range"
|
|
226
313
|
/>
|
|
227
314
|
</div>
|
|
228
315
|
</Dropdown>
|
|
@@ -5,15 +5,26 @@ import { Btn, NumberInput } from '@bagelink/vue'
|
|
|
5
5
|
import { computed, ref, toValue } from 'vue'
|
|
6
6
|
import Time, { WEEK_START_DAY } from '../../../utils/calendar/time'
|
|
7
7
|
|
|
8
|
+
export interface DateRangeValue {
|
|
9
|
+
start?: string
|
|
10
|
+
end?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
8
13
|
const props = withDefaults(
|
|
9
14
|
defineProps<{
|
|
10
|
-
modelValue?: string | Date
|
|
15
|
+
modelValue?: string | Date | DateRangeValue
|
|
11
16
|
min?: string | Date
|
|
12
17
|
max?: string | Date
|
|
13
18
|
mode?: ModeType
|
|
14
19
|
firstDayOfWeek?: WEEK_START_DAY
|
|
15
20
|
locale?: string
|
|
16
21
|
enableTime?: boolean
|
|
22
|
+
/**
|
|
23
|
+
* Whether to select a start/end date range instead of a single date.
|
|
24
|
+
* When true, `modelValue` is a `{ start, end }` object.
|
|
25
|
+
* @default false
|
|
26
|
+
*/
|
|
27
|
+
range?: boolean
|
|
17
28
|
highlightedDates?: MaybeRefOrGetter<(string | Date)[]>
|
|
18
29
|
disabledDates?: MaybeRefOrGetter<(string | Date)[]>
|
|
19
30
|
allowedDates?: MaybeRefOrGetter<(string | Date)[]>
|
|
@@ -24,11 +35,15 @@ const props = withDefaults(
|
|
|
24
35
|
firstDayOfWeek: WEEK_START_DAY.SUNDAY,
|
|
25
36
|
locale: '',
|
|
26
37
|
enableTime: false,
|
|
38
|
+
range: false,
|
|
27
39
|
},
|
|
28
40
|
)
|
|
29
41
|
|
|
30
42
|
const emit = defineEmits(['update:modelValue'])
|
|
31
43
|
|
|
44
|
+
// Tentative end date hovered during range selection
|
|
45
|
+
const hoverDate = ref<Date | null>(null)
|
|
46
|
+
|
|
32
47
|
const computedHighlightedDates = computed(() => toValue(props.highlightedDates))
|
|
33
48
|
const computedDisabledDates = computed(() => toValue(props.disabledDates))
|
|
34
49
|
const computedAllowedDates = computed(() => toValue(props.allowedDates))
|
|
@@ -128,17 +143,36 @@ function useDateValidation() {
|
|
|
128
143
|
// Date state composable
|
|
129
144
|
function useDateState() {
|
|
130
145
|
const selectedDate = computed(() => {
|
|
131
|
-
return parseDate(props.modelValue)
|
|
146
|
+
return parseDate(props.range ? undefined : (props.modelValue as string | Date | undefined))
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
const rangeStart = computed(() => {
|
|
150
|
+
if (!props.range) { return null }
|
|
151
|
+
return parseDate((props.modelValue as DateRangeValue | undefined)?.start)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
const rangeEnd = computed(() => {
|
|
155
|
+
if (!props.range) { return null }
|
|
156
|
+
return parseDate((props.modelValue as DateRangeValue | undefined)?.end)
|
|
132
157
|
})
|
|
133
158
|
|
|
134
159
|
return {
|
|
135
|
-
selectedDate
|
|
160
|
+
selectedDate,
|
|
161
|
+
rangeStart,
|
|
162
|
+
rangeEnd
|
|
136
163
|
}
|
|
137
164
|
}
|
|
138
165
|
|
|
139
166
|
// Initialize core composables
|
|
140
167
|
const { isDateDisabled, isMonthDisabled, isYearDisabled } = useDateValidation()
|
|
141
|
-
const { selectedDate } = useDateState()
|
|
168
|
+
const { selectedDate, rangeStart, rangeEnd } = useDateState()
|
|
169
|
+
|
|
170
|
+
function isSameDay(a: Date | null, b: Date | null): boolean {
|
|
171
|
+
if (!a || !b) { return false }
|
|
172
|
+
return a.getFullYear() === b.getFullYear()
|
|
173
|
+
&& a.getMonth() === b.getMonth()
|
|
174
|
+
&& a.getDate() === b.getDate()
|
|
175
|
+
}
|
|
142
176
|
|
|
143
177
|
// Calendar view composable
|
|
144
178
|
function useCalendarView() {
|
|
@@ -200,10 +234,35 @@ function useCalendarView() {
|
|
|
200
234
|
})
|
|
201
235
|
|
|
202
236
|
const isSelected = (date: Date | null) => {
|
|
203
|
-
if (!date
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
237
|
+
if (!date) { return false }
|
|
238
|
+
if (props.range) {
|
|
239
|
+
return isSameDay(date, rangeStart.value) || isSameDay(date, rangeEnd.value)
|
|
240
|
+
}
|
|
241
|
+
if (!selectedDate.value) { return false }
|
|
242
|
+
return isSameDay(date, selectedDate.value)
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Effective end of the range for display: committed end, or the hovered date
|
|
246
|
+
// while the user is picking the second endpoint.
|
|
247
|
+
const previewEnd = computed(() => {
|
|
248
|
+
if (rangeEnd.value) { return rangeEnd.value }
|
|
249
|
+
if (rangeStart.value && hoverDate.value) { return hoverDate.value }
|
|
250
|
+
return null
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
const isRangeStart = (date: Date | null) => props.range && isSameDay(date, rangeStart.value)
|
|
254
|
+
const isRangeEnd = (date: Date | null) => props.range && isSameDay(date, rangeEnd.value)
|
|
255
|
+
|
|
256
|
+
const isInRange = (date: Date | null) => {
|
|
257
|
+
if (!props.range || !date || !rangeStart.value) { return false }
|
|
258
|
+
const end = previewEnd.value
|
|
259
|
+
if (!end) { return false }
|
|
260
|
+
const lo = rangeStart.value < end ? rangeStart.value : end
|
|
261
|
+
const hi = rangeStart.value < end ? end : rangeStart.value
|
|
262
|
+
const d = new Date(date.getFullYear(), date.getMonth(), date.getDate())
|
|
263
|
+
const loD = new Date(lo.getFullYear(), lo.getMonth(), lo.getDate())
|
|
264
|
+
const hiD = new Date(hi.getFullYear(), hi.getMonth(), hi.getDate())
|
|
265
|
+
return d > loD && d < hiD
|
|
207
266
|
}
|
|
208
267
|
|
|
209
268
|
const isToday = (date: Date | null) => {
|
|
@@ -243,6 +302,9 @@ function useCalendarView() {
|
|
|
243
302
|
years,
|
|
244
303
|
weekDays,
|
|
245
304
|
isSelected,
|
|
305
|
+
isRangeStart,
|
|
306
|
+
isRangeEnd,
|
|
307
|
+
isInRange,
|
|
246
308
|
isToday,
|
|
247
309
|
isNotInMonth,
|
|
248
310
|
isHighlighted
|
|
@@ -336,6 +398,9 @@ const {
|
|
|
336
398
|
years,
|
|
337
399
|
weekDays,
|
|
338
400
|
isSelected,
|
|
401
|
+
isRangeStart,
|
|
402
|
+
isRangeEnd,
|
|
403
|
+
isInRange,
|
|
339
404
|
isToday,
|
|
340
405
|
isNotInMonth,
|
|
341
406
|
isHighlighted
|
|
@@ -343,10 +408,42 @@ const {
|
|
|
343
408
|
const { selectMonth, selectYear, previousMonth, nextMonth, previousYear, nextYear } = useNavigation()
|
|
344
409
|
const { hours, minutes, handleHourInput, handleMinuteInput } = useTimeHandling()
|
|
345
410
|
|
|
411
|
+
function toDayString(date: Date): string {
|
|
412
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate()).toISOString()
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Range selection handler: first click sets start (clears end), second click
|
|
416
|
+
// sets end (ordering start/end automatically). A third click starts over.
|
|
417
|
+
function selectRange(date: Date) {
|
|
418
|
+
const start = rangeStart.value
|
|
419
|
+
const end = rangeEnd.value
|
|
420
|
+
|
|
421
|
+
if (!start || end) {
|
|
422
|
+
// Start a new range
|
|
423
|
+
emit('update:modelValue', { start: toDayString(date), end: undefined })
|
|
424
|
+
return
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Completing the range — order the two endpoints
|
|
428
|
+
const lo = date < start ? date : start
|
|
429
|
+
const hi = date < start ? start : date
|
|
430
|
+
emit('update:modelValue', { start: toDayString(lo), end: toDayString(hi) })
|
|
431
|
+
hoverDate.value = null
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function onDayHover(date: Date | null) {
|
|
435
|
+
if (props.range && date) { hoverDate.value = date }
|
|
436
|
+
}
|
|
437
|
+
|
|
346
438
|
// Date selection handler
|
|
347
439
|
function selectDate(date: Date | null) {
|
|
348
440
|
if (!date) { return }
|
|
349
441
|
|
|
442
|
+
if (props.range) {
|
|
443
|
+
selectRange(date)
|
|
444
|
+
return
|
|
445
|
+
}
|
|
446
|
+
|
|
350
447
|
if (props.enableTime) {
|
|
351
448
|
// Create date with current time values
|
|
352
449
|
const newDate = new Date(date.getFullYear(), date.getMonth(), date.getDate())
|
|
@@ -400,11 +497,15 @@ function selectDate(date: Date | null) {
|
|
|
400
497
|
class="day aspect-ratio-1 flex align-items-center justify-content-center pointer round txt14 p-0"
|
|
401
498
|
:class="{
|
|
402
499
|
'selected': isSelected(date),
|
|
500
|
+
'range-start': isRangeStart(date),
|
|
501
|
+
'range-end': isRangeEnd(date),
|
|
502
|
+
'in-range': isInRange(date),
|
|
403
503
|
'today': isToday(date),
|
|
404
504
|
'disabled': isDateDisabled(date),
|
|
405
505
|
'not-in-month': isNotInMonth(date),
|
|
406
506
|
'highlighted': isHighlighted(date),
|
|
407
507
|
}" :disabled="isDateDisabled(date)" @click="selectDate(date)"
|
|
508
|
+
@mouseenter="onDayHover(date)"
|
|
408
509
|
>
|
|
409
510
|
{{ date?.getDate() }}
|
|
410
511
|
</button>
|
|
@@ -529,6 +630,21 @@ color: var(--bgl-white);
|
|
|
529
630
|
border: 1px solid var(--bgl-primary);
|
|
530
631
|
}
|
|
531
632
|
|
|
633
|
+
.day.in-range:not(.selected) {
|
|
634
|
+
background-color: var(--bgl-secondary-light, rgba(var(--bgl-primary-rgb, 0, 123, 255), 0.15));
|
|
635
|
+
border-radius: 0;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
.day.range-start:not(.range-end) {
|
|
639
|
+
border-top-right-radius: 0;
|
|
640
|
+
border-bottom-right-radius: 0;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
.day.range-end:not(.range-start) {
|
|
644
|
+
border-top-left-radius: 0;
|
|
645
|
+
border-bottom-left-radius: 0;
|
|
646
|
+
}
|
|
647
|
+
|
|
532
648
|
.day.disabled {
|
|
533
649
|
opacity: 0.6;
|
|
534
650
|
filter: grayscale(0.3);
|
|
@@ -303,7 +303,7 @@ onMounted(() => {
|
|
|
303
303
|
<div v-if="clearable && selectedItemCount > 0" class="ms-auto ps-05 me-05">
|
|
304
304
|
<Btn flat thin icon="clear" class="color-gray" @click="selectedItems = []; emitUpdate()" />
|
|
305
305
|
</div>
|
|
306
|
-
<Icon v-if="!disabled" thin transition="
|
|
306
|
+
<Icon v-if="!disabled" thin transition="scale" :icon="open ? (underlined ? 'expand_less' : 'unfold_less') : (underlined ? 'expand_more' : 'unfold_more')" />
|
|
307
307
|
</button>
|
|
308
308
|
<input
|
|
309
309
|
v-if="required && !underlined" tabindex="-1"
|
|
@@ -244,29 +244,55 @@ gap: 0.5rem;
|
|
|
244
244
|
background: var(--input-disabled-bg);
|
|
245
245
|
}
|
|
246
246
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
247
|
+
/* Icon centering.
|
|
248
|
+
The icon is absolutely positioned inside the <label>, whose only positioned ancestor
|
|
249
|
+
is `.bagel-input`. In the DEFAULT variant the label text is in normal flow and pushes
|
|
250
|
+
the input DOWN, but the old `top: input-height/2` was measured from the component top —
|
|
251
|
+
so with a label the icon rode up onto the label text (the misalignment).
|
|
252
|
+
|
|
253
|
+
Fix: make the <label> the containing block and anchor the icon into a band the height
|
|
254
|
+
of the input row pinned to the BOTTOM of the label (the input is the last row), then
|
|
255
|
+
flex-center it. Bottom-anchored + fixed band height ⇒ centered on the input regardless
|
|
256
|
+
of a label above, and in dense/small modes. Scoped to :not(.underlined) because the
|
|
257
|
+
underlined variant floats its label as an overlay (input isn't pushed down). */
|
|
258
|
+
.bagel-input:not(.underlined).textInputIconWrap > label,
|
|
259
|
+
.bagel-input:not(.underlined).txtInputIconStart > label {
|
|
260
|
+
position: relative;
|
|
261
|
+
display: block;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.bagel-input:not(.underlined).textInputIconWrap .bgl_icon-font,
|
|
265
|
+
.bagel-input:not(.underlined).txtInputIconStart .iconStart {
|
|
266
|
+
top: auto;
|
|
267
|
+
bottom: calc(var(--bgl-input-height) / 2);
|
|
268
|
+
transform: translateY(50%);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/* underlined: label floats as an overlay, so the original top-center is correct */
|
|
272
|
+
.underlined.textInputIconWrap .bgl_icon-font,
|
|
273
|
+
.underlined.txtInputIconStart .iconStart {
|
|
252
274
|
top: calc(var(--bgl-input-height) / 2);
|
|
253
275
|
transform: translateY(-50%);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.textInputIconWrap .bgl_icon-font,
|
|
279
|
+
.txtInputIconStart .iconStart {
|
|
280
|
+
color: var(--bgl-input-color);
|
|
281
|
+
position: absolute;
|
|
254
282
|
line-height: 0;
|
|
255
283
|
}
|
|
256
284
|
|
|
285
|
+
.textInputIconWrap .bgl_icon-font {
|
|
286
|
+
inset-inline-end: calc(var(--bgl-input-height) / 3 - 0.25rem);
|
|
287
|
+
}
|
|
288
|
+
|
|
257
289
|
.textInputIconWrap input,
|
|
258
290
|
.textInputIconWrap textarea {
|
|
259
291
|
padding-inline-end: calc(var(--bgl-input-height) / 3 + 1.5rem);
|
|
260
292
|
}
|
|
261
293
|
|
|
262
294
|
.txtInputIconStart .iconStart {
|
|
263
|
-
color: var(--bgl-input-color);
|
|
264
|
-
position: absolute;
|
|
265
295
|
inset-inline-start: calc(var(--bgl-input-height) / 3 - 0.25rem);
|
|
266
|
-
/* center on the input row regardless of input height (dense/small/default) */
|
|
267
|
-
top: calc(var(--bgl-input-height) / 2);
|
|
268
|
-
transform: translateY(-50%);
|
|
269
|
-
line-height: 0;
|
|
270
296
|
}
|
|
271
297
|
|
|
272
298
|
.txtInputIconStart input,
|
package/src/styles/dark.css
CHANGED
|
@@ -25,10 +25,8 @@
|
|
|
25
25
|
#00000020 (translucent dark). Blends over any dark surface (bg, card,
|
|
26
26
|
input) instead of being a fixed opaque gray. */
|
|
27
27
|
|
|
28
|
-
/* ---- Brand (
|
|
29
|
-
--bgl-primary:
|
|
30
|
-
--bgl-primary-tint: #4f7cff33;
|
|
31
|
-
--bgl-primary-light: #1a2236;
|
|
28
|
+
/* ---- Brand (derived from the project's --bgl-primary, not hardcoded) --- */
|
|
29
|
+
--bgl-primary-tint: color-mix(in srgb, var(--bgl-primary) 20%, transparent);
|
|
32
30
|
|
|
33
31
|
/* ---- Status / accent colors (tuned for dark backgrounds) ------------- *
|
|
34
32
|
* The light-mode bases (e.g. blue #2e5bff, green #75c98f, red #ed6c6f,
|