From 7b25a87d6dff4bda29da5f2b2739c2c0cd1e86ed Mon Sep 17 00:00:00 2001 From: catanswer <564029918@qq.com> Date: Wed, 6 Aug 2025 04:34:39 +0800 Subject: [PATCH] feat(components): [autocomplete] add header and footer slot (#21594) * feat(components): [autocomplete] add header and footer slot * docs(components): add a bump version for new slots * test(components): [autocomplete] add header/footer slots test case * docs(components): [autocomplete] add header and footer slots example --------- Co-authored-by: yinshenghao --- docs/en-US/component/autocomplete.md | 28 +++-- .../autocomplete/custom-header-footer.vue | 106 ++++++++++++++++++ .../__tests__/autocomplete.test.tsx | 37 +++++- .../autocomplete/src/autocomplete.vue | 14 +++ packages/theme-chalk/src/autocomplete.scss | 10 ++ 5 files changed, 186 insertions(+), 9 deletions(-) create mode 100644 docs/examples/autocomplete/custom-header-footer.vue diff --git a/docs/en-US/component/autocomplete.md b/docs/en-US/component/autocomplete.md index 4aea2599d0..a293de7837 100644 --- a/docs/en-US/component/autocomplete.md +++ b/docs/en-US/component/autocomplete.md @@ -47,6 +47,16 @@ autocomplete/custom-loading ::: +## Custom Header & Footer ^(2.11.0) + +You can customize both the header and footer of the dropdown using slots + +:::demo Use slot to customize the content. + +autocomplete/custom-header-footer + +::: + ## API ### Attributes @@ -87,14 +97,16 @@ autocomplete/custom-loading ### Slots -| Name | Description | Type | -| ---------------- | ------------------------------------ | ---------------------------------------- | -| default | custom content for input suggestions | ^[object]`{ item: Record }` | -| prefix | content as Input prefix | - | -| suffix | content as Input suffix | - | -| prepend | content to prepend before Input | - | -| append | content to append after Input | - | -| loading ^(2.5.0) | override loading content | - | +| Name | Description | Type | +| ---------------- | ------------------------------------- | ---------------------------------------- | +| default | custom content for input suggestions | ^[object]`{ item: Record }` | +| header ^(2.11.0) | content at the top of the dropdown | - | +| footer ^(2.11.0) | content at the bottom of the dropdown | - | +| prefix | content as Input prefix | - | +| suffix | content as Input suffix | - | +| prepend | content to prepend before Input | - | +| append | content to append after Input | - | +| loading ^(2.5.0) | override loading content | - | ### Exposes diff --git a/docs/examples/autocomplete/custom-header-footer.vue b/docs/examples/autocomplete/custom-header-footer.vue new file mode 100644 index 0000000000..bf34d25ab0 --- /dev/null +++ b/docs/examples/autocomplete/custom-header-footer.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/packages/components/autocomplete/__tests__/autocomplete.test.tsx b/packages/components/autocomplete/__tests__/autocomplete.test.tsx index b77234e3f3..327d2c10f2 100644 --- a/packages/components/autocomplete/__tests__/autocomplete.test.tsx +++ b/packages/components/autocomplete/__tests__/autocomplete.test.tsx @@ -18,7 +18,8 @@ vi.useFakeTimers() const _mount = ( payload = {}, type: 'fn-cb' | 'fn-promise' | 'fn-arr' | 'fn-async' | 'arr' = 'fn-cb', - defaultValue = '' + defaultValue = '', + slots: Record = {} ) => mount( defineComponent({ @@ -75,6 +76,7 @@ const _mount = ( v-model={state.value} fetch-suggestions={querySearch} {...state.payload} + v-slots={slots} /> ) }, @@ -482,4 +484,37 @@ describe('Autocomplete.vue', () => { expect(container.attributes('aria-expanded')).toBe('true') }) }) + + describe('new slots: header & footer', () => { + test('header slot renders', async () => { + const wrapper = _mount({ debounce: 0 }, 'fn-cb', '', { + header: () => 'Custom Header', + }) + await wrapper.find('input').trigger('focus') + vi.runAllTimers() + await nextTick() + + const headerEl = document.body.querySelector( + '.el-autocomplete-suggestion__header' + ) + expect(headerEl).not.toBeNull() + expect(headerEl!.textContent).toBe('Custom Header') + }) + + test('should render footer slot', async () => { + const wrapper = _mount({ debounce: 0 }, 'fn-cb', '', { + footer: () => 'Custom Footer', + }) + await nextTick() + await wrapper.find('input').trigger('focus') + vi.runAllTimers() + await nextTick() + + const footerEl = document.body.querySelector( + '.el-autocomplete-suggestion__footer' + ) + expect(footerEl).not.toBeNull() + expect(footerEl!.textContent).toBe('Custom Footer') + }) + }) }) diff --git a/packages/components/autocomplete/src/autocomplete.vue b/packages/components/autocomplete/src/autocomplete.vue index 1954295dae..b3fce1edeb 100644 --- a/packages/components/autocomplete/src/autocomplete.vue +++ b/packages/components/autocomplete/src/autocomplete.vue @@ -68,6 +68,13 @@ }" role="region" > +
+ +
+
+ +
diff --git a/packages/theme-chalk/src/autocomplete.scss b/packages/theme-chalk/src/autocomplete.scss index 0f8a1d99a1..2406b34904 100644 --- a/packages/theme-chalk/src/autocomplete.scss +++ b/packages/theme-chalk/src/autocomplete.scss @@ -26,6 +26,16 @@ border-radius: getCssVar('border-radius', 'base'); box-sizing: border-box; + @include e(header) { + padding: 10px; + border-bottom: 1px solid getCssVar('border-color', 'lighter'); + } + + @include e(footer) { + padding: 10px; + border-top: 1px solid getCssVar('border-color', 'lighter'); + } + @include e(wrap) { max-height: 280px; padding: 10px 0;