Skip to content

Commit

Permalink
fix(InputMenu/SelectMenu): allow access nested object in `option-attr…
Browse files Browse the repository at this point in the history
…ibute` (#2465)

Co-authored-by: Benjamin Canac <[email protected]>
  • Loading branch information
rdjanuar and benjamincanac authored Oct 30, 2024
1 parent b6ed1c5 commit ff18061
Show file tree
Hide file tree
Showing 4 changed files with 20 additions and 10 deletions.
2 changes: 1 addition & 1 deletion docs/content/2.components/input-menu.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
---
Expand Down
2 changes: 1 addition & 1 deletion docs/content/2.components/select-menu.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
---
Expand Down
11 changes: 8 additions & 3 deletions src/runtime/components/forms/InputMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down Expand Up @@ -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)
}
})
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -475,6 +479,7 @@ export default defineComponent({
filteredOptions,
// eslint-disable-next-line vue/no-dupe-keys
query,
accessor,
onUpdate,
onQueryChange
}
Expand Down
15 changes: 10 additions & 5 deletions src/runtime/components/forms/SelectMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand All @@ -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>
Expand Down Expand Up @@ -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)
}
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -573,6 +577,7 @@ export default defineComponent({
container,
selected,
label,
accessor,
isLeading,
isTrailing,
// eslint-disable-next-line vue/no-dupe-keys
Expand Down

0 comments on commit ff18061

Please sign in to comment.