@rdlabo/ionic-theme-ios27 1.2.0-1 → 1.2.0-3

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 (75) hide show
  1. package/README.md +9 -0
  2. package/dist/css/components/ion-tabs.css +1 -1
  3. package/dist/css/ionic-theme-ios27.css +1 -1
  4. package/dist/css/vertical-bars.css +1 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +31 -12
  7. package/dist/native/components/index.d.ts.map +1 -1
  8. package/dist/native/components/index.js +4 -1
  9. package/dist/native/components/ion-button.d.ts.map +1 -1
  10. package/dist/native/components/ion-button.js +3 -3
  11. package/dist/native/components/ion-buttons.d.ts.map +1 -1
  12. package/dist/native/components/ion-buttons.js +3 -2
  13. package/dist/native/components/searchable-tabs.d.ts.map +1 -1
  14. package/dist/native/components/searchable-tabs.js +6 -7
  15. package/dist/native/definitions.d.ts +8 -15
  16. package/dist/native/definitions.d.ts.map +1 -1
  17. package/dist/native/definitions.js +1 -6
  18. package/dist/native/index.d.ts +1 -2
  19. package/dist/native/index.d.ts.map +1 -1
  20. package/dist/native/index.js +64 -28
  21. package/dist/native/prehide.d.ts.map +1 -1
  22. package/dist/native/prehide.js +6 -4
  23. package/dist/native/runtime.d.ts.map +1 -1
  24. package/dist/native/runtime.js +40 -19
  25. package/dist/native/shared/candidate.d.ts.map +1 -1
  26. package/dist/native/shared/candidate.js +8 -0
  27. package/dist/native/shared/dom.d.ts.map +1 -1
  28. package/dist/native/shared/dom.js +11 -10
  29. package/dist/native/shared/modal.d.ts +8 -0
  30. package/dist/native/shared/modal.d.ts.map +1 -0
  31. package/dist/native/shared/modal.js +143 -0
  32. package/dist/native/vertical-bars-web.d.ts.map +1 -1
  33. package/dist/native/vertical-bars-web.js +11 -3
  34. package/dist/sheets-of-glass/animations.d.ts +13 -4
  35. package/dist/sheets-of-glass/animations.d.ts.map +1 -1
  36. package/dist/sheets-of-glass/animations.js +56 -40
  37. package/dist/sheets-of-glass/index.d.ts +1 -1
  38. package/dist/sheets-of-glass/index.d.ts.map +1 -1
  39. package/dist/sheets-of-glass/index.js +64 -20
  40. package/dist/tab-bar-searchable/index.d.ts.map +1 -1
  41. package/dist/tab-bar-searchable/index.js +39 -11
  42. package/dist/vertical-bars.d.ts +1 -2
  43. package/dist/vertical-bars.d.ts.map +1 -1
  44. package/dist/vertical-bars.js +0 -1
  45. package/docs/iphone-duo-with-original-theme.md +37 -15
  46. package/docs/iphone-duo.md +90 -47
  47. package/docs/migration.md +3 -3
  48. package/docs/native-ui-shell.md +12 -8
  49. package/docs/special-markup.md +0 -13
  50. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellSearchController.swift +1 -1
  51. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellVerticalBars.swift +50 -6
  52. package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellVerticalSearch.swift +118 -0
  53. package/ios/Sources/IonicNativeUIShellPlugin/IonicNativeUIShellPlugin.swift +32 -179
  54. package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellSnapshot.swift +5 -0
  55. package/ios/Tests/IonicNativeUIShellPluginTests/ShellSnapshotTests.swift +67 -0
  56. package/package.json +1 -1
  57. package/src/index.ts +40 -9
  58. package/src/native/components/index.ts +6 -1
  59. package/src/native/components/ion-button.ts +4 -3
  60. package/src/native/components/ion-buttons.ts +4 -2
  61. package/src/native/components/searchable-tabs.ts +8 -11
  62. package/src/native/definitions.ts +11 -19
  63. package/src/native/index.ts +67 -30
  64. package/src/native/prehide.ts +7 -4
  65. package/src/native/runtime.ts +40 -18
  66. package/src/native/shared/candidate.ts +10 -0
  67. package/src/native/shared/dom.ts +11 -10
  68. package/src/native/shared/modal.ts +157 -0
  69. package/src/native/vertical-bars-web.ts +12 -3
  70. package/src/sheets-of-glass/animations.ts +66 -41
  71. package/src/sheets-of-glass/index.ts +82 -16
  72. package/src/styles/components/ion-tabs.scss +7 -4
  73. package/src/styles/vertical-bars.scss +110 -35
  74. package/src/tab-bar-searchable/index.ts +40 -9
  75. package/src/vertical-bars.ts +1 -2
@@ -1,5 +1,18 @@
1
1
  import { createAnimation } from '@ionic/core';
2
2
  import { getStep } from '../utils';
