@rdlabo/ionic-theme-ios27 1.0.4 → 1.1.0-beta.pr224.shac63de393e27b

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 (96) hide show
  1. package/dist/css/components/ion-toggle.css +1 -1
  2. package/dist/css/ionic-theme-ios27.css +1 -1
  3. package/dist/native/components/searchable-tabs.d.ts.map +1 -1
  4. package/dist/native/components/searchable-tabs.js +2 -0
  5. package/dist/native/runtime.d.ts.map +1 -1
  6. package/dist/native/runtime.js +73 -7
  7. package/dist/native/shared/crossfade.d.ts +2 -2
  8. package/dist/native/shared/crossfade.d.ts.map +1 -1
  9. package/dist/native/shared/crossfade.js +3 -3
  10. package/dist/segment/index.d.ts.map +1 -1
  11. package/dist/segment/index.js +7 -0
  12. package/docs/animation.md +2 -2
  13. package/docs/native-ui-shell.md +2 -0
  14. package/package.json +2 -5
  15. package/src/native/components/searchable-tabs.ts +3 -0
  16. package/src/native/runtime.ts +81 -6
  17. package/src/native/shared/crossfade.ts +4 -3
  18. package/src/segment/index.ts +11 -0
  19. package/src/styles/components/ion-toggle.scss +8 -2
  20. package/src/tab-bar-searchable/index.ts +1 -1
  21. package/node_modules/@rdlabo/ionic-theme-utils/LICENSE +0 -21
  22. package/node_modules/@rdlabo/ionic-theme-utils/README.md +0 -28
  23. package/node_modules/@rdlabo/ionic-theme-utils/dist/dom.d.ts +0 -3
  24. package/node_modules/@rdlabo/ionic-theme-utils/dist/dom.d.ts.map +0 -1
  25. package/node_modules/@rdlabo/ionic-theme-utils/dist/dom.js +0 -13
  26. package/node_modules/@rdlabo/ionic-theme-utils/dist/dom.js.map +0 -1
  27. package/node_modules/@rdlabo/ionic-theme-utils/dist/focus-controller.d.ts +0 -6
  28. package/node_modules/@rdlabo/ionic-theme-utils/dist/focus-controller.d.ts.map +0 -1
  29. package/node_modules/@rdlabo/ionic-theme-utils/dist/focus-controller.js +0 -37
  30. package/node_modules/@rdlabo/ionic-theme-utils/dist/focus-controller.js.map +0 -1
  31. package/node_modules/@rdlabo/ionic-theme-utils/dist/index.d.ts +0 -13
  32. package/node_modules/@rdlabo/ionic-theme-utils/dist/index.d.ts.map +0 -1
  33. package/node_modules/@rdlabo/ionic-theme-utils/dist/index.js +0 -11
  34. package/node_modules/@rdlabo/ionic-theme-utils/dist/index.js.map +0 -1
  35. package/node_modules/@rdlabo/ionic-theme-utils/dist/popover/animations/ios.enter.d.ts +0 -3
  36. package/node_modules/@rdlabo/ionic-theme-utils/dist/popover/animations/ios.enter.d.ts.map +0 -1
  37. package/node_modules/@rdlabo/ionic-theme-utils/dist/popover/animations/ios.enter.js +0 -196
  38. package/node_modules/@rdlabo/ionic-theme-utils/dist/popover/animations/ios.enter.js.map +0 -1
  39. package/node_modules/@rdlabo/ionic-theme-utils/dist/popover/animations/ios.leave.d.ts +0 -3
  40. package/node_modules/@rdlabo/ionic-theme-utils/dist/popover/animations/ios.leave.d.ts.map +0 -1
  41. package/node_modules/@rdlabo/ionic-theme-utils/dist/popover/animations/ios.leave.js +0 -80
  42. package/node_modules/@rdlabo/ionic-theme-utils/dist/popover/animations/ios.leave.js.map +0 -1
  43. package/node_modules/@rdlabo/ionic-theme-utils/dist/popover/callout-surface.d.ts +0 -2
  44. package/node_modules/@rdlabo/ionic-theme-utils/dist/popover/callout-surface.d.ts.map +0 -1
  45. package/node_modules/@rdlabo/ionic-theme-utils/dist/popover/callout-surface.js +0 -45
  46. package/node_modules/@rdlabo/ionic-theme-utils/dist/popover/callout-surface.js.map +0 -1
  47. package/node_modules/@rdlabo/ionic-theme-utils/dist/popover/popover-interface.d.ts +0 -38
  48. package/node_modules/@rdlabo/ionic-theme-utils/dist/popover/popover-interface.d.ts.map +0 -1
  49. package/node_modules/@rdlabo/ionic-theme-utils/dist/popover/popover-interface.js +0 -2
  50. package/node_modules/@rdlabo/ionic-theme-utils/dist/popover/popover-interface.js.map +0 -1
  51. package/node_modules/@rdlabo/ionic-theme-utils/dist/popover/utils.d.ts +0 -38
  52. package/node_modules/@rdlabo/ionic-theme-utils/dist/popover/utils.d.ts.map +0 -1
  53. package/node_modules/@rdlabo/ionic-theme-utils/dist/popover/utils.js +0 -378
  54. package/node_modules/@rdlabo/ionic-theme-utils/dist/popover/utils.js.map +0 -1
  55. package/node_modules/@rdlabo/ionic-theme-utils/dist/sheets-of-glass.d.ts +0 -16
  56. package/node_modules/@rdlabo/ionic-theme-utils/dist/sheets-of-glass.d.ts.map +0 -1
  57. package/node_modules/@rdlabo/ionic-theme-utils/dist/sheets-of-glass.js +0 -2
  58. package/node_modules/@rdlabo/ionic-theme-utils/dist/sheets-of-glass.js.map +0 -1
  59. package/node_modules/@rdlabo/ionic-theme-utils/dist/tab-bar-searchable/interfaces.d.ts +0 -41
  60. package/node_modules/@rdlabo/ionic-theme-utils/dist/tab-bar-searchable/interfaces.d.ts.map +0 -1
  61. package/node_modules/@rdlabo/ionic-theme-utils/dist/tab-bar-searchable/interfaces.js +0 -6
  62. package/node_modules/@rdlabo/ionic-theme-utils/dist/tab-bar-searchable/interfaces.js.map +0 -1
  63. package/node_modules/@rdlabo/ionic-theme-utils/dist/tab-bar-searchable/utils.d.ts +0 -12
  64. package/node_modules/@rdlabo/ionic-theme-utils/dist/tab-bar-searchable/utils.d.ts.map +0 -1
  65. package/node_modules/@rdlabo/ionic-theme-utils/dist/tab-bar-searchable/utils.js +0 -61
  66. package/node_modules/@rdlabo/ionic-theme-utils/dist/tab-bar-searchable/utils.js.map +0 -1
  67. package/node_modules/@rdlabo/ionic-theme-utils/dist/theme.d.ts +0 -15
  68. package/node_modules/@rdlabo/ionic-theme-utils/dist/theme.d.ts.map +0 -1
  69. package/node_modules/@rdlabo/ionic-theme-utils/dist/theme.js +0 -49
  70. package/node_modules/@rdlabo/ionic-theme-utils/dist/theme.js.map +0 -1
  71. package/node_modules/@rdlabo/ionic-theme-utils/dist/transition/ios.transition.d.ts +0 -18
  72. package/node_modules/@rdlabo/ionic-theme-utils/dist/transition/ios.transition.d.ts.map +0 -1
  73. package/node_modules/@rdlabo/ionic-theme-utils/dist/transition/ios.transition.js +0 -412
  74. package/node_modules/@rdlabo/ionic-theme-utils/dist/transition/ios.transition.js.map +0 -1
  75. package/node_modules/@rdlabo/ionic-theme-utils/package.json +0 -68
  76. package/node_modules/@rdlabo/ionic-theme-utils/scripts/copy-structured-list.mjs +0 -17
  77. package/node_modules/@rdlabo/ionic-theme-utils/src/dom.spec.ts +0 -19
  78. package/node_modules/@rdlabo/ionic-theme-utils/src/dom.ts +0 -16
  79. package/node_modules/@rdlabo/ionic-theme-utils/src/focus-controller.ts +0 -38
  80. package/node_modules/@rdlabo/ionic-theme-utils/src/index.ts +0 -59
  81. package/node_modules/@rdlabo/ionic-theme-utils/src/popover/animations/ios.enter.ts +0 -259
  82. package/node_modules/@rdlabo/ionic-theme-utils/src/popover/animations/ios.leave.ts +0 -99
  83. package/node_modules/@rdlabo/ionic-theme-utils/src/popover/callout-surface.ts +0 -54
  84. package/node_modules/@rdlabo/ionic-theme-utils/src/popover/popover-interface.ts +0 -44
  85. package/node_modules/@rdlabo/ionic-theme-utils/src/popover/utils.spec.ts +0 -58
  86. package/node_modules/@rdlabo/ionic-theme-utils/src/popover/utils.ts +0 -698
  87. package/node_modules/@rdlabo/ionic-theme-utils/src/sheets-of-glass.ts +0 -17
  88. package/node_modules/@rdlabo/ionic-theme-utils/src/styles/index.scss +0 -1
  89. package/node_modules/@rdlabo/ionic-theme-utils/src/styles/md-ion-list-inset.scss +0 -20
  90. package/node_modules/@rdlabo/ionic-theme-utils/src/styles/structured-list.scss +0 -310
  91. package/node_modules/@rdlabo/ionic-theme-utils/src/tab-bar-searchable/interfaces.ts +0 -28
  92. package/node_modules/@rdlabo/ionic-theme-utils/src/tab-bar-searchable/utils.spec.ts +0 -56
  93. package/node_modules/@rdlabo/ionic-theme-utils/src/tab-bar-searchable/utils.ts +0 -75
  94. package/node_modules/@rdlabo/ionic-theme-utils/src/theme.ts +0 -51
  95. package/node_modules/@rdlabo/ionic-theme-utils/src/transition/ios.transition.spec.ts +0 -52
  96. package/node_modules/@rdlabo/ionic-theme-utils/src/transition/ios.transition.ts +0 -542
