@bagelink/vue 1.15.309 → 1.15.321
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/AccordionItem.vue.d.ts.map +1 -1
- package/dist/components/Dropdown.vue.d.ts.map +1 -1
- package/dist/components/FilterQuery.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/SelectBtn.vue.d.ts +2 -0
- package/dist/components/form/inputs/SelectBtn.vue.d.ts.map +1 -1
- package/dist/components/layout/AppContent.vue.d.ts.map +1 -1
- package/dist/index.cjs +28 -28
- package/dist/index.mjs +5845 -5814
- package/dist/style.css +1 -1
- package/package.json +1 -1
- package/src/components/AccordionItem.vue +5 -1
- package/src/components/Dropdown.vue +20 -3
- package/src/components/FilterQuery.vue +22 -20
- package/src/components/form/inputs/SelectBtn.vue +55 -5
- package/src/components/layout/AppContent.vue +1 -1
- package/src/styles/buttons.css +16 -0
package/package.json
CHANGED
|
@@ -15,6 +15,7 @@ const props = defineProps<{
|
|
|
15
15
|
const emit = defineEmits(['update:open'])
|
|
16
16
|
|
|
17
17
|
const openVal = ref(!!props.open)
|
|
18
|
+
const hasOpened = ref(openVal.value)
|
|
18
19
|
|
|
19
20
|
const isOpen = computed({
|
|
20
21
|
get: () => openVal.value,
|
|
@@ -39,6 +40,9 @@ const computedIcon = computed(() => {
|
|
|
39
40
|
const iconPosition = computed(() => props.iconPosition || 'end')
|
|
40
41
|
|
|
41
42
|
watch(() => props.open, () => isOpen.value = props.open, { immediate: true })
|
|
43
|
+
watch(isOpen, (open) => {
|
|
44
|
+
if (open) hasOpened.value = true
|
|
45
|
+
}, { immediate: true })
|
|
42
46
|
|
|
43
47
|
if (accordionState) {
|
|
44
48
|
watch(
|
|
@@ -94,7 +98,7 @@ function toggle() {
|
|
|
94
98
|
:class="{ open: isOpen }" :aria-hidden="isOpen ? 'false' : 'true'"
|
|
95
99
|
>
|
|
96
100
|
<div class="accordion-body-inner">
|
|
97
|
-
<slot />
|
|
101
|
+
<slot v-if="hasOpened" />
|
|
98
102
|
</div>
|
|
99
103
|
</div>
|
|
100
104
|
</div>
|
|
@@ -61,6 +61,7 @@ const props = withDefaults(defineProps<{
|
|
|
61
61
|
const emit = defineEmits<{ show: []; hide: [] }>()
|
|
62
62
|
|
|
63
63
|
const shown = defineModel('shown', { type: Boolean, default: false })
|
|
64
|
+
const hasOpened = ref(shown.value)
|
|
64
65
|
|
|
65
66
|
const triggerWrapRef = ref<HTMLElement | null>(null)
|
|
66
67
|
const popoverRef = ref<HTMLElement | null>(null)
|
|
@@ -141,6 +142,7 @@ function isOpen(): boolean {
|
|
|
141
142
|
async function internalShow() {
|
|
142
143
|
if (props.disabled || isOpen()) return
|
|
143
144
|
|
|
145
|
+
hasOpened.value = true
|
|
144
146
|
teleportTarget.value = triggerWrapRef.value?.closest('dialog[open]') ?? 'body'
|
|
145
147
|
|
|
146
148
|
if (props.showGroup && groupRegistry.has(props.showGroup)) {
|
|
@@ -255,10 +257,12 @@ watch(shown, (val) => {
|
|
|
255
257
|
function onPopoverToggle(e: Event) {
|
|
256
258
|
const te = e as ToggleEvent
|
|
257
259
|
if (te.newState === 'open') {
|
|
260
|
+
addOpenListeners()
|
|
258
261
|
shown.value = true
|
|
259
262
|
nextTick(updatePosition)
|
|
260
263
|
emit('show')
|
|
261
264
|
} else {
|
|
265
|
+
removeOpenListeners()
|
|
262
266
|
shown.value = false
|
|
263
267
|
emit('hide')
|
|
264
268
|
}
|
|
@@ -360,9 +364,22 @@ function onScroll() {
|
|
|
360
364
|
if (isOpen()) updatePosition()
|
|
361
365
|
}
|
|
362
366
|
|
|
363
|
-
|
|
367
|
+
let openListenersActive = false
|
|
368
|
+
function addOpenListeners() {
|
|
369
|
+
if (openListenersActive) return
|
|
364
370
|
document.addEventListener('pointerdown', onDocumentPointerDown, true)
|
|
365
371
|
document.addEventListener('scroll', onScroll, { capture: true, passive: true })
|
|
372
|
+
openListenersActive = true
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function removeOpenListeners() {
|
|
376
|
+
if (!openListenersActive) return
|
|
377
|
+
document.removeEventListener('pointerdown', onDocumentPointerDown, true)
|
|
378
|
+
document.removeEventListener('scroll', onScroll, true)
|
|
379
|
+
openListenersActive = false
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
onMounted(() => {
|
|
366
383
|
if (props.showGroup) {
|
|
367
384
|
if (!groupRegistry.has(props.showGroup)) groupRegistry.set(props.showGroup, new Set())
|
|
368
385
|
groupRegistry.get(props.showGroup)!.add(internalHide)
|
|
@@ -372,8 +389,7 @@ onMounted(() => {
|
|
|
372
389
|
})
|
|
373
390
|
|
|
374
391
|
onBeforeUnmount(() => {
|
|
375
|
-
|
|
376
|
-
document.removeEventListener('scroll', onScroll, true)
|
|
392
|
+
removeOpenListeners()
|
|
377
393
|
clearTimers()
|
|
378
394
|
if (props.showGroup) groupRegistry.get(props.showGroup)?.delete(internalHide)
|
|
379
395
|
if (isOpen()) popoverRef.value?.hidePopover()
|
|
@@ -405,6 +421,7 @@ defineExpose({ show, hide, shown })
|
|
|
405
421
|
<!-- Popup — teleported to closest dialog[open] or body, promoted to top layer via popover -->
|
|
406
422
|
<Teleport :to="teleportTarget">
|
|
407
423
|
<div
|
|
424
|
+
v-if="hasOpened"
|
|
408
425
|
:id="bglId" ref="popoverRef" popover="manual" class="bgl-dropdown m-0 p-0 border-none"
|
|
409
426
|
role="menu"
|
|
410
427
|
style="border-radius:var(--bgl-card-border-radius);"
|
|
@@ -14,7 +14,7 @@ interface UICondition {
|
|
|
14
14
|
id: string
|
|
15
15
|
field: string
|
|
16
16
|
operator: ComparisonOperator
|
|
17
|
-
value: string | string
|
|
17
|
+
value: string | number | boolean | null | Array<string | number | boolean | null>
|
|
18
18
|
connector: LogicalOperator | ''
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -145,8 +145,8 @@ const conditions = computed(() => {
|
|
|
145
145
|
field: cond.field as string,
|
|
146
146
|
operator: cond.op,
|
|
147
147
|
value: Array.isArray(cond.value)
|
|
148
|
-
? cond.value
|
|
149
|
-
: (cond.value ===
|
|
148
|
+
? [...cond.value]
|
|
149
|
+
: (cond.value === undefined ? '' : cond.value),
|
|
150
150
|
connector: index > 0 ? (cond.connector || 'and') : '',
|
|
151
151
|
}
|
|
152
152
|
return uiCond
|
|
@@ -271,20 +271,21 @@ function collapseToPresets(conditions: QueryConditions<T>): QueryConditions<T> {
|
|
|
271
271
|
}
|
|
272
272
|
|
|
273
273
|
const booleanOptions = computed(() => [
|
|
274
|
-
{ label: currentTexts.value.boolean.true, value:
|
|
275
|
-
{ label: currentTexts.value.boolean.false, value:
|
|
274
|
+
{ label: currentTexts.value.boolean.true, value: true },
|
|
275
|
+
{ label: currentTexts.value.boolean.false, value: false },
|
|
276
276
|
])
|
|
277
277
|
|
|
278
|
-
function parseValue(value: string | number | boolean | null): string | number | boolean | null {
|
|
278
|
+
function parseValue(value: string | number | boolean | null, fieldType: string): string | number | boolean | null {
|
|
279
279
|
if (typeof value === 'number' || typeof value === 'boolean' || value === null) return value
|
|
280
|
-
if (
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
280
|
+
if (fieldType === 'boolean') {
|
|
281
|
+
if (value === 'true') return true
|
|
282
|
+
if (value === 'false') return false
|
|
283
|
+
}
|
|
284
|
+
if (fieldType === 'number' && !Number.isNaN(Number(value)) && value.trim() !== '') return Number(value)
|
|
284
285
|
return value
|
|
285
286
|
}
|
|
286
287
|
|
|
287
|
-
function updateConditionAtIndex(index: number, updates: Partial<{ field: string, op: ComparisonOperator, value:
|
|
288
|
+
function updateConditionAtIndex(index: number, updates: Partial<{ field: string, op: ComparisonOperator, value: UICondition['value'], connector: LogicalOperator }>) {
|
|
288
289
|
const newModel = [...internalModel.value]
|
|
289
290
|
const existing = newModel[index]
|
|
290
291
|
if (!existing) return
|
|
@@ -293,11 +294,12 @@ function updateConditionAtIndex(index: number, updates: Partial<{ field: string,
|
|
|
293
294
|
if (updates.field !== undefined) updated.field = updates.field
|
|
294
295
|
if (updates.op !== undefined) updated.op = updates.op
|
|
295
296
|
if (updates.value !== undefined) {
|
|
297
|
+
const fieldType = getFieldType(updated.field as string)
|
|
296
298
|
updated.value = Array.isArray(updates.value)
|
|
297
299
|
// Coerce each list/range element (e.g. "18" → 18) so numeric filters
|
|
298
300
|
// emit bare numbers in the bracket literal.
|
|
299
|
-
? updates.value.map(v => parseValue(
|
|
300
|
-
: parseValue(updates.value)
|
|
301
|
+
? updates.value.map(v => parseValue(v, fieldType))
|
|
302
|
+
: parseValue(updates.value, fieldType)
|
|
301
303
|
}
|
|
302
304
|
if (updates.connector !== undefined) updated.connector = updates.connector
|
|
303
305
|
|
|
@@ -361,8 +363,8 @@ function isFreeListOperator(condition: UICondition): boolean {
|
|
|
361
363
|
}
|
|
362
364
|
|
|
363
365
|
/** Convert an array value to comma-separated text for the free-list input. */
|
|
364
|
-
function listToText(value:
|
|
365
|
-
return Array.isArray(value) ? value.join(', ') : (value ?? '')
|
|
366
|
+
function listToText(value: UICondition['value']): string {
|
|
367
|
+
return Array.isArray(value) ? value.join(', ') : String(value ?? '')
|
|
366
368
|
}
|
|
367
369
|
|
|
368
370
|
/** Split comma-separated free-list text into a trimmed string[]. */
|
|
@@ -372,7 +374,7 @@ function textToList(text: string): string[] {
|
|
|
372
374
|
|
|
373
375
|
/** Read one bound of a `between` value ([lo, hi]); index 0 = lo, 1 = hi. */
|
|
374
376
|
function betweenBound(condition: UICondition, index: 0 | 1): string {
|
|
375
|
-
return Array.isArray(condition.value) ? (condition.value[index] ?? '') : ''
|
|
377
|
+
return Array.isArray(condition.value) ? String(condition.value[index] ?? '') : ''
|
|
376
378
|
}
|
|
377
379
|
|
|
378
380
|
/** Update one bound of a `between` value, preserving the other. */
|
|
@@ -384,7 +386,7 @@ function onBetweenBoundChange(id: string, index: 0 | 1, v: string) {
|
|
|
384
386
|
}
|
|
385
387
|
|
|
386
388
|
const activeFiltersCount = computed(() => {
|
|
387
|
-
return conditions.value.filter(
|
|
389
|
+
return conditions.value.filter(c => isComplete({ field: c.field, op: c.operator, value: c.value })).length
|
|
388
390
|
})
|
|
389
391
|
|
|
390
392
|
function getConditionIndex(id: string): number {
|
|
@@ -429,7 +431,7 @@ function onOperatorChange(id: string, operator: ComparisonOperator) {
|
|
|
429
431
|
updateConditionAtIndex(index, updates)
|
|
430
432
|
}
|
|
431
433
|
|
|
432
|
-
function onValueChange(id: string, value:
|
|
434
|
+
function onValueChange(id: string, value: UICondition['value']) {
|
|
433
435
|
const index = getConditionIndex(id)
|
|
434
436
|
if (index >= 0) updateConditionAtIndex(index, { value })
|
|
435
437
|
}
|
|
@@ -440,7 +442,7 @@ function onQueryOptionChange(id: string, optionValue: string) {
|
|
|
440
442
|
}
|
|
441
443
|
|
|
442
444
|
function getQueryOptionValue(condition: UICondition): string {
|
|
443
|
-
return Array.isArray(condition.value) ? '' : condition.value
|
|
445
|
+
return Array.isArray(condition.value) ? '' : String(condition.value ?? '')
|
|
444
446
|
}
|
|
445
447
|
|
|
446
448
|
function onConnectorChange(id: string, connector: LogicalOperator) {
|
|
@@ -591,7 +593,7 @@ function onConnectorChange(id: string, connector: LogicalOperator) {
|
|
|
591
593
|
:model-value="condition.value" :options="booleanOptions"
|
|
592
594
|
:placeholder="currentTexts.placeholders.selectOption"
|
|
593
595
|
class="m-0 light-input borderHover"
|
|
594
|
-
@update:model-value="(v:
|
|
596
|
+
@update:model-value="(v: boolean) => onValueChange(condition.id, v)"
|
|
595
597
|
/>
|
|
596
598
|
<DateInput
|
|
597
599
|
v-else-if="getFieldType(condition.field) === 'date'"
|
|
@@ -17,6 +17,8 @@ const props = withDefaults(
|
|
|
17
17
|
multiselect?: boolean
|
|
18
18
|
/** Render only each option's icon (label becomes a tooltip). */
|
|
19
19
|
iconOnly?: boolean
|
|
20
|
+
/** Background applied to the options area only; the field label stays outside it. */
|
|
21
|
+
bgColor?: string
|
|
20
22
|
} & BagelInputShellProps>(),
|
|
21
23
|
{ multiselect: false, iconOnly: false },
|
|
22
24
|
)
|
|
@@ -34,6 +36,13 @@ const hasValue = computed(() => {
|
|
|
34
36
|
return Array.isArray(v) ? v.length > 0 : v != null && v !== ''
|
|
35
37
|
})
|
|
36
38
|
|
|
39
|
+
/** Theme token names resolve to Bagel CSS variables; raw CSS values pass through. */
|
|
40
|
+
const resolvedBgColor = computed(() => {
|
|
41
|
+
const color = props.bgColor
|
|
42
|
+
if (!color) { return undefined }
|
|
43
|
+
return /^[a-z][a-z0-9-]*$/i.test(color) ? `var(--bgl-${color})` : color
|
|
44
|
+
})
|
|
45
|
+
|
|
37
46
|
type OptionValue = string | number | boolean
|
|
38
47
|
|
|
39
48
|
const selected = defineModel<OptionValue | OptionValue[] | null>('modelValue', { default: null })
|
|
@@ -57,10 +66,14 @@ function isSelected(val: OptionValue) {
|
|
|
57
66
|
<div
|
|
58
67
|
class="bagel-input SelectBtn" :class="[shellClass, { 'has-value': hasValue }]" :style="shellStyle"
|
|
59
68
|
>
|
|
60
|
-
<label v-if="label || required"
|
|
69
|
+
<label v-if="label || required" >
|
|
61
70
|
{{ resolveI18n(label) }}<span v-if="required"> *</span>
|
|
62
71
|
</label>
|
|
63
|
-
<div
|
|
72
|
+
<div
|
|
73
|
+
class="select-btn-items flex flex-wrap gap-025"
|
|
74
|
+
:class="{ 'has-background': !!bgColor, 'default-size': !thin && !size }"
|
|
75
|
+
:style="{ backgroundColor: resolvedBgColor }"
|
|
76
|
+
>
|
|
64
77
|
<Btn
|
|
65
78
|
v-for="option in options" :key="String(getOptionValue(option))" :thin="thin"
|
|
66
79
|
:size="size"
|
|
@@ -93,10 +106,47 @@ function isSelected(val: OptionValue) {
|
|
|
93
106
|
<style scoped>
|
|
94
107
|
|
|
95
108
|
.SelectBtn {
|
|
96
|
-
--bgl-btn-height:
|
|
109
|
+
--bgl-btn-height: var(--bgl-input-height);
|
|
110
|
+
font-size: var(--bgl-input-font-size);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.SelectBtn-padding {
|
|
114
|
+
padding: 0 var(--bgl-select-btn-padding, 0.7rem);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.select-btn-items {
|
|
118
|
+
width: fit-content;
|
|
119
|
+
max-width: 100%;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.select-btn-items.has-background,
|
|
123
|
+
.SelectBtn.frame .select-btn-items,
|
|
124
|
+
.SelectBtn.bgl-outline .select-btn-items {
|
|
125
|
+
--bgl-select-btn-group-padding: 0.25rem;
|
|
126
|
+
padding: var(--bgl-select-btn-group-padding);
|
|
127
|
+
border-radius: calc(var(--bgl-radius) * 2);
|
|
97
128
|
}
|
|
98
|
-
|
|
99
|
-
|
|
129
|
+
|
|
130
|
+
.SelectBtn.frame .select-btn-items.default-size .bgl_btn,
|
|
131
|
+
.SelectBtn.bgl-outline .select-btn-items.default-size .bgl_btn,
|
|
132
|
+
.select-btn-items.has-background.default-size .bgl_btn {
|
|
133
|
+
height: calc(var(--bgl-input-height) - var(--bgl-select-btn-group-padding) - var(--bgl-select-btn-group-padding));
|
|
134
|
+
line-height: calc(var(--bgl-input-height) - var(--bgl-select-btn-group-padding) - var(--bgl-select-btn-group-padding));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.SelectBtn.frame .select-btn-items,
|
|
138
|
+
.SelectBtn.bgl-outline .select-btn-items {
|
|
139
|
+
outline: 1.5px solid var(--bgl-border-color);
|
|
140
|
+
outline-offset: -1px;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.SelectBtn.frame .select-btn-items {
|
|
144
|
+
transition: outline-color 0.2s ease, box-shadow 0.2s ease;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.SelectBtn.frame:focus-within .select-btn-items {
|
|
148
|
+
outline-color: var(--bgl-input-label-active-color, var(--bgl-primary));
|
|
149
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--bgl-input-label-active-color, var(--bgl-primary)) 12%, transparent);
|
|
100
150
|
}
|
|
101
151
|
|
|
102
152
|
</style>
|
package/src/styles/buttons.css
CHANGED
|
@@ -217,6 +217,22 @@
|
|
|
217
217
|
transform: rotate(180deg);
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
.rotate-45 {
|
|
221
|
+
transform: rotate(45deg);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.rotate-135 {
|
|
225
|
+
transform: rotate(135deg);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.rotate-225 {
|
|
229
|
+
transform: rotate(225deg);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.rotate-315 {
|
|
233
|
+
transform: rotate(315deg);
|
|
234
|
+
}
|
|
235
|
+
|
|
220
236
|
[dir="rtl"] .rotate-180-rtl,
|
|
221
237
|
[dir="rtl"] .rtl-rotate-180,
|
|
222
238
|
[dir="rtl"] .rotate-rtl,
|