diff --git a/core/src/components/popover/test/arrow/popover.e2e.ts-snapshots/popover-arrow-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/popover/test/arrow/popover.e2e.ts-snapshots/popover-arrow-ios-rtl-Mobile-Firefox-linux.png index 5351495d922..acedb197bf1 100644 Binary files a/core/src/components/popover/test/arrow/popover.e2e.ts-snapshots/popover-arrow-ios-rtl-Mobile-Firefox-linux.png and b/core/src/components/popover/test/arrow/popover.e2e.ts-snapshots/popover-arrow-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/popover/test/position/popover.e2e.ts-snapshots/popover-position-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/popover/test/position/popover.e2e.ts-snapshots/popover-position-ios-rtl-Mobile-Firefox-linux.png index 73decfbfb46..8abcb19e5ea 100644 Binary files a/core/src/components/popover/test/position/popover.e2e.ts-snapshots/popover-position-ios-rtl-Mobile-Firefox-linux.png and b/core/src/components/popover/test/position/popover.e2e.ts-snapshots/popover-position-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/themes/ionic.functions.string.scss b/core/src/themes/ionic.functions.string.scss index fc861fa36c8..098a2e9f73f 100644 --- a/core/src/themes/ionic.functions.string.scss +++ b/core/src/themes/ionic.functions.string.scss @@ -84,23 +84,7 @@ // Add Root Selector // -------------------------------------------------------------------------------- -// Adds a root selector using host-context based on the selector passed -// -// Examples -// -------------------------------------------------------------------------------- -// @include add-root-selector("[dir=rtl]", ":host") -// --> :host-context([dir=rtl]) -// -// @include add-root-selector("[dir=rtl]", ":host(.fixed)") -// --> :host-context([dir=rtl]):host(.fixed) -// --> :host-context([dir=rtl]).fixed -// -// @include add-root-selector("[dir=rtl]", ":host(.tab-layout-icon-hide) ::slotted(ion-badge)") -// --> :host-context([dir=rtl]).tab-layout-icon-hide ::slotted(ion-badge) -// -// @include add-root-selector("[dir=rtl]", ".shadow") -// --> [dir=rtl] .shadow -// --> :host-context([dir=rtl]) .shadow +// Adds a root selector using host based on the selector passed // -------------------------------------------------------------------------------- @function add-root-selector($root, $addHostSelector) { @@ -110,8 +94,13 @@ @each $selector in $selectors { // If the selector contains :host( it means it is targeting a class on the host - // element so we need to change how we target it + // element so we need to change how we target it: + // @include add-root-selector(":host(.fixed)", "[dir=rtl]") + // --> :host-context([dir=rtl]):host(.fixed) + // --> :host-context([dir=rtl]).fixed @if str-contains($selector, ":host(") { + // @include add-root-selector(":host(.fixed)", "[dir=rtl]") + // --> :host-context([dir=rtl]):host(.fixed) $shadow-element: str-replace($selector, ":host(", ":host-context(#{$addHostSelector}):host("); $list: append($list, $shadow-element, comma); @@ -122,26 +111,63 @@ @if str-contains($element, ":host(") { $scoped-element: $element; - @if str-contains($element, "))") { - $scoped-element: str-replace($scoped-element, "))", ")"); - } @else { - $scoped-element: str-replace($scoped-element, ")", ""); - } - $scoped-element: str-replace($scoped-element, ":host(", ":host-context(#{$addHostSelector})"); + // Replace the :host( and ) so all we have left is the class + // inside of it: + // :host(.fixed) -> .fixed + $scoped-element: str-replace($scoped-element, ")", ""); + $scoped-element: str-replace($scoped-element, ":host(", ""); + + // Add the class back inside of host with the rtl selector: + // .fixed -> :host-context([dir=rtl]).fixed + $scoped-element: str-replace($scoped-element, $scoped-element, ":host-context(#{$addHostSelector})#{$scoped-element}"); + // @include add-root-selector(":host(.fixed)", "[dir=rtl]") + // --> :host-context([dir=rtl]).fixed $new-element: append($new-element, $scoped-element, space); } @else { + // Add back any selectors that followed the host after transforming the + // first selector: + // :host(.fixed) ::slotted(ion-icon) + // --> :host-context([dir=rtl]):host(.fixed) ::slotted(ion-icon) + // --> :host-context([dir=rtl]).fixed ::slotted(ion-icon) $new-element: append($new-element, $element, space); } } $list: append($list, $new-element, comma); - // If the selector contains :host it means it is targeting just the host + // If the selector contains :host without a parantheses + // it means it is targeting just the host // element so we can change it to look for host-context + // @include add-root-selector(":host", "[dir=rtl]") + // --> :host-context([dir=rtl]) + // --> :host:dir(rtl) } @else if str-contains($selector, ":host") { - $list: append($list, ":host-context(#{$addHostSelector})", comma); + $new-element: (); + $elements: str-split($selector, " "); + + @each $element in $elements { + @if str-contains($element, ":host") { + // Replace the :host with the addHostSelector: + // :host -> :host-context([dir=rtl]) + $updated-element: str-replace($element, ":host", ":host-context(#{$addHostSelector})"); + + // Add the final selector after all transformations: + // :host -> :host-context([dir=rtl]) + $new-element: append($new-element, $updated-element, space); + } @else { + // Add back any selectors that followed the host after transforming the + // first selector: + // :host ::slotted(ion-icon) -> :host-context([dir=rtl]) ::slotted(ion-icon) + $new-element: append($new-element, $element, space); + } + } + + $list: append($list, $new-element, comma); // If the selector does not contain host at all it is either a shadow - // or normal element so append both the dir check and host-context + // or normal element so append both the addHostSelector and host-context + // @include add-root-selector("ion-component", "[dir=rtl]") + // --> :host-context([dir=rtl]) ion-component + // --> [dir=rtl] ion-component } @else { $list: append($list, "#{$addHostSelector} #{$selector}", comma); $list: append($list, ":host-context(#{$addHostSelector}) #{$selector}", comma); diff --git a/core/src/themes/ionic.mixins.scss b/core/src/themes/ionic.mixins.scss index 3a77c28db1d..053a4627c61 100644 --- a/core/src/themes/ionic.mixins.scss +++ b/core/src/themes/ionic.mixins.scss @@ -194,8 +194,66 @@ @mixin rtl() { $root: #{&}; - @at-root #{add-root-selector($root, "[dir=rtl]")} { - @content; + $rootSplit: str-split($root, ","); + $selectors: #{add-root-selector($root, "[dir=rtl]")}; + $selectorsSplit: str-split($selectors, ","); + + $hostContextSelectors: (); + $restSelectors: (); + $dirSelectors: (); + + // Selectors must be split into individual selectors in case the browser + // doesn't support a specific selector. + // For example, Firefox and Safari doesn't support `:host-context()`. + // If an invalid selector is used, then the entire group of selectors + // will be ignored. + // @link https://www.w3.org/TR/selectors-3/#grouping + @each $selector in $selectorsSplit { + // Group the selectors back into a single selector to optimize the output. + @if str-index($selector, ":host-context") { + $hostContextSelectors: append($hostContextSelectors, $selector, comma); + } @else { + // Group the selectors back into a single selector to optimize the output. + $restSelectors: append($restSelectors, $selector, comma); + } + } + + // Supported by Chrome. + @if length($hostContextSelectors) > 0 { + @at-root #{$hostContextSelectors} { + @content; + } + } + + // Supported by all browsers. + @if length($restSelectors) > 0 { + @at-root #{$restSelectors} { + @content; + } + } + + // If browser can support `:dir()`, then add the `:dir()` selectors. + @supports selector(:dir(rtl)) { + // Adding :dir() in case the browser doesn't support `:host-context()` and does support `:dir()`. + // `:host-context()` is added: + // - through the `add-root-selector()` function. + // - first so that it takes precedence over `:dir()`. + // For example, + // - Firefox doesn't support `:host-context()`, but does support `:dir()`. + // - Safari doesn't support `:host-context()`, but Safari 16.4+ supports `:dir()` + // @link https://webkit.org/blog/13966/webkit-features-in-safari-16-4/ + @each $selector in $rootSplit { + $dirSelector: "#{$selector}:dir(rtl)"; + // Group the selectors back into a single selector to optimize the output. + $dirSelectors: append($dirSelectors, $dirSelector, comma); + } + + // Supported by Firefox. + @if length($dirSelectors) > 0 { + @at-root #{$dirSelectors} { + @content; + } + } } }