fix(rtl): allow :host to use rtl() (#28353)
Issue number: N/A
---------
<!-- Please do not submit updates to dependencies unless it fixes an
issue. -->
<!-- Please try to limit your pull request to one type (bugfix, feature,
etc). Submit multiple pull requests if needed. -->
## What is the current behavior?
<!-- Please describe the current behavior that you are modifying. -->
While working on a safe area padding mixin, I realized that `rtl()`
wasn't being applied for `:host` when using Firefox or Safari. This is
happening because the syntax for `:dir()` is wrong. The placement needs
to be updated for Firefox and Safari to register it.
```scss
:host {
@include rtl() { // <- won't work
// styles
}
}
// generates
:host-context([dir=rtl]) {
// styles
}
:host:dir(rtl) { // <- wrong syntax
// styles
}
```
```scss
:host(.class) {
@include rtl() { // <- won't work
// styles
}
}
// generates
:host-context([dir=rtl]):host(.class) {
// styles
}
:host-context([dir=rtl]).class {
// styles
}
:host(.class):dir(rtl) { // <- wrong syntax
// styles
}
```
## What is the new behavior?
<!-- Please describe the behavior or changes that are being added by
this PR. -->
I updated `rtl()` to use `:dir()` as the `addHostSelector` in the
`add-root-selector` function. This generates all the correct selectors
for Firefox and Safari. However, `:dir()` does not have the structure of
`:host-context()` so I had to add a new parameter to `add-root-selector`
to determine whether to use `:host-context()` or not. I set the default
to `true` since the function originally used `:host-context()`.
An extra win is that the updated function will be ready for when
`:host-context()` can be removed from the codebase.
```diff
:host {
@include rtl() { // <- works
// styles
}
}
// generates
:host-context([dir=rtl]) {
// styles
}
- :host:dir(rtl) {
+ :host(:dir(rtl)) {
// styles
}
```
```diff
:host(.class) {
@include rtl() { // <- works
// styles
}
}
// generates
:host-context([dir=rtl]):host(.class) {
padding-right: 40px;
}
:host-context([dir=rtl]).class {
// styles
}
- :host(.class):dir(rtl) {
+ :host(.class:dir(rtl)) {
// styles
}
```
## Does this introduce a breaking change?
- [ ] Yes
- [x] No
<!-- If this introduces a breaking change, please describe the impact
and migration path for existing applications below. -->
## Other information
<!-- Any other information that is important to this PR such as
screenshots of how the component looks before and after the change. -->
N/A
---------
Co-authored-by: ionitron <hi@ionicframework.com>
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
@@ -85,9 +85,15 @@
|
||||
// Add Root Selector
|
||||
// --------------------------------------------------------------------------------
|
||||
// Adds a root selector using host based on the selector passed
|
||||
// $root: The selector that needs to be updated to include the $addHostSelector.
|
||||
// - Example: ion-button
|
||||
// $addHostSelector: The selector that is used to add the host to the $root selector.
|
||||
// - Example: [dir=rtl]
|
||||
// $useHostContext: Whether to use host-context or not. Defaults to true.
|
||||
// --------------------------------------------------------------------------------
|
||||
|
||||
@function add-root-selector($root, $addHostSelector) {
|
||||
|
||||
@function add-root-selector($root, $addHostSelector, $useHostContext: true) {
|
||||
$selectors: str-split($root, ",");
|
||||
|
||||
$list: ();
|
||||
@@ -95,14 +101,21 @@
|
||||
@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:
|
||||
// Example with `useHostContext=true`
|
||||
// @include add-root-selector(":host(.fixed)", "[dir=rtl]")
|
||||
// --> :host-context([dir=rtl]):host(.fixed)
|
||||
// --> :host-context([dir=rtl]).fixed
|
||||
// ---
|
||||
// Example with `useHostContext=false`
|
||||
// @include add-root(":host(.fixed)", ":dir(rtl)", false)
|
||||
// --> :host(.fixed:dir(rtl))
|
||||
@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);
|
||||
@if $useHostContext {
|
||||
// @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);
|
||||
}
|
||||
|
||||
$new-element: ();
|
||||
$elements: str-split($selector, " ");
|
||||
@@ -117,19 +130,28 @@
|
||||
$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}");
|
||||
|
||||
// Add the class back inside of host with the addHostSelector:
|
||||
@if $useHostContext {
|
||||
// .fixed -> :host-context([dir=rtl]).fixed
|
||||
$scoped-element: str-replace($scoped-element, $scoped-element, ":host-context(#{$addHostSelector})#{$scoped-element}");
|
||||
} @else {
|
||||
// .fixed -> :host(.fixed:dir(rtl))
|
||||
$scoped-element: str-replace($scoped-element, $scoped-element, ":host(#{$scoped-element}#{$addHostSelector})");
|
||||
}
|
||||
|
||||
// @include add-root-selector(":host(.fixed)", "[dir=rtl]")
|
||||
// --> :host-context([dir=rtl]).fixed
|
||||
// @include add-root(":host(.fixed)", ":dir(rtl)", false)
|
||||
// --> :host(.fixed:dir(rtl))
|
||||
$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)
|
||||
// Add back any selectors that followed the host
|
||||
// after transforming the first selector:
|
||||
// @include add-root-selector(":host(.fixed) ::slotted(ion-icon)", "[dir=rtl]")
|
||||
// --> :host-context([dir=rtl]):host(.fixed) ::slotted(ion-icon)
|
||||
// --> :host-context([dir=rtl]).fixed ::slotted(ion-icon)
|
||||
// @include add-root(":host(.fixed) ::slotted(ion-icon)", ":dir(rtl)", false)
|
||||
// --> :host(.fixed:dir(rtl)) ::slotted(ion-icon)
|
||||
$new-element: append($new-element, $element, space);
|
||||
}
|
||||
}
|
||||
@@ -140,24 +162,38 @@
|
||||
// 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)
|
||||
// @include add-root(":host", ":dir(rtl)", false)
|
||||
// --> :host(:dir(rtl))
|
||||
} @else if str-contains($selector, ":host") {
|
||||
$new-element: ();
|
||||
$elements: str-split($selector, " ");
|
||||
|
||||
@each $element in $elements {
|
||||
@if str-contains($element, ":host") {
|
||||
$updated-element: '';
|
||||
|
||||
// Replace the :host with the addHostSelector:
|
||||
// :host -> :host-context([dir=rtl])
|
||||
$updated-element: str-replace($element, ":host", ":host-context(#{$addHostSelector})");
|
||||
@if $useHostContext {
|
||||
// :host -> :host-context([dir=rtl])
|
||||
$updated-element: str-replace($element, ":host", ":host-context(#{$addHostSelector})");
|
||||
} @else {
|
||||
// :host -> :host(:dir(rtl))
|
||||
$updated-element: str-replace($element, ":host", ":host(#{$addHostSelector})");
|
||||
}
|
||||
|
||||
// Add the final selector after all transformations:
|
||||
// :host -> :host-context([dir=rtl])
|
||||
// @include add-root-selector(":host", "[dir=rtl]")
|
||||
// --> :host-context([dir=rtl])
|
||||
// @include add-root(":host", ":dir(rtl)", false)
|
||||
// --> :host(: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)
|
||||
// Add back any selectors that followed the host
|
||||
// after transforming the first selector:
|
||||
// @include add-root-selector(":host ::slotted(ion-icon)", "[dir=rtl]")
|
||||
// --> :host-context([dir=rtl]) ::slotted(ion-icon)
|
||||
// @include add-root(":host ::slotted(ion-icon)", ":dir(rtl)", false)
|
||||
// --> :host(:dir(rtl)) ::slotted(ion-icon)
|
||||
$new-element: append($new-element, $element, space);
|
||||
}
|
||||
}
|
||||
@@ -168,9 +204,15 @@
|
||||
// @include add-root-selector("ion-component", "[dir=rtl]")
|
||||
// --> :host-context([dir=rtl]) ion-component
|
||||
// --> [dir=rtl] ion-component
|
||||
// @include add-root("ion-component", ":dir(rtl)", false)
|
||||
// --> ion-component:dir(rtl)
|
||||
} @else {
|
||||
$list: append($list, "#{$addHostSelector} #{$selector}", comma);
|
||||
$list: append($list, ":host-context(#{$addHostSelector}) #{$selector}", comma);
|
||||
@if ($useHostContext) {
|
||||
$list: append($list, ":host-context(#{$addHostSelector}) #{$selector}", comma);
|
||||
$list: append($list, "#{$addHostSelector} #{$selector}", comma);
|
||||
} @else {
|
||||
$list: append($list, "#{$selector}#{$addHostSelector}", comma);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -242,17 +242,13 @@
|
||||
// - 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);
|
||||
}
|
||||
// -- However, there is a Webkit bug on v16 that prevents `:dir()` from working when
|
||||
// -- the app direction is changed dynamically. v17+ works fine.
|
||||
// -- @link https://bugs.webkit.org/show_bug.cgi?id=257133
|
||||
|
||||
// Supported by Firefox.
|
||||
@if length($dirSelectors) > 0 {
|
||||
@at-root #{$dirSelectors} {
|
||||
@content;
|
||||
}
|
||||
@at-root #{add-root-selector($root, ":dir(rtl)", false)} {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||