@rdlabo/ionic-theme-ios27 1.2.1 → 2.0.0-1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -280
- package/dist/css/components/ion-toolbar.css +1 -1
- package/dist/css/ionic-theme-ios27.css +1 -1
- package/dist/css/vertical-bars.css +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/layout/index.d.ts +18 -0
- package/dist/layout/index.d.ts.map +1 -0
- package/dist/layout/index.js +26 -0
- package/dist/layout/index.js.map +1 -0
- package/dist/segment/index.js +3 -3
- package/dist/segment/index.js.map +1 -1
- package/dist/sheets-of-glass/index.js +7 -7
- package/dist/sheets-of-glass/index.js.map +1 -1
- package/dist/tab-bar-searchable/dismiss-button.js +53 -3
- package/dist/tab-bar-searchable/dismiss-button.js.map +1 -1
- package/dist/tab-bar-searchable/index.js +18 -3
- package/dist/tab-bar-searchable/index.js.map +1 -1
- package/dist/transition/ios.transition.d.ts.map +1 -1
- package/dist/transition/ios.transition.js +7 -4
- package/dist/transition/ios.transition.js.map +1 -1
- package/dist/transition/native-ui.d.ts +6 -0
- package/dist/transition/native-ui.d.ts.map +1 -0
- package/dist/{native-integration/index.js → transition/native-ui.js} +16 -30
- package/dist/transition/native-ui.js.map +1 -0
- package/docs/adaptive-themes.md +161 -0
- package/docs/add-native-ui.md +53 -0
- package/docs/animation.md +19 -3
- package/docs/eslint.md +3 -1
- package/docs/features.md +9 -5
- package/docs/iphone-duo.md +62 -35
- package/docs/migration.md +17 -26
- package/docs/setup.md +92 -0
- package/docs/special-markup.md +41 -10
- package/docs/using-ion-item-group.md +2 -2
- package/docs/vertical-bars-layout.md +67 -0
- package/package.json +12 -42
- package/src/index.ts +3 -1
- package/src/layout/index.ts +29 -0
- package/src/segment/index.ts +3 -3
- package/src/sheets-of-glass/index.ts +7 -7
- package/src/styles/components/ion-toolbar.scss +1 -1
- package/src/styles/vertical-bars.scss +15 -18
- package/src/tab-bar-searchable/dismiss-button.ts +73 -4
- package/src/tab-bar-searchable/index.ts +20 -3
- package/src/transition/ios.transition.ts +7 -4
- package/src/{native-integration/index.ts → transition/native-ui.ts} +17 -46
- package/Package.swift +0 -21
- package/dist/_virtual/_rolldown/runtime.js +0 -13
- package/dist/native/components/index.d.ts +0 -19
- package/dist/native/components/index.d.ts.map +0 -1
- package/dist/native/components/index.js +0 -40
- package/dist/native/components/index.js.map +0 -1
- package/dist/native/components/ion-back-button.d.ts +0 -9
- package/dist/native/components/ion-back-button.d.ts.map +0 -1
- package/dist/native/components/ion-back-button.js +0 -20
- package/dist/native/components/ion-back-button.js.map +0 -1
- package/dist/native/components/ion-button.d.ts +0 -10
- package/dist/native/components/ion-button.d.ts.map +0 -1
- package/dist/native/components/ion-button.js +0 -23
- package/dist/native/components/ion-button.js.map +0 -1
- package/dist/native/components/ion-buttons.d.ts +0 -11
- package/dist/native/components/ion-buttons.d.ts.map +0 -1
- package/dist/native/components/ion-buttons.js +0 -30
- package/dist/native/components/ion-buttons.js.map +0 -1
- package/dist/native/components/ion-fab.d.ts +0 -10
- package/dist/native/components/ion-fab.d.ts.map +0 -1
- package/dist/native/components/ion-fab.js +0 -132
- package/dist/native/components/ion-fab.js.map +0 -1
- package/dist/native/components/ion-menu-button.d.ts +0 -13
- package/dist/native/components/ion-menu-button.d.ts.map +0 -1
- package/dist/native/components/ion-menu-button.js +0 -31
- package/dist/native/components/ion-menu-button.js.map +0 -1
- package/dist/native/components/ion-segment.d.ts +0 -11
- package/dist/native/components/ion-segment.d.ts.map +0 -1
- package/dist/native/components/ion-segment.js +0 -30
- package/dist/native/components/ion-segment.js.map +0 -1
- package/dist/native/components/ion-tab-bar.d.ts +0 -11
- package/dist/native/components/ion-tab-bar.d.ts.map +0 -1
- package/dist/native/components/ion-tab-bar.js +0 -50
- package/dist/native/components/ion-tab-bar.js.map +0 -1
- package/dist/native/components/searchable-tabs.d.ts +0 -20
- package/dist/native/components/searchable-tabs.d.ts.map +0 -1
- package/dist/native/components/searchable-tabs.js +0 -383
- package/dist/native/components/searchable-tabs.js.map +0 -1
- package/dist/native/definitions.d.ts +0 -152
- package/dist/native/definitions.d.ts.map +0 -1
- package/dist/native/definitions.js +0 -0
- package/dist/native/foldable/fold-state.d.ts +0 -5
- package/dist/native/foldable/fold-state.d.ts.map +0 -1
- package/dist/native/foldable/fold-state.js +0 -14
- package/dist/native/foldable/fold-state.js.map +0 -1
- package/dist/native/foldable/types.d.ts +0 -12
- package/dist/native/foldable/types.d.ts.map +0 -1
- package/dist/native/foldable/types.js +0 -0
- package/dist/native/index.d.ts +0 -11
- package/dist/native/index.d.ts.map +0 -1
- package/dist/native/index.js +0 -242
- package/dist/native/index.js.map +0 -1
- package/dist/native/prehide.d.ts +0 -7
- package/dist/native/prehide.d.ts.map +0 -1
- package/dist/native/prehide.js +0 -230
- package/dist/native/prehide.js.map +0 -1
- package/dist/native/runtime.d.ts +0 -5
- package/dist/native/runtime.d.ts.map +0 -1
- package/dist/native/runtime.js +0 -640
- package/dist/native/runtime.js.map +0 -1
- package/dist/native/shared/candidate.d.ts +0 -21
- package/dist/native/shared/candidate.d.ts.map +0 -1
- package/dist/native/shared/candidate.js +0 -98
- package/dist/native/shared/candidate.js.map +0 -1
- package/dist/native/shared/crossfade.d.ts +0 -10
- package/dist/native/shared/crossfade.d.ts.map +0 -1
- package/dist/native/shared/crossfade.js +0 -50
- package/dist/native/shared/crossfade.js.map +0 -1
- package/dist/native/shared/dom.d.ts +0 -46
- package/dist/native/shared/dom.d.ts.map +0 -1
- package/dist/native/shared/dom.js +0 -172
- package/dist/native/shared/dom.js.map +0 -1
- package/dist/native/shared/events.d.ts +0 -11
- package/dist/native/shared/events.d.ts.map +0 -1
- package/dist/native/shared/events.js +0 -13
- package/dist/native/shared/events.js.map +0 -1
- package/dist/native/shared/icons.d.ts +0 -8
- package/dist/native/shared/icons.d.ts.map +0 -1
- package/dist/native/shared/icons.js +0 -61
- package/dist/native/shared/icons.js.map +0 -1
- package/dist/native/shared/modal.d.ts +0 -10
- package/dist/native/shared/modal.d.ts.map +0 -1
- package/dist/native/shared/modal.js +0 -160
- package/dist/native/shared/modal.js.map +0 -1
- package/dist/native/vertical-bars-web-search.d.ts +0 -9
- package/dist/native/vertical-bars-web-search.d.ts.map +0 -1
- package/dist/native/vertical-bars-web-search.js +0 -84
- package/dist/native/vertical-bars-web-search.js.map +0 -1
- package/dist/native/vertical-bars-web.d.ts +0 -5
- package/dist/native/vertical-bars-web.d.ts.map +0 -1
- package/dist/native/vertical-bars-web.js +0 -341
- package/dist/native/vertical-bars-web.js.map +0 -1
- package/dist/native-integration/index.d.ts +0 -23
- package/dist/native-integration/index.d.ts.map +0 -1
- package/dist/native-integration/index.js.map +0 -1
- package/dist/native-integration/transition.d.ts +0 -5
- package/dist/native-integration/transition.d.ts.map +0 -1
- package/dist/native-integration/transition.js +0 -16
- package/dist/native-integration/transition.js.map +0 -1
- package/dist/vertical-bars.d.ts +0 -5
- package/dist/vertical-bars.js +0 -4
- package/docs/e2e-testing.md +0 -51
- package/docs/feedback.md +0 -65
- package/docs/iphone-duo-with-original-theme.md +0 -158
- package/docs/native-ui-shell.md +0 -285
- package/docs/vertical-bars.md +0 -285
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellButton.swift +0 -71
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellButtons.swift +0 -24
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellComponents.swift +0 -18
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellFab.swift +0 -83
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellSearchController.swift +0 -432
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellSegment.swift +0 -91
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellTabBar.swift +0 -221
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellVerticalBars.swift +0 -437
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellVerticalSearch.swift +0 -118
- package/ios/Sources/IonicNativeUIShellPlugin/IonicNativeUIShellPlugin.swift +0 -396
- package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellCrossfade.swift +0 -35
- package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellHost.swift +0 -8
- package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellRendering.swift +0 -27
- package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellSnapshot.swift +0 -135
- package/ios/Tests/IonicNativeUIShellPluginTests/ShellSnapshotTests.swift +0 -650
- package/src/native/components/index.ts +0 -53
- package/src/native/components/ion-back-button.ts +0 -19
- package/src/native/components/ion-button.ts +0 -24
- package/src/native/components/ion-buttons.ts +0 -38
- package/src/native/components/ion-fab.ts +0 -151
- package/src/native/components/ion-menu-button.ts +0 -24
- package/src/native/components/ion-segment.ts +0 -27
- package/src/native/components/ion-tab-bar.ts +0 -50
- package/src/native/components/searchable-tabs.ts +0 -459
- package/src/native/definitions.ts +0 -191
- package/src/native/foldable/fold-state.ts +0 -9
- package/src/native/foldable/types.ts +0 -4
- package/src/native/index.ts +0 -281
- package/src/native/prehide.ts +0 -265
- package/src/native/runtime.ts +0 -781
- package/src/native/shared/candidate.ts +0 -150
- package/src/native/shared/crossfade.ts +0 -50
- package/src/native/shared/dom.ts +0 -286
- package/src/native/shared/events.ts +0 -8
- package/src/native/shared/icons.ts +0 -57
- package/src/native/shared/modal.ts +0 -157
- package/src/native/vertical-bars-web-search.ts +0 -80
- package/src/native/vertical-bars-web.ts +0 -411
- package/src/native-integration/transition.ts +0 -15
- package/src/vertical-bars.ts +0 -11
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import type { Frame } from '../definitions';
|
|
2
|
-
|
|
3
|
-
/** Modal hosts cover the viewport even when their visible dialog is centered. */
|
|
4
|
-
export const modalRailClass = 'ios-theme-vertical-bars-modal';
|
|
5
|
-
const states = new WeakMap<Document, { top?: HTMLIonModalElement }>();
|
|
6
|
-
|
|
7
|
-
export const topModal = (doc: Document): HTMLIonModalElement | undefined => states.get(doc)?.top;
|
|
8
|
-
export const modalUsesVerticalBars = (element: Element): boolean => {
|
|
9
|
-
const modal = element.closest('ion-modal');
|
|
10
|
-
return !modal || modal.classList.contains(modalRailClass);
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
/** Only the foreground modal may contribute controls; never the covered page. */
|
|
14
|
-
export const inVerticalBarsSurface = (element: Element): boolean => {
|
|
15
|
-
const modal = topModal(element.ownerDocument);
|
|
16
|
-
return modal ? element.closest('ion-modal') === modal && modalUsesVerticalBars(element) : !element.closest('ion-modal');
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export const observeVerticalBarsModals = (doc: Document): (() => void) => {
|
|
20
|
-
const win = doc.defaultView!;
|
|
21
|
-
const state: { top?: HTMLIonModalElement } = {};
|
|
22
|
-
states.set(doc, state);
|
|
23
|
-
const presenting = new Set<HTMLIonModalElement>();
|
|
24
|
-
const dismissed = new WeakSet<HTMLIonModalElement>();
|
|
25
|
-
const observed = new Set<Element>();
|
|
26
|
-
const listeners = new AbortController();
|
|
27
|
-
let stopped = false;
|
|
28
|
-
let frame = 0;
|
|
29
|
-
let pointerActive = false;
|
|
30
|
-
let bounds = '';
|
|
31
|
-
const toolbarTop = '--ios-theme-vertical-bars-modal-offset-top';
|
|
32
|
-
const refresh = () => {
|
|
33
|
-
win.cancelAnimationFrame(frame);
|
|
34
|
-
frame = 0;
|
|
35
|
-
if (stopped) return;
|
|
36
|
-
const app = doc.querySelector('ion-app.ios-theme-vertical-bars');
|
|
37
|
-
const modals = Array.from(doc.querySelectorAll<HTMLIonModalElement>('ion-modal'));
|
|
38
|
-
const active = modals.filter(
|
|
39
|
-
(modal) => !dismissed.has(modal) && ((modal as HTMLIonModalElement & { presented?: boolean }).presented || presenting.has(modal)),
|
|
40
|
-
);
|
|
41
|
-
for (const modal of presenting) if (!modal.isConnected) presenting.delete(modal);
|
|
42
|
-
const top = active.sort((a, b) => a.overlayIndex - b.overlayIndex).slice(-1)[0];
|
|
43
|
-
let changed = state.top !== top;
|
|
44
|
-
state.top = top;
|
|
45
|
-
const nextBounds = top ? JSON.stringify(modalVerticalBarFrame(top)) : '';
|
|
46
|
-
changed ||= bounds !== nextBounds;
|
|
47
|
-
bounds = nextBounds;
|
|
48
|
-
const wanted = new Set<Element>();
|
|
49
|
-
for (const modal of modals) {
|
|
50
|
-
const content = modal.shadowRoot?.querySelector<HTMLElement>('[part~="content"]');
|
|
51
|
-
if (content) wanted.add(content);
|
|
52
|
-
// offsetWidth is unaffected by Ionic's presentation transforms.
|
|
53
|
-
const fullWidth = !!content && Math.abs(content.offsetWidth - win.innerWidth) <= 1 && Math.abs(content.offsetLeft) <= 1;
|
|
54
|
-
const enabled = !!app?.contains(modal) && active.includes(modal) && fullWidth;
|
|
55
|
-
if (enabled) {
|
|
56
|
-
const top = modalVerticalBarFrame(modal)?.y ?? 0;
|
|
57
|
-
const offset = `${top}px`;
|
|
58
|
-
if (modal.style.getPropertyValue(toolbarTop) !== offset) {
|
|
59
|
-
modal.style.setProperty(toolbarTop, offset);
|
|
60
|
-
changed = true;
|
|
61
|
-
}
|
|
62
|
-
} else modal.style.removeProperty(toolbarTop);
|
|
63
|
-
if (modal.classList.contains(modalRailClass) !== enabled) {
|
|
64
|
-
modal.classList.toggle(modalRailClass, enabled);
|
|
65
|
-
changed = true;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
if (wanted.size !== observed.size || [...wanted].some((element) => !observed.has(element))) {
|
|
69
|
-
resize?.disconnect();
|
|
70
|
-
observer.disconnect();
|
|
71
|
-
observer.observe(doc.documentElement, observation);
|
|
72
|
-
observed.clear();
|
|
73
|
-
for (const element of wanted) {
|
|
74
|
-
resize?.observe(element);
|
|
75
|
-
observer.observe(element, { attributes: true, attributeFilter: ['style', 'class'] });
|
|
76
|
-
observed.add(element);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
if (changed) win.dispatchEvent(new Event('nativeUIShellRefresh'));
|
|
80
|
-
if (
|
|
81
|
-
top &&
|
|
82
|
-
(pointerActive ||
|
|
83
|
-
Array.from(wanted).some((element) => element.getAnimations().some((animation) => animation.playState === 'running')))
|
|
84
|
-
)
|
|
85
|
-
schedule();
|
|
86
|
-
};
|
|
87
|
-
const schedule = () => {
|
|
88
|
-
if (!stopped && !frame) frame = win.requestAnimationFrame(refresh);
|
|
89
|
-
};
|
|
90
|
-
const resize = win.ResizeObserver ? new win.ResizeObserver(schedule) : undefined;
|
|
91
|
-
const observer = new MutationObserver(schedule);
|
|
92
|
-
const observation = { subtree: true, childList: true, attributes: true, attributeFilter: ['class', 'style'] };
|
|
93
|
-
observer.observe(doc.documentElement, observation);
|
|
94
|
-
for (const name of ['ionModalWillPresent', 'ionModalDidPresent', 'ionModalWillDismiss', 'ionModalDidDismiss', 'ionBreakpointDidChange']) {
|
|
95
|
-
doc.addEventListener(
|
|
96
|
-
name,
|
|
97
|
-
(event) => {
|
|
98
|
-
const modal = event.target as HTMLIonModalElement;
|
|
99
|
-
if (modal.localName !== 'ion-modal') return;
|
|
100
|
-
if (name === 'ionModalDidDismiss') {
|
|
101
|
-
presenting.delete(modal);
|
|
102
|
-
dismissed.add(modal);
|
|
103
|
-
} else {
|
|
104
|
-
presenting.add(modal);
|
|
105
|
-
dismissed.delete(modal);
|
|
106
|
-
}
|
|
107
|
-
refresh();
|
|
108
|
-
},
|
|
109
|
-
{ capture: true, signal: listeners.signal },
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
doc.addEventListener(
|
|
113
|
-
'pointerdown',
|
|
114
|
-
(event) => {
|
|
115
|
-
if ((event.target as Element).closest('ion-modal')) {
|
|
116
|
-
pointerActive = true;
|
|
117
|
-
schedule();
|
|
118
|
-
}
|
|
119
|
-
},
|
|
120
|
-
{ capture: true, signal: listeners.signal },
|
|
121
|
-
);
|
|
122
|
-
for (const name of ['pointerup', 'pointercancel'])
|
|
123
|
-
doc.addEventListener(
|
|
124
|
-
name,
|
|
125
|
-
() => {
|
|
126
|
-
pointerActive = false;
|
|
127
|
-
schedule();
|
|
128
|
-
},
|
|
129
|
-
{ capture: true, signal: listeners.signal },
|
|
130
|
-
);
|
|
131
|
-
win.addEventListener('resize', schedule, { signal: listeners.signal });
|
|
132
|
-
win.addEventListener('nativeUIShellRefresh', schedule, { signal: listeners.signal });
|
|
133
|
-
refresh();
|
|
134
|
-
return () => {
|
|
135
|
-
stopped = true;
|
|
136
|
-
win.cancelAnimationFrame(frame);
|
|
137
|
-
observer.disconnect();
|
|
138
|
-
resize?.disconnect();
|
|
139
|
-
listeners.abort();
|
|
140
|
-
if (states.get(doc) !== state) return;
|
|
141
|
-
states.delete(doc);
|
|
142
|
-
doc.querySelectorAll<HTMLElement>(`.${modalRailClass}`).forEach((modal) => {
|
|
143
|
-
modal.classList.remove(modalRailClass);
|
|
144
|
-
modal.style.removeProperty(toolbarTop);
|
|
145
|
-
});
|
|
146
|
-
};
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
/** Visible modal bounds, clipped at the viewport for Ionic's translated sheets. */
|
|
150
|
-
export const modalVerticalBarFrame = (modal: Element): Frame | undefined => {
|
|
151
|
-
const rect = modal.shadowRoot?.querySelector('[part~="content"]')?.getBoundingClientRect();
|
|
152
|
-
const win = modal.ownerDocument.defaultView!;
|
|
153
|
-
if (!rect) return;
|
|
154
|
-
const y = Math.max(0, rect.top);
|
|
155
|
-
const height = Math.min(win.innerHeight, rect.bottom) - y;
|
|
156
|
-
return height > 0 ? { x: 0, y, width: win.innerWidth, height } : undefined;
|
|
157
|
-
};
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import { getNativeSearchBindings, type NativeSearchBinding } from '../native-integration';
|
|
2
|
-
import { activateProjectedElement, marker, unprojected } from './shared/dom';
|
|
3
|
-
|
|
4
|
-
export const searchProjectionClass = 'ios-theme-vertical-bars-search-projection';
|
|
5
|
-
|
|
6
|
-
/** Web counterpart of the system search button above the vertical tabs, or at the rail bottom without visible tabs. */
|
|
7
|
-
export const createVerticalBarsWebSearch = (doc: Document, eligible: (element: HTMLElement) => boolean, refresh: () => void) => {
|
|
8
|
-
let binding: NativeSearchBinding | undefined;
|
|
9
|
-
let projection: HTMLIonFabButtonElement | undefined;
|
|
10
|
-
let container: HTMLIonFabElement | undefined;
|
|
11
|
-
const iconObserver = new MutationObserver(refresh);
|
|
12
|
-
let previousAriaHidden: string | null = null;
|
|
13
|
-
const restore = () => {
|
|
14
|
-
iconObserver.disconnect();
|
|
15
|
-
container?.remove();
|
|
16
|
-
container = undefined;
|
|
17
|
-
projection = undefined;
|
|
18
|
-
if (binding) {
|
|
19
|
-
binding.trigger.removeAttribute(marker);
|
|
20
|
-
if (previousAriaHidden === null) binding.trigger.removeAttribute('aria-hidden');
|
|
21
|
-
else binding.trigger.setAttribute('aria-hidden', previousAriaHidden);
|
|
22
|
-
}
|
|
23
|
-
binding = undefined;
|
|
24
|
-
};
|
|
25
|
-
return {
|
|
26
|
-
restore,
|
|
27
|
-
get source() {
|
|
28
|
-
return binding?.trigger;
|
|
29
|
-
},
|
|
30
|
-
update(root: HTMLElement) {
|
|
31
|
-
const next = unprojected(binding ? [binding.trigger] : [], () =>
|
|
32
|
-
getNativeSearchBindings(doc).find(({ trigger, footer }) => eligible(trigger) && eligible(footer)),
|
|
33
|
-
);
|
|
34
|
-
if (next !== binding || container?.parentElement !== root) restore();
|
|
35
|
-
if (!next) return;
|
|
36
|
-
if (!projection) {
|
|
37
|
-
binding = next;
|
|
38
|
-
previousAriaHidden = next.trigger.getAttribute('aria-hidden');
|
|
39
|
-
projection = next.trigger.cloneNode(true) as HTMLIonFabButtonElement;
|
|
40
|
-
for (const name of ['id', 'slot', marker, 'aria-hidden', 'style']) projection.removeAttribute(name);
|
|
41
|
-
container = doc.createElement('ion-fab');
|
|
42
|
-
container.setAttribute('mode', (next.trigger as HTMLIonFabButtonElement).mode ?? 'ios');
|
|
43
|
-
container.classList.add(searchProjectionClass, 'ion-cloned-element');
|
|
44
|
-
projection.addEventListener(
|
|
45
|
-
'click',
|
|
46
|
-
(event) => {
|
|
47
|
-
event.preventDefault();
|
|
48
|
-
event.stopImmediatePropagation();
|
|
49
|
-
activateProjectedElement(next.trigger);
|
|
50
|
-
},
|
|
51
|
-
{ capture: true },
|
|
52
|
-
);
|
|
53
|
-
container.append(projection);
|
|
54
|
-
root.append(container);
|
|
55
|
-
next.trigger.setAttribute(marker, '');
|
|
56
|
-
next.trigger.setAttribute('aria-hidden', 'true');
|
|
57
|
-
}
|
|
58
|
-
projection.setAttribute(
|
|
59
|
-
'aria-label',
|
|
60
|
-
next.trigger.getAttribute('aria-label') ?? next.footer.querySelector('ion-searchbar')?.getAttribute('aria-label') ?? 'Search',
|
|
61
|
-
);
|
|
62
|
-
const sourceIcon = next.trigger.querySelector('ion-icon');
|
|
63
|
-
const icon = projection.querySelector('ion-icon');
|
|
64
|
-
if (sourceIcon?.shadowRoot) iconObserver.observe(sourceIcon.shadowRoot, { subtree: true, childList: true, attributes: true });
|
|
65
|
-
if (sourceIcon && icon) {
|
|
66
|
-
icon.icon = sourceIcon.icon;
|
|
67
|
-
icon.name = sourceIcon.name;
|
|
68
|
-
icon.src = sourceIcon.src;
|
|
69
|
-
}
|
|
70
|
-
container!.hidden = next.trigger.style.pointerEvents === 'none';
|
|
71
|
-
projection.disabled = (next.trigger as HTMLIonFabButtonElement).disabled;
|
|
72
|
-
const bounds = next.tabBar.getBoundingClientRect();
|
|
73
|
-
const tabs = bounds.width > 0 && bounds.height > 0 && getComputedStyle(next.tabBar).visibility === 'visible' ? bounds : undefined;
|
|
74
|
-
container!.style.left = tabs ? `${tabs.left + (tabs.width - 46) / 2}px` : '';
|
|
75
|
-
container!.style.top = tabs ? `${tabs.top - 56}px` : '';
|
|
76
|
-
container!.style.right = tabs ? 'auto' : '';
|
|
77
|
-
container!.style.bottom = tabs ? 'auto' : '';
|
|
78
|
-
},
|
|
79
|
-
};
|
|
80
|
-
};
|
|
@@ -1,411 +0,0 @@
|
|
|
1
|
-
import { createVerticalBarsWebSearch, searchProjectionClass } from './vertical-bars-web-search';
|
|
2
|
-
import { inVerticalBarsSurface, topModal } from './shared/modal';
|
|
3
|
-
import type { NativeUIShellHandle, NativeUIShellOptions, NativeUIShellStatus } from './definitions';
|
|
4
|
-
import { VERTICAL_BARS_TRANSITION_CANCELED } from '../native-integration';
|
|
5
|
-
import {
|
|
6
|
-
activateProjectedElement,
|
|
7
|
-
createVerticalBarsPageState,
|
|
8
|
-
verticalBarsActionCandidate,
|
|
9
|
-
verticalBarsBackCandidate,
|
|
10
|
-
verticalBarsEnteringPage,
|
|
11
|
-
verticalBarsToolbarActions,
|
|
12
|
-
isExcluded,
|
|
13
|
-
isShellDisabled,
|
|
14
|
-
isVerticalBarsToolbarGroup,
|
|
15
|
-
preferredVerticalBarsBack,
|
|
16
|
-
verticalBarsOwned,
|
|
17
|
-
marker,
|
|
18
|
-
syncToolbarText,
|
|
19
|
-
toolbarTextMarker,
|
|
20
|
-
prehideOnlyMutation,
|
|
21
|
-
prehiddenClass,
|
|
22
|
-
unprojected,
|
|
23
|
-
withoutPrehide,
|
|
24
|
-
} from './shared/dom';
|
|
25
|
-
|
|
26
|
-
const backProjectionClass = 'ios-theme-vertical-bars-back-button-projection';
|
|
27
|
-
const toolbarProjectionClass = 'ios-theme-vertical-bars-toolbar-projection';
|
|
28
|
-
const readyClass = 'ios-theme-vertical-bars-toolbar-ready';
|
|
29
|
-
const toolbarControlSize = 46;
|
|
30
|
-
const toolbarControlGap = 10;
|
|
31
|
-
|
|
32
|
-
interface ToolbarProjection {
|
|
33
|
-
source: HTMLIonButtonsElement;
|
|
34
|
-
projection: HTMLElement;
|
|
35
|
-
actions: { source: HTMLElement; projection: HTMLElement; previousAriaHidden: string | null }[];
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
interface ToolbarSource {
|
|
39
|
-
group: HTMLIonButtonsElement;
|
|
40
|
-
actions: HTMLElement[];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export const createVerticalBarsWebProjection = (
|
|
44
|
-
doc: Document,
|
|
45
|
-
options: NativeUIShellOptions,
|
|
46
|
-
enabled: () => boolean = () => true,
|
|
47
|
-
): NativeUIShellHandle => {
|
|
48
|
-
const win = doc.defaultView!;
|
|
49
|
-
const toolbarEnabled = options.controls === undefined || options.controls.toolbar === true;
|
|
50
|
-
let root: HTMLElement | undefined;
|
|
51
|
-
let backSource: HTMLIonBackButtonElement | undefined;
|
|
52
|
-
let backProjection: HTMLIonBackButtonElement | undefined;
|
|
53
|
-
let previousBackAriaHidden: string | null = null;
|
|
54
|
-
let toolbarProjections: ToolbarProjection[] = [];
|
|
55
|
-
let suspended = 0;
|
|
56
|
-
let stopped = false;
|
|
57
|
-
let frame = 0;
|
|
58
|
-
let updates = 0;
|
|
59
|
-
let observingVerticalBars = false;
|
|
60
|
-
const verticalBarsPages = createVerticalBarsPageState();
|
|
61
|
-
let sourceObserver: MutationObserver | undefined;
|
|
62
|
-
let waiters: (() => void)[] = [];
|
|
63
|
-
const listeners = new AbortController();
|
|
64
|
-
const verticalBarsRoot = () => doc.querySelector<HTMLElement>('ion-app.ios-theme-vertical-bars');
|
|
65
|
-
const projectedSources = () =>
|
|
66
|
-
[backSource, search.source, ...toolbarProjections.flatMap(({ actions }) => actions.map(({ source }) => source))].filter(
|
|
67
|
-
(source): source is HTMLElement => !!source,
|
|
68
|
-
);
|
|
69
|
-
const isRendered = (element: HTMLElement) =>
|
|
70
|
-
withoutPrehide(element, () => {
|
|
71
|
-
const style = getComputedStyle(element);
|
|
72
|
-
const rect = element.getBoundingClientRect();
|
|
73
|
-
return element.isConnected && style.display !== 'none' && style.visibility === 'visible' && rect.width > 0 && rect.height > 0;
|
|
74
|
-
});
|
|
75
|
-
const search = createVerticalBarsWebSearch(
|
|
76
|
-
doc,
|
|
77
|
-
(element) =>
|
|
78
|
-
(options.controls === undefined || options.controls.tabs === true) &&
|
|
79
|
-
!!verticalBarsRoot()?.contains(element) &&
|
|
80
|
-
inVerticalBarsSurface(element) &&
|
|
81
|
-
!isExcluded(element, verticalBarsEnteringPage(element)) &&
|
|
82
|
-
!isShellDisabled(element) &&
|
|
83
|
-
!verticalBarsPages.isDeparted(element) &&
|
|
84
|
-
isRendered(element),
|
|
85
|
-
() => {
|
|
86
|
-
void schedule();
|
|
87
|
-
},
|
|
88
|
-
);
|
|
89
|
-
const inEligibleToolbar = (element: HTMLElement) =>
|
|
90
|
-
!!verticalBarsRoot()?.contains(element) &&
|
|
91
|
-
inVerticalBarsSurface(element) &&
|
|
92
|
-
verticalBarsActionCandidate(element) &&
|
|
93
|
-
!isExcluded(element, verticalBarsEnteringPage(element)) &&
|
|
94
|
-
!verticalBarsPages.isDeparted(element);
|
|
95
|
-
const isEligibleBack = (element: HTMLIonBackButtonElement) =>
|
|
96
|
-
!!verticalBarsRoot()?.contains(element) &&
|
|
97
|
-
inVerticalBarsSurface(element) &&
|
|
98
|
-
verticalBarsOwned(element) &&
|
|
99
|
-
verticalBarsBackCandidate(element) &&
|
|
100
|
-
!isExcluded(element, verticalBarsEnteringPage(element)) &&
|
|
101
|
-
!verticalBarsPages.isDeparted(element) &&
|
|
102
|
-
unprojected(projectedSources(), () => isRendered(element));
|
|
103
|
-
const pageOrder = (element: Element) => Array.from(doc.querySelectorAll('.ion-page')).indexOf(element.closest('.ion-page')!);
|
|
104
|
-
const findBack = () => {
|
|
105
|
-
const candidates = Array.from(doc.querySelectorAll<HTMLIonBackButtonElement>(`ion-back-button:not(.${backProjectionClass})`)).filter(
|
|
106
|
-
isEligibleBack,
|
|
107
|
-
);
|
|
108
|
-
return preferredVerticalBarsBack(candidates, doc);
|
|
109
|
-
};
|
|
110
|
-
const findToolbarGroups = () => {
|
|
111
|
-
const candidates = Array.from(doc.querySelectorAll<HTMLIonButtonsElement>(`ion-buttons:not(.${toolbarProjectionClass})`)).flatMap(
|
|
112
|
-
(group): ToolbarSource[] => {
|
|
113
|
-
const actions = verticalBarsToolbarActions(group).filter((action) => unprojected(projectedSources(), () => isRendered(action)));
|
|
114
|
-
if (!actions.length) return [];
|
|
115
|
-
if (!isVerticalBarsToolbarGroup(group)) return actions.filter(inEligibleToolbar).map((action) => ({ group, actions: [action] }));
|
|
116
|
-
return inEligibleToolbar(group) && unprojected(projectedSources(), () => isRendered(group)) ? [{ group, actions }] : [];
|
|
117
|
-
},
|
|
118
|
-
);
|
|
119
|
-
const highestPage = Math.max(...candidates.map(({ group }) => pageOrder(group)));
|
|
120
|
-
return candidates.filter(({ group }) => pageOrder(group) === highestPage);
|
|
121
|
-
};
|
|
122
|
-
const isCurrentToolbarAction = (source: HTMLElement) => findToolbarGroups().some(({ actions }) => actions.includes(source));
|
|
123
|
-
const restore = () => {
|
|
124
|
-
search.restore();
|
|
125
|
-
backProjection?.remove();
|
|
126
|
-
backProjection = undefined;
|
|
127
|
-
if (backSource) {
|
|
128
|
-
backSource.removeAttribute(marker);
|
|
129
|
-
if (previousBackAriaHidden === null) backSource.removeAttribute('aria-hidden');
|
|
130
|
-
else backSource.setAttribute('aria-hidden', previousBackAriaHidden);
|
|
131
|
-
backSource.dispatchEvent(new CustomEvent('nativeUIShellChange'));
|
|
132
|
-
}
|
|
133
|
-
backSource = undefined;
|
|
134
|
-
previousBackAriaHidden = null;
|
|
135
|
-
for (const { projection, actions } of toolbarProjections) {
|
|
136
|
-
projection.remove();
|
|
137
|
-
for (const { source, previousAriaHidden } of actions) {
|
|
138
|
-
source.removeAttribute(marker);
|
|
139
|
-
if (previousAriaHidden === null) source.removeAttribute('aria-hidden');
|
|
140
|
-
else source.setAttribute('aria-hidden', previousAriaHidden);
|
|
141
|
-
source.dispatchEvent(new CustomEvent('nativeUIShellChange'));
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
toolbarProjections = [];
|
|
145
|
-
sourceObserver?.disconnect();
|
|
146
|
-
sourceObserver = undefined;
|
|
147
|
-
root?.classList.remove(readyClass);
|
|
148
|
-
root = undefined;
|
|
149
|
-
};
|
|
150
|
-
const copyAttributes = (target: HTMLElement, original: HTMLElement) => {
|
|
151
|
-
for (const name of ['id', 'slot', marker, 'aria-hidden']) target.removeAttribute(name);
|
|
152
|
-
for (const attribute of Array.from(target.attributes))
|
|
153
|
-
if (!['class', marker].includes(attribute.name) && !original.hasAttribute(attribute.name)) target.removeAttribute(attribute.name);
|
|
154
|
-
for (const attribute of Array.from(original.attributes))
|
|
155
|
-
if (!['id', 'slot', marker, 'aria-hidden'].includes(attribute.name)) target.setAttribute(attribute.name, attribute.value);
|
|
156
|
-
target.classList.remove(prehiddenClass);
|
|
157
|
-
};
|
|
158
|
-
const syncBack = (target: HTMLIonBackButtonElement, original: HTMLIonBackButtonElement) => {
|
|
159
|
-
copyAttributes(target, original);
|
|
160
|
-
target.classList.add(backProjectionClass, 'ion-cloned-element');
|
|
161
|
-
target.disabled = original.disabled;
|
|
162
|
-
target.defaultHref = original.defaultHref;
|
|
163
|
-
target.icon = original.icon;
|
|
164
|
-
target.color = original.color;
|
|
165
|
-
target.mode = original.mode;
|
|
166
|
-
target.text = '';
|
|
167
|
-
};
|
|
168
|
-
const syncAction = (target: HTMLElement, original: HTMLElement) => {
|
|
169
|
-
copyAttributes(target, original);
|
|
170
|
-
target.classList.add('ios-theme-vertical-bars-toolbar-action', 'ion-cloned-element');
|
|
171
|
-
const label =
|
|
172
|
-
original.getAttribute('aria-label') ??
|
|
173
|
-
original.shadowRoot?.querySelector('[part~=native]')?.getAttribute('aria-label') ??
|
|
174
|
-
original.textContent?.trim();
|
|
175
|
-
if (label) target.setAttribute('aria-label', label);
|
|
176
|
-
if ('disabled' in original) (target as HTMLIonButtonElement).disabled = (original as HTMLIonButtonElement).disabled;
|
|
177
|
-
if (original.matches('ion-button')) {
|
|
178
|
-
const icon = original.querySelector<HTMLElement>('ion-icon, svg');
|
|
179
|
-
const clonedIcon = icon?.cloneNode(true) as HTMLElement | undefined;
|
|
180
|
-
clonedIcon?.setAttribute('slot', 'icon-only');
|
|
181
|
-
target.replaceChildren(...(clonedIcon ? [clonedIcon] : []));
|
|
182
|
-
}
|
|
183
|
-
};
|
|
184
|
-
const sameSources = (back: HTMLIonBackButtonElement | undefined, groups: ToolbarSource[]) =>
|
|
185
|
-
back === backSource &&
|
|
186
|
-
groups.length === toolbarProjections.length &&
|
|
187
|
-
groups.every(({ group, actions }, index) => {
|
|
188
|
-
const current = toolbarProjections[index];
|
|
189
|
-
return (
|
|
190
|
-
group === current.source &&
|
|
191
|
-
actions.length === current.actions.length &&
|
|
192
|
-
actions.every((action, actionIndex) => action === current.actions[actionIndex].source)
|
|
193
|
-
);
|
|
194
|
-
});
|
|
195
|
-
const syncExisting = () => {
|
|
196
|
-
if (backSource && backProjection) syncBack(backProjection, backSource);
|
|
197
|
-
let topOffset = backSource ? toolbarControlSize + toolbarControlGap : 0;
|
|
198
|
-
for (const { projection, source, actions } of toolbarProjections) {
|
|
199
|
-
if (actions.length === 1 && !projection.matches('ion-buttons')) syncAction(projection, actions[0].source);
|
|
200
|
-
else copyAttributes(projection, source);
|
|
201
|
-
projection.classList.add(toolbarProjectionClass, 'ion-cloned-element');
|
|
202
|
-
projection.style.setProperty('--ios-theme-vertical-bars-toolbar-offset', `${topOffset}px`);
|
|
203
|
-
if (projection.matches('ion-buttons')) actions.forEach(({ source: action, projection: clone }) => syncAction(clone, action));
|
|
204
|
-
topOffset += actions.length * toolbarControlSize + toolbarControlGap;
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
const project = (nextBack: HTMLIonBackButtonElement | undefined, groups: ToolbarSource[]) => {
|
|
208
|
-
root = topModal(doc) ?? verticalBarsRoot()!;
|
|
209
|
-
let topOffset = 0;
|
|
210
|
-
if (nextBack) {
|
|
211
|
-
backSource = nextBack;
|
|
212
|
-
previousBackAriaHidden = nextBack.getAttribute('aria-hidden');
|
|
213
|
-
backProjection = nextBack.cloneNode(false) as HTMLIonBackButtonElement;
|
|
214
|
-
syncBack(backProjection, nextBack);
|
|
215
|
-
backProjection.addEventListener(
|
|
216
|
-
'click',
|
|
217
|
-
(event) => {
|
|
218
|
-
event.preventDefault();
|
|
219
|
-
event.stopImmediatePropagation();
|
|
220
|
-
if (backSource && backSource === findBack()) activateProjectedElement(backSource);
|
|
221
|
-
},
|
|
222
|
-
{ capture: true },
|
|
223
|
-
);
|
|
224
|
-
root.append(backProjection);
|
|
225
|
-
nextBack.setAttribute(marker, '');
|
|
226
|
-
nextBack.setAttribute('aria-hidden', 'true');
|
|
227
|
-
nextBack.dispatchEvent(new CustomEvent('nativeUIShellChange'));
|
|
228
|
-
topOffset = toolbarControlSize + toolbarControlGap;
|
|
229
|
-
}
|
|
230
|
-
for (const { group, actions: sources } of groups) {
|
|
231
|
-
const projection = (sources.length === 1 ? sources[0] : group).cloneNode(false) as HTMLElement;
|
|
232
|
-
if (sources.length === 1) syncAction(projection, sources[0]);
|
|
233
|
-
else copyAttributes(projection, group);
|
|
234
|
-
projection.classList.add(toolbarProjectionClass, 'ion-cloned-element');
|
|
235
|
-
projection.style.setProperty('--ios-theme-vertical-bars-toolbar-offset', `${topOffset}px`);
|
|
236
|
-
const actions = sources.map((source) => {
|
|
237
|
-
const clone = sources.length === 1 ? projection : (source.cloneNode(false) as HTMLElement);
|
|
238
|
-
if (sources.length > 1) syncAction(clone, source);
|
|
239
|
-
clone.addEventListener(
|
|
240
|
-
'click',
|
|
241
|
-
(event) => {
|
|
242
|
-
event.preventDefault();
|
|
243
|
-
event.stopImmediatePropagation();
|
|
244
|
-
if (isCurrentToolbarAction(source)) activateProjectedElement(source);
|
|
245
|
-
},
|
|
246
|
-
{ capture: true },
|
|
247
|
-
);
|
|
248
|
-
if (sources.length > 1) projection.append(clone);
|
|
249
|
-
const previousAriaHidden = source.getAttribute('aria-hidden');
|
|
250
|
-
source.setAttribute(marker, '');
|
|
251
|
-
source.setAttribute('aria-hidden', 'true');
|
|
252
|
-
source.dispatchEvent(new CustomEvent('nativeUIShellChange'));
|
|
253
|
-
return { source, projection: clone, previousAriaHidden };
|
|
254
|
-
});
|
|
255
|
-
root.append(projection);
|
|
256
|
-
toolbarProjections.push({ source: group, projection, actions });
|
|
257
|
-
topOffset += actions.length * toolbarControlSize + toolbarControlGap;
|
|
258
|
-
}
|
|
259
|
-
if (nextBack || toolbarProjections.length) root.classList.add(readyClass);
|
|
260
|
-
sourceObserver = new MutationObserver((records) => {
|
|
261
|
-
if (
|
|
262
|
-
records.some(
|
|
263
|
-
(record) =>
|
|
264
|
-
record.type !== 'attributes' || (![marker, 'aria-hidden'].includes(record.attributeName ?? '') && !prehideOnlyMutation(record)),
|
|
265
|
-
)
|
|
266
|
-
)
|
|
267
|
-
schedule();
|
|
268
|
-
});
|
|
269
|
-
if (nextBack?.shadowRoot)
|
|
270
|
-
sourceObserver.observe(nextBack.shadowRoot, { subtree: true, childList: true, attributes: true, attributeOldValue: true });
|
|
271
|
-
for (const { group } of groups)
|
|
272
|
-
sourceObserver.observe(group, { subtree: true, childList: true, attributes: true, attributeOldValue: true });
|
|
273
|
-
};
|
|
274
|
-
const performUpdate = () => {
|
|
275
|
-
frame = 0;
|
|
276
|
-
syncToolbarText(doc);
|
|
277
|
-
const currentRoot = verticalBarsRoot();
|
|
278
|
-
if (observingVerticalBars !== !!currentRoot) {
|
|
279
|
-
observingVerticalBars = !!currentRoot;
|
|
280
|
-
observer.disconnect();
|
|
281
|
-
observer.observe(doc.documentElement, {
|
|
282
|
-
subtree: true,
|
|
283
|
-
childList: true,
|
|
284
|
-
characterData: observingVerticalBars,
|
|
285
|
-
attributes: true,
|
|
286
|
-
attributeOldValue: true,
|
|
287
|
-
attributeFilter: observingVerticalBars ? undefined : ['class'],
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
if (stopped || suspended || !currentRoot || !enabled()) return restore();
|
|
291
|
-
const nextBack = toolbarEnabled ? findBack() : undefined;
|
|
292
|
-
const groups = toolbarEnabled ? findToolbarGroups() : [];
|
|
293
|
-
const projectionRoot = topModal(doc) ?? currentRoot;
|
|
294
|
-
search.update(projectionRoot);
|
|
295
|
-
if (!nextBack && !groups.length && !search.source) return restore();
|
|
296
|
-
if (root === (topModal(doc) ?? currentRoot) && sameSources(nextBack, groups)) return syncExisting();
|
|
297
|
-
restore();
|
|
298
|
-
project(nextBack, groups);
|
|
299
|
-
search.update(projectionRoot);
|
|
300
|
-
updates++;
|
|
301
|
-
};
|
|
302
|
-
const update = () => {
|
|
303
|
-
try {
|
|
304
|
-
performUpdate();
|
|
305
|
-
} catch {
|
|
306
|
-
restore();
|
|
307
|
-
} finally {
|
|
308
|
-
const pending = waiters;
|
|
309
|
-
waiters = [];
|
|
310
|
-
pending.forEach((resolve) => resolve());
|
|
311
|
-
}
|
|
312
|
-
};
|
|
313
|
-
const schedule = (): Promise<void> => {
|
|
314
|
-
if (stopped) return Promise.resolve();
|
|
315
|
-
// Release Web ownership before the native runtime can acknowledge its next frame.
|
|
316
|
-
if (!enabled()) restore();
|
|
317
|
-
const done = new Promise<void>((resolve) => waiters.push(resolve));
|
|
318
|
-
if (!frame) frame = win.requestAnimationFrame(update);
|
|
319
|
-
return done;
|
|
320
|
-
};
|
|
321
|
-
const observer = new MutationObserver((records) => {
|
|
322
|
-
const insideProjection = (target: Node) => {
|
|
323
|
-
const element = target instanceof Element ? target : target.parentNode instanceof Element ? target.parentNode : undefined;
|
|
324
|
-
const rootNode = target.getRootNode();
|
|
325
|
-
const shadowHost = rootNode instanceof ShadowRoot ? rootNode.host : undefined;
|
|
326
|
-
return !![element, shadowHost].some((candidate) =>
|
|
327
|
-
candidate?.closest(`.${backProjectionClass}, .${toolbarProjectionClass}, .${searchProjectionClass}`),
|
|
328
|
-
);
|
|
329
|
-
};
|
|
330
|
-
const verticalBarsChanged = records.some(
|
|
331
|
-
(record) =>
|
|
332
|
-
(record.type === 'attributes' && (record.target as Element).matches('ion-app')) ||
|
|
333
|
-
(record.type === 'childList' &&
|
|
334
|
-
Array.from(record.addedNodes).some(
|
|
335
|
-
(node) =>
|
|
336
|
-
node instanceof Element &&
|
|
337
|
-
(node.matches('ion-app.ios-theme-vertical-bars') || !!node.querySelector('ion-app.ios-theme-vertical-bars')),
|
|
338
|
-
)),
|
|
339
|
-
);
|
|
340
|
-
if (
|
|
341
|
-
(observingVerticalBars &&
|
|
342
|
-
records.some(
|
|
343
|
-
(record) =>
|
|
344
|
-
record.attributeName !== marker &&
|
|
345
|
-
record.attributeName !== toolbarTextMarker &&
|
|
346
|
-
!prehideOnlyMutation(record) &&
|
|
347
|
-
!insideProjection(record.target),
|
|
348
|
-
)) ||
|
|
349
|
-
(!observingVerticalBars && verticalBarsChanged && records.some((record) => !prehideOnlyMutation(record)))
|
|
350
|
-
)
|
|
351
|
-
schedule();
|
|
352
|
-
});
|
|
353
|
-
observer.observe(doc.documentElement, {
|
|
354
|
-
subtree: true,
|
|
355
|
-
childList: true,
|
|
356
|
-
attributes: true,
|
|
357
|
-
attributeOldValue: true,
|
|
358
|
-
attributeFilter: ['class'],
|
|
359
|
-
});
|
|
360
|
-
const pageLifecycle = (event: Event) => {
|
|
361
|
-
const page = event.target;
|
|
362
|
-
if (!(page instanceof HTMLElement) || !page.matches('.ion-page')) return;
|
|
363
|
-
if (event.type === VERTICAL_BARS_TRANSITION_CANCELED) {
|
|
364
|
-
const entering = (event as CustomEvent<{ entering?: HTMLElement }>).detail?.entering;
|
|
365
|
-
verticalBarsPages.cancel(entering, page);
|
|
366
|
-
} else verticalBarsPages.lifecycle(event);
|
|
367
|
-
if (verticalBarsRoot()) schedule();
|
|
368
|
-
};
|
|
369
|
-
for (const name of ['ionViewWillEnter', 'ionViewWillLeave', 'ionViewDidEnter', 'ionViewDidLeave'])
|
|
370
|
-
doc.addEventListener(name, pageLifecycle, { capture: true, signal: listeners.signal });
|
|
371
|
-
doc.addEventListener(VERTICAL_BARS_TRANSITION_CANCELED, pageLifecycle, { capture: true, signal: listeners.signal });
|
|
372
|
-
for (const name of ['ionModalWillPresent', 'ionModalDidDismiss'])
|
|
373
|
-
doc.addEventListener(name, schedule, { capture: true, signal: listeners.signal });
|
|
374
|
-
win.addEventListener('resize', schedule, { signal: listeners.signal });
|
|
375
|
-
win.addEventListener('nativeUIShellRefresh', schedule, { signal: listeners.signal });
|
|
376
|
-
schedule();
|
|
377
|
-
|
|
378
|
-
return {
|
|
379
|
-
getStatus: (): NativeUIShellStatus => ({
|
|
380
|
-
state: stopped ? 'stopped' : 'web',
|
|
381
|
-
projected:
|
|
382
|
-
Number(!!backSource) + Number(!!search.source) + toolbarProjections.reduce((count, group) => count + group.actions.length, 0),
|
|
383
|
-
updates,
|
|
384
|
-
}),
|
|
385
|
-
async suspend() {
|
|
386
|
-
suspended++;
|
|
387
|
-
restore();
|
|
388
|
-
let resumed = false;
|
|
389
|
-
return {
|
|
390
|
-
async resume() {
|
|
391
|
-
if (resumed) return;
|
|
392
|
-
resumed = true;
|
|
393
|
-
suspended = Math.max(0, suspended - 1);
|
|
394
|
-
if (!suspended) await schedule();
|
|
395
|
-
},
|
|
396
|
-
};
|
|
397
|
-
},
|
|
398
|
-
async destroy() {
|
|
399
|
-
if (stopped) return;
|
|
400
|
-
stopped = true;
|
|
401
|
-
win.cancelAnimationFrame(frame);
|
|
402
|
-
frame = 0;
|
|
403
|
-
waiters.forEach((resolve) => resolve());
|
|
404
|
-
waiters = [];
|
|
405
|
-
observer.disconnect();
|
|
406
|
-
sourceObserver?.disconnect();
|
|
407
|
-
listeners.abort();
|
|
408
|
-
restore();
|
|
409
|
-
},
|
|
410
|
-
};
|
|
411
|
-
};
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import type { AnimationBuilder } from '@ionic/core/components';
|
|
2
|
-
import { connectNativeUIShellTransition } from './index';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Keeps an existing Ionic navigation animation and coordinates native control handoff.
|
|
6
|
-
* Register the returned builder as navAnimation. Do not use it for overlay animations.
|
|
7
|
-
* The builder must return a fresh Animation for each navigation; Ionic destroys it afterward.
|
|
8
|
-
*/
|
|
9
|
-
export const withNativeUIShellTransition =
|
|
10
|
-
(builder: AnimationBuilder): AnimationBuilder =>
|
|
11
|
-
(baseEl, opts) => {
|
|
12
|
-
const animation = builder(baseEl, opts);
|
|
13
|
-
if (opts?.enteringEl) connectNativeUIShellTransition(animation, opts.enteringEl, opts.leavingEl);
|
|
14
|
-
return animation;
|
|
15
|
-
};
|
package/src/vertical-bars.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export { enableVerticalControlArea, setVerticalControlAreaPlacement, IonicNativeUIShell } from './native';
|
|
2
|
-
export { withNativeUIShellTransition } from './native-integration/transition';
|
|
3
|
-
export type {
|
|
4
|
-
NativeUIShellStatus,
|
|
5
|
-
NativeUIShellSuspension,
|
|
6
|
-
VerticalBarEdge,
|
|
7
|
-
VerticalControlAreaHandle,
|
|
8
|
-
VerticalControlAreaOptions,
|
|
9
|
-
} from './native';
|
|
10
|
-
|
|
11
|
-
export { applyFoldStateClasses } from './native/foldable/fold-state';
|