@rdlabo/ionic-theme-ios27 2.0.0-beta.pr316.sha812440877761 → 2.0.0-beta.pr317.sha707be106d55b
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/dist/transition/ios.transition.d.ts.map +1 -1
- package/dist/transition/ios.transition.js +41 -1
- package/dist/transition/ios.transition.js.map +1 -1
- package/dist/transition/native-ui.d.ts +1 -1
- package/dist/transition/native-ui.d.ts.map +1 -1
- package/dist/transition/native-ui.js +43 -9
- package/dist/transition/native-ui.js.map +1 -1
- package/package.json +2 -2
- package/src/transition/ios.transition.ts +46 -3
- package/src/transition/native-ui.ts +56 -11
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ios.transition.d.ts","names":[],"sources":["../../src/transition/ios.transition.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"ios.transition.d.ts","names":[],"sources":["../../src/transition/ios.transition.ts"],"mappings":";;;;iBAUiB;EACf;;qBAYW,YAAa,QAAQ,QAAQ;qBAK7B,yBAAyB,OAAO,aAAa,MAAM,sBAAsB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getIonPageElement } from "./index.js";
|
|
2
2
|
import { withNativeUITransition } from "./native-ui.js";
|
|
3
|
+
import { createAnimation } from "@ionic/core/components/index.js";
|
|
3
4
|
import { nativeWebViewMetrics } from "@rdlabo/capacitor-native-ui/integration";
|
|
4
5
|
import { createIosTransitionAnimation, shadow } from "@rdlabo/ionic-theme-utils";
|
|
5
6
|
//#region src/transition/ios.transition.ts
|
|
@@ -14,7 +15,46 @@ const transitionConfig = {
|
|
|
14
15
|
const setConfig = (config) => {
|
|
15
16
|
if (config.radius !== void 0) nativeWebViewMetrics.radius = config.radius;
|
|
16
17
|
};
|
|
17
|
-
const
|
|
18
|
+
const buildTransition = createIosTransitionAnimation(transitionConfig);
|
|
19
|
+
const iosTransitionAnimation = withNativeUITransition((navEl, opts) => {
|
|
20
|
+
const animation = buildTransition(navEl, opts);
|
|
21
|
+
const backing = "var(--ion-background-color, #fff)";
|
|
22
|
+
let ownsBacking = false;
|
|
23
|
+
animation.beforeAddWrite(() => {
|
|
24
|
+
if (navEl.style.backgroundColor) return;
|
|
25
|
+
const style = navEl.ownerDocument.defaultView?.getComputedStyle(navEl);
|
|
26
|
+
if (!style || !["transparent", "rgba(0, 0, 0, 0)"].includes(style.backgroundColor)) return;
|
|
27
|
+
if (style.backgroundImage && style.backgroundImage !== "none") return;
|
|
28
|
+
navEl.style.backgroundColor = backing;
|
|
29
|
+
ownsBacking = true;
|
|
30
|
+
});
|
|
31
|
+
const restoreBacking = () => {
|
|
32
|
+
if (ownsBacking && navEl.style.backgroundColor === backing && !navEl.style.getPropertyPriority("background-color")) navEl.style.removeProperty("background-color");
|
|
33
|
+
ownsBacking = false;
|
|
34
|
+
};
|
|
35
|
+
animation.afterAddWrite(restoreBacking);
|
|
36
|
+
const destroy = animation.destroy.bind(animation);
|
|
37
|
+
animation.destroy = (...args) => {
|
|
38
|
+
restoreBacking();
|
|
39
|
+
return destroy(...args);
|
|
40
|
+
};
|
|
41
|
+
const buttons = opts.leavingEl?.querySelectorAll(":scope > ion-header.header-translucent ion-buttons, :scope > ion-header.header-translucent ion-toolbar > ion-button");
|
|
42
|
+
if (buttons?.length) animation.addAnimation(createAnimation().addElement(buttons).keyframes([
|
|
43
|
+
{
|
|
44
|
+
offset: 0,
|
|
45
|
+
opacity: 1
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
offset: .5,
|
|
49
|
+
opacity: 0
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
offset: 1,
|
|
53
|
+
opacity: 0
|
|
54
|
+
}
|
|
55
|
+
]));
|
|
56
|
+
return animation;
|
|
57
|
+
});
|
|
18
58
|
//#endregion
|
|
19
59
|
export { iosTransitionAnimation, setConfig, shadow };
|
|
20
60
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ios.transition.js","names":[],"sources":["../../src/transition/ios.transition.ts"],"sourcesContent":["import { createIosTransitionAnimation, shadow } from '@rdlabo/ionic-theme-utils';\nimport type { Animation } from '@ionic/core/components';\nimport type { TransitionOptions } from './index';\nimport { getIonPageElement } from './index';\nimport { nativeWebViewMetrics } from '@rdlabo/capacitor-native-ui/integration';\nimport { withNativeUITransition } from './native-ui';\n\nexport { shadow };\n\nexport interface IosTransitionConfig {\n radius: number;\n}\n\nconst transitionConfig = {\n offLeftPercent: 30,\n getIonPageElement,\n shouldAnimateFixedBackButton: (navEl: HTMLElement) => !navEl.closest('ion-app.ios-theme-vertical-bars'),\n get radius() {\n return nativeWebViewMetrics.radius;\n },\n};\n\nexport const setConfig = (config: Partial<IosTransitionConfig>): void => {\n if (config.radius !== undefined) nativeWebViewMetrics.radius = config.radius;\n};\n\nexport const iosTransitionAnimation: (navEl: HTMLElement, opts: TransitionOptions) => Animation = withNativeUITransition(\n
|
|
1
|
+
{"version":3,"file":"ios.transition.js","names":[],"sources":["../../src/transition/ios.transition.ts"],"sourcesContent":["import { createIosTransitionAnimation, shadow } from '@rdlabo/ionic-theme-utils';\nimport { createAnimation } from '@ionic/core/components/index.js';\nimport type { Animation } from '@ionic/core/components';\nimport type { TransitionOptions } from './index';\nimport { getIonPageElement } from './index';\nimport { nativeWebViewMetrics } from '@rdlabo/capacitor-native-ui/integration';\nimport { withNativeUITransition } from './native-ui';\n\nexport { shadow };\n\nexport interface IosTransitionConfig {\n radius: number;\n}\n\nconst transitionConfig = {\n offLeftPercent: 30,\n getIonPageElement,\n shouldAnimateFixedBackButton: (navEl: HTMLElement) => !navEl.closest('ion-app.ios-theme-vertical-bars'),\n get radius() {\n return nativeWebViewMetrics.radius;\n },\n};\n\nexport const setConfig = (config: Partial<IosTransitionConfig>): void => {\n if (config.radius !== undefined) nativeWebViewMetrics.radius = config.radius;\n};\n\nconst buildTransition = createIosTransitionAnimation<TransitionOptions>(transitionConfig);\nexport const iosTransitionAnimation: (navEl: HTMLElement, opts: TransitionOptions) => Animation = withNativeUITransition((navEl, opts) => {\n const animation = buildTransition(navEl, opts);\n const backing = 'var(--ion-background-color, #fff)';\n let ownsBacking = false;\n // Translucent pages expose the outlet as they move. Keep that backing surface\n // in the page theme instead of exposing WKWebView's transparent native backing.\n animation.beforeAddWrite(() => {\n if (navEl.style.backgroundColor) return;\n const style = navEl.ownerDocument.defaultView?.getComputedStyle(navEl);\n if (!style || !['transparent', 'rgba(0, 0, 0, 0)'].includes(style.backgroundColor)) return;\n if (style.backgroundImage && style.backgroundImage !== 'none') return;\n navEl.style.backgroundColor = backing;\n ownsBacking = true;\n });\n const restoreBacking = () => {\n if (ownsBacking && navEl.style.backgroundColor === backing && !navEl.style.getPropertyPriority('background-color'))\n navEl.style.removeProperty('background-color');\n ownsBacking = false;\n };\n animation.afterAddWrite(restoreBacking);\n // Ionic destroy does not run after-write hooks when playback is interrupted.\n const destroy = animation.destroy.bind(animation);\n animation.destroy = (...args) => {\n restoreBacking();\n return destroy(...args);\n };\n // Translucent pages move as a whole, but outgoing toolbar actions still need\n // the same exit fade as opaque headers when they return from native to Web.\n const buttons = opts.leavingEl?.querySelectorAll(\n ':scope > ion-header.header-translucent ion-buttons, :scope > ion-header.header-translucent ion-toolbar > ion-button',\n );\n if (buttons?.length)\n animation.addAnimation(\n createAnimation()\n .addElement(buttons)\n .keyframes([\n { offset: 0, opacity: 1 },\n // Finish the exit before the incoming title occupies this toolbar.\n { offset: 0.5, opacity: 0 },\n { offset: 1, opacity: 0 },\n ]),\n );\n return animation;\n});\n"],"mappings":";;;;;;AAcA,MAAM,mBAAmB;CACvB,gBAAgB;CAChB;CACA,+BAA+B,UAAuB,CAAC,MAAM,QAAQ,iCAAiC;CACtG,IAAI,SAAS;EACX,OAAO,qBAAqB;CAC9B;AACF;AAEA,MAAa,aAAa,WAA+C;CACvE,IAAI,OAAO,WAAW,KAAA,GAAW,qBAAqB,SAAS,OAAO;AACxE;AAEA,MAAM,kBAAkB,6BAAgD,gBAAgB;AACxF,MAAa,yBAAqF,wBAAwB,OAAO,SAAS;CACxI,MAAM,YAAY,gBAAgB,OAAO,IAAI;CAC7C,MAAM,UAAU;CAChB,IAAI,cAAc;CAGlB,UAAU,qBAAqB;EAC7B,IAAI,MAAM,MAAM,iBAAiB;EACjC,MAAM,QAAQ,MAAM,cAAc,aAAa,iBAAiB,KAAK;EACrE,IAAI,CAAC,SAAS,CAAC,CAAC,eAAe,kBAAkB,CAAC,CAAC,SAAS,MAAM,eAAe,GAAG;EACpF,IAAI,MAAM,mBAAmB,MAAM,oBAAoB,QAAQ;EAC/D,MAAM,MAAM,kBAAkB;EAC9B,cAAc;CAChB,CAAC;CACD,MAAM,uBAAuB;EAC3B,IAAI,eAAe,MAAM,MAAM,oBAAoB,WAAW,CAAC,MAAM,MAAM,oBAAoB,kBAAkB,GAC/G,MAAM,MAAM,eAAe,kBAAkB;EAC/C,cAAc;CAChB;CACA,UAAU,cAAc,cAAc;CAEtC,MAAM,UAAU,UAAU,QAAQ,KAAK,SAAS;CAChD,UAAU,WAAW,GAAG,SAAS;EAC/B,eAAe;EACf,OAAO,QAAQ,GAAG,IAAI;CACxB;CAGA,MAAM,UAAU,KAAK,WAAW,iBAC9B,qHACF;CACA,IAAI,SAAS,QACX,UAAU,aACR,gBAAgB,CAAC,CACd,WAAW,OAAO,CAAC,CACnB,UAAU;EACT;GAAE,QAAQ;GAAG,SAAS;EAAE;EAExB;GAAE,QAAQ;GAAK,SAAS;EAAE;EAC1B;GAAE,QAAQ;GAAG,SAAS;EAAE;CAC1B,CAAC,CACL;CACF,OAAO;AACT,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Animation, AnimationBuilder } from "@ionic/core/components";
|
|
2
2
|
//#region src/transition/native-ui.d.ts
|
|
3
3
|
export declare const withNativeUITransition: (builder: AnimationBuilder) => AnimationBuilder;
|
|
4
|
-
export declare const connectNativeUITransition: (animation: Animation, entering: HTMLElement, leaving?: HTMLElement) => void;
|
|
4
|
+
export declare const connectNativeUITransition: (animation: Animation, entering: HTMLElement, leaving?: HTMLElement, direction?: "forward" | "back" | "root") => void;
|
|
5
5
|
//#endregion
|
|
6
6
|
//# sourceMappingURL=native-ui.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-ui.d.ts","names":[],"sources":["../../src/transition/native-ui.ts"],"mappings":";;
|
|
1
|
+
{"version":3,"file":"native-ui.d.ts","names":[],"sources":["../../src/transition/native-ui.ts"],"mappings":";;qBAUa,yBACV,SAAS,qBAAmB;qBAWlB,4BACX,WAAW,WACX,UAAU,aACV,UAAU,aACV"}
|
|
@@ -7,13 +7,13 @@ import { VERTICAL_BARS_TRANSITION_CANCELED, hasNativeUIIntegration, suspendNativ
|
|
|
7
7
|
*/
|
|
8
8
|
const withNativeUITransition = (builder) => (baseEl, opts) => {
|
|
9
9
|
const animation = builder(baseEl, opts);
|
|
10
|
-
if (opts?.enteringEl) connectNativeUITransition(animation, opts.enteringEl, opts.leavingEl);
|
|
10
|
+
if (opts?.enteringEl) connectNativeUITransition(animation, opts.enteringEl, opts.leavingEl, opts.direction);
|
|
11
11
|
return animation;
|
|
12
12
|
};
|
|
13
13
|
const connectedTransitions = /* @__PURE__ */ new WeakSet();
|
|
14
14
|
const cancellationTransitions = /* @__PURE__ */ new WeakSet();
|
|
15
15
|
/** Ionic write hooks cannot await the bridge. Gate playback, including interactive playback. */
|
|
16
|
-
const connectNativeUITransition = (animation, entering, leaving) => {
|
|
16
|
+
const connectNativeUITransition = (animation, entering, leaving, direction) => {
|
|
17
17
|
if (leaving && !cancellationTransitions.has(animation)) {
|
|
18
18
|
cancellationTransitions.add(animation);
|
|
19
19
|
animation.onFinish((step) => {
|
|
@@ -32,16 +32,35 @@ const connectNativeUITransition = (animation, entering, leaving) => {
|
|
|
32
32
|
const progressEnd = animation.progressEnd.bind(animation);
|
|
33
33
|
const destroy = animation.destroy.bind(animation);
|
|
34
34
|
let release;
|
|
35
|
+
let frame = 0;
|
|
36
|
+
const win = entering.ownerDocument.defaultView;
|
|
37
|
+
const webAnimations = (value) => [...value.getWebAnimations(), ...value.childAnimations.flatMap(webAnimations)];
|
|
38
|
+
const track = () => {
|
|
39
|
+
if (!release?.progress || !win) return;
|
|
40
|
+
const sample = () => {
|
|
41
|
+
const progress = webAnimations(animation).map((value) => value.effect?.getComputedTiming().progress).find((value) => value != null);
|
|
42
|
+
if (progress != null) release?.progress?.(progress);
|
|
43
|
+
frame = win.requestAnimationFrame(sample);
|
|
44
|
+
};
|
|
45
|
+
frame = win.requestAnimationFrame(sample);
|
|
46
|
+
};
|
|
35
47
|
let preparation;
|
|
36
48
|
let disposed = false;
|
|
37
49
|
let interactiveReady = false;
|
|
38
50
|
let step;
|
|
39
51
|
let end;
|
|
40
|
-
const prepare = () => preparation ?? (preparation = suspendNativeUI(scopes
|
|
52
|
+
const prepare = (interactive = false) => preparation ?? (preparation = suspendNativeUI(scopes, {
|
|
53
|
+
navigation: true,
|
|
54
|
+
...direction ? { direction } : {},
|
|
55
|
+
...interactive ? { interactive: true } : {}
|
|
56
|
+
}).then((resume) => {
|
|
41
57
|
if (disposed) resume(true);
|
|
42
58
|
else release = resume;
|
|
43
59
|
}));
|
|
44
60
|
const finish = (canceled = false) => {
|
|
61
|
+
if (frame) win?.cancelAnimationFrame(frame);
|
|
62
|
+
frame = 0;
|
|
63
|
+
release?.progress?.(canceled ? 0 : 1);
|
|
45
64
|
release?.(canceled);
|
|
46
65
|
release = void 0;
|
|
47
66
|
preparation = void 0;
|
|
@@ -51,7 +70,9 @@ const connectNativeUITransition = (animation, entering, leaving) => {
|
|
|
51
70
|
await prepare();
|
|
52
71
|
if (disposed) return;
|
|
53
72
|
try {
|
|
54
|
-
|
|
73
|
+
const playing = play(options);
|
|
74
|
+
track();
|
|
75
|
+
await playing;
|
|
55
76
|
} finally {
|
|
56
77
|
finish();
|
|
57
78
|
}
|
|
@@ -60,23 +81,36 @@ const connectNativeUITransition = (animation, entering, leaving) => {
|
|
|
60
81
|
interactiveReady = false;
|
|
61
82
|
step = void 0;
|
|
62
83
|
end = void 0;
|
|
63
|
-
prepare().then(() => {
|
|
84
|
+
prepare(true).then(() => {
|
|
64
85
|
if (disposed) return;
|
|
65
86
|
progressStart(...args);
|
|
87
|
+
track();
|
|
66
88
|
interactiveReady = true;
|
|
67
|
-
if (step !== void 0)
|
|
68
|
-
|
|
89
|
+
if (step !== void 0) {
|
|
90
|
+
progressStep(step);
|
|
91
|
+
release?.progress?.(step);
|
|
92
|
+
}
|
|
93
|
+
if (end) {
|
|
94
|
+
release?.finishGesture?.(end[0] === 1);
|
|
95
|
+
progressEnd(...end);
|
|
96
|
+
}
|
|
69
97
|
});
|
|
70
98
|
return animation;
|
|
71
99
|
};
|
|
72
100
|
animation.progressStep = (value) => {
|
|
73
101
|
step = value;
|
|
74
|
-
if (interactiveReady)
|
|
102
|
+
if (interactiveReady) {
|
|
103
|
+
progressStep(value);
|
|
104
|
+
release?.progress?.(value);
|
|
105
|
+
}
|
|
75
106
|
return animation;
|
|
76
107
|
};
|
|
77
108
|
animation.progressEnd = (...args) => {
|
|
78
109
|
end = args;
|
|
79
|
-
if (interactiveReady)
|
|
110
|
+
if (interactiveReady) {
|
|
111
|
+
release?.finishGesture?.(args[0] === 1);
|
|
112
|
+
progressEnd(...args);
|
|
113
|
+
}
|
|
80
114
|
return animation;
|
|
81
115
|
};
|
|
82
116
|
animation.destroy = (...args) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"native-ui.js","names":[],"sources":["../../src/transition/native-ui.ts"],"sourcesContent":["import type { Animation } from '@ionic/core/components';\nimport { hasNativeUIIntegration, suspendNativeUI, VERTICAL_BARS_TRANSITION_CANCELED } from '@rdlabo/capacitor-native-ui/integration';\nimport type { AnimationBuilder } from '@ionic/core/components';\n\n/**\n * Keeps an existing Ionic navigation animation and coordinates native control handoff.\n * Register the returned builder as navAnimation. Do not use it for overlay animations.\n * The builder must return a fresh Animation for each navigation; Ionic destroys it afterward.\n */\nexport const withNativeUITransition =\n (builder: AnimationBuilder): AnimationBuilder =>\n (baseEl, opts) => {\n const animation = builder(baseEl, opts);\n if (opts?.enteringEl) connectNativeUITransition(animation, opts.enteringEl, opts.leavingEl);\n return animation;\n };\n\nconst connectedTransitions = new WeakSet<Animation>();\nconst cancellationTransitions = new WeakSet<Animation>();\n\n/** Ionic write hooks cannot await the bridge. Gate playback, including interactive playback. */\nexport const connectNativeUITransition = (animation: Animation
|
|
1
|
+
{"version":3,"file":"native-ui.js","names":[],"sources":["../../src/transition/native-ui.ts"],"sourcesContent":["import type { NativeUITransitionRelease } from '@rdlabo/capacitor-native-ui/integration';\nimport type { Animation } from '@ionic/core/components';\nimport { hasNativeUIIntegration, suspendNativeUI, VERTICAL_BARS_TRANSITION_CANCELED } from '@rdlabo/capacitor-native-ui/integration';\nimport type { AnimationBuilder } from '@ionic/core/components';\n\n/**\n * Keeps an existing Ionic navigation animation and coordinates native control handoff.\n * Register the returned builder as navAnimation. Do not use it for overlay animations.\n * The builder must return a fresh Animation for each navigation; Ionic destroys it afterward.\n */\nexport const withNativeUITransition =\n (builder: AnimationBuilder): AnimationBuilder =>\n (baseEl, opts) => {\n const animation = builder(baseEl, opts);\n if (opts?.enteringEl) connectNativeUITransition(animation, opts.enteringEl, opts.leavingEl, opts.direction);\n return animation;\n };\n\nconst connectedTransitions = new WeakSet<Animation>();\nconst cancellationTransitions = new WeakSet<Animation>();\n\n/** Ionic write hooks cannot await the bridge. Gate playback, including interactive playback. */\nexport const connectNativeUITransition = (\n animation: Animation,\n entering: HTMLElement,\n leaving?: HTMLElement,\n direction?: 'forward' | 'back' | 'root',\n) => {\n if (leaving && !cancellationTransitions.has(animation)) {\n cancellationTransitions.add(animation);\n animation.onFinish((step) => {\n if (step === 0) leaving.dispatchEvent(new CustomEvent(VERTICAL_BARS_TRANSITION_CANCELED, { bubbles: true, detail: { entering } }));\n });\n }\n if (connectedTransitions.has(animation) || !hasNativeUIIntegration(entering.ownerDocument)) return;\n connectedTransitions.add(animation);\n const scopes = leaving ? [entering, leaving] : [entering];\n const play = animation.play.bind(animation);\n const progressStart = animation.progressStart.bind(animation);\n const progressStep = animation.progressStep.bind(animation);\n const progressEnd = animation.progressEnd.bind(animation);\n const destroy = animation.destroy.bind(animation);\n let release: NativeUITransitionRelease | undefined;\n let frame = 0;\n const win = entering.ownerDocument.defaultView;\n const webAnimations = (value: Animation): globalThis.Animation[] => [\n ...value.getWebAnimations(),\n ...value.childAnimations.flatMap(webAnimations),\n ];\n const track = () => {\n if (!release?.progress || !win) return;\n const sample = () => {\n const progress = webAnimations(animation)\n .map((value) => value.effect?.getComputedTiming().progress)\n .find((value) => value != null);\n if (progress != null) release?.progress?.(progress);\n frame = win.requestAnimationFrame(sample);\n };\n frame = win.requestAnimationFrame(sample);\n };\n let preparation: Promise<void> | undefined;\n let disposed = false;\n let interactiveReady = false;\n let step: number | undefined;\n let end: Parameters<Animation['progressEnd']> | undefined;\n const prepare = (interactive = false) =>\n (preparation ??= suspendNativeUI(scopes, {\n navigation: true,\n ...(direction ? { direction } : {}),\n ...(interactive ? { interactive: true } : {}),\n }).then((resume) => {\n if (disposed) resume(true);\n else release = resume;\n }));\n const finish = (canceled = false) => {\n if (frame) win?.cancelAnimationFrame(frame);\n frame = 0;\n release?.progress?.(canceled ? 0 : 1);\n release?.(canceled);\n release = undefined;\n preparation = undefined;\n };\n animation.onFinish((step) => finish(step === 0));\n animation.play = async (options) => {\n await prepare();\n if (disposed) return;\n try {\n const playing = play(options);\n track();\n await playing;\n } finally {\n finish();\n }\n };\n animation.progressStart = (...args) => {\n interactiveReady = false;\n step = undefined;\n end = undefined;\n void prepare(true).then(() => {\n if (disposed) return;\n progressStart(...args);\n track();\n interactiveReady = true;\n if (step !== undefined) {\n progressStep(step);\n release?.progress?.(step);\n }\n if (end) {\n release?.finishGesture?.(end[0] === 1);\n progressEnd(...end);\n }\n });\n return animation;\n };\n animation.progressStep = (value) => {\n step = value;\n if (interactiveReady) {\n progressStep(value);\n release?.progress?.(value);\n }\n return animation;\n };\n animation.progressEnd = (...args) => {\n end = args;\n if (interactiveReady) {\n release?.finishGesture?.(args[0] === 1);\n progressEnd(...args);\n }\n return animation;\n };\n animation.destroy = (...args) => {\n disposed = true;\n finish(true);\n return destroy(...args);\n };\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rdlabo/ionic-theme-ios27",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "2.0.0-beta.
|
|
4
|
+
"version": "2.0.0-beta.pr317.sha707be106d55b",
|
|
5
5
|
"description": "iOS27 Theme for Ionic Framework",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.js",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
},
|
|
47
47
|
"homepage": "https://docs.rdlabo.dev/projects/ionic-theme-ios27",
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@rdlabo/capacitor-native-ui": "2.0.0-beta.
|
|
49
|
+
"@rdlabo/capacitor-native-ui": "2.0.0-beta.pr317.sha707be106d55b",
|
|
50
50
|
"@rdlabo/ionic-theme-utils": "0.1.5",
|
|
51
51
|
"@stencil/core": "^4.43.5",
|
|
52
52
|
"ionicons": "^8.1.0"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createIosTransitionAnimation, shadow } from '@rdlabo/ionic-theme-utils';
|
|
2
|
+
import { createAnimation } from '@ionic/core/components/index.js';
|
|
2
3
|
import type { Animation } from '@ionic/core/components';
|
|
3
4
|
import type { TransitionOptions } from './index';
|
|
4
5
|
import { getIonPageElement } from './index';
|
|
@@ -24,6 +25,48 @@ export const setConfig = (config: Partial<IosTransitionConfig>): void => {
|
|
|
24
25
|
if (config.radius !== undefined) nativeWebViewMetrics.radius = config.radius;
|
|
25
26
|
};
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
);
|
|
28
|
+
const buildTransition = createIosTransitionAnimation<TransitionOptions>(transitionConfig);
|
|
29
|
+
export const iosTransitionAnimation: (navEl: HTMLElement, opts: TransitionOptions) => Animation = withNativeUITransition((navEl, opts) => {
|
|
30
|
+
const animation = buildTransition(navEl, opts);
|
|
31
|
+
const backing = 'var(--ion-background-color, #fff)';
|
|
32
|
+
let ownsBacking = false;
|
|
33
|
+
// Translucent pages expose the outlet as they move. Keep that backing surface
|
|
34
|
+
// in the page theme instead of exposing WKWebView's transparent native backing.
|
|
35
|
+
animation.beforeAddWrite(() => {
|
|
36
|
+
if (navEl.style.backgroundColor) return;
|
|
37
|
+
const style = navEl.ownerDocument.defaultView?.getComputedStyle(navEl);
|
|
38
|
+
if (!style || !['transparent', 'rgba(0, 0, 0, 0)'].includes(style.backgroundColor)) return;
|
|
39
|
+
if (style.backgroundImage && style.backgroundImage !== 'none') return;
|
|
40
|
+
navEl.style.backgroundColor = backing;
|
|
41
|
+
ownsBacking = true;
|
|
42
|
+
});
|
|
43
|
+
const restoreBacking = () => {
|
|
44
|
+
if (ownsBacking && navEl.style.backgroundColor === backing && !navEl.style.getPropertyPriority('background-color'))
|
|
45
|
+
navEl.style.removeProperty('background-color');
|
|
46
|
+
ownsBacking = false;
|
|
47
|
+
};
|
|
48
|
+
animation.afterAddWrite(restoreBacking);
|
|
49
|
+
// Ionic destroy does not run after-write hooks when playback is interrupted.
|
|
50
|
+
const destroy = animation.destroy.bind(animation);
|
|
51
|
+
animation.destroy = (...args) => {
|
|
52
|
+
restoreBacking();
|
|
53
|
+
return destroy(...args);
|
|
54
|
+
};
|
|
55
|
+
// Translucent pages move as a whole, but outgoing toolbar actions still need
|
|
56
|
+
// the same exit fade as opaque headers when they return from native to Web.
|
|
57
|
+
const buttons = opts.leavingEl?.querySelectorAll(
|
|
58
|
+
':scope > ion-header.header-translucent ion-buttons, :scope > ion-header.header-translucent ion-toolbar > ion-button',
|
|
59
|
+
);
|
|
60
|
+
if (buttons?.length)
|
|
61
|
+
animation.addAnimation(
|
|
62
|
+
createAnimation()
|
|
63
|
+
.addElement(buttons)
|
|
64
|
+
.keyframes([
|
|
65
|
+
{ offset: 0, opacity: 1 },
|
|
66
|
+
// Finish the exit before the incoming title occupies this toolbar.
|
|
67
|
+
{ offset: 0.5, opacity: 0 },
|
|
68
|
+
{ offset: 1, opacity: 0 },
|
|
69
|
+
]),
|
|
70
|
+
);
|
|
71
|
+
return animation;
|
|
72
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { NativeUITransitionRelease } from '@rdlabo/capacitor-native-ui/integration';
|
|
1
2
|
import type { Animation } from '@ionic/core/components';
|
|
2
3
|
import { hasNativeUIIntegration, suspendNativeUI, VERTICAL_BARS_TRANSITION_CANCELED } from '@rdlabo/capacitor-native-ui/integration';
|
|
3
4
|
import type { AnimationBuilder } from '@ionic/core/components';
|
|
@@ -11,7 +12,7 @@ export const withNativeUITransition =
|
|
|
11
12
|
(builder: AnimationBuilder): AnimationBuilder =>
|
|
12
13
|
(baseEl, opts) => {
|
|
13
14
|
const animation = builder(baseEl, opts);
|
|
14
|
-
if (opts?.enteringEl) connectNativeUITransition(animation, opts.enteringEl, opts.leavingEl);
|
|
15
|
+
if (opts?.enteringEl) connectNativeUITransition(animation, opts.enteringEl, opts.leavingEl, opts.direction);
|
|
15
16
|
return animation;
|
|
16
17
|
};
|
|
17
18
|
|
|
@@ -19,7 +20,12 @@ const connectedTransitions = new WeakSet<Animation>();
|
|
|
19
20
|
const cancellationTransitions = new WeakSet<Animation>();
|
|
20
21
|
|
|
21
22
|
/** Ionic write hooks cannot await the bridge. Gate playback, including interactive playback. */
|
|
22
|
-
export const connectNativeUITransition = (
|
|
23
|
+
export const connectNativeUITransition = (
|
|
24
|
+
animation: Animation,
|
|
25
|
+
entering: HTMLElement,
|
|
26
|
+
leaving?: HTMLElement,
|
|
27
|
+
direction?: 'forward' | 'back' | 'root',
|
|
28
|
+
) => {
|
|
23
29
|
if (leaving && !cancellationTransitions.has(animation)) {
|
|
24
30
|
cancellationTransitions.add(animation);
|
|
25
31
|
animation.onFinish((step) => {
|
|
@@ -34,18 +40,42 @@ export const connectNativeUITransition = (animation: Animation, entering: HTMLEl
|
|
|
34
40
|
const progressStep = animation.progressStep.bind(animation);
|
|
35
41
|
const progressEnd = animation.progressEnd.bind(animation);
|
|
36
42
|
const destroy = animation.destroy.bind(animation);
|
|
37
|
-
let release:
|
|
43
|
+
let release: NativeUITransitionRelease | undefined;
|
|
44
|
+
let frame = 0;
|
|
45
|
+
const win = entering.ownerDocument.defaultView;
|
|
46
|
+
const webAnimations = (value: Animation): globalThis.Animation[] => [
|
|
47
|
+
...value.getWebAnimations(),
|
|
48
|
+
...value.childAnimations.flatMap(webAnimations),
|
|
49
|
+
];
|
|
50
|
+
const track = () => {
|
|
51
|
+
if (!release?.progress || !win) return;
|
|
52
|
+
const sample = () => {
|
|
53
|
+
const progress = webAnimations(animation)
|
|
54
|
+
.map((value) => value.effect?.getComputedTiming().progress)
|
|
55
|
+
.find((value) => value != null);
|
|
56
|
+
if (progress != null) release?.progress?.(progress);
|
|
57
|
+
frame = win.requestAnimationFrame(sample);
|
|
58
|
+
};
|
|
59
|
+
frame = win.requestAnimationFrame(sample);
|
|
60
|
+
};
|
|
38
61
|
let preparation: Promise<void> | undefined;
|
|
39
62
|
let disposed = false;
|
|
40
63
|
let interactiveReady = false;
|
|
41
64
|
let step: number | undefined;
|
|
42
65
|
let end: Parameters<Animation['progressEnd']> | undefined;
|
|
43
|
-
const prepare = () =>
|
|
44
|
-
(preparation ??= suspendNativeUI(scopes
|
|
66
|
+
const prepare = (interactive = false) =>
|
|
67
|
+
(preparation ??= suspendNativeUI(scopes, {
|
|
68
|
+
navigation: true,
|
|
69
|
+
...(direction ? { direction } : {}),
|
|
70
|
+
...(interactive ? { interactive: true } : {}),
|
|
71
|
+
}).then((resume) => {
|
|
45
72
|
if (disposed) resume(true);
|
|
46
73
|
else release = resume;
|
|
47
74
|
}));
|
|
48
75
|
const finish = (canceled = false) => {
|
|
76
|
+
if (frame) win?.cancelAnimationFrame(frame);
|
|
77
|
+
frame = 0;
|
|
78
|
+
release?.progress?.(canceled ? 0 : 1);
|
|
49
79
|
release?.(canceled);
|
|
50
80
|
release = undefined;
|
|
51
81
|
preparation = undefined;
|
|
@@ -55,7 +85,9 @@ export const connectNativeUITransition = (animation: Animation, entering: HTMLEl
|
|
|
55
85
|
await prepare();
|
|
56
86
|
if (disposed) return;
|
|
57
87
|
try {
|
|
58
|
-
|
|
88
|
+
const playing = play(options);
|
|
89
|
+
track();
|
|
90
|
+
await playing;
|
|
59
91
|
} finally {
|
|
60
92
|
finish();
|
|
61
93
|
}
|
|
@@ -64,23 +96,36 @@ export const connectNativeUITransition = (animation: Animation, entering: HTMLEl
|
|
|
64
96
|
interactiveReady = false;
|
|
65
97
|
step = undefined;
|
|
66
98
|
end = undefined;
|
|
67
|
-
void prepare().then(() => {
|
|
99
|
+
void prepare(true).then(() => {
|
|
68
100
|
if (disposed) return;
|
|
69
101
|
progressStart(...args);
|
|
102
|
+
track();
|
|
70
103
|
interactiveReady = true;
|
|
71
|
-
if (step !== undefined)
|
|
72
|
-
|
|
104
|
+
if (step !== undefined) {
|
|
105
|
+
progressStep(step);
|
|
106
|
+
release?.progress?.(step);
|
|
107
|
+
}
|
|
108
|
+
if (end) {
|
|
109
|
+
release?.finishGesture?.(end[0] === 1);
|
|
110
|
+
progressEnd(...end);
|
|
111
|
+
}
|
|
73
112
|
});
|
|
74
113
|
return animation;
|
|
75
114
|
};
|
|
76
115
|
animation.progressStep = (value) => {
|
|
77
116
|
step = value;
|
|
78
|
-
if (interactiveReady)
|
|
117
|
+
if (interactiveReady) {
|
|
118
|
+
progressStep(value);
|
|
119
|
+
release?.progress?.(value);
|
|
120
|
+
}
|
|
79
121
|
return animation;
|
|
80
122
|
};
|
|
81
123
|
animation.progressEnd = (...args) => {
|
|
82
124
|
end = args;
|
|
83
|
-
if (interactiveReady)
|
|
125
|
+
if (interactiveReady) {
|
|
126
|
+
release?.finishGesture?.(args[0] === 1);
|
|
127
|
+
progressEnd(...args);
|
|
128
|
+
}
|
|
84
129
|
return animation;
|
|
85
130
|
};
|
|
86
131
|
animation.destroy = (...args) => {
|