@rdlabo/ionic-theme-ios26 1.1.1 → 1.2.0

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 (45) hide show
  1. package/README.md +62 -3
  2. package/dist/css/components/ion-fab.css +1 -1
  3. package/dist/css/components/ion-searchbar.css +1 -1
  4. package/dist/css/components/ion-toolbar.css +1 -1
  5. package/dist/css/ionic-theme-ios26.css +1 -1
  6. package/dist/css/utils/searchable.css +1 -0
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +1 -0
  10. package/dist/popover/animations/ios.enter.d.ts.map +1 -1
  11. package/dist/popover/animations/ios.enter.js +3 -17
  12. package/dist/popover/utils.d.ts +2 -13
  13. package/dist/popover/utils.d.ts.map +1 -1
  14. package/dist/popover/utils.js +11 -119
  15. package/dist/sheets-of-glass/index.d.ts.map +1 -1
  16. package/dist/sheets-of-glass/index.js +6 -1
  17. package/dist/tab-bar-searchable/animations/enter.d.ts +8 -0
  18. package/dist/tab-bar-searchable/animations/enter.d.ts.map +1 -0
  19. package/dist/tab-bar-searchable/animations/enter.js +73 -0
  20. package/dist/tab-bar-searchable/animations/leave.d.ts +8 -0
  21. package/dist/tab-bar-searchable/animations/leave.d.ts.map +1 -0
  22. package/dist/tab-bar-searchable/animations/leave.js +66 -0
  23. package/dist/tab-bar-searchable/index.d.ts +4 -0
  24. package/dist/tab-bar-searchable/index.d.ts.map +1 -0
  25. package/dist/tab-bar-searchable/index.js +75 -0
  26. package/dist/tab-bar-searchable/interfaces.d.ts +41 -0
  27. package/dist/tab-bar-searchable/interfaces.d.ts.map +1 -0
  28. package/dist/tab-bar-searchable/interfaces.js +5 -0
  29. package/dist/tab-bar-searchable/utils.d.ts +12 -0
  30. package/dist/tab-bar-searchable/utils.d.ts.map +1 -0
  31. package/dist/tab-bar-searchable/utils.js +60 -0
  32. package/package.json +1 -1
  33. package/src/index.ts +1 -0
  34. package/src/popover/animations/ios.enter.ts +20 -52
  35. package/src/popover/utils.ts +8 -230
  36. package/src/sheets-of-glass/index.ts +6 -1
  37. package/src/styles/components/ion-fab.scss +31 -1
  38. package/src/styles/components/ion-searchbar.scss +10 -0
  39. package/src/styles/components/ion-toolbar.scss +20 -0
  40. package/src/styles/utils/searchable.scss +0 -0
  41. package/src/tab-bar-searchable/animations/enter.ts +92 -0
  42. package/src/tab-bar-searchable/animations/leave.ts +89 -0
  43. package/src/tab-bar-searchable/index.ts +141 -0
  44. package/src/tab-bar-searchable/interfaces.ts +28 -0
  45. package/src/tab-bar-searchable/utils.ts +75 -0
@@ -1,12 +1,5 @@
1
1
  import { getElementRoot, raf } from '../utils';
2
2
  import { POPOVER_IOS_BODY_MARGIN } from './animations/ios.enter';
3
- export const getArrowDimensions = (arrowEl) => {
4
- if (!arrowEl) {
5
- return { arrowWidth: 0, arrowHeight: 0 };
6
- }
7
- const { width, height } = arrowEl.getBoundingClientRect();
8
- return { arrowWidth: width, arrowHeight: height };
9
- };
10
3
  export const getPopoverDimensions = (size, contentEl, triggerEl) => {
11
4
  const contentDimentions = contentEl.getBoundingClientRect();
12
5
  const contentHeight = contentDimentions.height;
@@ -170,70 +163,7 @@ const focusItem = (item) => {
170
163
  raf(() => button.focus());
171
164
  }
172
165
  };
