@rdlabo/ionic-theme-ios26 1.1.1 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +62 -3
  2. package/dist/css/components/ion-button.css +1 -1
  3. package/dist/css/components/ion-fab.css +1 -1
  4. package/dist/css/components/ion-searchbar.css +1 -1
  5. package/dist/css/components/ion-toggle.css +1 -1
  6. package/dist/css/components/ion-toolbar.css +1 -1
  7. package/dist/css/ionic-theme-ios26.css +1 -1
  8. package/dist/css/utils/searchable.css +1 -0
  9. package/dist/index.d.ts +1 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +1 -0
  12. package/dist/popover/animations/ios.enter.d.ts.map +1 -1
  13. package/dist/popover/animations/ios.enter.js +3 -17
  14. package/dist/popover/utils.d.ts +2 -13
  15. package/dist/popover/utils.d.ts.map +1 -1
  16. package/dist/popover/utils.js +11 -119
  17. package/dist/sheets-of-glass/index.d.ts.map +1 -1
  18. package/dist/sheets-of-glass/index.js +6 -1
  19. package/dist/tab-bar-searchable/animations/enter.d.ts +8 -0
  20. package/dist/tab-bar-searchable/animations/enter.d.ts.map +1 -0
  21. package/dist/tab-bar-searchable/animations/enter.js +73 -0
  22. package/dist/tab-bar-searchable/animations/leave.d.ts +8 -0
  23. package/dist/tab-bar-searchable/animations/leave.d.ts.map +1 -0
  24. package/dist/tab-bar-searchable/animations/leave.js +66 -0
  25. package/dist/tab-bar-searchable/index.d.ts +4 -0
  26. package/dist/tab-bar-searchable/index.d.ts.map +1 -0
  27. package/dist/tab-bar-searchable/index.js +75 -0
  28. package/dist/tab-bar-searchable/interfaces.d.ts +41 -0
  29. package/dist/tab-bar-searchable/interfaces.d.ts.map +1 -0
  30. package/dist/tab-bar-searchable/interfaces.js +5 -0
  31. package/dist/tab-bar-searchable/utils.d.ts +12 -0
  32. package/dist/tab-bar-searchable/utils.d.ts.map +1 -0
  33. package/dist/tab-bar-searchable/utils.js +60 -0
  34. package/package.json +1 -1
  35. package/src/index.ts +1 -0
  36. package/src/popover/animations/ios.enter.ts +20 -52
  37. package/src/popover/utils.ts +8 -230
  38. package/src/sheets-of-glass/index.ts +6 -1
  39. package/src/styles/components/ion-button.scss +6 -0
  40. package/src/styles/components/ion-fab.scss +31 -1
  41. package/src/styles/components/ion-searchbar.scss +10 -0
  42. package/src/styles/components/ion-toggle.scss +3 -0
  43. package/src/styles/components/ion-toolbar.scss +20 -0
  44. package/src/styles/utils/searchable.scss +0 -0
  45. package/src/tab-bar-searchable/animations/enter.ts +92 -0
  46. package/src/tab-bar-searchable/animations/leave.ts +89 -0
  47. package/src/tab-bar-searchable/index.ts +141 -0
  48. package/src/tab-bar-searchable/interfaces.ts +28 -0
  49. package/src/tab-bar-searchable/utils.ts +75 -0
