selective-ui 1.0.2 → 1.0.4
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.
- package/LICENSE +21 -21
- package/README.md +7 -2
- package/dist/selective-ui.css +567 -567
- package/dist/selective-ui.css.map +1 -1
- package/dist/selective-ui.esm.js +6186 -6047
- package/dist/selective-ui.esm.js.map +1 -1
- package/dist/selective-ui.esm.min.js +1 -1
- package/dist/selective-ui.esm.min.js.br +0 -0
- package/dist/selective-ui.min.js +2 -2
- package/dist/selective-ui.min.js.br +0 -0
- package/dist/selective-ui.umd.js +6186 -6047
- package/dist/selective-ui.umd.js.map +1 -1
- package/package.json +68 -68
- package/src/css/components/accessorybox.css +63 -63
- package/src/css/components/directive.css +19 -19
- package/src/css/components/empty-state.css +25 -25
- package/src/css/components/loading-state.css +25 -25
- package/src/css/components/optgroup.css +61 -61
- package/src/css/components/option-handle.css +33 -33
- package/src/css/components/option.css +129 -129
- package/src/css/components/placeholder.css +14 -14
- package/src/css/components/popup.css +38 -38
- package/src/css/components/searchbox.css +28 -28
- package/src/css/components/selectbox.css +53 -53
- package/src/css/index.css +74 -74
- package/src/js/adapter/mixed-adapter.js +434 -434
- package/src/js/components/accessorybox.js +124 -124
- package/src/js/components/directive.js +37 -37
- package/src/js/components/empty-state.js +67 -67
- package/src/js/components/loading-state.js +59 -59
- package/src/js/components/option-handle.js +113 -113
- package/src/js/components/placeholder.js +56 -56
- package/src/js/components/popup.js +470 -470
- package/src/js/components/searchbox.js +167 -167
- package/src/js/components/selectbox.js +749 -692
- package/src/js/core/base/adapter.js +162 -162
- package/src/js/core/base/model.js +59 -59
- package/src/js/core/base/recyclerview.js +82 -82
- package/src/js/core/base/view.js +62 -62
- package/src/js/core/model-manager.js +286 -286
- package/src/js/core/search-controller.js +603 -521
- package/src/js/index.js +136 -136
- package/src/js/models/group-model.js +142 -142
- package/src/js/models/option-model.js +236 -236
- package/src/js/services/dataset-observer.js +73 -73
- package/src/js/services/ea-observer.js +87 -87
- package/src/js/services/effector.js +403 -403
- package/src/js/services/refresher.js +39 -39
- package/src/js/services/resize-observer.js +151 -151
- package/src/js/services/select-observer.js +60 -60
- package/src/js/types/adapter.type.js +32 -32
- package/src/js/types/effector.type.js +23 -23
- package/src/js/types/ievents.type.js +10 -10
- package/src/js/types/libs.type.js +27 -27
- package/src/js/types/model.type.js +11 -11
- package/src/js/types/recyclerview.type.js +11 -11
- package/src/js/types/resize-observer.type.js +18 -18
- package/src/js/types/view.group.type.js +12 -12
- package/src/js/types/view.option.type.js +14 -14
- package/src/js/types/view.type.js +10 -10
- package/src/js/utils/guard.js +46 -46
- package/src/js/utils/ievents.js +83 -83
- package/src/js/utils/istorage.js +60 -60
- package/src/js/utils/libs.js +618 -618
- package/src/js/utils/selective.js +385 -385
- package/src/js/views/group-view.js +102 -102
- package/src/js/views/option-view.js +152 -152
|
@@ -1,404 +1,404 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @returns {EffectorInterface}
|
|
3
|
-
*/
|
|
4
|
-
export function Effector(query) {
|
|
5
|
-
return new class {
|
|
6
|
-
/**
|
|
7
|
-
* @type {HTMLElement}
|
|
8
|
-
*/
|
|
9
|
-
element;
|
|
10
|
-
#timeOut = null;
|
|
11
|
-
#resizeTimeout = null;
|
|
12
|
-
#isAnimating = false;
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Provides an effector utility that controls animations and resizing for a target element.
|
|
16
|
-
* Supports setting the element by selector or node, canceling in-flight animations/timers,
|
|
17
|
-
* and exposes methods (expand, collapse, resize) via the returned object instance.
|
|
18
|
-
*
|
|
19
|
-
* @param {string|HTMLElement} [query] - A CSS selector or the target element to control.
|
|
20
|
-
*/
|
|
21
|
-
constructor(query = null) {
|
|
22
|
-
if (query) {
|
|
23
|
-
this.setElement(query);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Sets the target element to be controlled by the effector.
|
|
29
|
-
* Accepts either a CSS selector or a direct HTMLElement reference.
|
|
30
|
-
*
|
|
31
|
-
* @param {string|HTMLElement} query - The element or selector to bind.
|
|
32
|
-
*/
|
|
33
|
-
setElement(query) {
|
|
34
|
-
if (typeof query === "string") {
|
|
35
|
-
this.element = document.querySelector(query);
|
|
36
|
-
} else {
|
|
37
|
-
this.element = query;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Cancels any pending timeouts or resize triggers and resets the animation state.
|
|
43
|
-
* Use this to stop ongoing expand/collapse/resize animations immediately.
|
|
44
|
-
*
|
|
45
|
-
* @returns {this} - The effector instance for chaining.
|
|
46
|
-
*/
|
|
47
|
-
cancel() {
|
|
48
|
-
if (this.#timeOut) {
|
|
49
|
-
clearTimeout(this.#timeOut);
|
|
50
|
-
this.#timeOut = null;
|
|
51
|
-
}
|
|
52
|
-
if (this.#resizeTimeout) {
|
|
53
|
-
clearTimeout(this.#resizeTimeout);
|
|
54
|
-
this.#resizeTimeout = null;
|
|
55
|
-
}
|
|
56
|
-
this.#isAnimating = false;
|
|
57
|
-
return this;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Get hidden dimensions
|
|
62
|
-
* @param {string} display
|
|
63
|
-
* @returns {{width: number, height: number, scrollHeight: number}}
|
|
64
|
-
*/
|
|
65
|
-
getHiddenDimensions(display = "flex") {
|
|
66
|
-
const originalStyles = {
|
|
67
|
-
display: this.element.style.display,
|
|
68
|
-
visibility: this.element.style.visibility,
|
|
69
|
-
position: this.element.style.position,
|
|
70
|
-
height: this.element.style.height,
|
|
71
|
-
width: this.element.style.width,
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
Object.assign(this.element.style, {
|
|
75
|
-
display: display,
|
|
76
|
-
visibility: "hidden",
|
|
77
|
-
position: "fixed",
|
|
78
|
-
height: "fit-content",
|
|
79
|
-
width: "fit-content"
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
const getComputedStyle = window.getComputedStyle(this.element);
|
|
83
|
-
const borderTopWidth = parseFloat(getComputedStyle.borderTopWidth);
|
|
84
|
-
const borderBottomWidth = parseFloat(getComputedStyle.borderBottomWidth);
|
|
85
|
-
|
|
86
|
-
const scrollHeight = this.element.scrollHeight + borderTopWidth + borderBottomWidth;
|
|
87
|
-
|
|
88
|
-
const rect = this.element.getBoundingClientRect();
|
|
89
|
-
|
|
90
|
-
const dimensions = {
|
|
91
|
-
width: rect.width,
|
|
92
|
-
height: rect.height + borderTopWidth + borderBottomWidth,
|
|
93
|
-
scrollHeight: scrollHeight
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
Object.assign(this.element.style, originalStyles);
|
|
97
|
-
|
|
98
|
-
return dimensions;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Expand animation (open popup)
|
|
103
|
-
* @param {Object} config
|
|
104
|
-
* @param {number} config.duration - Animation duration in ms
|
|
105
|
-
* @param {string} config.display - Display type
|
|
106
|
-
* @param {number} config.width - Target width
|
|
107
|
-
* @param {number} config.left - Left position
|
|
108
|
-
* @param {number} config.top - Top position
|
|
109
|
-
* @param {number} config.maxHeight - Max height
|
|
110
|
-
* @param {number} config.realHeight - Real height
|
|
111
|
-
* @param {string} config.position - Position type (top/bottom)
|
|
112
|
-
* @param {Function} config.onComplete - Callback when complete
|
|
113
|
-
* @returns {this}
|
|
114
|
-
*/
|
|
115
|
-
expand(config) {
|
|
116
|
-
this.cancel();
|
|
117
|
-
this.#isAnimating = true;
|
|
118
|
-
|
|
119
|
-
const {
|
|
120
|
-
duration = 200,
|
|
121
|
-
display = "flex",
|
|
122
|
-
width,
|
|
123
|
-
left,
|
|
124
|
-
top,
|
|
125
|
-
maxHeight,
|
|
126
|
-
realHeight,
|
|
127
|
-
position = "bottom",
|
|
128
|
-
onComplete
|
|
129
|
-
} = config;
|
|
130
|
-
|
|
131
|
-
const initialTop = position === "bottom"
|
|
132
|
-
? top
|
|
133
|
-
: top + realHeight;
|
|
134
|
-
|
|
135
|
-
Object.assign(this.element.style, {
|
|
136
|
-
display: display,
|
|
137
|
-
width: `${width}px`,
|
|
138
|
-
left: `${left}px`,
|
|
139
|
-
top: `${initialTop}px`,
|
|
140
|
-
maxHeight: `${maxHeight}px`,
|
|
141
|
-
height: "0px",
|
|
142
|
-
opacity: "0",
|
|
143
|
-
overflow: "hidden",
|
|
144
|
-
transition: "none"
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
this.element.classList.toggle("position-top", position === "top");
|
|
148
|
-
this.element.classList.toggle("position-bottom", position === "bottom");
|
|
149
|
-
|
|
150
|
-
requestAnimationFrame(() => {
|
|
151
|
-
const isScrollable = realHeight >= maxHeight;
|
|
152
|
-
|
|
153
|
-
Object.assign(this.element.style, {
|
|
154
|
-
transition: `top ${duration}ms, height ${duration}ms, opacity ${duration}ms`,
|
|
155
|
-
top: `${top}px`,
|
|
156
|
-
height: `${realHeight}px`,
|
|
157
|
-
opacity: "1",
|
|
158
|
-
overflow: isScrollable ? "auto" : "hidden"
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
this.#timeOut = setTimeout(() => {
|
|
162
|
-
this.element.style.transition = "none";
|
|
163
|
-
this.#isAnimating = false;
|
|
164
|
-
onComplete && onComplete();
|
|
165
|
-
}, duration);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
return this;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Collapse animation (close popup)
|
|
173
|
-
* @param {Object} config
|
|
174
|
-
* @param {number} config.duration - Animation duration in ms
|
|
175
|
-
* @param {Function} config.onComplete - Callback when complete
|
|
176
|
-
* @returns {this}
|
|
177
|
-
*/
|
|
178
|
-
collapse(config) {
|
|
179
|
-
this.cancel();
|
|
180
|
-
this.#isAnimating = true;
|
|
181
|
-
|
|
182
|
-
const {
|
|
183
|
-
duration = 200,
|
|
184
|
-
onComplete
|
|
185
|
-
} = config;
|
|
186
|
-
|
|
187
|
-
const currentHeight = this.element.offsetHeight;
|
|
188
|
-
const currentTop = this.element.offsetTop;
|
|
189
|
-
const position = this.element.classList.contains("position-top") ? "top" : "bottom";
|
|
190
|
-
const isScrollable = (this.element.scrollHeight - this.element.offsetHeight) > 0;
|
|
191
|
-
|
|
192
|
-
const finalTop = position === "top"
|
|
193
|
-
? currentTop + currentHeight
|
|
194
|
-
: currentTop;
|
|
195
|
-
|
|
196
|
-
requestAnimationFrame(() => {
|
|
197
|
-
Object.assign(this.element.style, {
|
|
198
|
-
transition: `height ${duration}ms, top ${duration}ms, opacity ${duration}ms`,
|
|
199
|
-
height: "0px",
|
|
200
|
-
top: `${finalTop}px`,
|
|
201
|
-
opacity: "0",
|
|
202
|
-
overflow: isScrollable ? "auto" : "hidden"
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
this.#timeOut = setTimeout(() => {
|
|
206
|
-
Object.assign(this.element.style, {
|
|
207
|
-
display: "none",
|
|
208
|
-
transition: "none"
|
|
209
|
-
});
|
|
210
|
-
this.#isAnimating = false;
|
|
211
|
-
onComplete && onComplete();
|
|
212
|
-
}, duration);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
return this;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* show Swipe animation (close element)
|
|
220
|
-
* @param {Object} config
|
|
221
|
-
* @param {number} config.duration - Animation duration in ms
|
|
222
|
-
* @param {String} config.display - Display for element
|
|
223
|
-
* @param {Function} config.onComplete - Callback when complete
|
|
224
|
-
* @returns {this}
|
|
225
|
-
*/
|
|
226
|
-
showSwipeWidth(config) {
|
|
227
|
-
this.cancel();
|
|
228
|
-
this.#isAnimating = true;
|
|
229
|
-
|
|
230
|
-
const {
|
|
231
|
-
duration = 200,
|
|
232
|
-
display = "block",
|
|
233
|
-
onComplete
|
|
234
|
-
} = config;
|
|
235
|
-
|
|
236
|
-
Object.assign(this.element.style, {
|
|
237
|
-
transition: "none",
|
|
238
|
-
display: display,
|
|
239
|
-
width: "fit-content"
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
const maxWidth = this.getHiddenDimensions(display).width;
|
|
243
|
-
|
|
244
|
-
Object.assign(this.element.style, {
|
|
245
|
-
width: "0px"
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
requestAnimationFrame(() => {
|
|
249
|
-
Object.assign(this.element.style, {
|
|
250
|
-
transition: `width ${duration}ms`,
|
|
251
|
-
width: `${maxWidth}px`,
|
|
252
|
-
overflow: "hidden"
|
|
253
|
-
});
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
this.#timeOut = setTimeout(() => {
|
|
257
|
-
Object.assign(this.element.style, {
|
|
258
|
-
width: null,
|
|
259
|
-
overflow: null,
|
|
260
|
-
transition: null
|
|
261
|
-
});
|
|
262
|
-
this.#isAnimating = false;
|
|
263
|
-
onComplete && onComplete();
|
|
264
|
-
}, duration);
|
|
265
|
-
|
|
266
|
-
return this;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* hide Swipe animation (close element)
|
|
271
|
-
* @param {Object} config
|
|
272
|
-
* @param {number} config.duration - Animation duration in ms
|
|
273
|
-
* @param {Function} config.onComplete - Callback when complete
|
|
274
|
-
* @returns {this}
|
|
275
|
-
*/
|
|
276
|
-
hideSwipeWidth(config) {
|
|
277
|
-
this.cancel();
|
|
278
|
-
this.#isAnimating = true;
|
|
279
|
-
|
|
280
|
-
const {
|
|
281
|
-
duration = 200,
|
|
282
|
-
onComplete
|
|
283
|
-
} = config;
|
|
284
|
-
|
|
285
|
-
const maxWidth = this.getHiddenDimensions().width;
|
|
286
|
-
|
|
287
|
-
Object.assign(this.element.style, {
|
|
288
|
-
transition: "none",
|
|
289
|
-
width: `${maxWidth}px`
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
requestAnimationFrame(() => {
|
|
293
|
-
Object.assign(this.element.style, {
|
|
294
|
-
transition: `width ${duration}ms`,
|
|
295
|
-
width: `0px`,
|
|
296
|
-
overflow: "hidden"
|
|
297
|
-
});
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
this.#timeOut = setTimeout(() => {
|
|
301
|
-
Object.assign(this.element.style, {
|
|
302
|
-
width: null,
|
|
303
|
-
overflow: null,
|
|
304
|
-
transition: null,
|
|
305
|
-
display: null
|
|
306
|
-
});
|
|
307
|
-
this.#isAnimating = false;
|
|
308
|
-
onComplete && onComplete();
|
|
309
|
-
}, duration);
|
|
310
|
-
|
|
311
|
-
return this;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Resize animation (when content changes)
|
|
316
|
-
* @param {Object} config
|
|
317
|
-
* @param {number} config.duration - Animation duration in ms
|
|
318
|
-
* @param {number} config.width - Target width
|
|
319
|
-
* @param {number} config.left - Left position
|
|
320
|
-
* @param {number} config.top - Top position
|
|
321
|
-
* @param {number} config.maxHeight - Max height
|
|
322
|
-
* @param {number} config.realHeight - Real height
|
|
323
|
-
* @param {string} config.position - Position type (top/bottom)
|
|
324
|
-
* @param {boolean} config.animate - Whether to animate
|
|
325
|
-
* @param {Function} config.onComplete - Callback when complete
|
|
326
|
-
* @returns {this}
|
|
327
|
-
*/
|
|
328
|
-
resize(config) {
|
|
329
|
-
if (this.#resizeTimeout) {
|
|
330
|
-
clearTimeout(this.#resizeTimeout);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
const {
|
|
334
|
-
duration = 200,
|
|
335
|
-
width,
|
|
336
|
-
left,
|
|
337
|
-
top,
|
|
338
|
-
maxHeight,
|
|
339
|
-
realHeight,
|
|
340
|
-
position = "bottom",
|
|
341
|
-
animate = true,
|
|
342
|
-
onComplete
|
|
343
|
-
} = config;
|
|
344
|
-
|
|
345
|
-
const currentPosition = this.element.classList.contains("position-top") ? "top" : "bottom";
|
|
346
|
-
const isPositionChanged = currentPosition !== position;
|
|
347
|
-
const isScrollable = this.element.scrollHeight > maxHeight;
|
|
348
|
-
|
|
349
|
-
this.element.classList.toggle("position-top", position === "top");
|
|
350
|
-
this.element.classList.toggle("position-bottom", position === "bottom");
|
|
351
|
-
|
|
352
|
-
if (isPositionChanged) {
|
|
353
|
-
this.element.style.transition = `top ${duration}ms ease-out, height ${duration}ms ease-out, max-height ${duration}ms ease-out;`;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
requestAnimationFrame(() => {
|
|
357
|
-
const curTop = this.element.offsetTop;
|
|
358
|
-
const styles = {
|
|
359
|
-
width: `${width}px`,
|
|
360
|
-
left: `${left}px`,
|
|
361
|
-
top: `${top}px`,
|
|
362
|
-
maxHeight: `${maxHeight}px`,
|
|
363
|
-
height: `${realHeight}px`,
|
|
364
|
-
overflowY: isScrollable ? "auto" : "hidden"
|
|
365
|
-
};
|
|
366
|
-
|
|
367
|
-
if (animate && (isPositionChanged || Math.abs(this.element.offsetHeight - realHeight) > 5) ) {
|
|
368
|
-
styles.transition = `height ${duration}ms, top ${duration}ms`;
|
|
369
|
-
} else {
|
|
370
|
-
this.#resizeTimeout = setTimeout(() => {
|
|
371
|
-
this.element.style.transition = "none";
|
|
372
|
-
}, duration);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
Object.assign(this.element.style, styles);
|
|
376
|
-
|
|
377
|
-
if (animate && (isPositionChanged || Math.abs(this.element.offsetHeight - realHeight) > 1)) {
|
|
378
|
-
this.#resizeTimeout = setTimeout(() => {
|
|
379
|
-
this.element.style.transition = "none";
|
|
380
|
-
if (isPositionChanged) {
|
|
381
|
-
delete this.element.style.transition;
|
|
382
|
-
}
|
|
383
|
-
onComplete && onComplete();
|
|
384
|
-
}, duration);
|
|
385
|
-
} else {
|
|
386
|
-
if (isPositionChanged) {
|
|
387
|
-
delete this.element.style.transition;
|
|
388
|
-
}
|
|
389
|
-
onComplete && onComplete();
|
|
390
|
-
}
|
|
391
|
-
});
|
|
392
|
-
|
|
393
|
-
return this;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
/**
|
|
397
|
-
* Check if currently animating
|
|
398
|
-
* @returns {boolean}
|
|
399
|
-
*/
|
|
400
|
-
get isAnimating() {
|
|
401
|
-
return this.#isAnimating;
|
|
402
|
-
}
|
|
403
|
-
}(query);
|
|
1
|
+
/**
|
|
2
|
+
* @returns {EffectorInterface}
|
|
3
|
+
*/
|
|
4
|
+
export function Effector(query) {
|
|
5
|
+
return new class {
|
|
6
|
+
/**
|
|
7
|
+
* @type {HTMLElement}
|
|
8
|
+
*/
|
|
9
|
+
element;
|
|
10
|
+
#timeOut = null;
|
|
11
|
+
#resizeTimeout = null;
|
|
12
|
+
#isAnimating = false;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Provides an effector utility that controls animations and resizing for a target element.
|
|
16
|
+
* Supports setting the element by selector or node, canceling in-flight animations/timers,
|
|
17
|
+
* and exposes methods (expand, collapse, resize) via the returned object instance.
|
|
18
|
+
*
|
|
19
|
+
* @param {string|HTMLElement} [query] - A CSS selector or the target element to control.
|
|
20
|
+
*/
|
|
21
|
+
constructor(query = null) {
|
|
22
|
+
if (query) {
|
|
23
|
+
this.setElement(query);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Sets the target element to be controlled by the effector.
|
|
29
|
+
* Accepts either a CSS selector or a direct HTMLElement reference.
|
|
30
|
+
*
|
|
31
|
+
* @param {string|HTMLElement} query - The element or selector to bind.
|
|
32
|
+
*/
|
|
33
|
+
setElement(query) {
|
|
34
|
+
if (typeof query === "string") {
|
|
35
|
+
this.element = document.querySelector(query);
|
|
36
|
+
} else {
|
|
37
|
+
this.element = query;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Cancels any pending timeouts or resize triggers and resets the animation state.
|
|
43
|
+
* Use this to stop ongoing expand/collapse/resize animations immediately.
|
|
44
|
+
*
|
|
45
|
+
* @returns {this} - The effector instance for chaining.
|
|
46
|
+
*/
|
|
47
|
+
cancel() {
|
|
48
|
+
if (this.#timeOut) {
|
|
49
|
+
clearTimeout(this.#timeOut);
|
|
50
|
+
this.#timeOut = null;
|
|
51
|
+
}
|
|
52
|
+
if (this.#resizeTimeout) {
|
|
53
|
+
clearTimeout(this.#resizeTimeout);
|
|
54
|
+
this.#resizeTimeout = null;
|
|
55
|
+
}
|
|
56
|
+
this.#isAnimating = false;
|
|
57
|
+
return this;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get hidden dimensions
|
|
62
|
+
* @param {string} display
|
|
63
|
+
* @returns {{width: number, height: number, scrollHeight: number}}
|
|
64
|
+
*/
|
|
65
|
+
getHiddenDimensions(display = "flex") {
|
|
66
|
+
const originalStyles = {
|
|
67
|
+
display: this.element.style.display,
|
|
68
|
+
visibility: this.element.style.visibility,
|
|
69
|
+
position: this.element.style.position,
|
|
70
|
+
height: this.element.style.height,
|
|
71
|
+
width: this.element.style.width,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
Object.assign(this.element.style, {
|
|
75
|
+
display: display,
|
|
76
|
+
visibility: "hidden",
|
|
77
|
+
position: "fixed",
|
|
78
|
+
height: "fit-content",
|
|
79
|
+
width: "fit-content"
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const getComputedStyle = window.getComputedStyle(this.element);
|
|
83
|
+
const borderTopWidth = parseFloat(getComputedStyle.borderTopWidth);
|
|
84
|
+
const borderBottomWidth = parseFloat(getComputedStyle.borderBottomWidth);
|
|
85
|
+
|
|
86
|
+
const scrollHeight = this.element.scrollHeight + borderTopWidth + borderBottomWidth;
|
|
87
|
+
|
|
88
|
+
const rect = this.element.getBoundingClientRect();
|
|
89
|
+
|
|
90
|
+
const dimensions = {
|
|
91
|
+
width: rect.width,
|
|
92
|
+
height: rect.height + borderTopWidth + borderBottomWidth,
|
|
93
|
+
scrollHeight: scrollHeight
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
Object.assign(this.element.style, originalStyles);
|
|
97
|
+
|
|
98
|
+
return dimensions;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Expand animation (open popup)
|
|
103
|
+
* @param {Object} config
|
|
104
|
+
* @param {number} config.duration - Animation duration in ms
|
|
105
|
+
* @param {string} config.display - Display type
|
|
106
|
+
* @param {number} config.width - Target width
|
|
107
|
+
* @param {number} config.left - Left position
|
|
108
|
+
* @param {number} config.top - Top position
|
|
109
|
+
* @param {number} config.maxHeight - Max height
|
|
110
|
+
* @param {number} config.realHeight - Real height
|
|
111
|
+
* @param {string} config.position - Position type (top/bottom)
|
|
112
|
+
* @param {Function} config.onComplete - Callback when complete
|
|
113
|
+
* @returns {this}
|
|
114
|
+
*/
|
|
115
|
+
expand(config) {
|
|
116
|
+
this.cancel();
|
|
117
|
+
this.#isAnimating = true;
|
|
118
|
+
|
|
119
|
+
const {
|
|
120
|
+
duration = 200,
|
|
121
|
+
display = "flex",
|
|
122
|
+
width,
|
|
123
|
+
left,
|
|
124
|
+
top,
|
|
125
|
+
maxHeight,
|
|
126
|
+
realHeight,
|
|
127
|
+
position = "bottom",
|
|
128
|
+
onComplete
|
|
129
|
+
} = config;
|
|
130
|
+
|
|
131
|
+
const initialTop = position === "bottom"
|
|
132
|
+
? top
|
|
133
|
+
: top + realHeight;
|
|
134
|
+
|
|
135
|
+
Object.assign(this.element.style, {
|
|
136
|
+
display: display,
|
|
137
|
+
width: `${width}px`,
|
|
138
|
+
left: `${left}px`,
|
|
139
|
+
top: `${initialTop}px`,
|
|
140
|
+
maxHeight: `${maxHeight}px`,
|
|
141
|
+
height: "0px",
|
|
142
|
+
opacity: "0",
|
|
143
|
+
overflow: "hidden",
|
|
144
|
+
transition: "none"
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
this.element.classList.toggle("position-top", position === "top");
|
|
148
|
+
this.element.classList.toggle("position-bottom", position === "bottom");
|
|
149
|
+
|
|
150
|
+
requestAnimationFrame(() => {
|
|
151
|
+
const isScrollable = realHeight >= maxHeight;
|
|
152
|
+
|
|
153
|
+
Object.assign(this.element.style, {
|
|
154
|
+
transition: `top ${duration}ms, height ${duration}ms, opacity ${duration}ms`,
|
|
155
|
+
top: `${top}px`,
|
|
156
|
+
height: `${realHeight}px`,
|
|
157
|
+
opacity: "1",
|
|
158
|
+
overflow: isScrollable ? "auto" : "hidden"
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
this.#timeOut = setTimeout(() => {
|
|
162
|
+
this.element.style.transition = "none";
|
|
163
|
+
this.#isAnimating = false;
|
|
164
|
+
onComplete && onComplete();
|
|
165
|
+
}, duration);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
return this;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Collapse animation (close popup)
|
|
173
|
+
* @param {Object} config
|
|
174
|
+
* @param {number} config.duration - Animation duration in ms
|
|
175
|
+
* @param {Function} config.onComplete - Callback when complete
|
|
176
|
+
* @returns {this}
|
|
177
|
+
*/
|
|
178
|
+
collapse(config) {
|
|
179
|
+
this.cancel();
|
|
180
|
+
this.#isAnimating = true;
|
|
181
|
+
|
|
182
|
+
const {
|
|
183
|
+
duration = 200,
|
|
184
|
+
onComplete
|
|
185
|
+
} = config;
|
|
186
|
+
|
|
187
|
+
const currentHeight = this.element.offsetHeight;
|
|
188
|
+
const currentTop = this.element.offsetTop;
|
|
189
|
+
const position = this.element.classList.contains("position-top") ? "top" : "bottom";
|
|
190
|
+
const isScrollable = (this.element.scrollHeight - this.element.offsetHeight) > 0;
|
|
191
|
+
|
|
192
|
+
const finalTop = position === "top"
|
|
193
|
+
? currentTop + currentHeight
|
|
194
|
+
: currentTop;
|
|
195
|
+
|
|
196
|
+
requestAnimationFrame(() => {
|
|
197
|
+
Object.assign(this.element.style, {
|
|
198
|
+
transition: `height ${duration}ms, top ${duration}ms, opacity ${duration}ms`,
|
|
199
|
+
height: "0px",
|
|
200
|
+
top: `${finalTop}px`,
|
|
201
|
+
opacity: "0",
|
|
202
|
+
overflow: isScrollable ? "auto" : "hidden"
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
this.#timeOut = setTimeout(() => {
|
|
206
|
+
Object.assign(this.element.style, {
|
|
207
|
+
display: "none",
|
|
208
|
+
transition: "none"
|
|
209
|
+
});
|
|
210
|
+
this.#isAnimating = false;
|
|
211
|
+
onComplete && onComplete();
|
|
212
|
+
}, duration);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
return this;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* show Swipe animation (close element)
|
|
220
|
+
* @param {Object} config
|
|
221
|
+
* @param {number} config.duration - Animation duration in ms
|
|
222
|
+
* @param {String} config.display - Display for element
|
|
223
|
+
* @param {Function} config.onComplete - Callback when complete
|
|
224
|
+
* @returns {this}
|
|
225
|
+
*/
|
|
226
|
+
showSwipeWidth(config) {
|
|
227
|
+
this.cancel();
|
|
228
|
+
this.#isAnimating = true;
|
|
229
|
+
|
|
230
|
+
const {
|
|
231
|
+
duration = 200,
|
|
232
|
+
display = "block",
|
|
233
|
+
onComplete
|
|
234
|
+
} = config;
|
|
235
|
+
|
|
236
|
+
Object.assign(this.element.style, {
|
|
237
|
+
transition: "none",
|
|
238
|
+
display: display,
|
|
239
|
+
width: "fit-content"
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const maxWidth = this.getHiddenDimensions(display).width;
|
|
243
|
+
|
|
244
|
+
Object.assign(this.element.style, {
|
|
245
|
+
width: "0px"
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
requestAnimationFrame(() => {
|
|
249
|
+
Object.assign(this.element.style, {
|
|
250
|
+
transition: `width ${duration}ms`,
|
|
251
|
+
width: `${maxWidth}px`,
|
|
252
|
+
overflow: "hidden"
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
this.#timeOut = setTimeout(() => {
|
|
257
|
+
Object.assign(this.element.style, {
|
|
258
|
+
width: null,
|
|
259
|
+
overflow: null,
|
|
260
|
+
transition: null
|
|
261
|
+
});
|
|
262
|
+
this.#isAnimating = false;
|
|
263
|
+
onComplete && onComplete();
|
|
264
|
+
}, duration);
|
|
265
|
+
|
|
266
|
+
return this;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* hide Swipe animation (close element)
|
|
271
|
+
* @param {Object} config
|
|
272
|
+
* @param {number} config.duration - Animation duration in ms
|
|
273
|
+
* @param {Function} config.onComplete - Callback when complete
|
|
274
|
+
* @returns {this}
|
|
275
|
+
*/
|
|
276
|
+
hideSwipeWidth(config) {
|
|
277
|
+
this.cancel();
|
|
278
|
+
this.#isAnimating = true;
|
|
279
|
+
|
|
280
|
+
const {
|
|
281
|
+
duration = 200,
|
|
282
|
+
onComplete
|
|
283
|
+
} = config;
|
|
284
|
+
|
|
285
|
+
const maxWidth = this.getHiddenDimensions().width;
|
|
286
|
+
|
|
287
|
+
Object.assign(this.element.style, {
|
|
288
|
+
transition: "none",
|
|
289
|
+
width: `${maxWidth}px`
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
requestAnimationFrame(() => {
|
|
293
|
+
Object.assign(this.element.style, {
|
|
294
|
+
transition: `width ${duration}ms`,
|
|
295
|
+
width: `0px`,
|
|
296
|
+
overflow: "hidden"
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
this.#timeOut = setTimeout(() => {
|
|
301
|
+
Object.assign(this.element.style, {
|
|
302
|
+
width: null,
|
|
303
|
+
overflow: null,
|
|
304
|
+
transition: null,
|
|
305
|
+
display: null
|
|
306
|
+
});
|
|
307
|
+
this.#isAnimating = false;
|
|
308
|
+
onComplete && onComplete();
|
|
309
|
+
}, duration);
|
|
310
|
+
|
|
311
|
+
return this;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Resize animation (when content changes)
|
|
316
|
+
* @param {Object} config
|
|
317
|
+
* @param {number} config.duration - Animation duration in ms
|
|
318
|
+
* @param {number} config.width - Target width
|
|
319
|
+
* @param {number} config.left - Left position
|
|
320
|
+
* @param {number} config.top - Top position
|
|
321
|
+
* @param {number} config.maxHeight - Max height
|
|
322
|
+
* @param {number} config.realHeight - Real height
|
|
323
|
+
* @param {string} config.position - Position type (top/bottom)
|
|
324
|
+
* @param {boolean} config.animate - Whether to animate
|
|
325
|
+
* @param {Function} config.onComplete - Callback when complete
|
|
326
|
+
* @returns {this}
|
|
327
|
+
*/
|
|
328
|
+
resize(config) {
|
|
329
|
+
if (this.#resizeTimeout) {
|
|
330
|
+
clearTimeout(this.#resizeTimeout);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const {
|
|
334
|
+
duration = 200,
|
|
335
|
+
width,
|
|
336
|
+
left,
|
|
337
|
+
top,
|
|
338
|
+
maxHeight,
|
|
339
|
+
realHeight,
|
|
340
|
+
position = "bottom",
|
|
341
|
+
animate = true,
|
|
342
|
+
onComplete
|
|
343
|
+
} = config;
|
|
344
|
+
|
|
345
|
+
const currentPosition = this.element.classList.contains("position-top") ? "top" : "bottom";
|
|
346
|
+
const isPositionChanged = currentPosition !== position;
|
|
347
|
+
const isScrollable = this.element.scrollHeight > maxHeight;
|
|
348
|
+
|
|
349
|
+
this.element.classList.toggle("position-top", position === "top");
|
|
350
|
+
this.element.classList.toggle("position-bottom", position === "bottom");
|
|
351
|
+
|
|
352
|
+
if (isPositionChanged) {
|
|
353
|
+
this.element.style.transition = `top ${duration}ms ease-out, height ${duration}ms ease-out, max-height ${duration}ms ease-out;`;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
requestAnimationFrame(() => {
|
|
357
|
+
const curTop = this.element.offsetTop;
|
|
358
|
+
const styles = {
|
|
359
|
+
width: `${width}px`,
|
|
360
|
+
left: `${left}px`,
|
|
361
|
+
top: `${top}px`,
|
|
362
|
+
maxHeight: `${maxHeight}px`,
|
|
363
|
+
height: `${realHeight}px`,
|
|
364
|
+
overflowY: isScrollable ? "auto" : "hidden"
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
if (animate && (isPositionChanged || Math.abs(this.element.offsetHeight - realHeight) > 5) ) {
|
|
368
|
+
styles.transition = `height ${duration}ms, top ${duration}ms`;
|
|
369
|
+
} else {
|
|
370
|
+
this.#resizeTimeout = setTimeout(() => {
|
|
371
|
+
this.element.style.transition = "none";
|
|
372
|
+
}, duration);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
Object.assign(this.element.style, styles);
|
|
376
|
+
|
|
377
|
+
if (animate && (isPositionChanged || Math.abs(this.element.offsetHeight - realHeight) > 1)) {
|
|
378
|
+
this.#resizeTimeout = setTimeout(() => {
|
|
379
|
+
this.element.style.transition = "none";
|
|
380
|
+
if (isPositionChanged) {
|
|
381
|
+
delete this.element.style.transition;
|
|
382
|
+
}
|
|
383
|
+
onComplete && onComplete();
|
|
384
|
+
}, duration);
|
|
385
|
+
} else {
|
|
386
|
+
if (isPositionChanged) {
|
|
387
|
+
delete this.element.style.transition;
|
|
388
|
+
}
|
|
389
|
+
onComplete && onComplete();
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
return this;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Check if currently animating
|
|
398
|
+
* @returns {boolean}
|
|
399
|
+
*/
|
|
400
|
+
get isAnimating() {
|
|
401
|
+
return this.#isAnimating;
|
|
402
|
+
}
|
|
403
|
+
}(query);
|
|
404
404
|
}
|