@vue-ui-kit/ant
Version:
Vue3 UI Kit based on Ant Design
124 lines (110 loc) • 4.02 kB
text/typescript
import type { PFormItemProps } from '#/antProxy';
import { defaultItemResponsive } from '@/utils/core';
import type { ColProps } from 'ant-design-vue';
import { computed, onBeforeUnmount, type Ref, ref, watch } from 'vue';
import { omit } from 'xe-utils';
const breakpointOrder = ['xxl', 'xl', 'lg', 'md', 'sm', 'xs'] as const;
type Breakpoint = (typeof breakpointOrder)[number];
const breakpointMinWidth: Record<Breakpoint, number> = {
xxl: 1600,
xl: 1200,
lg: 992,
md: 768,
sm: 576,
xs: 0,
};
function getActiveBreakpoint(width: number): Breakpoint {
return breakpointOrder.find((breakpoint) => width >= breakpointMinWidth[breakpoint]) ?? 'xs';
}
function resolveItemCol<F extends Recordable>(
item: PFormItemProps<F>,
breakpoint: Breakpoint,
): ColProps {
if (!item.col) {
return item.span ? { span: item.span } : { span: defaultItemResponsive[breakpoint] ?? 24 };
}
const baseCol = omit(item.col, [...breakpointOrder]) as ColProps;
const activeBreakpointIndex = breakpointOrder.indexOf(breakpoint);
for (const responsiveBreakpoint of breakpointOrder.slice(activeBreakpointIndex)) {
const responsive = item.col[responsiveBreakpoint];
if (typeof responsive === 'number') return { ...baseCol, span: responsive };
if (responsive && typeof responsive === 'object') return { ...baseCol, ...responsive };
}
return baseCol;
}
function getItemSpan<F extends Recordable>(item: PFormItemProps<F>, breakpoint: Breakpoint) {
const activeBreakpointIndex = breakpointOrder.indexOf(breakpoint);
for (const responsiveBreakpoint of breakpointOrder.slice(activeBreakpointIndex)) {
const responsive = item.col?.[responsiveBreakpoint];
if (typeof responsive === 'number') return responsive;
if (responsive && typeof responsive === 'object' && typeof responsive.span === 'number') {
return responsive.span;
}
}
if (typeof item.col?.span === 'number') return item.col.span;
if (typeof item.span === 'number') return item.span;
return defaultItemResponsive[breakpoint] ?? 24;
}
function countRows(spans: number[]) {
if (spans.length === 0) return 0;
let rows = 1;
let currentSpan = 0;
for (const span of spans) {
if (currentSpan + span > 24) {
rows += 1;
currentSpan = span;
} else {
currentSpan += span;
}
}
return rows;
}
export function useFormRowLayout<F extends Recordable>(
items: Ref<PFormItemProps<F>[]>,
containerRef: Readonly<Ref<HTMLElement | null>>,
) {
const containerWidth = ref(0);
let resizeObserver: ResizeObserver | null = null;
const disconnect = () => {
resizeObserver?.disconnect();
resizeObserver = null;
};
watch(
containerRef,
(container) => {
disconnect();
if (!container) {
containerWidth.value = 0;
return;
}
const updateWidth = () => {
containerWidth.value = container.getBoundingClientRect().width;
};
updateWidth();
if (typeof ResizeObserver !== 'undefined') {
resizeObserver = new ResizeObserver(updateWidth);
resizeObserver.observe(container);
}
},
{ flush: 'post' },
);
onBeforeUnmount(disconnect);
const activeBreakpoint = computed<Breakpoint>(() => getActiveBreakpoint(containerWidth.value));
const resolvedItems = computed<PFormItemProps<F>[]>(() => {
if (containerWidth.value <= 0) return items.value;
return items.value.map((item) => ({
...item,
col: resolveItemCol(item, activeBreakpoint.value),
}));
});
const fieldRowCount = computed(() => {
const spans = resolvedItems.value
.filter((item) => item.itemRender || item.slots?.default)
.map((item) => getItemSpan(item, activeBreakpoint.value));
return countRows(spans);
});
return {
resolvedItems,
isSingleRow: computed(() => resolvedItems.value.length > 0 && fieldRowCount.value === 1),
};
}