3
+ export const animationRect = (element, vertical = false) => {
4
+ const rect = element.getBoundingClientRect();
5
+ return vertical
6
+ ? { left: rect.top, top: rect.left, right: rect.bottom, width: rect.height, height: rect.width, x: rect.y, y: rect.x }
7
+ : rect;
8
+ };
9
+ const translate = (x, y, vertical) => `translate3d(${vertical ? y : x}px, ${vertical ? x : y}px, 0)`;
10
+ const stretchTransform = (x, y, vertical) => `scale(${vertical ? y : x}, ${vertical ? x : y})`;
11
+ const mainSize = (element, vertical) => (vertical ? element.offsetHeight : element.offsetWidth);
12
+ const axisScale = (transform, vertical) => {
13
+ const matrix = new DOMMatrixReadOnly(transform);
14
+ return vertical ? { a: matrix.d, d: matrix.a } : matrix;
15
+ };
3
16
  export const getScaleAnimation = (effectElement) => {
4
17
  return createAnimation().addElement(effectElement.shadowRoot.querySelector('[part="native"]')).easing('ease-out');
5
18
  };
@@ -37,66 +50,67 @@ const tabBarTapFrames = [
37
50
  [0.7, 1.003, -0.74, 0.59],
38
51
  [0.8, 1, 0, 0],
39
52
  ];
