From dec46b5d317080dd5d97dc056f0d8e6d4c8c45ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20M=2E?= Date: Fri, 3 Apr 2026 19:24:33 +0100 Subject: [PATCH 1/2] fix(tab-button): update dark palette focused background color (#31050) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue number: resolves resolves internal --------- ## What is the current behavior? In the dark palette, the `--ion-tab-bar-background-focused` CSS token was not defined. When a `ion-tab-button` receives focus, the `::after` overlay on `.button-native` falls back to `get-color-shade(#fff)` → `#e0e0e0` — a light grey that blends into the dark background and makes the focus indicator invisible. ## What is the new behavior? - `--ion-tab-bar-background-focused` is now defined in the dark palette: `#252525` for iOS and `#353535` for MD , providing a dark-appropriate focus indicator. The same token is added to `high-contrast-dark.scss` with the same values. - A screenshot e2e test is added under `tab-button/test/states/` (where the existing focused-state tests live) using `configs({ palettes: ['dark'] })` and `page.setContent()` with `class="ion-focused"` applied directly, matching the established pattern for focus-state testing in this component. ## Does this introduce a breaking change? - [ ] Yes - [X] No ## Other information The test triggers keyboard mode (required by `focus-visible.ts`) before focusing the inner `` element inside the tab button's shadow DOM, since calling `.focus()` on the host element is a no-op without `delegatesFocus`. - [Preview 1](https://ionic-framework-git-fw-6293-ionic1.vercel.app/src/components/tabs/test/basic?ionic:mode=ios&palette=dark) - [Preview 2](https://ionic-framework-git-fw-6293-ionic1.vercel.app/src/components/tabs/test/basic?ionic:mode=md&palette=dark) --------- Co-authored-by: ionitron Co-authored-by: Brandy Smith Co-authored-by: Brandy Smith <6577830+brandyscarney@users.noreply.github.com> --- .../tab-button/test/states/tab-button.e2e.ts | 22 ++++++++++++++++++ ...focus-ios-ltr-dark-Mobile-Chrome-linux.png | Bin 0 -> 1482 bytes ...ocus-ios-ltr-dark-Mobile-Firefox-linux.png | Bin 0 -> 1790 bytes ...focus-ios-ltr-dark-Mobile-Safari-linux.png | Bin 0 -> 1686 bytes ...-focus-md-ltr-dark-Mobile-Chrome-linux.png | Bin 0 -> 1305 bytes ...focus-md-ltr-dark-Mobile-Firefox-linux.png | Bin 0 -> 1538 bytes ...-focus-md-ltr-dark-Mobile-Safari-linux.png | Bin 0 -> 1432 bytes core/src/css/palettes/dark.scss | 2 ++ core/src/css/palettes/high-contrast-dark.scss | 2 ++ 9 files changed, 26 insertions(+) create mode 100644 core/src/components/tab-button/test/states/tab-button.e2e.ts-snapshots/tab-button-focus-ios-ltr-dark-Mobile-Chrome-linux.png create mode 100644 core/src/components/tab-button/test/states/tab-button.e2e.ts-snapshots/tab-button-focus-ios-ltr-dark-Mobile-Firefox-linux.png create mode 100644 core/src/components/tab-button/test/states/tab-button.e2e.ts-snapshots/tab-button-focus-ios-ltr-dark-Mobile-Safari-linux.png create mode 100644 core/src/components/tab-button/test/states/tab-button.e2e.ts-snapshots/tab-button-focus-md-ltr-dark-Mobile-Chrome-linux.png create mode 100644 core/src/components/tab-button/test/states/tab-button.e2e.ts-snapshots/tab-button-focus-md-ltr-dark-Mobile-Firefox-linux.png create mode 100644 core/src/components/tab-button/test/states/tab-button.e2e.ts-snapshots/tab-button-focus-md-ltr-dark-Mobile-Safari-linux.png diff --git a/core/src/components/tab-button/test/states/tab-button.e2e.ts b/core/src/components/tab-button/test/states/tab-button.e2e.ts index bd10cad844b..5af2a6f660b 100644 --- a/core/src/components/tab-button/test/states/tab-button.e2e.ts +++ b/core/src/components/tab-button/test/states/tab-button.e2e.ts @@ -80,3 +80,25 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, screenshot, c }); }); }); + +configs({ palettes: ['dark'], directions: ['ltr'] }).forEach(({ title, screenshot, config }) => { + test.describe(title('tab-button: states in dark palette'), () => { + test.describe('focus', () => { + test('should render correct focus state in dark palette', async ({ page }) => { + await page.setContent( + ` + + + Favorites + + + `, + config + ); + + const tabBar = page.locator('ion-tab-bar'); + await expect(tabBar).toHaveScreenshot(screenshot('tab-button-focus')); + }); + }); + }); +}); diff --git a/core/src/components/tab-button/test/states/tab-button.e2e.ts-snapshots/tab-button-focus-ios-ltr-dark-Mobile-Chrome-linux.png b/core/src/components/tab-button/test/states/tab-button.e2e.ts-snapshots/tab-button-focus-ios-ltr-dark-Mobile-Chrome-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..14d96bb13658fd3abd2f2de83671402def9dfe3c GIT binary patch literal 1482 zcmai!c{~#e0LSO3C-H2Axk9{=Yx0;YOOD4JlL@6Va`c+bO>>RrPDO6gd_IIQ49zlS z9%YZpHH1A|jvVFVSab6{?~nIy@1Ni2`}uwU|7Ko6+KG$Givj=uafH1M3IGt$Ip$wQ z1dq33sbT^EIFX33xqKC0wlY2AsBlk_xxsy%oMjs4>nY{bi|Z@IsNC(zU;Y{hvlHrX z(P~g}TDqouX3IxnPgYzM4&3T$wiG?D8>s@O0QC*W*?KsK5y<>CBy@AUBZ^V%<=Jm0 z=SV=oq4Bq8p&G?BE}9nyB;fJI0DyVOGm|i4By(s8D1djn7|YLy{K*B7blyg!3w%!4 zM+_vWpC%#zwoU;&RJ8*10#yNi8Nj3qKi17~!4P$%Xs=z5f!*hc9`zMzq&%V;a?qWn zqsY_@WVP4X(v!ZYKx!C3SltxVeRgZlrryI?QIH8bw<5<;dtcF6`0-ox;z_0fzwyij z0h?qk(SISl^hzrTQ_IoI%R7Mo zaZwRXoG(fZY~V~Cd9SBcg5LE!rB};1KFdEb36)~yTDjW3kcK(H2EV6B&0BV9gjPU- zDEq1w1}P%W8bmKM)W*0rw2{z6Z-X)8P z$L`2#D}#%q)uQ!uABw|qa8;5-?*Iv;EUOGgGI+b)EBoeOJ^U^qOyRUpk9a)I-=xf# z;Je&ym+X?rT~>JDOgO8IeZhc{313=MceDL!2F2oRZ^Pr)?;IRp@peXnZslb^+Y1RL z^ANs^e;=kHT}cc2lyum1Q@a z*;%JSQPftYsPtu?L$v9w-v_UqH=~)Dg0!lQJT+w~StaF7MP898t>z5%p~aJFzEG9! zv$HX-a!+$5e zXesLu$#cqI!rQI~m7*@Z312LrIVe?@F)lNfWbPRnm75s59?p1_SZ>cyNJdQ49nU`z zGkrF1Bk-N8OPJyHN08ao*k-IEr`mC2o}cj{jA%@{C>!PuGh)R|ODhh{71^%(v}?dY zca`T@LWi~Q);gV2r}>I%B)W;zW?N!fGzTleRQuq*bIC{2V0>gexM(rAs0p%cT-{b{ zl+we642zG%bSona?K)p9;I))3@*IX8=Fjuh%*OLUevy uqW=lie|iOa9O9#+g*ai0V>tP5iiH5w1pWM_9+#lw2Y|3e+B91E5dQ&ex482F literal 0 HcmV?d00001 diff --git a/core/src/components/tab-button/test/states/tab-button.e2e.ts-snapshots/tab-button-focus-ios-ltr-dark-Mobile-Firefox-linux.png b/core/src/components/tab-button/test/states/tab-button.e2e.ts-snapshots/tab-button-focus-ios-ltr-dark-Mobile-Firefox-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..841928b47fabd10488053aaee8946bb7963fbe2e GIT binary patch literal 1790 zcmd6o{Xf%tAIHC2vu3R1W`s@~>lik2JGYs;Yje|<+?-v~q@^6>wqw#MqokuQH(57W zIGKo5?nUj;6wZYs>THjigzUJTn}cPD^ZgI5U$0+2kJk^+A71bG=k?ppg1V z002-vRIlRz03m?d1_1|8Vvb!N0Knh)c)16qGUX4Xe!(ZSv$Ovdn)1$~VZg$RS?+J< zYi+FFm)-BHjoSBk9-B5zx07;D0S%M@u(OrtzC z#rfpGW%LT^)ouwhIoGkn^kDL><}(yNpYM+_P|XKaQ#s_|om+qW;%_Wbh18*3LA!eE)(z2)V^B4_qf0aF-SdgQXx2G);iXN#>=0RJnNIT#;{lE6B84PA!H8<_4@;7c2KOI?zhB2a8Y(AuO(oQ zhglMde>#ry;WMsjtJLpYQZ?+mTT~v^#Yz*e&SlkMRC0^wY|N4>(>JjYYN3|b6KEhZBF8slG4I+X46=nH8Pnjbj4!;WfkzX{byx^BD^KG zCA+rQJ&(r}iRbh<-EvlahG>N3WF!lb*oVcaC}xYl(PRT}^%0&{=rG!z0ts=>nDZz)7YX5*b!mlszizx3o4 zwjfN6|H``VIDuc?D5vW(PYbQ ziR?pvsApyK9Fx!9*7Ka{yYz^{5fy(&QXn(Pha=oA3`lX@ni?}#_sXiO+8f6jlSYR^ zpC|l^ZVNa=(N~Ul?f%c~igML9I54n+zKp^1#BcYSMplcQ(pIa*eG8+)S`*T(n}s9b zD{Pw1T~{iV!$U(@xm;e{(B0i#gAcHjw*OR14yAq6hiZ;mdqTCv>X3%bbS=d91YSN8 ziEIkV(h53vuIhD9Pc0c5Zbr1Q5K9a#m@genHIRxIDe~bePSo_(dmaVQ>t+P1CJVEz zT$%dGuxcqc8(7Z}U;+@R#YDNZm7agagP}n@9x~|%%9=qnEH-$2%<+XitaB-gN{Pj3vAz>1aBSYbcV+=UP z2BwPxXEqox?Fbf0# zPy%PJV<4Id*+7-~Wk|Wx> z3MxQA4?~XF&*|3y-7IL7*Nu$#27rJ%DAmbl5uh73=<$Ep6&(_E0NV9lM|iea7w}&L Ne7tF1Pd!d${sU+KH;@1T literal 0 HcmV?d00001 diff --git a/core/src/components/tab-button/test/states/tab-button.e2e.ts-snapshots/tab-button-focus-ios-ltr-dark-Mobile-Safari-linux.png b/core/src/components/tab-button/test/states/tab-button.e2e.ts-snapshots/tab-button-focus-ios-ltr-dark-Mobile-Safari-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..86b46f9a31fa76252c5ba0992b24727b84501b46 GIT binary patch literal 1686 zcmb_dYgE!{6aQ;rE)tox$tK4w%gdX-YF>yG<<%%-MK(2)yd|4?SxVVZGbGo`5h2Ys zO)VE}^<8gic$Yv^H@uQsE{kMXW)ezNC~l=ey8rL{Wk2kEnCF>u=9y>CZ+?9`o3Rn7|9i#b8h9Acg8lsf-ULzw8k=c;_!GvUFpOha0ATb# zJkIw-I_K>(InjJ$*U+-2V2cZTsq918VQ#licW9hxOA0Q(q$nsn z{QK{+dwMVq;wvM4yRRQ31Gi%>9h==F@$KFW%Dy)Hy?b@~kbF7$HQh>HgD_c71yKln-NdNp!}> z#^yw5=o}Tjy1KkqB9Q=p$YgFF9)n`>Hy>v1+}R)8u%nga*nR(^Ci>+vdX9QC*%|di z_cNq7{l?^EkYP3e&(95Ojt)9`a%gM}1{$wlzdpwu96VB6Tbp%vcVko2fGWj|jq5de z3Wj9(eBE&3%5i(#{?boj&82!SCyB4ap4>47I}_j8(cywZ4QaL7i^JbjC<4ZsYm1Z( z$@L{jV&meNMMZFo)wb)$HUlD;MU{$7r&r|VLFjaPeM7?@E|IpnMBZg-$;MglE5Y|f zJ6kwMEBdoHtulO2C=~1H|09Jd?3x-If@GhmF$l@@lP9}NMqS+8a%VLQ;!+1Rmk5PI z6DX9u*N?eivD^9uv<37JFE^{zY9uE8r?Rr0&+ljChbCt|y*f2D<=D^&r&u)9*Y~U6 z$MMU{%D@lMNVt_Z9fw95{nFW4dSoZeoa-&O8RGWFA+iIqN= z#RV4506J5TK7amv&t+X1!S?WO(-(81d;)<0P_+uYc4dVAQ)pg7a`N!P$^kF0f>}-cg$u<5TT#{F z{F@aOPt&|mo}S-?g~_^vRmT_WWTfC=KQeg_h{&{jFvT-4I@+qHre`~VjFErsF$ zfj|gEqALcYw<|w#4wv0o0E5HVsmw27Mu|yDahERLO2=4qpIKFHEmI&k zB!2j?{h6fFJA)J%IrAZEG}hHJZB{RK@y24mmbfqLKFc#bI2_K{x_DYH4+se{Pfblt zNlVkNt;Abl(qpFOtg{Os{SW1ferpJ+ThwS4#(zEt{2`T2K7U^6ZMOqDDU}{^+`0D_ zL93G30U(J72M6W7%^e*#q*Cc@xW6Yij!ZTXxgd~8y%Vet%nWl$yBWAPEb64ljE!<( zFqr;ARI7zITKMo*E#9`I>fSws$Ym)VT$&dbh>{UXIPF)@+MU|_m8-m7Id%F9C{ zA_~ZeH*)^9p`oKgLqnOub-u4)R{Qhi4|Z9k3pVXzU)n*P(YHH!i@M_3wVA*bMvF;@CQ?m z88I0G}SBDPd0K1s^rDl%mc6L|$HA87BJ8%371X!$vL{p-?K3AXWEvGzM}~u6$*vECBqe#tJgEk*aKaUMka1@xt+iAJRJ?XJnv$^ z3g1xm>-?+Q1%gqfQn~HX!9Yn{eD;861^HhYpMM+1u>RtW6pQU^p&O7b9H&OoyM^1} OZUOK|h&Y~KOwL~jT{?0A literal 0 HcmV?d00001 diff --git a/core/src/components/tab-button/test/states/tab-button.e2e.ts-snapshots/tab-button-focus-md-ltr-dark-Mobile-Chrome-linux.png b/core/src/components/tab-button/test/states/tab-button.e2e.ts-snapshots/tab-button-focus-md-ltr-dark-Mobile-Chrome-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..e12f984796d8456670a880440b63edfb62d9215a GIT binary patch literal 1305 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 literal 0 HcmV?d00001 diff --git a/core/src/css/palettes/dark.scss b/core/src/css/palettes/dark.scss index 0c5ef08dd93..18179f2e2e8 100644 --- a/core/src/css/palettes/dark.scss +++ b/core/src/css/palettes/dark.scss @@ -126,6 +126,7 @@ $colors: ( --ion-text-color-step-900: #1a1a1a; --ion-text-color-step-950: #0d0d0d; --ion-item-background: #000000; + --ion-tab-bar-background-focused: #252525; --ion-card-background: #1c1c1d; } @@ -183,6 +184,7 @@ $colors: ( --ion-item-background: #1e1e1e; --ion-toolbar-background: #1f1f1f; --ion-tab-bar-background: #1f1f1f; + --ion-tab-bar-background-focused: #353535; --ion-card-background: #1e1e1e; } } diff --git a/core/src/css/palettes/high-contrast-dark.scss b/core/src/css/palettes/high-contrast-dark.scss index e0f3b8aeb57..c5533a12405 100644 --- a/core/src/css/palettes/high-contrast-dark.scss +++ b/core/src/css/palettes/high-contrast-dark.scss @@ -119,6 +119,7 @@ $lightest-text-color: $text-color; --ion-text-color-rgb: #{color-to-rgb-list($text-color)}; --ion-item-background: #000000; --ion-card-background: #1c1c1d; + --ion-tab-bar-background-focused: #252525; /// Only the item borders should increase in contrast /// Borders for elements like toolbars should remain the same @@ -185,6 +186,7 @@ $lightest-text-color: $text-color; --ion-item-background: #1e1e1e; --ion-toolbar-background: #1f1f1f; --ion-tab-bar-background: #1f1f1f; + --ion-tab-bar-background-focused: #353535; --ion-card-background: #1e1e1e; /// Only the item borders should increase in contrast From 308aef569d8c6ebc3ad2186bca6969da8e4b2a8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Louren=C3=A7o?= Date: Tue, 7 Apr 2026 08:36:37 +0000 Subject: [PATCH 2/2] fix(datetime): multiple month selected and flakiness display (#31053) Issue number: resolves internal --------- ## What is the current behavior? Currently, the Datepicker keeps all the months visited as selected instead of only the one that is really selected On iOS devices, the month picker sometimes doesn't appear since the `datepicker-ready` class is not added ## What is the new behavior? Only the selected month appears selected in the month picker On iOS devices, an additional validation is done to add the `datepicker-ready` class ## Does this introduce a breaking change? - [ ] Yes - [x] No ## Other information The [Basic](https://ionic-framework-7el9b9l3f-ionic1.vercel.app/src/components/datetime/test/basic/?ionic:theme=ionic) test should be used to test the multiple-month selection case --- core/src/components/datetime/datetime.tsx | 17 +++++++-- .../datetime/test/basic/datetime.e2e.ts | 37 +++++++++++++++++++ .../picker-column/picker-column.tsx | 6 +-- 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx index c591972fd8d..e73cd55e0b8 100644 --- a/core/src/components/datetime/datetime.tsx +++ b/core/src/components/datetime/datetime.tsx @@ -1086,6 +1086,9 @@ export class Datetime implements ComponentInterface { connectedCallback() { this.clearFocusVisible = startFocusVisible(this.el).destroy; + this.loadTimeout = setTimeout(() => { + this.ensureReadyIfVisible(); + }, 100); } disconnectedCallback() { @@ -1093,9 +1096,7 @@ export class Datetime implements ComponentInterface { this.clearFocusVisible(); this.clearFocusVisible = undefined; } - if (this.loadTimeout) { - clearTimeout(this.loadTimeout); - } + this.loadTimeoutCleanup(); } /** @@ -1146,6 +1147,13 @@ export class Datetime implements ComponentInterface { }); }; + private loadTimeoutCleanup = () => { + if (this.loadTimeout) { + clearTimeout(this.loadTimeout); + this.loadTimeout = undefined; + } + }; + componentDidLoad() { const { el, intersectionTrackerRef } = this; @@ -1193,7 +1201,10 @@ export class Datetime implements ComponentInterface { * we still initialize listeners and mark the component as ready. * * We schedule this after everything has had a chance to run. + * + * We also clean up the load timeout to ensure that we don't have multiple timeouts running. */ + this.loadTimeoutCleanup(); this.loadTimeout = setTimeout(() => { this.ensureReadyIfVisible(); }, 100); diff --git a/core/src/components/datetime/test/basic/datetime.e2e.ts b/core/src/components/datetime/test/basic/datetime.e2e.ts index 6104d0014cf..bbc6033374f 100644 --- a/core/src/components/datetime/test/basic/datetime.e2e.ts +++ b/core/src/components/datetime/test/basic/datetime.e2e.ts @@ -349,6 +349,43 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => { }); }); +/** + * This behavior does not differ across + * modes/directions. + */ + +configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => { + test.describe(title('datetime: month picker selection'), () => { + test('datetime: month picker selection', async ({ page }) => { + await page.setContent( + ` + + `, + config + ); + + await page.locator('.datetime-ready').waitFor(); + + const nextMonthButton = page.locator('ion-datetime .calendar-next-prev ion-button').nth(1); + const monthYearButton = page.locator('ion-datetime .calendar-month-year'); + + await expect(monthYearButton).toHaveText(/May 2022/); + + await nextMonthButton.click(); + await expect(monthYearButton).toHaveText(/June 2022/); + + await nextMonthButton.click(); + await expect(monthYearButton).toHaveText(/July 2022/); + + await monthYearButton.click(); + await page.waitForChanges(); + + const selectedMonthOptions = page.locator('.month-column ion-picker-column-option.option-active'); + await expect(selectedMonthOptions).toHaveCount(1); + }); + }); +}); + /** * This behavior does not differ across * modes/directions. diff --git a/core/src/components/picker-column/picker-column.tsx b/core/src/components/picker-column/picker-column.tsx index 905ba8cf81e..53d6ca90d17 100644 --- a/core/src/components/picker-column/picker-column.tsx +++ b/core/src/components/picker-column/picker-column.tsx @@ -1,7 +1,7 @@ import type { ComponentInterface, EventEmitter } from '@stencil/core'; import { Component, Element, Event, Host, Method, Prop, State, Watch, h } from '@stencil/core'; import { doc } from '@utils/browser'; -import { getElementRoot, raf } from '@utils/helpers'; +import { raf } from '@utils/helpers'; import { hapticSelectionChanged, hapticSelectionEnd, hapticSelectionStart } from '@utils/native/haptic'; import { isPlatform } from '@utils/platform'; import { createColorClasses } from '@utils/theme'; @@ -122,9 +122,7 @@ export class PickerColumn implements ComponentInterface { * Because this initial call to scrollActiveItemIntoView has to fire before * the scroll listener is set up, we need to manage the active class manually. */ - const oldActive = getElementRoot(el).querySelector( - `.${PICKER_ITEM_ACTIVE_CLASS}` - ); + const oldActive = el.querySelector(`.${PICKER_ITEM_ACTIVE_CLASS}`); if (oldActive) { this.setPickerItemActiveState(oldActive, false); }