@rdlabo/ionic-theme-ios27 1.2.0-1 → 1.2.0-3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/css/components/ion-tabs.css +1 -1
- package/dist/css/ionic-theme-ios27.css +1 -1
- package/dist/css/vertical-bars.css +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -12
- package/dist/native/components/index.d.ts.map +1 -1
- package/dist/native/components/index.js +4 -1
- package/dist/native/components/ion-button.d.ts.map +1 -1
- package/dist/native/components/ion-button.js +3 -3
- package/dist/native/components/ion-buttons.d.ts.map +1 -1
- package/dist/native/components/ion-buttons.js +3 -2
- package/dist/native/components/searchable-tabs.d.ts.map +1 -1
- package/dist/native/components/searchable-tabs.js +6 -7
- package/dist/native/definitions.d.ts +8 -15
- package/dist/native/definitions.d.ts.map +1 -1
- package/dist/native/definitions.js +1 -6
- package/dist/native/index.d.ts +1 -2
- package/dist/native/index.d.ts.map +1 -1
- package/dist/native/index.js +64 -28
- package/dist/native/prehide.d.ts.map +1 -1
- package/dist/native/prehide.js +6 -4
- package/dist/native/runtime.d.ts.map +1 -1
- package/dist/native/runtime.js +40 -19
- package/dist/native/shared/candidate.d.ts.map +1 -1
- package/dist/native/shared/candidate.js +8 -0
- package/dist/native/shared/dom.d.ts.map +1 -1
- package/dist/native/shared/dom.js +11 -10
- package/dist/native/shared/modal.d.ts +8 -0
- package/dist/native/shared/modal.d.ts.map +1 -0
- package/dist/native/shared/modal.js +143 -0
- package/dist/native/vertical-bars-web.d.ts.map +1 -1
- package/dist/native/vertical-bars-web.js +11 -3
- package/dist/sheets-of-glass/animations.d.ts +13 -4
- package/dist/sheets-of-glass/animations.d.ts.map +1 -1
- package/dist/sheets-of-glass/animations.js +56 -40
- package/dist/sheets-of-glass/index.d.ts +1 -1
- package/dist/sheets-of-glass/index.d.ts.map +1 -1
- package/dist/sheets-of-glass/index.js +64 -20
- package/dist/tab-bar-searchable/index.d.ts.map +1 -1
- package/dist/tab-bar-searchable/index.js +39 -11
- package/dist/vertical-bars.d.ts +1 -2
- package/dist/vertical-bars.d.ts.map +1 -1
- package/dist/vertical-bars.js +0 -1
- package/docs/iphone-duo-with-original-theme.md +37 -15
- package/docs/iphone-duo.md +90 -47
- package/docs/migration.md +3 -3
- package/docs/native-ui-shell.md +12 -8
- package/docs/special-markup.md +0 -13
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellSearchController.swift +1 -1
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellVerticalBars.swift +50 -6
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellVerticalSearch.swift +118 -0
- package/ios/Sources/IonicNativeUIShellPlugin/IonicNativeUIShellPlugin.swift +32 -179
- package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellSnapshot.swift +5 -0
- package/ios/Tests/IonicNativeUIShellPluginTests/ShellSnapshotTests.swift +67 -0
- package/package.json +1 -1
- package/src/index.ts +40 -9
- package/src/native/components/index.ts +6 -1
- package/src/native/components/ion-button.ts +4 -3
- package/src/native/components/ion-buttons.ts +4 -2
- package/src/native/components/searchable-tabs.ts +8 -11
- package/src/native/definitions.ts +11 -19
- package/src/native/index.ts +67 -30
- package/src/native/prehide.ts +7 -4
- package/src/native/runtime.ts +40 -18
- package/src/native/shared/candidate.ts +10 -0
- package/src/native/shared/dom.ts +11 -10
- package/src/native/shared/modal.ts +157 -0
- package/src/native/vertical-bars-web.ts +12 -3
- package/src/sheets-of-glass/animations.ts +66 -41
- package/src/sheets-of-glass/index.ts +82 -16
- package/src/styles/components/ion-tabs.scss +7 -4
- package/src/styles/vertical-bars.scss +110 -35
- package/src/tab-bar-searchable/index.ts +40 -9
- package/src/vertical-bars.ts +1 -2
|
@@ -3,6 +3,21 @@ import { AnimationPosition, EffectScales } from './interfaces';
|
|
|
3
3
|
import { createAnimation, GestureDetail } from '@ionic/core';
|
|
4
4
|
import { getStep } from '../utils';
|
|
5
5
|
|
|
6
|
+
// Work in main/cross-axis coordinates so native horizontal samples also drive the rail.
|
|
7
|
+
export const animationRect = (element: Element, vertical = false) => {
|
|
8
|
+
const rect = element.getBoundingClientRect();
|
|
9
|
+
return vertical
|
|
10
|
+
? { left: rect.top, top: rect.left, right: rect.bottom, width: rect.height, height: rect.width, x: rect.y, y: rect.x }
|
|
11
|
+
: rect;
|
|
12
|
+
};
|
|
13
|
+
const translate = (x: number, y: number, vertical: boolean) => `translate3d(${vertical ? y : x}px, ${vertical ? x : y}px, 0)`;
|
|
14
|
+
const stretchTransform = (x: number, y: number, vertical: boolean) => `scale(${vertical ? y : x}, ${vertical ? x : y})`;
|
|
15
|
+
const mainSize = (element: HTMLElement, vertical: boolean) => (vertical ? element.offsetHeight : element.offsetWidth);
|
|
16
|
+
const axisScale = (transform: string, vertical: boolean) => {
|
|
17
|
+
const matrix = new DOMMatrixReadOnly(transform);
|
|
18
|
+
return vertical ? { a: matrix.d, d: matrix.a } : matrix;
|
|
19
|
+
};
|
|
20
|
+
|
|
6
21
|
export const getScaleAnimation = (effectElement: Element): Animation => {
|
|
7
22
|
return createAnimation().addElement(effectElement.shadowRoot!.querySelector<HTMLElement>('[part="native"]')!).easing('ease-out');
|
|
8
23
|
};
|
|
@@ -46,55 +61,60 @@ const tabBarTapFrames = [
|
|
|
46
61
|
];
|
|
47
62
|
|
|
48
63
|
// UITabBarController presentation-layer samples: horizontal stretch precedes vertical stretch.
|
|
49
|
-
export const createTabBarPressAnimation = (
|
|
50
|
-
|
|
64
|
+
export const createTabBarPressAnimation = (
|
|
65
|
+
effect: HTMLElement,
|
|
66
|
+
from: HTMLElement,
|
|
67
|
+
to: HTMLElement,
|
|
68
|
+
bar: HTMLElement,
|
|
69
|
+
vertical = false,
|
|
70
|
+
): Animation => {
|
|
71
|
+
const box = animationRect(bar, vertical);
|
|
51
72
|
const center = box.left + box.width / 2;
|
|
52
|
-
const left = center - bar
|
|
53
|
-
const fromBox = from
|
|
54
|
-
const toBox = to
|
|
55
|
-
const parentScale = box.width / bar
|
|
56
|
-
const buttonScale =
|
|
73
|
+
const left = center - mainSize(bar, vertical) / 2;
|
|
74
|
+
const fromBox = animationRect(from, vertical);
|
|
75
|
+
const toBox = animationRect(to, vertical);
|
|
76
|
+
const parentScale = box.width / mainSize(bar, vertical);
|
|
77
|
+
const buttonScale = axisScale(getComputedStyle(from).transform, vertical);
|
|
57
78
|
const width = fromBox.width / parentScale / buttonScale.a;
|
|
58
79
|
const height = fromBox.height / parentScale / buttonScale.d;
|
|
59
80
|
const start = (fromBox.left + fromBox.width / 2 - box.left) / parentScale;
|
|
60
81
|
const end = (toBox.left + toBox.width / 2 - box.left) / parentScale;
|
|
61
82
|
const selected = from === to;
|
|
83
|
+
const growth = vertical ? 20 / 16 : 1;
|
|
62
84
|
const frames = tabBarPressFrames.map(([time, position, extraWidth, extraHeight, expansion]) => {
|
|
63
|
-
const scale = 1 + (14.14 / bar
|
|
85
|
+
const scale = vertical ? 1 : 1 + (14.14 / mainSize(bar, vertical)) * expansion;
|
|
64
86
|
// A selected item expands in place, without the transfer's sideways deformation.
|
|
65
87
|
const press = 1 - Math.exp(-18 * time) * (1 + 18 * time);
|
|
66
88
|
return {
|
|
67
89
|
offset: time / 0.9,
|
|
68
90
|
x: center + (left + start + (end - start) * position - center) * scale - width / 2,
|
|
69
|
-
sx: ((width + (selected ? 16 * press : extraWidth)) * scale) / width,
|
|
70
|
-
sy: ((height + (selected ? 16 * press : extraHeight)) * scale) / height,
|
|
91
|
+
sx: ((width + (selected ? 16 * press : extraWidth) * growth) * scale) / width,
|
|
92
|
+
sy: ((height + (selected ? 16 * press : extraHeight) * growth) * scale) / height,
|
|
71
93
|
};
|
|
72
94
|
});
|
|
73
95
|
const move = createAnimation()
|
|
74
96
|
.addElement(effect)
|
|
75
97
|
.beforeStyles({
|
|
76
|
-
width: `${width}px`,
|
|
77
|
-
height: `${height}px`,
|
|
98
|
+
width: `${vertical ? height : width}px`,
|
|
99
|
+
height: `${vertical ? width : height}px`,
|
|
78
100
|
display: 'block',
|
|
79
101
|
opacity: '1',
|
|
80
102
|
})
|
|
81
|
-
.keyframes(
|
|
82
|
-
frames.map(({ offset, x }) => ({ offset, transform: `translate3d(${x}px, ${box.top + box.height / 2 - height / 2}px, 0)` })),
|
|
83
|
-
);
|
|
103
|
+
.keyframes(frames.map(({ offset, x }) => ({ offset, transform: translate(x, box.top + box.height / 2 - height / 2, vertical) })));
|
|
84
104
|
const stretch = getScaleAnimation(effect)
|
|
85
105
|
.easing('linear')
|
|
86
|
-
.keyframes(frames.map(({ offset, sx, sy }) => ({ offset, transform:
|
|
106
|
+
.keyframes(frames.map(({ offset, sx, sy }) => ({ offset, transform: stretchTransform(sx, sy, vertical) })));
|
|
87
107
|
return createAnimation().duration(900).easing('linear').addAnimation([move, stretch]);
|
|
88
108
|
};
|
|
89
109
|
|
|
90
110
|
// Drag samples: faster movement stretches sideways, then rebounds vertically at rest.
|
|
91
|
-
export const createTabBarDragAnimation = (effect: HTMLElement, bar: HTMLElement, velocity: number): Animation => {
|
|
111
|
+
export const createTabBarDragAnimation = (effect: HTMLElement, bar: HTMLElement, velocity: number, vertical = false): Animation => {
|
|
92
112
|
const native = effect.shadowRoot!.querySelector<HTMLElement>('[part="native"]')!;
|
|
93
|
-
const current =
|
|
94
|
-
const { width, height } = effect
|
|
95
|
-
const scale = 1 + 14.14 / bar
|
|
96
|
-
const restX = ((width + 16) * scale) / width;
|
|
97
|
-
const restY = ((height + 16) * scale) / height;
|
|
113
|
+
const current = axisScale(native.style.transform || getComputedStyle(native).transform, vertical);
|
|
114
|
+
const { width, height } = animationRect(effect, vertical);
|
|
115
|
+
const scale = vertical ? 1 : 1 + 14.14 / mainSize(bar, vertical);
|
|
116
|
+
const restX = ((width + (vertical ? 20 : 16)) * scale) / width;
|
|
117
|
+
const restY = ((height + (vertical ? 20 : 16)) * scale) / height;
|
|
98
118
|
// Approximate the 100/600pt/s UIKit samples; keep deformation bounded for fast swipes.
|
|
99
119
|
const stretch = Math.min(16, 32 * velocity * velocity) * scale;
|
|
100
120
|
const rebound = Math.max(stretch, (current.a - restX) * width) * 0.8;
|
|
@@ -102,10 +122,10 @@ export const createTabBarDragAnimation = (effect: HTMLElement, bar: HTMLElement,
|
|
|
102
122
|
.duration(500)
|
|
103
123
|
.easing('linear')
|
|
104
124
|
.keyframes([
|
|
105
|
-
{ offset: 0, transform:
|
|
106
|
-
{ offset: 0.2, transform:
|
|
107
|
-
{ offset: 0.44, transform:
|
|
108
|
-
{ offset: 1, transform:
|
|
125
|
+
{ offset: 0, transform: stretchTransform(current.a, current.d, vertical) },
|
|
126
|
+
{ offset: 0.2, transform: stretchTransform(restX + stretch / width, restY - stretch / height, vertical) },
|
|
127
|
+
{ offset: 0.44, transform: stretchTransform(restX - rebound / width, restY + rebound / height, vertical) },
|
|
128
|
+
{ offset: 1, transform: stretchTransform(restX, restY, vertical) },
|
|
109
129
|
]);
|
|
110
130
|
};
|
|
111
131
|
|
|
@@ -114,10 +134,11 @@ export const createTabBarReleaseAnimation = (
|
|
|
114
134
|
effectElement: Element,
|
|
115
135
|
target: HTMLElement,
|
|
116
136
|
tap?: { elapsed: number; distance: number },
|
|
137
|
+
vertical = false,
|
|
117
138
|
): Animation => {
|
|
118
139
|
const tapElapsed = tap?.elapsed;
|
|
119
140
|
const native = effectElement.shadowRoot!.querySelector<HTMLElement>('[part="native"]')!;
|
|
120
|
-
const scale =
|
|
141
|
+
const scale = axisScale(native.style.transform || getComputedStyle(native).transform, vertical);
|
|
121
142
|
const samples = [
|
|
122
143
|
[0, 1],
|
|
123
144
|
[0.033, 0.85],
|
|
@@ -131,22 +152,25 @@ export const createTabBarReleaseAnimation = (
|
|
|
131
152
|
[0.45, 0],
|
|
132
153
|
];
|
|
133
154
|
const bar = target.parentElement!;
|
|
134
|
-
const box = bar
|
|
135
|
-
const current = effectElement
|
|
136
|
-
const targetBox = target
|
|
155
|
+
const box = animationRect(bar, vertical);
|
|
156
|
+
const current = animationRect(effectElement, vertical);
|
|
157
|
+
const targetBox = animationRect(target, vertical);
|
|
137
158
|
const center = box.left + box.width / 2;
|
|
138
|
-
const x = center + (targetBox.left + targetBox.width / 2 - center) / (box.width / bar
|
|
159
|
+
const x = center + (targetBox.left + targetBox.width / 2 - center) / (box.width / mainSize(bar, vertical)) - current.width / 2;
|
|
139
160
|
const y = box.top + box.height / 2 - current.height / 2;
|
|
161
|
+
// The vertical platter contracts from 64pt items to 44 × 58pt on release.
|
|
162
|
+
const endScaleX = vertical ? target.offsetHeight / current.width : 1;
|
|
163
|
+
const endScaleY = vertical ? target.offsetWidth / current.height : 1;
|
|
140
164
|
const elapsed = (tapElapsed ?? 0) / 1000;
|
|
141
165
|
const duration = tapElapsed === undefined ? 450 : 800 - tapElapsed;
|
|
142
166
|
let frames = samples.map(([time, remaining]) => ({
|
|
143
167
|
offset: time / 0.45,
|
|
144
168
|
x: x + (current.x - x) * remaining,
|
|
145
169
|
y: y + (current.y - y) * remaining,
|
|
146
|
-
sx:
|
|
147
|
-
sy:
|
|
170
|
+
sx: endScaleX + (scale.a - endScaleX) * remaining,
|
|
171
|
+
sy: endScaleY + (scale.d - endScaleY) * remaining,
|
|
148
172
|
}));
|
|
149
|
-
if (tapElapsed !== undefined) {
|
|
173
|
+
if (tapElapsed !== undefined && !vertical) {
|
|
150
174
|
const nextIndex = tabBarTapFrames.findIndex(([time]) => time > elapsed);
|
|
151
175
|
const previous = tabBarTapFrames[Math.max(0, nextIndex - 1)];
|
|
152
176
|
const next = tabBarTapFrames[nextIndex];
|
|
@@ -159,7 +183,7 @@ export const createTabBarReleaseAnimation = (
|
|
|
159
183
|
...tabBarTapFrames.slice(nextIndex).map(([time, position, width, height]) => ({
|
|
160
184
|
offset: (time - elapsed) / (0.8 - elapsed),
|
|
161
185
|
x: x + ((current.x - x) * (1 - position)) / Math.max(0.1, 1 - progress),
|
|
162
|
-
y,
|
|
186
|
+
y: y + ((current.y - y) * (1 - position)) / Math.max(0.1, 1 - progress),
|
|
163
187
|
sx: 1 + (width * (time >= 0.367 ? 1 + 0.75 * extraDistance : 1)) / current.width,
|
|
164
188
|
sy: 1 + (height * (time >= 0.367 ? 1 + 0.5 * extraDistance : 1)) / current.height,
|
|
165
189
|
})),
|
|
@@ -169,7 +193,7 @@ export const createTabBarReleaseAnimation = (
|
|
|
169
193
|
.addElement(native)
|
|
170
194
|
.duration(duration)
|
|
171
195
|
.easing('linear')
|
|
172
|
-
.keyframes(frames.map(({ offset, sx, sy }) => ({ offset, transform:
|
|
196
|
+
.keyframes(frames.map(({ offset, sx, sy }) => ({ offset, transform: stretchTransform(sx, sy, vertical) })));
|
|
173
197
|
const move = createAnimation()
|
|
174
198
|
.addElement(effectElement)
|
|
175
199
|
.duration(duration)
|
|
@@ -177,7 +201,7 @@ export const createTabBarReleaseAnimation = (
|
|
|
177
201
|
.keyframes(
|
|
178
202
|
frames.map(({ offset, x, y }) => ({
|
|
179
203
|
offset,
|
|
180
|
-
transform:
|
|
204
|
+
transform: translate(x, y, vertical),
|
|
181
205
|
})),
|
|
182
206
|
);
|
|
183
207
|
// Blend the reflective lens into the selected surface during the final 200ms.
|
|
@@ -201,10 +225,10 @@ export const createTabBarReleaseAnimation = (
|
|
|
201
225
|
{ offset: 1, backgroundColor: selectedBackground },
|
|
202
226
|
]);
|
|
203
227
|
const release = createAnimation().duration(duration).addAnimation([stretch, move, glass, selected]);
|
|
204
|
-
if (tapElapsed !== undefined) {
|
|
228
|
+
if (tapElapsed !== undefined && !vertical) {
|
|
205
229
|
// A short UIKit tap keeps expanding after lift-off, then settles independently of the lens.
|
|
206
230
|
// Width deltas from the 50ms tap; preserve the rendered size for an uninterrupted handoff.
|
|
207
|
-
const width = bar
|
|
231
|
+
const width = mainSize(bar, vertical);
|
|
208
232
|
const expansion = box.width - width;
|
|
209
233
|
const peak = Math.max(8.65, expansion);
|
|
210
234
|
const platter = createAnimation()
|
|
@@ -282,6 +306,7 @@ export const createMoveAnimation = (
|
|
|
282
306
|
detail: GestureDetail,
|
|
283
307
|
tabSelectedElement: Element,
|
|
284
308
|
animationPosition: AnimationPosition,
|
|
309
|
+
vertical = false,
|
|
285
310
|
): Animation => {
|
|
286
311
|
return createAnimation()
|
|
287
312
|
.duration(500)
|
|
@@ -295,8 +320,8 @@ export const createMoveAnimation = (
|
|
|
295
320
|
})
|
|
296
321
|
.fromTo(
|
|
297
322
|
'transform',
|
|
298
|
-
|
|
299
|
-
|
|
323
|
+
translate(animationPosition.minPositionX, animationPosition.positionY, vertical),
|
|
324
|
+
translate(animationPosition.maxPositionX, animationPosition.positionY, vertical),
|
|
300
325
|
)
|
|
301
326
|
.progressStep(getStep(detail.currentX, animationPosition));
|
|
302
327
|
};
|
|
@@ -4,6 +4,7 @@ import type { Animation, Gesture, GestureDetail } from '@ionic/core';
|
|
|
4
4
|
import { changeSelectedElement, cloneElement, getStep } from '../utils';
|
|
5
5
|
import { isNativeUIShell } from '../native-integration';
|
|
6
6
|
import {
|
|
7
|
+
animationRect,
|
|
7
8
|
createMoveAnimation,
|
|
8
9
|
createPreMoveAnimation,
|
|
9
10
|
createTabBarDragAnimation,
|
|
@@ -21,6 +22,7 @@ export const registerEffect = (
|
|
|
21
22
|
effectTagName: string,
|
|
22
23
|
selectedClassName: string,
|
|
23
24
|
scales: EffectScales,
|
|
25
|
+
vertical = false,
|
|
24
26
|
): registeredEffect | undefined => {
|
|
25
27
|
if (!targetElement.classList.contains('ios')) {
|
|
26
28
|
return undefined;
|
|
@@ -42,6 +44,7 @@ export const registerEffect = (
|
|
|
42
44
|
let destroyed = false;
|
|
43
45
|
const originalTabBarScale = targetElement.style.getPropertyValue('--ios27-tab-bar-scale');
|
|
44
46
|
const effectElement = cloneElement(effectTagName, false);
|
|
47
|
+
if (vertical) effectElement.classList.add('ios27-vertical-tab-effect');
|
|
45
48
|
const stopTabPress = () => {
|
|
46
49
|
if (!tabPressAnimation) return;
|
|
47
50
|
const native = effectElement.shadowRoot!.querySelector<HTMLElement>('[part="native"]')!;
|
|
@@ -121,18 +124,39 @@ export const registerEffect = (
|
|
|
121
124
|
document.addEventListener('pointercancel', onPointerCancel);
|
|
122
125
|
};
|
|
123
126
|
targetElement.addEventListener('pointerdown', onPointerDown);
|
|
127
|
+
const onClick = (event: MouseEvent) => {
|
|
128
|
+
// A drag commits its destination itself; the browser may click the old pressed tab.
|
|
129
|
+
if (vertical && tabDragging && event.isTrusted && event.detail > 0) {
|
|
130
|
+
event.preventDefault();
|
|
131
|
+
event.stopImmediatePropagation();
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
targetElement.addEventListener('click', onClick, true);
|
|
124
135
|
|
|
136
|
+
const axisDetail = (detail: GestureDetail): GestureDetail =>
|
|
137
|
+
vertical
|
|
138
|
+
? {
|
|
139
|
+
...detail,
|
|
140
|
+
currentX: detail.currentY,
|
|
141
|
+
currentY: detail.currentX,
|
|
142
|
+
startX: detail.startY,
|
|
143
|
+
startY: detail.startX,
|
|
144
|
+
velocityX: detail.velocityY,
|
|
145
|
+
velocityY: detail.velocityX,
|
|
146
|
+
}
|
|
147
|
+
: detail;
|
|
125
148
|
const createAnimationGesture = () => {
|
|
126
149
|
targetElement.classList.add(GESTURE_NAME);
|
|
127
150
|
gesture = createGesture({
|
|
128
151
|
el: targetElement,
|
|
129
152
|
threshold: 0,
|
|
153
|
+
direction: vertical ? 'y' : 'x',
|
|
130
154
|
gestureName: `${GESTURE_NAME}_${effectTagName}_${crypto.randomUUID()}`,
|
|
131
|
-
onStart: (event) => onStartGesture(event),
|
|
132
|
-
onMove: (event) => onMoveGesture(event),
|
|
155
|
+
onStart: (event) => onStartGesture(axisDetail(event)),
|
|
156
|
+
onMove: (event) => onMoveGesture(axisDetail(event)),
|
|
133
157
|
onEnd: (detail) => {
|
|
134
158
|
// Ionic coalesces moves; consume the release coordinate before committing a drag.
|
|
135
|
-
if (effectTagName === 'ion-tab-button' && tabDragging) onMoveGesture(detail);
|
|
159
|
+
if (effectTagName === 'ion-tab-button' && tabDragging) onMoveGesture(axisDetail(detail));
|
|
136
160
|
onEndGesture().then();
|
|
137
161
|
},
|
|
138
162
|
});
|
|
@@ -160,34 +184,49 @@ export const registerEffect = (
|
|
|
160
184
|
if (isNativeUIShell(targetElement)) return false;
|
|
161
185
|
currentTouchedElement = ((detail.event.target as HTMLElement).closest(effectTagName) as HTMLElement) || undefined;
|
|
162
186
|
const tabSelectedElement = targetElement.querySelector(`${effectTagName}.${selectedClassName}`);
|
|
163
|
-
if (
|
|
187
|
+
if (
|
|
188
|
+
currentTouchedElement === undefined ||
|
|
189
|
+
currentTouchedElement.parentElement !== targetElement ||
|
|
190
|
+
(currentTouchedElement as HTMLIonTabButtonElement).disabled ||
|
|
191
|
+
tabSelectedElement === null
|
|
192
|
+
) {
|
|
193
|
+
currentTouchedElement = undefined;
|
|
164
194
|
return false;
|
|
165
195
|
}
|
|
166
196
|
selectedElementBeforeGesture = tabSelectedElement as HTMLElement;
|
|
167
197
|
if (effectTagName === 'ion-tab-button') {
|
|
168
198
|
// UIKit expands the platter by 14.14pt at both two- and four-tab widths.
|
|
169
|
-
targetElement.style.setProperty(
|
|
199
|
+
targetElement.style.setProperty(
|
|
200
|
+
'--ios27-tab-bar-scale',
|
|
201
|
+
String(1 + 14.14 / (vertical ? targetElement.offsetHeight : targetElement.offsetWidth)),
|
|
202
|
+
);
|
|
170
203
|
}
|
|
204
|
+
if (vertical) targetElement.classList.add(ANIMATED_NAME);
|
|
171
205
|
animationPosition = {
|
|
172
|
-
minPositionX: targetElement
|
|
173
|
-
maxPositionX:
|
|
174
|
-
|
|
175
|
-
|
|
206
|
+
minPositionX: animationRect(targetElement, vertical).left,
|
|
207
|
+
maxPositionX:
|
|
208
|
+
animationRect(targetElement, vertical).right - (vertical ? tabSelectedElement.clientHeight : tabSelectedElement.clientWidth),
|
|
209
|
+
width: vertical ? tabSelectedElement.clientHeight : tabSelectedElement.clientWidth,
|
|
210
|
+
positionY: animationRect(tabSelectedElement, vertical).top,
|
|
176
211
|
};
|
|
177
212
|
targetElement.classList.add(ANIMATED_NAME);
|
|
178
213
|
changeSelectedElement(targetElement, currentTouchedElement, effectTagName, selectedClassName);
|
|
179
214
|
|
|
180
215
|
if (effectTagName === 'ion-tab-button') {
|
|
181
|
-
moveAnimation = createMoveAnimation(effectElement, detail, tabSelectedElement, animationPosition);
|
|
216
|
+
moveAnimation = createMoveAnimation(effectElement, detail, tabSelectedElement, animationPosition, vertical);
|
|
182
217
|
moveAnimation.progressStart(
|
|
183
218
|
true,
|
|
184
|
-
getStep(
|
|
219
|
+
getStep(
|
|
220
|
+
animationRect(tabSelectedElement, vertical).left + animationRect(tabSelectedElement, vertical).width / 2,
|
|
221
|
+
animationPosition,
|
|
222
|
+
),
|
|
185
223
|
);
|
|
186
224
|
tabPressAnimation = createTabBarPressAnimation(
|
|
187
225
|
effectElement,
|
|
188
226
|
tabSelectedElement as HTMLElement,
|
|
189
227
|
currentTouchedElement,
|
|
190
228
|
targetElement,
|
|
229
|
+
vertical,
|
|
191
230
|
);
|
|
192
231
|
tabPressStartedAt = performance.now();
|
|
193
232
|
void tabPressAnimation.play();
|
|
@@ -225,7 +264,7 @@ export const registerEffect = (
|
|
|
225
264
|
if (Math.abs(detail.currentX - detail.startX) < 3 && !tabDragging) return;
|
|
226
265
|
tabDragging = true;
|
|
227
266
|
stopTabPress();
|
|
228
|
-
tabPressAnimation = createTabBarDragAnimation(effectElement, targetElement, detail.velocityX);
|
|
267
|
+
tabPressAnimation = createTabBarDragAnimation(effectElement, targetElement, detail.velocityX, vertical);
|
|
229
268
|
void tabPressAnimation.play();
|
|
230
269
|
} else if (scaleAnimationPromise === undefined) {
|
|
231
270
|
if (Math.abs(detail.velocityX) > maxVelocity) {
|
|
@@ -249,11 +288,32 @@ export const registerEffect = (
|
|
|
249
288
|
}
|
|
250
289
|
|
|
251
290
|
const currentX = detail.currentX;
|
|
252
|
-
const previousY = targetElement
|
|
253
|
-
const nextEl = (targetElement.getRootNode() as Document | ShadowRoot).elementFromPoint(
|
|
254
|
-
|
|
291
|
+
const previousY = animationRect(targetElement, vertical).top + animationRect(targetElement, vertical).height / 2;
|
|
292
|
+
const nextEl = (targetElement.getRootNode() as Document | ShadowRoot).elementFromPoint(
|
|
293
|
+
vertical ? previousY : currentX,
|
|
294
|
+
vertical ? currentX : previousY,
|
|
295
|
+
);
|
|
296
|
+
let latestTouchedElement: HTMLElement | undefined = (nextEl?.closest(effectTagName) as HTMLElement) || undefined;
|
|
297
|
+
if (vertical && effectTagName === 'ion-tab-button') {
|
|
298
|
+
// Overlapping, scaled tabs must not steal a drag from their neighbor.
|
|
299
|
+
const bar = targetElement.getBoundingClientRect();
|
|
300
|
+
const tabs = Array.from(targetElement.querySelectorAll<HTMLElement>(effectTagName)).filter((tab) => tab.offsetWidth > 0);
|
|
301
|
+
const distance = (tab: HTMLElement) => Math.abs(currentX - (bar.top + tab.offsetTop + tab.offsetHeight / 2));
|
|
302
|
+
latestTouchedElement =
|
|
303
|
+
currentX >= bar.top && currentX <= bar.bottom
|
|
304
|
+
? tabs.reduce<HTMLElement | undefined>(
|
|
305
|
+
(closest, tab) => (!closest || distance(tab) < distance(closest) ? tab : closest),
|
|
306
|
+
undefined,
|
|
307
|
+
)
|
|
308
|
+
: undefined;
|
|
309
|
+
}
|
|
255
310
|
|
|
256
|
-
if (
|
|
311
|
+
if (
|
|
312
|
+
latestTouchedElement &&
|
|
313
|
+
latestTouchedElement.parentElement === targetElement &&
|
|
314
|
+
!(latestTouchedElement as HTMLIonTabButtonElement).disabled &&
|
|
315
|
+
currentTouchedElement !== latestTouchedElement
|
|
316
|
+
) {
|
|
257
317
|
currentTouchedElement = latestTouchedElement;
|
|
258
318
|
changeSelectedElement(targetElement, currentTouchedElement, effectTagName, selectedClassName);
|
|
259
319
|
}
|
|
@@ -308,6 +368,10 @@ export const registerEffect = (
|
|
|
308
368
|
moveAnimation.destroy();
|
|
309
369
|
moveAnimation = undefined;
|
|
310
370
|
const tabs = Array.from(targetElement.querySelectorAll<HTMLElement>(effectTagName)).filter((tab) => tab.offsetWidth > 0);
|
|
371
|
+
if (vertical) {
|
|
372
|
+
currentTouchedElement.classList.remove('ion-activated');
|
|
373
|
+
targetElement.classList.remove(ANIMATED_NAME);
|
|
374
|
+
}
|
|
311
375
|
const releasing = createTabBarReleaseAnimation(
|
|
312
376
|
effectElement,
|
|
313
377
|
currentTouchedElement,
|
|
@@ -317,6 +381,7 @@ export const registerEffect = (
|
|
|
317
381
|
distance: Math.abs(tabs.indexOf(currentTouchedElement) - tabs.indexOf(selectedElementBeforeGesture!)),
|
|
318
382
|
}
|
|
319
383
|
: undefined,
|
|
384
|
+
vertical,
|
|
320
385
|
);
|
|
321
386
|
currentTouchedElement.classList.remove('ion-activated');
|
|
322
387
|
releaseAnimation = releasing;
|
|
@@ -347,6 +412,7 @@ export const registerEffect = (
|
|
|
347
412
|
cancelActiveGesture();
|
|
348
413
|
// Remove event listeners
|
|
349
414
|
targetElement.removeEventListener('pointerdown', onPointerDown);
|
|
415
|
+
targetElement.removeEventListener('click', onClick, true);
|
|
350
416
|
targetElement.removeEventListener('nativeUIShellChange', nativeChanged);
|
|
351
417
|
|
|
352
418
|
// Destroy gesture
|
|
@@ -70,11 +70,14 @@ ion-tab-bar.ios:not(.ios-theme-disabled, .ios26-disabled) {
|
|
|
70
70
|
height: 54px;
|
|
71
71
|
min-height: 54px;
|
|
72
72
|
&:has(> ion-tab-button:nth-child(2):last-child) {
|
|
73
|
-
width: 180px; // 188px including the 4px inset on each side.
|
|
73
|
+
width: var(--ios-theme-side-tab-content-width, 180px); // 188px including the 4px inset on each side.
|
|
74
74
|
}
|
|
75
75
|
&:has(:nth-child(5)) {
|
|
76
|
-
width:
|
|
77
|
-
|
|
76
|
+
width: var(
|
|
77
|
+
--ios-theme-side-tab-content-width,
|
|
78
|
+
calc(100% - calc(18px + var(--ion-safe-area-left, 0px)) - calc(18px + var(--ion-safe-area-right, 0px)) - 3px)
|
|
79
|
+
);
|
|
80
|
+
max-width: var(--ios-theme-side-tab-content-width, 543px);
|
|
78
81
|
}
|
|
79
82
|
padding: 4px;
|
|
80
83
|
|
|
@@ -96,7 +99,7 @@ ion-tab-bar.ios:not(.ios-theme-disabled, .ios26-disabled) {
|
|
|
96
99
|
1 of ion-tab-button:not(.ion-cloned-element)
|
|
97
100
|
)
|
|
98
101
|
) {
|
|
99
|
-
max-width: $width - 8px;
|
|
102
|
+
max-width: var(--ios-theme-side-tab-content-width, #{$width - 8px});
|
|
100
103
|
}
|
|
101
104
|
}
|
|
102
105
|
}
|