@rdlabo/ionic-theme-ios26 1.0.0 → 1.0.1
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/README.md +2 -0
- package/dist/css/components/ion-button.css +1 -1
- package/dist/css/components/ion-toolbar.css +1 -1
- package/dist/css/ionic-theme-ios26.css +1 -1
- package/dist/gestures/animations.d.ts +8 -0
- package/dist/gestures/animations.d.ts.map +1 -0
- package/dist/gestures/animations.js +97 -0
- package/dist/gestures/index.d.ts.map +1 -1
- package/dist/gestures/index.js +77 -160
- package/dist/gestures/interfaces.d.ts +6 -0
- package/dist/gestures/interfaces.d.ts.map +1 -1
- package/dist/gestures/utils.d.ts +3 -1
- package/dist/gestures/utils.d.ts.map +1 -1
- package/dist/gestures/utils.js +14 -11
- package/dist/index.js +2 -2
- package/package.json +2 -2
- package/src/gestures/animations.ts +120 -0
- package/src/gestures/index.ts +83 -176
- package/src/gestures/interfaces.ts +7 -0
- package/src/gestures/utils.ts +22 -12
- package/src/index.ts +2 -2
- package/src/styles/components/ion-button.scss +11 -1
package/src/gestures/index.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { EffectScales, registeredEffect } from './interfaces';
|
|
2
|
-
import { createGesture, GestureDetail
|
|
1
|
+
import { AnimationPosition, EffectScales, registeredEffect } from './interfaces';
|
|
2
|
+
import { createAnimation, createGesture, GestureDetail } from '@ionic/core';
|
|
3
3
|
import type { Animation } from '@ionic/core/dist/types/utils/animation/animation-interface';
|
|
4
4
|
import { Gesture } from '@ionic/core/dist/types/utils/gesture';
|
|
5
|
-
import { cloneElement,
|
|
5
|
+
import { changeSelectedElement, cloneElement, getStep } from './utils';
|
|
6
|
+
import { createMoveAnimation, createPreMoveAnimation, getMoveAnimationKeyframe, getScaleAnimation } from './animations';
|
|
6
7
|
|
|
7
8
|
const GESTURE_NAME = 'ios26-enable-gesture';
|
|
8
9
|
const ANIMATED_NAME = 'ios26-animated';
|
|
@@ -20,13 +21,11 @@ export const registerEffect = (
|
|
|
20
21
|
let gesture!: Gesture;
|
|
21
22
|
let moveAnimation: Animation | undefined;
|
|
22
23
|
let currentTouchedElement: HTMLElement | undefined;
|
|
23
|
-
let animationLatestX: number | undefined;
|
|
24
|
-
let effectElementPositionY: number | undefined;
|
|
25
|
-
|
|
26
|
-
let enterAnimationPromise: Promise<void> | undefined;
|
|
27
|
-
let moveAnimationPromise: Promise<void> | undefined;
|
|
28
24
|
let clearActivatedTimer: ReturnType<typeof setTimeout> | undefined;
|
|
29
|
-
|
|
25
|
+
let animationPosition: AnimationPosition | undefined = undefined;
|
|
26
|
+
let scaleAnimationPromise: Promise<void> | undefined;
|
|
27
|
+
let startAnimationPromise: Promise<void> | undefined;
|
|
28
|
+
let maxVelocity = 0;
|
|
30
29
|
const effectElement = cloneElement(effectTagName);
|
|
31
30
|
|
|
32
31
|
/**
|
|
@@ -35,14 +34,12 @@ export const registerEffect = (
|
|
|
35
34
|
*/
|
|
36
35
|
const onPointerDown = () => {
|
|
37
36
|
clearActivated();
|
|
38
|
-
currentTouchedElement?.classList.remove('ion-activated');
|
|
39
37
|
gesture.destroy();
|
|
40
38
|
createAnimationGesture();
|
|
41
39
|
};
|
|
42
40
|
const onPointerUp = (event: PointerEvent) => {
|
|
43
|
-
clearActivatedTimer = setTimeout(() => {
|
|
44
|
-
onEndGesture();
|
|
45
|
-
currentTouchedElement?.classList.remove('ion-activated');
|
|
41
|
+
clearActivatedTimer = setTimeout(async () => {
|
|
42
|
+
await onEndGesture();
|
|
46
43
|
gesture.destroy();
|
|
47
44
|
createAnimationGesture();
|
|
48
45
|
});
|
|
@@ -59,7 +56,9 @@ export const registerEffect = (
|
|
|
59
56
|
gestureName: `${GESTURE_NAME}_${effectTagName}_${crypto.randomUUID()}`,
|
|
60
57
|
onStart: (event) => onStartGesture(event),
|
|
61
58
|
onMove: (event) => onMoveGesture(event),
|
|
62
|
-
onEnd: () =>
|
|
59
|
+
onEnd: () => {
|
|
60
|
+
onEndGesture().then();
|
|
61
|
+
},
|
|
63
62
|
});
|
|
64
63
|
gesture.enable(true);
|
|
65
64
|
};
|
|
@@ -69,197 +68,106 @@ export const registerEffect = (
|
|
|
69
68
|
if (!currentTouchedElement) {
|
|
70
69
|
return;
|
|
71
70
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
effectElement.innerHTML = '';
|
|
75
|
-
effectElement.style.transform = 'none';
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
targetElement.classList.remove(ANIMATED_NAME);
|
|
71
|
+
currentTouchedElement!.click();
|
|
72
|
+
currentTouchedElement?.classList.remove('ion-activated');
|
|
79
73
|
currentTouchedElement = undefined;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
74
|
+
effectElement.style.display = 'none';
|
|
75
|
+
maxVelocity = 0;
|
|
76
|
+
targetElement.classList.remove(ANIMATED_NAME);
|
|
83
77
|
};
|
|
84
78
|
|
|
85
79
|
const onStartGesture = (detail: GestureDetail): boolean | undefined => {
|
|
86
|
-
enterAnimationPromise = undefined;
|
|
87
80
|
currentTouchedElement = ((detail.event.target as HTMLElement).closest(effectTagName) as HTMLElement) || undefined;
|
|
88
81
|
const tabSelectedElement = targetElement.querySelector(`${effectTagName}.${selectedClassName}`);
|
|
89
82
|
if (currentTouchedElement === undefined || tabSelectedElement === null) {
|
|
90
83
|
return false;
|
|
91
84
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
currentTouchedElement
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
tabSelectedElement.childNodes.forEach((node) => {
|
|
116
|
-
effectElement.appendChild(node.cloneNode(true));
|
|
117
|
-
});
|
|
118
|
-
targetElement.classList.add(ANIMATED_NAME);
|
|
119
|
-
currentTouchedElement!.classList.add('ion-activated');
|
|
120
|
-
currentTouchedElement!.click();
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
if (currentTouchedElement === tabSelectedElement) {
|
|
124
|
-
enterAnimation
|
|
125
|
-
.keyframes([
|
|
126
|
-
{
|
|
127
|
-
transform: `${startTransform} ${scales.small}`,
|
|
128
|
-
opacity: 1,
|
|
129
|
-
offset: 0,
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
transform: `${middleTransform} ${scales.large}`,
|
|
133
|
-
opacity: 1,
|
|
134
|
-
offset: 0.6,
|
|
135
|
-
},
|
|
136
|
-
{
|
|
137
|
-
transform: `${endTransform} ${scales.medium}`,
|
|
138
|
-
opacity: 1,
|
|
139
|
-
offset: 1,
|
|
140
|
-
},
|
|
141
|
-
])
|
|
142
|
-
.duration(160);
|
|
143
|
-
} else {
|
|
144
|
-
enterAnimation
|
|
145
|
-
.keyframes([
|
|
146
|
-
{
|
|
147
|
-
transform: `${startTransform} ${scales.small}`,
|
|
148
|
-
opacity: 1,
|
|
149
|
-
offset: 0,
|
|
150
|
-
},
|
|
151
|
-
{
|
|
152
|
-
transform: `${middleTransform} ${scales.large}`,
|
|
153
|
-
opacity: 1,
|
|
154
|
-
offset: 0.65,
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
transform: `${endTransform} ${scales.medium}`,
|
|
158
|
-
opacity: 1,
|
|
159
|
-
offset: 1,
|
|
160
|
-
},
|
|
161
|
-
])
|
|
162
|
-
.duration(280);
|
|
163
|
-
}
|
|
164
|
-
animationLatestX = detail.currentX;
|
|
165
|
-
enterAnimationPromise = enterAnimation.play().then(() => {
|
|
166
|
-
enterAnimationPromise = undefined;
|
|
85
|
+
animationPosition = {
|
|
86
|
+
minPositionX: targetElement.getBoundingClientRect().left,
|
|
87
|
+
maxPositionX: targetElement.getBoundingClientRect().right - tabSelectedElement.clientWidth,
|
|
88
|
+
width: tabSelectedElement.clientWidth,
|
|
89
|
+
positionY: tabSelectedElement.getBoundingClientRect().top,
|
|
90
|
+
};
|
|
91
|
+
targetElement.classList.add(ANIMATED_NAME);
|
|
92
|
+
changeSelectedElement(targetElement, currentTouchedElement, effectTagName, selectedClassName);
|
|
93
|
+
|
|
94
|
+
startAnimationPromise = (() => {
|
|
95
|
+
if (tabSelectedElement === currentTouchedElement) {
|
|
96
|
+
return new Promise<void>((resolve) => resolve());
|
|
97
|
+
} else {
|
|
98
|
+
const preMoveAnimation = createPreMoveAnimation(effectElement, tabSelectedElement, currentTouchedElement, animationPosition!);
|
|
99
|
+
return preMoveAnimation.play().finally(() => preMoveAnimation.destroy());
|
|
100
|
+
}
|
|
101
|
+
})();
|
|
102
|
+
startAnimationPromise.then(() => {
|
|
103
|
+
moveAnimation = createMoveAnimation(effectElement, detail, tabSelectedElement, animationPosition!);
|
|
104
|
+
moveAnimation.progressStart(
|
|
105
|
+
true,
|
|
106
|
+
getStep(currentTouchedElement!.getBoundingClientRect().left + currentTouchedElement!.clientWidth / 2, animationPosition!),
|
|
107
|
+
);
|
|
167
108
|
});
|
|
109
|
+
getScaleAnimation(effectElement).duration(200).to('opacity', 1).to('transform', scales.large).play();
|
|
168
110
|
return true;
|
|
169
111
|
};
|
|
170
112
|
|
|
171
113
|
const onMoveGesture = (detail: GestureDetail): boolean | undefined => {
|
|
172
|
-
if (currentTouchedElement === undefined ||
|
|
173
|
-
return
|
|
114
|
+
if (currentTouchedElement === undefined || !moveAnimation) {
|
|
115
|
+
return false; // Skip Animation
|
|
174
116
|
}
|
|
117
|
+
if (scaleAnimationPromise === undefined) {
|
|
118
|
+
if (Math.abs(detail.velocityX) > maxVelocity) {
|
|
119
|
+
maxVelocity = Math.abs(detail.velocityX);
|
|
120
|
+
}
|
|
121
|
+
if (Math.abs(detail.velocityX) > 0.2) {
|
|
122
|
+
scaleAnimationPromise = getScaleAnimation(effectElement)
|
|
123
|
+
.duration(720)
|
|
124
|
+
.keyframes(getMoveAnimationKeyframe('slowly', scales))
|
|
125
|
+
.play()
|
|
126
|
+
.finally(() => (scaleAnimationPromise = undefined));
|
|
127
|
+
}
|
|
128
|
+
if (maxVelocity > 0.2 && Math.abs(detail.velocityX) < 0.15 && Math.abs(detail.startX - detail.currentX) > 100) {
|
|
129
|
+
scaleAnimationPromise = getScaleAnimation(effectElement)
|
|
130
|
+
.duration(720)
|
|
131
|
+
.keyframes(getMoveAnimationKeyframe(detail.velocityX > 0 ? 'moveRight' : 'moveLeft', scales))
|
|
132
|
+
.play()
|
|
133
|
+
.finally(() => (scaleAnimationPromise = undefined));
|
|
134
|
+
maxVelocity = 0;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const latestTouchedElement = ((detail.event.target as HTMLElement).closest(effectTagName) as HTMLElement) || undefined;
|
|
175
138
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
// Move用のアニメーションオブジェクトを初回のみ作成し、再利用する
|
|
180
|
-
if (!moveAnimation) {
|
|
181
|
-
moveAnimation = createAnimation();
|
|
182
|
-
moveAnimation
|
|
183
|
-
.addElement(effectElement)
|
|
184
|
-
.duration(800)
|
|
185
|
-
.easing('ease-in-out')
|
|
186
|
-
.keyframes([
|
|
187
|
-
{
|
|
188
|
-
transform: `${startTransform} ${scales.medium}`,
|
|
189
|
-
opacity: 1,
|
|
190
|
-
offset: 0,
|
|
191
|
-
},
|
|
192
|
-
{
|
|
193
|
-
transform: `${startTransform} ${scales.xlarge}`,
|
|
194
|
-
opacity: 1,
|
|
195
|
-
offset: 0.2,
|
|
196
|
-
},
|
|
197
|
-
{
|
|
198
|
-
transform: `${endTransform} ${scales.medium}`,
|
|
199
|
-
opacity: 1,
|
|
200
|
-
offset: 1,
|
|
201
|
-
},
|
|
202
|
-
]);
|
|
203
|
-
} else {
|
|
204
|
-
moveAnimation.duration(0).keyframes([
|
|
205
|
-
{
|
|
206
|
-
transform: `${endTransform} ${scales.medium}`,
|
|
207
|
-
opacity: 1,
|
|
208
|
-
offset: 1,
|
|
209
|
-
},
|
|
210
|
-
{
|
|
211
|
-
transform: `${endTransform} ${scales.medium}`,
|
|
212
|
-
opacity: 1,
|
|
213
|
-
offset: 1,
|
|
214
|
-
},
|
|
215
|
-
]);
|
|
139
|
+
if (latestTouchedElement && currentTouchedElement !== latestTouchedElement) {
|
|
140
|
+
currentTouchedElement = latestTouchedElement;
|
|
141
|
+
changeSelectedElement(targetElement, currentTouchedElement, effectTagName, selectedClassName);
|
|
216
142
|
}
|
|
217
|
-
|
|
218
|
-
moveAnimationPromise = moveAnimation.play().then(() => {
|
|
219
|
-
moveAnimationPromise = undefined;
|
|
220
|
-
});
|
|
143
|
+
moveAnimation.progressStep(getStep(detail.currentX, animationPosition!));
|
|
221
144
|
return true;
|
|
222
145
|
};
|
|
223
146
|
|
|
224
|
-
const onEndGesture = (): boolean | undefined => {
|
|
147
|
+
const onEndGesture = async (): Promise<boolean | undefined> => {
|
|
225
148
|
// タイマーをクリア(正常にonEndGestureが実行された場合)
|
|
226
149
|
if (clearActivatedTimer !== undefined) {
|
|
227
150
|
clearTimeout(clearActivatedTimer);
|
|
228
151
|
clearActivatedTimer = undefined;
|
|
229
152
|
}
|
|
230
153
|
|
|
231
|
-
if (
|
|
154
|
+
if (startAnimationPromise) {
|
|
155
|
+
await startAnimationPromise;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (currentTouchedElement === undefined || !moveAnimation) {
|
|
232
159
|
return false;
|
|
233
160
|
}
|
|
234
161
|
|
|
235
|
-
|
|
162
|
+
setTimeout(() => {
|
|
163
|
+
const targetX = currentTouchedElement!.getBoundingClientRect().left + currentTouchedElement!.clientWidth / 2;
|
|
164
|
+
const step = getStep(targetX, animationPosition!);
|
|
165
|
+
moveAnimation!.progressStep(step);
|
|
166
|
+
});
|
|
167
|
+
await getScaleAnimation(effectElement).duration(120).to('transform', `scale(1, 0.92)`).play();
|
|
168
|
+
moveAnimation!.destroy();
|
|
236
169
|
|
|
237
|
-
|
|
238
|
-
leaveAnimation.addElement(effectElement);
|
|
239
|
-
leaveAnimation
|
|
240
|
-
.onFinish(() => clearActivated())
|
|
241
|
-
.easing('ease-in')
|
|
242
|
-
.duration(80)
|
|
243
|
-
.keyframes([
|
|
244
|
-
{
|
|
245
|
-
transform: `${transform} ${scales.medium}`,
|
|
246
|
-
opacity: 1,
|
|
247
|
-
},
|
|
248
|
-
{
|
|
249
|
-
transform: `${transform} ${scales.small}`,
|
|
250
|
-
opacity: 0,
|
|
251
|
-
},
|
|
252
|
-
]);
|
|
253
|
-
(async () => {
|
|
254
|
-
// Wait for enter animation to complete before playing leave animation
|
|
255
|
-
if (enterAnimationPromise) {
|
|
256
|
-
setTimeout(() => currentTouchedElement!.classList.remove('ion-activated'), 50);
|
|
257
|
-
await enterAnimationPromise;
|
|
258
|
-
} else {
|
|
259
|
-
currentTouchedElement!.classList.remove('ion-activated');
|
|
260
|
-
}
|
|
261
|
-
leaveAnimation.play();
|
|
262
|
-
})();
|
|
170
|
+
clearActivated();
|
|
263
171
|
return true;
|
|
264
172
|
};
|
|
265
173
|
|
|
@@ -282,7 +190,6 @@ export const registerEffect = (
|
|
|
282
190
|
if (gesture) {
|
|
283
191
|
gesture.destroy();
|
|
284
192
|
}
|
|
285
|
-
|
|
286
193
|
// Remove gesture class
|
|
287
194
|
targetElement.classList.remove(GESTURE_NAME);
|
|
288
195
|
},
|
package/src/gestures/utils.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { AnimationPosition } from './interfaces';
|
|
2
|
+
|
|
1
3
|
export const cloneElement = (tagName: string): HTMLElement => {
|
|
2
4
|
const getCachedEl = document.querySelector(`${tagName}.ion-cloned-element`);
|
|
3
5
|
if (getCachedEl !== null) {
|
|
@@ -12,17 +14,25 @@ export const cloneElement = (tagName: string): HTMLElement => {
|
|
|
12
14
|
return clonedEl;
|
|
13
15
|
};
|
|
14
16
|
|
|
15
|
-
export const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const maxLeft = tabSelectedActual.getBoundingClientRect().left + diff;
|
|
19
|
-
const maxRight = tabSelectedActual.getBoundingClientRect().right - diff - tabSelectedActual.clientWidth;
|
|
20
|
-
|
|
21
|
-
if (maxLeft < currentX && currentX < maxRight) {
|
|
22
|
-
return `translate3d(${currentX}px, ${tabEffectElY}px, 0)`;
|
|
23
|
-
}
|
|
24
|
-
if (maxLeft > currentX) {
|
|
25
|
-
return `translate3d(${maxLeft}px, ${tabEffectElY}px, 0)`;
|
|
17
|
+
export const getStep = (targetX: number, animationPosition: AnimationPosition) => {
|
|
18
|
+
if (animationPosition === undefined) {
|
|
19
|
+
return 0;
|
|
26
20
|
}
|
|
27
|
-
|
|
21
|
+
const currentX = targetX - animationPosition.width / 2;
|
|
22
|
+
let progress = (currentX - animationPosition.minPositionX) / (animationPosition.maxPositionX - animationPosition.minPositionX);
|
|
23
|
+
progress = Math.max(0, Math.min(1, progress)); // clamp 0〜1
|
|
24
|
+
return progress;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const changeSelectedElement = (
|
|
28
|
+
targetElement: HTMLElement,
|
|
29
|
+
selectedElement: HTMLElement,
|
|
30
|
+
effectTagName: string,
|
|
31
|
+
selectedClassName: string,
|
|
32
|
+
): void => {
|
|
33
|
+
targetElement.querySelectorAll(effectTagName).forEach((element) => {
|
|
34
|
+
element.classList.remove(selectedClassName);
|
|
35
|
+
element.classList.remove('ion-activated');
|
|
36
|
+
});
|
|
37
|
+
selectedElement.classList.add(selectedClassName, 'ion-activated');
|
|
28
38
|
};
|
package/src/index.ts
CHANGED
|
@@ -4,10 +4,10 @@ export * from './gestures/interfaces';
|
|
|
4
4
|
|
|
5
5
|
export const registerTabBarEffect = (targetElement: HTMLElement): registeredEffect | undefined => {
|
|
6
6
|
return registerEffect(targetElement, 'ion-tab-button', 'tab-selected', {
|
|
7
|
-
small: 'scale(1.1)',
|
|
7
|
+
small: 'scale(1.1, 1)',
|
|
8
8
|
medium: 'scale(1.2)',
|
|
9
9
|
large: 'scale(1.3)',
|
|
10
|
-
xlarge: 'scale(1.
|
|
10
|
+
xlarge: 'scale(1.15, 1.4)',
|
|
11
11
|
});
|
|
12
12
|
};
|
|
13
13
|
|
|
@@ -175,9 +175,19 @@ $scaleup-large-icon-only: 1.22;
|
|
|
175
175
|
* fill=solid && type=submit button has edge brightness.
|
|
176
176
|
*/
|
|
177
177
|
&.button-solid[type='submit'] {
|
|
178
|
+
/*
|
|
179
|
+
* If not set ion-color-**, button color set primary.
|
|
180
|
+
* https://github.com/ionic-team/ionic-framework/blob/main/core/src/components/button/button.scss#L78-L81
|
|
181
|
+
*/
|
|
182
|
+
--color: var(--ion-color-primary-brightness, var(--ion-color-contrast));
|
|
183
|
+
--color-activated: var(--ion-color-primary-brightness);
|
|
184
|
+
--border-color: var(--ion-color-primary-brightness, transparent);
|
|
185
|
+
--border-width: 0.5px;
|
|
186
|
+
--border-style: solid;
|
|
187
|
+
|
|
178
188
|
@each $item in primary, secondary, tertiary, success, warning, danger, light, medium, dark {
|
|
179
189
|
&.ion-color-#{$item} {
|
|
180
|
-
--color: var(--ion-color-#{$item}-brightness, var(--ion-color-contrast));
|
|
190
|
+
--color: var(--ion-color-#{$item}-brightness, var(--ion-color-#{$item}-contrast, var(--ion-color-contrast)));
|
|
181
191
|
--color-activated: var(--ion-color-#{$item}-brightness);
|
|
182
192
|
--border-color: var(--ion-color-#{$item}-brightness, transparent);
|
|
183
193
|
--border-width: 0.5px;
|