@rdlabo/ionic-theme-ios26 1.0.5 → 1.1.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/README.md +35 -1
- package/dist/css/components/ion-action-sheet.css +1 -1
- package/dist/css/components/ion-alert.css +1 -1
- package/dist/css/components/ion-button.css +1 -1
- package/dist/css/components/ion-datetime.css +1 -1
- package/dist/css/components/ion-fab.css +1 -1
- package/dist/css/components/ion-loading.css +1 -1
- package/dist/css/components/ion-modal.css +1 -1
- package/dist/css/components/ion-popover.css +1 -1
- package/dist/css/components/ion-range.css +1 -1
- package/dist/css/components/ion-searchbar.css +1 -1
- package/dist/css/components/ion-segment.css +1 -1
- package/dist/css/components/ion-tabs.css +1 -1
- package/dist/css/components/ion-toast.css +1 -1
- package/dist/css/components/ion-toggle.css +1 -1
- package/dist/css/components/ion-toolbar.css +1 -1
- package/dist/css/ionic-theme-ios26-dark-always.css +1 -1
- package/dist/css/ionic-theme-ios26-dark-class.css +1 -1
- package/dist/css/ionic-theme-ios26-dark-system.css +1 -1
- package/dist/css/ionic-theme-ios26.css +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/popover/animations/ios.enter.d.ts +4 -0
- package/dist/popover/animations/ios.enter.d.ts.map +1 -0
- package/dist/popover/animations/ios.enter.js +111 -0
- package/dist/popover/animations/ios.leave.d.ts +3 -0
- package/dist/popover/animations/ios.leave.d.ts.map +1 -0
- package/dist/popover/animations/ios.leave.js +60 -0
- package/dist/popover/popover-interface.d.ts +38 -0
- package/dist/popover/popover-interface.d.ts.map +1 -0
- package/dist/popover/popover-interface.js +1 -0
- package/dist/popover/utils.d.ts +48 -0
- package/dist/popover/utils.d.ts.map +1 -0
- package/dist/popover/utils.js +479 -0
- package/dist/sheets-of-glass/animations.d.ts +8 -0
- package/dist/sheets-of-glass/animations.d.ts.map +1 -0
- package/dist/sheets-of-glass/animations.js +97 -0
- package/dist/sheets-of-glass/index.d.ts +3 -0
- package/dist/sheets-of-glass/index.d.ts.map +1 -0
- package/dist/sheets-of-glass/index.js +160 -0
- package/dist/sheets-of-glass/interfaces.d.ts +16 -0
- package/dist/sheets-of-glass/interfaces.d.ts.map +1 -0
- package/dist/sheets-of-glass/interfaces.js +1 -0
- package/dist/utils.d.ts +5 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +26 -11
- package/package.json +1 -1
- package/src/index.ts +5 -3
- package/src/popover/animations/ios.enter.ts +176 -0
- package/src/popover/animations/ios.leave.ts +76 -0
- package/src/popover/popover-interface.ts +44 -0
- package/src/popover/utils.ts +912 -0
- package/src/{gestures → sheets-of-glass}/animations.ts +1 -1
- package/src/{gestures → sheets-of-glass}/index.ts +1 -1
- package/src/styles/components/ion-alert.scss +1 -0
- package/src/styles/components/ion-button.scss +29 -8
- package/src/styles/components/ion-popover.scss +6 -0
- package/src/styles/components/ion-range.scss +2 -1
- package/src/styles/components/ion-segment.scss +22 -7
- package/src/styles/components/ion-tabs.scss +9 -2
- package/src/styles/components/ion-toggle.scss +8 -4
- package/src/styles/utils/api.scss +12 -0
- package/src/{gestures/utils.ts → utils.ts} +18 -1
- /package/src/{gestures → sheets-of-glass}/interfaces.ts +0 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { createAnimation } from '@ionic/core';
|
|
2
|
+
import { getStep } from '../utils';
|
|
3
|
+
export const getScaleAnimation = (effectElement) => {
|
|
4
|
+
return createAnimation().addElement(effectElement.shadowRoot.querySelector('[part="native"]')).easing('ease-out');
|
|
5
|
+
};
|
|
6
|
+
export const createPreMoveAnimation = (effectElement, tabSelectedElement, currentTouchedElement, animationPosition) => {
|
|
7
|
+
const diff = Math.max(Math.abs(tabSelectedElement.getBoundingClientRect().left - currentTouchedElement.getBoundingClientRect().left), 120);
|
|
8
|
+
return createAnimation()
|
|
9
|
+
.duration(diff * 1.8)
|
|
10
|
+
.easing('ease-out')
|
|
11
|
+
.addElement(effectElement)
|
|
12
|
+
.beforeStyles({
|
|
13
|
+
width: `${tabSelectedElement.clientWidth}px`,
|
|
14
|
+
height: `${tabSelectedElement.clientHeight}px`,
|
|
15
|
+
display: 'block',
|
|
16
|
+
opacity: '1',
|
|
17
|
+
transform: 'none',
|
|
18
|
+
})
|
|
19
|
+
.keyframes([
|
|
20
|
+
{
|
|
21
|
+
offset: 0,
|
|
22
|
+
transform: `translate3d(${tabSelectedElement.getBoundingClientRect().left}px, ${animationPosition.positionY}px, 0)`,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
offset: 1,
|
|
26
|
+
transform: `translate3d(${currentTouchedElement.getBoundingClientRect().left}px, ${animationPosition.positionY}px, 0)`,
|
|
27
|
+
},
|
|
28
|
+
]);
|
|
29
|
+
};
|
|
30
|
+
export const createMoveAnimation = (effectElement, detail, tabSelectedElement, animationPosition) => {
|
|
31
|
+
return createAnimation()
|
|
32
|
+
.duration(500)
|
|
33
|
+
.addElement(effectElement)
|
|
34
|
+
.beforeStyles({
|
|
35
|
+
width: `${tabSelectedElement.clientWidth}px`,
|
|
36
|
+
height: `${tabSelectedElement.clientHeight}px`,
|
|
37
|
+
display: 'block',
|
|
38
|
+
opacity: '1',
|
|
39
|
+
transform: 'none',
|
|
40
|
+
})
|
|
41
|
+
.fromTo('transform', `translate3d(${animationPosition.minPositionX}px, ${animationPosition.positionY}px, 0)`, `translate3d(${animationPosition.maxPositionX}px, ${animationPosition.positionY}px, 0)`)
|
|
42
|
+
.progressStep(getStep(detail.currentX, animationPosition));
|
|
43
|
+
};
|
|
44
|
+
export const getMoveAnimationKeyframe = (type, scales) => {
|
|
45
|
+
return {
|
|
46
|
+
moveRight: [
|
|
47
|
+
{
|
|
48
|
+
offset: 0,
|
|
49
|
+
transform: scales.large,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
offset: 0.4,
|
|
53
|
+
transform: scales.small,
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
offset: 0.75,
|
|
57
|
+
transform: scales.xlarge,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
offset: 1,
|
|
61
|
+
transform: scales.large,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
moveLeft: [
|
|
65
|
+
{
|
|
66
|
+
offset: 0,
|
|
67
|
+
transform: scales.large,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
offset: 0.1,
|
|
71
|
+
transform: scales.xlarge,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
offset: 0.6,
|
|
75
|
+
transform: scales.small,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
offset: 1,
|
|
79
|
+
transform: scales.large,
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
slowly: [
|
|
83
|
+
{
|
|
84
|
+
offset: 0,
|
|
85
|
+
transform: scales.large,
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
offset: 0.4,
|
|
89
|
+
transform: scales.medium,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
offset: 1,
|
|
93
|
+
transform: scales.large,
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
}[type];
|
|
97
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sheets-of-glass/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAUjF,eAAO,MAAM,cAAc,GACzB,eAAe,WAAW,EAC1B,eAAe,MAAM,EACrB,mBAAmB,MAAM,EACzB,QAAQ,YAAY,KACnB,gBAAgB,GAAG,SAyLrB,CAAC"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { createGesture } from '@ionic/core';
|
|
2
|
+
import { changeSelectedElement, cloneElement, getStep } from '../utils';
|
|
3
|
+
import { createMoveAnimation, createPreMoveAnimation, getMoveAnimationKeyframe, getScaleAnimation } from './animations';
|
|
4
|
+
const GESTURE_NAME = 'ios26-enable-gesture';
|
|
5
|
+
const ANIMATED_NAME = 'ios26-animated';
|
|
6
|
+
export const registerEffect = (targetElement, effectTagName, selectedClassName, scales) => {
|
|
7
|
+
if (!targetElement.classList.contains('ios')) {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
let gesture;
|
|
11
|
+
let moveAnimation;
|
|
12
|
+
let currentTouchedElement;
|
|
13
|
+
let clearActivatedTimer;
|
|
14
|
+
let animationPosition = undefined;
|
|
15
|
+
let scaleAnimationPromise;
|
|
16
|
+
let startAnimationPromise;
|
|
17
|
+
let maxVelocity = 0;
|
|
18
|
+
const effectElement = cloneElement(effectTagName);
|
|
19
|
+
const onPointerDown = () => {
|
|
20
|
+
clearActivated();
|
|
21
|
+
gesture.destroy();
|
|
22
|
+
createAnimationGesture();
|
|
23
|
+
};
|
|
24
|
+
const onPointerUp = (event) => {
|
|
25
|
+
clearActivatedTimer = setTimeout(async () => {
|
|
26
|
+
await onEndGesture();
|
|
27
|
+
gesture.destroy();
|
|
28
|
+
createAnimationGesture();
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
targetElement.addEventListener('pointerdown', onPointerDown);
|
|
32
|
+
targetElement.addEventListener('pointerup', onPointerUp);
|
|
33
|
+
const createAnimationGesture = () => {
|
|
34
|
+
targetElement.classList.add(GESTURE_NAME);
|
|
35
|
+
gesture = createGesture({
|
|
36
|
+
el: targetElement,
|
|
37
|
+
threshold: 0,
|
|
38
|
+
gestureName: `${GESTURE_NAME}_${effectTagName}_${crypto.randomUUID()}`,
|
|
39
|
+
onStart: (event) => onStartGesture(event),
|
|
40
|
+
onMove: (event) => onMoveGesture(event),
|
|
41
|
+
onEnd: () => {
|
|
42
|
+
onEndGesture().then();
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
gesture.enable(true);
|
|
46
|
+
};
|
|
47
|
+
createAnimationGesture();
|
|
48
|
+
const clearActivated = () => {
|
|
49
|
+
if (!currentTouchedElement) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
currentTouchedElement.click();
|
|
53
|
+
currentTouchedElement?.classList.remove('ion-activated');
|
|
54
|
+
currentTouchedElement = undefined;
|
|
55
|
+
effectElement.style.display = 'none';
|
|
56
|
+
maxVelocity = 0;
|
|
57
|
+
targetElement.classList.remove(ANIMATED_NAME);
|
|
58
|
+
};
|
|
59
|
+
const onStartGesture = (detail) => {
|
|
60
|
+
currentTouchedElement = detail.event.target.closest(effectTagName) || undefined;
|
|
61
|
+
const tabSelectedElement = targetElement.querySelector(`${effectTagName}.${selectedClassName}`);
|
|
62
|
+
if (currentTouchedElement === undefined || tabSelectedElement === null) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
animationPosition = {
|
|
66
|
+
minPositionX: targetElement.getBoundingClientRect().left,
|
|
67
|
+
maxPositionX: targetElement.getBoundingClientRect().right - tabSelectedElement.clientWidth,
|
|
68
|
+
width: tabSelectedElement.clientWidth,
|
|
69
|
+
positionY: tabSelectedElement.getBoundingClientRect().top,
|
|
70
|
+
};
|
|
71
|
+
targetElement.classList.add(ANIMATED_NAME);
|
|
72
|
+
changeSelectedElement(targetElement, currentTouchedElement, effectTagName, selectedClassName);
|
|
73
|
+
startAnimationPromise = (() => {
|
|
74
|
+
if (tabSelectedElement === currentTouchedElement) {
|
|
75
|
+
return new Promise((resolve) => resolve());
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
const preMoveAnimation = createPreMoveAnimation(effectElement, tabSelectedElement, currentTouchedElement, animationPosition);
|
|
79
|
+
return preMoveAnimation.play().finally(() => preMoveAnimation.destroy());
|
|
80
|
+
}
|
|
81
|
+
})();
|
|
82
|
+
startAnimationPromise.then(() => {
|
|
83
|
+
moveAnimation = createMoveAnimation(effectElement, detail, tabSelectedElement, animationPosition);
|
|
84
|
+
moveAnimation.progressStart(true, getStep(currentTouchedElement.getBoundingClientRect().left + currentTouchedElement.clientWidth / 2, animationPosition));
|
|
85
|
+
});
|
|
86
|
+
getScaleAnimation(effectElement).duration(200).to('opacity', 1).to('transform', scales.large).play();
|
|
87
|
+
return true;
|
|
88
|
+
};
|
|
89
|
+
const onMoveGesture = (detail) => {
|
|
90
|
+
if (currentTouchedElement === undefined || !moveAnimation) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
if (scaleAnimationPromise === undefined) {
|
|
94
|
+
if (Math.abs(detail.velocityX) > maxVelocity) {
|
|
95
|
+
maxVelocity = Math.abs(detail.velocityX);
|
|
96
|
+
}
|
|
97
|
+
if (Math.abs(detail.velocityX) > 0.2) {
|
|
98
|
+
scaleAnimationPromise = getScaleAnimation(effectElement)
|
|
99
|
+
.duration(720)
|
|
100
|
+
.keyframes(getMoveAnimationKeyframe('slowly', scales))
|
|
101
|
+
.play()
|
|
102
|
+
.finally(() => (scaleAnimationPromise = undefined));
|
|
103
|
+
}
|
|
104
|
+
if (maxVelocity > 0.2 && Math.abs(detail.velocityX) < 0.15 && Math.abs(detail.startX - detail.currentX) > 100) {
|
|
105
|
+
scaleAnimationPromise = getScaleAnimation(effectElement)
|
|
106
|
+
.duration(720)
|
|
107
|
+
.keyframes(getMoveAnimationKeyframe(detail.velocityX > 0 ? 'moveRight' : 'moveLeft', scales))
|
|
108
|
+
.play()
|
|
109
|
+
.finally(() => (scaleAnimationPromise = undefined));
|
|
110
|
+
maxVelocity = 0;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const currentX = detail.currentX;
|
|
114
|
+
const previousY = targetElement.getBoundingClientRect().top + targetElement.getBoundingClientRect().height / 2;
|
|
115
|
+
const nextEl = targetElement.getRootNode().elementFromPoint(currentX, previousY);
|
|
116
|
+
const latestTouchedElement = nextEl?.closest(effectTagName) || undefined;
|
|
117
|
+
if (latestTouchedElement && currentTouchedElement !== latestTouchedElement) {
|
|
118
|
+
currentTouchedElement = latestTouchedElement;
|
|
119
|
+
changeSelectedElement(targetElement, currentTouchedElement, effectTagName, selectedClassName);
|
|
120
|
+
}
|
|
121
|
+
moveAnimation.progressStep(getStep(detail.currentX, animationPosition));
|
|
122
|
+
return true;
|
|
123
|
+
};
|
|
124
|
+
const onEndGesture = async () => {
|
|
125
|
+
if (clearActivatedTimer !== undefined) {
|
|
126
|
+
clearTimeout(clearActivatedTimer);
|
|
127
|
+
clearActivatedTimer = undefined;
|
|
128
|
+
}
|
|
129
|
+
if (startAnimationPromise) {
|
|
130
|
+
await startAnimationPromise;
|
|
131
|
+
}
|
|
132
|
+
if (currentTouchedElement === undefined || !moveAnimation) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
setTimeout(() => {
|
|
136
|
+
const targetX = currentTouchedElement.getBoundingClientRect().left + currentTouchedElement.clientWidth / 2;
|
|
137
|
+
const step = getStep(targetX, animationPosition);
|
|
138
|
+
moveAnimation.progressStep(step);
|
|
139
|
+
});
|
|
140
|
+
await getScaleAnimation(effectElement).duration(120).to('transform', `scale(1, 0.92)`).play();
|
|
141
|
+
moveAnimation.destroy();
|
|
142
|
+
clearActivated();
|
|
143
|
+
return true;
|
|
144
|
+
};
|
|
145
|
+
return {
|
|
146
|
+
destroy: () => {
|
|
147
|
+
targetElement.removeEventListener('pointerdown', onPointerDown);
|
|
148
|
+
targetElement.removeEventListener('pointerup', onPointerUp);
|
|
149
|
+
if (clearActivatedTimer !== undefined) {
|
|
150
|
+
clearTimeout(clearActivatedTimer);
|
|
151
|
+
clearActivatedTimer = undefined;
|
|
152
|
+
}
|
|
153
|
+
clearActivated();
|
|
154
|
+
if (gesture) {
|
|
155
|
+
gesture.destroy();
|
|
156
|
+
}
|
|
157
|
+
targetElement.classList.remove(GESTURE_NAME);
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface EffectScales {
|
|
2
|
+
small: string;
|
|
3
|
+
medium: string;
|
|
4
|
+
large: string;
|
|
5
|
+
xlarge: string;
|
|
6
|
+
}
|
|
7
|
+
export interface registeredEffect {
|
|
8
|
+
destroy: () => void;
|
|
9
|
+
}
|
|
10
|
+
export interface AnimationPosition {
|
|
11
|
+
minPositionX: number;
|
|
12
|
+
maxPositionX: number;
|
|
13
|
+
width: number;
|
|
14
|
+
positionY: number;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=interfaces.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../../src/sheets-of-glass/interfaces.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { AnimationPosition } from './sheets-of-glass/interfaces';
|
|
2
|
+
export declare const getElementRoot: (el: HTMLElement, fallback?: HTMLElement) => HTMLElement | ShadowRoot;
|
|
3
|
+
export declare const raf: (h: FrameRequestCallback) => any;
|
|
1
4
|
export declare const cloneElement: (tagName: string) => HTMLElement;
|
|
2
|
-
export declare const
|
|
5
|
+
export declare const getStep: (targetX: number, animationPosition: AnimationPosition) => number;
|
|
6
|
+
export declare const changeSelectedElement: (targetElement: HTMLElement, selectedElement: HTMLElement, effectTagName: string, selectedClassName: string) => void;
|
|
3
7
|
//# sourceMappingURL=utils.d.ts.map
|
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,GAAI,SAAS,MAAM,KAAG,WAY9C,CAAC;AAEF,eAAO,MAAM,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAKjE,eAAO,MAAM,cAAc,GAAI,IAAI,WAAW,EAAE,WAAU,WAAgB,6BAEzE,CAAC;AAEF,eAAO,MAAM,GAAG,GAAI,GAAG,oBAAoB,QAQ1C,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,SAAS,MAAM,KAAG,WAY9C,CAAC;AAEF,eAAO,MAAM,OAAO,GAAI,SAAS,MAAM,EAAE,mBAAmB,iBAAiB,WAQ5E,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,eAAe,WAAW,EAC1B,iBAAiB,WAAW,EAC5B,eAAe,MAAM,EACrB,mBAAmB,MAAM,KACxB,IAMF,CAAC"}
|
package/dist/utils.js
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
export const getElementRoot = (el, fallback = el) => {
|
|
2
|
+
return el.shadowRoot || fallback;
|
|
3
|
+
};
|
|
4
|
+
export const raf = (h) => {
|
|
5
|
+
if (typeof __zone_symbol__requestAnimationFrame === 'function') {
|
|
6
|
+
return __zone_symbol__requestAnimationFrame(h);
|
|
7
|
+
}
|
|
8
|
+
if (typeof requestAnimationFrame === 'function') {
|
|
9
|
+
return requestAnimationFrame(h);
|
|
10
|
+
}
|
|
11
|
+
return setTimeout(h);
|
|
12
|
+
};
|
|
1
13
|
export const cloneElement = (tagName) => {
|
|
2
14
|
const getCachedEl = document.querySelector(`${tagName}.ion-cloned-element`);
|
|
3
15
|
if (getCachedEl !== null) {
|
|
@@ -9,16 +21,19 @@ export const cloneElement = (tagName) => {
|
|
|
9
21
|
document.body.appendChild(clonedEl);
|
|
10
22
|
return clonedEl;
|
|
11
23
|
};
|
|
12
|
-
export const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const maxLeft = tabSelectedActual.getBoundingClientRect().left + diff;
|
|
16
|
-
const maxRight = tabSelectedActual.getBoundingClientRect().right - diff - tabSelectedActual.clientWidth;
|
|
17
|
-
if (maxLeft < currentX && currentX < maxRight) {
|
|
18
|
-
return `translate3d(${currentX}px, ${tabEffectElY}px, 0)`;
|
|
19
|
-
}
|
|
20
|
-
if (maxLeft > currentX) {
|
|
21
|
-
return `translate3d(${maxLeft}px, ${tabEffectElY}px, 0)`;
|
|
24
|
+
export const getStep = (targetX, animationPosition) => {
|
|
25
|
+
if (animationPosition === undefined) {
|
|
26
|
+
return 0;
|
|
22
27
|
}
|
|
23
|
-
|
|
28
|
+
const currentX = targetX - animationPosition.width / 2;
|
|
29
|
+
let progress = (currentX - animationPosition.minPositionX) / (animationPosition.maxPositionX - animationPosition.minPositionX);
|
|
30
|
+
progress = Math.max(0, Math.min(1, progress));
|
|
31
|
+
return progress;
|
|
32
|
+
};
|
|
33
|
+
export const changeSelectedElement = (targetElement, selectedElement, effectTagName, selectedClassName) => {
|
|
34
|
+
targetElement.querySelectorAll(effectTagName).forEach((element) => {
|
|
35
|
+
element.classList.remove(selectedClassName);
|
|
36
|
+
element.classList.remove('ion-activated');
|
|
37
|
+
});
|
|
38
|
+
selectedElement.classList.add(selectedClassName, 'ion-activated');
|
|
24
39
|
};
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { registeredEffect } from './
|
|
2
|
-
import { registerEffect } from './
|
|
3
|
-
export * from './
|
|
1
|
+
import { registeredEffect } from './sheets-of-glass/interfaces';
|
|
2
|
+
import { registerEffect } from './sheets-of-glass';
|
|
3
|
+
export * from './sheets-of-glass/interfaces';
|
|
4
|
+
export { iosEnterAnimation as popoverEnterAnimation } from './popover/animations/ios.enter';
|
|
5
|
+
export { iosLeaveAnimation as popoverLeaveAnimation } from './popover/animations/ios.leave';
|
|
4
6
|
|
|
5
7
|
export const registerTabBarEffect = (targetElement: HTMLElement): registeredEffect | undefined => {
|
|
6
8
|
return registerEffect(targetElement, 'ion-tab-button', 'tab-selected', {
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { createAnimation, isPlatform } from '@ionic/core';
|
|
2
|
+
import type { Animation } from '@ionic/core/dist/types/utils/animation/animation-interface';
|
|
3
|
+
import { getElementRoot } from '../../utils';
|
|
4
|
+
import { calculateWindowAdjustment, getArrowDimensions, getPopoverDimensions, getPopoverPosition, shouldShowArrow } from '../utils';
|
|
5
|
+
|
|
6
|
+
const POPOVER_IOS_BODY_PADDING = 5;
|
|
7
|
+
export const POPOVER_IOS_BODY_MARGIN = 8;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* iOS Popover Enter Animation
|
|
11
|
+
*/
|
|
12
|
+
// TODO(FW-2832): types
|
|
13
|
+
export const iosEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation => {
|
|
14
|
+
const { event: ev, size, trigger, reference, side, align } = opts;
|
|
15
|
+
const doc = baseEl.ownerDocument as any;
|
|
16
|
+
const isRTL = doc.dir === 'rtl';
|
|
17
|
+
const bodyWidth = doc.defaultView.innerWidth;
|
|
18
|
+
const bodyHeight = doc.defaultView.innerHeight;
|
|
19
|
+
|
|
20
|
+
const root = getElementRoot(baseEl);
|
|
21
|
+
const contentEl = root.querySelector('.popover-content') as HTMLElement;
|
|
22
|
+
const arrowEl = root.querySelector('.popover-arrow') as HTMLElement | null;
|
|
23
|
+
|
|
24
|
+
const referenceSizeEl = trigger || ev?.detail?.ionShadowTarget || ev?.target;
|
|
25
|
+
const { contentWidth, contentHeight } = getPopoverDimensions(size, contentEl, referenceSizeEl);
|
|
26
|
+
const { arrowWidth, arrowHeight } = getArrowDimensions(arrowEl);
|
|
27
|
+
|
|
28
|
+
const isReplace = ((): boolean => {
|
|
29
|
+
if (!['ion-button', 'ion-buttons'].includes(referenceSizeEl.localName)) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
if (referenceSizeEl.classList.contains('ios26-disabled')) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
})();
|
|
37
|
+
|
|
38
|
+
const defaultPosition = {
|
|
39
|
+
top: bodyHeight / 2 - contentHeight / 2,
|
|
40
|
+
left: bodyWidth / 2 - contentWidth / 2,
|
|
41
|
+
originX: isRTL ? 'right' : 'left',
|
|
42
|
+
originY: 'top',
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const results = getPopoverPosition(
|
|
46
|
+
isRTL,
|
|
47
|
+
contentWidth,
|
|
48
|
+
contentHeight,
|
|
49
|
+
arrowWidth,
|
|
50
|
+
arrowHeight,
|
|
51
|
+
reference,
|
|
52
|
+
side,
|
|
53
|
+
align,
|
|
54
|
+
defaultPosition,
|
|
55
|
+
trigger,
|
|
56
|
+
ev,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
const padding = size === 'cover' ? 0 : POPOVER_IOS_BODY_PADDING;
|
|
60
|
+
const margin = size === 'cover' ? 0 : 25;
|
|
61
|
+
|
|
62
|
+
const { originX, originY, top, left, bottom, checkSafeAreaLeft, checkSafeAreaRight, arrowTop, arrowLeft, addPopoverBottomClass } =
|
|
63
|
+
calculateWindowAdjustment(
|
|
64
|
+
side,
|
|
65
|
+
results.top,
|
|
66
|
+
results.left,
|
|
67
|
+
padding,
|
|
68
|
+
bodyWidth,
|
|
69
|
+
bodyHeight,
|
|
70
|
+
contentWidth,
|
|
71
|
+
contentHeight,
|
|
72
|
+
margin,
|
|
73
|
+
results.originX,
|
|
74
|
+
results.originY,
|
|
75
|
+
results.referenceCoordinates,
|
|
76
|
+
results.arrowTop,
|
|
77
|
+
results.arrowLeft,
|
|
78
|
+
arrowHeight,
|
|
79
|
+
referenceSizeEl.getBoundingClientRect(),
|
|
80
|
+
isReplace,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const baseAnimation = createAnimation();
|
|
84
|
+
const backdropAnimation = createAnimation();
|
|
85
|
+
const contentAnimation = createAnimation();
|
|
86
|
+
const targetAnimation = createAnimation();
|
|
87
|
+
|
|
88
|
+
backdropAnimation
|
|
89
|
+
.delay(100)
|
|
90
|
+
.duration(300)
|
|
91
|
+
.addElement(root.querySelector('ion-backdrop')!)
|
|
92
|
+
.fromTo('opacity', 0.01, 'var(--backdrop-opacity)')
|
|
93
|
+
.beforeStyles({
|
|
94
|
+
'pointer-events': 'none',
|
|
95
|
+
})
|
|
96
|
+
.afterClearStyles(['pointer-events']);
|
|
97
|
+
|
|
98
|
+
// In Chromium, if the wrapper animates, the backdrop filter doesn't work.
|
|
99
|
+
// The Chromium team stated that this behavior is expected and not a bug. The element animating opacity creates a backdrop root for the backdrop-filter.
|
|
100
|
+
// To get around this, instead of animating the wrapper, animate both the arrow and content.
|
|
101
|
+
// https://bugs.chromium.org/p/chromium/issues/detail?id=1148826
|
|
102
|
+
contentAnimation
|
|
103
|
+
.easing('cubic-bezier(0, 1, 0.22, 1)')
|
|
104
|
+
.delay(100)
|
|
105
|
+
.duration(400)
|
|
106
|
+
.addElement(root.querySelector('.popover-arrow')!)
|
|
107
|
+
.addElement(root.querySelector('.popover-content')!)
|
|
108
|
+
.beforeStyles({ 'transform-origin': `${originY} ${originX}` })
|
|
109
|
+
.beforeAddWrite(() => {
|
|
110
|
+
/**
|
|
111
|
+
* 'transformOrigin' use for leave animation.
|
|
112
|
+
*/
|
|
113
|
+
root.querySelector<HTMLElement>('.popover-content')!.dataset['transformOrigin'] = `${originY} ${originX}`;
|
|
114
|
+
})
|
|
115
|
+
.fromTo('transform', 'scale(0)', 'scale(1)')
|
|
116
|
+
.fromTo('opacity', 0.01, 1);
|
|
117
|
+
// TODO(FW-4376) Ensure that arrow also blurs when translucent
|
|
118
|
+
|
|
119
|
+
if (isReplace) {
|
|
120
|
+
targetAnimation
|
|
121
|
+
.delay(0)
|
|
122
|
+
.duration(200)
|
|
123
|
+
.addElement(referenceSizeEl)
|
|
124
|
+
.beforeStyles({ 'transform-origin': `${originY} ${originX}` })
|
|
125
|
+
.beforeAddClass('ios26-replace-element')
|
|
126
|
+
.fromTo('transform', 'scale(1)', 'scale(1.05)')
|
|
127
|
+
.fromTo('opacity', 1, 0);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return baseAnimation
|
|
131
|
+
.easing('ease')
|
|
132
|
+
.delay(100)
|
|
133
|
+
.duration(100)
|
|
134
|
+
.beforeAddWrite(() => {
|
|
135
|
+
if (size === 'cover') {
|
|
136
|
+
baseEl.style.setProperty('--width', `${contentWidth}px`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (addPopoverBottomClass) {
|
|
140
|
+
baseEl.classList.add('popover-bottom');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (bottom !== undefined) {
|
|
144
|
+
contentEl.style.setProperty('bottom', `${bottom}px`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const safeAreaLeft = ' + var(--ion-safe-area-left, 0)';
|
|
148
|
+
const safeAreaRight = ' - var(--ion-safe-area-right, 0)';
|
|
149
|
+
|
|
150
|
+
let leftValue = `${left}px`;
|
|
151
|
+
|
|
152
|
+
if (checkSafeAreaLeft) {
|
|
153
|
+
leftValue = `${left}px${safeAreaLeft}`;
|
|
154
|
+
}
|
|
155
|
+
if (checkSafeAreaRight) {
|
|
156
|
+
leftValue = `${left}px${safeAreaRight}`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
contentEl.style.setProperty('top', `calc(${top}px + var(--offset-y, 0))`);
|
|
160
|
+
contentEl.style.setProperty('left', `calc(${leftValue} + var(--offset-x, 0))`);
|
|
161
|
+
contentEl.style.setProperty('transform-origin', `${originY} ${originX}`);
|
|
162
|
+
|
|
163
|
+
if (arrowEl !== null) {
|
|
164
|
+
const didAdjustBounds = results.top !== top || results.left !== left;
|
|
165
|
+
const showArrow = shouldShowArrow(side, didAdjustBounds, ev, trigger);
|
|
166
|
+
|
|
167
|
+
if (showArrow) {
|
|
168
|
+
arrowEl.style.setProperty('top', `calc(${arrowTop}px + var(--offset-y, 0))`);
|
|
169
|
+
arrowEl.style.setProperty('left', `calc(${arrowLeft}px + var(--offset-x, 0))`);
|
|
170
|
+
} else {
|
|
171
|
+
arrowEl.style.setProperty('display', 'none');
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
.addAnimation([backdropAnimation, contentAnimation, targetAnimation]);
|
|
176
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createAnimation } from '@ionic/core';
|
|
2
|
+
import { getElementRoot } from '../../utils';
|
|
3
|
+
|
|
4
|
+
import type { Animation } from '@ionic/core/dist/types/utils/animation/animation-interface';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* iOS Popover Leave Animation
|
|
8
|
+
*/
|
|
9
|
+
export const iosLeaveAnimation = (baseEl: HTMLElement): Animation => {
|
|
10
|
+
const root = getElementRoot(baseEl);
|
|
11
|
+
const contentEl = root.querySelector('.popover-content') as HTMLElement;
|
|
12
|
+
const arrowEl = root.querySelector('.popover-arrow') as HTMLElement | null;
|
|
13
|
+
|
|
14
|
+
const baseAnimation = createAnimation();
|
|
15
|
+
const backdropAnimation = createAnimation();
|
|
16
|
+
const contentAnimation = createAnimation();
|
|
17
|
+
const targetAnimation = createAnimation();
|
|
18
|
+
|
|
19
|
+
const doc = baseEl.ownerDocument as any;
|
|
20
|
+
const replaceElement = doc.querySelector('.ios26-replace-element') as HTMLElement | null;
|
|
21
|
+
|
|
22
|
+
if (replaceElement) {
|
|
23
|
+
const ratio = contentEl.getBoundingClientRect().width / contentEl.getBoundingClientRect().height;
|
|
24
|
+
const scale = ratio > 1 ? `${Math.min(1.2, 1.05 * ratio)}, 1.05` : `1.05, ${Math.min(1.2, 1.05 * ratio)}`;
|
|
25
|
+
|
|
26
|
+
targetAnimation
|
|
27
|
+
.addElement(replaceElement)
|
|
28
|
+
.delay(100)
|
|
29
|
+
.duration(300)
|
|
30
|
+
.afterRemoveClass('ios26-replace-element')
|
|
31
|
+
.fromTo('transform', `scale(${scale})`, 'scale(1)')
|
|
32
|
+
.fromTo('opacity', 0, 0.9);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
backdropAnimation.addElement(root.querySelector('ion-backdrop')!).fromTo('opacity', 'var(--backdrop-opacity)', 0);
|
|
36
|
+
|
|
37
|
+
contentAnimation
|
|
38
|
+
.duration(400)
|
|
39
|
+
.easing('ease')
|
|
40
|
+
.addElement(root.querySelector('.popover-arrow')!)
|
|
41
|
+
.addElement(root.querySelector('.popover-content')!)
|
|
42
|
+
.fromTo('opacity', 0.99, 0);
|
|
43
|
+
|
|
44
|
+
const popoverContentDataset = root.querySelector<HTMLElement>('.popover-content')!.dataset['transformOrigin'];
|
|
45
|
+
if (popoverContentDataset) {
|
|
46
|
+
contentAnimation.beforeStyles({ 'transform-origin': popoverContentDataset }).afterAddRead(() => {
|
|
47
|
+
root.querySelector<HTMLElement>('.popover-content')!.dataset['transformOrigin'] = '';
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (replaceElement) {
|
|
51
|
+
contentAnimation.fromTo('transform', 'scale(1)', 'scale(0.55)');
|
|
52
|
+
} else {
|
|
53
|
+
contentAnimation.fromTo('transform', 'scale(1)', 'scale(0)');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return baseAnimation
|
|
58
|
+
.easing('ease')
|
|
59
|
+
.afterAddWrite(() => {
|
|
60
|
+
baseEl.style.removeProperty('--width');
|
|
61
|
+
baseEl.classList.remove('popover-bottom');
|
|
62
|
+
|
|
63
|
+
contentEl.style.removeProperty('top');
|
|
64
|
+
contentEl.style.removeProperty('left');
|
|
65
|
+
contentEl.style.removeProperty('bottom');
|
|
66
|
+
contentEl.style.removeProperty('transform-origin');
|
|
67
|
+
|
|
68
|
+
if (arrowEl) {
|
|
69
|
+
arrowEl.style.removeProperty('top');
|
|
70
|
+
arrowEl.style.removeProperty('left');
|
|
71
|
+
arrowEl.style.removeProperty('display');
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
.duration(300)
|
|
75
|
+
.addAnimation([backdropAnimation, contentAnimation, targetAnimation]);
|
|
76
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { AnimationBuilder, ComponentProps, ComponentRef, FrameworkDelegate, Mode, OverlayInterface } from '@ionic/core';
|
|
2
|
+
|
|
3
|
+
export interface PopoverInterface extends OverlayInterface {
|
|
4
|
+
present: (event?: MouseEvent | TouchEvent | PointerEvent) => Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export interface PopoverOptions<T extends ComponentRef = ComponentRef> {
|
|
8
|
+
component: T;
|
|
9
|
+
componentProps?: ComponentProps<T>;
|
|
10
|
+
showBackdrop?: boolean;
|
|
11
|
+
backdropDismiss?: boolean;
|
|
12
|
+
translucent?: boolean;
|
|
13
|
+
cssClass?: string | string[];
|
|
14
|
+
event?: Event;
|
|
15
|
+
delegate?: FrameworkDelegate;
|
|
16
|
+
animated?: boolean;
|
|
17
|
+
focusTrap?: boolean;
|
|
18
|
+
|
|
19
|
+
mode?: Mode;
|
|
20
|
+
keyboardClose?: boolean;
|
|
21
|
+
id?: string;
|
|
22
|
+
htmlAttributes?: { [key: string]: any };
|
|
23
|
+
|
|
24
|
+
enterAnimation?: AnimationBuilder;
|
|
25
|
+
leaveAnimation?: AnimationBuilder;
|
|
26
|
+
|
|
27
|
+
size?: PopoverSize;
|
|
28
|
+
dismissOnSelect?: boolean;
|
|
29
|
+
reference?: PositionReference;
|
|
30
|
+
side?: PositionSide;
|
|
31
|
+
alignment?: PositionAlign;
|
|
32
|
+
arrow?: boolean;
|
|
33
|
+
|
|
34
|
+
trigger?: string;
|
|
35
|
+
triggerAction?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type PopoverSize = 'cover' | 'auto';
|
|
39
|
+
|
|
40
|
+
export type TriggerAction = 'click' | 'hover' | 'context-menu';
|
|
41
|
+
|
|
42
|
+
export type PositionReference = 'trigger' | 'event';
|
|
43
|
+
export type PositionSide = 'top' | 'right' | 'bottom' | 'left' | 'start' | 'end';
|
|
44
|
+
export type PositionAlign = 'start' | 'center' | 'end';
|