@rdlabo/ionic-theme-ios27 1.2.0-0 → 1.2.0-2

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 (78) hide show
  1. package/README.md +12 -2
  2. package/dist/css/components/ion-tabs.css +1 -1
  3. package/dist/css/ionic-theme-ios27.css +1 -1
  4. package/dist/css/vertical-bars.css +1 -1
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +32 -12
  8. package/dist/native/components/index.d.ts.map +1 -1
  9. package/dist/native/components/index.js +4 -1
  10. package/dist/native/components/ion-button.d.ts.map +1 -1
  11. package/dist/native/components/ion-button.js +3 -3
  12. package/dist/native/components/ion-buttons.d.ts.map +1 -1
  13. package/dist/native/components/ion-buttons.js +3 -2
  14. package/dist/native/definitions.d.ts +8 -15
  15. package/dist/native/definitions.d.ts.map +1 -1
  16. package/dist/native/definitions.js +1 -6
  17. package/dist/native/index.d.ts +1 -2
  18. package/dist/native/index.d.ts.map +1 -1
  19. package/dist/native/index.js +64 -28
  20. package/dist/native/prehide.d.ts.map +1 -1
  21. package/dist/native/prehide.js +6 -4
  22. package/dist/native/runtime.d.ts.map +1 -1
  23. package/dist/native/runtime.js +27 -12
  24. package/dist/native/shared/candidate.d.ts.map +1 -1
  25. package/dist/native/shared/candidate.js +8 -0
  26. package/dist/native/shared/dom.d.ts.map +1 -1
  27. package/dist/native/shared/dom.js +11 -10
  28. package/dist/native/shared/modal.d.ts +8 -0
  29. package/dist/native/shared/modal.d.ts.map +1 -0
  30. package/dist/native/shared/modal.js +143 -0
  31. package/dist/native/vertical-bars-web.d.ts.map +1 -1
  32. package/dist/native/vertical-bars-web.js +11 -3
  33. package/dist/native-integration/index.d.ts.map +1 -1
  34. package/dist/native-integration/index.js +7 -2
  35. package/dist/native-integration/transition.d.ts +3 -0
  36. package/dist/native-integration/transition.d.ts.map +1 -0
  37. package/dist/native-integration/transition.js +7 -0
  38. package/dist/sheets-of-glass/animations.d.ts +13 -4
  39. package/dist/sheets-of-glass/animations.d.ts.map +1 -1
  40. package/dist/sheets-of-glass/animations.js +56 -40
  41. package/dist/sheets-of-glass/index.d.ts +1 -1
  42. package/dist/sheets-of-glass/index.d.ts.map +1 -1
  43. package/dist/sheets-of-glass/index.js +64 -20
  44. package/dist/transition/ios.transition.d.ts.map +1 -1
  45. package/dist/transition/ios.transition.js +2 -3
  46. package/dist/vertical-bars.d.ts +2 -2
  47. package/dist/vertical-bars.d.ts.map +1 -1
  48. package/dist/vertical-bars.js +1 -1
  49. package/docs/iphone-duo-with-original-theme.md +171 -0
  50. package/docs/iphone-duo.md +105 -50
  51. package/docs/migration.md +23 -0
  52. package/docs/native-ui-shell.md +10 -6
  53. package/docs/special-markup.md +3 -14
  54. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellVerticalBars.swift +23 -4
  55. package/ios/Sources/IonicNativeUIShellPlugin/IonicNativeUIShellPlugin.swift +15 -171
  56. package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellSnapshot.swift +5 -0
  57. package/ios/Tests/IonicNativeUIShellPluginTests/ShellSnapshotTests.swift +30 -0
  58. package/package.json +1 -1
  59. package/src/index.ts +41 -9
  60. package/src/native/components/index.ts +6 -1
  61. package/src/native/components/ion-button.ts +4 -3
  62. package/src/native/components/ion-buttons.ts +4 -2
  63. package/src/native/definitions.ts +11 -19
  64. package/src/native/index.ts +67 -30
  65. package/src/native/prehide.ts +7 -4
  66. package/src/native/runtime.ts +28 -11
  67. package/src/native/shared/candidate.ts +10 -0
  68. package/src/native/shared/dom.ts +11 -10
  69. package/src/native/shared/modal.ts +157 -0
  70. package/src/native/vertical-bars-web.ts +12 -3
  71. package/src/native-integration/index.ts +8 -2
  72. package/src/native-integration/transition.ts +15 -0
  73. package/src/sheets-of-glass/animations.ts +66 -41
  74. package/src/sheets-of-glass/index.ts +82 -16
  75. package/src/styles/components/ion-tabs.scss +7 -4
  76. package/src/styles/vertical-bars.scss +102 -35
  77. package/src/transition/ios.transition.ts +4 -4
  78. package/src/vertical-bars.ts +2 -2
