From ff1806143c45a7d83b00e78bec979a8f412a2827 Mon Sep 17 00:00:00 2001 From: kyyy <60952577+rdjanuar@users.noreply.github.com> Date: Wed, 30 Oct 2024 23:33:06 +0700 Subject: [PATCH] fix(InputMenu/SelectMenu): allow access nested object in `option-attribute` (#2465) Co-authored-by: Benjamin Canac --- docs/content/2.components/input-menu.md | 2 +- docs/content/2.components/select-menu.md | 2 +- src/runtime/components/forms/InputMenu.vue | 11 ++++++++--- src/runtime/components/forms/SelectMenu.vue | 15 ++++++++++----- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/docs/content/2.components/input-menu.md b/docs/content/2.components/input-menu.md index 4adc74aa..171349cc 100644 --- a/docs/content/2.components/input-menu.md +++ b/docs/content/2.components/input-menu.md @@ -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 --- diff --git a/docs/content/2.components/select-menu.md b/docs/content/2.components/select-menu.md index 3b7f8c38..f8503ebe 100644 --- a/docs/content/2.components/select-menu.md +++ b/docs/content/2.components/select-menu.md @@ -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 --- diff --git a/src/runtime/components/forms/InputMenu.vue b/src/runtime/components/forms/InputMenu.vue index f6872a79..7714983c 100644 --- a/src/runtime/components/forms/InputMenu.vue +++ b/src/runtime/components/forms/InputMenu.vue @@ -63,7 +63,7 @@ /> - {{ ['string', 'number'].includes(typeof option) ? option : option[optionAttribute] }} + {{ ['string', 'number'].includes(typeof option) ? option : accessor(option, optionAttribute) }} @@ -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, props.optionAttribute) } }) @@ -442,6 +442,10 @@ export default defineComponent({ emitFormChange() } + function accessor>(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 } diff --git a/src/runtime/components/forms/SelectMenu.vue b/src/runtime/components/forms/SelectMenu.vue index 761cfcf9..cdd829e3 100644 --- a/src/runtime/components/forms/SelectMenu.vue +++ b/src/runtime/components/forms/SelectMenu.vue @@ -86,7 +86,7 @@ /> - {{ ['string', 'number'].includes(typeof option) ? option : option[optionAttribute] }} + {{ ['string', 'number'].includes(typeof option) ? option : accessor(option, optionAttribute) }} @@ -100,7 +100,7 @@
  • - Create "{{ createOption[optionAttribute] }}" + Create "{{ typeof createOption === 'string' ? createOption : accessor(createOption, optionAttribute) }}"
  • @@ -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, props.optionAttribute) } } @@ -489,6 +489,10 @@ export default defineComponent({ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') } + function accessor>(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