@rdlabo/ionic-theme-ios27 1.2.1 → 2.0.0-beta.pr303.shaf4f5ca16816f

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 (195) hide show
  1. package/README.md +24 -280
  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/index.d.ts +3 -2
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +3 -2
  8. package/dist/index.js.map +1 -1
  9. package/dist/layout/index.d.ts +18 -0
  10. package/dist/layout/index.d.ts.map +1 -0
  11. package/dist/layout/index.js +26 -0
  12. package/dist/layout/index.js.map +1 -0
  13. package/dist/segment/index.js +3 -3
  14. package/dist/segment/index.js.map +1 -1
  15. package/dist/sheets-of-glass/index.js +7 -7
  16. package/dist/sheets-of-glass/index.js.map +1 -1
  17. package/dist/tab-bar-searchable/dismiss-button.js +53 -3
  18. package/dist/tab-bar-searchable/dismiss-button.js.map +1 -1
  19. package/dist/tab-bar-searchable/index.js +18 -3
  20. package/dist/tab-bar-searchable/index.js.map +1 -1
  21. package/dist/transition/ios.transition.d.ts.map +1 -1
  22. package/dist/transition/ios.transition.js +7 -4
  23. package/dist/transition/ios.transition.js.map +1 -1
  24. package/dist/transition/native-ui.d.ts +6 -0
  25. package/dist/transition/native-ui.d.ts.map +1 -0
  26. package/dist/{native-integration/index.js → transition/native-ui.js} +16 -30
  27. package/dist/transition/native-ui.js.map +1 -0
  28. package/docs/adaptive-themes.md +151 -0
  29. package/docs/add-native-ui.md +53 -0
  30. package/docs/animation.md +1 -1
  31. package/docs/eslint.md +1 -1
  32. package/docs/features.md +3 -3
  33. package/docs/iphone-duo-with-original-theme.md +40 -28
  34. package/docs/iphone-duo.md +29 -32
  35. package/docs/migration.md +12 -24
  36. package/docs/setup.md +82 -0
  37. package/docs/special-markup.md +5 -5
  38. package/docs/using-ion-item-group.md +2 -2
  39. package/docs/vertical-bars-layout.md +47 -0
  40. package/package.json +12 -42
  41. package/src/index.ts +3 -1
  42. package/src/layout/index.ts +29 -0
  43. package/src/segment/index.ts +3 -3
  44. package/src/sheets-of-glass/index.ts +7 -7
  45. package/src/styles/components/ion-toolbar.scss +1 -1
  46. package/src/styles/vertical-bars.scss +15 -18
  47. package/src/tab-bar-searchable/dismiss-button.ts +73 -4
  48. package/src/tab-bar-searchable/index.ts +20 -3
  49. package/src/transition/ios.transition.ts +7 -4
  50. package/src/{native-integration/index.ts → transition/native-ui.ts} +17 -46
  51. package/Package.swift +0 -21
  52. package/dist/_virtual/_rolldown/runtime.js +0 -13
  53. package/dist/native/components/index.d.ts +0 -19
  54. package/dist/native/components/index.d.ts.map +0 -1
  55. package/dist/native/components/index.js +0 -40
  56. package/dist/native/components/index.js.map +0 -1
  57. package/dist/native/components/ion-back-button.d.ts +0 -9
  58. package/dist/native/components/ion-back-button.d.ts.map +0 -1
  59. package/dist/native/components/ion-back-button.js +0 -20
  60. package/dist/native/components/ion-back-button.js.map +0 -1
  61. package/dist/native/components/ion-button.d.ts +0 -10
  62. package/dist/native/components/ion-button.d.ts.map +0 -1
  63. package/dist/native/components/ion-button.js +0 -23
  64. package/dist/native/components/ion-button.js.map +0 -1
  65. package/dist/native/components/ion-buttons.d.ts +0 -11
  66. package/dist/native/components/ion-buttons.d.ts.map +0 -1
  67. package/dist/native/components/ion-buttons.js +0 -30
  68. package/dist/native/components/ion-buttons.js.map +0 -1
  69. package/dist/native/components/ion-fab.d.ts +0 -10
  70. package/dist/native/components/ion-fab.d.ts.map +0 -1
  71. package/dist/native/components/ion-fab.js +0 -132
  72. package/dist/native/components/ion-fab.js.map +0 -1
  73. package/dist/native/components/ion-menu-button.d.ts +0 -13
  74. package/dist/native/components/ion-menu-button.d.ts.map +0 -1
  75. package/dist/native/components/ion-menu-button.js +0 -31
  76. package/dist/native/components/ion-menu-button.js.map +0 -1
  77. package/dist/native/components/ion-segment.d.ts +0 -11
  78. package/dist/native/components/ion-segment.d.ts.map +0 -1
  79. package/dist/native/components/ion-segment.js +0 -30
  80. package/dist/native/components/ion-segment.js.map +0 -1
  81. package/dist/native/components/ion-tab-bar.d.ts +0 -11
  82. package/dist/native/components/ion-tab-bar.d.ts.map +0 -1
  83. package/dist/native/components/ion-tab-bar.js +0 -50
  84. package/dist/native/components/ion-tab-bar.js.map +0 -1
  85. package/dist/native/components/searchable-tabs.d.ts +0 -20
  86. package/dist/native/components/searchable-tabs.d.ts.map +0 -1
  87. package/dist/native/components/searchable-tabs.js +0 -383
  88. package/dist/native/components/searchable-tabs.js.map +0 -1
  89. package/dist/native/definitions.d.ts +0 -152
  90. package/dist/native/definitions.d.ts.map +0 -1
  91. package/dist/native/definitions.js +0 -0
  92. package/dist/native/foldable/fold-state.d.ts +0 -5
  93. package/dist/native/foldable/fold-state.d.ts.map +0 -1
  94. package/dist/native/foldable/fold-state.js +0 -14
  95. package/dist/native/foldable/fold-state.js.map +0 -1
  96. package/dist/native/foldable/types.d.ts +0 -12
  97. package/dist/native/foldable/types.d.ts.map +0 -1
  98. package/dist/native/foldable/types.js +0 -0
  99. package/dist/native/index.d.ts +0 -11
  100. package/dist/native/index.d.ts.map +0 -1
  101. package/dist/native/index.js +0 -242
  102. package/dist/native/index.js.map +0 -1
  103. package/dist/native/prehide.d.ts +0 -7
  104. package/dist/native/prehide.d.ts.map +0 -1
  105. package/dist/native/prehide.js +0 -230
  106. package/dist/native/prehide.js.map +0 -1
  107. package/dist/native/runtime.d.ts +0 -5
  108. package/dist/native/runtime.d.ts.map +0 -1
  109. package/dist/native/runtime.js +0 -640
  110. package/dist/native/runtime.js.map +0 -1
  111. package/dist/native/shared/candidate.d.ts +0 -21
  112. package/dist/native/shared/candidate.d.ts.map +0 -1
  113. package/dist/native/shared/candidate.js +0 -98
  114. package/dist/native/shared/candidate.js.map +0 -1
  115. package/dist/native/shared/crossfade.d.ts +0 -10
  116. package/dist/native/shared/crossfade.d.ts.map +0 -1
  117. package/dist/native/shared/crossfade.js +0 -50
  118. package/dist/native/shared/crossfade.js.map +0 -1
  119. package/dist/native/shared/dom.d.ts +0 -46
  120. package/dist/native/shared/dom.d.ts.map +0 -1
  121. package/dist/native/shared/dom.js +0 -172
  122. package/dist/native/shared/dom.js.map +0 -1
  123. package/dist/native/shared/events.d.ts +0 -11
  124. package/dist/native/shared/events.d.ts.map +0 -1
  125. package/dist/native/shared/events.js +0 -13
  126. package/dist/native/shared/events.js.map +0 -1
  127. package/dist/native/shared/icons.d.ts +0 -8
  128. package/dist/native/shared/icons.d.ts.map +0 -1
  129. package/dist/native/shared/icons.js +0 -61
  130. package/dist/native/shared/icons.js.map +0 -1
  131. package/dist/native/shared/modal.d.ts +0 -10
  132. package/dist/native/shared/modal.d.ts.map +0 -1
  133. package/dist/native/shared/modal.js +0 -160
  134. package/dist/native/shared/modal.js.map +0 -1
  135. package/dist/native/vertical-bars-web-search.d.ts +0 -9
  136. package/dist/native/vertical-bars-web-search.d.ts.map +0 -1
  137. package/dist/native/vertical-bars-web-search.js +0 -84
  138. package/dist/native/vertical-bars-web-search.js.map +0 -1
  139. package/dist/native/vertical-bars-web.d.ts +0 -5
  140. package/dist/native/vertical-bars-web.d.ts.map +0 -1
  141. package/dist/native/vertical-bars-web.js +0 -341
  142. package/dist/native/vertical-bars-web.js.map +0 -1
  143. package/dist/native-integration/index.d.ts +0 -23
  144. package/dist/native-integration/index.d.ts.map +0 -1
  145. package/dist/native-integration/index.js.map +0 -1
  146. package/dist/native-integration/transition.d.ts +0 -5
  147. package/dist/native-integration/transition.d.ts.map +0 -1
  148. package/dist/native-integration/transition.js +0 -16
  149. package/dist/native-integration/transition.js.map +0 -1
  150. package/dist/vertical-bars.d.ts +0 -5
  151. package/dist/vertical-bars.js +0 -4
  152. package/docs/e2e-testing.md +0 -51
  153. package/docs/feedback.md +0 -65
  154. package/docs/native-ui-shell.md +0 -285
  155. package/docs/vertical-bars.md +0 -285
  156. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellButton.swift +0 -71
  157. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellButtons.swift +0 -24
  158. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellComponents.swift +0 -18
  159. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellFab.swift +0 -83
  160. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellSearchController.swift +0 -432
  161. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellSegment.swift +0 -91
  162. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellTabBar.swift +0 -221
  163. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellVerticalBars.swift +0 -437
  164. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellVerticalSearch.swift +0 -118
  165. package/ios/Sources/IonicNativeUIShellPlugin/IonicNativeUIShellPlugin.swift +0 -396
  166. package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellCrossfade.swift +0 -35
  167. package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellHost.swift +0 -8
  168. package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellRendering.swift +0 -27
  169. package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellSnapshot.swift +0 -135
  170. package/ios/Tests/IonicNativeUIShellPluginTests/ShellSnapshotTests.swift +0 -650
  171. package/src/native/components/index.ts +0 -53
  172. package/src/native/components/ion-back-button.ts +0 -19
  173. package/src/native/components/ion-button.ts +0 -24
  174. package/src/native/components/ion-buttons.ts +0 -38
  175. package/src/native/components/ion-fab.ts +0 -151
  176. package/src/native/components/ion-menu-button.ts +0 -24
  177. package/src/native/components/ion-segment.ts +0 -27
  178. package/src/native/components/ion-tab-bar.ts +0 -50
  179. package/src/native/components/searchable-tabs.ts +0 -459
  180. package/src/native/definitions.ts +0 -191
  181. package/src/native/foldable/fold-state.ts +0 -9
  182. package/src/native/foldable/types.ts +0 -4
  183. package/src/native/index.ts +0 -281
  184. package/src/native/prehide.ts +0 -265
  185. package/src/native/runtime.ts +0 -781
  186. package/src/native/shared/candidate.ts +0 -150
  187. package/src/native/shared/crossfade.ts +0 -50
  188. package/src/native/shared/dom.ts +0 -286
  189. package/src/native/shared/events.ts +0 -8
  190. package/src/native/shared/icons.ts +0 -57
  191. package/src/native/shared/modal.ts +0 -157
  192. package/src/native/vertical-bars-web-search.ts +0 -80
  193. package/src/native/vertical-bars-web.ts +0 -411
  194. package/src/native-integration/transition.ts +0 -15
  195. package/src/vertical-bars.ts +0 -11
