@rdlabo/ionic-theme-ios27 1.2.0-beta.pr231.sha22a35d481cf3 → 1.2.0-beta.pr233.sha647eef34274b

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.
Files changed (34) hide show
  1. package/README.md +9 -0
  2. package/dist/native/components/searchable-tabs.d.ts.map +1 -1
  3. package/dist/native/components/searchable-tabs.js +6 -7
  4. package/dist/native/definitions.d.ts +4 -15
  5. package/dist/native/definitions.d.ts.map +1 -1
  6. package/dist/native/definitions.js +1 -6
  7. package/dist/native/index.d.ts +1 -2
  8. package/dist/native/index.d.ts.map +1 -1
  9. package/dist/native/index.js +57 -27
  10. package/dist/native/runtime.d.ts.map +1 -1
  11. package/dist/native/runtime.js +18 -10
  12. package/dist/native/vertical-bars-web.d.ts.map +1 -1
  13. package/dist/native/vertical-bars-web.js +2 -0
  14. package/dist/tab-bar-searchable/index.d.ts.map +1 -1
  15. package/dist/tab-bar-searchable/index.js +39 -11
  16. package/dist/vertical-bars.d.ts +1 -2
  17. package/dist/vertical-bars.d.ts.map +1 -1
  18. package/dist/vertical-bars.js +0 -1
  19. package/docs/iphone-duo-with-original-theme.md +16 -8
  20. package/docs/iphone-duo.md +57 -44
  21. package/docs/native-ui-shell.md +5 -3
  22. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellSearchController.swift +1 -1
  23. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellVerticalBars.swift +27 -2
  24. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellVerticalSearch.swift +118 -0
  25. package/ios/Sources/IonicNativeUIShellPlugin/IonicNativeUIShellPlugin.swift +27 -179
  26. package/ios/Tests/IonicNativeUIShellPluginTests/ShellSnapshotTests.swift +37 -0
  27. package/package.json +1 -1
  28. package/src/native/components/searchable-tabs.ts +8 -11
  29. package/src/native/definitions.ts +6 -19
  30. package/src/native/index.ts +60 -29
  31. package/src/native/runtime.ts +16 -10
  32. package/src/native/vertical-bars-web.ts +2 -0
  33. package/src/tab-bar-searchable/index.ts +40 -9
  34. package/src/vertical-bars.ts +1 -2
package/README.md CHANGED
@@ -125,6 +125,15 @@ Use this markup to preview the inset grouped list look. For the list structure t
125
125
 
126
126
  Keep your existing Ionic theme and move tabs and supported toolbar actions into a vertical side area. **Start in Chrome** with one stylesheet, an app class, and `enableVerticalControlArea()`; then connect the layout to iPhone Duo device events for the system rail and hinge posture.
127
127
 
