feat: initial commit - Phase 1 & 2 core features

This commit is contained in:
hiderfong
2026-04-22 17:07:33 +08:00
commit 1773bda06b
25005 changed files with 6252106 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
import { ComponentSize } from "../../constants/size.js";
import { Option } from "./src/types.js";
import { Props, SegmentedEmits, SegmentedInstance, SegmentedProps, SegmentedPropsPublic, defaultProps, segmentedEmits, segmentedProps } from "./src/segmented.js";
import "../../index.js";
import * as vue from "vue";
//#region ../../packages/components/segmented/index.d.ts
declare const ElSegmented: (<T extends Option = Option>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: {
attrs: any;
emit: ((event: "change", val: any) => void) & ((event: "update:modelValue", val: any) => void);
slots: {
default?: (props: {
item: T;
}) => any;
};
}, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
props: vue.PublicProps & {
direction?: ("vertical" | "horizontal") | undefined;
options?: T[] | undefined;
modelValue?: (string | number | boolean) | undefined;
props?: Props | undefined;
block?: boolean | undefined;
size?: ComponentSize | undefined;
disabled?: boolean | undefined;
validateEvent?: boolean | undefined;
id?: string | undefined;
name?: string | undefined;
ariaLabel?: string | undefined;
onChange?: ((val: any) => any) | undefined;
"onUpdate:modelValue"?: ((val: any) => any) | undefined;
} & (typeof globalThis extends {
__VLS_PROPS_FALLBACK: infer P;
} ? P : {});
expose: (exposed: {}) => void;
attrs: any;
slots: {
default?: (props: {
item: T;
}) => any;
};
emit: ((event: "change", val: any) => void) & ((event: "update:modelValue", val: any) => void);
}>) => vue.VNode<vue.RendererNode, vue.RendererElement, {
[key: string]: any;
}> & {
__ctx?: Awaited<typeof __VLS_setup>;
}) & vue.ObjectPlugin & Record<string, any>;
//#endregion
export { ElSegmented, ElSegmented as default, Props, SegmentedEmits, SegmentedInstance, SegmentedProps, SegmentedPropsPublic, defaultProps, segmentedEmits, segmentedProps };
+10
View File
@@ -0,0 +1,10 @@
import { withInstall } from "../../utils/vue/install.mjs";
import { defaultProps, segmentedEmits, segmentedProps } from "./src/segmented.mjs";
import segmented_default from "./src/segmented2.mjs";
//#region ../../packages/components/segmented/index.ts
const ElSegmented = withInstall(segmented_default);
//#endregion
export { ElSegmented, ElSegmented as default, defaultProps, segmentedEmits, segmentedProps };
//# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
{"version":3,"file":"index.mjs","names":["Segmented"],"sources":["../../../../../packages/components/segmented/index.ts"],"sourcesContent":["import { withInstall } from '@element-plus/utils'\nimport Segmented from './src/segmented.vue'\n\nexport const ElSegmented = withInstall(Segmented)\nexport default ElSegmented\n\nexport * from './src/segmented'\n"],"mappings":";;;;;AAGA,MAAa,cAAc,YAAYA,kBAAU"}
@@ -0,0 +1,92 @@
import { EpPropFinalized, EpPropMergeType } from "../../../utils/vue/props/types.js";
import { ComponentSize } from "../../../constants/size.js";
import "../../../utils/index.js";
import { Option } from "./types.js";
import { _default } from "./segmented.vue.js";
import * as vue from "vue";
import { ComponentInstance, ExtractPublicPropTypes } from "vue";
import { ComponentExposed } from "vue-component-type-helpers";
//#region ../../packages/components/segmented/src/segmented.d.ts
interface Props {
label?: string;
value?: string;
disabled?: string;
}
declare const defaultProps: Required<Props>;
interface SegmentedProps<T extends Option = Option> {
direction?: 'vertical' | 'horizontal';
/**
* @description options of segmented
*/
options?: T[];
/**
* @description binding value
*/
modelValue?: string | number | boolean;
/**
* @description configuration options, see the following table
*/
props?: Props;
/**
* @description fit width of parent content
*/
block?: boolean;
/**
* @description size of component
*/
size?: ComponentSize;
/**
* @description whether segmented is disabled
*/
disabled?: boolean;
/**
* @description whether to trigger form validation
*/
validateEvent?: boolean;
/**
* @description native input id
*/
id?: string;
/**
* @description native `name` attribute
*/
name?: string;
/**
* @description native `aria-label` attribute
*/
ariaLabel?: string;
}
/**
* @deprecated Removed after 3.0.0, Use `SegmentedProps` instead.
*/
declare const segmentedProps: {
ariaLabel: StringConstructor;
direction: EpPropFinalized<(new (...args: any[]) => "horizontal" | "vertical") | (() => "horizontal" | "vertical") | (((new (...args: any[]) => "horizontal" | "vertical") | (() => "horizontal" | "vertical")) | null)[], unknown, unknown, string, boolean>;
options: EpPropFinalized<(new (...args: any[]) => Option[]) | (() => Option[]) | (((new (...args: any[]) => Option[]) | (() => Option[])) | null)[], unknown, unknown, () => never[], boolean>;
modelValue: EpPropFinalized<(BooleanConstructor | NumberConstructor | StringConstructor)[], unknown, unknown, undefined, boolean>;
props: EpPropFinalized<(new (...args: any[]) => Props) | (() => Props) | (((new (...args: any[]) => Props) | (() => Props)) | null)[], unknown, unknown, () => Required<Props>, boolean>;
block: BooleanConstructor;
size: {
readonly type: vue.PropType<EpPropMergeType<StringConstructor, "" | "default" | "small" | "large", never>>;
readonly required: false;
readonly validator: ((val: unknown) => boolean) | undefined;
__epPropKey: true;
};
disabled: EpPropFinalized<BooleanConstructor, unknown, unknown, undefined, boolean>;
validateEvent: EpPropFinalized<BooleanConstructor, unknown, unknown, boolean, boolean>;
id: StringConstructor;
name: StringConstructor;
};
/**
* @deprecated Removed after 3.0.0, Use `SegmentedProps` instead.
*/
type SegmentedPropsPublic = ExtractPublicPropTypes<typeof segmentedProps>;
declare const segmentedEmits: {
"update:modelValue": (val: any) => val is string | number | boolean;
change: (val: any) => val is string | number | boolean;
};
type SegmentedEmits = typeof segmentedEmits;
type SegmentedInstance = ComponentInstance<typeof _default> & ComponentExposed<typeof _default>;
//#endregion
export { Props, SegmentedEmits, SegmentedInstance, SegmentedProps, SegmentedPropsPublic, defaultProps, segmentedEmits, segmentedProps };
@@ -0,0 +1,58 @@
import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from "../../../constants/event.mjs";
import { isBoolean, isNumber, isString } from "../../../utils/types.mjs";
import { buildProps, definePropType } from "../../../utils/vue/props/runtime.mjs";
import { useSizeProp } from "../../../hooks/use-size/index.mjs";
import { useAriaProps } from "../../../hooks/use-aria/index.mjs";
//#region ../../packages/components/segmented/src/segmented.ts
const defaultProps = {
label: "label",
value: "value",
disabled: "disabled"
};
/**
* @deprecated Removed after 3.0.0, Use `SegmentedProps` instead.
*/
const segmentedProps = buildProps({
direction: {
type: definePropType(String),
default: "horizontal"
},
options: {
type: definePropType(Array),
default: () => []
},
modelValue: {
type: [
String,
Number,
Boolean
],
default: void 0
},
props: {
type: definePropType(Object),
default: () => defaultProps
},
block: Boolean,
size: useSizeProp,
disabled: {
type: Boolean,
default: void 0
},
validateEvent: {
type: Boolean,
default: true
},
id: String,
name: String,
...useAriaProps(["ariaLabel"])
});
const segmentedEmits = {
[UPDATE_MODEL_EVENT]: (val) => isString(val) || isNumber(val) || isBoolean(val),
[CHANGE_EVENT]: (val) => isString(val) || isNumber(val) || isBoolean(val)
};
//#endregion
export { defaultProps, segmentedEmits, segmentedProps };
//# sourceMappingURL=segmented.mjs.map
@@ -0,0 +1 @@
{"version":3,"file":"segmented.mjs","names":[],"sources":["../../../../../../packages/components/segmented/src/segmented.ts"],"sourcesContent":["import {\n buildProps,\n definePropType,\n isBoolean,\n isNumber,\n isString,\n} from '@element-plus/utils'\nimport { useAriaProps, useSizeProp } from '@element-plus/hooks'\nimport { CHANGE_EVENT, UPDATE_MODEL_EVENT } from '@element-plus/constants'\n\nimport type { Option } from './types'\nimport type { ComponentInstance, ExtractPublicPropTypes } from 'vue'\nimport type { ComponentExposed } from 'vue-component-type-helpers'\nimport type { ComponentSize } from '@element-plus/constants'\nimport type Segmented from './segmented.vue'\n\nexport interface Props {\n label?: string\n value?: string\n disabled?: string\n}\n\nexport const defaultProps: Required<Props> = {\n label: 'label',\n value: 'value',\n disabled: 'disabled',\n}\n\nexport interface SegmentedProps<T extends Option = Option> {\n direction?: 'vertical' | 'horizontal'\n /**\n * @description options of segmented\n */\n options?: T[]\n /**\n * @description binding value\n */\n modelValue?: string | number | boolean\n /**\n * @description configuration options, see the following table\n */\n props?: Props\n /**\n * @description fit width of parent content\n */\n block?: boolean\n /**\n * @description size of component\n */\n size?: ComponentSize\n /**\n * @description whether segmented is disabled\n */\n disabled?: boolean\n /**\n * @description whether to trigger form validation\n */\n validateEvent?: boolean\n /**\n * @description native input id\n */\n id?: string\n /**\n * @description native `name` attribute\n */\n name?: string\n /**\n * @description native `aria-label` attribute\n */\n ariaLabel?: string\n}\n\n/**\n * @deprecated Removed after 3.0.0, Use `SegmentedProps` instead.\n */\nexport const segmentedProps = buildProps({\n direction: {\n type: definePropType<'vertical' | 'horizontal'>(String),\n default: 'horizontal',\n },\n /**\n * @description options of segmented\n */\n options: {\n type: definePropType<Option[]>(Array),\n default: () => [],\n },\n /**\n * @description binding value\n */\n modelValue: {\n type: [String, Number, Boolean],\n default: undefined,\n },\n /**\n * @description configuration options, see the following table\n */\n props: {\n type: definePropType<Props>(Object),\n default: () => defaultProps,\n },\n /**\n * @description fit width of parent content\n */\n block: Boolean,\n /**\n * @description size of component\n */\n size: useSizeProp,\n /**\n * @description whether segmented is disabled\n */\n disabled: {\n type: Boolean,\n default: undefined,\n },\n /**\n * @description whether to trigger form validation\n */\n validateEvent: {\n type: Boolean,\n default: true,\n },\n /**\n * @description native input id\n */\n id: String,\n /**\n * @description native `name` attribute\n */\n name: String,\n ...useAriaProps(['ariaLabel']),\n})\n\n/**\n * @deprecated Removed after 3.0.0, Use `SegmentedProps` instead.\n */\nexport type SegmentedPropsPublic = ExtractPublicPropTypes<typeof segmentedProps>\n\nexport const segmentedEmits = {\n [UPDATE_MODEL_EVENT]: (val: any) =>\n isString(val) || isNumber(val) || isBoolean(val),\n [CHANGE_EVENT]: (val: any) =>\n isString(val) || isNumber(val) || isBoolean(val),\n}\nexport type SegmentedEmits = typeof segmentedEmits\n\nexport type SegmentedInstance = ComponentInstance<typeof Segmented> &\n ComponentExposed<typeof Segmented>\n"],"mappings":";;;;;;;AAsBA,MAAa,eAAgC;CAC3C,OAAO;CACP,OAAO;CACP,UAAU;CACX;;;;AAiDD,MAAa,iBAAiB,WAAW;CACvC,WAAW;EACT,MAAM,eAA0C,OAAO;EACvD,SAAS;EACV;CAID,SAAS;EACP,MAAM,eAAyB,MAAM;EACrC,eAAe,EAAE;EAClB;CAID,YAAY;EACV,MAAM;GAAC;GAAQ;GAAQ;GAAQ;EAC/B,SAAS;EACV;CAID,OAAO;EACL,MAAM,eAAsB,OAAO;EACnC,eAAe;EAChB;CAID,OAAO;CAIP,MAAM;CAIN,UAAU;EACR,MAAM;EACN,SAAS;EACV;CAID,eAAe;EACb,MAAM;EACN,SAAS;EACV;CAID,IAAI;CAIJ,MAAM;CACN,GAAG,aAAa,CAAC,YAAY,CAAC;CAC/B,CAAC;AAOF,MAAa,iBAAiB;EAC3B,sBAAsB,QACrB,SAAS,IAAI,IAAI,SAAS,IAAI,IAAI,UAAU,IAAI;EACjD,gBAAgB,QACf,SAAS,IAAI,IAAI,SAAS,IAAI,IAAI,UAAU,IAAI;CACnD"}
@@ -0,0 +1,29 @@
import { Option } from "./types.js";
import { SegmentedProps } from "./segmented.js";
import * as vue from "vue";
//#region ../../packages/components/segmented/src/segmented.vue.d.ts
declare const __VLS_export: <T extends Option = Option>(__VLS_props: NonNullable<Awaited<typeof __VLS_setup>>["props"], __VLS_ctx?: __VLS_PrettifyLocal<Pick<NonNullable<Awaited<typeof __VLS_setup>>, "attrs" | "emit" | "slots">>, __VLS_exposed?: NonNullable<Awaited<typeof __VLS_setup>>["expose"], __VLS_setup?: Promise<{
props: vue.PublicProps & __VLS_PrettifyLocal<SegmentedProps<T> & {
onChange?: ((val: any) => any) | undefined;
"onUpdate:modelValue"?: ((val: any) => any) | undefined;
}> & (typeof globalThis extends {
__VLS_PROPS_FALLBACK: infer P;
} ? P : {});
expose: (exposed: {}) => void;
attrs: any;
slots: {
default?: (props: {
item: T;
}) => any;
};
emit: ((event: "change", val: any) => void) & ((event: "update:modelValue", val: any) => void);
}>) => vue.VNode<vue.RendererNode, vue.RendererElement, {
[key: string]: any;
}> & {
__ctx?: Awaited<typeof __VLS_setup>;
};
declare const _default: typeof __VLS_export;
type __VLS_PrettifyLocal<T> = (T extends any ? { [K in keyof T]: T[K] } : { [K in keyof T as K]: T[K] }) & {};
//#endregion
export { _default };
@@ -0,0 +1,161 @@
import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from "../../../constants/event.mjs";
import { isObject } from "../../../utils/types.mjs";
import { debugWarn } from "../../../utils/error.mjs";
import { useNamespace } from "../../../hooks/use-namespace/index.mjs";
import { useId } from "../../../hooks/use-id/index.mjs";
import { useFormDisabled, useFormSize } from "../../form/src/hooks/use-form-common-props.mjs";
import { useFormItem, useFormItemInputId } from "../../form/src/hooks/use-form-item.mjs";
import { defaultProps, segmentedEmits, segmentedProps } from "./segmented.mjs";
import { useActiveElement, useResizeObserver } from "@vueuse/core";
import { Fragment, computed, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, defineComponent, normalizeClass, normalizeStyle, openBlock, reactive, ref, renderList, renderSlot, toDisplayString, unref, watch } from "vue";
//#region ../../packages/components/segmented/src/segmented.vue?vue&type=script&setup=true&lang.ts
const _hoisted_1 = [
"id",
"aria-label",
"aria-labelledby"
];
const _hoisted_2 = [
"name",
"disabled",
"checked",
"onChange"
];
var segmented_vue_vue_type_script_setup_true_lang_default = /* @__PURE__ */ defineComponent({
name: "ElSegmented",
__name: "segmented",
props: segmentedProps,
emits: segmentedEmits,
setup(__props, { emit: __emit }) {
const props = __props;
const emit = __emit;
const ns = useNamespace("segmented");
const segmentedId = useId();
const segmentedSize = useFormSize();
const _disabled = useFormDisabled();
const { formItem } = useFormItem();
const { inputId, isLabeledByFormItem } = useFormItemInputId(props, { formItemContext: formItem });
const segmentedRef = ref(null);
const activeElement = useActiveElement();
const state = reactive({
isInit: false,
width: 0,
height: 0,
translateX: 0,
translateY: 0,
focusVisible: false
});
const handleChange = (evt, item) => {
const value = getValue(item);
emit(UPDATE_MODEL_EVENT, value);
emit(CHANGE_EVENT, value);
evt.target.checked = value === props.modelValue;
};
const aliasProps = computed(() => ({
...defaultProps,
...props.props
}));
const getValue = (item) => {
return isObject(item) ? item[aliasProps.value.value] : item;
};
const getLabel = (item) => {
return isObject(item) ? item[aliasProps.value.label] : item;
};
const getDisabled = (item) => {
return !!(_disabled.value || (isObject(item) ? item[aliasProps.value.disabled] : false));
};
const getSelected = (item) => {
return props.modelValue === getValue(item);
};
const getOption = (value) => {
return props.options.find((item) => getValue(item) === value);
};
const getItemCls = (item) => {
return [
ns.e("item"),
ns.is("selected", getSelected(item)),
ns.is("disabled", getDisabled(item))
];
};
const updateSelect = () => {
if (!segmentedRef.value) return;
const selectedItem = segmentedRef.value.querySelector(".is-selected");
const selectedItemInput = segmentedRef.value.querySelector(".is-selected input");
if (!selectedItem || !selectedItemInput) {
state.width = 0;
state.height = 0;
state.translateX = 0;
state.translateY = 0;
state.focusVisible = false;
return;
}
state.isInit = true;
if (props.direction === "vertical") {
state.height = selectedItem.offsetHeight;
state.translateY = selectedItem.offsetTop;
} else {
state.width = selectedItem.offsetWidth;
state.translateX = selectedItem.offsetLeft;
}
try {
state.focusVisible = selectedItemInput.matches(":focus-visible");
} catch {}
};
const segmentedCls = computed(() => [
ns.b(),
ns.m(segmentedSize.value),
ns.is("block", props.block)
]);
const selectedStyle = computed(() => ({
width: props.direction === "vertical" ? "100%" : `${state.width}px`,
height: props.direction === "vertical" ? `${state.height}px` : "100%",
transform: props.direction === "vertical" ? `translateY(${state.translateY}px)` : `translateX(${state.translateX}px)`,
display: state.isInit ? "block" : "none"
}));
const selectedCls = computed(() => [
ns.e("item-selected"),
ns.is("disabled", getDisabled(getOption(props.modelValue))),
ns.is("focus-visible", state.focusVisible)
]);
const name = computed(() => {
return props.name || segmentedId.value;
});
useResizeObserver(segmentedRef, updateSelect);
watch(activeElement, updateSelect);
watch(() => props.modelValue, () => {
updateSelect();
if (props.validateEvent) formItem?.validate?.("change").catch((err) => debugWarn(err));
}, { flush: "post" });
return (_ctx, _cache) => {
return __props.options.length ? (openBlock(), createElementBlock("div", {
key: 0,
id: unref(inputId),
ref_key: "segmentedRef",
ref: segmentedRef,
class: normalizeClass(segmentedCls.value),
role: "radiogroup",
"aria-label": !unref(isLabeledByFormItem) ? __props.ariaLabel || "segmented" : void 0,
"aria-labelledby": unref(isLabeledByFormItem) ? unref(formItem).labelId : void 0
}, [createElementVNode("div", { class: normalizeClass([unref(ns).e("group"), unref(ns).m(__props.direction)]) }, [createElementVNode("div", {
style: normalizeStyle(selectedStyle.value),
class: normalizeClass(selectedCls.value)
}, null, 6), (openBlock(true), createElementBlock(Fragment, null, renderList(__props.options, (item, index) => {
return openBlock(), createElementBlock("label", {
key: index,
class: normalizeClass(getItemCls(item))
}, [createElementVNode("input", {
class: normalizeClass(unref(ns).e("item-input")),
type: "radio",
name: name.value,
disabled: getDisabled(item),
checked: getSelected(item),
onChange: ($event) => handleChange($event, item)
}, null, 42, _hoisted_2), createElementVNode("div", { class: normalizeClass(unref(ns).e("item-label")) }, [renderSlot(_ctx.$slots, "default", { item }, () => [createTextVNode(toDisplayString(getLabel(item)), 1)])], 2)], 2);
}), 128))], 2)], 10, _hoisted_1)) : createCommentVNode("v-if", true);
};
}
});
//#endregion
export { segmented_vue_vue_type_script_setup_true_lang_default as default };
//# sourceMappingURL=segmented.vue_vue_type_script_setup_true_lang.mjs.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
import segmented_vue_vue_type_script_setup_true_lang_default from "./segmented.vue_vue_type_script_setup_true_lang.mjs";
//#region ../../packages/components/segmented/src/segmented.vue
var segmented_default = segmented_vue_vue_type_script_setup_true_lang_default;
//#endregion
export { segmented_default as default };
//# sourceMappingURL=segmented2.mjs.map
File diff suppressed because one or more lines are too long
@@ -0,0 +1,4 @@
//#region ../../packages/components/segmented/src/types.d.ts
type Option = Record<string, any> | string | number | boolean;
//#endregion
export { Option };
@@ -0,0 +1,2 @@
import "../../base/style/css.mjs";
import "element-plus/theme-chalk/el-segmented.css";
@@ -0,0 +1,2 @@
import "../../base/style/index.mjs";
import "element-plus/theme-chalk/src/segmented.scss";