@egjs/flicking 4.13.2-beta.0 → 4.14.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.
- package/core-packages-link.js +75 -0
- package/debug/reactive/index.html +240 -0
- package/declaration/Flicking.d.ts +12 -1
- package/declaration/control/Control.d.ts +1 -1
- package/declaration/core/AutoResizer.d.ts +5 -0
- package/declaration/index.d.ts +1 -0
- package/declaration/reactive/index.d.ts +25 -0
- package/dist/flicking.cjs.js +399 -33
- package/dist/flicking.cjs.js.map +1 -1
- package/dist/flicking.esm.js +393 -31
- package/dist/flicking.esm.js.map +1 -1
- package/dist/flicking.js +403 -35
- package/dist/flicking.js.map +1 -1
- package/dist/flicking.min.js +2 -2
- package/dist/flicking.min.js.map +1 -1
- package/dist/flicking.pkgd.js +727 -49
- package/dist/flicking.pkgd.js.map +1 -1
- package/dist/flicking.pkgd.min.js +2 -2
- package/dist/flicking.pkgd.min.js.map +1 -1
- package/package.json +3 -2
- package/src/Flicking.ts +522 -153
- package/src/camera/mode/CameraMode.ts +2 -1
- package/src/control/Control.ts +1 -1
- package/src/control/SnapControl.ts +1 -8
- package/src/core/AutoResizer.ts +110 -11
- package/src/index.ts +1 -0
- package/src/index.umd.ts +2 -0
- package/src/reactive/index.ts +326 -0
- package/src/renderer/Renderer.ts +13 -0
- package/src/utils.ts +6 -1
|
@@ -33,8 +33,9 @@ abstract class CameraMode {
|
|
|
33
33
|
|
|
34
34
|
public findAnchorIncludePosition(position: number): AnchorPoint | null {
|
|
35
35
|
const anchors = this._flicking.camera.anchorPoints;
|
|
36
|
+
const anchorsIncludingPosition = anchors.filter(anchor => anchor.panel.includePosition(position, true));
|
|
36
37
|
|
|
37
|
-
return
|
|
38
|
+
return anchorsIncludingPosition.reduce((nearest: AnchorPoint | null, anchor) => {
|
|
38
39
|
if (!nearest) return anchor;
|
|
39
40
|
|
|
40
41
|
return Math.abs(nearest.position - position) < Math.abs(anchor.position - position)
|
package/src/control/Control.ts
CHANGED
|
@@ -397,7 +397,7 @@ abstract class Control {
|
|
|
397
397
|
}
|
|
398
398
|
}
|
|
399
399
|
|
|
400
|
-
|
|
400
|
+
private _getPosition(panel: Panel, direction: ValueOf<typeof DIRECTION> = DIRECTION.NONE) {
|
|
401
401
|
const flicking = getFlickingAttached(this._flicking);
|
|
402
402
|
const camera = flicking.camera;
|
|
403
403
|
|
|
@@ -9,7 +9,6 @@ import AnchorPoint from "../core/AnchorPoint";
|
|
|
9
9
|
import { circulateIndex, clamp, getFlickingAttached } from "../utils";
|
|
10
10
|
import * as AXES from "../const/axes";
|
|
11
11
|
import * as ERROR from "../const/error";
|
|
12
|
-
import { DIRECTION } from "../const/external";
|
|
13
12
|
|
|
14
13
|
import Control from "./Control";
|
|
15
14
|
|
|
@@ -110,12 +109,7 @@ class SnapControl extends Control {
|
|
|
110
109
|
if (snapDelta >= snapThreshold && snapDelta > 0) {
|
|
111
110
|
// Move to anchor at position
|
|
112
111
|
targetAnchor = this._findSnappedAnchor(position, anchorAtCamera);
|
|
113
|
-
|
|
114
|
-
// const nextPosition = this._getPosition(targetPanel, DIRECTION.NEXT);
|
|
115
|
-
// const prevPosition = this._getPosition(targetPanel, DIRECTION.PREV);
|
|
116
|
-
|
|
117
|
-
// targetPosition = Math.abs(camera.position - nextPosition) < Math.abs(camera.position - prevPosition) ? nextPosition : prevPosition;
|
|
118
|
-
} else if (absPosDelta >= flicking.threshold && absPosDelta > 0 && anchorAtCamera === activeAnchor) {
|
|
112
|
+
} else if (absPosDelta >= flicking.threshold && absPosDelta > 0) {
|
|
119
113
|
// Move to the adjacent panel
|
|
120
114
|
targetAnchor = this._findAdjacentAnchor(position, posDelta, anchorAtCamera);
|
|
121
115
|
} else {
|
|
@@ -150,7 +144,6 @@ class SnapControl extends Control {
|
|
|
150
144
|
throw new FlickingError(ERROR.MESSAGE.POSITION_NOT_REACHABLE(position), ERROR.CODE.POSITION_NOT_REACHABLE);
|
|
151
145
|
}
|
|
152
146
|
|
|
153
|
-
// console.log("_findSnappedAnchor", anchorAtPosition);
|
|
154
147
|
if (!isFinite(count)) {
|
|
155
148
|
return anchorAtPosition;
|
|
156
149
|
}
|
package/src/core/AutoResizer.ts
CHANGED
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
* Copyright (c) 2015 NAVER Corp.
|
|
3
3
|
* egjs projects are licensed under the MIT license
|
|
4
4
|
*/
|
|
5
|
+
import { getElementSize, getStyle } from "../utils";
|
|
5
6
|
import Flicking from "../Flicking";
|
|
6
7
|
|
|
8
|
+
/**
|
|
9
|
+
* A component that detects size change and trigger resize method when the autoResize option is used
|
|
10
|
+
* @ko autoResize 옵션을 사용할 때 크기 변화를 감지하고 Flicking의 resize를 호출하는 컴포넌트
|
|
11
|
+
*/
|
|
7
12
|
class AutoResizer {
|
|
8
13
|
private _flicking: Flicking;
|
|
9
14
|
private _enabled: boolean;
|
|
@@ -11,7 +16,9 @@ class AutoResizer {
|
|
|
11
16
|
private _resizeTimer: number;
|
|
12
17
|
private _maxResizeDebounceTimer: number;
|
|
13
18
|
|
|
14
|
-
public get enabled() {
|
|
19
|
+
public get enabled() {
|
|
20
|
+
return this._enabled;
|
|
21
|
+
}
|
|
15
22
|
|
|
16
23
|
public constructor(flicking: Flicking) {
|
|
17
24
|
this._flicking = flicking;
|
|
@@ -36,11 +43,15 @@ class AutoResizer {
|
|
|
36
43
|
? new ResizeObserver(this._skipFirstResize)
|
|
37
44
|
: new ResizeObserver(this._onResize);
|
|
38
45
|
|
|
39
|
-
resizeObserver.observe(flicking.viewport.element);
|
|
40
|
-
|
|
41
46
|
this._resizeObserver = resizeObserver;
|
|
47
|
+
|
|
48
|
+
this.observe(flicking.viewport.element);
|
|
49
|
+
|
|
50
|
+
if (flicking.observePanelResize) {
|
|
51
|
+
this.observePanels();
|
|
52
|
+
}
|
|
42
53
|
} else {
|
|
43
|
-
window.addEventListener("resize", this.
|
|
54
|
+
window.addEventListener("resize", this._onResizeWrapper);
|
|
44
55
|
}
|
|
45
56
|
|
|
46
57
|
this._enabled = true;
|
|
@@ -48,6 +59,44 @@ class AutoResizer {
|
|
|
48
59
|
return this;
|
|
49
60
|
}
|
|
50
61
|
|
|
62
|
+
public observePanels(): this {
|
|
63
|
+
this._flicking.panels.forEach(panel => {
|
|
64
|
+
this.observe(panel.element);
|
|
65
|
+
});
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public unobservePanels(): this {
|
|
70
|
+
this._flicking.panels.forEach(panel => {
|
|
71
|
+
this.unobserve(panel.element);
|
|
72
|
+
});
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public observe(element: HTMLElement): this {
|
|
77
|
+
const resizeObserver = this._resizeObserver;
|
|
78
|
+
|
|
79
|
+
if (!resizeObserver) return this;
|
|
80
|
+
|
|
81
|
+
resizeObserver.observe(element);
|
|
82
|
+
|
|
83
|
+
return this;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
public unobserve(element: HTMLElement): this {
|
|
87
|
+
const resizeObserver = this._resizeObserver;
|
|
88
|
+
|
|
89
|
+
if (!resizeObserver) return this;
|
|
90
|
+
|
|
91
|
+
resizeObserver.unobserve(element);
|
|
92
|
+
|
|
93
|
+
if (this._flicking.observePanelResize) {
|
|
94
|
+
this.unobservePanels();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return this;
|
|
98
|
+
}
|
|
99
|
+
|
|
51
100
|
public disable(): this {
|
|
52
101
|
if (!this._enabled) return this;
|
|
53
102
|
|
|
@@ -56,7 +105,7 @@ class AutoResizer {
|
|
|
56
105
|
resizeObserver.disconnect();
|
|
57
106
|
this._resizeObserver = null;
|
|
58
107
|
} else {
|
|
59
|
-
window.removeEventListener("resize", this.
|
|
108
|
+
window.removeEventListener("resize", this._onResizeWrapper);
|
|
60
109
|
}
|
|
61
110
|
|
|
62
111
|
this._enabled = false;
|
|
@@ -64,17 +113,64 @@ class AutoResizer {
|
|
|
64
113
|
return this;
|
|
65
114
|
}
|
|
66
115
|
|
|
67
|
-
private
|
|
116
|
+
private _onResizeWrapper = () => {
|
|
117
|
+
this._onResize([]);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
private _onResize = (entries: ResizeObserverEntry[]) => {
|
|
68
121
|
const flicking = this._flicking;
|
|
69
122
|
const resizeDebounce = flicking.resizeDebounce;
|
|
70
123
|
const maxResizeDebounce = flicking.maxResizeDebounce;
|
|
71
124
|
|
|
125
|
+
const resizedViewportElement = flicking.element;
|
|
126
|
+
// 현재 구현에서 리사이즈 옵저빙 대상은 패널과 뷰포트 2개만 존재.
|
|
127
|
+
// 아래는 뷰포트만 변경되었을 때 동작해야하는 로직이 있으므로 아래와 같이 조건문을 건다.
|
|
128
|
+
// 패널 쪽에서는 리사이즈 감지에 resizeObserver를 사용하지 않는 경우가 없으므로 이 조건은 곧 뷰포트만 리사이즈가 된 경우를 의미한다.
|
|
129
|
+
const isResizedViewportOnly = entries.find(e => e.target === flicking.element) && entries.length === 1;
|
|
130
|
+
|
|
131
|
+
// 참고: resizeObserver를 사용하지 않은 경우에는 entries.length가 0으로 오는데 이 경우에는 그냥 항상 리사이즈가 진행되도록 한다.
|
|
132
|
+
// (vw, vh 등을 사용하는 경우 이상 동작이 발생할 여지가 있기 때문이다)
|
|
133
|
+
if (isResizedViewportOnly) {
|
|
134
|
+
// resize 이벤트가 발생했으나 이전과 width, height의 변화가 없다면 이후 로직을 진행하지 않는다.
|
|
135
|
+
const beforeSize = {
|
|
136
|
+
width: flicking.viewport.width,
|
|
137
|
+
height: flicking.viewport.height
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const afterSize = {
|
|
141
|
+
width: getElementSize({
|
|
142
|
+
el: resizedViewportElement,
|
|
143
|
+
horizontal: true,
|
|
144
|
+
useFractionalSize: this._flicking.useFractionalSize,
|
|
145
|
+
useOffset: false,
|
|
146
|
+
style: getStyle(resizedViewportElement)
|
|
147
|
+
}),
|
|
148
|
+
height: getElementSize({
|
|
149
|
+
el: resizedViewportElement,
|
|
150
|
+
horizontal: false,
|
|
151
|
+
useFractionalSize: this._flicking.useFractionalSize,
|
|
152
|
+
useOffset: false,
|
|
153
|
+
style: getStyle(resizedViewportElement)
|
|
154
|
+
})
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
if (
|
|
158
|
+
beforeSize.height === afterSize.height &&
|
|
159
|
+
beforeSize.width === afterSize.width
|
|
160
|
+
) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
72
165
|
if (resizeDebounce <= 0) {
|
|
73
166
|
void flicking.resize();
|
|
74
167
|
} else {
|
|
75
168
|
if (this._maxResizeDebounceTimer <= 0) {
|
|
76
169
|
if (maxResizeDebounce > 0 && maxResizeDebounce >= resizeDebounce) {
|
|
77
|
-
this._maxResizeDebounceTimer = window.setTimeout(
|
|
170
|
+
this._maxResizeDebounceTimer = window.setTimeout(
|
|
171
|
+
this._doScheduledResize,
|
|
172
|
+
maxResizeDebounce
|
|
173
|
+
);
|
|
78
174
|
}
|
|
79
175
|
}
|
|
80
176
|
|
|
@@ -83,7 +179,10 @@ class AutoResizer {
|
|
|
83
179
|
this._resizeTimer = 0;
|
|
84
180
|
}
|
|
85
181
|
|
|
86
|
-
this._resizeTimer = window.setTimeout(
|
|
182
|
+
this._resizeTimer = window.setTimeout(
|
|
183
|
+
this._doScheduledResize,
|
|
184
|
+
resizeDebounce
|
|
185
|
+
);
|
|
87
186
|
}
|
|
88
187
|
};
|
|
89
188
|
|
|
@@ -101,13 +200,13 @@ class AutoResizer {
|
|
|
101
200
|
private _skipFirstResize = (() => {
|
|
102
201
|
let isFirstResize = true;
|
|
103
202
|
|
|
104
|
-
return (
|
|
203
|
+
return (entries) => {
|
|
105
204
|
if (isFirstResize) {
|
|
106
205
|
isFirstResize = false;
|
|
107
206
|
return;
|
|
108
207
|
}
|
|
109
|
-
this._onResize();
|
|
110
|
-
}
|
|
208
|
+
this._onResize(entries);
|
|
209
|
+
};
|
|
111
210
|
})();
|
|
112
211
|
}
|
|
113
212
|
|
package/src/index.ts
CHANGED
package/src/index.umd.ts
CHANGED
|
@@ -10,6 +10,7 @@ import * as Control from "./control";
|
|
|
10
10
|
import * as Renderer from "./renderer";
|
|
11
11
|
import * as Constants from "./const/external";
|
|
12
12
|
import * as CFC from "./cfc";
|
|
13
|
+
import * as FlickingReactiveAPI from "./reactive";
|
|
13
14
|
import * as Utils from "./utils";
|
|
14
15
|
import { merge } from "./utils";
|
|
15
16
|
|
|
@@ -21,5 +22,6 @@ merge(Flicking, Constants);
|
|
|
21
22
|
merge(Flicking, CFC);
|
|
22
23
|
merge(Flicking, Utils);
|
|
23
24
|
merge(Flicking, CrossFlicking);
|
|
25
|
+
merge(Flicking, FlickingReactiveAPI);
|
|
24
26
|
|
|
25
27
|
export default Flicking;
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import {
|
|
2
|
+
adaptReactive,
|
|
3
|
+
reactive,
|
|
4
|
+
ReactiveObject,
|
|
5
|
+
ReactiveSetupAdapter
|
|
6
|
+
} from "@cfcs/core";
|
|
7
|
+
|
|
8
|
+
import Flicking from "../Flicking";
|
|
9
|
+
|
|
10
|
+
// Check if Flicking has reached the first panel
|
|
11
|
+
const getIsReachStart = (flicking: Flicking) => !flicking.circular && flicking.index === 0;
|
|
12
|
+
|
|
13
|
+
// Check if Flicking has reached the last panel
|
|
14
|
+
const getIsReachEnd = (flicking: Flicking) => !flicking.circular && flicking.index === flicking.panelCount - 1;
|
|
15
|
+
|
|
16
|
+
// Get the total number of panels
|
|
17
|
+
const getTotalPanelCount = (flicking: Flicking) => flicking.panelCount;
|
|
18
|
+
|
|
19
|
+
// Get the current active panel index
|
|
20
|
+
const getCurrentPanelIndex = (flicking: Flicking) => flicking.index;
|
|
21
|
+
|
|
22
|
+
// Calculate the overall scroll progress percentage based on the current camera position
|
|
23
|
+
const getProgress = (flicking: Flicking) => {
|
|
24
|
+
const cam = flicking.camera;
|
|
25
|
+
|
|
26
|
+
const progressRatio = (cam.position - cam.range.min) / (cam.range.max - cam.range.min);
|
|
27
|
+
|
|
28
|
+
const percent = Math.min(Math.max(progressRatio, 0), 1) * 100;
|
|
29
|
+
|
|
30
|
+
return percent;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Calculate the progress between panels including decimal values
|
|
34
|
+
const getIndexProgress = (flicking: Flicking) => {
|
|
35
|
+
const cam = flicking.camera;
|
|
36
|
+
const anchorPoints = cam.anchorPoints;
|
|
37
|
+
const length = anchorPoints.length;
|
|
38
|
+
const cameraPosition = cam.position;
|
|
39
|
+
const isCircular = flicking.circularEnabled;
|
|
40
|
+
let indexProgress = 0;
|
|
41
|
+
|
|
42
|
+
const { min, max } = cam.range;
|
|
43
|
+
const firstAnchorPoint = anchorPoints[0];
|
|
44
|
+
const lastAnchorPoint = anchorPoints[length - 1];
|
|
45
|
+
const distanceLastToFirst = (max - lastAnchorPoint.position) + (firstAnchorPoint.position - min);
|
|
46
|
+
|
|
47
|
+
anchorPoints.some((anchorPoint, index) => {
|
|
48
|
+
const anchorPosition = anchorPoint.position;
|
|
49
|
+
const nextAnchorPoint = anchorPoints[index + 1];
|
|
50
|
+
|
|
51
|
+
if (index === 0 && cameraPosition <= anchorPosition) {
|
|
52
|
+
if (isCircular) {
|
|
53
|
+
indexProgress = (cameraPosition - anchorPosition) / distanceLastToFirst;
|
|
54
|
+
} else {
|
|
55
|
+
indexProgress = (cameraPosition - anchorPosition) / anchorPoint.panel.size;
|
|
56
|
+
}
|
|
57
|
+
} else if (index === length - 1 && cameraPosition >= anchorPosition) {
|
|
58
|
+
if (isCircular) {
|
|
59
|
+
indexProgress = index + (cameraPosition - anchorPosition) / distanceLastToFirst;
|
|
60
|
+
} else {
|
|
61
|
+
indexProgress = index + (cameraPosition - anchorPosition) / anchorPoint.panel.size;
|
|
62
|
+
}
|
|
63
|
+
} else if (nextAnchorPoint && anchorPosition <= cameraPosition && cameraPosition <= nextAnchorPoint.position) {
|
|
64
|
+
indexProgress = index + (cameraPosition - anchorPosition) / (nextAnchorPoint.position - anchorPosition);
|
|
65
|
+
} else {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
return indexProgress;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Reactive object type that combines state and methods for Flicking
|
|
76
|
+
* This type provides reactive state properties and methods that automatically update
|
|
77
|
+
* when the Flicking instance state changes.
|
|
78
|
+
* @ko Flicking의 상태와 메서드를 결합한 반응형 객체 타입
|
|
79
|
+
* 이 타입은 Flicking 인스턴스의 상태가 변경될 때 자동으로 업데이트되는
|
|
80
|
+
* 반응형 상태 속성들과 메서드들을 제공합니다.
|
|
81
|
+
* @typedef
|
|
82
|
+
* @see {@link https://naver.github.io/egjs-flicking/Demos#reactive-api-demo}
|
|
83
|
+
* @example
|
|
84
|
+
* ```jsx
|
|
85
|
+
* const flickingRef = React.useRef(null);
|
|
86
|
+
* const {
|
|
87
|
+
* progress
|
|
88
|
+
* } = useFlickingReactiveAPI(flickingRef);
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export type FlickingReactiveObject = ReactiveObject<FlickingReactiveState & FlickingReactiveMethod>;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Reactive state properties for Flicking
|
|
95
|
+
* @ko Flicking의 반응형 상태 속성들
|
|
96
|
+
* @typedef
|
|
97
|
+
*/
|
|
98
|
+
export interface FlickingReactiveState {
|
|
99
|
+
/**
|
|
100
|
+
* Whether Flicking has reached the first panel<ko>첫 번째 패널에 도달했는지 여부</ko>
|
|
101
|
+
*/
|
|
102
|
+
isReachStart: boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Whether Flicking has reached the last panel<ko>마지막 패널에 도달했는지 여부</ko>
|
|
105
|
+
*/
|
|
106
|
+
isReachEnd: boolean;
|
|
107
|
+
/**
|
|
108
|
+
* Total number of panels<ko>전체 패널 개수</ko>
|
|
109
|
+
*/
|
|
110
|
+
totalPanelCount: number;
|
|
111
|
+
/**
|
|
112
|
+
* Current active panel index<ko>현재 활성화된 패널의 인덱스</ko>
|
|
113
|
+
*/
|
|
114
|
+
currentPanelIndex: number;
|
|
115
|
+
/**
|
|
116
|
+
* Overall scroll progress percentage (0-100)<ko>전체 스크롤 진행률 (0-100)</ko>
|
|
117
|
+
*/
|
|
118
|
+
progress: number;
|
|
119
|
+
/**
|
|
120
|
+
* Panel progress with decimal values<ko>소수점을 포함한 패널 진행률</ko>
|
|
121
|
+
*/
|
|
122
|
+
indexProgress: number;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Reactive methods for Flicking
|
|
127
|
+
* @ko Flicking의 반응형 메서드들
|
|
128
|
+
* @typedef
|
|
129
|
+
*/
|
|
130
|
+
export interface FlickingReactiveMethod {
|
|
131
|
+
/**
|
|
132
|
+
* Move to a specific panel index<ko>특정 패널 인덱스로 이동</ko>
|
|
133
|
+
* @param i - Target panel index<ko>목표 패널 인덱스</ko>
|
|
134
|
+
* @returns Promise that resolves when movement is complete<ko>이동이 완료되면 resolve되는 Promise</ko>
|
|
135
|
+
*/
|
|
136
|
+
moveTo: (i: number) => Promise<void>;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Data required for reactive API setup
|
|
141
|
+
* @ko 반응형 API 설정에 필요한 데이터
|
|
142
|
+
* @typedef
|
|
143
|
+
*/
|
|
144
|
+
export interface FlickingReactiveData {
|
|
145
|
+
/**
|
|
146
|
+
* Flicking instance to connect<ko>연결할 Flicking 인스턴스</ko>
|
|
147
|
+
*/
|
|
148
|
+
flicking?: Flicking;
|
|
149
|
+
/**
|
|
150
|
+
* Flicking options used for initialization<ko>초기화에 사용되는 Flicking 옵션</ko>
|
|
151
|
+
*/
|
|
152
|
+
options?: FlickingReactiveAPIOptions;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Options for Flicking reactive API that help optimize initial rendering in SSR scenarios
|
|
157
|
+
* These options allow you to set initial state values before the Flicking instance is fully initialized,
|
|
158
|
+
* preventing unnecessary re-renders when the actual Flicking state is applied.
|
|
159
|
+
* @ko SSR 상황 등에서 초기 렌더링을 최적화할 수 있게 하는 Flicking 반응형 API 옵션
|
|
160
|
+
* 이 옵션들을 통해 Flicking 인스턴스가 완전히 초기화되기 전에 초기 상태 값을 설정할 수 있어,
|
|
161
|
+
* 실제 Flicking 상태가 적용될 때 불필요한 리렌더링을 방지할 수 있습니다.
|
|
162
|
+
* @typedef
|
|
163
|
+
* @example
|
|
164
|
+
* ```js
|
|
165
|
+
* const options = {
|
|
166
|
+
* defaultIndex: 2,
|
|
167
|
+
* totalPanelCount: 5
|
|
168
|
+
* };
|
|
169
|
+
* const reactiveObj = connectFlickingReactiveAPI(flicking, options);
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
export interface FlickingReactiveAPIOptions {
|
|
173
|
+
/**
|
|
174
|
+
* Initial panel index to start with. This sets the currentPanelIndex and indexProgress initial values.
|
|
175
|
+
* Also affects isReachStart and isReachEnd initial value setting.
|
|
176
|
+
* @ko 시작할 초기 패널 인덱스. currentPanelIndex와 indexProgress의 초기값을 설정합니다.
|
|
177
|
+
* 또한 isReachStart, isReachEnd 초기값 계산에도 영향을 줍니다.
|
|
178
|
+
* @default 0
|
|
179
|
+
*/
|
|
180
|
+
defaultIndex?: number;
|
|
181
|
+
/**
|
|
182
|
+
* Total number of panels in the Flicking instance. This sets the totalPanelCount initial value
|
|
183
|
+
* and helps prevent layout shifts during SSR hydration.
|
|
184
|
+
* @ko Flicking 인스턴스의 전체 패널 개수. totalPanelCount의 초기값을 설정하며
|
|
185
|
+
* SSR 하이드레이션 과정에서 레이아웃 시프트를 방지하는 데 도움이 됩니다.
|
|
186
|
+
* @default 0
|
|
187
|
+
*/
|
|
188
|
+
totalPanelCount?: number;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Internal reactive API adapter for Flicking that manages state and event listeners
|
|
194
|
+
* This adapter is used internally by framework-specific packages (react-flicking, vue-flicking, etc.)
|
|
195
|
+
* to provide reactive API support. Users rarely need to use this directly.
|
|
196
|
+
* @ko Flicking의 상태와 이벤트 리스너를 관리하는 내부 반응형 API 어댑터
|
|
197
|
+
* 이 어댑터는 react-flicking, vue-flicking 등의 프레임워크별 패키지에서 내부적으로 사용되어
|
|
198
|
+
* 반응형 API 지원을 제공합니다. 사용자가 직접 사용할 일은 거의 없습니다.
|
|
199
|
+
* @param onInit - Callback when reactive object is initialized<ko>반응형 객체가 초기화될 때 호출되는 콜백</ko>
|
|
200
|
+
* @param onDestroy - Callback when reactive object is destroyed<ko>반응형 객체가 파괴될 때 호출되는 콜백</ko>
|
|
201
|
+
* @param setMethods - Function to set available methods<ko>사용 가능한 메서드를 설정하는 함수</ko>
|
|
202
|
+
* @returns Reactive object with Flicking state and methods<ko>Flicking 상태와 메서드를 포함한 반응형 객체</ko>
|
|
203
|
+
*/
|
|
204
|
+
const flickingReactiveAPIAdapter: ReactiveSetupAdapter<
|
|
205
|
+
FlickingReactiveObject,
|
|
206
|
+
FlickingReactiveState,
|
|
207
|
+
"moveTo",
|
|
208
|
+
FlickingReactiveData
|
|
209
|
+
> = ({ onInit, onDestroy, setMethods, getProps }) => {
|
|
210
|
+
let flicking: Flicking | undefined;
|
|
211
|
+
|
|
212
|
+
// Move to a specific panel index
|
|
213
|
+
const moveTo = (i: number) => {
|
|
214
|
+
if (flicking == null) {
|
|
215
|
+
return Promise.reject(new Error("Flicking instance is not available"));
|
|
216
|
+
}
|
|
217
|
+
if (flicking?.animating) {
|
|
218
|
+
return Promise.resolve();
|
|
219
|
+
}
|
|
220
|
+
return flicking.moveTo(i);
|
|
221
|
+
};
|
|
222
|
+
setMethods(["moveTo"]);
|
|
223
|
+
|
|
224
|
+
const options = getProps().options;
|
|
225
|
+
|
|
226
|
+
// options를 고려하지 않고 초기값을 설정해도 동작에는 아무런 문제가 없으나, 이 시점의 초기값과 컴포넌트 init 단계에서의 초기값이 다르면 화면 리렌더링이 발생할 수 있으므로
|
|
227
|
+
// 이렇게 미리 옵션을 통해서 예측할 수 있는 부분들은 맞춰둔다.
|
|
228
|
+
const reactiveObj: FlickingReactiveObject = reactive({
|
|
229
|
+
isReachStart: options?.defaultIndex ? options?.defaultIndex === 0 : true,
|
|
230
|
+
isReachEnd: (options?.totalPanelCount && options?.defaultIndex) ? (options.defaultIndex === options.totalPanelCount - 1) : false,
|
|
231
|
+
totalPanelCount: options?.totalPanelCount ?? 0,
|
|
232
|
+
currentPanelIndex: options?.defaultIndex ?? 0,
|
|
233
|
+
progress: 0,
|
|
234
|
+
indexProgress: options?.defaultIndex ?? 0,
|
|
235
|
+
moveTo
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
// Update state when panel changes
|
|
239
|
+
const onChanged = () => {
|
|
240
|
+
if (flicking === undefined) return;
|
|
241
|
+
|
|
242
|
+
reactiveObj.isReachStart = getIsReachStart(flicking);
|
|
243
|
+
reactiveObj.isReachEnd = getIsReachEnd(flicking);
|
|
244
|
+
reactiveObj.currentPanelIndex = getCurrentPanelIndex(flicking);
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
// Update state when panel count changes
|
|
248
|
+
const onPanelChange = () => {
|
|
249
|
+
if (flicking === undefined) return;
|
|
250
|
+
|
|
251
|
+
onChanged();
|
|
252
|
+
reactiveObj.totalPanelCount = getTotalPanelCount(flicking);
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// Update progress when camera moves
|
|
256
|
+
const onMove = () => {
|
|
257
|
+
if (flicking === undefined) return;
|
|
258
|
+
|
|
259
|
+
reactiveObj.progress = getProgress(flicking);
|
|
260
|
+
reactiveObj.indexProgress = getIndexProgress(flicking);
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
onInit((inst, data) => {
|
|
264
|
+
flicking = data.flicking;
|
|
265
|
+
if (flicking === undefined) return;
|
|
266
|
+
|
|
267
|
+
reactiveObj.isReachStart = getIsReachStart(flicking);
|
|
268
|
+
reactiveObj.isReachEnd = getIsReachEnd(flicking);
|
|
269
|
+
reactiveObj.currentPanelIndex = getCurrentPanelIndex(flicking);
|
|
270
|
+
reactiveObj.progress = getProgress(flicking);
|
|
271
|
+
|
|
272
|
+
reactiveObj.totalPanelCount = getTotalPanelCount(flicking);
|
|
273
|
+
|
|
274
|
+
flicking?.on("changed", onChanged);
|
|
275
|
+
flicking?.on("panelChange", onPanelChange);
|
|
276
|
+
flicking?.on("move", onMove);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
onDestroy(() => {
|
|
280
|
+
flicking?.off("changed", onChanged);
|
|
281
|
+
flicking?.off("panelChange", onPanelChange);
|
|
282
|
+
flicking?.off("move", onMove);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
return reactiveObj;
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Connect Flicking instance to reactive API
|
|
290
|
+
* @ko Flicking 인스턴스를 반응형 API에 연결합니다
|
|
291
|
+
* @param {Flicking} flicking - Flicking instance to connect<ko>연결할 Flicking 인스턴스</ko>
|
|
292
|
+
* @param {FlickingReactiveAPIOptions} [options] - Flicking options<ko>Flicking 옵션</ko>
|
|
293
|
+
* @returns {FlickingReactiveObject} Reactive object with Flicking state and methods<ko>Flicking 상태와 메서드를 포함한 반응형 객체</ko>
|
|
294
|
+
* @example
|
|
295
|
+
* ```js
|
|
296
|
+
* import Flicking, { connectFlickingReactiveAPI } from "@egjs/flicking";
|
|
297
|
+
*
|
|
298
|
+
* const flicking = new Flicking("#el");
|
|
299
|
+
* const reactiveObj = connectFlickingReactiveAPI(flicking);
|
|
300
|
+
*
|
|
301
|
+
* // Access reactive state
|
|
302
|
+
* console.log("Current panel:", reactiveObj.currentPanelIndex);
|
|
303
|
+
* console.log("Progress:", reactiveObj.progress + "%");
|
|
304
|
+
* console.log("Is at start:", reactiveObj.isReachStart);
|
|
305
|
+
* console.log("Is at end:", reactiveObj.isReachEnd);
|
|
306
|
+
* console.log("Total panels:", reactiveObj.totalPanelCount);
|
|
307
|
+
* console.log("Index progress:", reactiveObj.indexProgress);
|
|
308
|
+
*
|
|
309
|
+
* // Subscribe to state changes
|
|
310
|
+
* reactiveObj.subscribe("currentPanelIndex", (nextValue) => {
|
|
311
|
+
* console.log("Panel changed to:", nextValue);
|
|
312
|
+
* });
|
|
313
|
+
*
|
|
314
|
+
* // Use reactive methods
|
|
315
|
+
* reactiveObj.moveTo(2); // Move to third panel
|
|
316
|
+
* ```
|
|
317
|
+
*/
|
|
318
|
+
const connectFlickingReactiveAPI = (flicking: Flicking, options?: FlickingReactiveAPIOptions) => {
|
|
319
|
+
const obj = adaptReactive(flickingReactiveAPIAdapter, () => ({ flicking, options }));
|
|
320
|
+
obj.mounted();
|
|
321
|
+
const instance = obj.instance();
|
|
322
|
+
obj.init();
|
|
323
|
+
return instance;
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
export { flickingReactiveAPIAdapter, connectFlickingReactiveAPI };
|
package/src/renderer/Renderer.ts
CHANGED
|
@@ -335,6 +335,19 @@ abstract class Renderer {
|
|
|
335
335
|
// Update camera & control
|
|
336
336
|
this._updateCameraAndControl();
|
|
337
337
|
|
|
338
|
+
if (flicking.autoResize && flicking.useResizeObserver) {
|
|
339
|
+
panelsAdded.forEach((panel) => {
|
|
340
|
+
if (panel.element) {
|
|
341
|
+
flicking.autoResizer.observe(panel.element);
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
panelsRemoved.forEach((panel) => {
|
|
345
|
+
if (panel.element) {
|
|
346
|
+
flicking.autoResizer.unobserve(panel.element);
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
338
351
|
void this.render();
|
|
339
352
|
|
|
340
353
|
if (!flicking.animating) {
|
package/src/utils.ts
CHANGED
|
@@ -256,7 +256,12 @@ export const findIndex = <T>(array: T[], checker: (val: T) => boolean): number =
|
|
|
256
256
|
export const getProgress = (pos: number, prev: number, next: number) => (pos - prev) / (next - prev);
|
|
257
257
|
|
|
258
258
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
259
|
-
export const getStyle = (el: HTMLElement): CSSStyleDeclaration =>
|
|
259
|
+
export const getStyle = (el: HTMLElement): CSSStyleDeclaration => {
|
|
260
|
+
if (!el) {
|
|
261
|
+
return {} as CSSStyleDeclaration;
|
|
262
|
+
}
|
|
263
|
+
return window.getComputedStyle(el) || (el as any).currentStyle as CSSStyleDeclaration;
|
|
264
|
+
};
|
|
260
265
|
|
|
261
266
|
export const setSize = (el: HTMLElement, { width, height }: Partial<{
|
|
262
267
|
width: number | string;
|