40
- export const createTabBarPressAnimation = (effect, from, to, bar) => {
41
- const box = bar.getBoundingClientRect();
53
+ export const createTabBarPressAnimation = (effect, from, to, bar, vertical = false) => {
54
+ const box = animationRect(bar, vertical);
42
55
  const center = box.left + box.width / 2;
43
- const left = center - bar.offsetWidth / 2;
44
- const fromBox = from.getBoundingClientRect();
45
- const toBox = to.getBoundingClientRect();
46
- const parentScale = box.width / bar.offsetWidth;
47
- const buttonScale = new DOMMatrixReadOnly(getComputedStyle(from).transform);
56
+ const left = center - mainSize(bar, vertical) / 2;
57
+ const fromBox = animationRect(from, vertical);
58
+ const toBox = animationRect(to, vertical);
59
+ const parentScale = box.width / mainSize(bar, vertical);
60
+ const buttonScale = axisScale(getComputedStyle(from).transform, vertical);
48
61
  const width = fromBox.width / parentScale / buttonScale.a;
49
62
  const height = fromBox.height / parentScale / buttonScale.d;
50
63
  const start = (fromBox.left + fromBox.width / 2 - box.left) / parentScale;
51
64
  const end = (toBox.left + toBox.width / 2 - box.left) / parentScale;
52
65
  const selected = from === to;
66
+ const growth = vertical ? 20 / 16 : 1;
53
67
  const frames = tabBarPressFrames.map(([time, position, extraWidth, extraHeight, expansion]) => {
54
- const scale = 1 + (14.14 / bar.offsetWidth) * expansion;
68
+ const scale = vertical ? 1 : 1 + (14.14 / mainSize(bar, vertical)) * expansion;
55
69
  const press = 1 - Math.exp(-18 * time) * (1 + 18 * time);
56
70
  return {
57
71
  offset: time / 0.9,
58
72
  x: center + (left + start + (end - start) * position - center) * scale - width / 2,
59
- sx: ((width + (selected ? 16 * press : extraWidth)) * scale) / width,
60
- sy: ((height + (selected ? 16 * press : extraHeight)) * scale) / height,
73
+ sx: ((width + (selected ? 16 * press : extraWidth) * growth) * scale) / width,
74
+ sy: ((height + (selected ? 16 * press : extraHeight) * growth) * scale) / height,
61
75
  };
62
76
  });
63
77
  const move = createAnimation()
64
78
  .addElement(effect)
65
79
  .beforeStyles({
66
- width: `${width}px`,
67
- height: `${height}px`,
80
+ width: `${vertical ? height : width}px`,
81
+ height: `${vertical ? width : height}px`,
68
82
  display: 'block',
69
83
  opacity: '1',
70
84
  })
71
- .keyframes(frames.map(({ offset, x }) => ({ offset, transform: `translate3d(${x}px, ${box.top + box.height / 2 - height / 2}px, 0)` })));
85
+ .keyframes(frames.map(({ offset, x }) => ({ offset, transform: translate(x, box.top + box.height / 2 - height / 2, vertical) })));
72
86
  const stretch = getScaleAnimation(effect)
73
87
  .easing('linear')
74
- .keyframes(frames.map(({ offset, sx, sy }) => ({ offset, transform: `scale(${sx}, ${sy})` })));
88
+ .keyframes(frames.map(({ offset, sx, sy }) => ({ offset, transform: stretchTransform(sx, sy, vertical) })));
75
89
  return createAnimation().duration(900).easing('linear').addAnimation([move, stretch]);
76
90
  };
77
- export const createTabBarDragAnimation = (effect, bar, velocity) => {
91
+ export const createTabBarDragAnimation = (effect, bar, velocity, vertical = false) => {
78
92
  const native = effect.shadowRoot.querySelector('[part="native"]');
79
- const current = new DOMMatrixReadOnly(native.style.transform || getComputedStyle(native).transform);
80
- const { width, height } = effect.getBoundingClientRect();
81
- const scale = 1 + 14.14 / bar.offsetWidth;
82
- const restX = ((width + 16) * scale) / width;
83
- const restY = ((height + 16) * scale) / height;
93
+ const current = axisScale(native.style.transform || getComputedStyle(native).transform, vertical);
94
+ const { width, height } = animationRect(effect, vertical);
95
+ const scale = vertical ? 1 : 1 + 14.14 / mainSize(bar, vertical);
96
+ const restX = ((width + (vertical ? 20 : 16)) * scale) / width;
97
+ const restY = ((height + (vertical ? 20 : 16)) * scale) / height;
84
98
  const stretch = Math.min(16, 32 * velocity * velocity) * scale;
85
99
  const rebound = Math.max(stretch, (current.a - restX) * width) * 0.8;
86
100
  return getScaleAnimation(effect)
87
101
  .duration(500)
88
102
  .easing('linear')
89
103
  .keyframes([
90
- { offset: 0, transform: `scale(${current.a}, ${current.d})` },
91
- { offset: 0.2, transform: `scale(${restX + stretch / width}, ${restY - stretch / height})` },
92
- { offset: 0.44, transform: `scale(${restX - rebound / width}, ${restY + rebound / height})` },
93
- { offset: 1, transform: `scale(${restX}, ${restY})` },
104
+ { offset: 0, transform: stretchTransform(current.a, current.d, vertical) },
105
+ { offset: 0.2, transform: stretchTransform(restX + stretch / width, restY - stretch / height, vertical) },
106
+ { offset: 0.44, transform: stretchTransform(restX - rebound / width, restY + rebound / height, vertical) },
107
+ { offset: 1, transform: stretchTransform(restX, restY, vertical) },
94
108
  ]);
95
109
  };
96
- export const createTabBarReleaseAnimation = (effectElement, target, tap) => {
110
+ export const createTabBarReleaseAnimation = (effectElement, target, tap, vertical = false) => {
97
111
  const tapElapsed = tap?.elapsed;
98
112
  const native = effectElement.shadowRoot.querySelector('[part="native"]');
99
- const scale = new DOMMatrixReadOnly(native.style.transform || getComputedStyle(native).transform);
113
+ const scale = axisScale(native.style.transform || getComputedStyle(native).transform, vertical);
100
114
  const samples = [
101
115
  [0, 1],
102
116
  [0.033, 0.85],
@@ -110,22 +124,24 @@ export const createTabBarReleaseAnimation = (effectElement, target, tap) => {
110
124
  [0.45, 0],
111
125
  ];
112
126
  const bar = target.parentElement;
113
- const box = bar.getBoundingClientRect();
114
- const current = effectElement.getBoundingClientRect();
115
- const targetBox = target.getBoundingClientRect();
127
+ const box = animationRect(bar, vertical);
128
+ const current = animationRect(effectElement, vertical);
129
+ const targetBox = animationRect(target, vertical);
116
130
  const center = box.left + box.width / 2;
117
- const x = center + (targetBox.left + targetBox.width / 2 - center) / (box.width / bar.offsetWidth) - current.width / 2;
131
+ const x = center + (targetBox.left + targetBox.width / 2 - center) / (box.width / mainSize(bar, vertical)) - current.width / 2;
118
132
  const y = box.top + box.height / 2 - current.height / 2;
133
+ const endScaleX = vertical ? target.offsetHeight / current.width : 1;
134
+ const endScaleY = vertical ? target.offsetWidth / current.height : 1;
119
135
  const elapsed = (tapElapsed ?? 0) / 1000;
120
136
  const duration = tapElapsed === undefined ? 450 : 800 - tapElapsed;
121
137
  let frames = samples.map(([time, remaining]) => ({
122
138
  offset: time / 0.45,
123
139
  x: x + (current.x - x) * remaining,
124
140
  y: y + (current.y - y) * remaining,
125
- sx: 1 + (scale.a - 1) * remaining,
126
- sy: 1 + (scale.d - 1) * remaining,
141
+ sx: endScaleX + (scale.a - endScaleX) * remaining,
142
+ sy: endScaleY + (scale.d - endScaleY) * remaining,
127
143
  }));
128
- if (tapElapsed !== undefined) {
144
+ if (tapElapsed !== undefined && !vertical) {
129
145
  const nextIndex = tabBarTapFrames.findIndex(([time]) => time > elapsed);
130
146
  const previous = tabBarTapFrames[Math.max(0, nextIndex - 1)];
131
147
  const next = tabBarTapFrames[nextIndex];
@@ -136,7 +152,7 @@ export const createTabBarReleaseAnimation = (effectElement, target, tap) => {
136
152
  ...tabBarTapFrames.slice(nextIndex).map(([time, position, width, height]) => ({
137
153
  offset: (time - elapsed) / (0.8 - elapsed),
138
154
  x: x + ((current.x - x) * (1 - position)) / Math.max(0.1, 1 - progress),
139
- y,
155
+ y: y + ((current.y - y) * (1 - position)) / Math.max(0.1, 1 - progress),
140
156
  sx: 1 + (width * (time >= 0.367 ? 1 + 0.75 * extraDistance : 1)) / current.width,
141
157
  sy: 1 + (height * (time >= 0.367 ? 1 + 0.5 * extraDistance : 1)) / current.height,
142
158
  })),
@@ -146,14 +162,14 @@ export const createTabBarReleaseAnimation = (effectElement, target, tap) => {
146
162
  .addElement(native)
147
163
  .duration(duration)
148
164
  .easing('linear')
149
- .keyframes(frames.map(({ offset, sx, sy }) => ({ offset, transform: `scale(${sx}, ${sy})` })));
165
+ .keyframes(frames.map(({ offset, sx, sy }) => ({ offset, transform: stretchTransform(sx, sy, vertical) })));
150
166
  const move = createAnimation()
151
167
  .addElement(effectElement)
152
168
  .duration(duration)
153
169
  .easing('linear')
154
170
  .keyframes(frames.map(({ offset, x, y }) => ({
155
171
  offset,
156
- transform: `translate3d(${x}px, ${y}px, 0)`,
172
+ transform: translate(x, y, vertical),
157
173
  })));
158
174
  const offset = 1 - 200 / duration;
159
175
  const selectedBackground = 'var(--ios27-tab-selected-background, rgba(var(--ios-theme-button-color-selected-rgb, var(--ios26-button-color-selected-rgb, var(--ios27-button-color-selected-default-rgb))), 0.095))';
@@ -174,8 +190,8 @@ export const createTabBarReleaseAnimation = (effectElement, target, tap) => {
174
190
  { offset: 1, backgroundColor: selectedBackground },
175
191
  ]);
176
192
  const release = createAnimation().duration(duration).addAnimation([stretch, move, glass, selected]);
177
- if (tapElapsed !== undefined) {
178
- const width = bar.offsetWidth;
193
+ if (tapElapsed !== undefined && !vertical) {
194
+ const width = mainSize(bar, vertical);
179
195
  const expansion = box.width - width;
180
196
  const peak = Math.max(8.65, expansion);
181
197
  const platter = createAnimation()
@@ -236,7 +252,7 @@ export const createPreMoveAnimation = (effectElement, tabSelectedElement, curren
236
252
  },
237
253
  ]);
238
254
  };
239
- export const createMoveAnimation = (effectElement, detail, tabSelectedElement, animationPosition) => {
255
+ export const createMoveAnimation = (effectElement, detail, tabSelectedElement, animationPosition, vertical = false) => {
240
256
  return createAnimation()
241
257
  .duration(500)
242
258
  .addElement(effectElement)
@@ -247,7 +263,7 @@ export const createMoveAnimation = (effectElement, detail, tabSelectedElement, a
247
263
  opacity: '1',
248
264
  transform: 'none',
249
265
  })
250
- .fromTo('transform', `translate3d(${animationPosition.minPositionX}px, ${animationPosition.positionY}px, 0)`, `translate3d(${animationPosition.maxPositionX}px, ${animationPosition.positionY}px, 0)`)
266
+ .fromTo('transform', translate(animationPosition.minPositionX, animationPosition.positionY, vertical), translate(animationPosition.maxPositionX, animationPosition.positionY, vertical))
251
267
  .progressStep(getStep(detail.currentX, animationPosition));
252
268
  };
253
269
  export const getMoveAnimationKeyframe = (type, scales) => {
@@ -1,3 +1,3 @@
1
1
  import { EffectScales, registeredEffect } from './interfaces';
2
- export declare const registerEffect: (targetElement: HTMLElement, effectTagName: string, selectedClassName: string, scales: EffectScales) => registeredEffect | undefined;
2
+ export declare const registerEffect: (targetElement: HTMLElement, effectTagName: string, selectedClassName: string, scales: EffectScales, vertical?: boolean) => registeredEffect | undefined;
3
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sheets-of-glass/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAkBjF,eAAO,MAAM,cAAc,GACzB,eAAe,WAAW,EAC1B,eAAe,MAAM,EACrB,mBAAmB,MAAM,EACzB,QAAQ,YAAY,KACnB,gBAAgB,GAAG,SAqVrB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sheets-of-glass/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,YAAY,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAmBjF,eAAO,MAAM,cAAc,GACzB,eAAe,WAAW,EAC1B,eAAe,MAAM,EACrB,mBAAmB,MAAM,EACzB,QAAQ,YAAY,EACpB,kBAAgB,KACf,gBAAgB,GAAG,SAqZrB,CAAC"}
@@ -1,10 +1,10 @@
1
1
  import { createGesture } from '@ionic/core';
2
2
  import { changeSelectedElement, cloneElement, getStep } from '../utils';
3
3
  import { isNativeUIShell } from '../native-integration';
4
- import { createMoveAnimation, createPreMoveAnimation, createTabBarDragAnimation, createTabBarPressAnimation, createTabBarReleaseAnimation, getMoveAnimationKeyframe, getScaleAnimation, } from './animations';
4
+ import { animationRect, createMoveAnimation, createPreMoveAnimation, createTabBarDragAnimation, createTabBarPressAnimation, createTabBarReleaseAnimation, getMoveAnimationKeyframe, getScaleAnimation, } from './animations';
5
5
  const GESTURE_NAME = 'ios27-enable-gesture';
6
6
  const ANIMATED_NAME = 'ios27-animated';
7
- export const registerEffect = (targetElement, effectTagName, selectedClassName, scales) => {
7
+ export const registerEffect = (targetElement, effectTagName, selectedClassName, scales, vertical = false) => {
8
8
  if (!targetElement.classList.contains('ios')) {
9
9
  return undefined;
10
10
  }
@@ -24,6 +24,8 @@ export const registerEffect = (targetElement, effectTagName, selectedClassName,
24
24
  let destroyed = false;
25
25
  const originalTabBarScale = targetElement.style.getPropertyValue('--ios27-tab-bar-scale');
26
26
  const effectElement = cloneElement(effectTagName, false);
27
+ if (vertical)
28
+ effectElement.classList.add('ios27-vertical-tab-effect');
27
29
  const stopTabPress = () => {
28
30
  if (!tabPressAnimation)
29
31
  return;
@@ -98,17 +100,36 @@ export const registerEffect = (targetElement, effectTagName, selectedClassName,
98
100
  document.addEventListener('pointercancel', onPointerCancel);
99
101
  };
100
102
  targetElement.addEventListener('pointerdown', onPointerDown);
103
+ const onClick = (event) => {
104
+ if (vertical && tabDragging && event.isTrusted && event.detail > 0) {
105
+ event.preventDefault();
106
+ event.stopImmediatePropagation();
107
+ }
108
+ };
109
+ targetElement.addEventListener('click', onClick, true);
110
+ const axisDetail = (detail) => vertical
111
+ ? {
112
+ ...detail,
113
+ currentX: detail.currentY,
114
+ currentY: detail.currentX,
115
+ startX: detail.startY,
116
+ startY: detail.startX,
117
+ velocityX: detail.velocityY,
118
+ velocityY: detail.velocityX,
119
+ }
120
+ : detail;
101
121
  const createAnimationGesture = () => {
102
122
  targetElement.classList.add(GESTURE_NAME);
103
123
  gesture = createGesture({
104
124
  el: targetElement,
105
125
  threshold: 0,
126
+ direction: vertical ? 'y' : 'x',
106
127
  gestureName: `${GESTURE_NAME}_${effectTagName}_${crypto.randomUUID()}`,
107
- onStart: (event) => onStartGesture(event),
108
- onMove: (event) => onMoveGesture(event),
128
+ onStart: (event) => onStartGesture(axisDetail(event)),
129
+ onMove: (event) => onMoveGesture(axisDetail(event)),
109
130
  onEnd: (detail) => {
110
131
  if (effectTagName === 'ion-tab-button' && tabDragging)
111
- onMoveGesture(detail);
132
+ onMoveGesture(axisDetail(detail));
112
133
  onEndGesture().then();
113
134
  },
114
135
  });
@@ -135,25 +156,31 @@ export const registerEffect = (targetElement, effectTagName, selectedClassName,
135
156
  return false;
136
157
  currentTouchedElement = detail.event.target.closest(effectTagName) || undefined;
137
158
  const tabSelectedElement = targetElement.querySelector(`${effectTagName}.${selectedClassName}`);
138
- if (currentTouchedElement === undefined || tabSelectedElement === null) {
159
+ if (currentTouchedElement === undefined ||
160
+ currentTouchedElement.parentElement !== targetElement ||
161
+ currentTouchedElement.disabled ||
162
+ tabSelectedElement === null) {
163
+ currentTouchedElement = undefined;
139
164
  return false;
140
165
  }
141
166
  selectedElementBeforeGesture = tabSelectedElement;
142
167
  if (effectTagName === 'ion-tab-button') {
143
- targetElement.style.setProperty('--ios27-tab-bar-scale', String(1 + 14.14 / targetElement.offsetWidth));
168
+ targetElement.style.setProperty('--ios27-tab-bar-scale', String(1 + 14.14 / (vertical ? targetElement.offsetHeight : targetElement.offsetWidth)));
144
169
  }
170
+ if (vertical)
171
+ targetElement.classList.add(ANIMATED_NAME);
145
172
  animationPosition = {
146
- minPositionX: targetElement.getBoundingClientRect().left,
147
- maxPositionX: targetElement.getBoundingClientRect().right - tabSelectedElement.clientWidth,
148
- width: tabSelectedElement.clientWidth,
149
- positionY: tabSelectedElement.getBoundingClientRect().top,
173
+ minPositionX: animationRect(targetElement, vertical).left,
174
+ maxPositionX: animationRect(targetElement, vertical).right - (vertical ? tabSelectedElement.clientHeight : tabSelectedElement.clientWidth),
175
+ width: vertical ? tabSelectedElement.clientHeight : tabSelectedElement.clientWidth,
176
+ positionY: animationRect(tabSelectedElement, vertical).top,
150
177
  };
151
178
  targetElement.classList.add(ANIMATED_NAME);
152
179
  changeSelectedElement(targetElement, currentTouchedElement, effectTagName, selectedClassName);
153
180
  if (effectTagName === 'ion-tab-button') {
154
- moveAnimation = createMoveAnimation(effectElement, detail, tabSelectedElement, animationPosition);
155
- moveAnimation.progressStart(true, getStep(tabSelectedElement.getBoundingClientRect().left + tabSelectedElement.clientWidth / 2, animationPosition));
156
- tabPressAnimation = createTabBarPressAnimation(effectElement, tabSelectedElement, currentTouchedElement, targetElement);
181
+ moveAnimation = createMoveAnimation(effectElement, detail, tabSelectedElement, animationPosition, vertical);
182
+ moveAnimation.progressStart(true, getStep(animationRect(tabSelectedElement, vertical).left + animationRect(tabSelectedElement, vertical).width / 2, animationPosition));
183
+ tabPressAnimation = createTabBarPressAnimation(effectElement, tabSelectedElement, currentTouchedElement, targetElement, vertical);
157
184
  tabPressStartedAt = performance.now();
158
185
  void tabPressAnimation.play();
159
186
  return true;
@@ -187,7 +214,7 @@ export const registerEffect = (targetElement, effectTagName, selectedClassName,
187
214
  return;
188
215
  tabDragging = true;
189
216
  stopTabPress();
190
- tabPressAnimation = createTabBarDragAnimation(effectElement, targetElement, detail.velocityX);
217
+ tabPressAnimation = createTabBarDragAnimation(effectElement, targetElement, detail.velocityX, vertical);
191
218
  void tabPressAnimation.play();
192
219
  }
193
220
  else if (scaleAnimationPromise === undefined) {
@@ -211,10 +238,22 @@ export const registerEffect = (targetElement, effectTagName, selectedClassName,
211
238
  }
212
239
  }
213
240
  const currentX = detail.currentX;
214
- const previousY = targetElement.getBoundingClientRect().top + targetElement.getBoundingClientRect().height / 2;
215
- const nextEl = targetElement.getRootNode().elementFromPoint(currentX, previousY);
216
- const latestTouchedElement = nextEl?.closest(effectTagName) || undefined;
217
- if (latestTouchedElement && currentTouchedElement !== latestTouchedElement) {
241
+ const previousY = animationRect(targetElement, vertical).top + animationRect(targetElement, vertical).height / 2;
242
+ const nextEl = targetElement.getRootNode().elementFromPoint(vertical ? previousY : currentX, vertical ? currentX : previousY);
243
+ let latestTouchedElement = nextEl?.closest(effectTagName) || undefined;
244
+ if (vertical && effectTagName === 'ion-tab-button') {
245
+ const bar = targetElement.getBoundingClientRect();
246
+ const tabs = Array.from(targetElement.querySelectorAll(effectTagName)).filter((tab) => tab.offsetWidth > 0);
247
+ const distance = (tab) => Math.abs(currentX - (bar.top + tab.offsetTop + tab.offsetHeight / 2));
248
+ latestTouchedElement =
249
+ currentX >= bar.top && currentX <= bar.bottom
250
+ ? tabs.reduce((closest, tab) => (!closest || distance(tab) < distance(closest) ? tab : closest), undefined)
251
+ : undefined;
252
+ }
253
+ if (latestTouchedElement &&
254
+ latestTouchedElement.parentElement === targetElement &&
255
+ !latestTouchedElement.disabled &&
256
+ currentTouchedElement !== latestTouchedElement) {
218
257
  currentTouchedElement = latestTouchedElement;
219
258
  changeSelectedElement(targetElement, currentTouchedElement, effectTagName, selectedClassName);
220
259
  }
@@ -263,12 +302,16 @@ export const registerEffect = (targetElement, effectTagName, selectedClassName,
263
302
  moveAnimation.destroy();
264
303
  moveAnimation = undefined;
265
304
  const tabs = Array.from(targetElement.querySelectorAll(effectTagName)).filter((tab) => tab.offsetWidth > 0);
305
+ if (vertical) {
306
+ currentTouchedElement.classList.remove('ion-activated');
307
+ targetElement.classList.remove(ANIMATED_NAME);
308
+ }
266
309
  const releasing = createTabBarReleaseAnimation(effectElement, currentTouchedElement, tapElapsed !== undefined && tapElapsed < 200
267
310
  ? {
268
311
  elapsed: tapElapsed,
269
312
  distance: Math.abs(tabs.indexOf(currentTouchedElement) - tabs.indexOf(selectedElementBeforeGesture)),
270
313
  }
271
- : undefined);
314
+ : undefined, vertical);
272
315
  currentTouchedElement.classList.remove('ion-activated');
273
316
  releaseAnimation = releasing;
274
317
  await releasing.play();
@@ -297,6 +340,7 @@ export const registerEffect = (targetElement, effectTagName, selectedClassName,
297
340
  destroyed = true;
298
341
  cancelActiveGesture();
299
342
  targetElement.removeEventListener('pointerdown', onPointerDown);
343
+ targetElement.removeEventListener('click', onClick, true);
300
344
  targetElement.removeEventListener('nativeUIShellChange', nativeChanged);
301
345
  if (gesture) {
302
346
  gesture.destroy();
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tab-bar-searchable/index.ts"],"names":[],"mappings":"AACA,OAAO,EAQL,KAAK,wBAAwB,EAE9B,MAAM,2BAA2B,CAAC;AAiBnC,OAAO,EACL,oBAAoB,EACpB,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,KAAK,iBAAiB,GACvB,MAAM,2BAA2B,CAAC;AAqBnC,eAAO,MAAM,sBAAsB,GACjC,WAAW,WAAW,EACtB,cAAc,WAAW,EACzB,WAAW,WAAW,KACrB,wBAwDF,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tab-bar-searchable/index.ts"],"names":[],"mappings":"AACA,OAAO,EAQL,KAAK,wBAAwB,EAE9B,MAAM,2BAA2B,CAAC;AAiBnC,OAAO,EACL,oBAAoB,EACpB,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,KAAK,iBAAiB,GACvB,MAAM,2BAA2B,CAAC;AAqBnC,eAAO,MAAM,sBAAsB,GACjC,WAAW,WAAW,EACtB,cAAc,WAAW,EACzB,WAAW,WAAW,KACrB,wBA+EF,CAAC"}
@@ -10,11 +10,14 @@ export const attachTabBarSearchable = (ionTabBar, ionFabButton, ionFooter) => {
10
10
  }
11
11
  ionFooter.style.pointerEvents = 'none';
12
12
  ionFooter.style.opacity = '0';
13
+ ionFooter.querySelector('ion-buttons[slot=start]')?.classList.add('ios-theme-horizontal-only');
13
14
  const nativeSearch = registerNativeSearch(ionTabBar, ionFabButton, ionFooter);
14
15
  let searchableEventCache;
15
16
  let resumeNative;
16
17
  let nativeEntry = false;
17
18
  let nativeRequest = 0;
19
+ let verticalSearch = false;
20
+ let tabVisibilityObserver;
18
21
  return async (event, type) => {
19
22
  const selector = type === TabBarSearchableType.Enter ? 'ion-fab-button' : 'ion-buttons[slot=start] ion-button';
20
23
  if (!event.target?.closest(selector))
@@ -37,11 +40,28 @@ export const attachTabBarSearchable = (ionTabBar, ionFabButton, ionFooter) => {
37
40
  return;
38
41
  }
39
42
  if (type === TabBarSearchableType.Enter) {
40
- resumeNative ?? (resumeNative = await suspendNativeUIShell([ionTabBar, ionFooter, ionFabButton.closest('ion-fab') ?? ionFabButton]));
43
+ verticalSearch = !!ionTabBar.closest('ion-app.ios-theme-vertical-bars');
44
+ if (verticalSearch) {
45
+ const keepTabsVisible = () => {
46
+ if (ionTabBar.classList.contains('tab-bar-hidden') && ionTabBar.closest('ion-app.ios-theme-vertical-bars')) {
47
+ ionTabBar.classList.remove('tab-bar-hidden');
48
+ }
49
+ };
50
+ tabVisibilityObserver?.disconnect();
51
+ tabVisibilityObserver = new MutationObserver(keepTabsVisible);
52
+ tabVisibilityObserver.observe(ionTabBar, { attributes: true, attributeFilter: ['class'] });
53
+ keepTabsVisible();
54
+ }
55
+ resumeNative ?? (resumeNative = await suspendNativeUIShell([
56
+ ...(verticalSearch ? [] : [ionTabBar]),
57
+ ionFooter,
58
+ ionFabButton.closest('ion-fab') ?? ionFabButton,
59
+ ]));
41
60
  try {
42
- searchableEventCache = await enterEvent(event, ionTabBar, ionFabButton, ionFooter);
61
+ searchableEventCache = await enterEvent(event, ionTabBar, ionFabButton, ionFooter, verticalSearch);
43
62
  }
44
63
  catch (error) {
64
+ tabVisibilityObserver?.disconnect();
45
65
  resumeNative();
46
66
  resumeNative = undefined;
47
67
  throw error;
@@ -49,10 +69,11 @@ export const attachTabBarSearchable = (ionTabBar, ionFabButton, ionFooter) => {
49
69
  }
50
70
  else if (searchableEventCache !== undefined) {
51
71
  try {
52
- await leaveEvent(event, searchableEventCache, ionTabBar, ionFabButton, ionFooter);
72
+ await leaveEvent(event, searchableEventCache, ionTabBar, ionFabButton, ionFooter, verticalSearch);
53
73
  searchableEventCache = undefined;
54
74
  }
55
75
  finally {
76
+ tabVisibilityObserver?.disconnect();
56
77
  resumeNative?.();
57
78
  resumeNative = undefined;
58
79
  }
@@ -62,7 +83,7 @@ export const attachTabBarSearchable = (ionTabBar, ionFabButton, ionFooter) => {
62
83
  }
63
84
  };
64
85
  };
65
- const enterEvent = async (event, ionTabBar, ionFabButton, ionFooter) => {
86
+ const enterEvent = async (event, ionTabBar, ionFabButton, ionFooter, verticalSearch) => {
66
87
  if (!event.target?.closest('ion-fab-button')) {
67
88
  throw throwErrorByFailedClickElement('ion-fab-button');
68
89
  }
@@ -71,10 +92,13 @@ const enterEvent = async (event, ionTabBar, ionFabButton, ionFooter) => {
71
92
  const colorSelected = references.selectedTabButton
72
93
  ? getComputedStyle(references.selectedTabButton).getPropertyValue('--color-selected').trim()
73
94
  : '';
74
- const effectAnimation = createEffectAnimation(references, sizes);
95
+ if (verticalSearch)
96
+ references.closeButtonIcon.setAttribute('name', 'close');
97
+ const tabAnimations = verticalSearch
98
+ ? []
99
+ : [createTabBarAnimation(ionTabBar, references, sizes), createEffectAnimation(references, sizes)];
75
100
  const searchContainerAnimation = createSearchContainerAnimation(references, sizes);
76
101
  const closeButtonsAnimation = createCloseButtonsAnimation(references);
77
- const tabBarAnimation = createTabBarAnimation(ionTabBar, references, sizes);
78
102
  const fabButtonAnimation = createFabButtonAnimation(ionFabButton);
79
103
  await createAnimation()
80
104
  .delay(ANIMATION_DELAY_BASE)
@@ -83,22 +107,26 @@ const enterEvent = async (event, ionTabBar, ionFabButton, ionFooter) => {
83
107
  .addElement(ionFooter)
84
108
  .afterAddWrite(() => (ionFooter.style.pointerEvents = 'auto'))
85
109
  .fromTo('opacity', '0.8', '1')
86
- .addAnimation([tabBarAnimation, fabButtonAnimation, searchContainerAnimation, effectAnimation, closeButtonsAnimation])
110
+ .addAnimation([...tabAnimations, fabButtonAnimation, searchContainerAnimation, closeButtonsAnimation])
87
111
  .play();
88
112
  return {
89
113
  elementSizes: sizes,
90
114
  colorSelected,
91
115
  };
92
116
  };
93
- const leaveEvent = async (event, searchableEventCache, ionTabBar, ionFabButton, ionFooter) => {
117
+ const leaveEvent = async (event, searchableEventCache, ionTabBar, ionFabButton, ionFooter, verticalSearch) => {
94
118
  if (!event.target?.closest('ion-buttons[slot=start] ion-button')) {
95
119
  throw throwErrorByFailedClickElement('ion-buttons[slot=start] ion-button');
96
120
  }
97
121
  const references = getElementReferences(ionTabBar, ionFooter);
98
- const effectAnimation = createReverseEffectAnimation(references, searchableEventCache.elementSizes, searchableEventCache.colorSelected);
122
+ const tabAnimations = verticalSearch
123
+ ? []
124
+ : [
125
+ createReverseTabBarAnimation(ionTabBar, references, searchableEventCache.elementSizes),
126
+ createReverseEffectAnimation(references, searchableEventCache.elementSizes, searchableEventCache.colorSelected),
127
+ ];
99
128
  const searchContainerAnimation = createReverseSearchContainerAnimation(references, searchableEventCache.elementSizes);
100
129
  const closeButtonsAnimation = createReverseCloseButtonsAnimation(references);
101
- const tabBarAnimation = createReverseTabBarAnimation(ionTabBar, references, searchableEventCache.elementSizes);
102
130
  const fabButtonAnimation = createReverseFabButtonAnimation(ionFabButton, searchableEventCache.elementSizes);
103
131
  await createAnimation()
104
132
  .delay(ANIMATION_DELAY_BASE)
@@ -107,6 +135,6 @@ const leaveEvent = async (event, searchableEventCache, ionTabBar, ionFabButton,
107
135
  .addElement(ionFooter)
108
136
  .afterAddWrite(() => (ionFooter.style.pointerEvents = 'none'))
109
137
  .fromTo('opacity', '1', '0')
110
- .addAnimation([tabBarAnimation, fabButtonAnimation, searchContainerAnimation, effectAnimation, closeButtonsAnimation])
138
+ .addAnimation([...tabAnimations, fabButtonAnimation, searchContainerAnimation, closeButtonsAnimation])
111
139
  .play();
112
140
  };
@@ -1,5 +1,4 @@
1
1
  export { enableVerticalControlArea, setVerticalControlAreaPlacement, IonicNativeUIShell } from './native';
2
- export { HingeStatus } from './native';
3
2
  export { withNativeUIShellTransition } from './native-integration/transition';
4
- export type { DeviceLayout, NativeUIShellStatus, NativeUIShellSuspension, VerticalBarEdge, VerticalControlAreaHandle } from './native';
3
+ export type { NativeUIShellStatus, NativeUIShellSuspension, VerticalBarEdge, VerticalControlAreaHandle } from './native';
5
4
  //# sourceMappingURL=vertical-bars.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"vertical-bars.d.ts","sourceRoot":"","sources":["../src/vertical-bars.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,+BAA+B,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC1G,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAE,2BAA2B,EAAE,MAAM,iCAAiC,CAAC;AAC9E,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,eAAe,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"vertical-bars.d.ts","sourceRoot":"","sources":["../src/vertical-bars.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,+BAA+B,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAC1G,OAAO,EAAE,2BAA2B,EAAE,MAAM,iCAAiC,CAAC;AAC9E,YAAY,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,eAAe,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC"}
@@ -1,3 +1,2 @@
1
1
  export { enableVerticalControlArea, setVerticalControlAreaPlacement, IonicNativeUIShell } from './native';
2
- export { HingeStatus } from './native';
3
2
  export { withNativeUIShellTransition } from './native-integration/transition';