@veltra/utils 1.5.0 → 1.6.0

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.
@@ -15,8 +15,13 @@ declare class ExpandTransition {
15
15
  beforeLeave(el: HTMLElement): void;
16
16
  leave(el: HTMLElement): void;
17
17
  afterLeave(el: HTMLElement): void;
18
- expand(el: HTMLElement): void;
19
- collapse(el: HTMLElement): void;
18
+ /**
19
+ * 展开动画;`onEnd` 在动画落定(含无需动画直接落定)后调用,
20
+ * 被新的动画打断时不调用
21
+ */
22
+ expand(el: HTMLElement, onEnd?: () => void): void;
23
+ /** 收起动画;`onEnd` 语义同 `expand` */
24
+ collapse(el: HTMLElement, onEnd?: () => void): void;
20
25
  setExpanded(el: HTMLElement, expanded: boolean): void;
21
26
  private animate;
22
27
  private resetTransitionStyles;
@@ -69,11 +69,16 @@ var ExpandTransition = class {
69
69
  afterLeave(el) {
70
70
  this.resetTransitionStyles(el);
71
71
  }
72
- expand(el) {
73
- this.animate(el, true);
72
+ /**
73
+ * 展开动画;`onEnd` 在动画落定(含无需动画直接落定)后调用,
74
+ * 被新的动画打断时不调用
75
+ */
76
+ expand(el, onEnd) {
77
+ this.animate(el, true, onEnd);
74
78
  }
75
- collapse(el) {
76
- this.animate(el, false);
79
+ /** 收起动画;`onEnd` 语义同 `expand` */
80
+ collapse(el, onEnd) {
81
+ this.animate(el, false, onEnd);
77
82
  }
78
83
  setExpanded(el, expanded) {
79
84
  this.cancel(el);
@@ -81,13 +86,14 @@ var ExpandTransition = class {
81
86
  el.style.overflow = "hidden";
82
87
  el.style.height = expanded ? "auto" : "0px";
83
88
  }
84
- animate(el, expanded) {
89
+ animate(el, expanded, onEnd) {
85
90
  this.cancel(el);
86
91
  const startHeight = el.offsetHeight;
87
92
  const endHeight = expanded ? el.scrollHeight : 0;
88
93
  const { paddingTop, paddingBottom } = readVerticalPadding(el);
89
94
  if (startHeight === endHeight) {
90
95
  this.setExpanded(el, expanded);
96
+ onEnd?.();
91
97
  return;
92
98
  }
93
99
  setStyles(el, {
@@ -106,19 +112,20 @@ var ExpandTransition = class {
106
112
  paddingBottom: expanded ? paddingBottom : 0
107
113
  });
108
114
  const cleanup = () => {
109
- el.removeEventListener("transitionend", onEnd);
110
- el.removeEventListener("transitioncancel", onEnd);
115
+ el.removeEventListener("transitionend", onEndHandler);
116
+ el.removeEventListener("transitioncancel", onEndHandler);
111
117
  this.cleanupMap.delete(el);
112
118
  };
113
- const onEnd = (e) => {
119
+ const onEndHandler = (e) => {
114
120
  if (e.target !== el || e.propertyName !== "height") return;
115
121
  cleanup();
116
122
  this.resetTemporaryStyles(el);
117
123
  el.style.overflow = "hidden";
118
124
  el.style.height = expanded ? "auto" : "0px";
125
+ onEnd?.();
119
126
  };
120
- el.addEventListener("transitionend", onEnd);
121
- el.addEventListener("transitioncancel", onEnd);
127
+ el.addEventListener("transitionend", onEndHandler);
128
+ el.addEventListener("transitioncancel", onEndHandler);
122
129
  this.cleanupMap.set(el, cleanup);
123
130
  }
124
131
  resetTransitionStyles(el) {
@@ -1 +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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"}
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 /**\n * 展开动画;`onEnd` 在动画落定(含无需动画直接落定)后调用,\n * 被新的动画打断时不调用\n */\n expand(el: HTMLElement, onEnd?: () => void) {\n this.animate(el, true, onEnd)\n }\n\n /** 收起动画;`onEnd` 语义同 `expand` */\n collapse(el: HTMLElement, onEnd?: () => void) {\n this.animate(el, false, onEnd)\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, onEnd?: () => void) {\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 onEnd?.()\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', onEndHandler)\n el.removeEventListener('transitioncancel', onEndHandler)\n this.cleanupMap.delete(el)\n }\n\n const onEndHandler = (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 onEnd?.()\n }\n\n el.addEventListener('transitionend', onEndHandler)\n el.addEventListener('transitioncancel', onEndHandler)\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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@veltra/utils",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "files": [
5
5
  "dist",
6
6
  "src"
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from 'vitest'
1
+ import { describe, expect, it, vi } from 'vitest'
2
2
 
3
3
  import { ExpandTransition } from '../expand-transition'
4
4
 
@@ -96,4 +96,54 @@ describe('ExpandTransition', () => {
96
96
  transition.cancel(el)
97
97
  }
98
98
  })
99
+
100
+ it('invokes onEnd after the transition settles', () => {
101
+ const el = createPanel()
102
+ const transition = new ExpandTransition({ transition: 'height 0.25s ease' })
103
+ const onEnd = vi.fn()
104
+
105
+ try {
106
+ transition.collapse(el, onEnd)
107
+ expect(onEnd).not.toHaveBeenCalled()
108
+
109
+ fireHeightTransitionEnd(el)
110
+ expect(onEnd).toHaveBeenCalledTimes(1)
111
+ } finally {
112
+ el.remove()
113
+ transition.cancel(el)
114
+ }
115
+ })
116
+
117
+ it('does not invoke onEnd when interrupted by a new animation', () => {
118
+ const el = createPanel()
119
+ const transition = new ExpandTransition({ transition: 'height 0.25s ease' })
120
+ const onEnd = vi.fn()
121
+
122
+ try {
123
+ transition.collapse(el, onEnd)
124
+ // 新动画打断旧动画:旧 onEnd 不应再触发
125
+ transition.expand(el)
126
+ fireHeightTransitionEnd(el)
127
+ expect(onEnd).not.toHaveBeenCalled()
128
+ } finally {
129
+ el.remove()
130
+ transition.cancel(el)
131
+ }
132
+ })
133
+
134
+ it('invokes onEnd immediately when no animation is needed', () => {
135
+ const el = document.createElement('div')
136
+ document.body.appendChild(el)
137
+ const transition = new ExpandTransition({ transition: 'height 0.25s ease' })
138
+ const onEnd = vi.fn()
139
+
140
+ try {
141
+ // 高度已为 0(happy-dom 无布局),收起无需动画,onEnd 同步调用
142
+ transition.collapse(el, onEnd)
143
+ expect(onEnd).toHaveBeenCalledTimes(1)
144
+ } finally {
145
+ el.remove()
146
+ transition.cancel(el)
147
+ }
148
+ })
99
149
  })
@@ -93,12 +93,17 @@ export class ExpandTransition {
93
93
  this.resetTransitionStyles(el)
94
94
  }
95
95
 
96
- expand(el: HTMLElement) {
97
- this.animate(el, true)
96
+ /**
97
+ * 展开动画;`onEnd` 在动画落定(含无需动画直接落定)后调用,
98
+ * 被新的动画打断时不调用
99
+ */
100
+ expand(el: HTMLElement, onEnd?: () => void) {
101
+ this.animate(el, true, onEnd)
98
102
  }
99
103
 
100
- collapse(el: HTMLElement) {
101
- this.animate(el, false)
104
+ /** 收起动画;`onEnd` 语义同 `expand` */
105
+ collapse(el: HTMLElement, onEnd?: () => void) {
106
+ this.animate(el, false, onEnd)
102
107
  }
103
108
 
104
109
  setExpanded(el: HTMLElement, expanded: boolean) {
@@ -108,7 +113,7 @@ export class ExpandTransition {
108
113
  el.style.height = expanded ? 'auto' : '0px'
109
114
  }
110
115
 
111
- private animate(el: HTMLElement, expanded: boolean) {
116
+ private animate(el: HTMLElement, expanded: boolean, onEnd?: () => void) {
112
117
  this.cancel(el)
113
118
 
114
119
  const startHeight = el.offsetHeight
@@ -117,6 +122,7 @@ export class ExpandTransition {
117
122
 
118
123
  if (startHeight === endHeight) {
119
124
  this.setExpanded(el, expanded)
125
+ onEnd?.()
120
126
  return
121
127
  }
122
128
 
@@ -139,21 +145,22 @@ export class ExpandTransition {
139
145
  })
140
146
 
141
147
  const cleanup = () => {
142
- el.removeEventListener('transitionend', onEnd)
143
- el.removeEventListener('transitioncancel', onEnd)
148
+ el.removeEventListener('transitionend', onEndHandler)
149
+ el.removeEventListener('transitioncancel', onEndHandler)
144
150
  this.cleanupMap.delete(el)
145
151
  }
146
152
 
147
- const onEnd = (e: TransitionEvent) => {
153
+ const onEndHandler = (e: TransitionEvent) => {
148
154
  if (e.target !== el || e.propertyName !== 'height') return
149
155
  cleanup()
150
156
  this.resetTemporaryStyles(el)
151
157
  el.style.overflow = 'hidden'
152
158
  el.style.height = expanded ? 'auto' : '0px'
159
+ onEnd?.()
153
160
  }
154
161
 
155
- el.addEventListener('transitionend', onEnd)
156
- el.addEventListener('transitioncancel', onEnd)
162
+ el.addEventListener('transitionend', onEndHandler)
163
+ el.addEventListener('transitioncancel', onEndHandler)
157
164
  this.cleanupMap.set(el, cleanup)
158
165
  }
159
166