@veltra/utils 1.1.1 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,27 @@
1
+ //#region src/dom/expand-transition.d.ts
2
+ interface ExpandTransitionOptions {
3
+ transition?: string;
4
+ enterTransition?: string;
5
+ leaveTransition?: string;
6
+ opacity?: boolean;
7
+ }
8
+ declare class ExpandTransition {
9
+ private options;
10
+ private cleanupMap;
11
+ constructor(options?: ExpandTransitionOptions);
12
+ cancel(el: HTMLElement): void;
13
+ enter(el: HTMLElement): void;
14
+ afterEnter(el: HTMLElement): void;
15
+ beforeLeave(el: HTMLElement): void;
16
+ leave(el: HTMLElement): void;
17
+ afterLeave(el: HTMLElement): void;
18
+ expand(el: HTMLElement): void;
19
+ collapse(el: HTMLElement): void;
20
+ setExpanded(el: HTMLElement, expanded: boolean): void;
21
+ private animate;
22
+ private resetTransitionStyles;
23
+ private resetTemporaryStyles;
24
+ }
25
+ //#endregion
26
+ export { ExpandTransition, ExpandTransitionOptions };
27
+ //# sourceMappingURL=expand-transition.d.ts.map
@@ -0,0 +1,149 @@
1
+ import { removeStyles, setStyles } from "./style.js";
2
+ //#region src/dom/expand-transition.ts
3
+ function readVerticalPadding(el) {
4
+ const { paddingTop, paddingBottom } = getComputedStyle(el);
5
+ return {
6
+ paddingTop,
7
+ paddingBottom
8
+ };
9
+ }
10
+ function getTransition(options, type) {
11
+ return (type === "enter" ? options.enterTransition : options.leaveTransition) ?? options.transition ?? "height 0.25s cubic-bezier(0.4, 0, 0.2, 1)";
12
+ }
13
+ var ExpandTransition = class {
14
+ cleanupMap = /* @__PURE__ */ new Map();
15
+ constructor(options = {}) {
16
+ this.options = options;
17
+ }
18
+ cancel(el) {
19
+ this.cleanupMap.get(el)?.();
20
+ this.cleanupMap.delete(el);
21
+ }
22
+ enter(el) {
23
+ const height = el.scrollHeight;
24
+ const { paddingTop, paddingBottom } = readVerticalPadding(el);
25
+ setStyles(el, {
26
+ boxSizing: "border-box",
27
+ height: 0,
28
+ paddingTop: 0,
29
+ paddingBottom: 0,
30
+ overflow: "hidden",
31
+ transition: getTransition(this.options, "enter"),
32
+ willChange: this.options.opacity ? "height, opacity" : "height"
33
+ });
34
+ if (this.options.opacity) el.style.opacity = "0";
35
+ el.offsetHeight;
36
+ setStyles(el, {
37
+ height: `${height}px`,
38
+ paddingTop,
39
+ paddingBottom
40
+ });
41
+ if (this.options.opacity) el.style.opacity = "1";
42
+ }
43
+ afterEnter(el) {
44
+ this.resetTransitionStyles(el);
45
+ }
46
+ beforeLeave(el) {
47
+ const { paddingTop, paddingBottom } = readVerticalPadding(el);
48
+ setStyles(el, {
49
+ boxSizing: "border-box",
50
+ height: `${el.scrollHeight}px`,
51
+ paddingTop,
52
+ paddingBottom,
53
+ overflow: "hidden",
54
+ transition: getTransition(this.options, "leave"),
55
+ willChange: this.options.opacity ? "height, opacity" : "height"
56
+ });
57
+ if (this.options.opacity) el.style.opacity = "1";
58
+ }
59
+ leave(el) {
60
+ el.offsetHeight;
61
+ setStyles(el, {
62
+ height: 0,
63
+ paddingTop: 0,
64
+ paddingBottom: 0
65
+ });
66
+ if (this.options.opacity) el.style.opacity = "0";
67
+ }
68
+ afterLeave(el) {
69
+ this.resetTransitionStyles(el);
70
+ }
71
+ expand(el) {
72
+ this.animate(el, true);
73
+ }
74
+ collapse(el) {
75
+ this.animate(el, false);
76
+ }
77
+ setExpanded(el, expanded) {
78
+ this.cancel(el);
79
+ this.resetTemporaryStyles(el);
80
+ el.style.overflow = "hidden";
81
+ el.style.height = expanded ? "auto" : "0px";
82
+ }
83
+ animate(el, expanded) {
84
+ this.cancel(el);
85
+ const startHeight = el.offsetHeight;
86
+ const endHeight = expanded ? el.scrollHeight : 0;
87
+ const { paddingTop, paddingBottom } = readVerticalPadding(el);
88
+ if (startHeight === endHeight) {
89
+ this.setExpanded(el, expanded);
90
+ return;
91
+ }
92
+ setStyles(el, {
93
+ boxSizing: "border-box",
94
+ height: `${startHeight}px`,
95
+ paddingTop,
96
+ paddingBottom,
97
+ overflow: "hidden",
98
+ transition: getTransition(this.options, expanded ? "enter" : "leave"),
99
+ willChange: "height"
100
+ });
101
+ el.offsetHeight;
102
+ setStyles(el, {
103
+ height: `${endHeight}px`,
104
+ paddingTop: expanded ? paddingTop : 0,
105
+ paddingBottom: expanded ? paddingBottom : 0
106
+ });
107
+ const cleanup = () => {
108
+ el.removeEventListener("transitionend", onEnd);
109
+ el.removeEventListener("transitioncancel", onEnd);
110
+ this.cleanupMap.delete(el);
111
+ };
112
+ const onEnd = (e) => {
113
+ if (e.target !== el || e.propertyName !== "height") return;
114
+ cleanup();
115
+ this.resetTemporaryStyles(el);
116
+ el.style.overflow = "hidden";
117
+ el.style.height = expanded ? "auto" : "0px";
118
+ };
119
+ el.addEventListener("transitionend", onEnd);
120
+ el.addEventListener("transitioncancel", onEnd);
121
+ this.cleanupMap.set(el, cleanup);
122
+ }
123
+ resetTransitionStyles(el) {
124
+ removeStyles(el, [
125
+ "box-sizing",
126
+ "height",
127
+ "padding-top",
128
+ "padding-bottom",
129
+ "overflow",
130
+ "transition",
131
+ "opacity",
132
+ "will-change"
133
+ ]);
134
+ }
135
+ resetTemporaryStyles(el) {
136
+ removeStyles(el, [
137
+ "box-sizing",
138
+ "padding-top",
139
+ "padding-bottom",
140
+ "transition",
141
+ "opacity",
142
+ "will-change"
143
+ ]);
144
+ }
145
+ };
146
+ //#endregion
147
+ export { ExpandTransition };
148
+
149
+ //# sourceMappingURL=expand-transition.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expand-transition.js","names":[],"sources":["../../src/dom/expand-transition.ts"],"sourcesContent":["import { removeStyles, setStyles } from './style'\n\nexport interface ExpandTransitionOptions {\n transition?: string\n enterTransition?: string\n leaveTransition?: string\n opacity?: boolean\n}\n\nfunction readVerticalPadding(el: HTMLElement) {\n const { paddingTop, paddingBottom } = getComputedStyle(el)\n return { paddingTop, paddingBottom }\n}\n\nfunction getTransition(options: ExpandTransitionOptions, type: 'enter' | 'leave') {\n return (\n (type === 'enter' ? options.enterTransition : options.leaveTransition) ??\n options.transition ??\n 'height 0.25s cubic-bezier(0.4, 0, 0.2, 1)'\n )\n}\n\nexport class ExpandTransition {\n private cleanupMap = new Map<HTMLElement, () => void>()\n\n constructor(private options: ExpandTransitionOptions = {}) {}\n\n cancel(el: HTMLElement) {\n this.cleanupMap.get(el)?.()\n this.cleanupMap.delete(el)\n }\n\n enter(el: HTMLElement) {\n const height = el.scrollHeight\n const { paddingTop, paddingBottom } = readVerticalPadding(el)\n\n setStyles(el, {\n boxSizing: 'border-box',\n height: 0,\n paddingTop: 0,\n paddingBottom: 0,\n overflow: 'hidden',\n transition: getTransition(this.options, 'enter'),\n willChange: this.options.opacity ? 'height, opacity' : 'height'\n })\n\n if (this.options.opacity) {\n el.style.opacity = '0'\n }\n\n void el.offsetHeight\n\n setStyles(el, { height: `${height}px`, paddingTop, paddingBottom })\n\n if (this.options.opacity) {\n el.style.opacity = '1'\n }\n }\n\n afterEnter(el: HTMLElement) {\n this.resetTransitionStyles(el)\n }\n\n beforeLeave(el: HTMLElement) {\n const { paddingTop, paddingBottom } = readVerticalPadding(el)\n\n setStyles(el, {\n boxSizing: 'border-box',\n height: `${el.scrollHeight}px`,\n paddingTop,\n paddingBottom,\n overflow: 'hidden',\n transition: getTransition(this.options, 'leave'),\n willChange: this.options.opacity ? 'height, opacity' : 'height'\n })\n\n if (this.options.opacity) {\n el.style.opacity = '1'\n }\n }\n\n leave(el: HTMLElement) {\n void el.offsetHeight\n\n setStyles(el, { height: 0, paddingTop: 0, paddingBottom: 0 })\n\n if (this.options.opacity) {\n el.style.opacity = '0'\n }\n }\n\n afterLeave(el: HTMLElement) {\n this.resetTransitionStyles(el)\n }\n\n expand(el: HTMLElement) {\n this.animate(el, true)\n }\n\n collapse(el: HTMLElement) {\n this.animate(el, false)\n }\n\n setExpanded(el: HTMLElement, expanded: boolean) {\n this.cancel(el)\n this.resetTemporaryStyles(el)\n el.style.overflow = 'hidden'\n el.style.height = expanded ? 'auto' : '0px'\n }\n\n private animate(el: HTMLElement, expanded: boolean) {\n this.cancel(el)\n\n const startHeight = el.offsetHeight\n const endHeight = expanded ? el.scrollHeight : 0\n const { paddingTop, paddingBottom } = readVerticalPadding(el)\n\n if (startHeight === endHeight) {\n this.setExpanded(el, expanded)\n return\n }\n\n setStyles(el, {\n boxSizing: 'border-box',\n height: `${startHeight}px`,\n paddingTop,\n paddingBottom,\n overflow: 'hidden',\n transition: getTransition(this.options, expanded ? 'enter' : 'leave'),\n willChange: 'height'\n })\n\n void el.offsetHeight\n\n setStyles(el, {\n height: `${endHeight}px`,\n paddingTop: expanded ? paddingTop : 0,\n paddingBottom: expanded ? paddingBottom : 0\n })\n\n const cleanup = () => {\n el.removeEventListener('transitionend', onEnd)\n el.removeEventListener('transitioncancel', onEnd)\n this.cleanupMap.delete(el)\n }\n\n const onEnd = (e: TransitionEvent) => {\n if (e.target !== el || e.propertyName !== 'height') return\n cleanup()\n this.resetTemporaryStyles(el)\n el.style.overflow = 'hidden'\n el.style.height = expanded ? 'auto' : '0px'\n }\n\n el.addEventListener('transitionend', onEnd)\n el.addEventListener('transitioncancel', onEnd)\n this.cleanupMap.set(el, cleanup)\n }\n\n private resetTransitionStyles(el: HTMLElement) {\n removeStyles(el, [\n 'box-sizing',\n 'height',\n 'padding-top',\n 'padding-bottom',\n 'overflow',\n 'transition',\n 'opacity',\n 'will-change'\n ])\n }\n\n private resetTemporaryStyles(el: HTMLElement) {\n removeStyles(el, [\n 'box-sizing',\n 'padding-top',\n 'padding-bottom',\n 'transition',\n 'opacity',\n 'will-change'\n ])\n }\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { BEM, BEMFactory, makeBEM } from "./helper/make-bem.js";
2
2
  import { CLS_PREFIX, FORM_EMPTY_CONTENT, NAME_SPACE } from "./shared/constants.js";
