@rdlabo/ionic-theme-ios27 1.2.0 → 1.2.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.
Files changed (69) hide show
  1. package/dist/css/components/ion-button.css +1 -1
  2. package/dist/css/components/ion-toolbar.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/native/components/index.js +2 -2
  6. package/dist/native/components/index.js.map +1 -1
  7. package/dist/native/prehide.d.ts.map +1 -1
  8. package/dist/native/prehide.js +6 -2
  9. package/dist/native/prehide.js.map +1 -1
  10. package/dist/native/runtime.d.ts.map +1 -1
  11. package/dist/native/runtime.js +4 -3
  12. package/dist/native/runtime.js.map +1 -1
  13. package/dist/native/shared/dom.d.ts +2 -0
  14. package/dist/native/shared/dom.d.ts.map +1 -1
  15. package/dist/native/shared/dom.js +5 -1
  16. package/dist/native/shared/dom.js.map +1 -1
  17. package/dist/native/vertical-bars-web.d.ts.map +1 -1
  18. package/dist/native/vertical-bars-web.js +4 -2
  19. package/dist/native/vertical-bars-web.js.map +1 -1
  20. package/dist/native-integration/index.d.ts +1 -1
  21. package/dist/native-integration/index.js.map +1 -1
  22. package/dist/native-integration/transition.d.ts +1 -1
  23. package/dist/native-integration/transition.js.map +1 -1
  24. package/dist/sheets-of-glass/animations.d.ts +1 -1
  25. package/dist/sheets-of-glass/animations.js +1 -1
  26. package/dist/sheets-of-glass/animations.js.map +1 -1
  27. package/dist/sheets-of-glass/index.js +1 -1
  28. package/dist/sheets-of-glass/index.js.map +1 -1
  29. package/dist/tab-bar-searchable/animations/enter.d.ts +1 -1
  30. package/dist/tab-bar-searchable/animations/enter.d.ts.map +1 -1
  31. package/dist/tab-bar-searchable/animations/enter.js +1 -1
  32. package/dist/tab-bar-searchable/animations/enter.js.map +1 -1
  33. package/dist/tab-bar-searchable/animations/leave.d.ts +1 -1
  34. package/dist/tab-bar-searchable/animations/leave.d.ts.map +1 -1
  35. package/dist/tab-bar-searchable/animations/leave.js +1 -1
  36. package/dist/tab-bar-searchable/animations/leave.js.map +1 -1
  37. package/dist/tab-bar-searchable/index.js +1 -1
  38. package/dist/tab-bar-searchable/index.js.map +1 -1
  39. package/dist/transition/index.d.ts +1 -1
  40. package/dist/transition/index.js +1 -1
  41. package/dist/transition/index.js.map +1 -1
  42. package/dist/transition/ios.transition.d.ts +1 -1
  43. package/dist/transition/ios.transition.js.map +1 -1
  44. package/dist/utils.d.ts +1 -1
  45. package/dist/utils.js.map +1 -1
  46. package/docs/native-ui-shell.md +2 -2
  47. package/docs/vertical-bars.md +3 -1
  48. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellSearchController.swift +65 -91
  49. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellTabBar.swift +9 -3
  50. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellVerticalBars.swift +4 -2
  51. package/package.json +1 -1
  52. package/src/native/components/index.ts +3 -2
  53. package/src/native/prehide.ts +6 -1
  54. package/src/native/runtime.ts +5 -1
  55. package/src/native/shared/dom.ts +14 -0
  56. package/src/native/vertical-bars-web.ts +11 -1
  57. package/src/native-integration/index.ts +1 -1
  58. package/src/native-integration/transition.ts +1 -1
  59. package/src/sheets-of-glass/animations.ts +2 -2
  60. package/src/sheets-of-glass/index.ts +2 -2
  61. package/src/styles/components/ion-button.scss +47 -0
  62. package/src/styles/components/ion-toolbar.scss +62 -7
  63. package/src/styles/vertical-bars.scss +23 -0
  64. package/src/tab-bar-searchable/animations/enter.ts +2 -1
  65. package/src/tab-bar-searchable/animations/leave.ts +2 -1
  66. package/src/tab-bar-searchable/index.ts +1 -1
  67. package/src/transition/index.ts +3 -3
  68. package/src/transition/ios.transition.ts +1 -1
  69. package/src/utils.ts +1 -1
