@rdlabo/ionic-theme-ios27 1.1.0 → 1.2.0-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 +8 -1
- package/dist/css/components/ion-list.css +1 -1
- package/dist/css/components/ion-menu.css +1 -1
- package/dist/css/ionic-theme-ios27.css +1 -1
- package/dist/css/vertical-bars.css +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -8
- package/dist/native/components/index.d.ts +1 -0
- package/dist/native/components/index.d.ts.map +1 -1
- package/dist/native/components/index.js +8 -3
- package/dist/native/components/ion-back-button.d.ts.map +1 -1
- package/dist/native/components/ion-back-button.js +5 -2
- package/dist/native/components/ion-button.d.ts.map +1 -1
- package/dist/native/components/ion-button.js +10 -2
- package/dist/native/components/ion-buttons.d.ts.map +1 -1
- package/dist/native/components/ion-buttons.js +12 -4
- package/dist/native/components/ion-fab.js +4 -4
- package/dist/native/components/ion-menu-button.d.ts.map +1 -1
- package/dist/native/components/ion-menu-button.js +5 -4
- package/dist/native/components/searchable-tabs.d.ts.map +1 -1
- package/dist/native/components/searchable-tabs.js +4 -1
- package/dist/native/definitions.d.ts +29 -3
- package/dist/native/definitions.d.ts.map +1 -1
- package/dist/native/definitions.js +6 -1
- package/dist/native/index.d.ts +6 -2
- package/dist/native/index.d.ts.map +1 -1
- package/dist/native/index.js +154 -17
- package/dist/native/prehide.d.ts +5 -0
- package/dist/native/prehide.d.ts.map +1 -0
- package/dist/native/prehide.js +246 -0
- package/dist/native/runtime.d.ts +1 -1
- package/dist/native/runtime.d.ts.map +1 -1
- package/dist/native/runtime.js +162 -28
- package/dist/native/shared/candidate.d.ts.map +1 -1
- package/dist/native/shared/candidate.js +7 -3
- package/dist/native/shared/dom.d.ts +33 -2
- package/dist/native/shared/dom.d.ts.map +1 -1
- package/dist/native/shared/dom.js +171 -10
- package/dist/native/vertical-bars-web.d.ts +3 -0
- package/dist/native/vertical-bars-web.d.ts.map +1 -0
- package/dist/native/vertical-bars-web.js +341 -0
- package/dist/native-integration/index.d.ts +3 -2
- package/dist/native-integration/index.d.ts.map +1 -1
- package/dist/native-integration/index.js +11 -5
- package/dist/transition/index.d.ts +2 -2
- package/dist/transition/index.d.ts.map +1 -1
- package/dist/transition/index.js +1 -1
- package/dist/transition/ios.transition.d.ts.map +1 -1
- package/dist/transition/ios.transition.js +1 -0
- package/dist/utils.d.ts +3 -3
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +1 -1
- package/dist/vertical-bars.d.ts +4 -0
- package/dist/vertical-bars.d.ts.map +1 -0
- package/dist/vertical-bars.js +2 -0
- package/docs/iphone-duo.md +255 -0
- package/docs/migration.md +2 -2
- package/docs/native-ui-shell.md +15 -3
- package/docs/special-markup.md +6 -0
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellSearchController.swift +15 -1
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellVerticalBars.swift +358 -0
- package/ios/Sources/IonicNativeUIShellPlugin/IonicNativeUIShellPlugin.swift +215 -35
- package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellSnapshot.swift +10 -1
- package/ios/Tests/IonicNativeUIShellPluginTests/ShellSnapshotTests.swift +29 -0
- package/package.json +6 -2
- package/src/index.ts +15 -8
- package/src/native/components/index.ts +12 -3
- package/src/native/components/ion-back-button.ts +8 -2
- package/src/native/components/ion-button.ts +12 -2
- package/src/native/components/ion-buttons.ts +9 -4
- package/src/native/components/ion-fab.ts +4 -4
- package/src/native/components/ion-menu-button.ts +5 -4
- package/src/native/components/searchable-tabs.ts +9 -1
- package/src/native/definitions.ts +40 -3
- package/src/native/index.ts +188 -20
- package/src/native/prehide.ts +255 -0
- package/src/native/runtime.ts +173 -27
- package/src/native/shared/candidate.ts +11 -3
- package/src/native/shared/dom.ts +212 -11
- package/src/native/vertical-bars-web.ts +374 -0
- package/src/native-integration/index.ts +13 -8
- package/src/styles/components/ion-list.scss +2 -2
- package/src/styles/components/ion-menu.scss +2 -2
- package/src/styles/ionic-theme-ios27.scss +1 -0
- package/src/styles/vertical-bars.scss +342 -0
- package/src/transition/index.ts +11 -6
- package/src/transition/ios.transition.ts +1 -0
- package/src/utils.ts +8 -8
- package/src/vertical-bars.ts +3 -0
- package/dist/native/lifecycle.d.ts +0 -4
- package/dist/native/lifecycle.d.ts.map +0 -1
- package/dist/native/lifecycle.js +0 -34
- package/src/native/lifecycle.ts +0 -38
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { Animation } from '@ionic/core';
|
|
3
3
|
|
|
4
4
|
export interface NativeUIShellIntegration {
|
|
5
|
-
suspend(scopes: HTMLElement[]): Promise<() => void>;
|
|
5
|
+
suspend(scopes: HTMLElement[]): Promise<(canceled?: boolean) => void>;
|
|
6
6
|
search?(binding: NativeSearchBinding, active: boolean, focus?: boolean): Promise<boolean>;
|
|
7
7
|
}
|
|
8
8
|
|
|
@@ -32,6 +32,7 @@ export const requestNativeSearch = async (binding: NativeSearchBinding, active:
|
|
|
32
32
|
(await runtimes.get(binding.tabBar.ownerDocument)?.search?.(binding, active, focus)) ?? false;
|
|
33
33
|
|
|
34
34
|
const runtimes = new WeakMap<Document, NativeUIShellIntegration>();
|
|
35
|
+
export const VERTICAL_BARS_TRANSITION_CANCELED = 'iosThemeVerticalBarsTransitionCanceled';
|
|
35
36
|
|
|
36
37
|
export const setNativeUIShellIntegration = (doc: Document, runtime?: NativeUIShellIntegration) => {
|
|
37
38
|
if (runtime) runtimes.set(doc, runtime);
|
|
@@ -40,11 +41,15 @@ export const setNativeUIShellIntegration = (doc: Document, runtime?: NativeUIShe
|
|
|
40
41
|
|
|
41
42
|
export const isNativeUIShell = (element: HTMLElement) => element.hasAttribute('data-native-ui-shell');
|
|
42
43
|
|
|
43
|
-
export const suspendNativeUIShell = async (scopes: HTMLElement[]): Promise<() => void> =>
|
|
44
|
+
export const suspendNativeUIShell = async (scopes: HTMLElement[]): Promise<(canceled?: boolean) => void> =>
|
|
44
45
|
(await runtimes.get(scopes[0]?.ownerDocument)?.suspend(scopes)) ?? (() => {});
|
|
45
46
|
|
|
46
47
|
/** Ionic write hooks cannot await the bridge. Gate playback, including interactive playback. */
|
|
47
48
|
export const connectNativeUIShellTransition = (animation: Animation, entering: HTMLElement, leaving?: HTMLElement) => {
|
|
49
|
+
if (leaving)
|
|
50
|
+
animation.onFinish((step) => {
|
|
51
|
+
if (step === 0) leaving.dispatchEvent(new CustomEvent(VERTICAL_BARS_TRANSITION_CANCELED, { bubbles: true, detail: { entering } }));
|
|
52
|
+
});
|
|
48
53
|
if (!runtimes.has(entering.ownerDocument)) return;
|
|
49
54
|
const scopes = leaving ? [entering, leaving] : [entering];
|
|
50
55
|
const play = animation.play.bind(animation);
|
|
@@ -52,7 +57,7 @@ export const connectNativeUIShellTransition = (animation: Animation, entering: H
|
|
|
52
57
|
const progressStep = animation.progressStep.bind(animation);
|
|
53
58
|
const progressEnd = animation.progressEnd.bind(animation);
|
|
54
59
|
const destroy = animation.destroy.bind(animation);
|
|
55
|
-
let release: (() => void) | undefined;
|
|
60
|
+
let release: ((canceled?: boolean) => void) | undefined;
|
|
56
61
|
let preparation: Promise<void> | undefined;
|
|
57
62
|
let disposed = false;
|
|
58
63
|
let interactiveReady = false;
|
|
@@ -60,15 +65,15 @@ export const connectNativeUIShellTransition = (animation: Animation, entering: H
|
|
|
60
65
|
let end: Parameters<Animation['progressEnd']> | undefined;
|
|
61
66
|
const prepare = () =>
|
|
62
67
|
(preparation ??= suspendNativeUIShell(scopes).then((resume) => {
|
|
63
|
-
if (disposed) resume();
|
|
68
|
+
if (disposed) resume(true);
|
|
64
69
|
else release = resume;
|
|
65
70
|
}));
|
|
66
|
-
const finish = () => {
|
|
67
|
-
release?.();
|
|
71
|
+
const finish = (canceled = false) => {
|
|
72
|
+
release?.(canceled);
|
|
68
73
|
release = undefined;
|
|
69
74
|
preparation = undefined;
|
|
70
75
|
};
|
|
71
|
-
animation.onFinish(finish);
|
|
76
|
+
animation.onFinish((step) => finish(step === 0));
|
|
72
77
|
animation.play = async (options) => {
|
|
73
78
|
await prepare();
|
|
74
79
|
if (disposed) return;
|
|
@@ -103,7 +108,7 @@ export const connectNativeUIShellTransition = (animation: Animation, entering: H
|
|
|
103
108
|
};
|
|
104
109
|
animation.destroy = (...args) => {
|
|
105
110
|
disposed = true;
|
|
106
|
-
finish();
|
|
111
|
+
finish(true);
|
|
107
112
|
return destroy(...args);
|
|
108
113
|
};
|
|
109
114
|
};
|
|
@@ -34,11 +34,11 @@ ion-list.list-inset.ios:not(.ios-theme-disabled, .ios26-disabled) {
|
|
|
34
34
|
--inner-padding-end: 0;
|
|
35
35
|
|
|
36
36
|
&::part(native) {
|
|
37
|
-
padding-
|
|
37
|
+
padding-inline-end: calc(var(--ion-safe-area-right, 0px) + 18px);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
&:dir(rtl)::part(native) {
|
|
41
|
-
padding-
|
|
41
|
+
padding-inline-end: calc(var(--ion-safe-area-left, 0px) + 18px);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
--min-height: 52px;
|
|
@@ -4,7 +4,7 @@ ion-split-pane.ios:not(.ios-theme-disabled, .ios26-disabled) {
|
|
|
4
4
|
|
|
5
5
|
ion-split-pane.ios:not(.ios-theme-disabled, .ios26-disabled).split-pane-visible:has(ion-menu.menu-pane-visible) {
|
|
6
6
|
.split-pane-main {
|
|
7
|
-
ion-tab-bar.ios:not(.ios-theme-disabled, .ios26-disabled) {
|
|
7
|
+
ion-tab-bar.ios:not(.ios-theme-disabled, .ios26-disabled):not(:where(.ios-theme-vertical-bars *)) {
|
|
8
8
|
width: calc(
|
|
9
9
|
100% - calc(18px + var(--ion-safe-area-left, 0px)) - calc(18px + var(--ion-safe-area-right, 0px)) - 60px -
|
|
10
10
|
12px - var(--ios-theme-menu-width, var(--ios26-menu-width))
|
|
@@ -43,7 +43,7 @@ ion-split-pane.ios:not(.ios-theme-disabled, .ios26-disabled).split-pane-visible:
|
|
|
43
43
|
ion-footer ion-toolbar {
|
|
44
44
|
--padding-start: var(--ios-theme-menu-width, var(--ios26-menu-width));
|
|
45
45
|
}
|
|
46
|
-
ion-tab-bar.ios:not(.ios-theme-disabled, .ios26-disabled) {
|
|
46
|
+
ion-tab-bar.ios:not(.ios-theme-disabled, .ios26-disabled):not(:where(.ios-theme-vertical-bars *)) {
|
|
47
47
|
&[slot='bottom'] {
|
|
48
48
|
inset-inline-start: calc(16px + var(--ion-safe-area-left, 0px) + var(--ios-theme-menu-width, var(--ios26-menu-width)));
|
|
49
49
|
&:dir(rtl) {
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
@use 'utils/api';
|
|
2
|
+
@use 'utils/glass';
|
|
3
|
+
|
|
4
|
+
// iPhone Duo simulation: the system reserves 80pt at the physical right edge.
|
|
5
|
+
// This entry point does not load the iOS 27 theme or change ordinary Ionic UI.
|
|
6
|
+
// A split menu is independent of the vertical control area. Its width is
|
|
7
|
+
// selected by the application from the device posture, not the viewport width.
|
|
8
|
+
@property --ios-theme-split-pane-width {
|
|
9
|
+
syntax: '<length>';
|
|
10
|
+
inherits: false;
|
|
11
|
+
initial-value: 320px;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
ion-split-pane.ios-theme-split-pane-half-open {
|
|
15
|
+
--ios-theme-split-pane-width: 50vw;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
ion-app.ios-theme-vertical-bars {
|
|
19
|
+
--ios-theme-vertical-bars-safe-area-left-resolved: var(--ios-theme-vertical-bars-safe-area-left, 0px);
|
|
20
|
+
--ios-theme-vertical-bars-safe-area-right-resolved: var(
|
|
21
|
+
--ios-theme-vertical-bars-safe-area-right,
|
|
22
|
+
var(--ios-theme-vertical-bars-native-inset, max(80px, env(safe-area-inset-right, 0px)))
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
ion-app.ios-theme-vertical-bars.ios-theme-vertical-bars-left {
|
|
27
|
+
--ios-theme-vertical-bars-safe-area-left-resolved: var(
|
|
28
|
+
--ios-theme-vertical-bars-safe-area-left,
|
|
29
|
+
var(--ios-theme-vertical-bars-native-inset, max(80px, env(safe-area-inset-left, 0px)))
|
|
30
|
+
);
|
|
31
|
+
--ios-theme-vertical-bars-safe-area-right-resolved: var(--ios-theme-vertical-bars-safe-area-right, 0px);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Supply material defaults only to Web projections, never to ordinary controls.
|
|
35
|
+
ion-app.ios-theme-vertical-bars.ios-theme-vertical-bars-toolbar-ready
|
|
36
|
+
> :is(ion-menu-button, ion-button).ios-theme-vertical-bars-toolbar-projection {
|
|
37
|
+
--ios26-glass-background-rgb: var(--ion-background-color-rgb, 255, 255, 255);
|
|
38
|
+
--ios26-glass-border-color-rgb: var(--ion-background-color-rgb, 255, 255, 255);
|
|
39
|
+
--ios26-glass-box-shadow-color-rgb: var(--ion-text-color-rgb, 0, 0, 0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// The transition shade belongs to the Web page, never to the control area.
|
|
43
|
+
ion-app.ios-theme-vertical-bars .ios-transition-shade {
|
|
44
|
+
clip-path: inset(0 var(--ios-theme-vertical-bars-safe-area-right-resolved) 0 var(--ios-theme-vertical-bars-safe-area-left-resolved));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Keep page backgrounds and the router full width; move only their foregrounds.
|
|
48
|
+
ion-app.ios-theme-vertical-bars
|
|
49
|
+
ion-content:is(.ios, .md):not(.ios-theme-disabled, .ios26-disabled):not(:where(ion-menu *, ion-modal *, ion-popover *)) {
|
|
50
|
+
--ion-safe-area-left: 0px;
|
|
51
|
+
--ion-safe-area-right: 0px;
|
|
52
|
+
|
|
53
|
+
&::part(scroll) {
|
|
54
|
+
padding-inline-start: calc(var(--padding-start, 0px) + var(--ios-theme-vertical-bars-safe-area-left-resolved));
|
|
55
|
+
padding-inline-end: calc(var(--padding-end, 0px) + var(--ios-theme-vertical-bars-safe-area-right-resolved));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
&:dir(rtl)::part(scroll) {
|
|
59
|
+
padding-inline-start: calc(var(--padding-start, 0px) + var(--ios-theme-vertical-bars-safe-area-right-resolved));
|
|
60
|
+
padding-inline-end: calc(var(--padding-end, 0px) + var(--ios-theme-vertical-bars-safe-area-left-resolved));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
ion-app.ios-theme-vertical-bars
|
|
65
|
+
ion-toolbar:is(.ios, .md):not(.ios-theme-disabled, .ios26-disabled):not(:where(ion-menu *, ion-modal *, ion-popover *)) {
|
|
66
|
+
--ion-safe-area-left: 0px;
|
|
67
|
+
--ion-safe-area-right: 0px;
|
|
68
|
+
|
|
69
|
+
// Collapse headers already sit inside the padded ion-content scroll area.
|
|
70
|
+
&:not(:where(ion-header.header-collapse-condense *, ion-footer.footer-collapse-fade *)) {
|
|
71
|
+
&::part(container) {
|
|
72
|
+
padding-inline-start: calc(var(--padding-start, 0px) + var(--ios-theme-vertical-bars-safe-area-left-resolved));
|
|
73
|
+
padding-inline-end: calc(var(--padding-end, 0px) + var(--ios-theme-vertical-bars-safe-area-right-resolved));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
&:dir(rtl)::part(container) {
|
|
77
|
+
padding-inline-start: calc(var(--padding-start, 0px) + var(--ios-theme-vertical-bars-safe-area-right-resolved));
|
|
78
|
+
padding-inline-end: calc(var(--padding-end, 0px) + var(--ios-theme-vertical-bars-safe-area-left-resolved));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
ion-app.ios-theme-vertical-bars
|
|
84
|
+
ion-fab:is(.ios, .md):not(.ios-theme-disabled, .ios26-disabled):not(:where(ion-menu *, ion-modal *, ion-popover *)) {
|
|
85
|
+
--ion-safe-area-left: 0px;
|
|
86
|
+
--ion-safe-area-right: 0px;
|
|
87
|
+
--ios-theme-vertical-bars-fab-edge-gap: 16px;
|
|
88
|
+
--ios-theme-vertical-bars-fab-start-offset: var(--ios-theme-menu-width, var(--ios26-menu-width, 0px));
|
|
89
|
+
|
|
90
|
+
&.fab-horizontal-start {
|
|
91
|
+
inset-inline-start: calc(
|
|
92
|
+
var(--ios-theme-vertical-bars-fab-edge-gap) + var(--ios-theme-vertical-bars-fab-start-offset) +
|
|
93
|
+
var(--ios-theme-vertical-bars-safe-area-left-resolved)
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
&.fab-horizontal-end {
|
|
97
|
+
inset-inline-end: calc(var(--ios-theme-vertical-bars-fab-edge-gap) + var(--ios-theme-vertical-bars-safe-area-right-resolved));
|
|
98
|
+
}
|
|
99
|
+
&:dir(rtl).fab-horizontal-start {
|
|
100
|
+
inset-inline-start: calc(
|
|
101
|
+
var(--ios-theme-vertical-bars-fab-edge-gap) + var(--ios-theme-vertical-bars-fab-start-offset) +
|
|
102
|
+
var(--ios-theme-vertical-bars-safe-area-right-resolved)
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
&:dir(rtl).fab-horizontal-end {
|
|
106
|
+
inset-inline-end: calc(var(--ios-theme-vertical-bars-fab-edge-gap) + var(--ios-theme-vertical-bars-safe-area-left-resolved));
|
|
107
|
+
}
|
|
108
|
+
&:has(.fab-button-small) {
|
|
109
|
+
--ios-theme-vertical-bars-fab-edge-gap: 10px;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Menus retain their own safe-area handling and full-width animation host.
|
|
114
|
+
ion-app.ios-theme-vertical-bars {
|
|
115
|
+
ion-menu:is(.ios, .md).menu-side-end:not(:dir(rtl)):not(.ios-theme-disabled, .ios26-disabled),
|
|
116
|
+
ion-menu:is(.ios, .md).menu-side-start:dir(rtl):not(.ios-theme-disabled, .ios26-disabled) {
|
|
117
|
+
--ion-safe-area-right: 0px;
|
|
118
|
+
&::part(container) {
|
|
119
|
+
margin-right: var(--ios-theme-vertical-bars-safe-area-right-resolved);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
ion-menu:is(.ios, .md).menu-side-start:not(:dir(rtl)):not(.ios-theme-disabled, .ios26-disabled),
|
|
123
|
+
ion-menu:is(.ios, .md).menu-side-end:dir(rtl):not(.ios-theme-disabled, .ios26-disabled) {
|
|
124
|
+
--ion-safe-area-left: 0px;
|
|
125
|
+
&::part(container) {
|
|
126
|
+
margin-left: var(--ios-theme-vertical-bars-safe-area-left-resolved);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
ion-app.ios-theme-vertical-bars
|
|
132
|
+
ion-tabs:not(:where(ion-menu *, ion-modal *, ion-popover *))
|
|
133
|
+
> ion-tab-bar:is(.ios, .md):not(.ios-theme-disabled, .ios26-disabled) {
|
|
134
|
+
--ios-theme-side-tab-bar-width: 50px;
|
|
135
|
+
--ios-theme-side-tab-bar-gap: 8px;
|
|
136
|
+
--ios-theme-side-safe-area: var(--ios-theme-vertical-bars-safe-area-right-resolved);
|
|
137
|
+
contain: layout style;
|
|
138
|
+
position: absolute;
|
|
139
|
+
z-index: 2;
|
|
140
|
+
display: flex;
|
|
141
|
+
flex-direction: column;
|
|
142
|
+
width: 46px;
|
|
143
|
+
min-width: 46px;
|
|
144
|
+
max-width: 46px;
|
|
145
|
+
height: fit-content;
|
|
146
|
+
min-height: 0;
|
|
147
|
+
padding: 2px;
|
|
148
|
+
border-radius: 25px;
|
|
149
|
+
left: auto;
|
|
150
|
+
right: max(4px, calc((var(--ios-theme-side-safe-area) - var(--ios-theme-side-tab-bar-width)) / 2));
|
|
151
|
+
margin: 0;
|
|
152
|
+
top: auto;
|
|
153
|
+
bottom: calc(var(--ion-safe-area-bottom, 0px) + var(--ios-theme-side-tab-bar-gap));
|
|
154
|
+
|
|
155
|
+
> ion-tab-button:not(.ion-cloned-element) {
|
|
156
|
+
display: block;
|
|
157
|
+
flex: none;
|
|
158
|
+
width: 46px;
|
|
159
|
+
min-width: 46px;
|
|
160
|
+
height: 52px;
|
|
161
|
+
min-height: 52px;
|
|
162
|
+
border-radius: 23px;
|
|
163
|
+
&::part(native) {
|
|
164
|
+
justify-content: center;
|
|
165
|
+
}
|
|
166
|
+
ion-label {
|
|
167
|
+
position: absolute;
|
|
168
|
+
width: 1px;
|
|
169
|
+
height: 1px;
|
|
170
|
+
padding: 0;
|
|
171
|
+
margin: -1px;
|
|
172
|
+
overflow: hidden;
|
|
173
|
+
clip: rect(0, 0, 0, 0);
|
|
174
|
+
white-space: nowrap;
|
|
175
|
+
border: 0;
|
|
176
|
+
}
|
|
177
|
+
ion-icon {
|
|
178
|
+
margin: 0;
|
|
179
|
+
font-size: 28px;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
> ion-tab-button:not(.ion-cloned-element) ~ ion-tab-button:not(.ion-cloned-element) {
|
|
183
|
+
margin-block-start: 0;
|
|
184
|
+
margin-inline-start: 0;
|
|
185
|
+
}
|
|
186
|
+
&:has(> ion-tab-button:is(.ion-activated, :active)) > ion-tab-button:not(.ion-cloned-element) {
|
|
187
|
+
&::part(native) {
|
|
188
|
+
flex-direction: column;
|
|
189
|
+
padding-block: 4px;
|
|
190
|
+
}
|
|
191
|
+
ion-label {
|
|
192
|
+
position: static;
|
|
193
|
+
width: auto;
|
|
194
|
+
height: auto;
|
|
195
|
+
max-width: 44px;
|
|
196
|
+
padding: 0;
|
|
197
|
+
margin: 1px 0 0;
|
|
198
|
+
overflow: hidden;
|
|
199
|
+
clip: auto;
|
|
200
|
+
font-size: 0.5rem;
|
|
201
|
+
line-height: 1;
|
|
202
|
+
text-overflow: ellipsis;
|
|
203
|
+
white-space: nowrap;
|
|
204
|
+
}
|
|
205
|
+
ion-icon {
|
|
206
|
+
margin: 0;
|
|
207
|
+
font-size: 22px;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
ion-app.ios-theme-vertical-bars.ios-theme-vertical-bars-left
|
|
213
|
+
ion-tabs:not(:where(ion-menu *, ion-modal *, ion-popover *))
|
|
214
|
+
> ion-tab-bar:is(.ios, .md):not(.ios-theme-disabled, .ios26-disabled) {
|
|
215
|
+
--ios-theme-side-safe-area: var(--ios-theme-vertical-bars-safe-area-left-resolved);
|
|
216
|
+
right: auto;
|
|
217
|
+
left: max(4px, calc((var(--ios-theme-side-safe-area) - var(--ios-theme-side-tab-bar-width)) / 2));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.ios-theme-native-ui-shell-prehidden {
|
|
221
|
+
position: absolute !important;
|
|
222
|
+
visibility: hidden !important;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Hide an entering back source until JS decides whether it belongs in the rail.
|
|
226
|
+
html.ios-theme-native-ui-shell-prehide
|
|
227
|
+
ion-app.ios-theme-vertical-bars
|
|
228
|
+
ion-back-button:not(
|
|
229
|
+
[icon],
|
|
230
|
+
[color],
|
|
231
|
+
.ios-theme-vertical-bars-back-web-owned,
|
|
232
|
+
.ionic-theme-disabled,
|
|
233
|
+
.ios-theme-disabled,
|
|
234
|
+
.ios26-disabled,
|
|
235
|
+
.ios-theme-vertical-bars-back-button-projection
|
|
236
|
+
):not(
|
|
237
|
+
:where(
|
|
238
|
+
ion-menu *,
|
|
239
|
+
ion-modal *,
|
|
240
|
+
ion-popover *,
|
|
241
|
+
ion-header[collapse] *,
|
|
242
|
+
ion-footer[collapse] *,
|
|
243
|
+
ion-buttons.ios-theme-horizontal-only *,
|
|
244
|
+
.ios-theme-shell-disabled *,
|
|
245
|
+
.ionic-theme-disabled *,
|
|
246
|
+
.ios-theme-disabled *,
|
|
247
|
+
.ios26-disabled *
|
|
248
|
+
)
|
|
249
|
+
) {
|
|
250
|
+
position: absolute !important;
|
|
251
|
+
visibility: hidden !important;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
ion-app.ios-theme-vertical-bars.ios-theme-vertical-bars-toolbar-ready {
|
|
255
|
+
ion-toolbar:is(.ios, .md):not(.ios-theme-disabled, .ios26-disabled):not(:where(ion-menu *, ion-modal *, ion-popover *))
|
|
256
|
+
ion-back-button:is(.ios, .md)[data-native-ui-shell]:not(
|
|
257
|
+
.ios-theme-disabled,
|
|
258
|
+
.ios26-disabled,
|
|
259
|
+
.ios-theme-vertical-bars-back-button-projection
|
|
260
|
+
) {
|
|
261
|
+
display: none;
|
|
262
|
+
}
|
|
263
|
+
ion-toolbar:is(.ios, .md):not(.ios-theme-disabled, .ios26-disabled):not(:where(ion-menu *, ion-modal *, ion-popover *))
|
|
264
|
+
ion-buttons:is(.ios, .md)
|
|
265
|
+
> :is(ion-button, ion-menu-button):is(.ios, .md)[data-native-ui-shell]:not(
|
|
266
|
+
.ios-theme-disabled,
|
|
267
|
+
.ios26-disabled,
|
|
268
|
+
.ios-theme-vertical-bars-toolbar-action
|
|
269
|
+
) {
|
|
270
|
+
display: none;
|
|
271
|
+
}
|
|
272
|
+
ion-toolbar:is(.ios, .md):not(.ios-theme-disabled, .ios26-disabled):not(:where(ion-menu *, ion-modal *, ion-popover *))
|
|
273
|
+
ion-buttons:is(.ios, .md):has(> :is(ion-back-button, ion-button, ion-menu-button)[data-native-ui-shell]):not(
|
|
274
|
+
:has(> :is(ion-back-button, ion-button, ion-menu-button):not([data-native-ui-shell]))
|
|
275
|
+
) {
|
|
276
|
+
display: none;
|
|
277
|
+
}
|
|
278
|
+
> ion-back-button.ios-theme-vertical-bars-back-button-projection {
|
|
279
|
+
position: fixed;
|
|
280
|
+
z-index: 1001;
|
|
281
|
+
top: var(--ios-theme-vertical-bars-toolbar-top, 220px);
|
|
282
|
+
right: max(4px, calc((var(--ios-theme-vertical-bars-safe-area-right-resolved) - 46px) / 2));
|
|
283
|
+
display: block;
|
|
284
|
+
width: 46px;
|
|
285
|
+
height: 46px;
|
|
286
|
+
margin: 0;
|
|
287
|
+
}
|
|
288
|
+
> :is(.ios, .md).ios-theme-vertical-bars-toolbar-projection {
|
|
289
|
+
position: fixed;
|
|
290
|
+
z-index: 1001;
|
|
291
|
+
top: calc(var(--ios-theme-vertical-bars-toolbar-top, 220px) + var(--ios-theme-vertical-bars-toolbar-offset, 0px));
|
|
292
|
+
right: max(4px, calc((var(--ios-theme-vertical-bars-safe-area-right-resolved) - 46px) / 2));
|
|
293
|
+
width: 46px;
|
|
294
|
+
min-width: 46px;
|
|
295
|
+
height: 46px;
|
|
296
|
+
min-height: 46px;
|
|
297
|
+
margin: 0;
|
|
298
|
+
:is(ion-icon, svg) {
|
|
299
|
+
width: 22px;
|
|
300
|
+
height: 22px;
|
|
301
|
+
margin: 0;
|
|
302
|
+
font-size: 22px;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
> ion-menu-button:is(.ios, .md).ios-theme-vertical-bars-toolbar-projection {
|
|
306
|
+
@include api.glass-control-background;
|
|
307
|
+
border-radius: 50%;
|
|
308
|
+
}
|
|
309
|
+
> ion-button:is(.ios, .md).ios-theme-vertical-bars-toolbar-projection {
|
|
310
|
+
--background: rgba(var(--ios-theme-glass-background-rgb, var(--ios26-glass-background-rgb)), 0.72);
|
|
311
|
+
--border-radius: 50%;
|
|
312
|
+
@include glass.light-shadow('--box-shadow');
|
|
313
|
+
&::part(native) {
|
|
314
|
+
@include api.glass-control-background($include-background: false, $shadow: var(--box-shadow));
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
> ion-buttons:is(.ios, .md).ios-theme-vertical-bars-toolbar-projection {
|
|
318
|
+
display: flex;
|
|
319
|
+
flex-direction: column;
|
|
320
|
+
height: auto;
|
|
321
|
+
min-height: 0;
|
|
322
|
+
> :is(ion-button, ion-menu-button):is(.ios, .md).ios-theme-vertical-bars-toolbar-action {
|
|
323
|
+
display: block;
|
|
324
|
+
width: 44px;
|
|
325
|
+
min-width: 44px;
|
|
326
|
+
height: 46px;
|
|
327
|
+
min-height: 46px;
|
|
328
|
+
margin: 0 1px;
|
|
329
|
+
&::part(native) {
|
|
330
|
+
margin-inline: auto;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
ion-app.ios-theme-vertical-bars.ios-theme-vertical-bars-left {
|
|
337
|
+
> ion-back-button.ios-theme-vertical-bars-back-button-projection,
|
|
338
|
+
> :is(.ios, .md).ios-theme-vertical-bars-toolbar-projection {
|
|
339
|
+
right: auto;
|
|
340
|
+
left: max(4px, calc((var(--ios-theme-vertical-bars-safe-area-left-resolved) - 46px) / 2));
|
|
341
|
+
}
|
|
342
|
+
}
|
package/src/transition/index.ts
CHANGED
|
@@ -170,7 +170,7 @@ const waitForReady = async (opts: TransitionOptions, defaultDeep: boolean) => {
|
|
|
170
170
|
await notifyViewReady(opts.viewIsReady, opts.enteringEl);
|
|
171
171
|
};
|
|
172
172
|
|
|
173
|
-
const notifyViewReady = async (viewIsReady: undefined | ((enteringEl: HTMLElement) => Promise<
|
|
173
|
+
const notifyViewReady = async (viewIsReady: undefined | ((enteringEl: HTMLElement) => Promise<unknown>), enteringEl: HTMLElement) => {
|
|
174
174
|
if (viewIsReady) {
|
|
175
175
|
await viewIsReady(enteringEl);
|
|
176
176
|
}
|
|
@@ -180,7 +180,7 @@ const playTransition = (trans: Animation, opts: TransitionOptions): Promise<bool
|
|
|
180
180
|
const progressCallback = opts.progressCallback;
|
|
181
181
|
|
|
182
182
|
const promise = new Promise<boolean>((resolve) => {
|
|
183
|
-
trans.onFinish((currentStep
|
|
183
|
+
trans.onFinish((currentStep) => resolve(currentStep === 1));
|
|
184
184
|
});
|
|
185
185
|
|
|
186
186
|
// cool, let's do this, start the transition
|
|
@@ -236,8 +236,13 @@ export const waitForMount = (): Promise<void> => {
|
|
|
236
236
|
return new Promise((resolve) => raf(() => raf(() => resolve())));
|
|
237
237
|
};
|
|
238
238
|
|
|
239
|
-
|
|
240
|
-
|
|
239
|
+
type LazyLoadedElement = Element & {
|
|
240
|
+
componentOnReady?: () => Promise<unknown>;
|
|
241
|
+
__registerHost?: unknown;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
export const deepReady = async (el: Element | undefined): Promise<void> => {
|
|
245
|
+
const element = el as LazyLoadedElement | undefined;
|
|
241
246
|
if (element) {
|
|
242
247
|
if (element.componentOnReady != null) {
|
|
243
248
|
// eslint-disable-next-line custom-rules/no-component-on-ready-method
|
|
@@ -259,7 +264,7 @@ export const deepReady = async (el: any | undefined): Promise<void> => {
|
|
|
259
264
|
|
|
260
265
|
return;
|
|
261
266
|
}
|
|
262
|
-
await Promise.all(Array.from(element.children).map(deepReady));
|
|
267
|
+
await Promise.all(Array.from(element.children).map((child) => deepReady(child)));
|
|
263
268
|
}
|
|
264
269
|
};
|
|
265
270
|
|
|
@@ -358,7 +363,7 @@ const getIosIonHeader = (opts: TransitionOptions): HTMLElement | null => {
|
|
|
358
363
|
|
|
359
364
|
export interface TransitionOptions extends NavOptions {
|
|
360
365
|
progressCallback?: (ani: Animation | undefined) => void;
|
|
361
|
-
baseEl:
|
|
366
|
+
baseEl: HTMLElement;
|
|
362
367
|
enteringEl: HTMLElement;
|
|
363
368
|
leavingEl: HTMLElement | undefined;
|
|
364
369
|
}
|
package/src/utils.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { AnimationPosition } from './sheets-of-glass/interfaces';
|
|
2
2
|
import { IonicConfig } from '@ionic/core';
|
|
3
3
|
|
|
4
|
-
declare const __zone_symbol__requestAnimationFrame:
|
|
5
|
-
declare const requestAnimationFrame:
|
|
4
|
+
declare const __zone_symbol__requestAnimationFrame: ((callback: FrameRequestCallback) => number) | undefined;
|
|
5
|
+
declare const requestAnimationFrame: ((callback: FrameRequestCallback) => number) | undefined;
|
|
6
6
|
|
|
7
7
|
export const getElementRoot = (el: HTMLElement, fallback: HTMLElement = el) => {
|
|
8
8
|
return el.shadowRoot || fallback;
|
|
@@ -58,15 +58,15 @@ export const changeSelectedElement = (
|
|
|
58
58
|
};
|
|
59
59
|
|
|
60
60
|
export class Config {
|
|
61
|
-
private m = new Map<keyof IonicConfig,
|
|
61
|
+
private m = new Map<keyof IonicConfig, unknown>();
|
|
62
62
|
|
|
63
63
|
reset(configObj: IonicConfig) {
|
|
64
|
-
this.m = new Map<keyof IonicConfig,
|
|
64
|
+
this.m = new Map<keyof IonicConfig, unknown>(Object.entries(configObj) as [keyof IonicConfig, unknown][]);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
get(key: keyof IonicConfig, fallback?:
|
|
67
|
+
get<T>(key: keyof IonicConfig, fallback?: T): T {
|
|
68
68
|
const value = this.m.get(key);
|
|
69
|
-
return value !== undefined ? value : fallback;
|
|
69
|
+
return value !== undefined ? (value as T) : (fallback as T);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
getBoolean(key: keyof IonicConfig, fallback = false): boolean {
|
|
@@ -81,11 +81,11 @@ export class Config {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
getNumber(key: keyof IonicConfig, fallback?: number): number {
|
|
84
|
-
const val = parseFloat(this.m.get(key));
|
|
84
|
+
const val = parseFloat(String(this.m.get(key)));
|
|
85
85
|
return isNaN(val) ? (fallback !== undefined ? fallback : NaN) : val;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
set(key: keyof IonicConfig, value:
|
|
88
|
+
set(key: keyof IonicConfig, value: unknown) {
|
|
89
89
|
this.m.set(key, value);
|
|
90
90
|
}
|
|
91
91
|
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { enableVerticalControlArea, setVerticalControlAreaPlacement, IonicNativeUIShell } from './native';
|
|
2
|
+
export { HingeStatus } from './native';
|
|
3
|
+
export type { DeviceLayout, NativeUIShellStatus, NativeUIShellSuspension, VerticalBarEdge, VerticalControlAreaHandle } from './native';
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { PluginListenerHandle } from '@capacitor/core';
|
|
2
|
-
import type { NativeUIShellHandle } from './definitions';
|
|
3
|
-
export declare const bindMetricsLifecycle: (runtime: NativeUIShellHandle, listen: () => Promise<PluginListenerHandle>, deactivate: () => void) => Promise<NativeUIShellHandle>;
|
|
4
|
-
//# sourceMappingURL=lifecycle.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"lifecycle.d.ts","sourceRoot":"","sources":["../../src/native/lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEzD,eAAO,MAAM,oBAAoB,GAC/B,SAAS,mBAAmB,EAC5B,QAAQ,MAAM,OAAO,CAAC,oBAAoB,CAAC,EAC3C,YAAY,MAAM,IAAI,KACrB,OAAO,CAAC,mBAAmB,CA8B7B,CAAC"}
|
package/dist/native/lifecycle.js
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
export const bindMetricsLifecycle = async (runtime, listen, deactivate) => {
|
|
2
|
-
let listener;
|
|
3
|
-
try {
|
|
4
|
-
listener = await listen();
|
|
5
|
-
}
|
|
6
|
-
catch (error) {
|
|
7
|
-
await runtime.destroy();
|
|
8
|
-
throw error;
|
|
9
|
-
}
|
|
10
|
-
let destroying;
|
|
11
|
-
return {
|
|
12
|
-
getStatus: runtime.getStatus,
|
|
13
|
-
suspend: runtime.suspend,
|
|
14
|
-
destroy() {
|
|
15
|
-
return (destroying ?? (destroying = (async () => {
|
|
16
|
-
let removalError;
|
|
17
|
-
try {
|
|
18
|
-
await listener.remove();
|
|
19
|
-
}
|
|
20
|
-
catch (error) {
|
|
21
|
-
removalError = error;
|
|
22
|
-
}
|
|
23
|
-
try {
|
|
24
|
-
await runtime.destroy();
|
|
25
|
-
}
|
|
26
|
-
finally {
|
|
27
|
-
deactivate();
|
|
28
|
-
}
|
|
29
|
-
if (removalError)
|
|
30
|
-
throw removalError;
|
|
31
|
-
})()));
|
|
32
|
-
},
|
|
33
|
-
};
|
|
34
|
-
};
|
package/src/native/lifecycle.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import type { PluginListenerHandle } from '@capacitor/core';
|
|
2
|
-
import type { NativeUIShellHandle } from './definitions';
|
|
3
|
-
|
|
4
|
-
export const bindMetricsLifecycle = async (
|
|
5
|
-
runtime: NativeUIShellHandle,
|
|
6
|
-
listen: () => Promise<PluginListenerHandle>,
|
|
7
|
-
deactivate: () => void,
|
|
8
|
-
): Promise<NativeUIShellHandle> => {
|
|
9
|
-
let listener: PluginListenerHandle;
|
|
10
|
-
try {
|
|
11
|
-
listener = await listen();
|
|
12
|
-
} catch (error) {
|
|
13
|
-
await runtime.destroy();
|
|
14
|
-
throw error;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
let destroying: Promise<void> | undefined;
|
|
18
|
-
return {
|
|
19
|
-
getStatus: runtime.getStatus,
|
|
20
|
-
suspend: runtime.suspend,
|
|
21
|
-
destroy() {
|
|
22
|
-
return (destroying ??= (async () => {
|
|
23
|
-
let removalError: unknown;
|
|
24
|
-
try {
|
|
25
|
-
await listener.remove();
|
|
26
|
-
} catch (error) {
|
|
27
|
-
removalError = error;
|
|
28
|
-
}
|
|
29
|
-
try {
|
|
30
|
-
await runtime.destroy();
|
|
31
|
-
} finally {
|
|
32
|
-
deactivate();
|
|
33
|
-
}
|
|
34
|
-
if (removalError) throw removalError;
|
|
35
|
-
})());
|
|
36
|
-
},
|
|
37
|
-
};
|
|
38
|
-
};
|