3
3
  import { addClass, bem, removeClass } from "./dom/class-name.js";
4
+ import { ExpandTransition, ExpandTransitionOptions } from "./dom/expand-transition.js";
4
5
  import { getHighlightChunks } from "./dom/highlight.js";
5
6
  import { getNearestScrollParent, getScrollParents, scrollIntoContainerView } from "./dom/position.js";
6
7
  import { removeStyles, setStyles, withUnit } from "./dom/style.js";
@@ -15,5 +16,5 @@ import { extractNormalVNodes, isComment, isFragment, isTemplate, isTextNode, sha
15
16
  import { middleProxy } from "./reactive/proxy.js";
16
17
  import { DeconstructValue, DefineEvent, Index, Null, RenderReturn, Undef } from "./types/helper.js";
17
18
  import { BreakpointName, ColorType, ComponentProps, ComponentSize, FormComponentProps, PropsWithServerQuery } from "./types/component-common.js";
18
- import { FormContextInjectProps } from "./types/form-context.js";
19
- export { AnimeConfig, BEM, BEMFactory, BreakpointName, CLS_PREFIX, ColorType, ComponentProps, ComponentSize, Data, DeconstructValue, DefineEvent, FORM_EMPTY_CONTENT, FormComponentProps, FormContextInjectProps, Index, NAME_SPACE, Null, PresetRule, PropsWithServerQuery, RenderReturn, Tween, TweenConfig, Undef, ValidateRule, Validator, ValidatorConfig, addClass, bem, createIncrease, createToggle, extractNormalVNodes, getHighlightChunks, getNearestScrollParent, getScrollParents, isComment, isFragment, isTemplate, isTextNode, makeBEM, middleProxy, nextFrame, removeClass, removeStyles, scrollIntoContainerView, setStyles, shallowComputed, withUnit, zIndex };
19
+ import { FormContextModel, FormContextProps, injectFormContext, provideFormContext } from "./types/form-context.js";
20
+ export { AnimeConfig, BEM, BEMFactory, BreakpointName, CLS_PREFIX, ColorType, ComponentProps, ComponentSize, Data, DeconstructValue, DefineEvent, ExpandTransition, ExpandTransitionOptions, FORM_EMPTY_CONTENT, FormComponentProps, FormContextModel, FormContextProps, Index, NAME_SPACE, Null, PresetRule, PropsWithServerQuery, RenderReturn, Tween, TweenConfig, Undef, ValidateRule, Validator, ValidatorConfig, addClass, bem, createIncrease, createToggle, extractNormalVNodes, getHighlightChunks, getNearestScrollParent, getScrollParents, injectFormContext, isComment, isFragment, isTemplate, isTextNode, makeBEM, middleProxy, nextFrame, provideFormContext, removeClass, removeStyles, scrollIntoContainerView, setStyles, shallowComputed, withUnit, zIndex };
package/dist/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { makeBEM } from "./helper/make-bem.js";
2
2
  import { CLS_PREFIX, FORM_EMPTY_CONTENT, NAME_SPACE } from "./shared/constants.js";
3
3
  import { addClass, bem, removeClass } from "./dom/class-name.js";
4
+ import { removeStyles, setStyles, withUnit } from "./dom/style.js";
5
+ import { ExpandTransition } from "./dom/expand-transition.js";
4
6
  import { getHighlightChunks } from "./dom/highlight.js";
5
7
  import { getNearestScrollParent, getScrollParents, scrollIntoContainerView } from "./dom/position.js";
6
- import { removeStyles, setStyles, withUnit } from "./dom/style.js";
7
8
  import { createIncrease } from "./helper/create-increase.js";
8
9
  import { zIndex } from "./dom/z-index.js";
9
10
  import { Validator } from "./form/validate.js";
@@ -12,4 +13,5 @@ import { Tween } from "./helper/tween.js";
12
13
  import { nextFrame } from "./helper/frame.js";
13
14
  import { extractNormalVNodes, isComment, isFragment, isTemplate, isTextNode, shallowComputed } from "./helper/vue.js";
14
15
  import { middleProxy } from "./reactive/proxy.js";
15
- export { CLS_PREFIX, FORM_EMPTY_CONTENT, NAME_SPACE, Tween, Validator, addClass, bem, createIncrease, createToggle, extractNormalVNodes, getHighlightChunks, getNearestScrollParent, getScrollParents, isComment, isFragment, isTemplate, isTextNode, makeBEM, middleProxy, nextFrame, removeClass, removeStyles, scrollIntoContainerView, setStyles, shallowComputed, withUnit, zIndex };
16
+ import { injectFormContext, provideFormContext } from "./types/form-context.js";
17
+ export { CLS_PREFIX, ExpandTransition, FORM_EMPTY_CONTENT, NAME_SPACE, Tween, Validator, addClass, bem, createIncrease, createToggle, extractNormalVNodes, getHighlightChunks, getNearestScrollParent, getScrollParents, injectFormContext, isComment, isFragment, isTemplate, isTextNode, makeBEM, middleProxy, nextFrame, provideFormContext, removeClass, removeStyles, scrollIntoContainerView, setStyles, shallowComputed, withUnit, zIndex };
@@ -1,6 +1,34 @@
1
+ import { ComponentSize } from "./component-common.js";
2
+
1
3
  //#region src/types/form-context.d.ts
2
- /** 表单 provide/inject 上下文(完整 props 见 @veltra/desktop/types/form) */
3
- type FormContextInjectProps = Record<string, unknown>;
4
+ interface FormContextModel {
5
+ errors: Map<any, string[] | undefined>;
6
+ fields: Record<string, {
7
+ required?: unknown;
8
+ }>;
9
+ }
10
+ interface FormContextProps {
11
+ /** 表单列宽 */
12
+ labelWidth?: string | number;
13
+ /** 表单尺寸 */
14
+ size?: ComponentSize;
15
+ /** 是否禁用 */
16
+ disabled?: boolean;
17
+ /** 是否只读 */
18
+ readonly?: boolean;
19
+ /** 是否隐藏提示 */
20
+ noTips?: boolean;
21
+ /** 表单数据模型 */
22
+ model?: FormContextModel;
23
+ }
24
+ type FormPropsLike = Partial<FormContextProps> & Record<string, any>;
25
+ type DIContext = {
26
+ /** 表单属性 */formProps: FormPropsLike;
27
+ };
28
+ declare function provideFormContext<T extends FormPropsLike>(props: T): void;
29
+ declare function injectFormContext(): {
30
+ /** 是否在表单中 */inForm: boolean;
31
+ } & Partial<DIContext>;
4
32
  //#endregion
5
- export { FormContextInjectProps };
33
+ export { FormContextModel, FormContextProps, injectFormContext, provideFormContext };
6
34
  //# sourceMappingURL=form-context.d.ts.map
@@ -0,0 +1,17 @@
1
+ import { inject, provide } from "vue";
2
+ //#region src/types/form-context.ts
3
+ const FormComponentDIKey = Symbol("FormComponentDIKey");
4
+ function provideFormContext(props) {
5
+ if (props) provide(FormComponentDIKey, { formProps: props });
6
+ }
7
+ function injectFormContext() {
8
+ const context = inject(FormComponentDIKey, void 0) || {};
9
+ return {
10
+ inForm: !!context,
11
+ ...context
12
+ };
13
+ }
14
+ //#endregion
15
+ export { injectFormContext, provideFormContext };
16
+
17
+ //# sourceMappingURL=form-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form-context.js","names":[],"sources":["../../src/types/form-context.ts"],"sourcesContent":["import { type InjectionKey, inject, provide } from 'vue'\n\nimport type { ComponentSize } from './component-common'\n\nexport interface FormContextModel {\n errors: Map<any, string[] | undefined>\n fields: Record<string, { required?: unknown }>\n}\n\nexport interface FormContextProps {\n /** 表单列宽 */\n labelWidth?: string | number\n /** 表单尺寸 */\n size?: ComponentSize\n /** 是否禁用 */\n disabled?: boolean\n /** 是否只读 */\n readonly?: boolean\n /** 是否隐藏提示 */\n noTips?: boolean\n /** 表单数据模型 */\n model?: FormContextModel\n}\n\ntype FormPropsLike = Partial<FormContextProps> & Record<string, any>\n\ntype DIContext = {\n /** 表单属性 */\n formProps: FormPropsLike\n}\n\nconst FormComponentDIKey: InjectionKey<DIContext> = Symbol('FormComponentDIKey')\n\nexport function provideFormContext<T extends FormPropsLike>(props: T): void {\n if (props) {\n provide(FormComponentDIKey, { formProps: props })\n }\n}\n\nexport function injectFormContext(): {\n /** 是否在表单中 */\n inForm: boolean\n} & Partial<DIContext> {\n const context = inject(FormComponentDIKey, undefined) || {}\n return { inForm: !!context, ...context }\n}\n"],"mappings":";;AA+BA,MAAM,qBAA8C,OAAO,qBAAqB;AAEhF,SAAgB,mBAA4C,OAAgB;AAC1E,KAAI,MACF,SAAQ,oBAAoB,EAAE,WAAW,OAAO,CAAC;;AAIrD,SAAgB,oBAGO;CACrB,MAAM,UAAU,OAAO,oBAAoB,KAAA,EAAU,IAAI,EAAE;AAC3D,QAAO;EAAE,QAAQ,CAAC,CAAC;EAAS,GAAG;EAAS"}
@@ -1,5 +1,5 @@
1
1
  import { Data, PresetRule, ValidateRule, ValidatorConfig } from "./utils/form/validate.js";
2
2
  import { DeconstructValue, DefineEvent, Index, Null, RenderReturn, Undef } from "./helper.js";
3
3
  import { BreakpointName, ColorType, ComponentProps, ComponentSize, FormComponentProps, PropsWithServerQuery } from "./component-common.js";
4
- import { FormContextInjectProps } from "./form-context.js";
5
- export { BreakpointName, ColorType, ComponentProps, ComponentSize, Data, DeconstructValue, DefineEvent, FormComponentProps, FormContextInjectProps, Index, Null, PresetRule, PropsWithServerQuery, RenderReturn, Undef, ValidateRule, ValidatorConfig };
4
+ import { FormContextModel, FormContextProps, injectFormContext, provideFormContext } from "./form-context.js";
5
+ export { BreakpointName, ColorType, ComponentProps, ComponentSize, Data, DeconstructValue, DefineEvent, FormComponentProps, FormContextModel, FormContextProps, Index, Null, PresetRule, PropsWithServerQuery, RenderReturn, Undef, ValidateRule, ValidatorConfig, injectFormContext, provideFormContext };
@@ -0,0 +1,2 @@
1
+ import { injectFormContext, provideFormContext } from "./form-context.js";
2
+ export { injectFormContext, provideFormContext };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veltra/utils",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "files": [
5
5
  "dist",
6
6
  "src"
@@ -0,0 +1,98 @@
1
+ // @vitest-environment happy-dom
2
+ import { ExpandTransition } from '../expand-transition'
3
+
4
+ function createPanel() {
5
+ const el = document.createElement('div')
6
+
7
+ el.style.paddingTop = '2px'
8
+ el.style.paddingBottom = '2px'
9
+ document.body.appendChild(el)
10
+
11
+ Object.defineProperties(el, {
12
+ offsetHeight: { configurable: true, value: 16 },
13
+ scrollHeight: { configurable: true, value: 44 }
14
+ })
15
+
16
+ return el
17
+ }
18
+
19
+ function fireHeightTransitionEnd(el: HTMLElement) {
20
+ const event = new Event('transitionend') as TransitionEvent
21
+ Object.defineProperty(event, 'propertyName', { configurable: true, value: 'height' })
22
+ el.dispatchEvent(event)
23
+ }
24
+
25
+ describe('ExpandTransition', () => {
26
+ it('drives Vue transition hooks with a measured border-box height', () => {
27
+ const el = createPanel()
28
+ const transition = new ExpandTransition({
29
+ enterTransition: 'height 0.25s ease, padding-top 0.25s ease, padding-bottom 0.25s ease',
30
+ leaveTransition: 'height 0.2s ease, padding-top 0.2s ease, padding-bottom 0.2s ease',
31
+ opacity: true
32
+ })
33
+
34
+ try {
35
+ transition.enter(el)
36
+
37
+ expect(el.style.height).toBe('44px')
38
+ expect(el.style.paddingTop).toBe('2px')
39
+ expect(el.style.paddingBottom).toBe('2px')
40
+ expect(el.style.boxSizing).toBe('border-box')
41
+ expect(el.style.opacity).toBe('1')
42
+
43
+ transition.beforeLeave(el)
44
+ transition.leave(el)
45
+
46
+ expect(el.style.height).toBe('0px')
47
+ expect(el.style.paddingTop).toBe('0px')
48
+ expect(el.style.paddingBottom).toBe('0px')
49
+ expect(el.style.boxSizing).toBe('border-box')
50
+ expect(el.style.opacity).toBe('0')
51
+ } finally {
52
+ el.remove()
53
+ }
54
+ })
55
+
56
+ it('keeps expanded imperative panels at auto height after transition end', () => {
57
+ const el = createPanel()
58
+ const transition = new ExpandTransition({ transition: 'height 0.25s ease' })
59
+
60
+ try {
61
+ transition.expand(el)
62
+
63
+ expect(el.style.height).toBe('44px')
64
+ expect(el.style.overflow).toBe('hidden')
65
+
66
+ fireHeightTransitionEnd(el)
67
+
68
+ expect(el.style.height).toBe('auto')
69
+ expect(el.style.transition).toBe('')
70
+ expect(el.style.willChange).toBe('')
71
+ } finally {
72
+ el.remove()
73
+ transition.cancel(el)
74
+ }
75
+ })
76
+
77
+ it('keeps collapsed imperative panels at zero height after transition end', () => {
78
+ const el = createPanel()
79
+ const transition = new ExpandTransition({ transition: 'height 0.25s ease' })
80
+
81
+ try {
82
+ transition.collapse(el)
83
+
84
+ expect(el.style.height).toBe('0px')
85
+ expect(el.style.paddingTop).toBe('0px')
86
+ expect(el.style.paddingBottom).toBe('0px')
87
+
88
+ fireHeightTransitionEnd(el)
89
+
90
+ expect(el.style.height).toBe('0px')
91
+ expect(el.style.transition).toBe('')
92
+ expect(el.style.willChange).toBe('')
93
+ } finally {
94
+ el.remove()
95
+ transition.cancel(el)
96
+ }
97
+ })
98
+ })
@@ -0,0 +1,183 @@
1
+ import { removeStyles, setStyles } from './style'
2
+
3
+ export interface ExpandTransitionOptions {
4
+ transition?: string
5
+ enterTransition?: string
6
+ leaveTransition?: string
7
+ opacity?: boolean
8
+ }
9
+
10
+ function readVerticalPadding(el: HTMLElement) {
11
+ const { paddingTop, paddingBottom } = getComputedStyle(el)
12
+ return { paddingTop, paddingBottom }
13
+ }
14
+
15
+ function getTransition(options: ExpandTransitionOptions, type: 'enter' | 'leave') {
16
+ return (
17
+ (type === 'enter' ? options.enterTransition : options.leaveTransition) ??
18
+ options.transition ??
19
+ 'height 0.25s cubic-bezier(0.4, 0, 0.2, 1)'
20
+ )
21
+ }
22
+
23
+ export class ExpandTransition {
24
+ private cleanupMap = new Map<HTMLElement, () => void>()
25
+
26
+ constructor(private options: ExpandTransitionOptions = {}) {}
27
+
28
+ cancel(el: HTMLElement) {
29
+ this.cleanupMap.get(el)?.()
30
+ this.cleanupMap.delete(el)
31
+ }
32
+
33
+ enter(el: HTMLElement) {
34
+ const height = el.scrollHeight
35
+ const { paddingTop, paddingBottom } = readVerticalPadding(el)
36
+
37
+ setStyles(el, {
38
+ boxSizing: 'border-box',
39
+ height: 0,
40
+ paddingTop: 0,
41
+ paddingBottom: 0,
42
+ overflow: 'hidden',
43
+ transition: getTransition(this.options, 'enter'),
44
+ willChange: this.options.opacity ? 'height, opacity' : 'height'
45
+ })
46
+
47
+ if (this.options.opacity) {
48
+ el.style.opacity = '0'
49
+ }
50
+
51
+ void el.offsetHeight
52
+
53
+ setStyles(el, { height: `${height}px`, paddingTop, paddingBottom })
54
+
55
+ if (this.options.opacity) {
56
+ el.style.opacity = '1'
57
+ }
58
+ }
59
+
60
+ afterEnter(el: HTMLElement) {
61
+ this.resetTransitionStyles(el)
62
+ }
63
+
64
+ beforeLeave(el: HTMLElement) {
65
+ const { paddingTop, paddingBottom } = readVerticalPadding(el)
66
+
67
+ setStyles(el, {
68
+ boxSizing: 'border-box',
69
+ height: `${el.scrollHeight}px`,
70
+ paddingTop,
71
+ paddingBottom,
72
+ overflow: 'hidden',
73
+ transition: getTransition(this.options, 'leave'),
74
+ willChange: this.options.opacity ? 'height, opacity' : 'height'
75
+ })
76
+
77
+ if (this.options.opacity) {
78
+ el.style.opacity = '1'
79
+ }
80
+ }
81
+
82
+ leave(el: HTMLElement) {
83
+ void el.offsetHeight
84
+
85
+ setStyles(el, { height: 0, paddingTop: 0, paddingBottom: 0 })
86
+
87
+ if (this.options.opacity) {
88
+ el.style.opacity = '0'
89
+ }
90
+ }
91
+
92
+ afterLeave(el: HTMLElement) {
93
+ this.resetTransitionStyles(el)
94
+ }
95
+
96
+ expand(el: HTMLElement) {
97
+ this.animate(el, true)
98
+ }
99
+
100
+ collapse(el: HTMLElement) {
101
+ this.animate(el, false)
102
+ }
103
+
104
+ setExpanded(el: HTMLElement, expanded: boolean) {
105
+ this.cancel(el)
106
+ this.resetTemporaryStyles(el)
107
+ el.style.overflow = 'hidden'
108
+ el.style.height = expanded ? 'auto' : '0px'
109
+ }
110
+
111
+ private animate(el: HTMLElement, expanded: boolean) {
112
+ this.cancel(el)
113
+
114
+ const startHeight = el.offsetHeight
115
+ const endHeight = expanded ? el.scrollHeight : 0
116
+ const { paddingTop, paddingBottom } = readVerticalPadding(el)
117
+
118
+ if (startHeight === endHeight) {
119
+ this.setExpanded(el, expanded)
120
+ return
121
+ }
122
+
123
+ setStyles(el, {
124
+ boxSizing: 'border-box',
125
+ height: `${startHeight}px`,
126
+ paddingTop,
127
+ paddingBottom,
128
+ overflow: 'hidden',
129
+ transition: getTransition(this.options, expanded ? 'enter' : 'leave'),
130
+ willChange: 'height'
131
+ })
132
+
133
+ void el.offsetHeight
134
+
135
+ setStyles(el, {
136
+ height: `${endHeight}px`,
137
+ paddingTop: expanded ? paddingTop : 0,
138
+ paddingBottom: expanded ? paddingBottom : 0
139
+ })
140
+
141
+ const cleanup = () => {
142
+ el.removeEventListener('transitionend', onEnd)
143
+ el.removeEventListener('transitioncancel', onEnd)
144
+ this.cleanupMap.delete(el)
145
+ }
146
+
147
+ const onEnd = (e: TransitionEvent) => {
148
+ if (e.target !== el || e.propertyName !== 'height') return
149
+ cleanup()
150
+ this.resetTemporaryStyles(el)
151
+ el.style.overflow = 'hidden'
152
+ el.style.height = expanded ? 'auto' : '0px'
153
+ }
154
+
155
+ el.addEventListener('transitionend', onEnd)
156
+ el.addEventListener('transitioncancel', onEnd)
157
+ this.cleanupMap.set(el, cleanup)
158
+ }
159
+
160
+ private resetTransitionStyles(el: HTMLElement) {
161
+ removeStyles(el, [
162
+ 'box-sizing',
163
+ 'height',
164
+ 'padding-top',
165
+ 'padding-bottom',
166
+ 'overflow',
167
+ 'transition',
168
+ 'opacity',
169
+ 'will-change'
170
+ ])
171
+ }
172
+
173
+ private resetTemporaryStyles(el: HTMLElement) {
174
+ removeStyles(el, [
175
+ 'box-sizing',
176
+ 'padding-top',
177
+ 'padding-bottom',
178
+ 'transition',
179
+ 'opacity',
180
+ 'will-change'
181
+ ])
182
+ }
183
+ }
package/src/index.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  export * from './dom/class-name'
2
2
 
3
+ export * from './dom/expand-transition'
4
+
3
5
  export * from './dom/highlight'
4
6
 
5
7
  export * from './dom/position'
@@ -1,2 +1,46 @@
1
- /** 表单 provide/inject 上下文(完整 props @veltra/desktop/types/form) */
2
- export type FormContextInjectProps = Record<string, unknown>
1
+ import { type InjectionKey, inject, provide } from 'vue'
2
+
3
+ import type { ComponentSize } from './component-common'
4
+
5
+ export interface FormContextModel {
6
+ errors: Map<any, string[] | undefined>
7
+ fields: Record<string, { required?: unknown }>
8
+ }
9
+
10
+ export interface FormContextProps {
11
+ /** 表单列宽 */
12
+ labelWidth?: string | number
13
+ /** 表单尺寸 */
14
+ size?: ComponentSize
15
+ /** 是否禁用 */
16
+ disabled?: boolean
17
+ /** 是否只读 */
18
+ readonly?: boolean
19
+ /** 是否隐藏提示 */
20
+ noTips?: boolean
21
+ /** 表单数据模型 */
22
+ model?: FormContextModel
23
+ }
24
+
25
+ type FormPropsLike = Partial<FormContextProps> & Record<string, any>
26
+
27
+ type DIContext = {
28
+ /** 表单属性 */
29
+ formProps: FormPropsLike
30
+ }
31
+
32
+ const FormComponentDIKey: InjectionKey<DIContext> = Symbol('FormComponentDIKey')
33
+
34
+ export function provideFormContext<T extends FormPropsLike>(props: T): void {
35
+ if (props) {
36
+ provide(FormComponentDIKey, { formProps: props })
37
+ }
38
+ }
39
+
40
+ export function injectFormContext(): {
41
+ /** 是否在表单中 */
42
+ inForm: boolean
43
+ } & Partial<DIContext> {
44
+ const context = inject(FormComponentDIKey, undefined) || {}
45
+ return { inForm: !!context, ...context }
46
+ }