@@ -0,0 +1,92 @@
1
+ import { ElementReferences, ElementSizes } from '../interfaces';
2
+ import { Animation, createAnimation } from '@ionic/core';
3
+ import { cloneElement } from '../../utils';
4
+ import { ANIMATION_DELAY_CLOSE_BUTTONS, OPACITY_TRANSITION } from '../utils';
5
+
6
+ export const createEffectAnimation = (references: ElementReferences, sizes: ElementSizes): Animation => {
7
+ const effectElement = cloneElement('ion-icon');
8
+ const closeButtonRect = references.closeButtonIcon?.getBoundingClientRect();
9
+ const iconName = references.selectedTabButtonIcon?.getAttribute('name');
10
+ const selectedTabButtonIconRect = references.selectedTabButtonIcon?.getBoundingClientRect();
11
+
12
+ return createAnimation()
13
+ .addElement(effectElement)
14
+ .beforeAddWrite(() => {
15
+ effectElement.style.display = 'inline-block';
16
+ references.closeButtonIcon.style.opacity = '0';
17
+ if (iconName && closeButtonRect) {
18
+ effectElement.setAttribute('name', iconName);
19
+ effectElement.style.width = `${closeButtonRect.width}px`;
20
+ effectElement.style.height = `${closeButtonRect.height}px`;
21
+ }
22
+ })
23
+ .afterAddWrite(() => {
24
+ effectElement.style.display = 'none';
25
+ references.closeButtonIcon.style.opacity = '1';
26
+ })
27
+ .fromTo(
28
+ 'transform',
29
+ `translate3d(${selectedTabButtonIconRect.left}px, ${selectedTabButtonIconRect.top}px, 0)`,
30
+ `translate3d(${closeButtonRect.left}px, ${closeButtonRect.top}px, 0)`,
31
+ );
32
+ };
33
+
34
+ export const createSearchContainerAnimation = (references: ElementReferences, sizes: ElementSizes): Animation => {
35
+ return createAnimation()
36
+ .addElement(references.searchContainer)
37
+ .beforeAddWrite(() => (references.searchContainer.style.transformOrigin = 'right center'))
38
+ .fromTo(
39
+ 'transform',
40
+ `scale(${sizes.fabButton.width / sizes.searchContainer.width}, ${sizes.fabButton.height / sizes.searchContainer.height})`,
41
+ 'scale(1)',
42
+ )
43
+ .fromTo('opacity', '0.2', '1');
44
+ };
45
+
46
+ export const createCloseButtonsAnimation = (references: ElementReferences): Animation => {
47
+ return createAnimation()
48
+ .delay(ANIMATION_DELAY_CLOSE_BUTTONS)
49
+ .addElement(references.closeButtons)
50
+ .beforeAddWrite(() => (references.closeButtons.style.transformOrigin = 'left center'))
51
+ .afterClearStyles(['transform', 'transform-origin'])
52
+ .fromTo('transform', 'scale(1.5, 1)', 'scale(1)');
53
+ };
54
+
55
+ export const createTabBarAnimation = (ionTabBar: HTMLElement, references: ElementReferences, sizes: ElementSizes): Animation => {
56
+ return createAnimation()
57
+ .addElement(ionTabBar)
58
+ .beforeAddWrite(() => {
59
+ ionTabBar.querySelectorAll<HTMLElement>('ion-tab-button').forEach((element: HTMLElement) => {
60
+ element.style.transition = OPACITY_TRANSITION;
61
+ element.style.opacity = '0';
62
+ });
63
+ references.selectedTabButton.classList.remove('tab-selected');
64
+ const iconName = references.selectedTabButtonIcon?.getAttribute('name');
65
+ if (iconName) {
66
+ references.closeButtonIcon?.setAttribute('name', iconName);
67
+ }
68
+ })
69
+ .afterAddWrite(() => {
70
+ references.selectedTabButton.classList.add('tab-selected');
71
+ ionTabBar.style.pointerEvents = 'none';
72
+ })
73
+ .fromTo(
74
+ 'transform',
75
+ 'scale(1)',
76
+ `scale(${sizes.closeButton.width / sizes.tabBar.width}, ${sizes.closeButton.height / sizes.tabBar.height})`,
77
+ )
78
+ .fromTo('opacity', '1', '0');
79
+ };
80
+
81
+ export const createFabButtonAnimation = (ionFabButton: HTMLElement): Animation => {
82
+ return createAnimation()
83
+ .addElement(ionFabButton)
84
+ .beforeAddWrite(() => {
85
+ ionFabButton.style.transformOrigin = 'center right';
86
+ ionFabButton.querySelector<HTMLElement>('ion-icon')?.style.setProperty('opacity', '0');
87
+ })
88
+ .afterAddWrite(() => {
89
+ ionFabButton.style.pointerEvents = 'none';
90
+ })
91
+ .fromTo('opacity', '1', '0');
92
+ };
@@ -0,0 +1,89 @@
1
+ import { ElementReferences, ElementSizes } from '../interfaces';
2
+ import { Animation, createAnimation } from '@ionic/core';
3
+ import { cloneElement } from '../../utils';
4
+ import { ANIMATION_DELAY_CLOSE_BUTTONS, OPACITY_TRANSITION } from '../utils';
5
+
6
+ export const createReverseEffectAnimation = (
7
+ references: ElementReferences,
8
+ searchableElementSizes: ElementSizes,
9
+ colorSelected: string,
10
+ ): Animation => {
11
+ const effectElement = cloneElement('ion-icon');
12
+ const closeButtonRect = references.closeButtonIcon?.getBoundingClientRect();
13
+
14
+ return createAnimation()
15
+ .addElement(effectElement)
16
+ .beforeAddWrite(() => {
17
+ effectElement.style.display = 'inline-block';
18
+ references.closeButtonIcon.style.opacity = '0';
19
+ if (searchableElementSizes.selectedTabButtonIcon) {
20
+ effectElement.style.width = `${searchableElementSizes.selectedTabButtonIcon.width}px`;
21
+ effectElement.style.height = `${searchableElementSizes.selectedTabButtonIcon.height}px`;
22
+ effectElement.style.color = colorSelected;
23
+ }
24
+ })
25
+ .afterAddWrite(() => {
26
+ effectElement.style.display = 'none';
27
+ references.closeButtonIcon.style.opacity = '1';
28
+ effectElement.style.color = '';
29
+ })
30
+ .fromTo(
31
+ 'transform',
32
+ `translate3d(${closeButtonRect.left}px, ${closeButtonRect.top}px, 0)`,
33
+ `translate3d(${searchableElementSizes.selectedTabButtonIcon.left}px, ${searchableElementSizes.selectedTabButtonIcon.top}px, 0)`,
34
+ );
35
+ };
36
+
37
+ export const createReverseSearchContainerAnimation = (references: ElementReferences, sizes: ElementSizes): Animation => {
38
+ return createAnimation()
39
+ .addElement(references.searchContainer)
40
+ .beforeAddWrite(() => (references.searchContainer.style.transformOrigin = 'right center'))
41
+ .fromTo(
42
+ 'transform',
43
+ 'scale(1)',
44
+ `scale(${sizes.fabButton.width / sizes.searchContainer.width}, ${sizes.fabButton.height / sizes.searchContainer.height})`,
45
+ )
46
+ .fromTo('opacity', '1', '0.2');
47
+ };
48
+
49
+ export const createReverseCloseButtonsAnimation = (references: ElementReferences): Animation => {
50
+ return createAnimation()
51
+ .delay(ANIMATION_DELAY_CLOSE_BUTTONS)
52
+ .addElement(references.closeButtons)
53
+ .beforeAddWrite(() => (references.closeButtons.style.transformOrigin = 'left center'))
54
+ .afterClearStyles(['transform', 'transform-origin'])
55
+ .fromTo('transform', 'scale(1)', 'scale(1.5, 1)');
56
+ };
57
+
58
+ export const createReverseTabBarAnimation = (ionTabBar: HTMLElement, references: ElementReferences, sizes: ElementSizes): Animation => {
59
+ return createAnimation()
60
+ .addElement(ionTabBar)
61
+ .beforeAddWrite(() => {
62
+ ionTabBar.style.pointerEvents = 'auto';
63
+ ionTabBar.querySelectorAll<HTMLElement>('ion-tab-button').forEach((element: HTMLElement) => {
64
+ element.style.transition = OPACITY_TRANSITION;
65
+ element.style.opacity = '1';
66
+ });
67
+ })
68
+ .afterClearStyles(['transform', 'opacity'])
69
+ .fromTo(
70
+ 'transform',
71
+ `scale(${sizes.closeButton.width / sizes.tabBar.width}, ${sizes.closeButton.height / sizes.tabBar.height})`,
72
+ 'scale(1)',
73
+ )
74
+ .fromTo('opacity', '0', '1');
75
+ };
76
+
77
+ export const createReverseFabButtonAnimation = (ionFabButton: HTMLElement, references: ElementSizes): Animation => {
78
+ return createAnimation()
79
+ .addElement(ionFabButton)
80
+ .beforeAddWrite(() => {
81
+ ionFabButton.querySelector<HTMLElement>('ion-icon')?.style.setProperty('opacity', '1');
82
+ ionFabButton.style.transformOrigin = 'center right';
83
+ })
84
+ .afterAddWrite(() => {
85
+ ionFabButton.style.pointerEvents = 'auto';
86
+ })
87
+ .fromTo('opacity', '0', '1')
88
+ .fromTo('transform', `scale(${references.searchContainer.height / references.fabButton.height})`, 'scale(1)');
89
+ };
@@ -0,0 +1,141 @@
1
+ import { createAnimation } from '@ionic/core';
2
+ import {
3
+ ANIMATION_DELAY_BASE,
4
+ ANIMATION_DURATION,
5
+ ANIMATION_EASING,
6
+ getElementReferences,
7
+ getElementSizes,
8
+ throwErrorByFailedClickElement,
9
+ } from './utils';
10
+ import { SearchableEventCache, TabBarSearchableFunction, TabBarSearchableType } from './interfaces';
11
+ import {
12
+ createCloseButtonsAnimation,
13
+ createEffectAnimation,
14
+ createFabButtonAnimation,
15
+ createSearchContainerAnimation,
16
+ createTabBarAnimation,
17
+ } from './animations/enter';
18
+ import {
19
+ createReverseCloseButtonsAnimation,
20
+ createReverseEffectAnimation,
21
+ createReverseFabButtonAnimation,
22
+ createReverseSearchContainerAnimation,
23
+ createReverseTabBarAnimation,
24
+ } from './animations/leave';
25
+
26
+ export * from './interfaces';
27
+
28
+ /**
29
+ * <ion-fab vertical="bottom" horizontal="end" slot="fixed">
30
+ * <ion-fab-button (click)="present($event)">
31
+ * <ion-icon name="search"></ion-icon>
32
+ * </ion-fab-button>
33
+ * </ion-fab>
34
+ * <ion-footer [translucent]="true">
35
+ * <ion-toolbar>
36
+ * <ion-buttons slot="start">
37
+ * <!-- ion-icon does not need `name` attribute. -->
38
+ * <ion-button fill="default"><ion-icon slot="icon-only"></ion-icon>
39
+ * </ion-button>
40
+ * </ion-buttons>
41
+ * <!-- User set `ionChange` or other events. -->
42
+ * <ion-searchbar (ionChange)="example($event)"></ion-searchbar>
43
+ * </ion-toolbar>
44
+ * </ion-footer>
45
+ **/
46
+
47
+ export const attachTabBarSearchable = (
48
+ ionTabBar: HTMLElement,
49
+ ionFabButton: HTMLElement,
50
+ ionFooter: HTMLElement,
51
+ ): TabBarSearchableFunction => {
52
+ if (!ionTabBar || !ionFabButton || !ionFooter) {
53
+ throw new Error('TabBarSearchable should be defined with props');
54
+ }
55
+ // Initialize
56
+ ionFooter.style.pointerEvents = 'none';
57
+ ionFooter.style.opacity = '0';
58
+ ionTabBar.style.transformOrigin = 'left center';
59
+
60
+ // Saved Params
61
+ let searchableEventCache: SearchableEventCache | undefined;
62
+
63
+ return async (event: Event, type: TabBarSearchableType) => {
64
+ if (type === TabBarSearchableType.Enter) {
65
+ searchableEventCache = await enterEvent(event, ionTabBar, ionFabButton, ionFooter);
66
+ } else if (searchableEventCache !== undefined) {
67
+ await leaveEvent(event, searchableEventCache, ionTabBar, ionFabButton, ionFooter);
68
+ searchableEventCache = undefined;
69
+ } else {
70
+ throw new Error('TabBarSearchableType.Leave should be run after TabBarSearchableType.Enter');
71
+ }
72
+ };
73
+ };
74
+
75
+ const enterEvent = async (
76
+ event: Event,
77
+ ionTabBar: HTMLElement,
78
+ ionFabButton: HTMLElement,
79
+ ionFooter: HTMLElement,
80
+ ): Promise<SearchableEventCache> => {
81
+ if (!(event.target as HTMLElement)?.closest('ion-fab-button')) {
82
+ throw throwErrorByFailedClickElement('ion-fab-button');
83
+ }
84
+
85
+ const references = getElementReferences(ionTabBar, ionFooter);
86
+ const sizes = getElementSizes(ionTabBar, ionFabButton, references);
87
+ const colorSelected = references.selectedTabButton
88
+ ? getComputedStyle(references.selectedTabButton).getPropertyValue('--color-selected').trim()
89
+ : '';
90
+
91
+ const effectAnimation = createEffectAnimation(references, sizes);
92
+ const searchContainerAnimation = createSearchContainerAnimation(references, sizes);
93
+ const closeButtonsAnimation = createCloseButtonsAnimation(references);
94
+ const tabBarAnimation = createTabBarAnimation(ionTabBar, references, sizes);
95
+ const fabButtonAnimation = createFabButtonAnimation(ionFabButton);
96
+
97
+ await createAnimation()
98
+ .delay(ANIMATION_DELAY_BASE)
99
+ .duration(ANIMATION_DURATION)
100
+ .easing(ANIMATION_EASING)
101
+ .addElement(ionFooter)
102
+ .afterAddWrite(() => (ionFooter.style.pointerEvents = 'auto'))
103
+ .fromTo('opacity', '0.8', '1')
104
+ .addAnimation([tabBarAnimation, fabButtonAnimation, searchContainerAnimation, effectAnimation, closeButtonsAnimation])
105
+ .play();
106
+
107
+ return {
108
+ elementSizes: sizes,
109
+ colorSelected,
110
+ };
111
+ };
112
+
113
+ const leaveEvent = async (
114
+ event: Event,
115
+ searchableEventCache: SearchableEventCache,
116
+ ionTabBar: HTMLElement,
117
+ ionFabButton: HTMLElement,
118
+ ionFooter: HTMLElement,
119
+ ): Promise<void> => {
120
+ if (!(event.target as HTMLElement)?.closest('ion-buttons[slot=start] ion-button')) {
121
+ throw throwErrorByFailedClickElement('ion-buttons[slot=start] ion-button');
122
+ }
123
+
124
+ const references = getElementReferences(ionTabBar, ionFooter);
125
+
126
+ const effectAnimation = createReverseEffectAnimation(references, searchableEventCache.elementSizes, searchableEventCache.colorSelected);
127
+ const searchContainerAnimation = createReverseSearchContainerAnimation(references, searchableEventCache.elementSizes);
128
+ const closeButtonsAnimation = createReverseCloseButtonsAnimation(references);
129
+ const tabBarAnimation = createReverseTabBarAnimation(ionTabBar, references, searchableEventCache.elementSizes);
130
+ const fabButtonAnimation = createReverseFabButtonAnimation(ionFabButton, searchableEventCache.elementSizes);
131
+
132
+ await createAnimation()
133
+ .delay(ANIMATION_DELAY_BASE)
134
+ .duration(ANIMATION_DURATION)
135
+ .easing(ANIMATION_EASING)
136
+ .addElement(ionFooter)
137
+ .afterAddWrite(() => (ionFooter.style.pointerEvents = 'none'))
138
+ .fromTo('opacity', '1', '0')
139
+ .addAnimation([tabBarAnimation, fabButtonAnimation, searchContainerAnimation, effectAnimation, closeButtonsAnimation])
140
+ .play();
141
+ };
@@ -0,0 +1,28 @@
1
+ export enum TabBarSearchableType {
2
+ Enter = 'enter',
3
+ Leave = 'leave',
4
+ }
5
+
6
+ export type TabBarSearchableFunction = (event: Event, type: TabBarSearchableType) => Promise<void>;
7
+
8
+ export interface SearchableEventCache {
9
+ elementSizes: ElementSizes;
10
+ colorSelected: string;
11
+ }
12
+
13
+ // DOM要素とサイズ情報の型定義
14
+ export interface ElementSizes {
15
+ tabBar: { width: number; height: number };
16
+ closeButton: { width: number; height: number };
17
+ fabButton: { width: number; height: number };
18
+ searchContainer: { width: number; height: number };
19
+ selectedTabButtonIcon: { width: number; height: number; top: number; left: number };
20
+ }
21
+
22
+ export interface ElementReferences {
23
+ searchContainer: HTMLElement;
24
+ closeButtons: HTMLElement;
25
+ selectedTabButton: HTMLElement;
26
+ selectedTabButtonIcon: HTMLElement;
27
+ closeButtonIcon: HTMLElement;
28
+ }
@@ -0,0 +1,75 @@
1
+ import { ElementReferences, ElementSizes } from './interfaces';
2
+
3
+ export const ANIMATION_DURATION = 400;
4
+ export const ANIMATION_DELAY_BASE = 140;
5
+ export const ANIMATION_DELAY_CLOSE_BUTTONS = 240;
6
+ export const ANIMATION_EASING = 'cubic-bezier(0, 1, 0.22, 1)';
7
+ export const OPACITY_TRANSITION = 'opacity 140ms ease';
8
+
9
+ export const throwErrorByFailedClickElement = (selector: string): Error => {
10
+ return new Error('Expected click element to be inside `' + selector + '`');
11
+ };
12
+
13
+ export const throwErrorByFailedExistElement = (selector: string): Error => {
14
+ return new Error('Expected element `' + selector + '` to exist');
15
+ };
16
+
17
+ export const getElement = (docs: HTMLElement, selector: string): HTMLElement => {
18
+ const el = docs.querySelector<HTMLElement>(selector);
19
+ if (!el) {
20
+ throw throwErrorByFailedClickElement(selector);
21
+ }
22
+ return el;
23
+ };
24
+
25
+ export const getElementReferences = (ionTabBar: HTMLElement, ionFooter: HTMLElement): ElementReferences => {
26
+ const searchContainer = getElement(ionFooter, 'ion-searchbar .searchbar-input-container');
27
+ const closeButtons = getElement(ionFooter, 'ion-buttons[slot=start]');
28
+ const selectedTabButton = ionTabBar.querySelector<HTMLElement>('ion-tab-button.tab-selected');
29
+ const selectedTabButtonIcon = selectedTabButton?.querySelector('ion-icon');
30
+ const closeButtonIcon = closeButtons.querySelector('ion-icon');
31
+
32
+ if (!selectedTabButton) {
33
+ throw throwErrorByFailedExistElement('ion-tab-button.tab-selected');
34
+ }
35
+
36
+ if (!selectedTabButtonIcon) {
37
+ throw throwErrorByFailedExistElement('ion-tab-button.tab-selected ion-icon');
38
+ }
39
+
40
+ if (!closeButtonIcon) {
41
+ throw throwErrorByFailedExistElement('ion-buttons[slot=start] ion-button ion-icon');
42
+ }
43
+
44
+ return {
45
+ searchContainer,
46
+ closeButtons,
47
+ selectedTabButton,
48
+ selectedTabButtonIcon,
49
+ closeButtonIcon,
50
+ };
51
+ };
52
+
53
+ /**
54
+ * 各要素のサイズ情報を取得
55
+ */
56
+ export const getElementSizes = (ionTabBar: HTMLElement, ionFabButton: HTMLElement, references: ElementReferences): ElementSizes => {
57
+ const tabBarRect = ionTabBar.getBoundingClientRect();
58
+ const fabButtonRect = ionFabButton.getBoundingClientRect();
59
+ const closeButtonRect = references.closeButtons.getBoundingClientRect();
60
+ const searchContainerRect = references.searchContainer.getBoundingClientRect();
61
+ const selectedTabButtonIconRect = references.selectedTabButtonIcon!.getBoundingClientRect();
62
+
63
+ return {
64
+ tabBar: { width: tabBarRect.width, height: tabBarRect.height },
65
+ closeButton: { width: closeButtonRect.width, height: closeButtonRect.height },
66
+ fabButton: { width: fabButtonRect.width, height: fabButtonRect.height },
67
+ searchContainer: { width: searchContainerRect.width, height: searchContainerRect.height },
68
+ selectedTabButtonIcon: {
69
+ width: selectedTabButtonIconRect.width,
70
+ height: selectedTabButtonIconRect.height,
71
+ top: selectedTabButtonIconRect.top,
72
+ left: selectedTabButtonIconRect.left,
73
+ },
74
+ };
75
+ };