@@ -15,6 +15,8 @@ import {
15
15
  preferredVerticalBarsBack,
16
16
  verticalBarsOwned,
17
17
  marker,
18
+ syncToolbarText,
19
+ toolbarTextMarker,
18
20
  prehideOnlyMutation,
19
21
  prehiddenClass,
20
22
  unprojected,
@@ -271,6 +273,7 @@ export const createVerticalBarsWebProjection = (
271
273
  };
272
274
  const performUpdate = () => {
273
275
  frame = 0;
276
+ syncToolbarText(doc);
274
277
  const currentRoot = verticalBarsRoot();
275
278
  if (observingVerticalBars !== !!currentRoot) {
276
279
  observingVerticalBars = !!currentRoot;
@@ -278,6 +281,7 @@ export const createVerticalBarsWebProjection = (
278
281
  observer.observe(doc.documentElement, {
279
282
  subtree: true,
280
283
  childList: true,
284
+ characterData: observingVerticalBars,
281
285
  attributes: true,
282
286
  attributeOldValue: true,
283
287
  attributeFilter: observingVerticalBars ? undefined : ['class'],
@@ -335,7 +339,13 @@ export const createVerticalBarsWebProjection = (
335
339
  );
336
340
  if (
337
341
  (observingVerticalBars &&
338
- records.some((record) => record.attributeName !== marker && !prehideOnlyMutation(record) && !insideProjection(record.target))) ||
342
+ records.some(
343
+ (record) =>
344
+ record.attributeName !== marker &&
345
+ record.attributeName !== toolbarTextMarker &&
346
+ !prehideOnlyMutation(record) &&
347
+ !insideProjection(record.target),
348
+ )) ||
339
349
  (!observingVerticalBars && verticalBarsChanged && records.some((record) => !prehideOnlyMutation(record)))
340
350
  )
341
351
  schedule();
@@ -1,5 +1,5 @@
1
1
  /// <reference lib="es2021.weakref" />
2
- import type { Animation } from '@ionic/core';
2
+ import type { Animation } from '@ionic/core/components';
3
3
 
4
4
  export interface NativeUIShellIntegration {
5
5
  suspend(scopes: HTMLElement[]): Promise<(canceled?: boolean) => void>;
@@ -1,4 +1,4 @@
1
- import type { AnimationBuilder } from '@ionic/core';
1
+ import type { AnimationBuilder } from '@ionic/core/components';
2
2
  import { connectNativeUIShellTransition } from './index';
3
3
 
4
4
  /**
@@ -1,6 +1,6 @@
1
- import type { Animation, AnimationKeyFrames } from '@ionic/core';
1
+ import type { Animation, AnimationKeyFrames, GestureDetail } from '@ionic/core/components';
2
2
  import { AnimationPosition, EffectScales } from './interfaces';
3
- import { createAnimation, GestureDetail } from '@ionic/core';
3
+ import { createAnimation } from '@ionic/core/components/index.js';
4
4
  import { getStep } from '../utils';
5
5
 
6
6
  // Work in main/cross-axis coordinates so native horizontal samples also drive the rail.
@@ -1,6 +1,6 @@
1
1
  import { AnimationPosition, EffectScales, registeredEffect } from './interfaces';
2
- import { createAnimation, createGesture } from '@ionic/core';
3
- import type { Animation, Gesture, GestureDetail } from '@ionic/core';
2
+ import { createAnimation, createGesture } from '@ionic/core/components/index.js';
3
+ import type { Animation, Gesture, GestureDetail } from '@ionic/core/components';
4
4
  import { changeSelectedElement, cloneElement, getStep } from '../utils';
5
5
  import { isNativeUIShell } from '../native-integration';
6
6
  import {
@@ -436,3 +436,50 @@ ion-button.ios:not(.ios-theme-disabled, .ios26-disabled) {
436
436
  ion-back-button.ios:not(.ios-theme-disabled, .ios26-disabled) {
437
437
  @include theme-button($is-back-button: true);
438
438
  }
439
+
440
+ // Toolbar button typography and spacing follow system toolbar controls.
441
+ ion-toolbar.ios:not(.ios-theme-disabled, .ios26-disabled) {
442
+ ion-button.ios:not(.ios-theme-disabled, .ios26-disabled):not(.button-small, .button-large):not(
443
+ :where(ion-buttons.ios-theme-disabled *, ion-buttons.ios26-disabled *)
444
+ ) {
445
+ font-size: 1rem;
446
+ font-weight: 400;
447
+ // TODO(v2.0.0): Remove .back-button-has-icon-only; Ionic only adds it to ion-back-button.
448
+ &:not(.button-has-icon-only, .back-button-has-icon-only) {
449
+ --padding-start: 16px;
450
+ --padding-end: 16px;
451
+ }
452
+ }
453
+
454
+ ion-buttons.ios:not(.ios-theme-disabled, .ios26-disabled):has(ion-button ~ ion-button):not(
455
+ :has(
456
+ ion-back-button,
457
+ ion-button:not(.button-clear),
458
+ ion-menu-button,
459
+ .ios-theme-disabled,
460
+ .ios26-disabled,
461
+ .button-small,
462
+ .button-large
463
+ )
464
+ ) {
465
+ height: 44px;
466
+ padding-inline: 3.5px;
467
+ // SwiftUI toolbar items share glass with 16pt between their inner controls.
468
+ gap: 16px;
469
+
470
+ ion-button.ios:not(.ios-theme-disabled, .ios26-disabled).button-clear {
471
+ // Keep the action's hit area through the group's height, while the
472
+ // inner plain control follows the system's 36px visual layout.
473
+ height: 100%;
474
+ display: inline-flex;
475
+ align-items: center;
476
+ --padding-start: 7px;
477
+ --padding-end: 7px;
478
+ &::part(native) {
479
+ width: auto;
480
+ height: 36px;
481
+ min-height: 36px;
482
+ }
483
+ }
484
+ }
485
+ }
@@ -13,22 +13,27 @@ ion-header.ios:not(.ios-theme-disabled, .ios26-disabled) ion-toolbar:not(.ios-th
13
13
  ion-modal.ios:not(.ios-theme-disabled, .ios26-disabled).modal-card,
14
14
  ion-modal.ios:not(.ios-theme-disabled, .ios26-disabled).modal-sheet {
15
15
  ion-header.ios:not(.ios-theme-disabled, .ios26-disabled) ion-toolbar.ios:not(.ios-theme-disabled, .ios26-disabled):first-of-type {
16
- padding-top: 0;
16
+ // The system's modal safe area places the 44px control 16px below the
17
+ // content edge, with or without a sheet drag indicator.
18
+ padding-top: 6px;
17
19
  padding-bottom: 0;
18
20
  --min-height: 64px;
19
- --padding-start: 3px;
20
- --padding-end: 3px;
21
21
  }
22
22
  }
23
23
 
24
24
  @media (min-width: 768px) and (min-height: 600px) {
25
- ion-modal.ios:not(.ios-theme-disabled, .ios26-disabled) {
25
+ ion-modal.ios:not(.ios-theme-disabled, .ios26-disabled):not(.modal-sheet) {
26
26
  ion-header.ios:not(.ios-theme-disabled, .ios26-disabled) ion-toolbar.ios:not(.ios-theme-disabled, .ios26-disabled):first-of-type {
27
- padding-top: 0;
27
+ // Center a 44px control at 10px, retaining the safe area in fullscreen modals.
28
+ padding-top: max(calc(var(--ion-safe-area-top, 0px) - 10px), 0px);
28
29
  padding-bottom: 0;
29
30
  --min-height: 64px;
30
- --padding-start: 3px;
31
- --padding-end: 3px;
31
+ }
32
+ }
33
+ // Ionic keeps the root safe area in card modals, including centered dialogs.
34
+ ion-modal.ios:not(.ios-theme-disabled, .ios26-disabled).modal-card {
35
+ ion-header.ios:not(.ios-theme-disabled, .ios26-disabled) ion-toolbar.ios:not(.ios-theme-disabled, .ios26-disabled):first-of-type {
36
+ padding-top: 0;
32
37
  }
33
38
  }
34
39
  }
@@ -106,4 +111,54 @@ ion-toolbar.ios:not(.ios-theme-disabled, .ios26-disabled) {
106
111
  ion-buttons > ion-back-button {
107
112
  margin-inline: 0;
108
113
  }
114
+
115
+ // Keep the outer inset for individual actions too. Adjacent actions in the
116
+ // same slot share a 12px gap rather than adding two outer margins together.
117
+ > ion-button:not(.ios-theme-disabled, .ios26-disabled) {
118
+ margin-inline: 12px;
119
+ }
120
+ // Rail sources leave toolbar layout; ordinary native projection retains it.
121
+ $visible: ':not([hidden], .ios-theme-native-ui-shell-prehidden, [data-native-ui-shell][data-native-ui-shell-vertical-bars], ion-menu-button.menu-button-hidden, ion-back-button:not(.show-back-button))';
122
+ $button: ':is(ion-button, ion-back-button)#{$visible}';
123
+ $group: 'ion-buttons#{$visible}';
124
+ $action: ':is(#{$button}, #{$group}:has(> #{$visible}))';
125
+ $optOut: ':is(.ios-theme-disabled, .ios26-disabled)';
126
+
127
+ > ion-buttons:not(:has(> #{$visible})) {
128
+ display: none;
129
+ }
130
+
131
+ @each $slot in start, end, primary, secondary {
132
+ // Keep standalone margins when the slot includes a visible opted-out action.
133
+ &:not(.toolbar-searchbar):not(:has(> #{$button}#{$optOut}[slot='#{$slot}'], > #{$group}#{$optOut}[slot='#{$slot}'] > #{$visible})) {
134
+ > #{$action}[slot='#{$slot}'] {
135
+ margin-inline: 6px;
136
+ }
137
+ > :nth-child(1 of #{$action}[slot='#{$slot}']) {
138
+ margin-inline-start: 12px;
139
+ }
140
+ > :nth-last-child(1 of #{$action}[slot='#{$slot}']) {
141
+ margin-inline-end: 12px;
142
+ }
143
+ }
144
+ }
145
+ // Ionic orders these slots together, independently of their DOM order.
146
+ @each $first, $second in (start, secondary), (primary, end) {
147
+ &:not(.toolbar-searchbar):has(> #{$button}[slot='#{$first}'], > #{$group}[slot='#{$first}'] > #{$visible}):has(
148
+ > #{$button}[slot='#{$second}'],
149
+ > #{$group}[slot='#{$second}'] > #{$visible}
150
+ ):not(
151
+ :has(
152
+ > #{$button}#{$optOut}:is([slot='#{$first}'], [slot='#{$second}']),
153
+ > #{$group}#{$optOut}:is([slot='#{$first}'], [slot='#{$second}']) > #{$visible}
154
+ )
155
+ ) {
156
+ > :nth-last-child(1 of #{$action}[slot='#{$first}']) {
157
+ margin-inline-end: 6px;
158
+ }
159
+ > :nth-child(1 of #{$action}[slot='#{$second}']) {
160
+ margin-inline-start: 6px;
161
+ }
162
+ }
163
+ }
109
164
  }
@@ -92,8 +92,31 @@ ion-app.ios-theme-vertical-bars
92
92
  --ion-safe-area-left: 0px;
93
93
  --ion-safe-area-right: 0px;
94
94
 
95
+ // Reserve the final layout before a page transition, using the same prehide
96
+ // decision as its rail controls. Horizontal native projection keeps its space.
97
+ // Titles, custom content and horizontal-only controls keep the toolbar visible.
98
+ &:not([data-native-ui-shell-toolbar-text]):has(
99
+ > .ios-theme-native-ui-shell-prehidden,
100
+ > ion-buttons > .ios-theme-native-ui-shell-prehidden
101
+ ):not(:has(> :not(ion-buttons, .ios-theme-native-ui-shell-prehidden))):not(
102
+ :has(> ion-buttons:not(.ios-theme-native-ui-shell-prehidden) > :not(.ios-theme-native-ui-shell-prehidden))
103
+ ) {
104
+ display: none;
105
+ }
106
+
95
107
  // Collapse headers already sit inside the padded ion-content scroll area.
96
108
  &:not(:where(ion-header.header-collapse-condense *, ion-footer.footer-collapse-fade *)) {
109
+ // Ionic's centered iOS title is absolute and does not follow container padding.
110
+ // Center it within the same foreground width as the page content.
111
+ > ion-title.ios.title-default:not(.ios-theme-disabled, .ios26-disabled) {
112
+ inset-inline-start: var(--ios-theme-vertical-bars-safe-area-left-resolved);
113
+ width: calc(100% - var(--ios-theme-vertical-bars-safe-area-left-resolved) - var(--ios-theme-vertical-bars-safe-area-right-resolved));
114
+
115
+ &:dir(rtl) {
116
+ inset-inline-start: var(--ios-theme-vertical-bars-safe-area-right-resolved);
117
+ }
118
+ }
119
+
97
120
  &::part(container) {
98
121
  padding-inline-start: calc(var(--padding-start, 0px) + var(--ios-theme-vertical-bars-safe-area-left-resolved));
99
122
  padding-inline-end: calc(var(--padding-end, 0px) + var(--ios-theme-vertical-bars-safe-area-right-resolved));
@@ -1,4 +1,5 @@
1
- import { Animation, createAnimation } from '@ionic/core';
1
+ import type { Animation } from '@ionic/core/components';
2
+ import { createAnimation } from '@ionic/core/components/index.js';
2
3
  import { ANIMATION_DELAY_CLOSE_BUTTONS, OPACITY_TRANSITION, type ElementReferences, type ElementSizes } from '@rdlabo/ionic-theme-utils';
3
4
  import { cloneElement } from '../../utils';
4
5
 
@@ -1,4 +1,5 @@
1
- import { Animation, createAnimation } from '@ionic/core';
1
+ import type { Animation } from '@ionic/core/components';
2
+ import { createAnimation } from '@ionic/core/components/index.js';
2
3
  import { ANIMATION_DELAY_CLOSE_BUTTONS, OPACITY_TRANSITION, type ElementReferences, type ElementSizes } from '@rdlabo/ionic-theme-utils';
3
4
  import { cloneElement } from '../../utils';
4
5
 
@@ -1,4 +1,4 @@
1
- import { createAnimation } from '@ionic/core';
1
+ import { createAnimation } from '@ionic/core/components/index.js';
2
2
  import { addSearchDismissButton } from './dismiss-button';
3
3
  import {
4
4
  ANIMATION_DELAY_BASE,
@@ -1,9 +1,9 @@
1
1
  import { config } from '../utils';
2
2
  import { Build, writeTask } from '@stencil/core';
3
3
 
4
- import { LIFECYCLE_DID_ENTER, LIFECYCLE_DID_LEAVE, LIFECYCLE_WILL_ENTER, LIFECYCLE_WILL_LEAVE } from '@ionic/core';
5
- import type { NavOptions, NavDirection } from '@ionic/core';
6
- import type { Animation, AnimationBuilder } from '@ionic/core';
4
+ import { LIFECYCLE_DID_ENTER, LIFECYCLE_DID_LEAVE, LIFECYCLE_WILL_ENTER, LIFECYCLE_WILL_LEAVE } from '@ionic/core/components/index.js';
5
+ import type { NavOptions, NavDirection } from '@ionic/core/components';
6
+ import type { Animation, AnimationBuilder } from '@ionic/core/components';
7
7
  import { createFocusController } from '../focus-controller';
8
8
  import { raf } from '../utils';
9
9
 
@@ -1,5 +1,5 @@
1
1
  import { createIosTransitionAnimation, shadow } from '@rdlabo/ionic-theme-utils';
2
- import type { Animation } from '@ionic/core';
2
+ import type { Animation } from '@ionic/core/components';
3
3
  import type { TransitionOptions } from './index';
4
4
  import { getIonPageElement } from './index';
5
5
  import { withNativeUIShellTransition } from '../native-integration/transition';
package/src/utils.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { AnimationPosition } from './sheets-of-glass/interfaces';
2
- import { IonicConfig } from '@ionic/core';
2
+ import type { IonicConfig } from '@ionic/core/components';
3
3
 
4
4
  declare const __zone_symbol__requestAnimationFrame: ((callback: FrameRequestCallback) => number) | undefined;
5
5
  declare const requestAnimationFrame: ((callback: FrameRequestCallback) => number) | undefined;