128
+ Device state is supplied by [`@erkamyaman/capacitor-foldable`](https://github.com/erkamyaman/capacitor-foldable), installed in your app:
129
+
130
+ ```bash
131
+ npm install @erkamyaman/capacitor-foldable
132
+ npx cap sync
133
+ ```
134
+
135
+ Use its `getBarPlacement()` / `barPlacementChange` and `getFoldState()` / `foldStateChange` APIs to drive the theme's layout. Device monitoring is not bundled with the theme.
136
+
128
137
  Follow [iPhone Duo with your existing theme](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/iphone-duo-with-original-theme) for the browser preview and iOS setup. For shared layout rules and APIs, see [iPhone Duo support](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/iphone-duo). Available in `1.2.0-0` as an experimental feature; APIs and supported behavior may change.
129
138
 
130
139
  ### Use only the iOS 27 theme
@@ -1 +1 @@
1
- {"version":3,"file":"searchable-tabs.d.ts","sourceRoot":"","sources":["../../../src/native/components/searchable-tabs.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAGpE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EAA0B,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAmB/E,eAAO,MAAM,mBAAmB,GAAI,KAAK,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE,UAAU,MAAM,IAAI;sBA8InF,OAAO;;yBAmBJ,SAAS,EAAE,WAAW,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,GAAG,SAAS,EAAE;yBAlJhE,mBAAmB;;mBAkV9B,mBAAmB,YAAY,MAAM;uBAKjC,mBAAmB,UAAU,OAAO;qBAUtC,WAAW;gBAGhB,MAAM,EAAE;iBASP,gBAAgB;oBAqCb,mBAAmB;;CAoBtC,CAAC"}
1
+ {"version":3,"file":"searchable-tabs.d.ts","sourceRoot":"","sources":["../../../src/native/components/searchable-tabs.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAGpE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,EAA0B,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAmB/E,eAAO,MAAM,mBAAmB,GAAI,KAAK,QAAQ,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,EAAE,UAAU,MAAM,IAAI;sBA8InF,OAAO;;yBAwBJ,SAAS,EAAE,WAAW,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,GAAG,SAAS,EAAE;yBAvJhE,mBAAmB;;mBA+U9B,mBAAmB,YAAY,MAAM;uBAKjC,mBAAmB,UAAU,OAAO;qBAUtC,WAAW;gBAGhB,MAAM,EAAE;iBASP,gBAAgB;oBAqCb,mBAAmB;;CAoBtC,CAAC"}
@@ -151,7 +151,11 @@ export const createSearchSupport = (doc, id, schedule) => {
151
151
  keyboard(visible) {
152
152
  keyboardVisible = visible;
153
153
  if (!visible) {
154
- states.forEach((state) => (state.keyboardHidden = false));
154
+ for (const [binding, state] of states) {
155
+ if (state.keyboardHidden)
156
+ binding.tabBar.classList.remove('tab-bar-hidden');
157
+ state.keyboardHidden = false;
158
+ }
155
159
  releaseKeyboardResize();
156
160
  }
157
161
  schedule();
@@ -184,10 +188,7 @@ export const createSearchSupport = (doc, id, schedule) => {
184
188
  };
185
189
  for (const binding of bindings) {
186
190
  const candidate = candidates.find((c) => c.element === binding.tabBar);
187
- if (!candidate ||
188
- candidate.control.search ||
189
- candidate.control.placement === 'vertical-bars' ||
190
- binding.tabBar.getAttribute('slot') !== 'bottom')
191
+ if (!candidate || candidate.control.search || binding.tabBar.getAttribute('slot') !== 'bottom')
191
192
  continue;
192
193
  const fab = binding.trigger.parentElement;
193
194
  if (!fab?.matches('ion-fab[slot="fixed"]') ||
@@ -415,8 +416,6 @@ export const createSearchSupport = (doc, id, schedule) => {
415
416
  const state = states.get(binding);
416
417
  if (state?.keyboardHidden && keyboardVisible)
417
418
  binding.tabBar.classList.add('tab-bar-hidden');
418
- if (state)
419
- state.keyboardHidden = false;
420
419
  if (state?.focused) {
421
420
  state.focused = false;
422
421
  state.input.dispatchEvent(new FocusEvent('blur'));
@@ -26,12 +26,8 @@ export interface NativeUIShellHandle {
26
26
  export type VerticalBarEdge = 'leading' | 'trailing' | null;
27
27
  export interface VerticalBarPlacement {
28
28
  edge: VerticalBarEdge;
29
- inset: number;
30
- }
31
- export declare enum HingeStatus {
32
- Closed = "closed",
33
- PartiallyOpen = "partiallyOpen",
34
- FullyOpen = "fullyOpen"
29
+ inset?: number;
30
+ nativeEdge?: VerticalBarEdge;
35
31
  }
36
32
  export interface VerticalControlAreaHandle extends NativeUIShellHandle {
37
33
  setPlacement(placement: VerticalBarEdge | VerticalBarPlacement, rtl?: boolean): void;
@@ -127,20 +123,13 @@ export interface ShellActivation {
127
123
  export interface WebViewMetrics {
128
124
  radius: number;
129
125
  }
130
- export interface DeviceLayout {
131
- placement: VerticalBarPlacement;
132
- hingeStatus: HingeStatus | null;
133
- webViewMetrics: WebViewMetrics;
134
- }
135
126
  export interface NativeUIShellPlugin {
136
127
  configure(options?: {
137
128
  verticalBarsOnly?: boolean;
138
129
  }): Promise<{
139
130
  supported: boolean;
140
131
  }>;
141
- getDeviceLayout(): Promise<DeviceLayout>;
142
- startDeviceLayoutMonitoring(): Promise<void>;
143
- stopDeviceLayoutMonitoring(): Promise<void>;
132
+ getWebViewMetrics(): Promise<WebViewMetrics>;
144
133
  update(snapshot: ShellSnapshot): Promise<{
145
134
  revision: number;
146
135
  rejectedSearches?: string[];
@@ -151,6 +140,6 @@ export interface NativeUIShellPlugin {
151
140
  }): Promise<void>;
152
141
  addListener(name: 'activate', listener: (event: ShellActivation) => void): Promise<PluginListenerHandle>;
153
142
  addListener(name: 'search', listener: (event: ShellSearchEvent) => void): Promise<PluginListenerHandle>;
154
- addListener(name: 'deviceLayoutChange', listener: (event: DeviceLayout) => void): Promise<PluginListenerHandle>;
143
+ addListener(name: 'webViewMetricsChange', listener: (event: WebViewMetrics) => void): Promise<PluginListenerHandle>;
155
144
  }
156
145
  //# sourceMappingURL=definitions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.d.ts","sourceRoot":"","sources":["../../src/native/definitions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"definitions.d.ts","sourceRoot":"","sources":["../../src/native/definitions.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1 @@
1
- export var HingeStatus;
2
- (function (HingeStatus) {
3
- HingeStatus["Closed"] = "closed";
4
- HingeStatus["PartiallyOpen"] = "partiallyOpen";
5
- HingeStatus["FullyOpen"] = "fullyOpen";
6
- })(HingeStatus || (HingeStatus = {}));
1
+ export {};
@@ -1,6 +1,5 @@
1
1
  import type { NativeUIShellHandle, NativeUIShellOptions, NativeUIShellPlugin, VerticalBarEdge, VerticalBarPlacement, VerticalControlAreaHandle, WebViewMetrics } from './definitions';
2
- export type { NativeUIShellComponent, NativeUIShellControls, NativeUIShellHandle, NativeUIShellOptions, NativeUIShellStatus, NativeUIShellSuspension, DeviceLayout, VerticalBarEdge, VerticalBarPlacement, VerticalControlAreaHandle, WebViewMetrics, } from './definitions';
3
- export { HingeStatus } from './definitions';
2
+ export type { NativeUIShellComponent, NativeUIShellControls, NativeUIShellHandle, NativeUIShellOptions, NativeUIShellStatus, NativeUIShellSuspension, VerticalBarEdge, VerticalBarPlacement, VerticalControlAreaHandle, WebViewMetrics, } from './definitions';
4
3
  export declare const IonicNativeUIShell: NativeUIShellPlugin;
5
4
  export declare const configureNativeTransition: () => Promise<WebViewMetrics>;
6
5
  export declare const setVerticalControlAreaPlacement: (placement: VerticalBarEdge | VerticalBarPlacement, rtl?: boolean) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/native/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,eAAe,EACf,oBAAoB,EACpB,yBAAyB,EACzB,cAAc,EACf,MAAM,eAAe,CAAC;AAKvB,YAAY,EACV,sBAAsB,EACtB,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,uBAAuB,EACvB,YAAY,EACZ,eAAe,EACf,oBAAoB,EACpB,yBAAyB,EACzB,cAAc,GACf,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG5C,eAAO,MAAM,kBAAkB,qBAAS,CAAC;AAoEzC,eAAO,MAAM,yBAAyB,QAAa,OAAO,CAAC,cAAc,CAKxE,CAAC;AAYF,eAAO,MAAM,+BAA+B,GAAI,WAAW,eAAe,GAAG,oBAAoB,EAAE,MAAM,OAAO,KAAG,IASlH,CAAC;AAGF,eAAO,MAAM,yBAAyB,QAAa,OAAO,CAAC,yBAAyB,CAQnF,CAAC;AAGF,eAAO,MAAM,mBAAmB,GAAI,UAAS,oBAAyB,KAAG,OAAO,CAAC,mBAAmB,CAgGnG,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/native/index.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,eAAe,EACf,oBAAoB,EACpB,yBAAyB,EACzB,cAAc,EACf,MAAM,eAAe,CAAC;AAKvB,YAAY,EACV,sBAAsB,EACtB,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,uBAAuB,EACvB,eAAe,EACf,oBAAoB,EACpB,yBAAyB,EACzB,cAAc,GACf,MAAM,eAAe,CAAC;AAGvB,eAAO,MAAM,kBAAkB,qBAAS,CAAC;AAoEzC,eAAO,MAAM,yBAAyB,QAAa,OAAO,CAAC,cAAc,CAIxE,CAAC;AA6CF,eAAO,MAAM,+BAA+B,GAAI,WAAW,eAAe,GAAG,oBAAoB,EAAE,MAAM,OAAO,KAAG,IAclH,CAAC;AAGF,eAAO,MAAM,yBAAyB,QAAa,OAAO,CAAC,yBAAyB,CAQnF,CAAC;AAGF,eAAO,MAAM,mBAAmB,GAAI,UAAS,oBAAyB,KAAG,OAAO,CAAC,mBAAmB,CA4FnG,CAAC"}
@@ -4,7 +4,6 @@ import { createRuntime } from './runtime';
4
4
  import { createVerticalBarsWebProjection } from './vertical-bars-web';
5
5
  import { prehideVerticalBarsToolbarSources } from './prehide';
6
6
  import { observeVerticalBarsModals } from './shared/modal';
7
- export { HingeStatus } from './definitions';
8
7
  const plugin = registerPlugin('IonicNativeUIShell');
9
8
  export const IonicNativeUIShell = plugin;
10
9
  let active;
@@ -62,25 +61,59 @@ const manage = (handle, lifecycle = {}) => {
62
61
  };
63
62
  };
64
63
  export const configureNativeTransition = async () => {
65
- const metrics = typeof document !== 'undefined' && Capacitor.getPlatform() === 'ios' ? (await plugin.getDeviceLayout()).webViewMetrics : { radius: 0 };
64
+ const metrics = typeof document !== 'undefined' && Capacitor.getPlatform() === 'ios' ? await plugin.getWebViewMetrics() : { radius: 0 };
66
65
  setConfig({ radius: metrics.radius });
67
66
  return metrics;
68
67
  };
69
68
  const physicalVerticalBarEdge = (edge, rtl) => (edge === 'leading') !== rtl ? 'left' : 'right';
70
69
  const elementRtl = (element) => element.closest('[dir]')?.getAttribute('dir') === 'rtl';
70
+ const nativePlacements = new WeakMap();
71
+ const horizontalFallbackAttribute = 'data-native-ui-shell-vertical-bars-suspended';
72
+ const observeNativeVerticalBarsLayout = (doc) => {
73
+ const reconcile = () => {
74
+ const app = doc.querySelector('ion-app');
75
+ if (!app)
76
+ return;
77
+ const requested = app.classList.contains('ios-theme-vertical-bars') || app.hasAttribute(horizontalFallbackAttribute);
78
+ const suspended = requested && nativePlacements.get(app)?.edge == null;
79
+ if (app.hasAttribute(horizontalFallbackAttribute) !== suspended)
80
+ app.toggleAttribute(horizontalFallbackAttribute, suspended);
81
+ if (app.classList.contains('ios-theme-vertical-bars') !== (requested && !suspended)) {
82
+ app.classList.toggle('ios-theme-vertical-bars', requested && !suspended);
83
+ }
84
+ };
85
+ const observer = new MutationObserver(reconcile);
86
+ observer.observe(doc.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['class'] });
87
+ doc.defaultView?.addEventListener('nativeUIShellRefresh', reconcile);
88
+ reconcile();
89
+ return () => {
90
+ observer.disconnect();
91
+ doc.defaultView?.removeEventListener('nativeUIShellRefresh', reconcile);
92
+ const app = doc.querySelector(`ion-app[${horizontalFallbackAttribute}]`);
93
+ if (app) {
94
+ app.removeAttribute(horizontalFallbackAttribute);
95
+ app.classList.add('ios-theme-vertical-bars');
96
+ }
97
+ };
98
+ };
71
99
  export const setVerticalControlAreaPlacement = (placement, rtl) => {
72
100
  if (typeof document === 'undefined')
73
101
  return;
74
102
  const app = document.querySelector('ion-app');
75
103
  if (!app)
76
104
  throw new Error('Vertical Control Area requires ion-app');
77
- const { edge, inset } = placement && typeof placement === 'object' ? placement : { edge: placement, inset: 0 };
105
+ const { edge, inset = 0 } = placement && typeof placement === 'object' ? placement : { edge: placement, inset: 0 };
106
+ if (placement && typeof placement === 'object' && placement.nativeEdge !== undefined) {
107
+ nativePlacements.set(app, { edge: placement.nativeEdge, rtl });
108
+ }
109
+ app.removeAttribute(horizontalFallbackAttribute);
78
110
  app.classList.toggle('ios-theme-vertical-bars', edge !== null);
79
111
  app.classList.toggle('ios-theme-vertical-bars-left', edge !== null && physicalVerticalBarEdge(edge, rtl ?? elementRtl(app)) === 'left');
80
112
  if (edge && Number.isFinite(inset) && inset > 0)
81
113
  app.style.setProperty('--ios-theme-vertical-bars-native-inset', `${inset}px`);
82
114
  else
83
115
  app.style.removeProperty('--ios-theme-vertical-bars-native-inset');
116
+ document.defaultView?.dispatchEvent(new Event('nativeUIShellRefresh'));
84
117
  };
85
118
  export const enableVerticalControlArea = async () => {
86
119
  const handle = await enableNativeUIShell({ controls: { tabs: true, toolbar: true }, verticalBarsOnly: true });
@@ -138,40 +171,38 @@ export const enableNativeUIShell = (options = {}) => {
138
171
  if (Capacitor.getPlatform() !== 'ios')
139
172
  return fallback('Requires Capacitor iOS');
140
173
  let runtime;
141
- let placementListener;
142
- let monitoring = false;
174
+ let metricsListener;
175
+ let stopVerticalBarsLayout;
143
176
  try {
144
- if (!options.verticalBarsOnly)
145
- await configureNativeTransition().catch(() => undefined);
146
177
  const capabilities = await plugin.configure({ verticalBarsOnly: options.verticalBarsOnly === true });
147
178
  if (!capabilities.supported)
148
179
  return fallback('Requires iOS 26 or later');
149
- let nativeEdge = null;
180
+ if (!options.verticalBarsOnly)
181
+ stopVerticalBarsLayout = observeNativeVerticalBarsLayout(document);
150
182
  const nativeVerticalBars = () => {
151
- const root = document.querySelector('ion-app.ios-theme-vertical-bars');
152
- if (!root)
183
+ const app = document.querySelector('ion-app.ios-theme-vertical-bars');
184
+ if (!app)
153
185
  return false;
154
- const domEdge = root.classList.contains('ios-theme-vertical-bars-left') ? 'left' : 'right';
155
- return nativeEdge === null || physicalVerticalBarEdge(nativeEdge, elementRtl(root)) === domEdge;
186
+ const reported = nativePlacements.get(app);
187
+ if (!reported?.edge)
188
+ return false;
189
+ const physicalEdge = app.classList.contains('ios-theme-vertical-bars-left') ? 'left' : 'right';
190
+ return physicalEdge === physicalVerticalBarEdge(reported.edge, reported.rtl ?? elementRtl(app));
156
191
  };
157
- await plugin.startDeviceLayoutMonitoring();
158
- monitoring = true;
159
- placementListener = await plugin.addListener('deviceLayoutChange', ({ placement, webViewMetrics }) => {
160
- nativeEdge = placement.edge;
161
- if (!options.verticalBarsOnly)
162
- setConfig({ radius: webViewMetrics.radius });
163
- document.defaultView?.dispatchEvent(new Event('nativeUIShellRefresh'));
164
- });
165
- nativeEdge = (await plugin.getDeviceLayout()).placement.edge;
192
+ if (!options.verticalBarsOnly) {
193
+ metricsListener = await plugin.addListener('webViewMetricsChange', (metrics) => {
194
+ setConfig({ radius: metrics.radius });
195
+ });
196
+ await configureNativeTransition().catch(() => undefined);
197
+ }
166
198
  const native = await createRuntime(document, plugin, options, nativeVerticalBars, options.verticalBarsOnly === true);
167
199
  runtime = combine(native, createVerticalBarsWebProjection(document, options, () => !nativeVerticalBars() || native.getStatus().state === 'stopped'));
168
200
  return manage(runtime, {
169
201
  suspend: () => prehide?.suspend(),
170
202
  release,
171
203
  destroy: async () => {
172
- await placementListener?.remove().catch(() => { });
173
- if (monitoring)
174
- await plugin.stopDeviceLayoutMonitoring().catch(() => { });
204
+ await metricsListener?.remove().catch(() => { });
205
+ stopVerticalBarsLayout?.();
175
206
  prehide?.stop();
176
207
  stopModals();
177
208
  },
@@ -179,9 +210,8 @@ export const enableNativeUIShell = (options = {}) => {
179
210
  }
180
211
  catch (error) {
181
212
  await runtime?.destroy();
182
- await placementListener?.remove().catch(() => { });
183
- if (monitoring)
184
- await plugin.stopDeviceLayoutMonitoring().catch(() => { });
213
+ await metricsListener?.remove().catch(() => { });
214
+ stopVerticalBarsLayout?.();
185
215
  return fallback(error instanceof Error ? error.message : String(error));
186
216
  }
187
217
  })());
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/native/runtime.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAGV,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EAEpB,MAAM,eAAe,CAAC;AA6BvB,eAAO,MAAM,aAAa,GACxB,KAAK,QAAQ,EACb,QAAQ,mBAAmB,EAC3B,UAAS,oBAAyB,EAClC,qBAAoB,MAAM,OAAoB,EAC9C,0BAAwB,KACvB,OAAO,CAAC,mBAAmB,CAmtB7B,CAAC"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/native/runtime.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAGV,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EAEpB,MAAM,eAAe,CAAC;AA6BvB,eAAO,MAAM,aAAa,GACxB,KAAK,QAAQ,EACb,QAAQ,mBAAmB,EAC3B,UAAS,oBAAyB,EAClC,qBAAoB,MAAM,OAAoB,EAC9C,0BAAwB,KACvB,OAAO,CAAC,mBAAmB,CAytB7B,CAAC"}
@@ -378,13 +378,15 @@ export const createRuntime = async (doc, plugin, options = {}, nativeVerticalBar
378
378
  verticalBarsMembers.add(element);
379
379
  }
380
380
  for (const element of accepted.flatMap(candidateSources)) {
381
+ const newlyProjected = !sources.has(element) || !element.hasAttribute(marker);
381
382
  if (!sources.has(element)) {
382
383
  sources.set(element, element.getAttribute('aria-hidden'));
383
384
  crossfade.play(element, true, handoffInstant || isVerticalBarsSource(element));
384
- element.setAttribute(marker, '');
385
- element.setAttribute('aria-hidden', 'true');
386
- element.dispatchEvent(new CustomEvent('nativeUIShellChange'));
387
385
  }
386
+ element.setAttribute(marker, '');
387
+ element.setAttribute('aria-hidden', 'true');
388
+ if (newlyProjected)
389
+ element.dispatchEvent(new CustomEvent('nativeUIShellChange'));
388
390
  }
389
391
  const received = pendingActivations;
390
392
  pendingActivations = [];
@@ -567,25 +569,31 @@ export const createRuntime = async (doc, plugin, options = {}, nativeVerticalBar
567
569
  'focusout',
568
570
  ])
569
571
  on(doc, name);
572
+ const refreshLayout = () => {
573
+ rejected = new WeakMap();
574
+ forceRefresh = true;
575
+ schedule();
576
+ };
570
577
  on(doc, 'visibilitychange', () => {
571
- if (doc.hidden)
578
+ if (doc.hidden) {
572
579
  getNativeSearchBindings(doc).forEach((binding) => search.retire(binding));
573
- schedule();
580
+ schedule();
581
+ }
582
+ else
583
+ refreshLayout();
574
584
  });
575
- on(win, 'resize');
585
+ on(win, 'resize', refreshLayout);
576
586
  on(win, 'keyboardWillShow', () => search.keyboard(true));
577
587
  on(win, 'keyboardWillHide', () => search.keyboard(false));
578
588
  on(win, 'scroll');
579
589
  on(win.matchMedia('(prefers-color-scheme: dark)'), 'change');
580
590
  on(win, 'nativeUIShellRefresh', (event) => {
581
- rejected = new WeakMap();
582
591
  if (event.retireSearch)
583
592
  getNativeSearchBindings(doc).forEach((binding) => search.retire(binding));
584
- forceRefresh = true;
585
- schedule();
593
+ refreshLayout();
586
594
  });
587
595
  if (win.visualViewport) {
588
- on(win.visualViewport, 'resize');
596
+ on(win.visualViewport, 'resize', refreshLayout);
589
597
  on(win.visualViewport, 'scroll');
590
598
  }
591
599
  const handle = {
@@ -1 +1 @@
1
- {"version":3,"file":"vertical-bars-web.d.ts","sourceRoot":"","sources":["../../src/native/vertical-bars-web.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,oBAAoB,EAAuB,MAAM,eAAe,CAAC;AAqCpG,eAAO,MAAM,+BAA+B,GAC1C,KAAK,QAAQ,EACb,SAAS,oBAAoB,EAC7B,UAAS,MAAM,OAAoB,KAClC,mBAkVF,CAAC"}
1
+ {"version":3,"file":"vertical-bars-web.d.ts","sourceRoot":"","sources":["../../src/native/vertical-bars-web.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,oBAAoB,EAAuB,MAAM,eAAe,CAAC;AAqCpG,eAAO,MAAM,+BAA+B,GAC1C,KAAK,QAAQ,EACb,SAAS,oBAAoB,EAC7B,UAAS,MAAM,OAAoB,KAClC,mBAoVF,CAAC"}
@@ -260,6 +260,8 @@ export const createVerticalBarsWebProjection = (doc, options, enabled = () => tr
260
260
  const schedule = () => {
261
261
  if (stopped)
262
262
  return Promise.resolve();
263
+ if (!enabled())
264
+ restore();
263
265
  const done = new Promise((resolve) => waiters.push(resolve));
264
266
  if (!frame)
265
267
  frame = win.requestAnimationFrame(update);
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tab-bar-searchable/index.ts"],"names":[],"mappings":"AACA,OAAO,EAQL,KAAK,wBAAwB,EAE9B,MAAM,2BAA2B,CAAC;AAiBnC,OAAO,EACL,oBAAoB,EACpB,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,KAAK,iBAAiB,GACvB,MAAM,2BAA2B,CAAC;AAqBnC,eAAO,MAAM,sBAAsB,GACjC,WAAW,WAAW,EACtB,cAAc,WAAW,EACzB,WAAW,WAAW,KACrB,wBAwDF,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tab-bar-searchable/index.ts"],"names":[],"mappings":"AACA,OAAO,EAQL,KAAK,wBAAwB,EAE9B,MAAM,2BAA2B,CAAC;AAiBnC,OAAO,EACL,oBAAoB,EACpB,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,KAAK,iBAAiB,GACvB,MAAM,2BAA2B,CAAC;AAqBnC,eAAO,MAAM,sBAAsB,GACjC,WAAW,WAAW,EACtB,cAAc,WAAW,EACzB,WAAW,WAAW,KACrB,wBA+EF,CAAC"}
@@ -10,11 +10,14 @@ export const attachTabBarSearchable = (ionTabBar, ionFabButton, ionFooter) => {
10
10
  }
11
11
  ionFooter.style.pointerEvents = 'none';
12
12
  ionFooter.style.opacity = '0';
13
+ ionFooter.querySelector('ion-buttons[slot=start]')?.classList.add('ios-theme-horizontal-only');
13
14
  const nativeSearch = registerNativeSearch(ionTabBar, ionFabButton, ionFooter);
14
15
  let searchableEventCache;
15
16
  let resumeNative;
16
17
  let nativeEntry = false;
17
18
  let nativeRequest = 0;
19
+ let verticalSearch = false;
20
+ let tabVisibilityObserver;
18
21
  return async (event, type) => {
19
22
  const selector = type === TabBarSearchableType.Enter ? 'ion-fab-button' : 'ion-buttons[slot=start] ion-button';
20
23
  if (!event.target?.closest(selector))
@@ -37,11 +40,28 @@ export const attachTabBarSearchable = (ionTabBar, ionFabButton, ionFooter) => {
37
40
  return;
38
41
  }
39
42
  if (type === TabBarSearchableType.Enter) {
40
- resumeNative ?? (resumeNative = await suspendNativeUIShell([ionTabBar, ionFooter, ionFabButton.closest('ion-fab') ?? ionFabButton]));
43
+ verticalSearch = !!ionTabBar.closest('ion-app.ios-theme-vertical-bars');
44
+ if (verticalSearch) {
45
+ const keepTabsVisible = () => {
46
+ if (ionTabBar.classList.contains('tab-bar-hidden') && ionTabBar.closest('ion-app.ios-theme-vertical-bars')) {
47
+ ionTabBar.classList.remove('tab-bar-hidden');
48
+ }
49
+ };
50
+ tabVisibilityObserver?.disconnect();
51
+ tabVisibilityObserver = new MutationObserver(keepTabsVisible);
52
+ tabVisibilityObserver.observe(ionTabBar, { attributes: true, attributeFilter: ['class'] });
53
+ keepTabsVisible();
54
+ }
55
+ resumeNative ?? (resumeNative = await suspendNativeUIShell([
56
+ ...(verticalSearch ? [] : [ionTabBar]),
57
+ ionFooter,
58
+ ionFabButton.closest('ion-fab') ?? ionFabButton,
59
+ ]));
41
60
  try {
42
- searchableEventCache = await enterEvent(event, ionTabBar, ionFabButton, ionFooter);
61
+ searchableEventCache = await enterEvent(event, ionTabBar, ionFabButton, ionFooter, verticalSearch);
43
62
  }
44
63
  catch (error) {
64
+ tabVisibilityObserver?.disconnect();
45
65
  resumeNative();
46
66
  resumeNative = undefined;
47
67
  throw error;
@@ -49,10 +69,11 @@ export const attachTabBarSearchable = (ionTabBar, ionFabButton, ionFooter) => {
49
69
  }
50
70
  else if (searchableEventCache !== undefined) {
51
71
  try {
52
- await leaveEvent(event, searchableEventCache, ionTabBar, ionFabButton, ionFooter);
72
+ await leaveEvent(event, searchableEventCache, ionTabBar, ionFabButton, ionFooter, verticalSearch);
53
73
  searchableEventCache = undefined;
54
74
  }
55
75
  finally {
76
+ tabVisibilityObserver?.disconnect();
56
77
  resumeNative?.();
57
78
  resumeNative = undefined;
58
79
  }
@@ -62,7 +83,7 @@ export const attachTabBarSearchable = (ionTabBar, ionFabButton, ionFooter) => {
62
83
  }
63
84
  };
64
85
  };
65
- const enterEvent = async (event, ionTabBar, ionFabButton, ionFooter) => {
86
+ const enterEvent = async (event, ionTabBar, ionFabButton, ionFooter, verticalSearch) => {
66
87
  if (!event.target?.closest('ion-fab-button')) {
67
88
  throw throwErrorByFailedClickElement('ion-fab-button');
68
89
  }
@@ -71,10 +92,13 @@ const enterEvent = async (event, ionTabBar, ionFabButton, ionFooter) => {
71
92
  const colorSelected = references.selectedTabButton
72
93
  ? getComputedStyle(references.selectedTabButton).getPropertyValue('--color-selected').trim()
73
94
  : '';
74
- const effectAnimation = createEffectAnimation(references, sizes);
95
+ if (verticalSearch)
96
+ references.closeButtonIcon.setAttribute('name', 'close');
97
+ const tabAnimations = verticalSearch
98
+ ? []
99
+ : [createTabBarAnimation(ionTabBar, references, sizes), createEffectAnimation(references, sizes)];
75
100
  const searchContainerAnimation = createSearchContainerAnimation(references, sizes);
76
101
  const closeButtonsAnimation = createCloseButtonsAnimation(references);
77
- const tabBarAnimation = createTabBarAnimation(ionTabBar, references, sizes);
78
102
  const fabButtonAnimation = createFabButtonAnimation(ionFabButton);
79
103
  await createAnimation()
80
104
  .delay(ANIMATION_DELAY_BASE)
@@ -83,22 +107,26 @@ const enterEvent = async (event, ionTabBar, ionFabButton, ionFooter) => {
83
107
  .addElement(ionFooter)
84
108
  .afterAddWrite(() => (ionFooter.style.pointerEvents = 'auto'))
85
109
  .fromTo('opacity', '0.8', '1')
86
- .addAnimation([tabBarAnimation, fabButtonAnimation, searchContainerAnimation, effectAnimation, closeButtonsAnimation])
110
+ .addAnimation([...tabAnimations, fabButtonAnimation, searchContainerAnimation, closeButtonsAnimation])
87
111
  .play();
88
112
  return {
89
113
  elementSizes: sizes,
90
114
  colorSelected,
91
115
  };
92
116
  };
93
- const leaveEvent = async (event, searchableEventCache, ionTabBar, ionFabButton, ionFooter) => {
117
+ const leaveEvent = async (event, searchableEventCache, ionTabBar, ionFabButton, ionFooter, verticalSearch) => {
94
118
  if (!event.target?.closest('ion-buttons[slot=start] ion-button')) {
95
119
  throw throwErrorByFailedClickElement('ion-buttons[slot=start] ion-button');
96
120
  }
97
121
  const references = getElementReferences(ionTabBar, ionFooter);
98
- const effectAnimation = createReverseEffectAnimation(references, searchableEventCache.elementSizes, searchableEventCache.colorSelected);
122
+ const tabAnimations = verticalSearch
123
+ ? []
124
+ : [
125
+ createReverseTabBarAnimation(ionTabBar, references, searchableEventCache.elementSizes),
126
+ createReverseEffectAnimation(references, searchableEventCache.elementSizes, searchableEventCache.colorSelected),
127
+ ];
99
128
  const searchContainerAnimation = createReverseSearchContainerAnimation(references, searchableEventCache.elementSizes);
100
129
  const closeButtonsAnimation = createReverseCloseButtonsAnimation(references);
101
- const tabBarAnimation = createReverseTabBarAnimation(ionTabBar, references, searchableEventCache.elementSizes);
102
130
  const fabButtonAnimation = createReverseFabButtonAnimation(ionFabButton, searchableEventCache.elementSizes);
103
131
  await createAnimation()
104
132
  .delay(ANIMATION_DELAY_BASE)
@@ -107,6 +135,6 @@ const leaveEvent = async (event, searchableEventCache, ionTabBar, ionFabButton,
107
135
  .addElement(ionFooter)
108
136
  .afterAddWrite(() => (ionFooter.style.pointerEvents = 'none'))
109
137
  .fromTo('opacity', '1', '0')
110
- .addAnimation([tabBarAnimation, fabButtonAnimation, searchContainerAnimation, effectAnimation, closeButtonsAnimation])
138
+ .addAnimation([...tabAnimations, fabButtonAnimation, searchContainerAnimation, closeButtonsAnimation])
111
139
  .play();
112
140
  };
@@ -1,5 +1,4 @@
1
1
  export { enableVerticalControlArea, setVerticalControlAreaPlacement, IonicNativeUIShell } from './native';
2
- export { HingeStatus } from './native';
3
2
  export { withNativeUIShellTransition } from './native-integration/transition';
4
- export type { DeviceLayout, NativeUIShellStatus, NativeUIShellSuspension, VerticalBarEdge, VerticalControlAreaHandle } from './native';
3
+ export type { NativeUIShellStatus, NativeUIShellSuspension, VerticalBarEdge, VerticalControlAreaHandle } from './native';
5
4
  //# sourceMappingURL=vertical-bars.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"vertical-bars.d.ts","sourceRoot":"","sources":["../src/vertical-bars.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,+BAA+B,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC1G,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,2BAA2B,EAAE,MAAM,iCAAiC,CAAC;AAC9E,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,eAAe,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"vertical-bars.d.ts","sourceRoot":"","sources":["../src/vertical-bars.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,+BAA+B,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC1G,OAAO,EAAE,2BAA2B,EAAE,MAAM,iCAAiC,CAAC;AAC9E,YAAY,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,eAAe,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC"}
@@ -1,3 +1,2 @@
1
1
  export { enableVerticalControlArea, setVerticalControlAreaPlacement, IonicNativeUIShell } from './native';
2
- export { HingeStatus } from './native';
3
2
  export { withNativeUIShellTransition } from './native-integration/transition';
@@ -122,23 +122,31 @@ When the application owner is disposed, call `await rail.destroy()` to restore t
122
122
 
123
123
  ## Connect an iPhone Duo
124
124
 
125
- For Capacitor iOS, run `npx cap sync ios`. Build with Xcode 27.1 or newer and link against the iOS 27.1 SDK or later to receive the actual rail edge, safe-area inset, and hinge posture. The plugin uses Swift Package Manager; existing CocoaPods apps can follow [Native UI Shell setup](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/native-ui-shell#enable-the-shell).
125
+ Install [`@erkamyaman/capacitor-foldable`](https://github.com/erkamyaman/capacitor-foldable) for device state:
126
+
127
+ ```bash
128
+ npm install @erkamyaman/capacitor-foldable
129
+ npx cap sync ios
130
+ ```
131
+
132
+ Use Capacitor 8.5 or later and build with Xcode 27.1 or newer for actual rail placement and hinge posture on iOS 27.1. Native UI Shell uses Swift Package Manager; existing CocoaPods apps can follow [Native UI Shell setup](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/native-ui-shell#enable-the-shell). Keep this package's `vertical-bars.css`; the device plugin's `ionic-tabs.css` is not needed with our rail projection.
126
133
 
127
134
  Replace the browser-only startup above with this after `ion-app` is mounted:
128
135
 
129
136
  ```ts
130
137
  import { Capacitor, type PluginListenerHandle } from '@capacitor/core';
131
- import { enableVerticalControlArea, IonicNativeUIShell } from '@rdlabo/ionic-theme-ios27/vertical-bars';
138
+ import { enableVerticalControlArea } from '@rdlabo/ionic-theme-ios27/vertical-bars';
139
+ import { Foldable } from '@erkamyaman/capacitor-foldable';
132
140
 
133
141
  const rail = await enableVerticalControlArea();
134
142
  let layoutListener: PluginListenerHandle | undefined;
135
143
 
136
144
  if (Capacitor.getPlatform() === 'ios') {
137
- // The runtime already monitors device layout; only subscribe.
138
- layoutListener = await IonicNativeUIShell.addListener('deviceLayoutChange', ({ placement }) =>
139
- rail.setPlacement(placement),
145
+ layoutListener = await Foldable.addListener('barPlacementChange', ({ verticalBarEdge, inset }) =>
146
+ rail.setPlacement({ edge: verticalBarEdge, nativeEdge: verticalBarEdge, inset }),
140
147
  );
141
- rail.setPlacement((await IonicNativeUIShell.getDeviceLayout()).placement);
148
+ const { verticalBarEdge, inset } = await Foldable.getBarPlacement();
149
+ rail.setPlacement({ edge: verticalBarEdge, nativeEdge: verticalBarEdge, inset });
142
150
  }
143
151
 
144
152
  // Call when the application owner is disposed.
@@ -148,13 +156,13 @@ const stopVerticalArea = async () => {
148
156
  };
149
157
  ```
150
158
 
151
- `setPlacement()` applies the measured inset and resolves the logical edge through the document direction. A `null` edge restores the ordinary layout. Devices without a rail and apps built with older SDKs report `null`, so this example restores the ordinary layout there. To deliberately preview a DOM rail on such an iOS build, have your application choose a fixed edge with `rail.setPlacement('trailing')` instead of applying that null placement. This simulates the layout; it does not provide a real system rail or hinge measurements.
159
+ `setPlacement()` resolves the logical edge through the document direction. Pass Foldable’s measured `inset` with the reported edge so the theme reserves the actual bar width and follows width changes. An inset of `0` clears the explicit width; manually requested rails then use the theme’s CSS safe-area rules. A `null` edge restores the ordinary layout. Devices without a reported rail return `null`, so this example restores the ordinary layout there. On iOS 27.1 or later, Foldable can infer Duo bar placement from safe-area insets when the app is built with an older SDK. To deliberately request a rail when the plugin reports no edge, have your application choose a fixed edge with `rail.setPlacement('trailing')` instead of applying that null placement. This simulates the layout; it does not provide a real system rail or hinge measurements.
152
160
 
153
161
  On supported iOS, controls in the rail use the system SwiftUI appearance; your custom Web styling still applies to ordinary content and horizontal controls. Web and Android use Web clones.
154
162
 
155
163
  ## Use hinge posture without projecting controls
156
164
 
157
- If your existing theme needs only a posture-driven split pane or a layout switch, do not start a projection runtime or add `.ios-theme-vertical-bars`. Use `getDeviceLayout()` and `deviceLayoutChange` directly, pairing `startDeviceLayoutMonitoring()` with `stopDeviceLayoutMonitoring()` and removing the listener when finished.
165
+ If your existing theme needs only a posture-driven split pane or a layout switch, do not start a projection runtime or add `.ios-theme-vertical-bars`. Use `Foldable.getFoldState()` and `foldStateChange` directly, removing the listener when finished. There is no separate start/stop monitoring call.
158
166
 
159
167
  See [Read the device layout](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/iphone-duo#read-the-device-layout) for the subscription example, null values, and monitoring lifetime. See [Adapt the split pane](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/iphone-duo#adapt-the-split-pane) for the opt-in width rules and half-open state.
160
168