@@ -13,8 +13,58 @@ const addSearchDismissButton = (footer) => {
13
13
  button.fill = "default";
14
14
  button.type = "button";
15
15
  button.setAttribute("aria-label", original.getAttribute("aria-label") ?? "Close");
16
- button.addEventListener("pointerdown", (event) => event.preventDefault());
17
- button.addEventListener("click", () => original.click());
16
+ const doc = footer.ownerDocument;
17
+ let pointer;
18
+ let pointerTarget;
19
+ const actionTarget = (event) => {
20
+ const target = event.target instanceof Element ? event.target : void 0;
21
+ if (target?.closest("ion-button") === button) return button;
22
+ const clear = target?.closest("ion-searchbar .searchbar-clear-button");
23
+ return clear && footer.contains(clear) ? clear : void 0;
24
+ };
25
+ const syncEditing = () => {
26
+ if (!footer.contains(doc.activeElement)) footer.classList.remove("ios-theme-searchable-editing");
27
+ };
28
+ const releasePointer = () => {
29
+ pointer = void 0;
30
+ pointerTarget = void 0;
31
+ doc.removeEventListener("pointerup", finishPointer, true);
32
+ doc.removeEventListener("pointercancel", cancelPointer, true);
33
+ doc.defaultView?.removeEventListener("blur", releasePointer);
34
+ syncEditing();
35
+ };
36
+ const finishPointer = (event) => {
37
+ if (event.pointerId !== pointer || !pointerTarget) return;
38
+ const target = pointerTarget;
39
+ const rect = target.getBoundingClientRect();
40
+ const inside = rect.width > 0 && rect.height > 0 && event.clientX >= rect.left && event.clientX <= rect.right && event.clientY >= rect.top && event.clientY <= rect.bottom;
41
+ releasePointer();
42
+ if (inside) (target === button ? original : target).click();
43
+ };
44
+ const cancelPointer = (event) => {
45
+ if (event.pointerId === pointer) releasePointer();
46
+ };
47
+ footer.addEventListener("pointerdown", (event) => {
48
+ const target = actionTarget(event);
49
+ if (!target || !event.isPrimary || event.button !== 0) return;
50
+ event.preventDefault();
51
+ pointer = event.pointerId;
52
+ pointerTarget = target;
53
+ doc.addEventListener("pointerup", finishPointer, true);
54
+ doc.addEventListener("pointercancel", cancelPointer, true);
55
+ doc.defaultView?.addEventListener("blur", releasePointer);
56
+ }, true);
57
+ footer.addEventListener("click", (event) => {
58
+ if (event.detail !== 0 && actionTarget(event)) {
59
+ event.preventDefault();
60
+ event.stopPropagation();
61
+ }
62
+ }, true);
63
+ button.addEventListener("click", (event) => {
64
+ if (event.detail !== 0) return;
65
+ releasePointer();
66
+ original.click();
67
+ });
18
68
  const icon = footer.ownerDocument.createElement("ion-icon");
19
69
  icon.slot = "icon-only";
20
70
  icon.icon = close;
@@ -24,7 +74,7 @@ const addSearchDismissButton = (footer) => {
24
74
  if (event.target.closest("ion-searchbar")) footer.classList.add("ios-theme-searchable-editing");
25
75
  });
26
76
  footer.addEventListener("focusout", (event) => {
27
- if (!footer.contains(event.relatedTarget)) footer.classList.remove("ios-theme-searchable-editing");
77
+ if (pointer === void 0 && !footer.contains(event.relatedTarget)) footer.classList.remove("ios-theme-searchable-editing");
28
78
  });
29
79
  original.closest("ion-toolbar")?.append(buttons);
30
80
  };