@@ -1,59 +0,0 @@
1
- export {
2
- createIosTransitionAnimation,
3
- shadow,
4
- type IosTransitionAnimationConfig,
5
- type IosTransitionAnimationOptions,
6
- } from './transition/ios.transition';
7
-
8
- export type {
9
- PopoverInterface,
10
- PopoverOptions,
11
- PopoverSize,
12
- TriggerAction,
13
- PositionReference,
14
- PositionSide,
15
- PositionAlign,
16
- } from './popover/popover-interface';
17
-
18
- export {
19
- POPOVER_IOS_BODY_MARGIN,
20
- getPopoverDimensions,
21
- configureDismissInteraction,
22
- configureTriggerInteraction,
23
- getIndexOfItem,
24
- getNextItem,
25
- getPrevItem,
26
- getPopoverPosition,
27
- calculateWindowAdjustment,
28
- type ReferenceCoordinates,
29
- type PopoverStyles,
30
- } from './popover/utils';
31
-
32
- export { createCalloutSurface } from './popover/callout-surface';
33
- export { iosEnterAnimation as iosPopoverEnterAnimation } from './popover/animations/ios.enter';
34
- export { iosLeaveAnimation as iosPopoverLeaveAnimation } from './popover/animations/ios.leave';
35
- export { cloneElement, getStep, changeSelectedElement, Config, config } from './theme';
36
- export { getElementRoot, raf } from './dom';
37
- export { createFocusController, type FocusController } from './focus-controller';
38
- export type { EffectScales, registeredEffect, AnimationPosition } from './sheets-of-glass';
39
-
40
- export {
41
- TabBarSearchableType,
42
- type TabBarSearchableFunction,
43
- type SearchableEventCache,
44
- type ElementSizes,
45
- type ElementReferences,
46
- } from './tab-bar-searchable/interfaces';
47
-
48
- export {
49
- ANIMATION_DURATION,
50
- ANIMATION_DELAY_BASE,
51
- ANIMATION_DELAY_CLOSE_BUTTONS,
52
- ANIMATION_EASING,
53
- OPACITY_TRANSITION,
54
- throwErrorByFailedClickElement,
55
- throwErrorByFailedExistElement,
56
- getElement,
57
- getElementReferences,
58
- getElementSizes,
59
- } from './tab-bar-searchable/utils';
@@ -1,259 +0,0 @@
1
- import { createAnimation } from '@ionic/core';
2
- import type { Animation } from '@ionic/core';
3
- import { calculateWindowAdjustment, getPopoverDimensions, getPopoverPosition, POPOVER_IOS_BODY_MARGIN } from '../utils';
4
- import { createCalloutSurface } from '../callout-surface';
5
- import { getElementRoot } from '../../dom';
6
-
7
- const POPOVER_IOS_BODY_PADDING = 5;
8
-
9
- /**
10
- * iOS Popover Enter Animation
11
- */
12
- // TODO(FW-2832): types
13
- export const iosEnterAnimation = (baseEl: HTMLElement, opts: any = {}): Animation => {
14
- const { event: ev, size, trigger, reference, side, align } = opts;
15
- const doc = baseEl.ownerDocument as any;
16
- const isRTL = doc.dir === 'rtl';
17
- const bodyWidth = doc.defaultView.innerWidth;
18
- const bodyHeight = doc.defaultView.innerHeight;
19
-
20
- const root = getElementRoot(baseEl);
21
- const contentEl = root.querySelector('.popover-content') as HTMLElement;
22
- const arrowEl = root.querySelector<HTMLElement>('.popover-arrow');
23
-
24
- const referenceSizeEl = trigger || ev?.detail?.ionShadowTarget || ev?.target;
25
- const anchorBounds = referenceSizeEl?.getBoundingClientRect();
26
- // Overlays live outside the page, but must stay in the pane that opened them.
27
- let paneAnchor = referenceSizeEl as HTMLElement | undefined;
28
- let pane: HTMLElement | null = null;
29
- while (paneAnchor && !pane) {
30
- pane = paneAnchor.closest('ion-content, .ion-page, ion-menu');
31
- paneAnchor = (paneAnchor.getRootNode() as ShadowRoot).host as HTMLElement | undefined;
32
- }
33
- const paneBounds = pane?.getBoundingClientRect();
34
- const scroll = pane?.shadowRoot?.querySelector<HTMLElement>('[part="scroll"]');
35
- const scrollStyle = scroll ? getComputedStyle(scroll) : null;
36
- let paneLeft = Math.max(0, (paneBounds?.left ?? 0) + (parseFloat(scrollStyle?.paddingLeft ?? '0') || 0));
37
- let paneRight = Math.min(bodyWidth, (paneBounds?.right ?? bodyWidth) - (parseFloat(scrollStyle?.paddingRight ?? '0') || 0));
38
- // Toolbars may be siblings of ion-content; exclude the visible menu there too.
39
- const splitPane = pane?.closest('ion-split-pane.split-pane-visible');
40
- if (splitPane && pane?.closest('.split-pane-main')) {
41
- for (const menu of Array.from(splitPane.querySelectorAll<HTMLElement>(':scope > ion-menu.menu-pane-visible'))) {
42
- const rect = menu.getBoundingClientRect();
43
- if (rect.left <= paneLeft && rect.right > paneLeft) paneLeft = rect.right;
44
- if (rect.right >= paneRight && rect.left < paneRight) paneRight = rect.left;
45
- }
46
- }
47
- const paneMargin = size === 'cover' ? 0 : POPOVER_IOS_BODY_MARGIN;
48
- const availableWidth = Math.max(0, paneRight - paneLeft - paneMargin * 2);
49
- if (pane && availableWidth > 0 && contentEl.getBoundingClientRect().width > availableWidth) {
50
- contentEl.dataset['previousMaxWidth'] = contentEl.style.maxWidth;
51
- contentEl.dataset['previousMaxWidthPriority'] = contentEl.style.getPropertyPriority('max-width');
52
- contentEl.style.maxWidth = `${availableWidth}px`;
53
- }
54
- const { contentWidth, contentHeight } = getPopoverDimensions(size, contentEl, referenceSizeEl);
55
-
56
- const isReplace = ((): boolean => {
57
- if (reference === 'event' || !referenceSizeEl || !['ion-button', 'ion-buttons'].includes(referenceSizeEl.localName)) {
58
- return false;
59
- }
60
- if (referenceSizeEl.matches('.ios-theme-disabled, .ios26-disabled')) {
61
- return false;
62
- }
63
- return true;
64
- })();
65
-
66
- const defaultPosition = {
67
- top: bodyHeight / 2 - contentHeight / 2,
68
- left: bodyWidth / 2 - contentWidth / 2,
69
- originX: isRTL ? 'right' : 'left',
70
- originY: 'top',
71
- };
72
-
73
- const results = getPopoverPosition(isRTL, contentWidth, contentHeight, reference, side, align, defaultPosition, trigger, ev);
74
-
75
- // Use the same reference for placement, the callout and the animation origin.
76
- const anchor = reference === 'event' ? results.referenceCoordinates : anchorBounds;
77
-
78
- const padding = size === 'cover' ? 0 : POPOVER_IOS_BODY_PADDING;
79
- const margin = size === 'cover' ? 0 : POPOVER_IOS_BODY_MARGIN;
80
-
81
- const {
82
- originX,
83
- originY,
84
- top,
85
- left: windowLeft,
86
- bottom,
87
- checkSafeAreaLeft,
88
- checkSafeAreaRight,
89
- addPopoverBottomClass,
90
- } = calculateWindowAdjustment(
91
- side,
92
- results.top,
93
- results.left,
94
- padding,
95
- bodyWidth,
96
- bodyHeight,
97
- contentWidth,
98
- contentHeight,
99
- margin,
100
- results.originX,
101
- results.originY,
102
- results.referenceCoordinates,
103
- referenceSizeEl?.getBoundingClientRect(),
104
- isReplace,
105
- );
106
- // A replacing surface grows inward from the button's edge, not its center.
107
- const preferredLeft =
108
- isReplace && anchorBounds
109
- ? anchorBounds.left + anchorBounds.width / 2 <= (paneLeft + paneRight) / 2
110
- ? anchorBounds.left
111
- : anchorBounds.right - contentWidth
112
- : windowLeft;
113
- const left = pane ? Math.max(paneLeft + paneMargin, Math.min(paneRight - paneMargin - contentWidth, preferredLeft)) : preferredLeft;
114
- const physicalSide = side === 'start' ? (isRTL ? 'right' : 'left') : side === 'end' ? (isRTL ? 'left' : 'right') : side;
115
- const horizontal = physicalSide === 'left' || physicalSide === 'right';
116
- const above = addPopoverBottomClass || physicalSide === 'top';
117
- const hasCallout = !!arrowEl && !isReplace && !!anchor && size !== 'cover' && bottom === undefined;
118
- const surfaceTop = hasCallout && !horizontal ? top + (above ? -5 : 5) : top;
119
- const contentOrigin = anchor
120
- ? `${anchor.left + anchor.width / 2 - left}px ${anchor.top + anchor.height / 2 - surfaceTop}px`
121
- : `${originX} ${originY}`;
122
-
123
- const baseAnimation = createAnimation();
124
- const backdropAnimation = createAnimation();
125
- const contentAnimation = createAnimation();
126
- const targetAnimation = createAnimation();
127
- const arrowAnimation = createAnimation();
128
- const surfaceAnimation = createAnimation();
129
- if (arrowEl) {
130
- arrowAnimation.addElement(arrowEl).delay(300).duration(200).fromTo('opacity', 0, 1);
131
- }
132
- if (hasCallout && anchor) {
133
- const length = horizontal ? contentHeight : contentWidth;
134
- const inset = Math.min(48, length / 2);
135
- const center = Math.max(
136
- inset,
137
- Math.min(length - inset, horizontal ? anchor.top + anchor.height / 2 - top : anchor.left + anchor.width / 2 - left),
138
- );
139
- const arrowSide = horizontal ? (physicalSide === 'left' ? 'right' : 'left') : above ? 'bottom' : 'top';
140
- const layers = createCalloutSurface(root, contentWidth, contentHeight, arrowSide, center);
141
- baseEl.classList.add('ios-theme-callout');
142
- for (const layer of layers) {
143
- layer.style.left = `calc(${left - 32}px + var(--offset-x, 0))`;
144
- layer.style.top = `calc(${surfaceTop - 32}px + var(--offset-y, 0))`;
145
- const [originLeft, originTop] = contentOrigin.split(' ').map(parseFloat);
146
- layer.style.transformOrigin = `${originLeft + 32}px ${originTop + 32}px`;
147
- }
148
- surfaceAnimation
149
- .addElement(layers)
150
- .delay(100)
151
- .duration(400)
152
- .easing('cubic-bezier(0, 1, 0.22, 1)')
153
- .fromTo('transform', 'scale(0)', 'scale(1)')
154
- .fromTo('opacity', 0.01, 1);
155
- }
156
-
157
- backdropAnimation
158
- .delay(100)
159
- .duration(300)
160
- .addElement(root.querySelector('ion-backdrop')!)
161
- .fromTo('opacity', 0.01, 'var(--backdrop-opacity)')
162
- .beforeStyles({
163
- 'pointer-events': 'none',
164
- })
165
- .afterClearStyles(['pointer-events']);
166
-
167
- // In Chromium, if the wrapper animates, the backdrop filter doesn't work.
168
- // The Chromium team stated that this behavior is expected and not a bug. The element animating opacity creates a backdrop root for the backdrop-filter.
169
- // To get around this, instead of animating the wrapper, animate content.
170
- // https://bugs.chromium.org/p/chromium/issues/detail?id=1148826
171
- contentAnimation
172
- .easing('cubic-bezier(0, 1, 0.22, 1)')
173
- .delay(100)
174
- .duration(400)
175
- .addElement(root.querySelector('.popover-content')!)
176
- .beforeStyles({ 'transform-origin': contentOrigin })
177
- .beforeAddWrite(() => {
178
- /**
179
- * 'transformOrigin' use for leave animation.
180
- */
181
- root.querySelector<HTMLElement>('.popover-content')!.dataset['transformOrigin'] = contentOrigin;
182
- })
183
- .fromTo('transform', 'scale(0)', 'scale(1)')
184
- .fromTo('opacity', 0.01, 1);
185
-
186
- if (isReplace) {
187
- targetAnimation
188
- .delay(0)
189
- .duration(200)
190
- .addElement(referenceSizeEl)
191
- .beforeStyles({ 'transform-origin': `${originY} ${originX}` })
192
- .beforeAddClass('ios-theme-replace-element')
193
- .fromTo('transform', 'scale(1)', 'scale(1.05)')
194
- .fromTo('opacity', 1, 0);
195
- }
196
-
197
- return baseAnimation
198
- .easing('ease')
199
- .delay(100)
200
- .duration(100)
201
- .beforeAddWrite(() => {
202
- if (size === 'cover') {
203
- baseEl.dataset['iosThemePreviousWidth'] = baseEl.style.getPropertyValue('--width');
204
- baseEl.dataset['iosThemePreviousWidthPriority'] = baseEl.style.getPropertyPriority('--width');
205
- baseEl.style.setProperty('--width', `${contentWidth}px`);
206
- }
207
-
208
- if (addPopoverBottomClass) {
209
- baseEl.classList.add('popover-bottom');
210
- }
211
-
212
- if (bottom !== undefined) {
213
- contentEl.style.setProperty('bottom', `${bottom}px`);
214
- }
215
-
216
- const safeAreaLeft = ' + var(--ion-safe-area-left, 0)';
217
- const safeAreaRight = ' - var(--ion-safe-area-right, 0)';
218
-
219
- let leftValue = `${left}px`;
220
-
221
- if (checkSafeAreaLeft && !pane) {
222
- leftValue = `${left}px${safeAreaLeft}`;
223
- }
224
- if (checkSafeAreaRight && !pane) {
225
- leftValue = `${left}px${safeAreaRight}`;
226
- }
227
-
228
- contentEl.style.setProperty('top', `calc(${surfaceTop}px + var(--offset-y, 0))`);
229
- contentEl.style.setProperty('left', `calc(${leftValue} + var(--offset-x, 0))`);
230
- contentEl.style.setProperty('transform-origin', contentOrigin);
231
-
232
- // Morphing buttons replace their anchor; ordinary anchored popovers point to it.
233
- if (arrowEl) {
234
- arrowEl.style.display = 'none';
235
- if (hasCallout && anchor) {
236
- const inset = Math.min(48, (horizontal ? contentHeight : contentWidth) / 2);
237
- const clamp = (value: number, length: number) => Math.max(inset, Math.min(length - inset, value));
238
- let arrowLeft: number;
239
- let arrowTop: number;
240
- let rotation: number;
241
- if (horizontal) {
242
- arrowLeft = physicalSide === 'left' ? left + contentWidth - 10 : left - 24;
243
- arrowTop = top + clamp(anchor.top + anchor.height / 2 - top, contentHeight) - 7;
244
- rotation = physicalSide === 'left' ? 90 : -90;
245
- } else {
246
- arrowLeft = left + clamp(anchor.left + anchor.width / 2 - left, contentWidth) - 17;
247
- arrowTop = above ? surfaceTop + contentHeight - 1 : surfaceTop - 13;
248
- rotation = above ? 180 : 0;
249
- }
250
- arrowEl.style.setProperty('display', 'block');
251
- arrowEl.style.setProperty('top', `calc(${arrowTop}px + var(--offset-y, 0))`);
252
- arrowEl.style.setProperty('left', `calc(${arrowLeft}px + var(--offset-x, 0))`);
253
- arrowEl.style.setProperty('bottom', 'auto');
254
- arrowEl.style.setProperty('transform', `rotate(${rotation}deg)`);
255
- }
256
- }
257
- })
258
- .addAnimation([backdropAnimation, contentAnimation, targetAnimation, arrowAnimation, surfaceAnimation]);
259
- };
@@ -1,99 +0,0 @@
1
- import { createAnimation } from '@ionic/core';
2
- import { getElementRoot } from '../../dom';
3
-
4
- import type { Animation } from '@ionic/core';
5
-
6
- /**
7
- * iOS Popover Leave Animation
8
- */
9
- export const iosLeaveAnimation = (baseEl: HTMLElement): Animation => {
10
- const root = getElementRoot(baseEl);
11
- const contentEl = root.querySelector('.popover-content') as HTMLElement;
12
- const arrowEl = root.querySelector('.popover-arrow') as HTMLElement | null;
13
-
14
- const baseAnimation = createAnimation();
15
- const backdropAnimation = createAnimation();
16
- const contentAnimation = createAnimation();
17
- const targetAnimation = createAnimation();
18
- const surfaceAnimation = createAnimation()
19
- .addElement(Array.from(root.querySelectorAll<HTMLElement>('.ios-theme-callout-layer')))
20
- .duration(400)
21
- .easing('ease')
22
- .fromTo('opacity', 0.99, 0)
23
- .fromTo('transform', 'scale(1)', 'scale(0)');
24
-
25
- const doc = baseEl.ownerDocument as any;
26
- const replaceElement = doc.querySelector('.ios-theme-replace-element') as HTMLElement | null;
27
-
28
- if (replaceElement) {
29
- const ratio = contentEl.getBoundingClientRect().width / contentEl.getBoundingClientRect().height;
30
- const scale = ratio > 1 ? `${Math.min(1.2, 1.05 * ratio)}, 1.05` : `1.05, ${Math.min(1.2, 1.05 * ratio)}`;
31
-
32
- targetAnimation
33
- .addElement(replaceElement)
34
- .delay(100)
35
- .duration(300)
36
- .afterRemoveClass('ios-theme-replace-element')
37
- .fromTo('transform', `scale(${scale})`, 'scale(1)')
38
- .fromTo('opacity', 0, 0.9);
39
- }
40
-
41
- backdropAnimation.addElement(root.querySelector('ion-backdrop')!).fromTo('opacity', 'var(--backdrop-opacity)', 0);
42
-
43
- contentAnimation
44
- .duration(400)
45
- .easing('ease')
46
- .addElement(root.querySelector('.popover-arrow')!)
47
- .addElement(root.querySelector('.popover-content')!)
48
- .fromTo('opacity', 0.99, 0);
49
-
50
- const popoverContentDataset = root.querySelector<HTMLElement>('.popover-content')!.dataset['transformOrigin'];
51
- if (popoverContentDataset) {
52
- contentAnimation.beforeStyles({ 'transform-origin': popoverContentDataset }).afterAddRead(() => {
53
- root.querySelector<HTMLElement>('.popover-content')!.dataset['transformOrigin'] = '';
54
- });
55
-
56
- if (replaceElement) {
57
- contentAnimation.fromTo('transform', 'scale(1)', 'scale(0.55)');
58
- } else {
59
- contentAnimation.fromTo('transform', 'scale(1)', 'scale(0)');
60
- }
61
- }
62
-
63
- return baseAnimation
64
- .easing('ease')
65
- .afterAddWrite(() => {
66
- if (baseEl.dataset['iosThemePreviousWidth'] !== undefined) {
67
- baseEl.style.setProperty('--width', baseEl.dataset['iosThemePreviousWidth'], baseEl.dataset['iosThemePreviousWidthPriority'] ?? '');
68
- delete baseEl.dataset['iosThemePreviousWidth'];
69
- delete baseEl.dataset['iosThemePreviousWidthPriority'];
70
- }
71
- baseEl.classList.remove('popover-bottom');
72
- baseEl.classList.remove('ios-theme-callout');
73
- root.querySelectorAll('.ios-theme-callout-layer').forEach((layer) => layer.remove());
74
-
75
- contentEl.style.removeProperty('top');
76
- contentEl.style.removeProperty('left');
77
- contentEl.style.removeProperty('bottom');
78
- contentEl.style.removeProperty('transform-origin');
79
- if (contentEl.dataset['previousMaxWidth'] !== undefined) {
80
- contentEl.style.setProperty(
81
- 'max-width',
82
- contentEl.dataset['previousMaxWidth'],
83
- contentEl.dataset['previousMaxWidthPriority'] ?? '',
84
- );
85
- delete contentEl.dataset['previousMaxWidth'];
86
- delete contentEl.dataset['previousMaxWidthPriority'];
87
- }
88
-
89
- if (arrowEl) {
90
- arrowEl.style.removeProperty('top');
91
- arrowEl.style.removeProperty('left');
92
- arrowEl.style.removeProperty('display');
93
- arrowEl.style.removeProperty('bottom');
94
- arrowEl.style.removeProperty('transform');
95
- }
96
- })
97
- .duration(300)
98
- .addAnimation([backdropAnimation, contentAnimation, targetAnimation, surfaceAnimation]);
99
- };
@@ -1,54 +0,0 @@
1
- /** Draw one glass silhouette so the arrow cannot sample the body's own border/shadow. */
2
- export const createCalloutSurface = (
3
- root: ShadowRoot | HTMLElement,
4
- width: number,
5
- height: number,
6
- side: 'top' | 'right' | 'bottom' | 'left',
7
- center: number,
8
- ): HTMLElement[] => {
9
- const inset = 32;
10
- const x = inset;
11
- const y = inset;
12
- const right = x + width;
13
- const bottom = y + height;
14
- const radius = Math.max(0, Math.min(34, width / 2, height / 2, (side === 'left' || side === 'right' ? height : width) / 2 - 17));
15
- const point = (u: number, v: number): string => {
16
- switch (side) {
17
- case 'top':
18
- return `${x + center - 17 + u} ${y - 13 + v}`;
19
- case 'right':
20
- return `${right + 13 - v} ${y + center - 17 + u}`;
21
- case 'bottom':
22
- return `${x + center + 17 - u} ${bottom + 13 - v}`;
23
- case 'left':
24
- return `${x - 13 + v} ${y + center + 17 - u}`;
25
- }
26
- };
27
- const arrow = `L ${point(0, 13)} C ${point(3, 13)} ${point(4.5, 12)} ${point(6.5, 10)} L ${point(14.5, 1.5)} C ${point(16, -0.5)} ${point(18, -0.5)} ${point(19.5, 1.5)} L ${point(27.5, 10)} C ${point(29.5, 12)} ${point(31, 13)} ${point(34, 13)}`;
28
- const path = `M ${x + radius} ${y} ${side === 'top' ? arrow : ''}
29
- L ${right - radius} ${y} A ${radius} ${radius} 0 0 1 ${right} ${y + radius} ${side === 'right' ? arrow : ''}
30
- L ${right} ${bottom - radius} A ${radius} ${radius} 0 0 1 ${right - radius} ${bottom} ${side === 'bottom' ? arrow : ''}
31
- L ${x + radius} ${bottom} A ${radius} ${radius} 0 0 1 ${x} ${bottom - radius} ${side === 'left' ? arrow : ''}
32
- L ${x} ${y + radius} A ${radius} ${radius} 0 0 1 ${x + radius} ${y} Z`;
33
- const svg = (body: string) =>
34
- `url("data:image/svg+xml,${encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" width="${width + inset * 2}" height="${height + inset * 2}">${body}</svg>`)}")`;
35
- const fill = svg(`<path d="${path}" fill="white"/>`);
36
- const stroke = svg(`<path d="${path}" fill="none" stroke="white" stroke-width="0.5"/>`);
37
- const shadow = svg(
38
- `<defs><filter id="s" x="-50%" y="-100%" width="200%" height="300%"><feGaussianBlur stdDeviation="4"/><feOffset dy="2"/><feComposite in2="SourceGraphic" operator="out"/></filter></defs><path d="${path}" fill="white" filter="url(#s)"/>`,
39
- );
40
- const wrapper = root.querySelector('.popover-wrapper')!;
41
- return [
42
- ['callout-shadow', shadow],
43
- ['callout-glass', fill],
44
- ['callout-border', stroke],
45
- ].map(([part, mask]) => {
46
- const element = document.createElement('div');
47
- element.setAttribute('part', part);
48
- element.className = 'ios-theme-callout-layer';
49
- element.setAttribute('aria-hidden', 'true');
50
- element.style.cssText = `position:absolute;pointer-events:none;z-index:9;width:${width + inset * 2}px;height:${height + inset * 2}px;mask-image:${mask};-webkit-mask-image:${mask};mask-repeat:no-repeat;`;
51
- wrapper.append(element);
52
- return element;
53
- });
54
- };
@@ -1,44 +0,0 @@
1
- import type { AnimationBuilder, ComponentProps, ComponentRef, FrameworkDelegate, Mode, OverlayInterface } from '@ionic/core';
2
-
3
- export interface PopoverInterface extends OverlayInterface {
4
- present: (event?: MouseEvent | TouchEvent | PointerEvent) => Promise<void>;
5
- }
6
-
7
- export interface PopoverOptions<T extends ComponentRef = ComponentRef> {
8
- component: T;
9
- componentProps?: ComponentProps<T>;
10
- showBackdrop?: boolean;
11
- backdropDismiss?: boolean;
12
- translucent?: boolean;
13
- cssClass?: string | string[];
14
- event?: Event;
15
- delegate?: FrameworkDelegate;
16
- animated?: boolean;
17
- focusTrap?: boolean;
18
-
19
- mode?: Mode;
20
- keyboardClose?: boolean;
21
- id?: string;
22
- htmlAttributes?: { [key: string]: any };
23
-
24
- enterAnimation?: AnimationBuilder;
25
- leaveAnimation?: AnimationBuilder;
26
-
27
- size?: PopoverSize;
28
- dismissOnSelect?: boolean;
29
- reference?: PositionReference;
30
- side?: PositionSide;
31
- alignment?: PositionAlign;
32
- arrow?: boolean;
33
-
34
- trigger?: string;
35
- triggerAction?: string;
36
- }
37
-
38
- export type PopoverSize = 'cover' | 'auto';
39
-
40
- export type TriggerAction = 'click' | 'hover' | 'context-menu';
41
-
42
- export type PositionReference = 'trigger' | 'event';
43
- export type PositionSide = 'top' | 'right' | 'bottom' | 'left' | 'start' | 'end';
44
- export type PositionAlign = 'start' | 'center' | 'end';
@@ -1,58 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import { calculateWindowAdjustment, getIndexOfItem, getNextItem, getPopoverPosition, getPrevItem } from './utils';
4
-
5
- describe('popover utilities', () => {
6
- it('navigates only relative to ion-item elements', () => {
7
- const items = [document.createElement('ion-item'), document.createElement('ion-item')] as HTMLIonItemElement[];
8
-
9
- expect(getIndexOfItem(items, items[0])).toBe(0);
10
- expect(getIndexOfItem(items, document.createElement('div'))).toBe(-1);
11
- expect(getNextItem(items, items[0])).toBe(items[1]);
12
- expect(getPrevItem(items, items[1])).toBe(items[0]);
13
- });
14
-
15
- it('keeps a popover inside the left safe-area boundary', () => {
16
- expect(calculateWindowAdjustment('bottom', 20, -10, 16, 390, 844, 280, 140, 8, 'center', 'top')).toMatchObject({
17
- top: 28,
18
- left: 16,
19
- originX: 'left',
20
- checkSafeAreaLeft: true,
21
- checkSafeAreaRight: false,
22
- });
23
- });
24
-
25
- it('keeps a popover inside the right safe-area boundary', () => {
26
- expect(calculateWindowAdjustment('bottom', 20, 150, 16, 390, 844, 280, 140, 8, 'center', 'top')).toMatchObject({
27
- top: 28,
28
- left: 94,
29
- originX: 'right',
30
- checkSafeAreaLeft: false,
31
- checkSafeAreaRight: true,
32
- });
33
- });
34
-
35
- it.each([
36
- { isRTL: false, side: 'start' as const, left: 60, originX: 'right' },
37
- { isRTL: false, side: 'end' as const, left: 140, originX: 'left' },
38
- { isRTL: true, side: 'start' as const, left: 140, originX: 'left' },
39
- { isRTL: true, side: 'end' as const, left: 60, originX: 'right' },
40
- ])('maps the logical $side side in RTL=$isRTL', ({ isRTL, side, left, originX }) => {
41
- const trigger = document.createElement('button');
42
- trigger.getBoundingClientRect = () => ({
43
- top: 20,
44
- left: 100,
45
- width: 40,
46
- height: 30,
47
- right: 140,
48
- bottom: 50,
49
- x: 100,
50
- y: 20,
51
- toJSON: () => ({}),
52
- });
53
-
54
- expect(
55
- getPopoverPosition(isRTL, 40, 20, 'trigger', side, 'start', { top: 0, left: 0, originX: 'left', originY: 'top' }, trigger),
56
- ).toMatchObject({ left, originX });
57
- });
58
- });