diff --git a/examples/sites/demos/mobile-first/app/space/space-order.vue b/examples/sites/demos/mobile-first/app/space/space-order.vue index d017976b15..b375692405 100644 --- a/examples/sites/demos/mobile-first/app/space/space-order.vue +++ b/examples/sites/demos/mobile-first/app/space/space-order.vue @@ -1,13 +1,14 @@ diff --git a/examples/sites/demos/mobile-first/app/space/webdoc/space.js b/examples/sites/demos/mobile-first/app/space/webdoc/space.js index 54fe193819..74e7009a04 100644 --- a/examples/sites/demos/mobile-first/app/space/webdoc/space.js +++ b/examples/sites/demos/mobile-first/app/space/webdoc/space.js @@ -9,8 +9,8 @@ export default { 'en-US': 'Basic Usage' }, desc: { - 'zh-CN': '

默认横向排列,支持自动插槽分隔间距

', - 'en-US': '

Horizontal layout by default, with automatic spacing between slots

' + 'zh-CN': '

默认采用横向布局(row),自动为插槽内容添加间距。

', + 'en-US': '

Uses horizontal layout (row) by default, automatically adding spacing between slot content.

' }, codeFiles: ['basic-usage.vue'] }, @@ -21,9 +21,10 @@ export default { 'en-US': 'Spacing Size' }, desc: { - 'zh-CN': '

通过 `size` 属性设置间距,支持 small / medium / large 或自定义数值 / 数组。

', + 'zh-CN': + '

通过 size 属性设置间距大小,支持 small、medium、large 预定义值或自定义数值/数组。

', 'en-US': - '

Use the `size` prop to define spacing. Supports small / medium / large or custom values / arrays.

' + '

Use the size prop to set spacing size. Supports predefined values (small, medium, large) or custom values/arrays.

' }, codeFiles: ['space-size.vue'] }, @@ -31,11 +32,12 @@ export default { demoId: 'space-direction', name: { 'zh-CN': '排列方向', - 'en-US': 'Direction' + 'en-US': 'Layout Direction' }, desc: { - 'zh-CN': '

通过 `direction` 属性设置排列方向,支持 horizontal 或 vertical。

', - 'en-US': '

Use the `direction` prop to control layout direction: horizontal or vertical.

' + 'zh-CN': '

通过 direction 属性设置布局方向,支持 row(横向)或 column(纵向)。

', + 'en-US': + '

Use the direction prop to set layout direction: row (horizontal) or column (vertical).

' }, codeFiles: ['space-direction.vue'] }, @@ -43,37 +45,38 @@ export default { demoId: 'space-wrap', name: { 'zh-CN': '换行显示', - 'en-US': 'Wrapping' + 'en-US': 'Content Wrapping' }, desc: { - 'zh-CN': '

通过 `wrap` 属性控制是否换行显示内容。

', - 'en-US': '

Use the `wrap` prop to enable wrapping of child items.

' + 'zh-CN': '

通过 wrap 属性控制子项内容是否换行显示。

', + 'en-US': '

Use the wrap prop to control whether child items wrap to multiple lines.

' }, codeFiles: ['space-wrap.vue'] }, { demoId: 'space-align', name: { - 'zh-CN': '对齐方式', - 'en-US': 'Alignment' + 'zh-CN': '交叉轴对齐', + 'en-US': 'Cross Axis Alignment' }, desc: { - 'zh-CN': '

通过 `align` 设置交叉轴对齐方式,如 start、center、end、baseline 等。

', - 'en-US': '

Use `align` to define alignment on the cross axis, such as start, center, end, or baseline.

' + 'zh-CN': '

通过 align 属性设置交叉轴对齐方式,支持 start、center、end、baseline 等值。

', + 'en-US': + '

Use the align prop to define alignment on the cross axis, supporting values like start, center, end, and baseline.

' }, codeFiles: ['space-align.vue'] }, { demoId: 'space-justify', name: { - 'zh-CN': '主轴对齐方式', - 'en-US': 'Justify Content' + 'zh-CN': '主轴对齐', + 'en-US': 'Main Axis Justification' }, desc: { 'zh-CN': - '

通过 `justify` 设置主轴对齐方式,如 start、center、end、space-between、space-around、space-evenly。

', + '

通过 justify 属性设置主轴对齐方式,支持 start、center、end、space-between、space-around、space-evenly。

', 'en-US': - '

Use `justify` to set main axis alignment like start, center, end, space-between, space-around, space-evenly.

' + '

Use the justify prop to set main axis alignment, supporting start, center, end, space-between, space-around, and space-evenly.

' }, codeFiles: ['space-justify.vue'] }, @@ -81,11 +84,13 @@ export default { demoId: 'space-order', name: { 'zh-CN': '自定义排序', - 'en-US': 'Custom Order' + 'en-US': 'Custom Ordering' }, desc: { - 'zh-CN': '

通过 `order` 属性传入 key 数组,自定义子元素渲染顺序。

', - 'en-US': '

Use the `order` prop with a key array to customize rendering order of children.

' + 'zh-CN': + '

通过 order 属性传入 key 数组来自定义子元素的渲染顺序,未设置 key 的子元素将自动排列在最后。

', + 'en-US': + '

Use the order prop with an array of keys to customize the rendering order of child elements. Children without defined keys are automatically arranged at the end.

' }, codeFiles: ['space-order.vue'] } diff --git a/examples/sites/demos/pc/app/space/space-order.vue b/examples/sites/demos/pc/app/space/space-order.vue index d017976b15..b375692405 100644 --- a/examples/sites/demos/pc/app/space/space-order.vue +++ b/examples/sites/demos/pc/app/space/space-order.vue @@ -1,13 +1,14 @@ diff --git a/examples/sites/demos/pc/app/space/webdoc/space.js b/examples/sites/demos/pc/app/space/webdoc/space.js index 62fd1cb219..74e7009a04 100644 --- a/examples/sites/demos/pc/app/space/webdoc/space.js +++ b/examples/sites/demos/pc/app/space/webdoc/space.js @@ -3,14 +3,14 @@ export default { owner: '', demos: [ { - demoId: 'basic-usage', + demoId: 'basic-space', name: { 'zh-CN': '基本用法', 'en-US': 'Basic Usage' }, desc: { - 'zh-CN': '

默认横向排列,支持自动插槽分隔间距

', - 'en-US': '

Horizontal layout by default, with automatic spacing between slots

' + 'zh-CN': '

默认采用横向布局(row),自动为插槽内容添加间距。

', + 'en-US': '

Uses horizontal layout (row) by default, automatically adding spacing between slot content.

' }, codeFiles: ['basic-usage.vue'] }, @@ -21,9 +21,10 @@ export default { 'en-US': 'Spacing Size' }, desc: { - 'zh-CN': '

通过 `size` 属性设置间距,支持 small / medium / large 或自定义数值 / 数组。

', + 'zh-CN': + '

通过 size 属性设置间距大小,支持 small、medium、large 预定义值或自定义数值/数组。

', 'en-US': - '

Use the `size` prop to define spacing. Supports small / medium / large or custom values / arrays.

' + '

Use the size prop to set spacing size. Supports predefined values (small, medium, large) or custom values/arrays.

' }, codeFiles: ['space-size.vue'] }, @@ -31,11 +32,12 @@ export default { demoId: 'space-direction', name: { 'zh-CN': '排列方向', - 'en-US': 'Direction' + 'en-US': 'Layout Direction' }, desc: { - 'zh-CN': '

通过 `direction` 属性设置排列方向,支持 row 或column。

', - 'en-US': '

Use the `direction` prop to control layout direction: row or column.

' + 'zh-CN': '

通过 direction 属性设置布局方向,支持 row(横向)或 column(纵向)。

', + 'en-US': + '

Use the direction prop to set layout direction: row (horizontal) or column (vertical).

' }, codeFiles: ['space-direction.vue'] }, @@ -43,37 +45,38 @@ export default { demoId: 'space-wrap', name: { 'zh-CN': '换行显示', - 'en-US': 'Wrapping' + 'en-US': 'Content Wrapping' }, desc: { - 'zh-CN': '

通过 `wrap` 属性控制是否换行显示内容。

', - 'en-US': '

Use the `wrap` prop to enable wrapping of child items.

' + 'zh-CN': '

通过 wrap 属性控制子项内容是否换行显示。

', + 'en-US': '

Use the wrap prop to control whether child items wrap to multiple lines.

' }, codeFiles: ['space-wrap.vue'] }, { demoId: 'space-align', name: { - 'zh-CN': '对齐方式', - 'en-US': 'Alignment' + 'zh-CN': '交叉轴对齐', + 'en-US': 'Cross Axis Alignment' }, desc: { - 'zh-CN': '

通过 `align` 设置交叉轴对齐方式,如 start、center、end、baseline 等。

', - 'en-US': '

Use `align` to define alignment on the cross axis, such as start, center, end, or baseline.

' + 'zh-CN': '

通过 align 属性设置交叉轴对齐方式,支持 start、center、end、baseline 等值。

', + 'en-US': + '

Use the align prop to define alignment on the cross axis, supporting values like start, center, end, and baseline.

' }, codeFiles: ['space-align.vue'] }, { demoId: 'space-justify', name: { - 'zh-CN': '主轴对齐方式', - 'en-US': 'Justify Content' + 'zh-CN': '主轴对齐', + 'en-US': 'Main Axis Justification' }, desc: { 'zh-CN': - '

通过 `justify` 设置主轴对齐方式,如 start、center、end、space-between、space-around、space-evenly。

', + '

通过 justify 属性设置主轴对齐方式,支持 start、center、end、space-between、space-around、space-evenly。

', 'en-US': - '

Use `justify` to set main axis alignment like start, center, end, space-between, space-around, space-evenly.

' + '

Use the justify prop to set main axis alignment, supporting start, center, end, space-between, space-around, and space-evenly.

' }, codeFiles: ['space-justify.vue'] }, @@ -81,11 +84,13 @@ export default { demoId: 'space-order', name: { 'zh-CN': '自定义排序', - 'en-US': 'Custom Order' + 'en-US': 'Custom Ordering' }, desc: { - 'zh-CN': '

通过 `order` 属性传入 key 数组,自定义子元素渲染顺序。

', - 'en-US': '

Use the `order` prop with a key array to customize rendering order of children.

' + 'zh-CN': + '

通过 order 属性传入 key 数组来自定义子元素的渲染顺序,未设置 key 的子元素将自动排列在最后。

', + 'en-US': + '

Use the order prop with an array of keys to customize the rendering order of child elements. Children without defined keys are automatically arranged at the end.

' }, codeFiles: ['space-order.vue'] } diff --git a/packages/renderless/src/space/vue.ts b/packages/renderless/src/space/vue.ts index 8396e0d468..a385d92fee 100644 --- a/packages/renderless/src/space/vue.ts +++ b/packages/renderless/src/space/vue.ts @@ -2,14 +2,47 @@ import type { ISpaceProps } from '@/types' import { getGapStyle } from './index' -export const api = ['state'] +export const api = ['state', 'orderedChildren'] -export const renderless = (props: ISpaceProps, hooks, { constants }) => { +function isVNodeFn(node: any): boolean { + return !!(node && (node.__v_isVNode || node.componentOptions)) +} + +export const renderless = (props: ISpaceProps, hooks, { slots }) => { const { reactive, computed } = hooks const state = reactive({ gapStyle: computed(() => getGapStyle(props)) }) - return { state } + // 排序逻辑 + const orderedChildren = computed(() => { + const children = slots.default?.() || [] + + // 过滤掉非 VNode 或注释节点 + const validChildren = children.filter((v) => { + if (!isVNodeFn(v)) return false + const type = (v as any).type + return type !== 'Comment' && type !== Symbol.for('v-comment') + }) + + if (!props.order?.length) return validChildren + + // 根据 key 或 class 建立索引 + const map: Record = {} + validChildren.forEach((child) => { + const key = child.key ?? (Array.isArray(child.props?.class) ? child.props.class.join(' ') : child.props?.class) + if (key) map[String(key)] = child + }) + + // 按 order 排序 + const sorted = props.order.map((k) => map[k]).filter(Boolean) + + // 剩余没有在 order 里的保持原顺序 + const rest = validChildren.filter((v) => !props.order.includes(String(v.key))) + + return [...sorted, ...rest] + }) + + return { state, orderedChildren } } diff --git a/packages/vue/src/space/__tests__/space.test.tsx b/packages/vue/src/space/__tests__/space.test.tsx index bf626618f1..ad7c90f132 100644 --- a/packages/vue/src/space/__tests__/space.test.tsx +++ b/packages/vue/src/space/__tests__/space.test.tsx @@ -5,27 +5,6 @@ import Space from '@opentiny/vue-space' describe('PC Mode', () => { const mount = mountPcMode - test('base 基本用法', async () => { - const wrapper = mount(() => ( - - Item 1 - Item 2 - - )) - - // 1. 验证容器元素 - expect(wrapper.find('[data-tag="tiny-space"]').exists()).toBe(true) - - // 2. 验证子元素 - expect(wrapper.findAll('[data-tag="tiny-space"] > *').length).toBe(2) - - // 3. 验证文本内容 - expect(wrapper.text()).toContain('Item 1') - expect(wrapper.text()).toContain('Item 2') - - wrapper.unmount() - }) - test('props direction', async () => { const wrapper = mount(() => ( @@ -73,4 +52,20 @@ describe('PC Mode', () => { expect(wrapper.text()).toContain('Slot 2') wrapper.unmount() }) + + test('child element order', async () => { + const wrapper = mount(() => ( + + A + B + C + + )) + const items = wrapper.findAll('.item') + expect(items.length).toBe(3) + expect(items[0].text()).toBe('A') + expect(items[1].text()).toBe('B') + expect(items[2].text()).toBe('C') + wrapper.unmount() + }) }) diff --git a/packages/vue/src/space/src/index.ts b/packages/vue/src/space/src/index.ts index fee74dd7c8..2bcbd910ba 100644 --- a/packages/vue/src/space/src/index.ts +++ b/packages/vue/src/space/src/index.ts @@ -39,6 +39,10 @@ export const spaceProps = { type: String, default: '' }, + order: { + type: Array as PropType, + default: () => [] + }, /** 自定义样式 */ customStyle: { type: Object as PropType>, diff --git a/packages/vue/src/space/src/mobile-first.vue b/packages/vue/src/space/src/mobile-first.vue index 5ba522b46c..921e70350f 100644 --- a/packages/vue/src/space/src/mobile-first.vue +++ b/packages/vue/src/space/src/mobile-first.vue @@ -1,31 +1,73 @@ -