@@ -1 +1 @@
1
- {"version":3,"file":"dismiss-button.js","names":[],"sources":["../../src/tab-bar-searchable/dismiss-button.ts"],"sourcesContent":["import { close } from 'ionicons/icons';\n\n/** Keep the application's original dismissal handler for the focused search layout. */\nexport const addSearchDismissButton = (footer: HTMLElement): void => {\n if (footer.querySelector('.ios-theme-searchable-dismiss')) return;\n const original = footer.querySelector<HTMLElement>('ion-buttons[slot=start] ion-button');\n if (!original) return;\n const buttons = footer.ownerDocument.createElement('ion-buttons');\n buttons.slot = 'end';\n // Remain hidden when the full theme is absent or opted out.\n buttons.style.display = 'none';\n buttons.classList.add('ios-theme-searchable-dismiss', 'ios-theme-horizontal-only');\n const button = footer.ownerDocument.createElement('ion-button');\n button.fill = 'default';\n button.type = 'button';\n button.setAttribute('aria-label', original.getAttribute('aria-label') ?? 'Close');\n // Keep the field focused until click; blur would hide this button before dismissal.\n button.addEventListener('pointerdown', (event) => event.preventDefault());\n button.addEventListener('click', () => original.click());\n const icon = footer.ownerDocument.createElement('ion-icon');\n icon.slot = 'icon-only';\n icon.icon = close;\n button.append(icon);\n buttons.append(button);\n // Keep the editing layout while focus moves from clear to the slotted close button.\n footer.addEventListener('focusin', (event) => {\n if ((event.target as HTMLElement).closest('ion-searchbar')) footer.classList.add('ios-theme-searchable-editing');\n });\n footer.addEventListener('focusout', (event) => {\n if (!footer.contains(event.relatedTarget as Node | null)) footer.classList.remove('ios-theme-searchable-editing');\n });\n original.closest('ion-toolbar')?.append(buttons);\n};\n"],"mappings":";;;AAGA,MAAa,0BAA0B,WAA8B;CACnE,IAAI,OAAO,cAAc,+BAA+B,GAAG;CAC3D,MAAM,WAAW,OAAO,cAA2B,oCAAoC;CACvF,IAAI,CAAC,UAAU;CACf,MAAM,UAAU,OAAO,cAAc,cAAc,aAAa;CAChE,QAAQ,OAAO;CAEf,QAAQ,MAAM,UAAU;CACxB,QAAQ,UAAU,IAAI,gCAAgC,2BAA2B;CACjF,MAAM,SAAS,OAAO,cAAc,cAAc,YAAY;CAC9D,OAAO,OAAO;CACd,OAAO,OAAO;CACd,OAAO,aAAa,cAAc,SAAS,aAAa,YAAY,KAAK,OAAO;CAEhF,OAAO,iBAAiB,gBAAgB,UAAU,MAAM,eAAe,CAAC;CACxE,OAAO,iBAAiB,eAAe,SAAS,MAAM,CAAC;CACvD,MAAM,OAAO,OAAO,cAAc,cAAc,UAAU;CAC1D,KAAK,OAAO;CACZ,KAAK,OAAO;CACZ,OAAO,OAAO,IAAI;CAClB,QAAQ,OAAO,MAAM;CAErB,OAAO,iBAAiB,YAAY,UAAU;EAC5C,IAAK,MAAM,OAAuB,QAAQ,eAAe,GAAG,OAAO,UAAU,IAAI,8BAA8B;CACjH,CAAC;CACD,OAAO,iBAAiB,aAAa,UAAU;EAC7C,IAAI,CAAC,OAAO,SAAS,MAAM,aAA4B,GAAG,OAAO,UAAU,OAAO,8BAA8B;CAClH,CAAC;CACD,SAAS,QAAQ,aAAa,CAAC,EAAE,OAAO,OAAO;AACjD"}
1
+ {"version":3,"file":"dismiss-button.js","names":[],"sources":["../../src/tab-bar-searchable/dismiss-button.ts"],"sourcesContent":["import { close } from 'ionicons/icons';\n\n/** Keep the application's original dismissal handler for the focused search layout. */\nexport const addSearchDismissButton = (footer: HTMLElement): void => {\n if (footer.querySelector('.ios-theme-searchable-dismiss')) return;\n const original = footer.querySelector<HTMLElement>('ion-buttons[slot=start] ion-button');\n if (!original) return;\n const buttons = footer.ownerDocument.createElement('ion-buttons');\n buttons.slot = 'end';\n // Remain hidden when the full theme is absent or opted out.\n buttons.style.display = 'none';\n buttons.classList.add('ios-theme-searchable-dismiss', 'ios-theme-horizontal-only');\n const button = footer.ownerDocument.createElement('ion-button');\n button.fill = 'default';\n button.type = 'button';\n button.setAttribute('aria-label', original.getAttribute('aria-label') ?? 'Close');\n // WebKit can blur the field between pointerup and click despite preventDefault.\n // Complete pointer activation on release before blur can remove the button.\n const doc = footer.ownerDocument;\n let pointer: number | undefined;\n let pointerTarget: HTMLElement | undefined;\n const actionTarget = (event: Event): HTMLElement | undefined => {\n const target = event.target instanceof Element ? event.target : undefined;\n if (target?.closest('ion-button') === button) return button;\n const clear = target?.closest<HTMLElement>('ion-searchbar .searchbar-clear-button');\n return clear && footer.contains(clear) ? clear : undefined;\n };\n const syncEditing = () => {\n if (!footer.contains(doc.activeElement)) footer.classList.remove('ios-theme-searchable-editing');\n };\n const releasePointer = () => {\n pointer = undefined;\n pointerTarget = undefined;\n doc.removeEventListener('pointerup', finishPointer, true);\n doc.removeEventListener('pointercancel', cancelPointer, true);\n doc.defaultView?.removeEventListener('blur', releasePointer);\n syncEditing();\n };\n const finishPointer = (event: PointerEvent) => {\n if (event.pointerId !== pointer || !pointerTarget) return;\n const target = pointerTarget;\n const rect = target.getBoundingClientRect();\n const inside =\n rect.width > 0 &&\n rect.height > 0 &&\n event.clientX >= rect.left &&\n event.clientX <= rect.right &&\n event.clientY >= rect.top &&\n event.clientY <= rect.bottom;\n releasePointer();\n if (inside) (target === button ? original : target).click();\n };\n const cancelPointer = (event: PointerEvent) => {\n if (event.pointerId === pointer) releasePointer();\n };\n footer.addEventListener(\n 'pointerdown',\n (event) => {\n const target = actionTarget(event);\n if (!target || !event.isPrimary || event.button !== 0) return;\n event.preventDefault();\n pointer = event.pointerId;\n pointerTarget = target;\n doc.addEventListener('pointerup', finishPointer, true);\n doc.addEventListener('pointercancel', cancelPointer, true);\n doc.defaultView?.addEventListener('blur', releasePointer);\n },\n true,\n );\n // Capture before Ionic's clear handler. Our programmatic click (detail 0) still\n // invokes its existing ionClear/input/refocus behavior, including after hydration.\n footer.addEventListener(\n 'click',\n (event) => {\n if (event.detail !== 0 && actionTarget(event)) {\n event.preventDefault();\n event.stopPropagation();\n }\n },\n true,\n );\n button.addEventListener('click', (event) => {\n // Pointer release already invoked the action. Keyboard/AT activation has no pointer click count.\n if (event.detail !== 0) return;\n releasePointer();\n original.click();\n });\n const icon = footer.ownerDocument.createElement('ion-icon');\n icon.slot = 'icon-only';\n icon.icon = close;\n button.append(icon);\n buttons.append(button);\n // Keep the editing layout while focus moves from clear to the slotted close button.\n footer.addEventListener('focusin', (event) => {\n if ((event.target as HTMLElement).closest('ion-searchbar')) footer.classList.add('ios-theme-searchable-editing');\n });\n footer.addEventListener('focusout', (event) => {\n if (pointer === undefined && !footer.contains(event.relatedTarget as Node | null))\n footer.classList.remove('ios-theme-searchable-editing');\n });\n original.closest('ion-toolbar')?.append(buttons);\n};\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
- import { isNativeUIShell, registerNativeSearch, requestNativeSearch, suspendNativeUIShell } from "../native-integration/index.js";
2
1
  import { addSearchDismissButton } from "./dismiss-button.js";
3
2
  import { createCloseButtonsAnimation, createEffectAnimation, createFabButtonAnimation, createSearchContainerAnimation, createTabBarAnimation } from "./animations/enter.js";
4
3
  import { createReverseCloseButtonsAnimation, createReverseEffectAnimation, createReverseFabButtonAnimation, createReverseSearchContainerAnimation, createReverseTabBarAnimation } from "./animations/leave.js";
5
4
  import { createAnimation } from "@ionic/core/components/index.js";
5
+ import { isNativeUI, registerNativeSearch, requestNativeSearch, suspendNativeUI } from "@rdlabo/capacitor-native-ui/integration";
6
6
  import { ANIMATION_DELAY_BASE, ANIMATION_DURATION, ANIMATION_EASING, TabBarSearchableType, TabBarSearchableType as TabBarSearchableType$1, getElementReferences, getElementSizes, throwErrorByFailedClickElement } from "@rdlabo/ionic-theme-utils";
7
7
  //#region src/tab-bar-searchable/index.ts
8
8
  /**
@@ -34,6 +34,7 @@ const attachTabBarSearchable = (ionTabBar, ionFabButton, ionFooter) => {
34
34
  const nativeSearch = registerNativeSearch(ionTabBar, ionFabButton, ionFooter);
35
35
  let searchableEventCache;
36
36
  let resumeNative;
37
+ let restoreTrigger;
37
38
  let nativeEntry = false;
38
39
  let nativeRequest = 0;
39
40
  let verticalSearch = false;
@@ -44,7 +45,7 @@ const attachTabBarSearchable = (ionTabBar, ionFabButton, ionFooter) => {
44
45
  if (type === TabBarSearchableType$1.Leave && !nativeEntry && !searchableEventCache) throw new Error("TabBarSearchableType.Leave should be run after TabBarSearchableType.Enter");
45
46
  const hadNativeEntry = nativeEntry;
46
47
  const request = ++nativeRequest;
47
- if (type === TabBarSearchableType$1.Enter) nativeEntry || (nativeEntry = isNativeUIShell(ionFooter));
48
+ if (type === TabBarSearchableType$1.Enter) nativeEntry || (nativeEntry = isNativeUI(ionFooter));
48
49
  const accepted = await requestNativeSearch(nativeSearch, type === TabBarSearchableType$1.Enter);
49
50
  if (request !== nativeRequest) return;
50
51
  if (accepted) {
@@ -67,23 +68,37 @@ const attachTabBarSearchable = (ionTabBar, ionFabButton, ionFooter) => {
67
68
  });
68
69
  keepTabsVisible();
69
70
  }
70
- resumeNative ?? (resumeNative = await suspendNativeUIShell([
71
+ resumeNative ?? (resumeNative = await suspendNativeUI([
71
72
  ...verticalSearch ? [] : [ionTabBar],
72
73
  ionFooter,
73
74
  ionFabButton.closest("ion-fab") ?? ionFabButton
74
75
  ]));
75
76
  try {
76
77
  searchableEventCache = await enterEvent(event, ionTabBar, ionFabButton, ionFooter, verticalSearch);
78
+ if (!restoreTrigger) {
79
+ const trigger = ionFabButton.closest("ion-fab") ?? ionFabButton;
80
+ const visibility = trigger.style.getPropertyValue("visibility");
81
+ const priority = trigger.style.getPropertyPriority("visibility");
82
+ restoreTrigger = () => {
83
+ if (visibility) trigger.style.setProperty("visibility", visibility, priority);
84
+ else trigger.style.removeProperty("visibility");
85
+ restoreTrigger = void 0;
86
+ };
87
+ trigger.style.setProperty("visibility", "hidden", "important");
88
+ }
77
89
  } catch (error) {
90
+ restoreTrigger?.();
78
91
  tabVisibilityObserver?.disconnect();
79
92
  resumeNative();
80
93
  resumeNative = void 0;
81
94
  throw error;
82
95
  }
83
96
  } else if (searchableEventCache !== void 0) try {
97
+ restoreTrigger?.();
84
98
  await leaveEvent(event, searchableEventCache, ionTabBar, ionFabButton, ionFooter, verticalSearch);
85
99
  searchableEventCache = void 0;
86
100
  } finally {
101
+ restoreTrigger?.();
87
102
  tabVisibilityObserver?.disconnect();
88
103
  resumeNative?.();
89
104
  resumeNative = void 0;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["TabBarSearchableType"],"sources":["../../src/tab-bar-searchable/index.ts"],"sourcesContent":["import { createAnimation } from '@ionic/core/components/index.js';\nimport { addSearchDismissButton } from './dismiss-button';\nimport {\n ANIMATION_DELAY_BASE,\n ANIMATION_DURATION,\n ANIMATION_EASING,\n getElementReferences,\n getElementSizes,\n throwErrorByFailedClickElement,\n type SearchableEventCache,\n type TabBarSearchableFunction,\n TabBarSearchableType,\n} from '@rdlabo/ionic-theme-utils';\nimport { isNativeUIShell, registerNativeSearch, requestNativeSearch, suspendNativeUIShell } from '../native-integration';\nimport {\n createCloseButtonsAnimation,\n createEffectAnimation,\n createFabButtonAnimation,\n createSearchContainerAnimation,\n createTabBarAnimation,\n} from './animations/enter';\nimport {\n createReverseCloseButtonsAnimation,\n createReverseEffectAnimation,\n createReverseFabButtonAnimation,\n createReverseSearchContainerAnimation,\n createReverseTabBarAnimation,\n} from './animations/leave';\n\nexport {\n TabBarSearchableType,\n type TabBarSearchableFunction,\n type SearchableEventCache,\n type ElementSizes,\n type ElementReferences,\n} from '@rdlabo/ionic-theme-utils';\n\n/**\n * <ion-fab vertical=\"bottom\" horizontal=\"end\" slot=\"fixed\">\n * <ion-fab-button (click)=\"present($event)\">\n * <ion-icon name=\"search\"></ion-icon>\n * </ion-fab-button>\n * </ion-fab>\n * <ion-footer [translucent]=\"true\">\n * <ion-toolbar>\n * <ion-buttons slot=\"start\">\n * <!-- ion-icon does not need `name` attribute. -->\n * <ion-button fill=\"default\"><ion-icon slot=\"icon-only\"></ion-icon>\n * </ion-button>\n * </ion-buttons>\n * <!-- User set `ionChange` or other events. -->\n * <ion-searchbar aria-label=\"Search\" (ionChange)=\"example($event)\"></ion-searchbar>\n * </ion-toolbar>\n * </ion-footer>\n **/\n\nexport const attachTabBarSearchable = (\n ionTabBar: HTMLElement,\n ionFabButton: HTMLElement,\n ionFooter: HTMLElement,\n): TabBarSearchableFunction => {\n if (!ionTabBar || !ionFabButton || !ionFooter) {\n throw new Error('TabBarSearchable should be defined with props');\n }\n // Initialize\n ionFooter.classList.add('ios-theme-searchable');\n ionFabButton.closest('ion-fab')?.classList.add('ios-theme-searchable-trigger');\n addSearchDismissButton(ionFooter);\n ionFooter.style.pointerEvents = 'none';\n ionFooter.style.opacity = '0';\n // Search dismissal belongs beside the Web search field, never in the vertical rail.\n ionFooter.querySelector('ion-buttons[slot=start]')?.classList.add('ios-theme-horizontal-only');\n const nativeSearch = registerNativeSearch(ionTabBar, ionFabButton, ionFooter);\n\n // Saved Params\n let searchableEventCache: SearchableEventCache | undefined;\n let resumeNative: (() => void) | undefined;\n // Leave is valid both while native Enter awaits its ack and after it completes.\n let nativeEntry = false;\n let nativeRequest = 0;\n let verticalSearch = false;\n let tabVisibilityObserver: MutationObserver | undefined;\n\n return async (event: Event, type: TabBarSearchableType) => {\n const selector = type === TabBarSearchableType.Enter ? 'ion-fab-button' : 'ion-buttons[slot=start] ion-button';\n if (!(event.target as HTMLElement)?.closest(selector)) throw throwErrorByFailedClickElement(selector);\n if (type === TabBarSearchableType.Leave && !nativeEntry && !searchableEventCache)\n throw new Error('TabBarSearchableType.Leave should be run after TabBarSearchableType.Enter');\n const hadNativeEntry = nativeEntry;\n const request = ++nativeRequest;\n if (type === TabBarSearchableType.Enter) nativeEntry ||= isNativeUIShell(ionFooter);\n const accepted = await requestNativeSearch(nativeSearch, type === TabBarSearchableType.Enter);\n if (request !== nativeRequest) return;\n if (accepted) {\n nativeEntry = type === TabBarSearchableType.Enter;\n return;\n }\n nativeEntry = false;\n if (type === TabBarSearchableType.Leave && hadNativeEntry) {\n // Forced native retirement already restored the closed Web presentation.\n return;\n }\n if (type === TabBarSearchableType.Enter) {\n verticalSearch = !!ionTabBar.closest('ion-app.ios-theme-vertical-bars');\n if (verticalSearch) {\n // Ionic hides horizontal tabs when the keyboard opens; keep this rail available.\n const keepTabsVisible = () => {\n if (ionTabBar.classList.contains('tab-bar-hidden') && ionTabBar.closest('ion-app.ios-theme-vertical-bars')) {\n ionTabBar.classList.remove('tab-bar-hidden');\n }\n };\n tabVisibilityObserver?.disconnect();\n tabVisibilityObserver = new MutationObserver(keepTabsVisible);\n tabVisibilityObserver.observe(ionTabBar, { attributes: true, attributeFilter: ['class'] });\n keepTabsVisible();\n }\n resumeNative ??= await suspendNativeUIShell([\n ...(verticalSearch ? [] : [ionTabBar]),\n ionFooter,\n ionFabButton.closest('ion-fab') ?? ionFabButton,\n ]);\n try {\n searchableEventCache = await enterEvent(event, ionTabBar, ionFabButton, ionFooter, verticalSearch);\n } catch (error) {\n tabVisibilityObserver?.disconnect();\n resumeNative();\n resumeNative = undefined;\n throw error;\n }\n } else if (searchableEventCache !== undefined) {\n try {\n await leaveEvent(event, searchableEventCache, ionTabBar, ionFabButton, ionFooter, verticalSearch);\n searchableEventCache = undefined;\n } finally {\n tabVisibilityObserver?.disconnect();\n resumeNative?.();\n resumeNative = undefined;\n }\n } else {\n throw new Error('TabBarSearchableType.Leave should be run after TabBarSearchableType.Enter');\n }\n };\n};\n\nconst enterEvent = async (\n event: Event,\n ionTabBar: HTMLElement,\n ionFabButton: HTMLElement,\n ionFooter: HTMLElement,\n verticalSearch: boolean,\n): Promise<SearchableEventCache> => {\n if (!(event.target as HTMLElement)?.closest('ion-fab-button')) {\n throw throwErrorByFailedClickElement('ion-fab-button');\n }\n\n const references = getElementReferences(ionTabBar, ionFooter);\n const sizes = getElementSizes(ionTabBar, ionFabButton, references);\n const colorSelected = references.selectedTabButton\n ? getComputedStyle(references.selectedTabButton).getPropertyValue('--color-selected').trim()\n : '';\n\n if (verticalSearch) references.closeButtonIcon.setAttribute('name', 'close');\n const tabAnimations = verticalSearch\n ? []\n : [createTabBarAnimation(ionTabBar, references, sizes), createEffectAnimation(references, sizes)];\n const searchContainerAnimation = createSearchContainerAnimation(references, sizes);\n const closeButtonsAnimation = createCloseButtonsAnimation(references);\n const fabButtonAnimation = createFabButtonAnimation(ionFabButton);\n\n await createAnimation()\n .delay(ANIMATION_DELAY_BASE)\n .duration(ANIMATION_DURATION)\n .easing(ANIMATION_EASING)\n .addElement(ionFooter)\n .afterAddWrite(() => (ionFooter.style.pointerEvents = 'auto'))\n .fromTo('opacity', '0.8', '1')\n .addAnimation([...tabAnimations, fabButtonAnimation, searchContainerAnimation, closeButtonsAnimation])\n .play();\n\n return {\n elementSizes: sizes,\n colorSelected,\n };\n};\n\nconst leaveEvent = async (\n event: Event,\n searchableEventCache: SearchableEventCache,\n ionTabBar: HTMLElement,\n ionFabButton: HTMLElement,\n ionFooter: HTMLElement,\n verticalSearch: boolean,\n): Promise<void> => {\n if (!(event.target as HTMLElement)?.closest('ion-buttons[slot=start] ion-button')) {\n throw throwErrorByFailedClickElement('ion-buttons[slot=start] ion-button');\n }\n\n const focused = ionFooter.ownerDocument.activeElement;\n if (focused instanceof HTMLElement && ionFooter.contains(focused)) focused.blur();\n const references = getElementReferences(ionTabBar, ionFooter);\n\n const tabAnimations = verticalSearch\n ? []\n : [\n createReverseTabBarAnimation(ionTabBar, references, searchableEventCache.elementSizes),\n createReverseEffectAnimation(references, searchableEventCache.elementSizes, searchableEventCache.colorSelected),\n ];\n const searchContainerAnimation = createReverseSearchContainerAnimation(references, searchableEventCache.elementSizes);\n const closeButtonsAnimation = createReverseCloseButtonsAnimation(references);\n const fabButtonAnimation = createReverseFabButtonAnimation(ionFabButton, searchableEventCache.elementSizes);\n\n await createAnimation()\n .delay(ANIMATION_DELAY_BASE)\n .duration(ANIMATION_DURATION)\n .easing(ANIMATION_EASING)\n .addElement(ionFooter)\n .afterAddWrite(() => (ionFooter.style.pointerEvents = 'none'))\n .fromTo('opacity', '1', '0')\n .addAnimation([...tabAnimations, fabButtonAnimation, searchContainerAnimation, closeButtonsAnimation])\n .play();\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["TabBarSearchableType"],"sources":["../../src/tab-bar-searchable/index.ts"],"sourcesContent":["import { createAnimation } from '@ionic/core/components/index.js';\nimport { addSearchDismissButton } from './dismiss-button';\nimport {\n ANIMATION_DELAY_BASE,\n ANIMATION_DURATION,\n ANIMATION_EASING,\n getElementReferences,\n getElementSizes,\n throwErrorByFailedClickElement,\n type SearchableEventCache,\n type TabBarSearchableFunction,\n TabBarSearchableType,\n} from '@rdlabo/ionic-theme-utils';\nimport { isNativeUI, registerNativeSearch, requestNativeSearch, suspendNativeUI } from '@rdlabo/capacitor-native-ui/integration';\nimport {\n createCloseButtonsAnimation,\n createEffectAnimation,\n createFabButtonAnimation,\n createSearchContainerAnimation,\n createTabBarAnimation,\n} from './animations/enter';\nimport {\n createReverseCloseButtonsAnimation,\n createReverseEffectAnimation,\n createReverseFabButtonAnimation,\n createReverseSearchContainerAnimation,\n createReverseTabBarAnimation,\n} from './animations/leave';\n\nexport {\n TabBarSearchableType,\n type TabBarSearchableFunction,\n type SearchableEventCache,\n type ElementSizes,\n type ElementReferences,\n} from '@rdlabo/ionic-theme-utils';\n\n/**\n * <ion-fab vertical=\"bottom\" horizontal=\"end\" slot=\"fixed\">\n * <ion-fab-button (click)=\"present($event)\">\n * <ion-icon name=\"search\"></ion-icon>\n * </ion-fab-button>\n * </ion-fab>\n * <ion-footer [translucent]=\"true\">\n * <ion-toolbar>\n * <ion-buttons slot=\"start\">\n * <!-- ion-icon does not need `name` attribute. -->\n * <ion-button fill=\"default\"><ion-icon slot=\"icon-only\"></ion-icon>\n * </ion-button>\n * </ion-buttons>\n * <!-- User set `ionChange` or other events. -->\n * <ion-searchbar aria-label=\"Search\" (ionChange)=\"example($event)\"></ion-searchbar>\n * </ion-toolbar>\n * </ion-footer>\n **/\n\nexport const attachTabBarSearchable = (\n ionTabBar: HTMLElement,\n ionFabButton: HTMLElement,\n ionFooter: HTMLElement,\n): TabBarSearchableFunction => {\n if (!ionTabBar || !ionFabButton || !ionFooter) {\n throw new Error('TabBarSearchable should be defined with props');\n }\n // Initialize\n ionFooter.classList.add('ios-theme-searchable');\n ionFabButton.closest('ion-fab')?.classList.add('ios-theme-searchable-trigger');\n addSearchDismissButton(ionFooter);\n ionFooter.style.pointerEvents = 'none';\n ionFooter.style.opacity = '0';\n // Search dismissal belongs beside the Web search field, never in the vertical rail.\n ionFooter.querySelector('ion-buttons[slot=start]')?.classList.add('ios-theme-horizontal-only');\n const nativeSearch = registerNativeSearch(ionTabBar, ionFabButton, ionFooter);\n\n // Saved Params\n let searchableEventCache: SearchableEventCache | undefined;\n let resumeNative: (() => void) | undefined;\n let restoreTrigger: (() => void) | undefined;\n // Leave is valid both while native Enter awaits its ack and after it completes.\n let nativeEntry = false;\n let nativeRequest = 0;\n let verticalSearch = false;\n let tabVisibilityObserver: MutationObserver | undefined;\n\n return async (event: Event, type: TabBarSearchableType) => {\n const selector = type === TabBarSearchableType.Enter ? 'ion-fab-button' : 'ion-buttons[slot=start] ion-button';\n if (!(event.target as HTMLElement)?.closest(selector)) throw throwErrorByFailedClickElement(selector);\n if (type === TabBarSearchableType.Leave && !nativeEntry && !searchableEventCache)\n throw new Error('TabBarSearchableType.Leave should be run after TabBarSearchableType.Enter');\n const hadNativeEntry = nativeEntry;\n const request = ++nativeRequest;\n if (type === TabBarSearchableType.Enter) nativeEntry ||= isNativeUI(ionFooter);\n const accepted = await requestNativeSearch(nativeSearch, type === TabBarSearchableType.Enter);\n if (request !== nativeRequest) return;\n if (accepted) {\n nativeEntry = type === TabBarSearchableType.Enter;\n return;\n }\n nativeEntry = false;\n if (type === TabBarSearchableType.Leave && hadNativeEntry) {\n // Forced native retirement already restored the closed Web presentation.\n return;\n }\n if (type === TabBarSearchableType.Enter) {\n verticalSearch = !!ionTabBar.closest('ion-app.ios-theme-vertical-bars');\n if (verticalSearch) {\n // Ionic hides horizontal tabs when the keyboard opens; keep this rail available.\n const keepTabsVisible = () => {\n if (ionTabBar.classList.contains('tab-bar-hidden') && ionTabBar.closest('ion-app.ios-theme-vertical-bars')) {\n ionTabBar.classList.remove('tab-bar-hidden');\n }\n };\n tabVisibilityObserver?.disconnect();\n tabVisibilityObserver = new MutationObserver(keepTabsVisible);\n tabVisibilityObserver.observe(ionTabBar, { attributes: true, attributeFilter: ['class'] });\n keepTabsVisible();\n }\n resumeNative ??= await suspendNativeUI([\n ...(verticalSearch ? [] : [ionTabBar]),\n ionFooter,\n ionFabButton.closest('ion-fab') ?? ionFabButton,\n ]);\n try {\n searchableEventCache = await enterEvent(event, ionTabBar, ionFabButton, ionFooter, verticalSearch);\n if (!restoreTrigger) {\n // WebKit can hit an opacity-zero FAB above the focused close button.\n // Hide its complete host after the entrance animation; restore before animating out.\n const trigger = ionFabButton.closest('ion-fab') ?? ionFabButton;\n const visibility = trigger.style.getPropertyValue('visibility');\n const priority = trigger.style.getPropertyPriority('visibility');\n restoreTrigger = () => {\n if (visibility) trigger.style.setProperty('visibility', visibility, priority);\n else trigger.style.removeProperty('visibility');\n restoreTrigger = undefined;\n };\n trigger.style.setProperty('visibility', 'hidden', 'important');\n }\n } catch (error) {\n restoreTrigger?.();\n tabVisibilityObserver?.disconnect();\n resumeNative();\n resumeNative = undefined;\n throw error;\n }\n } else if (searchableEventCache !== undefined) {\n try {\n restoreTrigger?.();\n await leaveEvent(event, searchableEventCache, ionTabBar, ionFabButton, ionFooter, verticalSearch);\n searchableEventCache = undefined;\n } finally {\n restoreTrigger?.();\n tabVisibilityObserver?.disconnect();\n resumeNative?.();\n resumeNative = undefined;\n }\n } else {\n throw new Error('TabBarSearchableType.Leave should be run after TabBarSearchableType.Enter');\n }\n };\n};\n\nconst enterEvent = async (\n event: Event,\n ionTabBar: HTMLElement,\n ionFabButton: HTMLElement,\n ionFooter: HTMLElement,\n verticalSearch: boolean,\n): Promise<SearchableEventCache> => {\n if (!(event.target as HTMLElement)?.closest('ion-fab-button')) {\n throw throwErrorByFailedClickElement('ion-fab-button');\n }\n\n const references = getElementReferences(ionTabBar, ionFooter);\n const sizes = getElementSizes(ionTabBar, ionFabButton, references);\n const colorSelected = references.selectedTabButton\n ? getComputedStyle(references.selectedTabButton).getPropertyValue('--color-selected').trim()\n : '';\n\n if (verticalSearch) references.closeButtonIcon.setAttribute('name', 'close');\n const tabAnimations = verticalSearch\n ? []\n : [createTabBarAnimation(ionTabBar, references, sizes), createEffectAnimation(references, sizes)];\n const searchContainerAnimation = createSearchContainerAnimation(references, sizes);\n const closeButtonsAnimation = createCloseButtonsAnimation(references);\n const fabButtonAnimation = createFabButtonAnimation(ionFabButton);\n\n await createAnimation()\n .delay(ANIMATION_DELAY_BASE)\n .duration(ANIMATION_DURATION)\n .easing(ANIMATION_EASING)\n .addElement(ionFooter)\n .afterAddWrite(() => (ionFooter.style.pointerEvents = 'auto'))\n .fromTo('opacity', '0.8', '1')\n .addAnimation([...tabAnimations, fabButtonAnimation, searchContainerAnimation, closeButtonsAnimation])\n .play();\n\n return {\n elementSizes: sizes,\n colorSelected,\n };\n};\n\nconst leaveEvent = async (\n event: Event,\n searchableEventCache: SearchableEventCache,\n ionTabBar: HTMLElement,\n ionFabButton: HTMLElement,\n ionFooter: HTMLElement,\n verticalSearch: boolean,\n): Promise<void> => {\n if (!(event.target as HTMLElement)?.closest('ion-buttons[slot=start] ion-button')) {\n throw throwErrorByFailedClickElement('ion-buttons[slot=start] ion-button');\n }\n\n const focused = ionFooter.ownerDocument.activeElement;\n if (focused instanceof HTMLElement && ionFooter.contains(focused)) focused.blur();\n const references = getElementReferences(ionTabBar, ionFooter);\n\n const tabAnimations = verticalSearch\n ? []\n : [\n createReverseTabBarAnimation(ionTabBar, references, searchableEventCache.elementSizes),\n createReverseEffectAnimation(references, searchableEventCache.elementSizes, searchableEventCache.colorSelected),\n ];\n const searchContainerAnimation = createReverseSearchContainerAnimation(references, searchableEventCache.elementSizes);\n const closeButtonsAnimation = createReverseCloseButtonsAnimation(references);\n const fabButtonAnimation = createReverseFabButtonAnimation(ionFabButton, searchableEventCache.elementSizes);\n\n await createAnimation()\n .delay(ANIMATION_DELAY_BASE)\n .duration(ANIMATION_DURATION)\n .easing(ANIMATION_EASING)\n .addElement(ionFooter)\n .afterAddWrite(() => (ionFooter.style.pointerEvents = 'none'))\n .fromTo('opacity', '1', '0')\n .addAnimation([...tabAnimations, fabButtonAnimation, searchContainerAnimation, closeButtonsAnimation])\n .play();\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ios.transition.d.ts","names":[],"sources":["../../src/transition/ios.transition.ts"],"mappings":";;;;iBAQiB;EACf;;qBAUW,YAAa,QAAQ,QAAQ;qBAI7B,yBAAyB,OAAO,aAAa,MAAM,sBAAsB"}
1
+ {"version":3,"file":"ios.transition.d.ts","names":[],"sources":["../../src/transition/ios.transition.ts"],"mappings":";;;;iBASiB;EACf;;qBAYW,YAAa,QAAQ,QAAQ;qBAI7B,yBAAyB,OAAO,aAAa,MAAM,sBAAsB"}
@@ -1,17 +1,20 @@
1
1
  import { getIonPageElement } from "./index.js";
2
- import { withNativeUIShellTransition } from "../native-integration/transition.js";
2
+ import { withNativeUITransition } from "./native-ui.js";
3
+ import { nativeWebViewMetrics } from "@rdlabo/capacitor-native-ui/integration";
3
4
  import { createIosTransitionAnimation, shadow } from "@rdlabo/ionic-theme-utils";
4
5
  //#region src/transition/ios.transition.ts
5
6
  const transitionConfig = {
6
7
  offLeftPercent: 30,
7
8
  getIonPageElement,
8
9
  shouldAnimateFixedBackButton: (navEl) => !navEl.closest("ion-app.ios-theme-vertical-bars"),
9
- radius: 0
10
+ get radius() {
11
+ return nativeWebViewMetrics.radius;
12
+ }
10
13
  };
11
14
  const setConfig = (config) => {
12
- Object.assign(transitionConfig, config);
15
+ if (config.radius !== void 0) nativeWebViewMetrics.radius = config.radius;
13
16
  };
14
- const iosTransitionAnimation = withNativeUIShellTransition(createIosTransitionAnimation(transitionConfig));
17
+ const iosTransitionAnimation = withNativeUITransition(createIosTransitionAnimation(transitionConfig));
15
18
  //#endregion
16
19
  export { iosTransitionAnimation, setConfig, shadow };
17
20
 
@@ -1 +1 @@
1
- {"version":3,"file":"ios.transition.js","names":[],"sources":["../../src/transition/ios.transition.ts"],"sourcesContent":["import { createIosTransitionAnimation, shadow } from '@rdlabo/ionic-theme-utils';\nimport type { Animation } from '@ionic/core/components';\nimport type { TransitionOptions } from './index';\nimport { getIonPageElement } from './index';\nimport { withNativeUIShellTransition } from '../native-integration/transition';\n\nexport { shadow };\n\nexport interface IosTransitionConfig {\n radius: number;\n}\n\nconst transitionConfig = {\n offLeftPercent: 30,\n getIonPageElement,\n shouldAnimateFixedBackButton: (navEl: HTMLElement) => !navEl.closest('ion-app.ios-theme-vertical-bars'),\n radius: 0,\n};\n\nexport const setConfig = (config: Partial<IosTransitionConfig>): void => {\n Object.assign(transitionConfig, config);\n};\n\nexport const iosTransitionAnimation: (navEl: HTMLElement, opts: TransitionOptions) => Animation = withNativeUIShellTransition(\n createIosTransitionAnimation<TransitionOptions>(transitionConfig),\n);\n"],"mappings":";;;;AAYA,MAAM,mBAAmB;CACvB,gBAAgB;CAChB;CACA,+BAA+B,UAAuB,CAAC,MAAM,QAAQ,iCAAiC;CACtG,QAAQ;AACV;AAEA,MAAa,aAAa,WAA+C;CACvE,OAAO,OAAO,kBAAkB,MAAM;AACxC;AAEA,MAAa,yBAAqF,4BAChG,6BAAgD,gBAAgB,CAClE"}
1
+ {"version":3,"file":"ios.transition.js","names":[],"sources":["../../src/transition/ios.transition.ts"],"sourcesContent":["import { createIosTransitionAnimation, shadow } from '@rdlabo/ionic-theme-utils';\nimport type { Animation } from '@ionic/core/components';\nimport type { TransitionOptions } from './index';\nimport { getIonPageElement } from './index';\nimport { nativeWebViewMetrics } from '@rdlabo/capacitor-native-ui/integration';\nimport { withNativeUITransition } from './native-ui';\n\nexport { shadow };\n\nexport interface IosTransitionConfig {\n radius: number;\n}\n\nconst transitionConfig = {\n offLeftPercent: 30,\n getIonPageElement,\n shouldAnimateFixedBackButton: (navEl: HTMLElement) => !navEl.closest('ion-app.ios-theme-vertical-bars'),\n get radius() {\n return nativeWebViewMetrics.radius;\n },\n};\n\nexport const setConfig = (config: Partial<IosTransitionConfig>): void => {\n if (config.radius !== undefined) nativeWebViewMetrics.radius = config.radius;\n};\n\nexport const iosTransitionAnimation: (navEl: HTMLElement, opts: TransitionOptions) => Animation = withNativeUITransition(\n createIosTransitionAnimation<TransitionOptions>(transitionConfig),\n);\n"],"mappings":";;;;;AAaA,MAAM,mBAAmB;CACvB,gBAAgB;CAChB;CACA,+BAA+B,UAAuB,CAAC,MAAM,QAAQ,iCAAiC;CACtG,IAAI,SAAS;EACX,OAAO,qBAAqB;CAC9B;AACF;AAEA,MAAa,aAAa,WAA+C;CACvE,IAAI,OAAO,WAAW,KAAA,GAAW,qBAAqB,SAAS,OAAO;AACxE;AAEA,MAAa,yBAAqF,uBAChG,6BAAgD,gBAAgB,CAClE"}
@@ -0,0 +1,6 @@
1
+ import { Animation, AnimationBuilder } from "@ionic/core/components";
2
+ //#region src/transition/native-ui.d.ts
3
+ export declare const withNativeUITransition: (builder: AnimationBuilder) => AnimationBuilder;
4
+ export declare const connectNativeUITransition: (animation: Animation, entering: HTMLElement, leaving?: HTMLElement) => void;
5
+ //#endregion
6
+ //# sourceMappingURL=native-ui.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-ui.d.ts","names":[],"sources":["../../src/transition/native-ui.ts"],"mappings":";;qBASa,yBACV,SAAS,qBAAmB;qBAWlB,4BAA6B,WAAW,WAAW,UAAU,aAAa,UAAU"}
@@ -1,33 +1,19 @@
1
- //#region src/native-integration/index.ts
2
- const searches = /* @__PURE__ */ new WeakMap();
3
- const registerNativeSearch = (tabBar, trigger, footer) => {
4
- const binding = {
5
- tabBar,
6
- trigger,
7
- footer,
8
- active: false,
9
- focused: false
10
- };
11
- const entries = (searches.get(tabBar.ownerDocument) ?? []).filter((entry) => entry.deref() && entry.deref()?.footer !== footer);
12
- entries.push(new WeakRef(binding));
13
- searches.set(tabBar.ownerDocument, entries);
14
- tabBar.ownerDocument.defaultView?.dispatchEvent(new Event("nativeUIShellRefresh"));
15
- return binding;
16
- };
17
- const getNativeSearchBindings = (doc) => (searches.get(doc) ?? []).flatMap((entry) => entry.deref() ?? []);
18
- const requestNativeSearch = async (binding, active, focus) => await runtimes.get(binding.tabBar.ownerDocument)?.search?.(binding, active, focus) ?? false;
19
- const runtimes = /* @__PURE__ */ new WeakMap();
20
- const VERTICAL_BARS_TRANSITION_CANCELED = "iosThemeVerticalBarsTransitionCanceled";
21
- const setNativeUIShellIntegration = (doc, runtime) => {
22
- if (runtime) runtimes.set(doc, runtime);
23
- else runtimes.delete(doc);
1
+ import { VERTICAL_BARS_TRANSITION_CANCELED, hasNativeUIIntegration, suspendNativeUI } from "@rdlabo/capacitor-native-ui/integration";
2
+ //#region src/transition/native-ui.ts
3
+ /**
4
+ * Keeps an existing Ionic navigation animation and coordinates native control handoff.
5
+ * Register the returned builder as navAnimation. Do not use it for overlay animations.
6
+ * The builder must return a fresh Animation for each navigation; Ionic destroys it afterward.
7
+ */
8
+ const withNativeUITransition = (builder) => (baseEl, opts) => {
9
+ const animation = builder(baseEl, opts);
10
+ if (opts?.enteringEl) connectNativeUITransition(animation, opts.enteringEl, opts.leavingEl);
11
+ return animation;
24
12
  };
25
- const isNativeUIShell = (element) => element.hasAttribute("data-native-ui-shell");
26
- const suspendNativeUIShell = async (scopes) => await runtimes.get(scopes[0]?.ownerDocument)?.suspend(scopes) ?? (() => {});
27
13
  const connectedTransitions = /* @__PURE__ */ new WeakSet();
28
14
  const cancellationTransitions = /* @__PURE__ */ new WeakSet();
29
15
  /** Ionic write hooks cannot await the bridge. Gate playback, including interactive playback. */
30
- const connectNativeUIShellTransition = (animation, entering, leaving) => {
16
+ const connectNativeUITransition = (animation, entering, leaving) => {
31
17
  if (leaving && !cancellationTransitions.has(animation)) {
32
18
  cancellationTransitions.add(animation);
33
19
  animation.onFinish((step) => {
@@ -37,7 +23,7 @@ const connectNativeUIShellTransition = (animation, entering, leaving) => {
37
23
  }));
38
24
  });
39
25
  }
40
- if (connectedTransitions.has(animation) || !runtimes.has(entering.ownerDocument)) return;
26
+ if (connectedTransitions.has(animation) || !hasNativeUIIntegration(entering.ownerDocument)) return;
41
27
  connectedTransitions.add(animation);
42
28
  const scopes = leaving ? [entering, leaving] : [entering];
43
29
  const play = animation.play.bind(animation);
@@ -51,7 +37,7 @@ const connectNativeUIShellTransition = (animation, entering, leaving) => {
51
37
  let interactiveReady = false;
52
38
  let step;
53
39
  let end;
54
- const prepare = () => preparation ?? (preparation = suspendNativeUIShell(scopes).then((resume) => {
40
+ const prepare = () => preparation ?? (preparation = suspendNativeUI(scopes).then((resume) => {
55
41
  if (disposed) resume(true);
56
42
  else release = resume;
57
43
  }));
@@ -100,6 +86,6 @@ const connectNativeUIShellTransition = (animation, entering, leaving) => {
100
86
  };
101
87
  };
102
88
  //#endregion
103
- export { VERTICAL_BARS_TRANSITION_CANCELED, connectNativeUIShellTransition, getNativeSearchBindings, isNativeUIShell, registerNativeSearch, requestNativeSearch, setNativeUIShellIntegration, suspendNativeUIShell };
89
+ export { connectNativeUITransition, withNativeUITransition };
104
90
 
105
- //# sourceMappingURL=index.js.map
91
+ //# sourceMappingURL=native-ui.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"native-ui.js","names":[],"sources":["../../src/transition/native-ui.ts"],"sourcesContent":["import type { Animation } from '@ionic/core/components';\nimport { hasNativeUIIntegration, suspendNativeUI, VERTICAL_BARS_TRANSITION_CANCELED } from '@rdlabo/capacitor-native-ui/integration';\nimport type { AnimationBuilder } from '@ionic/core/components';\n\n/**\n * Keeps an existing Ionic navigation animation and coordinates native control handoff.\n * Register the returned builder as navAnimation. Do not use it for overlay animations.\n * The builder must return a fresh Animation for each navigation; Ionic destroys it afterward.\n */\nexport const withNativeUITransition =\n (builder: AnimationBuilder): AnimationBuilder =>\n (baseEl, opts) => {\n const animation = builder(baseEl, opts);\n if (opts?.enteringEl) connectNativeUITransition(animation, opts.enteringEl, opts.leavingEl);\n return animation;\n };\n\nconst connectedTransitions = new WeakSet<Animation>();\nconst cancellationTransitions = new WeakSet<Animation>();\n\n/** Ionic write hooks cannot await the bridge. Gate playback, including interactive playback. */\nexport const connectNativeUITransition = (animation: Animation, entering: HTMLElement, leaving?: HTMLElement) => {\n if (leaving && !cancellationTransitions.has(animation)) {\n cancellationTransitions.add(animation);\n animation.onFinish((step) => {\n if (step === 0) leaving.dispatchEvent(new CustomEvent(VERTICAL_BARS_TRANSITION_CANCELED, { bubbles: true, detail: { entering } }));\n });\n }\n if (connectedTransitions.has(animation) || !hasNativeUIIntegration(entering.ownerDocument)) return;\n connectedTransitions.add(animation);\n const scopes = leaving ? [entering, leaving] : [entering];\n const play = animation.play.bind(animation);\n const progressStart = animation.progressStart.bind(animation);\n const progressStep = animation.progressStep.bind(animation);\n const progressEnd = animation.progressEnd.bind(animation);\n const destroy = animation.destroy.bind(animation);\n let release: ((canceled?: boolean) => void) | undefined;\n let preparation: Promise<void> | undefined;\n let disposed = false;\n let interactiveReady = false;\n let step: number | undefined;\n let end: Parameters<Animation['progressEnd']> | undefined;\n const prepare = () =>\n (preparation ??= suspendNativeUI(scopes).then((resume) => {\n if (disposed) resume(true);\n else release = resume;\n }));\n const finish = (canceled = false) => {\n release?.(canceled);\n release = undefined;\n preparation = undefined;\n };\n animation.onFinish((step) => finish(step === 0));\n animation.play = async (options) => {\n await prepare();\n if (disposed) return;\n try {\n await play(options);\n } finally {\n finish();\n }\n };\n animation.progressStart = (...args) => {\n interactiveReady = false;\n step = undefined;\n end = undefined;\n void prepare().then(() => {\n if (disposed) return;\n progressStart(...args);\n interactiveReady = true;\n if (step !== undefined) progressStep(step);\n if (end) progressEnd(...end);\n });\n return animation;\n };\n animation.progressStep = (value) => {\n step = value;\n if (interactiveReady) progressStep(value);\n return animation;\n };\n animation.progressEnd = (...args) => {\n end = args;\n if (interactiveReady) progressEnd(...args);\n return animation;\n };\n animation.destroy = (...args) => {\n disposed = true;\n finish(true);\n return destroy(...args);\n };\n};\n"],"mappings":";;;;;;;AASA,MAAa,0BACV,aACA,QAAQ,SAAS;CAChB,MAAM,YAAY,QAAQ,QAAQ,IAAI;CACtC,IAAI,MAAM,YAAY,0BAA0B,WAAW,KAAK,YAAY,KAAK,SAAS;CAC1F,OAAO;AACT;AAEF,MAAM,uCAAuB,IAAI,QAAmB;AACpD,MAAM,0CAA0B,IAAI,QAAmB;;AAGvD,MAAa,6BAA6B,WAAsB,UAAuB,YAA0B;CAC/G,IAAI,WAAW,CAAC,wBAAwB,IAAI,SAAS,GAAG;EACtD,wBAAwB,IAAI,SAAS;EACrC,UAAU,UAAU,SAAS;GAC3B,IAAI,SAAS,GAAG,QAAQ,cAAc,IAAI,YAAY,mCAAmC;IAAE,SAAS;IAAM,QAAQ,EAAE,SAAS;GAAE,CAAC,CAAC;EACnI,CAAC;CACH;CACA,IAAI,qBAAqB,IAAI,SAAS,KAAK,CAAC,uBAAuB,SAAS,aAAa,GAAG;CAC5F,qBAAqB,IAAI,SAAS;CAClC,MAAM,SAAS,UAAU,CAAC,UAAU,OAAO,IAAI,CAAC,QAAQ;CACxD,MAAM,OAAO,UAAU,KAAK,KAAK,SAAS;CAC1C,MAAM,gBAAgB,UAAU,cAAc,KAAK,SAAS;CAC5D,MAAM,eAAe,UAAU,aAAa,KAAK,SAAS;CAC1D,MAAM,cAAc,UAAU,YAAY,KAAK,SAAS;CACxD,MAAM,UAAU,UAAU,QAAQ,KAAK,SAAS;CAChD,IAAI;CACJ,IAAI;CACJ,IAAI,WAAW;CACf,IAAI,mBAAmB;CACvB,IAAI;CACJ,IAAI;CACJ,MAAM,gBACH,gBAAA,cAAgB,gBAAgB,MAAM,CAAC,CAAC,MAAM,WAAW;EACxD,IAAI,UAAU,OAAO,IAAI;OACpB,UAAU;CACjB,CAAC;CACH,MAAM,UAAU,WAAW,UAAU;EACnC,UAAU,QAAQ;EAClB,UAAU,KAAA;EACV,cAAc,KAAA;CAChB;CACA,UAAU,UAAU,SAAS,OAAO,SAAS,CAAC,CAAC;CAC/C,UAAU,OAAO,OAAO,YAAY;EAClC,MAAM,QAAQ;EACd,IAAI,UAAU;EACd,IAAI;GACF,MAAM,KAAK,OAAO;EACpB,UAAU;GACR,OAAO;EACT;CACF;CACA,UAAU,iBAAiB,GAAG,SAAS;EACrC,mBAAmB;EACnB,OAAO,KAAA;EACP,MAAM,KAAA;EACN,QAAa,CAAC,CAAC,WAAW;GACxB,IAAI,UAAU;GACd,cAAc,GAAG,IAAI;GACrB,mBAAmB;GACnB,IAAI,SAAS,KAAA,GAAW,aAAa,IAAI;GACzC,IAAI,KAAK,YAAY,GAAG,GAAG;EAC7B,CAAC;EACD,OAAO;CACT;CACA,UAAU,gBAAgB,UAAU;EAClC,OAAO;EACP,IAAI,kBAAkB,aAAa,KAAK;EACxC,OAAO;CACT;CACA,UAAU,eAAe,GAAG,SAAS;EACnC,MAAM;EACN,IAAI,kBAAkB,YAAY,GAAG,IAAI;EACzC,OAAO;CACT;CACA,UAAU,WAAW,GAAG,SAAS;EAC/B,WAAW;EACX,OAAO,IAAI;EACX,OAAO,QAAQ,GAAG,IAAI;CACxB;AACF"}
@@ -0,0 +1,151 @@
1
+ # Use iOS 26, iOS 27, and MD3 themes
2
+
3
+ Use this optional setup when you want browser capabilities to select iOS styles, or want MD3 in Material Design mode. For a single iOS 27 appearance, follow [theme setup](setup.md). These steps replace the stylesheet and animation configuration in that guide.
4
+
5
+ ## 1. Install the themes
6
+
7
+ Install both themes in an existing Ionic 8 or 9 app (`@ionic/core` 8.8.1 or later):
8
+
9
+ ```bash
10
+ npm install @rdlabo/ionic-theme-ios26 @rdlabo/ionic-theme-ios27
11
+ ```
12
+
13
+ ## 2. Load styles
14
+
15
+ In your global Sass stylesheet (for example, `src/styles.scss`), load the styles by browser capability:
16
+
17
+ ```scss
18
+ @use 'sass:meta';
19
+
20
+ @supports (overflow-anchor: auto) {
21
+ @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/default-variables');
22
+ @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27');
23
+ @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27-dark-class');
24
+ @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/md-remove-ios-class-effect');
25
+ }
26
+
27
+ @supports (text-wrap: pretty) and (not (overflow-anchor: auto)) {
28
+ @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/default-variables');
29
+ @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26');
30
+ @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class');
31
+ @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect');
32
+ }
33
+ ```
34
+
35
+ If compilation reports `Can't find stylesheet to import.` for a `meta.load-css()` call, make `node_modules` available to Sass. In Angular, add this under the app's build `options` in `angular.json`:
36
+
37
+ ```json
38
+ "stylePreprocessorOptions": {
39
+ "includePaths": ["node_modules"]
40
+ }
41
+ ```
42
+
43
+ Alternatively, use a relative path from the Sass file containing `meta.load-css()` to the installed package, such as `../node_modules/@rdlabo/ionic-theme-ios27/src/styles/default-variables` from `src/styles.scss`. Adjust the `../` prefix for your file's location, and apply the same change to each theme import.
44
+
45
+ These checks select styles by browser features, not by iOS version. Browsers without either feature retain Ionic's default iOS appearance. The example uses class-based dark mode: also load [Ionic's matching dark palette](https://ionicframework.com/docs/theming/dark-mode). For system or always-dark mode, replace both `-dark-class` imports with the matching variant. The `md-remove-ios-class-effect` styles prevent iOS-specific classes from affecting Material Design mode.
46
+
47
+ ## 3. Configure animations
48
+
49
+ Keep the iOS 27 page transition and popover animations for both styled generations. Resolve these options before Ionic initializes. For Angular:
50
+
51
+ ```ts
52
+ import { isPlatform, provideIonicAngular } from '@ionic/angular/standalone'; // Ionic 8
53
+ import { iosTransitionAnimation, popoverEnterAnimation, popoverLeaveAnimation } from '@rdlabo/ionic-theme-ios27';
54
+
55
+ function loadIOSAnimations() {
56
+ if (typeof CSS === 'undefined') return {};
57
+ if (!CSS.supports('overflow-anchor: auto') && !CSS.supports('text-wrap: pretty')) return {};
58
+
59
+ return {
60
+ navAnimation: iosTransitionAnimation,
61
+ popoverEnter: popoverEnterAnimation,
62
+ popoverLeave: popoverLeaveAnimation,
63
+ };
64
+ }
65
+
66
+ provideIonicAngular(isPlatform('ios') ? loadIOSAnimations() : {});
67
+ ```
68
+
69
+ For Ionic 9 Angular, import `isPlatform` and `provideIonicAngular` from `@ionic/angular`. React and Vue can pass the same options to `setupIonicReact` or `IonicVue` during initialization. In server-rendered apps, run the selection during browser initialization.
70
+
71
+ The page-transition radius defaults to `0`. Native apps can update it after measuring the WebView:
72
+
73
+ ```ts
74
+ import { setConfig } from '@rdlabo/ionic-theme-ios27';
75
+
76
+ setConfig({ radius });
77
+ ```
78
+
79
+ ## 4. Check the theme
80
+
81
+ Test on iOS. When previewing on desktop, set Ionic mode to `ios` in your existing framework initialization config (for example `mode: 'ios'`).
82
+
83
+ Use this markup to preview the inset grouped list look. For the list structure the theme expects, see [Using ion-item-group](using-ion-item-group.md).
84
+
85
+ ```html
86
+ <ion-list mode="ios" inset="true">
87
+ <ion-item-group>
88
+ <ion-item><ion-label>Notifications</ion-label></ion-item>
89
+ <ion-item><ion-label>Appearance</ion-label></ion-item>
90
+ </ion-item-group>
91
+ </ion-list>
92
+ ```
93
+
94
+ ## Use with the MD3 theme
95
+
96
+ Install all three themes to use iOS 27 on supported Safari versions, fall back to iOS 26 on the preceding Safari generation, and use MD3 whenever Ionic runs in Material Design mode. All three themes require `@ionic/core` 8.8.1 or later:
97
+
98
+ ```bash
99
+ npm install @rdlabo/ionic-theme-ios26 @rdlabo/ionic-theme-ios27 @rdlabo/ionic-theme-md3
100
+ ```
101
+
102
+ Keep the two iOS themes behind the same browser feature checks used in step 2 above, then load MD3 unconditionally. Using `meta.load-css()` throughout also keeps the MD3 styles after the conditional iOS styles in the generated CSS:
103
+
104
+ ```scss
105
+ @use 'sass:meta';
106
+
107
+ @supports (overflow-anchor: auto) {
108
+ @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/default-variables');
109
+ @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27');
110
+ @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/ionic-theme-ios27-dark-class');
111
+ @include meta.load-css('@rdlabo/ionic-theme-ios27/src/styles/md-remove-ios-class-effect');
112
+ }
113
+
114
+ @supports (text-wrap: pretty) and (not (overflow-anchor: auto)) {
115
+ @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/default-variables');
116
+ @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26');
117
+ @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/ionic-theme-ios26-dark-class');
118
+ @include meta.load-css('@rdlabo/ionic-theme-ios26/src/styles/md-remove-ios-class-effect');
119
+ }
120
+
121
+ @include meta.load-css('@rdlabo/ionic-theme-md3/dist/css/default-variables.css');
122
+ @include meta.load-css('@rdlabo/ionic-theme-md3/dist/css/ionic-theme-md3.css');
123
+ ```
124
+
125
+ The iOS styles apply only to Ionic's `ios` mode, while MD3 applies to `md` mode. The `md-remove-ios-class-effect` stylesheet in each iOS branch prevents iOS-only utility classes from leaking into MD mode. MD3 already includes its inset-list styles, so do not load either iOS package's optional `md-ion-list-inset` stylesheet in this configuration.
126
+
127
+ Load Ionic's matching dark palette too. The example uses class-based dark mode; for system or always-dark mode, select the matching variant for Ionic and both iOS themes.
128
+
129
+ Keep the iOS 27 transition for both iOS theme generations and select the MD3 transition in Material Design mode. Extend the animation setup above as follows:
130
+
131
+ ```ts
132
+ import { isPlatform, provideIonicAngular } from '@ionic/angular/standalone'; // Ionic 8
133
+ import { iosTransitionAnimation, popoverEnterAnimation, popoverLeaveAnimation } from '@rdlabo/ionic-theme-ios27';
134
+ import { mdTransitionAnimation } from '@rdlabo/ionic-theme-md3';
135
+
136
+ function loadAnimations() {
137
+ if (!isPlatform('ios')) return { navAnimation: mdTransitionAnimation };
138
+ if (typeof CSS === 'undefined') return {};
139
+ if (!CSS.supports('overflow-anchor: auto') && !CSS.supports('text-wrap: pretty')) return {};
140
+
141
+ return {
142
+ navAnimation: iosTransitionAnimation,
143
+ popoverEnter: popoverEnterAnimation,
144
+ popoverLeave: popoverLeaveAnimation,
145
+ };
146
+ }
147
+
148
+ provideIonicAngular(loadAnimations());
149
+ ```
150
+
151
+ For Ionic 9 Angular, import `isPlatform` and `provideIonicAngular` from `@ionic/angular`. React and Vue can pass the same returned options to `setupIonicReact` or `IonicVue`. If Sass cannot resolve a package in `meta.load-css()`, use the `stylePreprocessorOptions.includePaths` or relative-path setup described above.
@@ -0,0 +1,53 @@
1
+ # Add Native UI to your theme
2
+
3
+ Already using `@rdlabo/ionic-theme-ios27`? Add native controls on iOS while keeping your theme styles, Web content, and routing. Unsupported controls, browsers, and Android retain their Web appearance.
4
+
5
+ Start with [theme setup](setup.md) if you have not configured the theme yet. If you use a different Web appearance, follow [Native UI setup](../../capacitor-native-ui/docs/setup.md) instead.
6
+
7
+ Read [how projection works](../../capacitor-native-ui/README.md#how-projection-works) for how native controls remain connected to your Web DOM and handlers.
8
+
9
+ ## 1. Install the native plugin
10
+
11
+ This guide assumes a Capacitor 8 application with an iOS project. Web-only theme users can continue without Capacitor; adding native iOS controls requires setting up that native project first. See [Native UI requirements](../../capacitor-native-ui/README.md#requirements) for supported Ionic, iOS, and Xcode versions.
12
+
13
+ Install both packages together from npm so the theme and native plugin use the same version:
14
+
15
+ ```sh
16
+ npm install @rdlabo/ionic-theme-ios27 @rdlabo/capacitor-native-ui
17
+ ```
18
+
19
+ Keep your existing theme stylesheets. Capacitor's default plugin discovery requires a direct dependency; the copy installed through the theme alone does not register the native plugin.
20
+
21
+ ## 2. Enable native controls
22
+
23
+ Enable the Shell once at application startup after `ion-app` is mounted:
24
+
25
+ ```ts
26
+ import { enableNativeUI } from '@rdlabo/capacitor-native-ui';
27
+
28
+ const shell = await enableNativeUI();
29
+ ```
30
+
31
+ Call `await shell.destroy()` when the application owner is disposed; do not create a shell for each page. Use the [Angular, React, or Vue app-root examples](../../capacitor-native-ui/docs/setup.md#place-startup-in-your-frameworks-app-root) to connect startup and cleanup.
32
+
33
+ Keep `navAnimation: iosTransitionAnimation` from the theme package. It already coordinates native controls during page transitions and back gestures; do not wrap it again. If you use a custom animation, follow the [transition adapter instructions](../../capacitor-native-ui/docs/migration.md#native-ui-transition-adapter).
34
+
35
+ Installing the plugin or configuring theme animations does not activate native controls. The application enables them with the startup call above.
36
+
37
+ ## 3. Sync and run on iOS
38
+
39
+ Build your Web assets, then run:
40
+
41
+ ```sh
42
+ npx cap sync ios
43
+ ```
44
+
45
+ Run the app on a supported iOS device or simulator. Eligible fixed controls should use native presentation and still invoke their existing handlers. Navigate forward and back, then check the same app in a browser to confirm Web fallback. If everything stays on the Web on supported iOS, confirm the direct dependency, startup call, sync, and native rebuild. See [supported controls](../../capacitor-native-ui/docs/native-ui.md#supported-markup) for eligible markup and Web fallback behavior. Retain your theme styles for Web content and controls that are not projected.
46
+
47
+ ## Optional: Vertical Bars
48
+
49
+ For a side rail, load the [Vertical Bars layout stylesheet](vertical-bars-layout.md) and configure [device placement](iphone-duo.md). The Shell includes the rail runtime; no additional startup call is needed. To project only rail controls, use `enableNativeUI({ verticalBars: { only: true } })` instead of the default startup call. See [configuration options](../../capacitor-native-ui/docs/configuration.md).
50
+
51
+ ## Upgrading an existing native integration
52
+
53
+ If you previously imported native APIs from the theme package, follow the [native import migration](../../capacitor-native-ui/docs/migration.md#2-replace-native-imports) and [native project migration](../../capacitor-native-ui/docs/migration.md#3-sync-the-native-project). Keep ordinary theme imports and stylesheets.
package/docs/animation.md CHANGED
@@ -65,7 +65,7 @@ Register after the Ionic element has initialized (for example, Angular's `ngAfte
65
65
 
66
66
  The clear button shares the 44px glass design and appears beside the input. It uses Ionic's `clearIcon`, `showClearButton`, and `--clear-button-color`; clearing retains input focus, while cancel ends the search. Clear and cancel can be displayed together. Set `clearIcon` and `cancelButtonIcon` to the same icon to give them the same appearance.
67
67
 
68
- Both external glass buttons use the iOS 27 cancel button's centered press spring, scaled down to fit the available 8px padding without clipping the border: 44px at rest, 57.2px held, with an approximately 58.26px overshoot (native: 60px held and 61.29px peak). Press and release curves follow UIKit presentation-layer measurements (iPhone 18 Pro simulator); reduced motion disables scaling. This shared external clear design intentionally differs from UIKit's small, non-expanding clear button inside the text field. Ionic supplies the icons, so glyph pixels are not identical to SF Symbols.
68
+ Both external glass buttons use a 44px resting size and expand while pressed. Reduced motion disables scaling. The clear button appears outside the text field; Ionic supplies the icons.
69
69
 
70
70
  While held, the glass background becomes opaque and the icon opacity is 0.55, preserving its configured color. Use `--background-activated` to customize the pressed background independently of `--background`.
71
71
 
package/docs/eslint.md CHANGED
@@ -60,7 +60,7 @@ Group the items and import `IonItemGroup` in the standalone component:
60
60
 
61
61
  The rule checks all `ion-list` elements, including non-inset lists. Radio, reorder, and accordion groups are also supported. It checks Angular templates; it does not inspect React or Vue templates.
62
62
 
63
- See [Using ion-item-group](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/using-ion-item-group) for layout examples and the [rule reference](https://docs.rdlabo.dev/projects/eslint-plugin-rules/docs/rules/require-ion-item-group) for supported structures and automatic fixes.
63
+ See [Using ion-item-group](using-ion-item-group.md) for layout examples and the [rule reference](https://docs.rdlabo.dev/projects/eslint-plugin-rules/docs/rules/require-ion-item-group) for supported structures and automatic fixes.
64
64
 
65
65
  ## Keep it checked
66
66
 
package/docs/features.md CHANGED
@@ -4,7 +4,7 @@ title: Features
4
4
 
5
5
  # Features
6
6
 
7
- Customize the theme with CSS variables and Sass mixins, or adopt it one component at a time. Markup-specific opt-ins are documented in [Special markup and classes](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/special-markup).
7
+ Customize the theme with CSS variables and Sass mixins, or adopt it one component at a time. Markup-specific opt-ins are documented in [Special markup and classes](special-markup.md).
8
8
 
9
9
  ## CSS variables
10
10
 
@@ -27,9 +27,9 @@ ion-textarea label.textarea-wrapper {
27
27
  }
28
28
  ```
29
29
 
30
- ## Native UI Shell (Preview)
30
+ ## Native UI (Preview)
31
31
 
32
- Capacitor iOS apps can use the optional [Native UI Shell](https://docs.rdlabo.dev/projects/ionic-theme-ios27/docs/native-ui-shell) to render supported fixed Ionic controls with UIKit and system Liquid Glass. It is available as a preview in `1.2.0`. Content and application logic stay in the WebView, with Ionic owning routing and page transitions. The guide explains the hybrid approach's origins in Basecamp and Capacitor, setup, supported controls and Web fallback behavior.
32
+ Once your theme is working, [add Native UI](add-native-ui.md) to render supported fixed controls with UIKit and SwiftUI on iOS. Keep theme styles for Web content and fallback controls. Native UI is an optional preview; adding styles alone does not enable it.
33
33
 
34
34
  ## Selective component imports
35
35