@@ -31,8 +31,21 @@ ion-app.ios-theme-vertical-bars.ios-theme-vertical-bars-left {
31
31
  --ios-theme-vertical-bars-safe-area-right-resolved: var(--ios-theme-vertical-bars-safe-area-right, 0px);
32
32
  }
33
33
 
34
+ // Centered dialogs do not inherit the page's rail reservation.
35
+ ion-app.ios-theme-vertical-bars ion-modal:not(.ios-theme-vertical-bars-modal) {
36
+ --ios-theme-vertical-bars-safe-area-left-resolved: 0px;
37
+ --ios-theme-vertical-bars-safe-area-right-resolved: 0px;
38
+ }
39
+
40
+ ion-app.ios-theme-vertical-bars ion-modal.ios-theme-vertical-bars-modal {
41
+ --ios-theme-vertical-bars-modal-toolbar-top: max(
42
+ 24px,
43
+ calc(var(--ios-theme-vertical-bars-toolbar-top, 220px) - var(--ios-theme-vertical-bars-modal-offset-top, 0px))
44
+ );
45
+ }
46
+
34
47
  // Supply material defaults only to Web projections, never to ordinary controls.
35
- ion-app.ios-theme-vertical-bars.ios-theme-vertical-bars-toolbar-ready
48
+ :is(ion-app.ios-theme-vertical-bars, ion-modal.ios-theme-vertical-bars-modal).ios-theme-vertical-bars-toolbar-ready
36
49
  > :is(ion-menu-button, ion-button).ios-theme-vertical-bars-toolbar-projection {
37
50
  --ios26-glass-background-rgb: var(--ion-background-color-rgb, 255, 255, 255);
38
51
  --ios26-glass-border-color-rgb: var(--ion-background-color-rgb, 255, 255, 255);
@@ -46,7 +59,9 @@ ion-app.ios-theme-vertical-bars .ios-transition-shade {
46
59
 
47
60
  // Keep page backgrounds and the router full width; move only their foregrounds.
48
61
  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 *)) {
62
+ ion-content:is(.ios, .md):not(.ios-theme-disabled, .ios26-disabled):not(
63
+ :where(ion-menu *, ion-modal:not(.ios-theme-vertical-bars-modal) *, ion-popover *)
64
+ ) {
50
65
  --ion-safe-area-left: 0px;
51
66
  --ion-safe-area-right: 0px;
52
67
 
@@ -62,7 +77,9 @@ ion-app.ios-theme-vertical-bars
62
77
  }
63
78
 
64
79
  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 *)) {
80
+ ion-toolbar:is(.ios, .md):not(.ios-theme-disabled, .ios26-disabled):not(
81
+ :where(ion-menu *, ion-modal:not(.ios-theme-vertical-bars-modal) *, ion-popover *)
82
+ ) {
66
83
  --ion-safe-area-left: 0px;
67
84
  --ion-safe-area-right: 0px;
68
85
 
@@ -131,37 +148,44 @@ ion-app.ios-theme-vertical-bars {
131
148
  ion-app.ios-theme-vertical-bars
132
149
  ion-tabs:not(:where(ion-menu *, ion-modal *, ion-popover *))
133
150
  > 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);
151
+ // iOS 27.1 Duo: 48pt platter, 44 × 58pt items with 8pt overlap, 24pt edge gaps.
152
+ --ios-theme-side-tab-bar-width: 48px;
153
+ --ios-theme-side-tab-content-width: 44px;
154
+ --ios-theme-side-tab-bar-gap: 24px;
137
155
  contain: layout style;
138
156
  position: absolute;
139
157
  z-index: 2;
140
158
  display: flex;
141
159
  flex-direction: column;
142
- width: 46px;
143
- min-width: 46px;
144
- max-width: 46px;
160
+ width: 44px;
161
+ min-width: 44px;
162
+ max-width: 44px;
145
163
  height: fit-content;
146
164
  min-height: 0;
147
165
  padding: 2px;
148
- border-radius: 25px;
166
+ border-radius: 24px;
149
167
  left: auto;
150
- right: max(4px, calc((var(--ios-theme-side-safe-area) - var(--ios-theme-side-tab-bar-width)) / 2));
168
+ right: calc(24px + (48px - var(--ios-theme-side-tab-bar-width)) / 2);
151
169
  margin: 0;
152
170
  top: auto;
153
- bottom: calc(var(--ion-safe-area-bottom, 0px) + var(--ios-theme-side-tab-bar-gap));
171
+ bottom: var(--ios-theme-side-tab-bar-gap);
172
+
173
+ &.ios27-enable-gesture {
174
+ touch-action: pan-x pinch-zoom;
175
+ }
154
176
 
155
177
  > ion-tab-button:not(.ion-cloned-element) {
156
178
  display: block;
157
179
  flex: none;
158
- width: 46px;
159
- min-width: 46px;
160
- height: 52px;
161
- min-height: 52px;
162
- border-radius: 23px;
180
+ width: 44px;
181
+ min-width: 44px;
182
+ height: 58px;
183
+ min-height: 58px;
184
+ border-radius: 22px;
163
185
  &::part(native) {
164
186
  justify-content: center;
187
+ min-height: 58px;
188
+ padding: 0;
165
189
  }
166
190
  ion-label {
167
191
  position: absolute;
@@ -179,32 +203,54 @@ ion-app.ios-theme-vertical-bars
179
203
  font-size: 28px;
180
204
  }
181
205
  }
206
+ > ion-tab-button.ion-activated {
207
+ transform: scale(1.16);
208
+ }
182
209
  > ion-tab-button:not(.ion-cloned-element) ~ ion-tab-button:not(.ion-cloned-element) {
183
- margin-block-start: 0;
210
+ margin-block-start: -8px;
184
211
  margin-inline-start: 0;
185
212
  }
186
- &:has(> ion-tab-button:is(.ion-activated, :active)) > ion-tab-button:not(.ion-cloned-element) {
213
+ // Duo's expanded platter keeps its center and reveals labels without shrinking icons.
214
+ &:is(.ios27-animated, :has(> ion-tab-button:is(.ion-activated, :active))) {
215
+ --ios-theme-side-tab-bar-width: 72px;
216
+ --ios-theme-side-tab-content-width: 64px;
217
+ --ios-theme-side-tab-bar-gap: 20px;
218
+ width: 64px;
219
+ min-width: 64px;
220
+ max-width: 64px;
221
+ padding: 4px;
222
+ border-radius: 36px;
223
+ transform: none;
224
+ -webkit-transform: none;
225
+ }
226
+ &:is(.ios27-animated, :has(> ion-tab-button:is(.ion-activated, :active))) > ion-tab-button:not(.ion-cloned-element) {
227
+ width: 64px;
228
+ min-width: 64px;
229
+ height: 64px;
230
+ min-height: 64px;
231
+ border-radius: 32px;
187
232
  &::part(native) {
188
233
  flex-direction: column;
189
- padding-block: 4px;
234
+ min-height: 64px;
235
+ padding-block: 11px;
190
236
  }
191
237
  ion-label {
192
238
  position: static;
193
239
  width: auto;
194
240
  height: auto;
195
- max-width: 44px;
241
+ max-width: 64px;
196
242
  padding: 0;
197
243
  margin: 1px 0 0;
198
244
  overflow: hidden;
199
245
  clip: auto;
200
- font-size: 0.5rem;
201
- line-height: 1;
246
+ font-size: 10px;
247
+ line-height: 12px;
202
248
  text-overflow: ellipsis;
203
249
  white-space: nowrap;
204
250
  }
205
251
  ion-icon {
206
252
  margin: 0;
207
- font-size: 22px;
253
+ font-size: 28px;
208
254
  }
209
255
  }
210
256
  }
@@ -212,9 +258,8 @@ ion-app.ios-theme-vertical-bars
212
258
  ion-app.ios-theme-vertical-bars.ios-theme-vertical-bars-left
213
259
  ion-tabs:not(:where(ion-menu *, ion-modal *, ion-popover *))
214
260
  > 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
261
  right: auto;
217
- left: max(4px, calc((var(--ios-theme-side-safe-area) - var(--ios-theme-side-tab-bar-width)) / 2));
262
+ left: calc(24px + (48px - var(--ios-theme-side-tab-bar-width)) / 2);
218
263
  }
219
264
 
220
265
  .ios-theme-native-ui-shell-prehidden {
@@ -251,8 +296,11 @@ html.ios-theme-native-ui-shell-prehide
251
296
  visibility: hidden !important;
252
297
  }
253
298
 
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 *))
299
+ ion-app.ios-theme-vertical-bars.ios-theme-vertical-bars-toolbar-ready,
300
+ ion-app.ios-theme-vertical-bars ion-modal.ios-theme-vertical-bars-modal.ios-theme-vertical-bars-toolbar-ready {
301
+ ion-toolbar:is(.ios, .md):not(.ios-theme-disabled, .ios26-disabled):not(
302
+ :where(ion-menu *, ion-modal:not(.ios-theme-vertical-bars-modal) *, ion-popover *)
303
+ )
256
304
  ion-back-button:is(.ios, .md)[data-native-ui-shell]:not(
257
305
  .ios-theme-disabled,
258
306
  .ios26-disabled,
@@ -260,7 +308,9 @@ ion-app.ios-theme-vertical-bars.ios-theme-vertical-bars-toolbar-ready {
260
308
  ) {
261
309
  display: none;
262
310
  }
263
- ion-toolbar:is(.ios, .md):not(.ios-theme-disabled, .ios26-disabled):not(:where(ion-menu *, ion-modal *, ion-popover *))
311
+ ion-toolbar:is(.ios, .md):not(.ios-theme-disabled, .ios26-disabled):not(
312
+ :where(ion-menu *, ion-modal:not(.ios-theme-vertical-bars-modal) *, ion-popover *)
313
+ )
264
314
  ion-buttons:is(.ios, .md)
265
315
  > :is(ion-button, ion-menu-button):is(.ios, .md)[data-native-ui-shell]:not(
266
316
  .ios-theme-disabled,
@@ -269,7 +319,9 @@ ion-app.ios-theme-vertical-bars.ios-theme-vertical-bars-toolbar-ready {
269
319
  ) {
270
320
  display: none;
271
321
  }
272
- ion-toolbar:is(.ios, .md):not(.ios-theme-disabled, .ios26-disabled):not(:where(ion-menu *, ion-modal *, ion-popover *))
322
+ ion-toolbar:is(.ios, .md):not(.ios-theme-disabled, .ios26-disabled):not(
323
+ :where(ion-menu *, ion-modal:not(.ios-theme-vertical-bars-modal) *, ion-popover *)
324
+ )
273
325
  ion-buttons:is(.ios, .md):has(> :is(ion-back-button, ion-button, ion-menu-button)[data-native-ui-shell]):not(
274
326
  :has(> :is(ion-back-button, ion-button, ion-menu-button):not([data-native-ui-shell]))
275
327
  ) {
@@ -278,7 +330,7 @@ ion-app.ios-theme-vertical-bars.ios-theme-vertical-bars-toolbar-ready {
278
330
  > ion-back-button.ios-theme-vertical-bars-back-button-projection {
279
331
  position: fixed;
280
332
  z-index: 1001;
281
- top: var(--ios-theme-vertical-bars-toolbar-top, 220px);
333
+ top: var(--ios-theme-vertical-bars-modal-toolbar-top, var(--ios-theme-vertical-bars-toolbar-top, 220px));
282
334
  right: max(4px, calc((var(--ios-theme-vertical-bars-safe-area-right-resolved) - 46px) / 2));
283
335
  display: block;
284
336
  width: 46px;
@@ -288,7 +340,10 @@ ion-app.ios-theme-vertical-bars.ios-theme-vertical-bars-toolbar-ready {
288
340
  > :is(.ios, .md).ios-theme-vertical-bars-toolbar-projection {
289
341
  position: fixed;
290
342
  z-index: 1001;
291
- top: calc(var(--ios-theme-vertical-bars-toolbar-top, 220px) + var(--ios-theme-vertical-bars-toolbar-offset, 0px));
343
+ top: calc(
344
+ var(--ios-theme-vertical-bars-modal-toolbar-top, var(--ios-theme-vertical-bars-toolbar-top, 220px)) +
345
+ var(--ios-theme-vertical-bars-toolbar-offset, 0px)
346
+ );
292
347
  right: max(4px, calc((var(--ios-theme-vertical-bars-safe-area-right-resolved) - 46px) / 2));
293
348
  width: 46px;
294
349
  min-width: 46px;
@@ -307,10 +362,12 @@ ion-app.ios-theme-vertical-bars.ios-theme-vertical-bars-toolbar-ready {
307
362
  border-radius: 50%;
308
363
  }
309
364
  > 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);
365
+ &:not(.button-solid):not(.button-outline) {
366
+ --background: rgba(var(--ios-theme-glass-background-rgb, var(--ios26-glass-background-rgb)), 0.72);
367
+ }
311
368
  --border-radius: 50%;
312
369
  @include glass.light-shadow('--box-shadow');
313
- &::part(native) {
370
+ &:not(.button-outline)::part(native) {
314
371
  @include api.glass-control-background($include-background: false, $shadow: var(--box-shadow));
315
372
  }
316
373
  }
@@ -333,10 +390,20 @@ ion-app.ios-theme-vertical-bars.ios-theme-vertical-bars-toolbar-ready {
333
390
  }
334
391
  }
335
392
 
336
- ion-app.ios-theme-vertical-bars.ios-theme-vertical-bars-left {
393
+ ion-app.ios-theme-vertical-bars.ios-theme-vertical-bars-left,
394
+ ion-app.ios-theme-vertical-bars.ios-theme-vertical-bars-left ion-modal.ios-theme-vertical-bars-modal {
337
395
  > ion-back-button.ios-theme-vertical-bars-back-button-projection,
338
396
  > :is(.ios, .md).ios-theme-vertical-bars-toolbar-projection {
339
397
  right: auto;
340
398
  left: max(4px, calc((var(--ios-theme-vertical-bars-safe-area-left-resolved) - 46px) / 2));
341
399
  }
342
400
  }
401
+
402
+ // The lens is attached to body, outside the rail selectors.
403
+ ion-tab-button.ios.ion-cloned-element.ios27-vertical-tab-effect {
404
+ border-radius: 32px;
405
+ &::part(native) {
406
+ min-height: 0;
407
+ height: 100%;
408
+ }
409
+ }
@@ -2,7 +2,7 @@ import { createIosTransitionAnimation, shadow } from '@rdlabo/ionic-theme-utils'
2
2
  import type { Animation } from '@ionic/core';
3
3
  import type { TransitionOptions } from './index';
4
4
  import { getIonPageElement } from './index';
5
- import { connectNativeUIShellTransition } from '../native-integration';
5
+ import { withNativeUIShellTransition } from '../native-integration/transition';
6
6
 
7
7
  export { shadow };
8
8
 
@@ -13,7 +13,6 @@ export interface IosTransitionConfig {
13
13
  const transitionConfig = {
14
14
  offLeftPercent: 30,
15
15
  getIonPageElement,
16
- connectNativeUIShellTransition,
17
16
  shouldAnimateFixedBackButton: (navEl: HTMLElement) => !navEl.closest('ion-app.ios-theme-vertical-bars'),
18
17
  radius: 0,
19
18
  };
@@ -22,5 +21,6 @@ export const setConfig = (config: Partial<IosTransitionConfig>): void => {
22
21
  Object.assign(transitionConfig, config);
23
22
  };
24
23
 
25
- export const iosTransitionAnimation: (navEl: HTMLElement, opts: TransitionOptions) => Animation =
26
- createIosTransitionAnimation<TransitionOptions>(transitionConfig);
24
+ export const iosTransitionAnimation: (navEl: HTMLElement, opts: TransitionOptions) => Animation = withNativeUIShellTransition(
25
+ createIosTransitionAnimation<TransitionOptions>(transitionConfig),
26
+ );
@@ -1,3 +1,3 @@
1
1
  export { enableVerticalControlArea, setVerticalControlAreaPlacement, IonicNativeUIShell } from './native';
2
- export { HingeStatus } from './native';
3
- export type { DeviceLayout, NativeUIShellStatus, NativeUIShellSuspension, VerticalBarEdge, VerticalControlAreaHandle } from './native';
2
+ export { withNativeUIShellTransition } from './native-integration/transition';
3
+ export type { NativeUIShellStatus, NativeUIShellSuspension, VerticalBarEdge, VerticalControlAreaHandle } from './native';