diff --git a/src/core/features/course/components/course-format/course-format.ts b/src/core/features/course/components/course-format/course-format.ts index d9e925eb9..3c432a9c1 100644 --- a/src/core/features/course/components/course-format/course-format.ts +++ b/src/core/features/course/components/course-format/course-format.ts @@ -51,7 +51,6 @@ import { CoreCourseCourseIndexTourComponent } from '../course-index-tour/course- import { CoreDom } from '@singletons/dom'; import { CoreUserTourDirectiveOptions } from '@directives/user-tour'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; -import { CorePlatform } from '@services/platform'; import { CoreBlockSideBlocksComponent } from '@features/block/components/side-blocks/side-blocks'; /** @@ -96,7 +95,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { id: 'course-index', component: CoreCourseCourseIndexTourComponent, side: CoreUserToursSide.Top, - alignment: CorePlatform.isRTL ? CoreUserToursAlignment.Start : CoreUserToursAlignment.End, + alignment: CoreUserToursAlignment.End, getFocusedElement: nativeButton => { const innerButton = Array.from(nativeButton.shadowRoot?.children ?? []).find(child => child.tagName === 'BUTTON'); diff --git a/src/theme/helpers/ionic.functions.string.scss b/src/theme/helpers/ionic.functions.string.scss index 34a01c114..6b411ef28 100644 --- a/src/theme/helpers/ionic.functions.string.scss +++ b/src/theme/helpers/ionic.functions.string.scss @@ -92,23 +92,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) { @@ -118,8 +102,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); @@ -130,27 +119,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") { - $shadow-element: str-replace($selector, ":host", ":host-context(#{$addHostSelector})"); - $list: append($list, $shadow-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 + $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 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/src/theme/helpers/ionic.mixins.scss b/src/theme/helpers/ionic.mixins.scss index f212ad5f7..d74f2011c 100644 --- a/src/theme/helpers/ionic.mixins.scss +++ b/src/theme/helpers/ionic.mixins.scss @@ -201,9 +201,67 @@ @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)) { // @ IMPOTANT NOTE: This has been removed because selector function is not supported in current SCSS + // 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; + } + } + //} } @mixin ltr() { @@ -249,26 +307,10 @@ #{$prop}-right: $end; } @else { - #{$prop}-left: $start; - #{$prop}-right: $end; - - @at-root { - @supports ((margin-inline-start: 0) or (-webkit-margin-start: 0)) { - & { - @if $start != null { - #{$prop}-left: unset; - } - @if $end != null { - #{$prop}-right: unset; - } - - -webkit-#{$prop}-start: $start; - #{$prop}-inline-start: $start; - -webkit-#{$prop}-end: $end; - #{$prop}-inline-end: $end; - } - } - } + -webkit-#{$prop}-start: $start; + #{$prop}-inline-start: $start; + -webkit-#{$prop}-end: $end; + #{$prop}-inline-end: $end; } } @@ -333,16 +375,32 @@ right: $end; } } @else { - @include ltr() { - left: $start; - right: $end; + @at-root { + @supports (inset-inline-start: 0) { + & { + inset-inline-start: $start; + inset-inline-end: $end; + } + } } - @include rtl() { - left: unset; - right: unset; - left: $end; - right: $start; + // TODO FW-3766 + @at-root { + @supports not (inset-inline-start: 0) { + & { + @include ltr() { + left: $start; + right: $end; + } + @include rtl() { + left: unset; + right: unset; + + left: $end; + right: $start; + } + } + } } } } diff --git a/src/theme/theme.base.scss b/src/theme/theme.base.scss index 88ba3383f..5ebfcbfd0 100644 --- a/src/theme/theme.base.scss +++ b/src/theme/theme.base.scss @@ -704,6 +704,8 @@ body.core-iframe-fullscreen ion-router-outlet { --ion-safe-area-right: 0px; .modal-wrapper { + @include margin-horizontal(var(--modal-lateral-margin), null); + position: absolute; @include position(0 !important, 0 !important, 0 !important, unset !important); display: block; @@ -722,14 +724,6 @@ body.core-iframe-fullscreen ion-router-outlet { } } -html:not([dir="rtl"]) { - .core-modal-lateral { - .modal-wrapper { - @include margin-horizontal(var(--modal-lateral-margin), null); - } - } -} - .core-modal-transparent-no-filter { @extend .core-modal-transparent; ion-backdrop { @@ -1340,6 +1334,25 @@ ion-fab[core-fab] { } } +// The following 4 selectors can probably be removed after Ionic migration to 7+ +ion-fab.fab-horizontal-start { + left: calc(10px + var(--ion-safe-area-right, 0px)); +} + +[dir=rtl] ion-fab.fab-horizontal-start { + right: calc(10px + var(--ion-safe-area-right, 0px)); + left: unset +} + +ion-fab.fab-horizontal-end { + right: calc(10px + var(--ion-safe-area-right, 0px)); +} + +[dir=rtl] ion-fab.fab-horizontal-end { + left: calc(10px + var(--ion-safe-area-right, 0px)); + right: unset +} + ion-content.has-collapsible-footer ion-fab { bottom: calc(var(--core-collapsible-footer-height, 0px) + 10px); @include core-transition(all, 200ms);