Skip to content

Commit d2a84ac

Browse files
jonadelinejrmi
andauthored
fix(builder): clear single-select record selector + truncate dropdown… (baserow#5303)
* fix(builder): clear single-select record selector + truncate dropdown labels * add changelog * please linter * Also allo to unselect choice elements --------- Co-authored-by: Jeremie Pardou <571533+jrmi@users.noreply.github.com>
1 parent e69f2a4 commit d2a84ac

8 files changed

Lines changed: 82 additions & 9 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "feature",
3+
"message": "clear single-select record selector",
4+
"issue_origin": "github",
5+
"issue_number": 3719,
6+
"domain": "builder",
7+
"bullet_points": [],
8+
"created_at": "2026-05-05"
9+
}

web-frontend/modules/builder/components/elements/baseComponents/ABDropdownItem.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<li
33
class="ab-dropdownitem__item ab-dropdownitem__item--no-options"
44
:class="{
5+
'ab-dropdownitem__item--single': !multiple.value,
56
hidden: !isVisible(query),
67
active: isActive(value),
78
disabled: disabled,
@@ -15,7 +16,7 @@
1516
@mousemove="hover(value, disabled)"
1617
>
1718
<div class="ab-dropdownitem__item-name">
18-
<div v-if="multiple.value">
19+
<div v-if="multiple.value" class="ab-dropdownitem__item-name-checkbox">
1920
<Checkbox :disabled="disabled" :checked="isActive(value)"></Checkbox>
2021
</div>
2122
<slot>

web-frontend/modules/builder/components/elements/components/ChoiceElement.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"
1515
:show-search="false"
1616
:multiple="element.multiple"
17+
:clearable="!element.multiple && !element.required"
1718
@hide="onFormElementTouch"
1819
>
1920
<ABDropdownItem

web-frontend/modules/builder/components/elements/components/RecordSelectorElement.vue

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
class="choice-element"
1414
:placeholder="resolvedPlaceholder"
1515
:multiple="element.multiple"
16+
:clearable="!element.multiple && !element.required"
1617
:before-show="beforeShow"
1718
@hide="onFormElementTouch"
1819
@query-change="adhocSearch = $event"

web-frontend/modules/core/assets/scss/components/builder/elements/ab_components/ab_dropdownitem.scss

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@
135135
text-decoration: none;
136136
}
137137

138+
.ab-dropdownitem__item--single & {
139+
padding-right: calc(var(--input-horizontal-padding, 12px) + 1.25em);
140+
}
141+
138142
.ab-dropdownitem__item.disabled & {
139143
color: $palette-neutral-700;
140144

@@ -150,8 +154,12 @@
150154
font-weight: 500;
151155
line-height: 120%;
152156
gap: 0.5em;
157+
min-width: 0;
158+
max-width: 100%;
153159

154-
@extend %ellipsis;
160+
.ab-dropdownitem__item-name-checkbox {
161+
flex-shrink: 0;
162+
}
155163

156164
.ab-dropdownitem__item-link:active & {
157165
color: color-mix(
@@ -161,3 +169,10 @@
161169
);
162170
}
163171
}
172+
173+
.ab-dropdownitem__item-name-text {
174+
@extend %ellipsis;
175+
176+
flex: 1 1 auto;
177+
min-width: 0;
178+
}

web-frontend/modules/core/mixins/dropdown.js

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,15 @@ export default {
138138
required: false,
139139
default: false,
140140
},
141+
/**
142+
* If true (single-select only), choosing the already selected value clears the
143+
* selection (emits `null`).
144+
*/
145+
clearable: {
146+
type: Boolean,
147+
required: false,
148+
default: false,
149+
},
141150
/**
142151
* Before show let the opportunity to execute something before actually opening the
143152
* dropdown. Useful when, for instance, you want to populate the item on demand only
@@ -487,11 +496,21 @@ export default {
487496
this.$emit('update:modelValue', newValue) // Vue 3 compatibility
488497
this.$emit('change', newValue)
489498
} else {
490-
// emitting the updated value Vue 2 style.
491-
this.$emit('input', value)
492-
// emitting the updated value Vue 3 style.
493-
this.$emit('update:modelValue', value) // Vue 3 compatibility
494-
this.$emit('change', value)
499+
const hasSingleValue =
500+
this.currentValue !== null && this.currentValue !== undefined
501+
if (
502+
this.clearable &&
503+
hasSingleValue &&
504+
_.isEqual(value, this.currentValue)
505+
) {
506+
this.$emit('input', null)
507+
this.$emit('update:modelValue', null)
508+
this.$emit('change', null)
509+
} else {
510+
this.$emit('input', value)
511+
this.$emit('update:modelValue', value)
512+
this.$emit('change', value)
513+
}
495514
this.hide()
496515
}
497516
},

web-frontend/test/unit/builder/components/elements/components/__snapshots__/ChoiceElement.spec.js.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ exports[`ChoiceElement > as manual dropdown 1`] = `
203203
204204
205205
<li
206-
class="ab-dropdownitem__item ab-dropdownitem__item--no-options"
206+
class="ab-dropdownitem__item ab-dropdownitem__item--no-options ab-dropdownitem__item--single"
207207
>
208208
<a
209209
class="ab-dropdownitem__item-link"
@@ -228,7 +228,7 @@ exports[`ChoiceElement > as manual dropdown 1`] = `
228228
/>
229229
</li>
230230
<li
231-
class="ab-dropdownitem__item ab-dropdownitem__item--no-options"
231+
class="ab-dropdownitem__item ab-dropdownitem__item--no-options ab-dropdownitem__item--single"
232232
>
233233
<a
234234
class="ab-dropdownitem__item-link"

web-frontend/test/unit/core/components/dropdown.spec.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,33 @@ describe('Dropdown component', () => {
117117
expect(wrapper.element).toMatchSnapshot()
118118
})
119119

120+
test('clearable single select clears when choosing the active value', async () => {
121+
let wrapper = null
122+
123+
const onInput = vi.fn(async (newVal) => {
124+
wrapper.setProps({ value: newVal })
125+
await wrapper.vm.$nextTick()
126+
})
127+
128+
wrapper = await mountComponent({
129+
props: { value: 'a', clearable: true },
130+
slots: {
131+
default: `<DropdownItem value="a" name="A"/>
132+
<DropdownItem value="b" name="B"/>`,
133+
},
134+
listeners: { input: onInput },
135+
})
136+
137+
await wrapper.find('.dropdown__selected').trigger('click')
138+
await wrapper
139+
.find('.select__items :nth-child(1)')
140+
.find('.select__item-link')
141+
.trigger('click')
142+
await wrapper.vm.$nextTick()
143+
144+
expect(onInput).toHaveBeenLastCalledWith(null)
145+
})
146+
120147
test('focus', async () => {
121148
let wrapper = null
122149

0 commit comments

Comments
 (0)