@rdlabo/ionic-theme-ios27 0.1.0-beta.pr154.shac3e35365120e → 0.1.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.
- package/README.md +3 -3
- package/dist/css/components/ion-action-sheet.css +1 -1
- package/dist/css/components/ion-alert.css +1 -1
- package/dist/css/components/ion-button.css +1 -1
- package/dist/css/components/ion-card.css +1 -1
- package/dist/css/components/ion-fab.css +1 -1
- package/dist/css/components/ion-list.css +1 -1
- package/dist/css/components/ion-menu.css +1 -1
- package/dist/css/components/ion-modal.css +1 -1
- package/dist/css/components/ion-popover.css +1 -1
- package/dist/css/components/ion-range.css +1 -1
- package/dist/css/components/ion-segment.css +1 -1
- package/dist/css/components/ion-tabs.css +1 -1
- package/dist/css/components/ion-toast.css +1 -1
- package/dist/css/components/ion-toolbar.css +1 -1
- package/dist/css/default-variables.css +1 -1
- package/dist/css/ionic-theme-ios27-dark-always.css +1 -1
- package/dist/css/ionic-theme-ios27-dark-class.css +1 -1
- package/dist/css/ionic-theme-ios27-dark-system.css +1 -1
- package/dist/css/ionic-theme-ios27.css +1 -1
- package/dist/css/md-ion-list-inset.css +1 -1
- package/dist/css/utils/dark/ion-list.css +1 -0
- package/dist/css/utils/group-heading.css +1 -0
- package/dist/native/components/searchable-tabs.d.ts.map +1 -1
- package/dist/native/components/searchable-tabs.js +9 -1
- package/dist/native/definitions.d.ts +4 -0
- package/dist/native/definitions.d.ts.map +1 -1
- package/dist/native/runtime.d.ts.map +1 -1
- package/dist/native/runtime.js +51 -27
- package/dist/native/shared/candidate.d.ts.map +1 -1
- package/dist/native/shared/candidate.js +17 -0
- package/dist/native/shared/crossfade.d.ts +8 -0
- package/dist/native/shared/crossfade.d.ts.map +1 -0
- package/dist/native/shared/crossfade.js +46 -0
- package/dist/native/shared/dom.d.ts.map +1 -1
- package/dist/native/shared/dom.js +2 -1
- package/dist/popover/animations/ios.enter.d.ts.map +1 -1
- package/dist/popover/animations/ios.enter.js +105 -9
- package/dist/popover/animations/ios.leave.d.ts.map +1 -1
- package/dist/popover/animations/ios.leave.js +15 -1
- package/dist/popover/callout-surface.d.ts +2 -0
- package/dist/popover/callout-surface.d.ts.map +1 -0
- package/dist/popover/callout-surface.js +44 -0
- package/dist/sheets-of-glass/index.js +2 -1
- package/dist/transition/ios.transition.d.ts.map +1 -1
- package/dist/transition/ios.transition.js +0 -223
- package/docs/experimental-animation.md +3 -3
- package/docs/features.md +7 -2
- package/docs/native-ui-shell.md +9 -5
- package/docs/special-markup.md +17 -0
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellButton.swift +7 -2
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellSearchController.swift +10 -0
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellSegment.swift +47 -15
- package/ios/Sources/IonicNativeUIShellPlugin/Components/ShellTabBar.swift +42 -7
- package/ios/Sources/IonicNativeUIShellPlugin/IonicNativeUIShellPlugin.swift +13 -6
- package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellCrossfade.swift +35 -0
- package/ios/Sources/IonicNativeUIShellPlugin/Shared/ShellSnapshot.swift +6 -1
- package/ios/Tests/IonicNativeUIShellPluginTests/ShellSnapshotTests.swift +59 -0
- package/package.json +1 -1
- package/src/native/components/searchable-tabs.ts +12 -1
- package/src/native/definitions.ts +4 -0
- package/src/native/runtime.ts +54 -31
- package/src/native/shared/candidate.ts +19 -0
- package/src/native/shared/crossfade.ts +49 -0
- package/src/native/shared/dom.ts +4 -1
- package/src/popover/animations/ios.enter.ts +123 -10
- package/src/popover/animations/ios.leave.ts +15 -1
- package/src/popover/callout-surface.ts +54 -0
- package/src/sheets-of-glass/index.ts +2 -2
- package/src/styles/components/ion-action-sheet.scss +35 -17
- package/src/styles/components/ion-alert.scss +51 -31
- package/src/styles/components/ion-button.scss +4 -7
- package/src/styles/components/ion-card.scss +64 -3
- package/src/styles/components/ion-fab.scss +3 -5
- package/src/styles/components/ion-list.scss +14 -1
- package/src/styles/components/ion-menu.scss +19 -7
- package/src/styles/components/ion-modal.scss +63 -14
- package/src/styles/components/ion-popover.scss +52 -9
- package/src/styles/components/ion-range.scss +49 -50
- package/src/styles/components/ion-segment.scss +15 -16
- package/src/styles/components/ion-tabs.scss +73 -13
- package/src/styles/components/ion-toast.scss +16 -25
- package/src/styles/components/ion-toolbar.scss +17 -1
- package/src/styles/default-variables.scss +4 -0
- package/src/styles/utils/_glass.scss +96 -0
- package/src/styles/utils/api.scss +46 -4
- package/src/styles/utils/dark/ion-button.scss +13 -0
- package/src/styles/utils/dark/ion-fab.scss +9 -0
- package/src/styles/utils/dark/ion-list.scss +7 -0
- package/src/styles/utils/dark/ion-segment.scss +8 -3
- package/src/styles/utils/dark/ion-tabs.scss +20 -0
- package/src/styles/utils/group-heading.scss +5 -0
- package/src/styles/utils/structured-list.scss +68 -15
- package/src/styles/utils/theme-dark.scss +23 -0
- package/src/transition/ios.transition.ts +0 -487
|
@@ -2,6 +2,7 @@ import { createAnimation } from '@ionic/core';
|
|
|
2
2
|
import type { Animation } from '@ionic/core';
|
|
3
3
|
import { getElementRoot } from '../../utils';
|
|
4
4
|
import { calculateWindowAdjustment, getPopoverDimensions, getPopoverPosition } from '../utils';
|
|
5
|
+
import { createCalloutSurface } from '../callout-surface';
|
|
5
6
|
|
|
6
7
|
const POPOVER_IOS_BODY_PADDING = 5;
|
|
7
8
|
export const POPOVER_IOS_BODY_MARGIN = 8;
|
|
@@ -19,12 +20,41 @@ export const iosEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation =>
|
|
|
19
20
|
|
|
20
21
|
const root = getElementRoot(baseEl);
|
|
21
22
|
const contentEl = root.querySelector('.popover-content') as HTMLElement;
|
|
23
|
+
const arrowEl = root.querySelector<HTMLElement>('.popover-arrow');
|
|
22
24
|
|
|
23
25
|
const referenceSizeEl = trigger || ev?.detail?.ionShadowTarget || ev?.target;
|
|
26
|
+
const anchorBounds = referenceSizeEl?.getBoundingClientRect();
|
|
27
|
+
// Overlays live outside the page, but must stay in the pane that opened them.
|
|
28
|
+
let paneAnchor = referenceSizeEl as HTMLElement | undefined;
|
|
29
|
+
let pane: HTMLElement | null = null;
|
|
30
|
+
while (paneAnchor && !pane) {
|
|
31
|
+
pane = paneAnchor.closest('ion-content, .ion-page, ion-menu');
|
|
32
|
+
paneAnchor = (paneAnchor.getRootNode() as ShadowRoot).host as HTMLElement | undefined;
|
|
33
|
+
}
|
|
34
|
+
const paneBounds = pane?.getBoundingClientRect();
|
|
35
|
+
const scroll = pane?.shadowRoot?.querySelector<HTMLElement>('[part="scroll"]');
|
|
36
|
+
const scrollStyle = scroll ? getComputedStyle(scroll) : null;
|
|
37
|
+
let paneLeft = Math.max(0, (paneBounds?.left ?? 0) + (parseFloat(scrollStyle?.paddingLeft ?? '0') || 0));
|
|
38
|
+
let paneRight = Math.min(bodyWidth, (paneBounds?.right ?? bodyWidth) - (parseFloat(scrollStyle?.paddingRight ?? '0') || 0));
|
|
39
|
+
// Toolbars may be siblings of ion-content; exclude the visible menu there too.
|
|
40
|
+
const splitPane = pane?.closest('ion-split-pane.split-pane-visible');
|
|
41
|
+
if (splitPane && pane?.closest('.split-pane-main')) {
|
|
42
|
+
for (const menu of Array.from(splitPane.querySelectorAll<HTMLElement>(':scope > ion-menu.menu-pane-visible'))) {
|
|
43
|
+
const rect = menu.getBoundingClientRect();
|
|
44
|
+
if (rect.left <= paneLeft && rect.right > paneLeft) paneLeft = rect.right;
|
|
45
|
+
if (rect.right >= paneRight && rect.left < paneRight) paneRight = rect.left;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
const paneMargin = size === 'cover' ? 0 : POPOVER_IOS_BODY_MARGIN;
|
|
49
|
+
const availableWidth = Math.max(0, paneRight - paneLeft - paneMargin * 2);
|
|
50
|
+
if (pane && availableWidth > 0 && contentEl.getBoundingClientRect().width > availableWidth) {
|
|
51
|
+
contentEl.dataset['previousMaxWidth'] = contentEl.style.maxWidth;
|
|
52
|
+
contentEl.style.maxWidth = `${availableWidth}px`;
|
|
53
|
+
}
|
|
24
54
|
const { contentWidth, contentHeight } = getPopoverDimensions(size, contentEl, referenceSizeEl);
|
|
25
55
|
|
|
26
56
|
const isReplace = ((): boolean => {
|
|
27
|
-
if (!['ion-button', 'ion-buttons'].includes(referenceSizeEl.localName)) {
|
|
57
|
+
if (reference === 'event' || !referenceSizeEl || !['ion-button', 'ion-buttons'].includes(referenceSizeEl.localName)) {
|
|
28
58
|
return false;
|
|
29
59
|
}
|
|
30
60
|
if (referenceSizeEl.matches('.ios-theme-disabled, .ios26-disabled')) {
|
|
@@ -42,10 +72,22 @@ export const iosEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation =>
|
|
|
42
72
|
|
|
43
73
|
const results = getPopoverPosition(isRTL, contentWidth, contentHeight, reference, side, align, defaultPosition, trigger, ev);
|
|
44
74
|
|
|
75
|
+
// Use the same reference for placement, the callout and the animation origin.
|
|
76
|
+
const anchor = reference === 'event' ? results.referenceCoordinates : anchorBounds;
|
|
77
|
+
|
|
45
78
|
const padding = size === 'cover' ? 0 : POPOVER_IOS_BODY_PADDING;
|
|
46
79
|
const margin = size === 'cover' ? 0 : POPOVER_IOS_BODY_MARGIN;
|
|
47
80
|
|
|
48
|
-
const {
|
|
81
|
+
const {
|
|
82
|
+
originX,
|
|
83
|
+
originY,
|
|
84
|
+
top,
|
|
85
|
+
left: windowLeft,
|
|
86
|
+
bottom,
|
|
87
|
+
checkSafeAreaLeft,
|
|
88
|
+
checkSafeAreaRight,
|
|
89
|
+
addPopoverBottomClass,
|
|
90
|
+
} = calculateWindowAdjustment(
|
|
49
91
|
side,
|
|
50
92
|
results.top,
|
|
51
93
|
results.left,
|
|
@@ -58,14 +100,59 @@ export const iosEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation =>
|
|
|
58
100
|
results.originX,
|
|
59
101
|
results.originY,
|
|
60
102
|
results.referenceCoordinates,
|
|
61
|
-
referenceSizeEl
|
|
103
|
+
referenceSizeEl?.getBoundingClientRect(),
|
|
62
104
|
isReplace,
|
|
63
105
|
);
|
|
106
|
+
// A replacing surface grows inward from the button's edge, not its center.
|
|
107
|
+
const preferredLeft =
|
|
108
|
+
isReplace && anchorBounds
|
|
109
|
+
? anchorBounds.left + anchorBounds.width / 2 <= (paneLeft + paneRight) / 2
|
|
110
|
+
? anchorBounds.left
|
|
111
|
+
: anchorBounds.right - contentWidth
|
|
112
|
+
: windowLeft;
|
|
113
|
+
const left = pane ? Math.max(paneLeft + paneMargin, Math.min(paneRight - paneMargin - contentWidth, preferredLeft)) : preferredLeft;
|
|
114
|
+
const physicalSide = side === 'start' ? (isRTL ? 'right' : 'left') : side === 'end' ? (isRTL ? 'left' : 'right') : side;
|
|
115
|
+
const horizontal = physicalSide === 'left' || physicalSide === 'right';
|
|
116
|
+
const above = addPopoverBottomClass || physicalSide === 'top';
|
|
117
|
+
const hasCallout = !!arrowEl && !isReplace && !!anchor && size !== 'cover' && bottom === undefined;
|
|
118
|
+
const surfaceTop = hasCallout && !horizontal ? top + (above ? -5 : 5) : top;
|
|
119
|
+
const contentOrigin = anchor
|
|
120
|
+
? `${anchor.left + anchor.width / 2 - left}px ${anchor.top + anchor.height / 2 - surfaceTop}px`
|
|
121
|
+
: `${originX} ${originY}`;
|
|
64
122
|
|
|
65
123
|
const baseAnimation = createAnimation();
|
|
66
124
|
const backdropAnimation = createAnimation();
|
|
67
125
|
const contentAnimation = createAnimation();
|
|
68
126
|
const targetAnimation = createAnimation();
|
|
127
|
+
const arrowAnimation = createAnimation();
|
|
128
|
+
const surfaceAnimation = createAnimation();
|
|
129
|
+
if (arrowEl) {
|
|
130
|
+
arrowAnimation.addElement(arrowEl).delay(300).duration(200).fromTo('opacity', 0, 1);
|
|
131
|
+
}
|
|
132
|
+
if (hasCallout && anchor) {
|
|
133
|
+
const length = horizontal ? contentHeight : contentWidth;
|
|
134
|
+
const inset = Math.min(48, length / 2);
|
|
135
|
+
const center = Math.max(
|
|
136
|
+
inset,
|
|
137
|
+
Math.min(length - inset, horizontal ? anchor.top + anchor.height / 2 - top : anchor.left + anchor.width / 2 - left),
|
|
138
|
+
);
|
|
139
|
+
const arrowSide = horizontal ? (physicalSide === 'left' ? 'right' : 'left') : above ? 'bottom' : 'top';
|
|
140
|
+
const layers = createCalloutSurface(root, contentWidth, contentHeight, arrowSide, center);
|
|
141
|
+
baseEl.classList.add('ios-theme-callout');
|
|
142
|
+
for (const layer of layers) {
|
|
143
|
+
layer.style.left = `calc(${left - 32}px + var(--offset-x, 0))`;
|
|
144
|
+
layer.style.top = `calc(${surfaceTop - 32}px + var(--offset-y, 0))`;
|
|
145
|
+
const [originLeft, originTop] = contentOrigin.split(' ').map(parseFloat);
|
|
146
|
+
layer.style.transformOrigin = `${originLeft + 32}px ${originTop + 32}px`;
|
|
147
|
+
}
|
|
148
|
+
surfaceAnimation
|
|
149
|
+
.addElement(layers)
|
|
150
|
+
.delay(100)
|
|
151
|
+
.duration(400)
|
|
152
|
+
.easing('cubic-bezier(0, 1, 0.22, 1)')
|
|
153
|
+
.fromTo('transform', 'scale(0)', 'scale(1)')
|
|
154
|
+
.fromTo('opacity', 0.01, 1);
|
|
155
|
+
}
|
|
69
156
|
|
|
70
157
|
backdropAnimation
|
|
71
158
|
.delay(100)
|
|
@@ -86,12 +173,12 @@ export const iosEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation =>
|
|
|
86
173
|
.delay(100)
|
|
87
174
|
.duration(400)
|
|
88
175
|
.addElement(root.querySelector('.popover-content')!)
|
|
89
|
-
.beforeStyles({ 'transform-origin':
|
|
176
|
+
.beforeStyles({ 'transform-origin': contentOrigin })
|
|
90
177
|
.beforeAddWrite(() => {
|
|
91
178
|
/**
|
|
92
179
|
* 'transformOrigin' use for leave animation.
|
|
93
180
|
*/
|
|
94
|
-
root.querySelector<HTMLElement>('.popover-content')!.dataset['transformOrigin'] =
|
|
181
|
+
root.querySelector<HTMLElement>('.popover-content')!.dataset['transformOrigin'] = contentOrigin;
|
|
95
182
|
})
|
|
96
183
|
.fromTo('transform', 'scale(0)', 'scale(1)')
|
|
97
184
|
.fromTo('opacity', 0.01, 1);
|
|
@@ -129,16 +216,42 @@ export const iosEnterAnimation = (baseEl: HTMLElement, opts?: any): Animation =>
|
|
|
129
216
|
|
|
130
217
|
let leftValue = `${left}px`;
|
|
131
218
|
|
|
132
|
-
if (checkSafeAreaLeft) {
|
|
219
|
+
if (checkSafeAreaLeft && !pane) {
|
|
133
220
|
leftValue = `${left}px${safeAreaLeft}`;
|
|
134
221
|
}
|
|
135
|
-
if (checkSafeAreaRight) {
|
|
222
|
+
if (checkSafeAreaRight && !pane) {
|
|
136
223
|
leftValue = `${left}px${safeAreaRight}`;
|
|
137
224
|
}
|
|
138
225
|
|
|
139
|
-
contentEl.style.setProperty('top', `calc(${
|
|
226
|
+
contentEl.style.setProperty('top', `calc(${surfaceTop}px + var(--offset-y, 0))`);
|
|
140
227
|
contentEl.style.setProperty('left', `calc(${leftValue} + var(--offset-x, 0))`);
|
|
141
|
-
contentEl.style.setProperty('transform-origin',
|
|
228
|
+
contentEl.style.setProperty('transform-origin', contentOrigin);
|
|
229
|
+
|
|
230
|
+
// Morphing buttons replace their anchor; ordinary anchored popovers point to it.
|
|
231
|
+
if (arrowEl) {
|
|
232
|
+
arrowEl.style.display = 'none';
|
|
233
|
+
if (hasCallout && anchor) {
|
|
234
|
+
const inset = Math.min(48, (horizontal ? contentHeight : contentWidth) / 2);
|
|
235
|
+
const clamp = (value: number, length: number) => Math.max(inset, Math.min(length - inset, value));
|
|
236
|
+
let arrowLeft: number;
|
|
237
|
+
let arrowTop: number;
|
|
238
|
+
let rotation: number;
|
|
239
|
+
if (horizontal) {
|
|
240
|
+
arrowLeft = physicalSide === 'left' ? left + contentWidth - 10 : left - 24;
|
|
241
|
+
arrowTop = top + clamp(anchor.top + anchor.height / 2 - top, contentHeight) - 7;
|
|
242
|
+
rotation = physicalSide === 'left' ? 90 : -90;
|
|
243
|
+
} else {
|
|
244
|
+
arrowLeft = left + clamp(anchor.left + anchor.width / 2 - left, contentWidth) - 17;
|
|
245
|
+
arrowTop = above ? surfaceTop + contentHeight - 1 : surfaceTop - 13;
|
|
246
|
+
rotation = above ? 180 : 0;
|
|
247
|
+
}
|
|
248
|
+
arrowEl.style.setProperty('display', 'block');
|
|
249
|
+
arrowEl.style.setProperty('top', `calc(${arrowTop}px + var(--offset-y, 0))`);
|
|
250
|
+
arrowEl.style.setProperty('left', `calc(${arrowLeft}px + var(--offset-x, 0))`);
|
|
251
|
+
arrowEl.style.setProperty('bottom', 'auto');
|
|
252
|
+
arrowEl.style.setProperty('transform', `rotate(${rotation}deg)`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
142
255
|
})
|
|
143
|
-
.addAnimation([backdropAnimation, contentAnimation, targetAnimation]);
|
|
256
|
+
.addAnimation([backdropAnimation, contentAnimation, targetAnimation, arrowAnimation, surfaceAnimation]);
|
|
144
257
|
};
|
|
@@ -15,6 +15,12 @@ export const iosLeaveAnimation = (baseEl: HTMLElement): Animation => {
|
|
|
15
15
|
const backdropAnimation = createAnimation();
|
|
16
16
|
const contentAnimation = createAnimation();
|
|
17
17
|
const targetAnimation = createAnimation();
|
|
18
|
+
const surfaceAnimation = createAnimation()
|
|
19
|
+
.addElement(Array.from(root.querySelectorAll<HTMLElement>('.ios-theme-callout-layer')))
|
|
20
|
+
.duration(400)
|
|
21
|
+
.easing('ease')
|
|
22
|
+
.fromTo('opacity', 0.99, 0)
|
|
23
|
+
.fromTo('transform', 'scale(1)', 'scale(0)');
|
|
18
24
|
|
|
19
25
|
const doc = baseEl.ownerDocument as any;
|
|
20
26
|
const replaceElement = doc.querySelector('.ios27-replace-element') as HTMLElement | null;
|
|
@@ -59,18 +65,26 @@ export const iosLeaveAnimation = (baseEl: HTMLElement): Animation => {
|
|
|
59
65
|
.afterAddWrite(() => {
|
|
60
66
|
baseEl.style.removeProperty('--width');
|
|
61
67
|
baseEl.classList.remove('popover-bottom');
|
|
68
|
+
baseEl.classList.remove('ios-theme-callout');
|
|
69
|
+
root.querySelectorAll('.ios-theme-callout-layer').forEach((layer) => layer.remove());
|
|
62
70
|
|
|
63
71
|
contentEl.style.removeProperty('top');
|
|
64
72
|
contentEl.style.removeProperty('left');
|
|
65
73
|
contentEl.style.removeProperty('bottom');
|
|
66
74
|
contentEl.style.removeProperty('transform-origin');
|
|
75
|
+
if (contentEl.dataset['previousMaxWidth'] !== undefined) {
|
|
76
|
+
contentEl.style.maxWidth = contentEl.dataset['previousMaxWidth'];
|
|
77
|
+
delete contentEl.dataset['previousMaxWidth'];
|
|
78
|
+
}
|
|
67
79
|
|
|
68
80
|
if (arrowEl) {
|
|
69
81
|
arrowEl.style.removeProperty('top');
|
|
70
82
|
arrowEl.style.removeProperty('left');
|
|
71
83
|
arrowEl.style.removeProperty('display');
|
|
84
|
+
arrowEl.style.removeProperty('bottom');
|
|
85
|
+
arrowEl.style.removeProperty('transform');
|
|
72
86
|
}
|
|
73
87
|
})
|
|
74
88
|
.duration(300)
|
|
75
|
-
.addAnimation([backdropAnimation, contentAnimation, targetAnimation]);
|
|
89
|
+
.addAnimation([backdropAnimation, contentAnimation, targetAnimation, surfaceAnimation]);
|
|
76
90
|
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** Draw one glass silhouette so the arrow cannot sample the body's own border/shadow. */
|
|
2
|
+
export const createCalloutSurface = (
|
|
3
|
+
root: ShadowRoot | HTMLElement,
|
|
4
|
+
width: number,
|
|
5
|
+
height: number,
|
|
6
|
+
side: 'top' | 'right' | 'bottom' | 'left',
|
|
7
|
+
center: number,
|
|
8
|
+
): HTMLElement[] => {
|
|
9
|
+
const inset = 32;
|
|
10
|
+
const x = inset;
|
|
11
|
+
const y = inset;
|
|
12
|
+
const right = x + width;
|
|
13
|
+
const bottom = y + height;
|
|
14
|
+
const radius = Math.max(0, Math.min(34, width / 2, height / 2, (side === 'left' || side === 'right' ? height : width) / 2 - 17));
|
|
15
|
+
const point = (u: number, v: number): string => {
|
|
16
|
+
switch (side) {
|
|
17
|
+
case 'top':
|
|
18
|
+
return `${x + center - 17 + u} ${y - 13 + v}`;
|
|
19
|
+
case 'right':
|
|
20
|
+
return `${right + 13 - v} ${y + center - 17 + u}`;
|
|
21
|
+
case 'bottom':
|
|
22
|
+
return `${x + center + 17 - u} ${bottom + 13 - v}`;
|
|
23
|
+
case 'left':
|
|
24
|
+
return `${x - 13 + v} ${y + center + 17 - u}`;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const arrow = `L ${point(0, 13)} C ${point(3, 13)} ${point(4.5, 12)} ${point(6.5, 10)} L ${point(14.5, 1.5)} C ${point(16, -0.5)} ${point(18, -0.5)} ${point(19.5, 1.5)} L ${point(27.5, 10)} C ${point(29.5, 12)} ${point(31, 13)} ${point(34, 13)}`;
|
|
28
|
+
const path = `M ${x + radius} ${y} ${side === 'top' ? arrow : ''}
|
|
29
|
+
L ${right - radius} ${y} A ${radius} ${radius} 0 0 1 ${right} ${y + radius} ${side === 'right' ? arrow : ''}
|
|
30
|
+
L ${right} ${bottom - radius} A ${radius} ${radius} 0 0 1 ${right - radius} ${bottom} ${side === 'bottom' ? arrow : ''}
|
|
31
|
+
L ${x + radius} ${bottom} A ${radius} ${radius} 0 0 1 ${x} ${bottom - radius} ${side === 'left' ? arrow : ''}
|
|
32
|
+
L ${x} ${y + radius} A ${radius} ${radius} 0 0 1 ${x + radius} ${y} Z`;
|
|
33
|
+
const svg = (body: string) =>
|
|
34
|
+
`url("data:image/svg+xml,${encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" width="${width + inset * 2}" height="${height + inset * 2}">${body}</svg>`)}")`;
|
|
35
|
+
const fill = svg(`<path d="${path}" fill="white"/>`);
|
|
36
|
+
const stroke = svg(`<path d="${path}" fill="none" stroke="white" stroke-width="0.5"/>`);
|
|
37
|
+
const shadow = svg(
|
|
38
|
+
`<defs><filter id="s" x="-50%" y="-100%" width="200%" height="300%"><feGaussianBlur stdDeviation="4"/><feOffset dy="2"/><feComposite in2="SourceGraphic" operator="out"/></filter></defs><path d="${path}" fill="white" filter="url(#s)"/>`,
|
|
39
|
+
);
|
|
40
|
+
const wrapper = root.querySelector('.popover-wrapper')!;
|
|
41
|
+
return [
|
|
42
|
+
['callout-shadow', shadow],
|
|
43
|
+
['callout-glass', fill],
|
|
44
|
+
['callout-border', stroke],
|
|
45
|
+
].map(([part, mask]) => {
|
|
46
|
+
const element = document.createElement('div');
|
|
47
|
+
element.setAttribute('part', part);
|
|
48
|
+
element.className = 'ios-theme-callout-layer';
|
|
49
|
+
element.setAttribute('aria-hidden', 'true');
|
|
50
|
+
element.style.cssText = `position:absolute;pointer-events:none;z-index:9;width:${width + inset * 2}px;height:${height + inset * 2}px;mask-image:${mask};-webkit-mask-image:${mask};mask-repeat:no-repeat;`;
|
|
51
|
+
wrapper.append(element);
|
|
52
|
+
return element;
|
|
53
|
+
});
|
|
54
|
+
};
|
|
@@ -307,14 +307,14 @@ export const registerEffect = (
|
|
|
307
307
|
stopTabPress();
|
|
308
308
|
moveAnimation.destroy();
|
|
309
309
|
moveAnimation = undefined;
|
|
310
|
+
const tabs = Array.from(targetElement.querySelectorAll<HTMLElement>(effectTagName)).filter((tab) => tab.offsetWidth > 0);
|
|
310
311
|
const releasing = createTabBarReleaseAnimation(
|
|
311
312
|
effectElement,
|
|
312
313
|
currentTouchedElement,
|
|
313
314
|
tapElapsed !== undefined && tapElapsed < 200
|
|
314
315
|
? {
|
|
315
316
|
elapsed: tapElapsed,
|
|
316
|
-
distance:
|
|
317
|
-
Math.abs(currentTouchedElement.offsetLeft - selectedElementBeforeGesture!.offsetLeft) / currentTouchedElement.offsetWidth,
|
|
317
|
+
distance: Math.abs(tabs.indexOf(currentTouchedElement) - tabs.indexOf(selectedElementBeforeGesture!)),
|
|
318
318
|
}
|
|
319
319
|
: undefined,
|
|
320
320
|
);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
@use '../utils/api';
|
|
2
2
|
|
|
3
3
|
ion-action-sheet.ios:not(.ios-theme-disabled, .ios26-disabled) {
|
|
4
|
+
@include api.glass-overlay-variables;
|
|
4
5
|
--backdrop-opacity: 0.2;
|
|
5
6
|
--color: var(--ion-color-step-600, var(--ion-text-color-step-400, #666666));
|
|
6
7
|
|
|
@@ -10,52 +11,69 @@ ion-action-sheet.ios:not(.ios-theme-disabled, .ios26-disabled) {
|
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
.action-sheet-container {
|
|
13
|
-
@include api.glass-
|
|
14
|
-
transition: transform 200ms ease;
|
|
15
|
-
&:has(.ion-activated) {
|
|
16
|
-
transform: scale(1.016);
|
|
17
|
-
@include api.glass-background-overlay-activated;
|
|
18
|
-
}
|
|
14
|
+
@include api.glass-overlay-surface;
|
|
19
15
|
margin: 0 12px;
|
|
20
|
-
border-radius:
|
|
16
|
+
border-radius: 36px;
|
|
17
|
+
padding: 16px;
|
|
21
18
|
|
|
22
19
|
.action-sheet-title,
|
|
23
20
|
.action-sheet-has-sub-title {
|
|
24
21
|
background: none;
|
|
25
22
|
}
|
|
26
23
|
.action-sheet-title {
|
|
27
|
-
padding
|
|
24
|
+
padding: 6px 14px 20px;
|
|
25
|
+
text-align: start;
|
|
26
|
+
font-size: 1rem;
|
|
27
|
+
line-height: 1.21;
|
|
28
|
+
font-weight: 600;
|
|
29
|
+
color: var(--ion-text-color, #000);
|
|
28
30
|
}
|
|
29
|
-
.action-sheet-
|
|
31
|
+
.action-sheet-sub-title {
|
|
32
|
+
padding-top: 8px;
|
|
33
|
+
font-size: 0.94rem;
|
|
34
|
+
line-height: 1.19;
|
|
35
|
+
color: var(--ion-text-color-step-600, var(--ion-color-step-400, #999999));
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
.action-sheet-group {
|
|
33
39
|
background: transparent;
|
|
40
|
+
margin: 0;
|
|
41
|
+
border-radius: 0;
|
|
34
42
|
|
|
35
43
|
button {
|
|
36
44
|
@include api.glass-background-overlay-button-variable;
|
|
37
45
|
&::after {
|
|
38
46
|
content: none;
|
|
39
47
|
}
|
|
40
|
-
&:not(.action-sheet-destructive):not(.action-sheet-selected) {
|
|
48
|
+
&:not(.action-sheet-destructive):not(.action-sheet-selected):not(.action-sheet-preferred) {
|
|
41
49
|
--button-color: var(--ion-text-color, #000);
|
|
42
50
|
--button-color-hover: var(--ion-text-color, #000);
|
|
43
51
|
--button-color-activated: var(--ion-text-color, #000);
|
|
44
52
|
}
|
|
53
|
+
&.action-sheet-preferred {
|
|
54
|
+
@include api.glass-overlay-preferred-button(--button-background, --button-color);
|
|
55
|
+
}
|
|
45
56
|
&.action-sheet-destructive {
|
|
46
|
-
--button-color: var(--ion-color-danger, #
|
|
47
|
-
|
|
57
|
+
--button-color: var(--ios-theme-destructive-color, var(--ion-color-danger, #ea292d));
|
|
58
|
+
}
|
|
59
|
+
&.action-sheet-destructive,
|
|
60
|
+
&.action-sheet-preferred {
|
|
61
|
+
color: var(--button-color);
|
|
62
|
+
--button-color-hover: var(--button-color);
|
|
63
|
+
--button-color-activated: var(--button-color);
|
|
64
|
+
--button-color-focused: var(--button-color);
|
|
48
65
|
}
|
|
49
|
-
font-weight:
|
|
66
|
+
font-weight: 500;
|
|
50
67
|
&.action-sheet-cancel {
|
|
51
|
-
margin-top:
|
|
52
|
-
font-weight:
|
|
68
|
+
margin-top: 8px;
|
|
69
|
+
font-weight: 600;
|
|
53
70
|
}
|
|
54
|
-
border-radius:
|
|
71
|
+
border-radius: 24px;
|
|
55
72
|
padding: 0 14px;
|
|
56
|
-
min-height:
|
|
73
|
+
min-height: 48px;
|
|
57
74
|
span.action-sheet-button-inner {
|
|
58
75
|
font-size: 1rem;
|
|
76
|
+
line-height: 1.19;
|
|
59
77
|
ion-icon {
|
|
60
78
|
font-size: 1.2rem;
|
|
61
79
|
}
|
|
@@ -1,35 +1,43 @@
|
|
|
1
1
|
@use '../utils/api';
|
|
2
2
|
|
|
3
3
|
ion-alert.ios:not(.ios-theme-disabled, .ios26-disabled) {
|
|
4
|
+
@include api.glass-overlay-variables;
|
|
4
5
|
--min-width: 280px;
|
|
6
|
+
--max-width: 320px;
|
|
5
7
|
--backdrop-opacity: 0.2;
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
.alert-wrapper {
|
|
10
|
+
@include api.glass-overlay-surface;
|
|
11
|
+
border-radius: 36px;
|
|
12
|
+
// The dialog receives programmatic focus; focus feedback belongs to its controls.
|
|
13
|
+
outline: none;
|
|
9
14
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
.alert-head {
|
|
16
|
+
padding: 22px 30px 7px;
|
|
17
|
+
text-align: start;
|
|
18
|
+
}
|
|
14
19
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
20
|
+
.alert-title {
|
|
21
|
+
margin: 0;
|
|
22
|
+
font-size: 1rem;
|
|
23
|
+
line-height: 1.21;
|
|
24
|
+
font-weight: 600;
|
|
19
25
|
}
|
|
20
|
-
border-radius: 32px;
|
|
21
26
|
|
|
22
|
-
.alert-
|
|
27
|
+
.alert-sub-title,
|
|
23
28
|
.alert-message {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
font-size: 0.94rem;
|
|
30
|
+
line-height: 1.19;
|
|
31
|
+
color: var(--ion-text-color-step-600, var(--ion-color-step-400, #999999));
|
|
27
32
|
}
|
|
28
33
|
|
|
29
34
|
.alert-message {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
padding: 0 30px 20px;
|
|
36
|
+
text-align: start;
|
|
37
|
+
|
|
38
|
+
&:empty {
|
|
39
|
+
padding: 0 0 12px;
|
|
40
|
+
}
|
|
33
41
|
}
|
|
34
42
|
|
|
35
43
|
.alert-radio-group {
|
|
@@ -41,26 +49,38 @@ ion-alert.ios:not(.ios-theme-disabled, .ios26-disabled) {
|
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
.alert-button-group {
|
|
44
|
-
padding: 0
|
|
45
|
-
gap: 8px
|
|
52
|
+
padding: 0 16px 16px;
|
|
53
|
+
gap: 8px;
|
|
46
54
|
.alert-button {
|
|
47
55
|
@include api.glass-background-overlay-button;
|
|
48
56
|
border: none;
|
|
49
|
-
border-radius:
|
|
50
|
-
margin: 0
|
|
51
|
-
min-width: calc(50% -
|
|
52
|
-
|
|
53
|
-
height:
|
|
57
|
+
border-radius: 24px;
|
|
58
|
+
margin: 0;
|
|
59
|
+
min-width: calc(50% - 4px);
|
|
60
|
+
min-height: 48px;
|
|
61
|
+
height: auto;
|
|
62
|
+
padding: 12px;
|
|
63
|
+
font-size: 1rem;
|
|
64
|
+
line-height: 1.19;
|
|
65
|
+
font-weight: 600;
|
|
66
|
+
|
|
67
|
+
.alert-button-inner {
|
|
68
|
+
min-height: 0;
|
|
69
|
+
height: auto;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
&.alert-button-role-destructive {
|
|
73
|
+
color: var(--ios-theme-destructive-color, var(--ion-color-danger, #ea292d));
|
|
74
|
+
}
|
|
54
75
|
|
|
55
|
-
|
|
76
|
+
&.alert-button-role-preferred {
|
|
77
|
+
@include api.glass-overlay-preferred-button;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
&:not(.alert-button-role-destructive):not(.alert-button-role-preferred) {
|
|
56
81
|
color: var(--ion-text-color, #000);
|
|
57
82
|
}
|
|
58
|
-
font-weight: 550;
|
|
59
83
|
}
|
|
60
84
|
}
|
|
61
|
-
|
|
62
|
-
button.action-sheet-cancel {
|
|
63
|
-
margin-top: 12px;
|
|
64
|
-
}
|
|
65
85
|
}
|
|
66
86
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
@use '../utils/api';
|
|
2
|
+
@use '../utils/glass';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Note: ion-back-button should not put inner ion-button.
|
|
@@ -13,7 +14,7 @@ $scaleup-large: 1.12;
|
|
|
13
14
|
$scaleup-large-icon-only: api.$button-scaleup-large-icon-only;
|
|
14
15
|
|
|
15
16
|
@mixin theme-buttons {
|
|
16
|
-
@include api.glass-background
|
|
17
|
+
@include api.glass-control-background;
|
|
17
18
|
border-radius: 25px;
|
|
18
19
|
overflow: hidden;
|
|
19
20
|
&:not(:has(.ion-activated)):hover {
|
|
@@ -299,11 +300,7 @@ $scaleup-large-icon-only: api.$button-scaleup-large-icon-only;
|
|
|
299
300
|
--background: rgba(var(--ios-theme-glass-background-rgb, var(--ios26-glass-background-rgb)), 0.72);
|
|
300
301
|
--background-hover: transparent;
|
|
301
302
|
--background-activated: transparent;
|
|
302
|
-
@
|
|
303
|
-
--box-shadow:
|
|
304
|
-
inset 0 0 8px 0 rgba(var(--ios-theme-glass-box-shadow-color-rgb, var(--ios26-glass-box-shadow-color-rgb)), 0.2),
|
|
305
|
-
0 0.5px 1px 0 rgba(var(--ios-theme-glass-box-shadow-color-rgb, var(--ios26-glass-box-shadow-color-rgb)), 0.08);
|
|
306
|
-
}
|
|
303
|
+
@include glass.light-shadow('--box-shadow');
|
|
307
304
|
|
|
308
305
|
&:not(.button-has-icon-only):not(.back-button-has-icon-only) {
|
|
309
306
|
&:not(.button-small):not(.button-large) {
|
|
@@ -312,7 +309,7 @@ $scaleup-large-icon-only: api.$button-scaleup-large-icon-only;
|
|
|
312
309
|
}
|
|
313
310
|
|
|
314
311
|
&::part(native) {
|
|
315
|
-
@include api.glass-background($include-background: false, $
|
|
312
|
+
@include api.glass-control-background($include-background: false, $shadow: var(--box-shadow));
|
|
316
313
|
}
|
|
317
314
|
|
|
318
315
|
&:not(.button-disabled) {
|
|
@@ -1,7 +1,68 @@
|
|
|
1
|
-
|
|
2
|
-
border-radius: 24px;
|
|
1
|
+
@use '../utils/group-heading';
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
ion-card.ios:not(.ios-theme-disabled, .ios26-disabled) {
|
|
4
|
+
ion-list:not(.ios-theme-disabled, .ios26-disabled) ion-item:not(.ios-theme-disabled, .ios26-disabled) {
|
|
5
5
|
--padding-start: 0;
|
|
6
6
|
}
|
|
7
|
+
|
|
8
|
+
// A standalone grouped surface, aligned with an inset list.
|
|
9
|
+
&:not(.ion-color) {
|
|
10
|
+
--background: var(--ion-card-background, var(--ion-item-background, var(--ion-background-color, #fff)));
|
|
11
|
+
--color: var(--ion-card-color, var(--ion-text-color, #000));
|
|
12
|
+
}
|
|
13
|
+
border-radius: 28px;
|
|
14
|
+
margin: 16px;
|
|
15
|
+
box-shadow: none;
|
|
16
|
+
font-size: 1rem;
|
|
17
|
+
line-height: 1.29;
|
|
18
|
+
|
|
19
|
+
> ion-card-header:not(.ios-theme-disabled, .ios26-disabled) {
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
gap: 4px;
|
|
22
|
+
padding: 16px 20px;
|
|
23
|
+
|
|
24
|
+
&:has(+ ion-card-content:not(.ios-theme-disabled, .ios26-disabled)) {
|
|
25
|
+
padding-bottom: 4px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
> ion-card-title:not(.ios-theme-disabled, .ios26-disabled) {
|
|
29
|
+
@include group-heading.typography;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
> ion-card-subtitle:not(.ios-theme-disabled, .ios26-disabled) {
|
|
33
|
+
margin: 0;
|
|
34
|
+
font-size: 1rem;
|
|
35
|
+
font-weight: 400;
|
|
36
|
+
line-height: 1.29;
|
|
37
|
+
letter-spacing: normal;
|
|
38
|
+
text-transform: none;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
> ion-card-content:not(.ios-theme-disabled, .ios26-disabled) {
|
|
43
|
+
padding: 16px 20px;
|
|
44
|
+
font-size: 1rem;
|
|
45
|
+
line-height: 1.29;
|
|
46
|
+
|
|
47
|
+
p {
|
|
48
|
+
font-size: inherit;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
> ion-list:not(.ios-theme-disabled, .ios26-disabled) {
|
|
52
|
+
background: transparent;
|
|
53
|
+
|
|
54
|
+
ion-item:not(.ios-theme-disabled, .ios26-disabled):not(.ion-color) {
|
|
55
|
+
--background: transparent;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
> ion-card-header:not(.ios-theme-disabled, .ios26-disabled) + ion-card-content:not(.ios-theme-disabled, .ios26-disabled) {
|
|
61
|
+
padding-top: 0;
|
|
62
|
+
}
|
|
63
|
+
&:not(.ion-color)
|
|
64
|
+
> ion-card-header:not(.ios-theme-disabled, .ios26-disabled)
|
|
65
|
+
> ion-card-subtitle:not(.ios-theme-disabled, .ios26-disabled):not(.ion-color) {
|
|
66
|
+
--color: rgba(var(--ion-color-medium-rgb, 146, 148, 156), 0.9);
|
|
67
|
+
}
|
|
7
68
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
@use '../utils/api';
|
|
2
|
+
@use '../utils/glass';
|
|
2
3
|
|
|
3
4
|
$scaleup-small-icon-only: api.$button-scaleup-small-icon-only;
|
|
4
5
|
$scaleup-default-icon-only: api.$button-scaleup-default-icon-only;
|
|
@@ -12,9 +13,7 @@ ion-fab.ios:not(.ios-theme-disabled, .ios26-disabled) {
|
|
|
12
13
|
|
|
13
14
|
ion-fab-button {
|
|
14
15
|
--background: rgba(var(--ios-theme-glass-background-rgb, var(--ios26-glass-background-rgb)), 0.72);
|
|
15
|
-
--box-shadow
|
|
16
|
-
inset 0 0 8px 0 rgba(var(--ios-theme-glass-box-shadow-color-rgb, var(--ios26-glass-box-shadow-color-rgb)), 0.2),
|
|
17
|
-
0 0.5px 1px 0 rgba(var(--ios-theme-glass-box-shadow-color-rgb, var(--ios26-glass-box-shadow-color-rgb)), 0.08);
|
|
16
|
+
@include glass.light-shadow('--box-shadow');
|
|
18
17
|
--transition:
|
|
19
18
|
var(--ios-theme-activated-transition-duration, var(--ios26-activated-transition-duration)) transform
|
|
20
19
|
cubic-bezier(0.25, 1.11, 0.78, 1.59),
|
|
@@ -62,8 +61,7 @@ ion-fab.ios:not(.ios-theme-disabled, .ios26-disabled) {
|
|
|
62
61
|
}
|
|
63
62
|
|
|
64
63
|
&::part(native) {
|
|
65
|
-
background-
|
|
66
|
-
@include api.glass-background($include-background: false, $include-box-shadow: false);
|
|
64
|
+
@include api.glass-control-background($include-background: false, $shadow: var(--box-shadow));
|
|
67
65
|
}
|
|
68
66
|
}
|
|
69
67
|
|