fix(InputMenu/SelectMenu): allow access nested object in option-attribute (#2465)

Co-authored-by: Benjamin Canac <canacb1@gmail.com>
This commit is contained in:
kyyy
2024-10-30 23:33:06 +07:00
committed by GitHub
parent b6ed1c59ff
commit ff1806143c
4 changed files with 20 additions and 10 deletions

View File

@@ -32,7 +32,7 @@ This component does not support multiple values. Use the [SelectMenu](/component
### Objects
You can pass an array of objects to `options` and either compare on the whole object or use the `by` prop to compare on a specific key. You can configure which field will be used to display the label through the `option-attribute` prop that defaults to `label`.
You can pass an array of objects to `options` and either compare on the whole object or use the `by` prop to compare on a specific key. You can configure which field will be used to display the label through the `option-attribute` prop that defaults to `label`. Additionally, you can use dot notation (e.g., `user.name`) to access nested object properties.
::component-example
---

View File

@@ -40,7 +40,7 @@ componentProps:
### Objects
You can pass an array of objects to `options` and either compare on the whole object or use the `by` prop to compare on a specific key. You can configure which field will be used to display the label through the `option-attribute` prop that defaults to `label`.
You can pass an array of objects to `options` and either compare on the whole object or use the `by` prop to compare on a specific key. You can configure which field will be used to display the label through the `option-attribute` prop that defaults to `label`. Additionally, you can use dot notation (e.g., `user.name`) to access nested object properties.
::component-example
---

View File

@@ -63,7 +63,7 @@
/>
<span v-else-if="option.chip" :class="uiMenu.option.chip.base" :style="{ background: `#${option.chip}` }" />
<span class="truncate">{{ ['string', 'number'].includes(typeof option) ? option : option[optionAttribute] }}</span>
<span class="truncate">{{ ['string', 'number'].includes(typeof option) ? option : accessor(option, optionAttribute) }}</span>
</slot>
</div>
@@ -310,9 +310,9 @@ export default defineComponent({
if (props.valueAttribute) {
const option = options.value.find(option => option[props.valueAttribute] === props.modelValue)
return option ? option[props.optionAttribute] : null
return option ? accessor(option, props.optionAttribute) : null
} else {
return ['string', 'number'].includes(typeof props.modelValue) ? props.modelValue : props.modelValue[props.optionAttribute]
return ['string', 'number'].includes(typeof props.modelValue) ? props.modelValue : accessor(props.modelValue as Record<string, any>, props.optionAttribute)
}
})
@@ -442,6 +442,10 @@ export default defineComponent({
emitFormChange()
}
function accessor<T extends Record<string, any>>(obj: T, key: string) {
return get(obj, key)
}
function onQueryChange(event: any) {
query.value = event.target.value
}
@@ -475,6 +479,7 @@ export default defineComponent({
filteredOptions,
// eslint-disable-next-line vue/no-dupe-keys
query,
accessor,
onUpdate,
onQueryChange
}

View File

@@ -86,7 +86,7 @@
/>
<span v-else-if="option.chip" :class="uiMenu.option.chip.base" :style="{ background: `#${option.chip}` }" />
<span class="truncate">{{ ['string', 'number'].includes(typeof option) ? option : option[optionAttribute] }}</span>
<span class="truncate">{{ ['string', 'number'].includes(typeof option) ? option : accessor(option, optionAttribute) }}</span>
</slot>
</div>
@@ -100,7 +100,7 @@
<li :class="[uiMenu.option.base, uiMenu.option.rounded, uiMenu.option.padding, uiMenu.option.size, uiMenu.option.color, active ? uiMenu.option.active : uiMenu.option.inactive]">
<div :class="uiMenu.option.container">
<slot name="option-create" :option="createOption" :active="active" :selected="optionSelected">
<span :class="uiMenu.option.create">Create "{{ createOption[optionAttribute] }}"</span>
<span :class="uiMenu.option.create">Create "{{ typeof createOption === 'string' ? createOption : accessor(createOption, optionAttribute) }}"</span>
</slot>
</div>
</li>
@@ -390,9 +390,9 @@ export default defineComponent({
}
} else if (props.modelValue !== undefined && props.modelValue !== null) {
if (props.valueAttribute) {
return selected.value?.[props.optionAttribute] ?? null
return accessor(selected.value, props.optionAttribute) ?? null
} else {
return ['string', 'number'].includes(typeof props.modelValue) ? props.modelValue : props.modelValue[props.optionAttribute]
return ['string', 'number'].includes(typeof props.modelValue) ? props.modelValue : accessor(props.modelValue as Record<string, any>, props.optionAttribute)
}
}
@@ -489,6 +489,10 @@ export default defineComponent({
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}
function accessor<T extends Record<string, any>>(obj: T, key: string) {
return get(obj, key)
}
const filteredOptions = computed(() => {
if (!query.value || debouncedSearch) {
return options.value
@@ -517,7 +521,7 @@ export default defineComponent({
return null
}
if (props.showCreateOptionWhen === 'always') {
const existingOption = filteredOptions.value.find(option => ['string', 'number'].includes(typeof option) ? option === query.value : option[props.optionAttribute] === query.value)
const existingOption = filteredOptions.value.find(option => ['string', 'number'].includes(typeof option) ? option === query.value : accessor(option, props.optionAttribute) === query.value)
if (existingOption) {
return null
}
@@ -573,6 +577,7 @@ export default defineComponent({
container,
selected,
label,
accessor,
isLeading,
isTrailing,
// eslint-disable-next-line vue/no-dupe-keys