173
- export const isTriggerElement = (el) => el.hasAttribute('data-ion-popover-trigger');
174
- export const configureKeyboardInteraction = (popoverEl) => {
175
- const callback = async (ev) => {
176
- const activeElement = document.activeElement;
177
- let items = [];
178
- const targetTagName = ev.target?.tagName;
179
- if (targetTagName !== 'ION-POPOVER' && targetTagName !== 'ION-ITEM') {
180
- return;
181
- }
182
- try {
183
- items = Array.from(popoverEl.querySelectorAll('ion-item:not(ion-popover ion-popover *):not([disabled])'));
184
- }
185
- catch { }
186
- switch (ev.key) {
187
- case 'ArrowLeft':
188
- const parentPopover = await popoverEl.getParentPopover();
189
- if (parentPopover) {
190
- popoverEl.dismiss(undefined, undefined, false);
191
- }
192
- break;
193
- case 'ArrowDown':
194
- ev.preventDefault();
195
- const nextItem = getNextItem(items, activeElement);
196
- if (nextItem !== undefined) {
197
- focusItem(nextItem);
198
- }
199
- break;
200
- case 'ArrowUp':
201
- ev.preventDefault();
202
- const prevItem = getPrevItem(items, activeElement);
203
- if (prevItem !== undefined) {
204
- focusItem(prevItem);
205
- }
206
- break;
207
- case 'Home':
208
- ev.preventDefault();
209
- const firstItem = items[0];
210
- if (firstItem !== undefined) {
211
- focusItem(firstItem);
212
- }
213
- break;
214
- case 'End':
215
- ev.preventDefault();
216
- const lastItem = items[items.length - 1];
217
- if (lastItem !== undefined) {
218
- focusItem(lastItem);
219
- }
220
- break;
221
- case 'ArrowRight':
222
- case ' ':
223
- case 'Enter':
224
- if (activeElement && isTriggerElement(activeElement)) {
225
- const rightEvent = new CustomEvent('ionPopoverActivateTrigger');
226
- activeElement.dispatchEvent(rightEvent);
227
- }
228
- break;
229
- default:
230
- break;
231
- }
232
- };
233
- popoverEl.addEventListener('keydown', callback);
234
- return () => popoverEl.removeEventListener('keydown', callback);
235
- };
236
- export const getPopoverPosition = (isRTL, contentWidth, contentHeight, arrowWidth, arrowHeight, reference, side, align, defaultPosition, triggerEl, event) => {
166
+ export const getPopoverPosition = (isRTL, contentWidth, contentHeight, reference, side, align, defaultPosition, triggerEl, event) => {
237
167
  let referenceCoordinates = {
238
168
  top: 0,
239
169
  left: 0,
@@ -269,13 +199,12 @@ export const getPopoverPosition = (isRTL, contentWidth, contentHeight, arrowWidt
269
199
  };
270
200
  break;
271
201
  }
272
- const coordinates = calculatePopoverSide(side, referenceCoordinates, contentWidth, contentHeight, arrowWidth, arrowHeight, isRTL);
202
+ const coordinates = calculatePopoverSide(side, referenceCoordinates, contentWidth, contentHeight, isRTL);
273
203
  const alignedCoordinates = calculatePopoverAlign(align, side, referenceCoordinates, contentWidth, contentHeight);
274
204
  const top = coordinates.top + alignedCoordinates.top;
275
205
  const left = coordinates.left + alignedCoordinates.left;
276
- const { arrowTop, arrowLeft } = calculateArrowPosition(side, arrowWidth, arrowHeight, top, left, contentWidth, contentHeight, isRTL);
277
206
  const { originX, originY } = calculatePopoverOrigin(side, align, isRTL);
278
- return { top, left, referenceCoordinates, arrowTop, arrowLeft, originX, originY };
207
+ return { top, left, referenceCoordinates, originX, originY };
279
208
  };
280
209
  const calculatePopoverOrigin = (side, align, isRTL) => {
281
210
  switch (side) {
@@ -313,49 +242,26 @@ const getOriginYAlignment = (align) => {
313
242
  return 'bottom';
314
243
  }
315
244
  };
316
- const calculateArrowPosition = (side, arrowWidth, arrowHeight, top, left, contentWidth, contentHeight, isRTL) => {
317
- const leftPosition = {
318
- arrowTop: top + contentHeight / 2 - arrowWidth / 2,
319
- arrowLeft: left + contentWidth - arrowWidth / 2,
320
- };
321
- const rightPosition = { arrowTop: top + contentHeight / 2 - arrowWidth / 2, arrowLeft: left - arrowWidth * 1.5 };
322
- switch (side) {
323
- case 'top':
324
- return { arrowTop: top + contentHeight, arrowLeft: left + contentWidth / 2 - arrowWidth / 2 };
325
- case 'bottom':
326
- return { arrowTop: top - arrowHeight, arrowLeft: left + contentWidth / 2 - arrowWidth / 2 };
327
- case 'left':
328
- return leftPosition;
329
- case 'right':
330
- return rightPosition;
331
- case 'start':
332
- return isRTL ? rightPosition : leftPosition;
333
- case 'end':
334
- return isRTL ? leftPosition : rightPosition;
335
- default:
336
- return { arrowTop: 0, arrowLeft: 0 };
337
- }
338
- };
339
- const calculatePopoverSide = (side, triggerBoundingBox, contentWidth, contentHeight, arrowWidth, arrowHeight, isRTL) => {
245
+ const calculatePopoverSide = (side, triggerBoundingBox, contentWidth, contentHeight, isRTL) => {
340
246
  const sideLeft = {
341
247
  top: triggerBoundingBox.top,
342
- left: triggerBoundingBox.left - contentWidth - arrowWidth,
248
+ left: triggerBoundingBox.left - contentWidth,
343
249
  };
344
250
  const sideRight = {
345
251
  top: triggerBoundingBox.top,
346
- left: triggerBoundingBox.left + triggerBoundingBox.width + arrowWidth,
252
+ left: triggerBoundingBox.left + triggerBoundingBox.width,
347
253
  };
348
254
  switch (side) {
349
255
  case 'top':
350
256
  return {
351
- top: triggerBoundingBox.top - contentHeight - arrowHeight,
257
+ top: triggerBoundingBox.top - contentHeight,
352
258
  left: triggerBoundingBox.left,
353
259
  };
354
260
  case 'right':
355
261
  return sideRight;
356
262
  case 'bottom':
357
263
  return {
358
- top: triggerBoundingBox.top + triggerBoundingBox.height + arrowHeight,
264
+ top: triggerBoundingBox.top + triggerBoundingBox.height,
359
265
  left: triggerBoundingBox.left,
360
266
  };
361
267
  case 'left':
@@ -415,9 +321,7 @@ const calculatePopoverCenterAlign = (side, triggerBoundingBox, contentWidth, con
415
321
  };
416
322
  }
417
323
  };
418
- export const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyWidth, bodyHeight, contentWidth, contentHeight, safeAreaMargin, contentOriginX, contentOriginY, triggerCoordinates, coordArrowTop = 0, coordArrowLeft = 0, arrowHeight = 0, eventElementRect, isReplace = false) => {
419
- let arrowTop = coordArrowTop;
420
- const arrowLeft = coordArrowLeft;
324
+ export const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding, bodyWidth, bodyHeight, contentWidth, contentHeight, safeAreaMargin, contentOriginX, contentOriginY, triggerCoordinates, eventElementRect, isReplace = false) => {
421
325
  const triggerTop = triggerCoordinates ? triggerCoordinates.top + triggerCoordinates.height : bodyHeight / 2 - contentHeight / 2;
422
326
  const triggerHeight = triggerCoordinates ? triggerCoordinates.height : 0;
423
327
  let left = coordLeft;
@@ -442,12 +346,11 @@ export const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding
442
346
  if (compareTop > bodyHeight / 2 && (side === 'top' || side === 'bottom')) {
443
347
  if (triggerTop - contentHeight > 0) {
444
348
  if (!isReplace) {
445
- top = Math.max(12, triggerTop - contentHeight - triggerHeight - (arrowHeight - 1)) - POPOVER_IOS_BODY_MARGIN;
349
+ top = Math.max(12, triggerTop - contentHeight - triggerHeight) - POPOVER_IOS_BODY_MARGIN;
446
350
  }
447
351
  else {
448
- top = Math.max(12, triggerTop - contentHeight - (arrowHeight - 1));
352
+ top = Math.max(12, triggerTop - contentHeight);
449
353
  }
450
- arrowTop = top + contentHeight;
451
354
  originY = 'bottom';
452
355
  addPopoverBottomClass = true;
453
356
  }
@@ -463,17 +366,6 @@ export const calculateWindowAdjustment = (side, coordTop, coordLeft, bodyPadding
463
366
  originY,
464
367
  checkSafeAreaLeft,
465
368
  checkSafeAreaRight,
466
- arrowTop,
467
- arrowLeft,
468
369
  addPopoverBottomClass,
469
370
  };
470
371
  };
471
- export const shouldShowArrow = (side, didAdjustBounds = false, ev, trigger) => {
472
- if (!ev && !trigger) {
473
- return false;
474
- }
475
- if (side !== 'top' && side !== 'bottom' && didAdjustBounds) {
476
- return false;
477
- }
478
- return true;
479
- };
@@ -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;AAUjF,eAAO,MAAM,cAAc,GACzB,eAAe,WAAW,EAC1B,eAAe,MAAM,EACrB,mBAAmB,MAAM,EACzB,QAAQ,YAAY,KACnB,gBAAgB,GAAG,SAyLrB,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;AAUjF,eAAO,MAAM,cAAc,GACzB,eAAe,WAAW,EAC1B,eAAe,MAAM,EACrB,mBAAmB,MAAM,EACzB,QAAQ,YAAY,KACnB,gBAAgB,GAAG,SA8LrB,CAAC"}
@@ -15,6 +15,7 @@ export const registerEffect = (targetElement, effectTagName, selectedClassName,
15
15
  let scaleAnimationPromise;
16
16
  let startAnimationPromise;
17
17
  let maxVelocity = 0;
18
+ let wasRealUserClick = false;
18
19
  const effectElement = cloneElement(effectTagName);
19
20
  const onPointerDown = () => {
20
21
  clearActivated();
@@ -22,6 +23,7 @@ export const registerEffect = (targetElement, effectTagName, selectedClassName,
22
23
  createAnimationGesture();
23
24
  };
24
25
  const onPointerUp = (event) => {
26
+ wasRealUserClick = true;
25
27
  clearActivatedTimer = setTimeout(async () => {
26
28
  await onEndGesture();
27
29
  gesture.destroy();
@@ -49,12 +51,15 @@ export const registerEffect = (targetElement, effectTagName, selectedClassName,
49
51
  if (!currentTouchedElement) {
50
52
  return;
51
53
  }
52
- currentTouchedElement.click();
54
+ if (!wasRealUserClick) {
55
+ currentTouchedElement.click();
56
+ }
53
57
  currentTouchedElement?.classList.remove('ion-activated');
54
58
  currentTouchedElement = undefined;
55
59
  effectElement.style.display = 'none';
56
60
  maxVelocity = 0;
57
61
  targetElement.classList.remove(ANIMATED_NAME);
62
+ wasRealUserClick = false;
58
63
  };
59
64
  const onStartGesture = (detail) => {
60
65
  currentTouchedElement = detail.event.target.closest(effectTagName) || undefined;
@@ -0,0 +1,8 @@
1
+ import { ElementReferences, ElementSizes } from '../interfaces';
2
+ import { Animation } from '@ionic/core';
3
+ export declare const createEffectAnimation: (references: ElementReferences, sizes: ElementSizes) => Animation;
4
+ export declare const createSearchContainerAnimation: (references: ElementReferences, sizes: ElementSizes) => Animation;
5
+ export declare const createCloseButtonsAnimation: (references: ElementReferences) => Animation;
6
+ export declare const createTabBarAnimation: (ionTabBar: HTMLElement, references: ElementReferences, sizes: ElementSizes) => Animation;
7
+ export declare const createFabButtonAnimation: (ionFabButton: HTMLElement) => Animation;
8
+ //# sourceMappingURL=enter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enter.d.ts","sourceRoot":"","sources":["../../../src/tab-bar-searchable/animations/enter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,SAAS,EAAmB,MAAM,aAAa,CAAC;AAIzD,eAAO,MAAM,qBAAqB,GAAI,YAAY,iBAAiB,EAAE,OAAO,YAAY,KAAG,SA0B1F,CAAC;AAEF,eAAO,MAAM,8BAA8B,GAAI,YAAY,iBAAiB,EAAE,OAAO,YAAY,KAAG,SAUnG,CAAC;AAEF,eAAO,MAAM,2BAA2B,GAAI,YAAY,iBAAiB,KAAG,SAO3E,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAAI,WAAW,WAAW,EAAE,YAAY,iBAAiB,EAAE,OAAO,YAAY,KAAG,SAwBlH,CAAC;AAEF,eAAO,MAAM,wBAAwB,GAAI,cAAc,WAAW,KAAG,SAWpE,CAAC"}
@@ -0,0 +1,73 @@
1
+ import { createAnimation } from '@ionic/core';
2
+ import { cloneElement } from '../../utils';
3
+ import { ANIMATION_DELAY_CLOSE_BUTTONS, OPACITY_TRANSITION } from '../utils';
4
+ export const createEffectAnimation = (references, sizes) => {
5
+ const effectElement = cloneElement('ion-icon');
6
+ const closeButtonRect = references.closeButtonIcon?.getBoundingClientRect();
7
+ const iconName = references.selectedTabButtonIcon?.getAttribute('name');
8
+ const selectedTabButtonIconRect = references.selectedTabButtonIcon?.getBoundingClientRect();
9
+ return createAnimation()
10
+ .addElement(effectElement)
11
+ .beforeAddWrite(() => {
12
+ effectElement.style.display = 'inline-block';
13
+ references.closeButtonIcon.style.opacity = '0';
14
+ if (iconName && closeButtonRect) {
15
+ effectElement.setAttribute('name', iconName);
16
+ effectElement.style.width = `${closeButtonRect.width}px`;
17
+ effectElement.style.height = `${closeButtonRect.height}px`;
18
+ }
19
+ })
20
+ .afterAddWrite(() => {
21
+ effectElement.style.display = 'none';
22
+ references.closeButtonIcon.style.opacity = '1';
23
+ })
24
+ .fromTo('transform', `translate3d(${selectedTabButtonIconRect.left}px, ${selectedTabButtonIconRect.top}px, 0)`, `translate3d(${closeButtonRect.left}px, ${closeButtonRect.top}px, 0)`);
25
+ };
26
+ export const createSearchContainerAnimation = (references, sizes) => {
27
+ return createAnimation()
28
+ .addElement(references.searchContainer)
29
+ .beforeAddWrite(() => (references.searchContainer.style.transformOrigin = 'right center'))
30
+ .fromTo('transform', `scale(${sizes.fabButton.width / sizes.searchContainer.width}, ${sizes.fabButton.height / sizes.searchContainer.height})`, 'scale(1)')
31
+ .fromTo('opacity', '0.2', '1');
32
+ };
33
+ export const createCloseButtonsAnimation = (references) => {
34
+ return createAnimation()
35
+ .delay(ANIMATION_DELAY_CLOSE_BUTTONS)
36
+ .addElement(references.closeButtons)
37
+ .beforeAddWrite(() => (references.closeButtons.style.transformOrigin = 'left center'))
38
+ .afterClearStyles(['transform', 'transform-origin'])
39
+ .fromTo('transform', 'scale(1.5, 1)', 'scale(1)');
40
+ };
41
+ export const createTabBarAnimation = (ionTabBar, references, sizes) => {
42
+ return createAnimation()
43
+ .addElement(ionTabBar)
44
+ .beforeAddWrite(() => {
45
+ ionTabBar.querySelectorAll('ion-tab-button').forEach((element) => {
46
+ element.style.transition = OPACITY_TRANSITION;
47
+ element.style.opacity = '0';
48
+ });
49
+ references.selectedTabButton.classList.remove('tab-selected');
50
+ const iconName = references.selectedTabButtonIcon?.getAttribute('name');
51
+ if (iconName) {
52
+ references.closeButtonIcon?.setAttribute('name', iconName);
53
+ }
54
+ })
55
+ .afterAddWrite(() => {
56
+ references.selectedTabButton.classList.add('tab-selected');
57
+ ionTabBar.style.pointerEvents = 'none';
58
+ })
59
+ .fromTo('transform', 'scale(1)', `scale(${sizes.closeButton.width / sizes.tabBar.width}, ${sizes.closeButton.height / sizes.tabBar.height})`)
60
+ .fromTo('opacity', '1', '0');
61
+ };
62
+ export const createFabButtonAnimation = (ionFabButton) => {
63
+ return createAnimation()
64
+ .addElement(ionFabButton)
65
+ .beforeAddWrite(() => {
66
+ ionFabButton.style.transformOrigin = 'center right';
67
+ ionFabButton.querySelector('ion-icon')?.style.setProperty('opacity', '0');
68
+ })
69
+ .afterAddWrite(() => {
70
+ ionFabButton.style.pointerEvents = 'none';
71
+ })
72
+ .fromTo('opacity', '1', '0');
73
+ };
@@ -0,0 +1,8 @@
1
+ import { ElementReferences, ElementSizes } from '../interfaces';
2
+ import { Animation } from '@ionic/core';
3
+ export declare const createReverseEffectAnimation: (references: ElementReferences, searchableElementSizes: ElementSizes, colorSelected: string) => Animation;
4
+ export declare const createReverseSearchContainerAnimation: (references: ElementReferences, sizes: ElementSizes) => Animation;
5
+ export declare const createReverseCloseButtonsAnimation: (references: ElementReferences) => Animation;
6
+ export declare const createReverseTabBarAnimation: (ionTabBar: HTMLElement, references: ElementReferences, sizes: ElementSizes) => Animation;
7
+ export declare const createReverseFabButtonAnimation: (ionFabButton: HTMLElement, references: ElementSizes) => Animation;
8
+ //# sourceMappingURL=leave.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"leave.d.ts","sourceRoot":"","sources":["../../../src/tab-bar-searchable/animations/leave.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EAAE,SAAS,EAAmB,MAAM,aAAa,CAAC;AAIzD,eAAO,MAAM,4BAA4B,GACvC,YAAY,iBAAiB,EAC7B,wBAAwB,YAAY,EACpC,eAAe,MAAM,KACpB,SAyBF,CAAC;AAEF,eAAO,MAAM,qCAAqC,GAAI,YAAY,iBAAiB,EAAE,OAAO,YAAY,KAAG,SAU1G,CAAC;AAEF,eAAO,MAAM,kCAAkC,GAAI,YAAY,iBAAiB,KAAG,SAOlF,CAAC;AAEF,eAAO,MAAM,4BAA4B,GAAI,WAAW,WAAW,EAAE,YAAY,iBAAiB,EAAE,OAAO,YAAY,KAAG,SAiBzH,CAAC;AAEF,eAAO,MAAM,+BAA+B,GAAI,cAAc,WAAW,EAAE,YAAY,YAAY,KAAG,SAYrG,CAAC"}
@@ -0,0 +1,66 @@
1
+ import { createAnimation } from '@ionic/core';
2
+ import { cloneElement } from '../../utils';
3
+ import { ANIMATION_DELAY_CLOSE_BUTTONS, OPACITY_TRANSITION } from '../utils';
4
+ export const createReverseEffectAnimation = (references, searchableElementSizes, colorSelected) => {
5
+ const effectElement = cloneElement('ion-icon');
6
+ const closeButtonRect = references.closeButtonIcon?.getBoundingClientRect();
7
+ return createAnimation()
8
+ .addElement(effectElement)
9
+ .beforeAddWrite(() => {
10
+ effectElement.style.display = 'inline-block';
11
+ references.closeButtonIcon.style.opacity = '0';
12
+ if (searchableElementSizes.selectedTabButtonIcon) {
13
+ effectElement.style.width = `${searchableElementSizes.selectedTabButtonIcon.width}px`;
14
+ effectElement.style.height = `${searchableElementSizes.selectedTabButtonIcon.height}px`;
15
+ effectElement.style.color = colorSelected;
16
+ }
17
+ })
18
+ .afterAddWrite(() => {
19
+ effectElement.style.display = 'none';
20
+ references.closeButtonIcon.style.opacity = '1';
21
+ effectElement.style.color = '';
22
+ })
23
+ .fromTo('transform', `translate3d(${closeButtonRect.left}px, ${closeButtonRect.top}px, 0)`, `translate3d(${searchableElementSizes.selectedTabButtonIcon.left}px, ${searchableElementSizes.selectedTabButtonIcon.top}px, 0)`);
24
+ };
25
+ export const createReverseSearchContainerAnimation = (references, sizes) => {
26
+ return createAnimation()
27
+ .addElement(references.searchContainer)
28
+ .beforeAddWrite(() => (references.searchContainer.style.transformOrigin = 'right center'))
29
+ .fromTo('transform', 'scale(1)', `scale(${sizes.fabButton.width / sizes.searchContainer.width}, ${sizes.fabButton.height / sizes.searchContainer.height})`)
30
+ .fromTo('opacity', '1', '0.2');
31
+ };
32
+ export const createReverseCloseButtonsAnimation = (references) => {
33
+ return createAnimation()
34
+ .delay(ANIMATION_DELAY_CLOSE_BUTTONS)
35
+ .addElement(references.closeButtons)
36
+ .beforeAddWrite(() => (references.closeButtons.style.transformOrigin = 'left center'))
37
+ .afterClearStyles(['transform', 'transform-origin'])
38
+ .fromTo('transform', 'scale(1)', 'scale(1.5, 1)');
39
+ };
40
+ export const createReverseTabBarAnimation = (ionTabBar, references, sizes) => {
41
+ return createAnimation()
42
+ .addElement(ionTabBar)
43
+ .beforeAddWrite(() => {
44
+ ionTabBar.style.pointerEvents = 'auto';
45
+ ionTabBar.querySelectorAll('ion-tab-button').forEach((element) => {
46
+ element.style.transition = OPACITY_TRANSITION;
47
+ element.style.opacity = '1';
48
+ });
49
+ })
50
+ .afterClearStyles(['transform', 'opacity'])
51
+ .fromTo('transform', `scale(${sizes.closeButton.width / sizes.tabBar.width}, ${sizes.closeButton.height / sizes.tabBar.height})`, 'scale(1)')
52
+ .fromTo('opacity', '0', '1');
53
+ };
54
+ export const createReverseFabButtonAnimation = (ionFabButton, references) => {
55
+ return createAnimation()
56
+ .addElement(ionFabButton)
57
+ .beforeAddWrite(() => {
58
+ ionFabButton.querySelector('ion-icon')?.style.setProperty('opacity', '1');
59
+ ionFabButton.style.transformOrigin = 'center right';
60
+ })
61
+ .afterAddWrite(() => {
62
+ ionFabButton.style.pointerEvents = 'auto';
63
+ })
64
+ .fromTo('opacity', '0', '1')
65
+ .fromTo('transform', `scale(${references.searchContainer.height / references.fabButton.height})`, 'scale(1)');
66
+ };
@@ -0,0 +1,4 @@
1
+ import { TabBarSearchableFunction } from './interfaces';
2
+ export * from './interfaces';
3
+ export declare const attachTabBarSearchable: (ionTabBar: HTMLElement, ionFabButton: HTMLElement, ionFooter: HTMLElement) => TabBarSearchableFunction;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tab-bar-searchable/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAwB,wBAAwB,EAAwB,MAAM,cAAc,CAAC;AAgBpG,cAAc,cAAc,CAAC;AAqB7B,eAAO,MAAM,sBAAsB,GACjC,WAAW,WAAW,EACtB,cAAc,WAAW,EACzB,WAAW,WAAW,KACrB,wBAsBF,CAAC"}
@@ -0,0 +1,75 @@
1
+ import { createAnimation } from '@ionic/core';
2
+ import { ANIMATION_DELAY_BASE, ANIMATION_DURATION, ANIMATION_EASING, getElementReferences, getElementSizes, throwErrorByFailedClickElement, } from './utils';
3
+ import { TabBarSearchableType } from './interfaces';
4
+ import { createCloseButtonsAnimation, createEffectAnimation, createFabButtonAnimation, createSearchContainerAnimation, createTabBarAnimation, } from './animations/enter';
5
+ import { createReverseCloseButtonsAnimation, createReverseEffectAnimation, createReverseFabButtonAnimation, createReverseSearchContainerAnimation, createReverseTabBarAnimation, } from './animations/leave';
6
+ export * from './interfaces';
7
+ export const attachTabBarSearchable = (ionTabBar, ionFabButton, ionFooter) => {
8
+ if (!ionTabBar || !ionFabButton || !ionFooter) {
9
+ throw new Error('TabBarSearchable should be defined with props');
10
+ }
11
+ ionFooter.style.pointerEvents = 'none';
12
+ ionFooter.style.opacity = '0';
13
+ ionTabBar.style.transformOrigin = 'left center';
14
+ let searchableEventCache;
15
+ return async (event, type) => {
16
+ if (type === TabBarSearchableType.Enter) {
17
+ searchableEventCache = await enterEvent(event, ionTabBar, ionFabButton, ionFooter);
18
+ }
19
+ else if (searchableEventCache !== undefined) {
20
+ await leaveEvent(event, searchableEventCache, ionTabBar, ionFabButton, ionFooter);
21
+ searchableEventCache = undefined;
22
+ }
23
+ else {
24
+ throw new Error('TabBarSearchableType.Leave should be run after TabBarSearchableType.Enter');
25
+ }
26
+ };
27
+ };
28
+ const enterEvent = async (event, ionTabBar, ionFabButton, ionFooter) => {
29
+ if (!event.target?.closest('ion-fab-button')) {
30
+ throw throwErrorByFailedClickElement('ion-fab-button');
31
+ }
32
+ const references = getElementReferences(ionTabBar, ionFooter);
33
+ const sizes = getElementSizes(ionTabBar, ionFabButton, references);
34
+ const colorSelected = references.selectedTabButton
35
+ ? getComputedStyle(references.selectedTabButton).getPropertyValue('--color-selected').trim()
36
+ : '';
37
+ const effectAnimation = createEffectAnimation(references, sizes);
38
+ const searchContainerAnimation = createSearchContainerAnimation(references, sizes);
39
+ const closeButtonsAnimation = createCloseButtonsAnimation(references);
40
+ const tabBarAnimation = createTabBarAnimation(ionTabBar, references, sizes);
41
+ const fabButtonAnimation = createFabButtonAnimation(ionFabButton);
42
+ await createAnimation()
43
+ .delay(ANIMATION_DELAY_BASE)
44
+ .duration(ANIMATION_DURATION)
45
+ .easing(ANIMATION_EASING)
46
+ .addElement(ionFooter)
47
+ .afterAddWrite(() => (ionFooter.style.pointerEvents = 'auto'))
48
+ .fromTo('opacity', '0.8', '1')
49
+ .addAnimation([tabBarAnimation, fabButtonAnimation, searchContainerAnimation, effectAnimation, closeButtonsAnimation])
50
+ .play();
51
+ return {
52
+ elementSizes: sizes,
53
+ colorSelected,
54
+ };
55
+ };
56
+ const leaveEvent = async (event, searchableEventCache, ionTabBar, ionFabButton, ionFooter) => {
57
+ if (!event.target?.closest('ion-buttons[slot=start] ion-button')) {
58
+ throw throwErrorByFailedClickElement('ion-buttons[slot=start] ion-button');
59
+ }
60
+ const references = getElementReferences(ionTabBar, ionFooter);
61
+ const effectAnimation = createReverseEffectAnimation(references, searchableEventCache.elementSizes, searchableEventCache.colorSelected);
62
+ const searchContainerAnimation = createReverseSearchContainerAnimation(references, searchableEventCache.elementSizes);
63
+ const closeButtonsAnimation = createReverseCloseButtonsAnimation(references);
64
+ const tabBarAnimation = createReverseTabBarAnimation(ionTabBar, references, searchableEventCache.elementSizes);
65
+ const fabButtonAnimation = createReverseFabButtonAnimation(ionFabButton, searchableEventCache.elementSizes);
66
+ await createAnimation()
67
+ .delay(ANIMATION_DELAY_BASE)
68
+ .duration(ANIMATION_DURATION)
69
+ .easing(ANIMATION_EASING)
70
+ .addElement(ionFooter)
71
+ .afterAddWrite(() => (ionFooter.style.pointerEvents = 'none'))
72
+ .fromTo('opacity', '1', '0')
73
+ .addAnimation([tabBarAnimation, fabButtonAnimation, searchContainerAnimation, effectAnimation, closeButtonsAnimation])
74
+ .play();
75
+ };
@@ -0,0 +1,41 @@
1
+ export declare enum TabBarSearchableType {
2
+ Enter = "enter",
3
+ Leave = "leave"
4
+ }
5
+ export type TabBarSearchableFunction = (event: Event, type: TabBarSearchableType) => Promise<void>;
6
+ export interface SearchableEventCache {
7
+ elementSizes: ElementSizes;
8
+ colorSelected: string;
9
+ }
10
+ export interface ElementSizes {
11
+ tabBar: {
12
+ width: number;
13
+ height: number;
14
+ };
15
+ closeButton: {
16
+ width: number;
17
+ height: number;
18
+ };
19
+ fabButton: {
20
+ width: number;
21
+ height: number;
22
+ };
23
+ searchContainer: {
24
+ width: number;
25
+ height: number;
26
+ };
27
+ selectedTabButtonIcon: {
28
+ width: number;
29
+ height: number;
30
+ top: number;
31
+ left: number;
32
+ };
33
+ }
34
+ export interface ElementReferences {
35
+ searchContainer: HTMLElement;
36
+ closeButtons: HTMLElement;
37
+ selectedTabButton: HTMLElement;
38
+ selectedTabButtonIcon: HTMLElement;
39
+ closeButtonIcon: HTMLElement;
40
+ }
41
+ //# sourceMappingURL=interfaces.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../../src/tab-bar-searchable/interfaces.ts"],"names":[],"mappings":"AAAA,oBAAY,oBAAoB;IAC9B,KAAK,UAAU;IACf,KAAK,UAAU;CAChB;AAED,MAAM,MAAM,wBAAwB,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnG,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,YAAY,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;CACvB;AAGD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,WAAW,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/C,SAAS,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,eAAe,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IACnD,qBAAqB,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACrF;AAED,MAAM,WAAW,iBAAiB;IAChC,eAAe,EAAE,WAAW,CAAC;IAC7B,YAAY,EAAE,WAAW,CAAC;IAC1B,iBAAiB,EAAE,WAAW,CAAC;IAC/B,qBAAqB,EAAE,WAAW,CAAC;IACnC,eAAe,EAAE,WAAW,CAAC;CAC9B"}
@@ -0,0 +1,5 @@
1
+ export var TabBarSearchableType;
2
+ (function (TabBarSearchableType) {
3
+ TabBarSearchableType["Enter"] = "enter";
4
+ TabBarSearchableType["Leave"] = "leave";
5
+ })(TabBarSearchableType || (TabBarSearchableType = {}));
@@ -0,0 +1,12 @@
1
+ import { ElementReferences, ElementSizes } from './interfaces';
2
+ export declare const ANIMATION_DURATION = 400;
3
+ export declare const ANIMATION_DELAY_BASE = 140;
4
+ export declare const ANIMATION_DELAY_CLOSE_BUTTONS = 240;
5
+ export declare const ANIMATION_EASING = "cubic-bezier(0, 1, 0.22, 1)";
6
+ export declare const OPACITY_TRANSITION = "opacity 140ms ease";
7
+ export declare const throwErrorByFailedClickElement: (selector: string) => Error;
8
+ export declare const throwErrorByFailedExistElement: (selector: string) => Error;
9
+ export declare const getElement: (docs: HTMLElement, selector: string) => HTMLElement;
10
+ export declare const getElementReferences: (ionTabBar: HTMLElement, ionFooter: HTMLElement) => ElementReferences;
11
+ export declare const getElementSizes: (ionTabBar: HTMLElement, ionFabButton: HTMLElement, references: ElementReferences) => ElementSizes;
12
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/tab-bar-searchable/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE/D,eAAO,MAAM,kBAAkB,MAAM,CAAC;AACtC,eAAO,MAAM,oBAAoB,MAAM,CAAC;AACxC,eAAO,MAAM,6BAA6B,MAAM,CAAC;AACjD,eAAO,MAAM,gBAAgB,gCAAgC,CAAC;AAC9D,eAAO,MAAM,kBAAkB,uBAAuB,CAAC;AAEvD,eAAO,MAAM,8BAA8B,GAAI,UAAU,MAAM,KAAG,KAEjE,CAAC;AAEF,eAAO,MAAM,8BAA8B,GAAI,UAAU,MAAM,KAAG,KAEjE,CAAC;AAEF,eAAO,MAAM,UAAU,GAAI,MAAM,WAAW,EAAE,UAAU,MAAM,KAAG,WAMhE,CAAC;AAEF,eAAO,MAAM,oBAAoB,GAAI,WAAW,WAAW,EAAE,WAAW,WAAW,KAAG,iBA0BrF,CAAC;AAKF,eAAO,MAAM,eAAe,GAAI,WAAW,WAAW,EAAE,cAAc,WAAW,EAAE,YAAY,iBAAiB,KAAG,YAmBlH,CAAC"}
@@ -0,0 +1,60 @@
1
+ export const ANIMATION_DURATION = 400;
2
+ export const ANIMATION_DELAY_BASE = 140;
3
+ export const ANIMATION_DELAY_CLOSE_BUTTONS = 240;
4
+ export const ANIMATION_EASING = 'cubic-bezier(0, 1, 0.22, 1)';
5
+ export const OPACITY_TRANSITION = 'opacity 140ms ease';
6
+ export const throwErrorByFailedClickElement = (selector) => {
7
+ return new Error('Expected click element to be inside `' + selector + '`');
8
+ };
9
+ export const throwErrorByFailedExistElement = (selector) => {
10
+ return new Error('Expected element `' + selector + '` to exist');
11
+ };
12
+ export const getElement = (docs, selector) => {
13
+ const el = docs.querySelector(selector);
14
+ if (!el) {
15
+ throw throwErrorByFailedClickElement(selector);
16
+ }
17
+ return el;
18
+ };
19
+ export const getElementReferences = (ionTabBar, ionFooter) => {
20
+ const searchContainer = getElement(ionFooter, 'ion-searchbar .searchbar-input-container');
21
+ const closeButtons = getElement(ionFooter, 'ion-buttons[slot=start]');
22
+ const selectedTabButton = ionTabBar.querySelector('ion-tab-button.tab-selected');
23
+ const selectedTabButtonIcon = selectedTabButton?.querySelector('ion-icon');
24
+ const closeButtonIcon = closeButtons.querySelector('ion-icon');
25
+ if (!selectedTabButton) {
26
+ throw throwErrorByFailedExistElement('ion-tab-button.tab-selected');
27
+ }
28
+ if (!selectedTabButtonIcon) {
29
+ throw throwErrorByFailedExistElement('ion-tab-button.tab-selected ion-icon');
30
+ }
31
+ if (!closeButtonIcon) {
32
+ throw throwErrorByFailedExistElement('ion-buttons[slot=start] ion-button ion-icon');
33
+ }
34
+ return {
35
+ searchContainer,
36
+ closeButtons,
37
+ selectedTabButton,
38
+ selectedTabButtonIcon,
39
+ closeButtonIcon,
40
+ };
41
+ };
42
+ export const getElementSizes = (ionTabBar, ionFabButton, references) => {
43
+ const tabBarRect = ionTabBar.getBoundingClientRect();
44
+ const fabButtonRect = ionFabButton.getBoundingClientRect();
45
+ const closeButtonRect = references.closeButtons.getBoundingClientRect();
46
+ const searchContainerRect = references.searchContainer.getBoundingClientRect();
47
+ const selectedTabButtonIconRect = references.selectedTabButtonIcon.getBoundingClientRect();
48
+ return {
49
+ tabBar: { width: tabBarRect.width, height: tabBarRect.height },
50
+ closeButton: { width: closeButtonRect.width, height: closeButtonRect.height },
51
+ fabButton: { width: fabButtonRect.width, height: fabButtonRect.height },
52
+ searchContainer: { width: searchContainerRect.width, height: searchContainerRect.height },
53
+ selectedTabButtonIcon: {
54
+ width: selectedTabButtonIconRect.width,
55
+ height: selectedTabButtonIconRect.height,
56
+ top: selectedTabButtonIconRect.top,
57
+ left: selectedTabButtonIconRect.left,
58
+ },
59
+ };
60
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rdlabo/ionic-theme-ios26",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "iOS26 Theme for Ionic Framework",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
package/src/index.ts CHANGED
@@ -3,6 +3,7 @@ import { registerEffect } from './sheets-of-glass';
3
3
  export * from './sheets-of-glass/interfaces';
4
4
  export { iosEnterAnimation as popoverEnterAnimation } from './popover/animations/ios.enter';
5
5
  export { iosLeaveAnimation as popoverLeaveAnimation } from './popover/animations/ios.leave';
6
+ export * from './tab-bar-searchable';
6
7
 
7
8
  export const registerTabBarEffect = (targetElement: HTMLElement): registeredEffect | undefined => {
8
9
  return registerEffect(targetElement, 'ion-tab-button', 'tab-selected', {