]> git.ipfire.org Git - thirdparty/bootstrap.git/commitdiff
feat(RTL): implement RTL
authorGaël Poupard <gael.poupard@orange.com>
Fri, 26 Jun 2020 14:06:20 +0000 (17:06 +0300)
committerXhmikosR <xhmikosr@gmail.com>
Fri, 4 Dec 2020 05:52:03 +0000 (07:52 +0200)
Using RTLCSS directives, renaming things to use logical names and following best practices.

37 files changed:
.bundlewatch.config.json
js/src/carousel.js
js/src/dropdown.js
js/src/modal.js
js/src/popover.js
js/src/tooltip.js
js/src/util/index.js
js/tests/unit/dropdown.spec.js
js/tests/unit/toast.spec.js
js/tests/unit/tooltip.spec.js
js/tests/visual/dropdown.html
js/tests/visual/popover.html
js/tests/visual/scrollspy.html
js/tests/visual/toast.html
js/tests/visual/tooltip.html
scss/_breadcrumb.scss
scss/_button-group.scss
scss/_card.scss
scss/_carousel.scss
scss/_dropdown.scss
scss/_list-group.scss
scss/_navbar.scss
scss/_pagination.scss
scss/_popover.scss
scss/_reboot.scss
scss/_spinners.scss
scss/_tooltip.scss
scss/_utilities.scss
scss/_variables.scss
scss/bootstrap-grid.scss
scss/forms/_form-check.scss
scss/forms/_input-group.scss
scss/mixins/_border-radius.scss
scss/mixins/_caret.scss
scss/mixins/_forms.scss
scss/mixins/_pagination.scss
scss/mixins/_utilities.scss

index 525d29f6a0d59afac5ae0a2c1da91277579e8015..8be4f8c7ab811f4bcb725bb277516ba450b466ca 100644 (file)
@@ -10,7 +10,7 @@
     },
     {
       "path": "./dist/css/bootstrap-reboot.css",
-      "maxSize": "2 kB"
+      "maxSize": "2.25 kB"
     },
     {
       "path": "./dist/css/bootstrap-reboot.min.css",
@@ -54,7 +54,7 @@
     },
     {
       "path": "./dist/js/bootstrap.min.js",
-      "maxSize": "15.5 kB"
+      "maxSize": "15.75 kB"
     }
   ],
   "ci": {
index 9c6fb53ee0428fa2d8a1e6d637c9b65c980d4c14..d8ad3a135415d3c8e1d45a6193d277a74dae0c70 100644 (file)
@@ -79,8 +79,8 @@ const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`
 const CLASS_NAME_CAROUSEL = 'carousel'
 const CLASS_NAME_ACTIVE = 'active'
 const CLASS_NAME_SLIDE = 'slide'
-const CLASS_NAME_RIGHT = 'carousel-item-right'
-const CLASS_NAME_LEFT = 'carousel-item-left'
+const CLASS_NAME_END = 'carousel-item-end'
+const CLASS_NAME_START = 'carousel-item-start'
 const CLASS_NAME_NEXT = 'carousel-item-next'
 const CLASS_NAME_PREV = 'carousel-item-prev'
 const CLASS_NAME_POINTER_EVENT = 'pointer-event'
@@ -442,11 +442,11 @@ class Carousel extends BaseComponent {
     let eventDirectionName
 
     if (direction === DIRECTION_NEXT) {
-      directionalClassName = CLASS_NAME_LEFT
+      directionalClassName = CLASS_NAME_START
       orderClassName = CLASS_NAME_NEXT
       eventDirectionName = DIRECTION_LEFT
     } else {
-      directionalClassName = CLASS_NAME_RIGHT
+      directionalClassName = CLASS_NAME_END
       orderClassName = CLASS_NAME_PREV
       eventDirectionName = DIRECTION_RIGHT
     }
index 7b3bf5b4ec3fc909b4a1318dbdfcf5fc579d61a2..0ac108ab8110b2287e437b2acffdf591100128fa 100644 (file)
@@ -11,6 +11,7 @@ import {
   getElementFromSelector,
   isElement,
   isVisible,
+  isRTL,
   noop,
   typeCheckConfig
 } from './util/index'
@@ -53,9 +54,9 @@ const EVENT_KEYUP_DATA_API = `keyup${EVENT_KEY}${DATA_API_KEY}`
 const CLASS_NAME_DISABLED = 'disabled'
 const CLASS_NAME_SHOW = 'show'
 const CLASS_NAME_DROPUP = 'dropup'
-const CLASS_NAME_DROPRIGHT = 'dropright'
-const CLASS_NAME_DROPLEFT = 'dropleft'
-const CLASS_NAME_MENURIGHT = 'dropdown-menu-right'
+const CLASS_NAME_DROPEND = 'dropend'
+const CLASS_NAME_DROPSTART = 'dropstart'
+const CLASS_NAME_MENUEND = 'dropdown-menu-end'
 const CLASS_NAME_NAVBAR = 'navbar'
 const CLASS_NAME_POSITION_STATIC = 'position-static'
 
@@ -65,12 +66,12 @@ const SELECTOR_MENU = '.dropdown-menu'
 const SELECTOR_NAVBAR_NAV = '.navbar-nav'
 const SELECTOR_VISIBLE_ITEMS = '.dropdown-menu .dropdown-item:not(.disabled):not(:disabled)'
 
-const PLACEMENT_TOP = 'top-start'
-const PLACEMENT_TOPEND = 'top-end'
-const PLACEMENT_BOTTOM = 'bottom-start'
-const PLACEMENT_BOTTOMEND = 'bottom-end'
-const PLACEMENT_RIGHT = 'right-start'
-const PLACEMENT_LEFT = 'left-start'
+const PLACEMENT_TOP = isRTL ? 'top-end' : 'top-start'
+const PLACEMENT_TOPEND = isRTL ? 'top-start' : 'top-end'
+const PLACEMENT_BOTTOM = isRTL ? 'bottom-end' : 'bottom-start'
+const PLACEMENT_BOTTOMEND = isRTL ? 'bottom-start' : 'bottom-end'
+const PLACEMENT_RIGHT = isRTL ? 'left-start' : 'right-start'
+const PLACEMENT_LEFT = isRTL ? 'right-start' : 'left-start'
 
 const Default = {
   offset: 0,
@@ -277,14 +278,14 @@ class Dropdown extends BaseComponent {
 
     // Handle dropup
     if (parentDropdown.classList.contains(CLASS_NAME_DROPUP)) {
-      placement = this._menu.classList.contains(CLASS_NAME_MENURIGHT) ?
+      placement = this._menu.classList.contains(CLASS_NAME_MENUEND) ?
         PLACEMENT_TOPEND :
         PLACEMENT_TOP
-    } else if (parentDropdown.classList.contains(CLASS_NAME_DROPRIGHT)) {
+    } else if (parentDropdown.classList.contains(CLASS_NAME_DROPEND)) {
       placement = PLACEMENT_RIGHT
-    } else if (parentDropdown.classList.contains(CLASS_NAME_DROPLEFT)) {
+    } else if (parentDropdown.classList.contains(CLASS_NAME_DROPSTART)) {
       placement = PLACEMENT_LEFT
-    } else if (this._menu.classList.contains(CLASS_NAME_MENURIGHT)) {
+    } else if (this._menu.classList.contains(CLASS_NAME_MENUEND)) {
       placement = PLACEMENT_BOTTOMEND
     }
 
index a9cf8ae6cff7cbd4cdf15dabd2189c4996ef7b54..94bf95f8ad8a185d67651d8b9fe1dc77eab03aa8 100644 (file)
@@ -13,6 +13,7 @@ import {
   getElementFromSelector,
   getTransitionDurationFromElement,
   isVisible,
+  isRTL,
   reflow,
   typeCheckConfig
 } from './util/index'
@@ -435,11 +436,11 @@ class Modal extends BaseComponent {
     const isModalOverflowing =
       this._element.scrollHeight > document.documentElement.clientHeight
 
-    if (!this._isBodyOverflowing && isModalOverflowing) {
+    if ((!this._isBodyOverflowing && isModalOverflowing && !isRTL) || (this._isBodyOverflowing && !isModalOverflowing && isRTL)) {
       this._element.style.paddingLeft = `${this._scrollbarWidth}px`
     }
 
-    if (this._isBodyOverflowing && !isModalOverflowing) {
+    if ((this._isBodyOverflowing && !isModalOverflowing && !isRTL) || (!this._isBodyOverflowing && isModalOverflowing && isRTL)) {
       this._element.style.paddingRight = `${this._scrollbarWidth}px`
     }
   }
index 66dcb47b90031d7c5237dd9fb9688b4884931a04..d8bd92eefb6436d4b10ebc8ea20860081177175d 100644 (file)
@@ -115,7 +115,7 @@ class Popover extends Tooltip {
   // Private
 
   _addAttachmentClass(attachment) {
-    this.getTipElement().classList.add(`${CLASS_PREFIX}-${attachment}`)
+    this.getTipElement().classList.add(`${CLASS_PREFIX}-${this.updateAttachment(attachment)}`)
   }
 
   _getContent() {
index 25599bb42f8f0a53191c46187ea5633ee8d0cc38..17148ed9a644d6cc51f8063b70cf66e941892132 100644 (file)
@@ -14,6 +14,7 @@ import {
   getTransitionDurationFromElement,
   getUID,
   isElement,
+  isRTL,
   noop,
   typeCheckConfig
 } from './util/index'
@@ -64,9 +65,9 @@ const DefaultType = {
 const AttachmentMap = {
   AUTO: 'auto',
   TOP: 'top',
-  RIGHT: 'right',
+  RIGHT: isRTL ? 'left' : 'right',
   BOTTOM: 'bottom',
-  LEFT: 'left'
+  LEFT: isRTL ? 'right' : 'left'
 }
 
 const Default = {
@@ -453,6 +454,18 @@ class Tooltip extends BaseComponent {
     return title
   }
 
+  updateAttachment(attachment) {
+    if (attachment === 'right') {
+      return 'end'
+    }
+
+    if (attachment === 'left') {
+      return 'start'
+    }
+
+    return attachment
+  }
+
   // Private
 
   _getPopperConfig(attachment) {
@@ -485,7 +498,7 @@ class Tooltip extends BaseComponent {
   }
 
   _addAttachmentClass(attachment) {
-    this.getTipElement().classList.add(`${CLASS_PREFIX}-${attachment}`)
+    this.getTipElement().classList.add(`${CLASS_PREFIX}-${this.updateAttachment(attachment)}`)
   }
 
   _getOffset() {
index 874827b16880c09c384d98bf33d61fbb8ea96d13..96cadc65bcbeac229eda2e5e2f58c446e8586957 100644 (file)
@@ -186,6 +186,8 @@ const onDOMContentLoaded = callback => {
   }
 }
 
+const isRTL = document.documentElement.dir === 'rtl'
+
 export {
   TRANSITION_END,
   getUID,
@@ -201,5 +203,6 @@ export {
   noop,
   reflow,
   getjQuery,
-  onDOMContentLoaded
+  onDOMContentLoaded,
+  isRTL
 }
index 145763d20f9943b97f378913bd76b3905e3e35aa..f6a5feb1b93e2320ea6e9b2edf1fb2d7256a336b 100644 (file)
@@ -227,7 +227,7 @@ describe('Dropdown', () => {
       fixtureEl.innerHTML = [
         '<div class="dropdown">',
         '  <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
-        '  <div class="dropdown-menu dropdown-menu-right">',
+        '  <div class="dropdown-menu dropdown-menu-end">',
         '    <a class="dropdown-item" href="#">Secondary link</a>',
         '  </div>',
         '</div>'
@@ -273,7 +273,7 @@ describe('Dropdown', () => {
       fixtureEl.innerHTML = [
         '<div class="dropup">',
         '  <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
-        '  <div class="dropdown-menu dropdown-menu-right">',
+        '  <div class="dropdown-menu dropdown-menu-end">',
         '    <a class="dropdown-item" href="#">Secondary link</a>',
         '  </div>',
         '</div>'
@@ -292,9 +292,9 @@ describe('Dropdown', () => {
       dropdown.toggle()
     })
 
-    it('should toggle a dropright', done => {
+    it('should toggle a dropend', done => {
       fixtureEl.innerHTML = [
-        '<div class="dropright">',
+        '<div class="dropend">',
         '  <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
         '  <div class="dropdown-menu">',
         '    <a class="dropdown-item" href="#">Secondary link</a>',
@@ -303,10 +303,10 @@ describe('Dropdown', () => {
       ].join('')
 
       const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const droprightEl = fixtureEl.querySelector('.dropright')
+      const dropendEl = fixtureEl.querySelector('.dropend')
       const dropdown = new Dropdown(btnDropdown)
 
-      droprightEl.addEventListener('shown.bs.dropdown', () => {
+      dropendEl.addEventListener('shown.bs.dropdown', () => {
         expect(btnDropdown.classList.contains('show')).toEqual(true)
         expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
         done()
@@ -315,9 +315,9 @@ describe('Dropdown', () => {
       dropdown.toggle()
     })
 
-    it('should toggle a dropleft', done => {
+    it('should toggle a dropstart', done => {
       fixtureEl.innerHTML = [
-        '<div class="dropleft">',
+        '<div class="dropstart">',
         '  <button class="btn dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropdown</button>',
         '  <div class="dropdown-menu">',
         '    <a class="dropdown-item" href="#">Secondary link</a>',
@@ -326,10 +326,10 @@ describe('Dropdown', () => {
       ].join('')
 
       const btnDropdown = fixtureEl.querySelector('[data-bs-toggle="dropdown"]')
-      const dropleftEl = fixtureEl.querySelector('.dropleft')
+      const dropstartEl = fixtureEl.querySelector('.dropstart')
       const dropdown = new Dropdown(btnDropdown)
 
-      dropleftEl.addEventListener('shown.bs.dropdown', () => {
+      dropstartEl.addEventListener('shown.bs.dropdown', () => {
         expect(btnDropdown.classList.contains('show')).toEqual(true)
         expect(btnDropdown.getAttribute('aria-expanded')).toEqual('true')
         done()
index a4ab4f76cd3ca96813006cd4f558602c288b5793..cc873d1fe68eb0f7918141dcf8f5258104d843c5 100644 (file)
@@ -46,7 +46,7 @@ describe('Toast', () => {
     it('should close toast when close element with data-bs-dismiss attribute is set', done => {
       fixtureEl.innerHTML = [
         '<div class="toast" data-bs-delay="1" data-bs-autohide="false" data-bs-animation="false">',
-        '  <button type="button" class="ml-2 mb-1 btn-close" data-bs-dismiss="toast" aria-label="Close"></button>',
+        '  <button type="button" class="ms-2 mb-1 btn-close" data-bs-dismiss="toast" aria-label="Close"></button>',
         '</div>'
       ].join('')
 
@@ -78,7 +78,7 @@ describe('Toast', () => {
 
       fixtureEl.innerHTML = [
         '<div class="toast" data-bs-autohide="false" data-bs-animation="false">',
-        '  <button type="button" class="ml-2 mb-1 btn-close" data-bs-dismiss="toast" aria-label="Close"></button>',
+        '  <button type="button" class="ms-2 mb-1 btn-close" data-bs-dismiss="toast" aria-label="Close"></button>',
         '</div>'
       ].join('')
 
index b119807511b87a00bab2952d529739327e3410c0..9ea9096de984affd3f77588799c6dd62f8598c72 100644 (file)
@@ -886,6 +886,40 @@ describe('Tooltip', () => {
     })
   })
 
+  describe('updateAttachment', () => {
+    it('should use end class name when right placement specified', done => {
+      fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+
+      const tooltipEl = fixtureEl.querySelector('a')
+      const tooltip = new Tooltip(tooltipEl, {
+        placement: 'right'
+      })
+
+      tooltipEl.addEventListener('inserted.bs.tooltip', () => {
+        expect(tooltip.getTipElement().classList.contains('bs-tooltip-end')).toEqual(true)
+        done()
+      })
+
+      tooltip.show()
+    })
+
+    it('should use start class name when left placement specified', done => {
+      fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
+
+      const tooltipEl = fixtureEl.querySelector('a')
+      const tooltip = new Tooltip(tooltipEl, {
+        placement: 'left'
+      })
+
+      tooltipEl.addEventListener('inserted.bs.tooltip', () => {
+        expect(tooltip.getTipElement().classList.contains('bs-tooltip-start')).toEqual(true)
+        done()
+      })
+
+      tooltip.show()
+    })
+  })
+
   describe('setElementContent', () => {
     it('should do nothing if the element is null', () => {
       fixtureEl.innerHTML = '<a href="#" rel="tooltip" title="Another tooltip">'
index 9dad43543e30ade4710008d0b1ae9bb0f9b0c86a..930940a157ee10d5afedcb87c7148155842721d6 100644 (file)
@@ -90,9 +90,9 @@
 
           <div class="btn-group">
             <button type="button" class="btn btn-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
-              This dropdown's menu is right-aligned
+              This dropdown's menu is end-aligned
             </button>
-            <div class="dropdown-menu dropdown-menu-right">
+            <div class="dropdown-menu dropdown-menu-end">
               <button class="dropdown-item" type="button">Action</button>
               <button class="dropdown-item" type="button">Another action</button>
               <button class="dropdown-item" type="button">Something else here</button>
 
         <div class="col-sm-12 mt-4">
           <div class="btn-group dropup" role="group">
-            <a href="#" class="btn btn-secondary">Dropup split align right</a>
+            <a href="#" class="btn btn-secondary">Dropup split align end</a>
             <button type="button" id="dropdown-page-subheader-button-3" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
               <span class="visually-hidden">Product actions</span>
             </button>
-            <div class="dropdown-menu dropdown-menu-right">
+            <div class="dropdown-menu dropdown-menu-end">
               <button class="dropdown-item" type="button">Action</button>
               <button class="dropdown-item" type="button">Another action</button>
               <button class="dropdown-item" type="button">Something else here with a long text</button>
             </div>
           </div>
           <div class="btn-group dropup">
-            <button type="button" class="btn btn-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropup align right</button>
-            <div class="dropdown-menu dropdown-menu-right">
+            <button type="button" class="btn btn-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">Dropup align end</button>
+            <div class="dropdown-menu dropdown-menu-end">
               <button class="dropdown-item" type="button">Action</button>
               <button class="dropdown-item" type="button">Another action</button>
               <button class="dropdown-item" type="button">Something else here with a long text</button>
         </div>
 
         <div class="col-sm-12 mt-4">
-          <div class="btn-group dropright" role="group">
-            <a href="#" class="btn btn-secondary">Dropright split</a>
+          <div class="btn-group dropend" role="group">
+            <a href="#" class="btn btn-secondary">Dropend split</a>
             <button type="button" id="dropdown-page-subheader-button-4" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
               <span class="visually-hidden">Product actions</span>
             </button>
               <button class="dropdown-item" type="button">Something else here with a long text</button>
             </div>
           </div>
-          <div class="btn-group dropright">
+          <div class="btn-group dropend">
             <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuRight" data-bs-toggle="dropdown" aria-expanded="false">
-              Dropright
+              Dropend
             </button>
             <div class="dropdown-menu" aria-labelledby="dropdownMenuRight">
               <button class="dropdown-item" type="button">Action</button>
               <button class="dropdown-item" type="button">Something else here</button>
             </div>
           </div>
-          <!-- dropleft -->
-          <div class="btn-group dropleft" role="group">
-            <a href="#" class="btn btn-secondary">Dropleft split</a>
+          <!-- dropstart -->
+          <div class="btn-group dropstart" role="group">
+            <a href="#" class="btn btn-secondary">Dropstart split</a>
             <button type="button" id="dropdown-page-subheader-button-5" class="btn btn-secondary dropdown-toggle dropdown-toggle-split" data-bs-toggle="dropdown" aria-expanded="false">
               <span class="visually-hidden">Product actions</span>
             </button>
               <button class="dropdown-item" type="button">Something else here with a long text</button>
             </div>
           </div>
-          <div class="btn-group dropleft">
-            <button class="btn btn-secondary dropdown-toggle" type="button" id="dropleftMenu" data-bs-toggle="dropdown" aria-expanded="false">
-              Dropleft
+          <div class="btn-group dropstart">
+            <button class="btn btn-secondary dropdown-toggle" type="button" id="dropstartMenu" data-bs-toggle="dropdown" aria-expanded="false">
+              Dropstart
             </button>
-            <div class="dropdown-menu" aria-labelledby="dropleftMenu">
+            <div class="dropdown-menu" aria-labelledby="dropstartMenu">
               <button class="dropdown-item" type="button">Action</button>
               <button class="dropdown-item" type="button">Another action</button>
               <button class="dropdown-item" type="button">Something else here</button>
index e33de5f0bdedbed40adb8b946c3077a8c0cf6799..c75825396949450f30d28b6acad4abd414ec7768 100644 (file)
@@ -19,7 +19,7 @@
       </button>
 
       <button type="button" class="btn btn-secondary" data-bs-container="body" data-bs-toggle="popover" data-bs-placement="right" data-bs-content="Vivamus sagittis lacus vel augue laoreet rutrum faucibus.">
-        Popover on right
+        Popover on end
       </button>
 
       <button type="button" class="btn btn-secondary" data-bs-container="body" data-bs-toggle="popover" data-bs-placement="bottom" data-bs-content="Vivamus sagittis lacus vel augue laoreet rutrum faucibus.">
@@ -27,7 +27,7 @@
       </button>
 
       <button type="button" class="btn btn-secondary" data-bs-container="body" data-bs-toggle="popover" data-bs-placement="left" data-bs-content="Vivamus sagittis lacus vel augue laoreet rutrum faucibus.">
-        Popover on left
+        Popover on start
       </button>
     </div>
 
index c86927d991f93fc56b28ecb4ee67521ced91e788..7b07932ebd71c70454f01c7448ba6ad126043f8b 100644 (file)
@@ -16,7 +16,7 @@
         <span class="navbar-toggler-icon"></span>
       </button>
       <div class="navbar-collapse collapse" id="navbarSupportedContent">
-        <ul class="navbar-nav mr-auto">
+        <ul class="navbar-nav me-auto">
           <li class="nav-item">
             <a class="nav-link" href="#fat">@fat</a>
           </li>
index c26278d94818dfe1310bf6742673a55e92759d1d..4765026f33d37c27fead21de656c8b59a32574ec 100644 (file)
@@ -28,8 +28,8 @@
     <div class="notifications">
       <div id="toastAutoHide" class="toast" role="alert" aria-live="assertive" aria-atomic="true" data-bs-delay="2000">
         <div class="toast-header">
-          <span class="d-block bg-primary rounded mr-2" style="width: 20px; height: 20px;"></span>
-          <strong class="mr-auto">Bootstrap</strong>
+          <span class="d-block bg-primary rounded me-2" style="width: 20px; height: 20px;"></span>
+          <strong class="me-auto">Bootstrap</strong>
           <small>11 mins ago</small>
         </div>
         <div class="toast-body">
 
       <div class="toast" data-bs-autohide="false" role="alert" aria-live="assertive" aria-atomic="true">
         <div class="toast-header">
-          <span class="d-block bg-primary rounded mr-2" style="width: 20px; height: 20px;"></span>
-          <strong class="mr-auto">Bootstrap</strong>
+          <span class="d-block bg-primary rounded me-2" style="width: 20px; height: 20px;"></span>
+          <strong class="me-auto">Bootstrap</strong>
           <small class="text-muted">2 seconds ago</small>
-          <button type="button" class="ml-2 mb-1 btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
+          <button type="button" class="ms-2 mb-1 btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
         </div>
         <div class="toast-body">
           Heads up, toasts will stack automatically
index 93536a04436717eaa6016f9930e3cd7392c73ec6..bade26a6c09b9b219a3ec544717b419a0806c0ed 100644 (file)
@@ -10,7 +10,6 @@
         border: 1px solid;
         width: 100px;
         height: 50px;
-        border: 1px solid;
         margin-left: 50px;
         transform: rotate(270deg);
         margin-top: 100px;
             Tooltip on top
           </button>
           <button type="button" class="btn btn-secondary" data-bs-toggle="tooltip" data-bs-placement="right" title="Tooltip on right">
-            Tooltip on right
+            Tooltip on end
           </button>
           <button type="button" class="btn btn-secondary" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Tooltip on bottom">
             Tooltip on bottom
           </button>
           <button type="button" class="btn btn-secondary" data-bs-toggle="tooltip" data-bs-placement="left" title="Tooltip on left">
-            Tooltip on left
+            Tooltip on start
           </button>
         </p>
       </div>
index f1ab3e8b9d108fb0ac20c9665a9bb2786ceaf611..f7fafe743ac2193eea3221e13776c319892fd03a 100644 (file)
@@ -18,7 +18,7 @@
       float: left; // Suppress inline spacings and underlining of the separator
       padding-right: $breadcrumb-item-padding-x;
       color: $breadcrumb-divider-color;
-      content: var(--#{$variable-prefix}breadcrumb-divider, escape-svg($breadcrumb-divider));
+      content: var(--#{$variable-prefix}breadcrumb-divider, escape-svg($breadcrumb-divider)) #{"/* rtl:"} var(--#{$variable-prefix}breadcrumb-divider, escape-svg($breadcrumb-divider-flipped)) #{"*/"};
     }
   }
 
index abb363a840378004b42dbd472e7827967302b1a8..13aa0569a50710789a992d2e4cb3591587501b1b 100644 (file)
@@ -43,7 +43,7 @@
   // Reset rounded corners
   > .btn:not(:last-child):not(.dropdown-toggle),
   > .btn-group:not(:last-child) > .btn {
-    @include border-right-radius(0);
+    @include border-end-radius(0);
   }
 
   // The left radius should be 0 if the button is:
@@ -53,7 +53,7 @@
   > .btn:nth-child(n + 3),
   > :not(.btn-check) + .btn,
   > .btn-group:not(:first-child) > .btn {
-    @include border-left-radius(0);
+    @include border-start-radius(0);
   }
 }
 
 
   &::after,
   .dropup &::after,
-  .dropright &::after {
+  .dropend &::after {
     margin-left: 0;
   }
 
-  .dropleft &::before {
+  .dropstart &::before {
     margin-right: 0;
   }
 }
index 9b0f4969a65bf2b805ff6e216ea0216f1376c614..4b2eebf135cd90eb43edec4a032d4b9638cc70ce 100644 (file)
@@ -69,7 +69,7 @@
   }
 
   + .card-link {
-    margin-left: $card-spacer-x;
+    margin-left: $card-spacer-x #{"/* rtl:ignore */"};
   }
 }
 
       // Handle rounded corners
       @if $enable-rounded {
         &:not(:last-child) {
-          @include border-right-radius(0);
+          @include border-end-radius(0);
 
           .card-img-top,
           .card-header {
         }
 
         &:not(:first-child) {
-          @include border-left-radius(0);
+          @include border-start-radius(0);
 
           .card-img-top,
           .card-header {
index ef77cbb618d0e1d9c5d1a20976764cd7cba51480..d2e42bc10bd8fa4699e268ebf8641b9cd5a07e1f 100644 (file)
@@ -3,12 +3,12 @@
 // 1. .carousel.pointer-event should ideally be pan-y (to allow for users to scroll vertically)
 //    even when their scroll action started on a carousel, but for compatibility (with Firefox)
 //    we're preventing all actions instead
-// 2. The .carousel-item-left and .carousel-item-right is used to indicate where
+// 2. The .carousel-item-start and .carousel-item-end is used to indicate where
 //    the active slide is heading.
 // 3. .active.carousel-item is the current slide.
-// 4. .active.carousel-item-left and .active.carousel-item-right is the current
+// 4. .active.carousel-item-start and .active.carousel-item-end is the current
 //    slide in its in-transition state. Only one of these occurs at a time.
-// 5. .carousel-item-next.carousel-item-left and .carousel-item-prev.carousel-item-right
+// 5. .carousel-item-next.carousel-item-start and .carousel-item-prev.carousel-item-end
 //    is the upcoming slide in transition.
 
 .carousel {
   display: block;
 }
 
-.carousel-item-next:not(.carousel-item-left),
-.active.carousel-item-right {
+/* rtl:begin:ignore */
+.carousel-item-next:not(.carousel-item-start),
+.active.carousel-item-end {
   transform: translateX(100%);
 }
 
-.carousel-item-prev:not(.carousel-item-right),
-.active.carousel-item-left {
+.carousel-item-prev:not(.carousel-item-end),
+.active.carousel-item-start {
   transform: translateX(-100%);
 }
 
+/* rtl:end:ignore */
+
 
 //
 // Alternate transitions
   }
 
   .carousel-item.active,
-  .carousel-item-next.carousel-item-left,
-  .carousel-item-prev.carousel-item-right {
+  .carousel-item-next.carousel-item-start,
+  .carousel-item-prev.carousel-item-end {
     z-index: 1;
     opacity: 1;
   }
 
-  .active.carousel-item-left,
-  .active.carousel-item-right {
+  .active.carousel-item-start,
+  .active.carousel-item-end {
     z-index: 0;
     opacity: 0;
     @include transition(opacity 0s $carousel-transition-duration);
   background-position: 50%;
   background-size: 100% 100%;
 }
+
+/* rtl:options: {
+  "autoRename": true,
+  "stringMap":[ {
+    "name"    : "prev-next",
+    "search"  : "prev",
+    "replace" : "next"
+  } ]
+} */
 .carousel-control-prev-icon {
   background-image: escape-svg($carousel-control-prev-icon-bg);
 }
   background-image: escape-svg($carousel-control-next-icon-bg);
 }
 
-
 // Optional indicator pips
 //
 // Add an ordered list with the following class and add a list item for each
index bff51db6c62439fa762a0cbf78e3f3fbce812dc4..7cad640096e7c10ba764cf60f825d8ede1a88f01 100644 (file)
@@ -1,8 +1,8 @@
 // The dropdown wrapper (`<div>`)
 .dropup,
-.dropright,
+.dropend,
 .dropdown,
-.dropleft {
+.dropstart {
   position: relative;
 }
 
   @include media-breakpoint-up($breakpoint) {
     $infix: breakpoint-infix($breakpoint, $grid-breakpoints);
 
-    .dropdown-menu#{$infix}-left {
-      right: auto;
-      left: 0;
+    .dropdown-menu#{$infix}-start {
+      right: auto #{"/* rtl:ignore */"};
+      left: 0 #{"/* rtl:ignore */"};
     }
 
-    .dropdown-menu#{$infix}-right {
-      right: 0;
-      left: auto;
+    .dropdown-menu#{$infix}-end {
+      right: 0 #{"/* rtl:ignore */"};
+      left: auto #{"/* rtl:ignore */"};
     }
   }
 }
@@ -67,7 +67,7 @@
   }
 }
 
-.dropright {
+.dropend {
   .dropdown-menu {
     top: 0;
     right: auto;
   }
 
   .dropdown-toggle {
-    @include caret(right);
+    @include caret(end);
     &::after {
       vertical-align: 0;
     }
   }
 }
 
-.dropleft {
+.dropstart {
   .dropdown-menu {
     top: 0;
     right: 100%;
@@ -94,7 +94,7 @@
   }
 
   .dropdown-toggle {
-    @include caret(left);
+    @include caret(start);
     &::before {
       vertical-align: 0;
     }
   &[x-placement^="left"] {
     right: auto;
     bottom: auto;
+    left: auto;
   }
 }
 
+
 // Dividers (basically an `<hr>`) within the dropdown
 .dropdown-divider {
   height: 0;
index 3d82f8f4395821626020de77b692291a56874ba2..a95adc181c9520b2aa562ac820165e797a2d9b2a 100644 (file)
 
       > .list-group-item {
         &:first-child {
-          @include border-bottom-left-radius($list-group-border-radius);
-          @include border-top-right-radius(0);
+          @include border-bottom-start-radius($list-group-border-radius);
+          @include border-top-end-radius(0);
         }
 
         &:last-child {
-          @include border-top-right-radius($list-group-border-radius);
-          @include border-bottom-left-radius(0);
+          @include border-top-end-radius($list-group-border-radius);
+          @include border-bottom-start-radius(0);
         }
 
         &.active {
index 607c3170c6a67d3daf814c62002aa9e4643b2189..852328aca1552d9396991a8e1fc4106321d475c6 100644 (file)
@@ -56,7 +56,7 @@
 .navbar-brand {
   padding-top: $navbar-brand-padding-y;
   padding-bottom: $navbar-brand-padding-y;
-  margin-right: $navbar-brand-margin-right;
+  margin-right: $navbar-brand-margin-end;
   @include font-size($navbar-brand-font-size);
   text-decoration: if($link-decoration == none, null, none);
   white-space: nowrap;
index 819f6d1eb0411827955067fe55c7e591b5abd14c..04bf82536f55850cc1359f1340b842f4d5edebc4 100644 (file)
@@ -31,7 +31,7 @@
 
 .page-item {
   &:not(:first-child) .page-link {
-    margin-left: $pagination-margin-left;
+    margin-left: $pagination-margin-start;
   }
 
   &.active .page-link {
index 55bfe1f9ab41cb74f953b700ad544b7ebff253e9..6688f729c270da36b7ace667f5e19d420149d476 100644 (file)
@@ -1,7 +1,7 @@
 .popover {
   position: absolute;
   top: 0;
-  left: 0;
+  left: 0 #{"/* rtl:ignore */"};
   z-index: $zindex-popover;
   display: block;
   max-width: $popover-max-width;
   }
 }
 
-.bs-popover-right {
-  margin-left: $popover-arrow-height;
+.bs-popover-end {
+  margin-left: $popover-arrow-height #{"/* rtl:ignore */"};
 
   > .popover-arrow {
-    left: subtract(-$popover-arrow-height, $popover-border-width);
+    left: subtract(-$popover-arrow-height, $popover-border-width) #{"/* rtl:ignore */"};
     width: $popover-arrow-height;
     height: $popover-arrow-width;
     margin: $popover-border-radius 0; // make sure the arrow does not touch the popover's rounded corners
 
     &::before {
-      left: 0;
-      border-width: ($popover-arrow-width / 2) $popover-arrow-height ($popover-arrow-width / 2) 0;
-      border-right-color: $popover-arrow-outer-color;
+      left: 0 #{"/* rtl:ignore */"};
+      border-width: ($popover-arrow-width / 2) $popover-arrow-height ($popover-arrow-width / 2) 0 #{"/* rtl:ignore */"};
+      border-right-color: $popover-arrow-outer-color #{"/* rtl:ignore */"};
     }
 
     &::after {
-      left: $popover-border-width;
-      border-width: ($popover-arrow-width / 2) $popover-arrow-height ($popover-arrow-width / 2) 0;
-      border-right-color: $popover-arrow-color;
+      left: $popover-border-width #{"/* rtl:ignore */"};
+      border-width: ($popover-arrow-width / 2) $popover-arrow-height ($popover-arrow-width / 2) 0 #{"/* rtl:ignore */"};
+      border-right-color: $popover-arrow-color #{"/* rtl:ignore */"};
     }
   }
 }
   }
 }
 
-.bs-popover-left {
-  margin-right: $popover-arrow-height;
+.bs-popover-start {
+  margin-right: $popover-arrow-height #{"/* rtl:ignore */"};
 
   > .popover-arrow {
-    right: subtract(-$popover-arrow-height, $popover-border-width);
+    right: subtract(-$popover-arrow-height, $popover-border-width) #{"/* rtl:ignore */"};
     width: $popover-arrow-height;
     height: $popover-arrow-width;
     margin: $popover-border-radius 0; // make sure the arrow does not touch the popover's rounded corners
 
     &::before {
-      right: 0;
-      border-width: ($popover-arrow-width / 2) 0 ($popover-arrow-width / 2) $popover-arrow-height;
-      border-left-color: $popover-arrow-outer-color;
+      right: 0 #{"/* rtl:ignore */"};
+      border-width: ($popover-arrow-width / 2) 0 ($popover-arrow-width / 2) $popover-arrow-height #{"/* rtl:ignore */"};
+      border-left-color: $popover-arrow-outer-color #{"/* rtl:ignore */"};
     }
 
     &::after {
-      right: $popover-border-width;
-      border-width: ($popover-arrow-width / 2) 0 ($popover-arrow-width / 2) $popover-arrow-height;
-      border-left-color: $popover-arrow-color;
+      right: $popover-border-width #{"/* rtl:ignore */"};
+      border-width: ($popover-arrow-width / 2) 0 ($popover-arrow-width / 2) $popover-arrow-height #{"/* rtl:ignore */"};
+      border-left-color: $popover-arrow-color #{"/* rtl:ignore */"};
     }
   }
 }
     @extend .bs-popover-top;
   }
   &[x-placement^="right"] {
-    @extend .bs-popover-right;
+    @extend .bs-popover-end;
   }
   &[x-placement^="bottom"] {
     @extend .bs-popover-bottom;
   }
   &[x-placement^="left"] {
-    @extend .bs-popover-left;
+    @extend .bs-popover-start;
   }
 }
 
-
 // Offset the popover to account for the popover arrow
 .popover-header {
   padding: $popover-header-padding-y $popover-header-padding-x;
index 61e9c4af666e559e1c5ed7bbe7e7318b8417ace7..50ba7431b99137a3b0bf1f146063de6468847dab 100644 (file)
@@ -290,6 +290,8 @@ kbd,
 samp {
   font-family: $font-family-code;
   @include font-size(1em); // Correct the odd `em` font sizing in all browsers.
+  direction: ltr #{"/* rtl:ignore */"};
+  unicode-bidi: bidi-override;
 }
 
 // 1. Remove browser default top margin
@@ -551,6 +553,20 @@ legend {
   -webkit-appearance: textfield; // 2
 }
 
+// 1. A few input types should stay LTR
+// See https://rtlstyling.com/posts/rtl-styling#form-inputs
+// 2. RTL only output
+// See https://rtlcss.com/learn/usage-guide/control-directives/#raw
+
+/* rtl:raw:
+[type="tel"],
+[type="url"],
+[type="email"],
+[type="number"] {
+  direction: ltr;
+}
+*/
+
 // Remove the inner padding in Chrome and Safari on macOS.
 
 ::-webkit-search-decoration {
index e932aacd9a46a291bf7bced0c920c8326f821172..7444ed1d12fa89459e9b4ba2aa4ff455829c1017 100644 (file)
@@ -3,7 +3,7 @@
 //
 
 @keyframes spinner-border {
-  to { transform: rotate(360deg); }
+  to { transform: rotate(360deg) #{"/* rtl:ignore */"}; }
 }
 
 .spinner-border {
index 4d405e0319bd2c84bd970ec7b641f88525701a56..a98ff4db5f26ab0fa51d8e7f815c6c3e8b6cacee 100644 (file)
   }
 }
 
-.bs-tooltip-right {
+.bs-tooltip-end {
   padding: 0 $tooltip-arrow-height;
 
   .tooltip-arrow {
-    left: 0;
+    left: 0 #{"/* rtl:ignore */"};
     width: $tooltip-arrow-height;
     height: $tooltip-arrow-width;
 
     &::before {
-      right: 0;
-      border-width: ($tooltip-arrow-width / 2) $tooltip-arrow-height ($tooltip-arrow-width / 2) 0;
-      border-right-color: $tooltip-arrow-color;
+      right: 0 #{"/* rtl:ignore */"};
+      border-width: ($tooltip-arrow-width / 2) $tooltip-arrow-height ($tooltip-arrow-width / 2) 0 #{"/* rtl:ignore */"};
+      border-right-color: $tooltip-arrow-color #{"/* rtl:ignore */"};
     }
   }
 }
   }
 }
 
-.bs-tooltip-left {
+.bs-tooltip-start {
   padding: 0 $tooltip-arrow-height;
 
   .tooltip-arrow {
-    right: 0;
+    right: 0 #{"/* rtl:ignore */"};
     width: $tooltip-arrow-height;
     height: $tooltip-arrow-width;
 
     &::before {
-      left: 0;
-      border-width: ($tooltip-arrow-width / 2) 0 ($tooltip-arrow-width / 2) $tooltip-arrow-height;
-      border-left-color: $tooltip-arrow-color;
+      left: 0 #{"/* rtl:ignore */"};
+      border-width: ($tooltip-arrow-width / 2) 0 ($tooltip-arrow-width / 2) $tooltip-arrow-height #{"/* rtl:ignore */"};
+      border-left-color: $tooltip-arrow-color #{"/* rtl:ignore */"};
     }
   }
 }
     @extend .bs-tooltip-top;
   }
   &[x-placement^="right"] {
-    @extend .bs-tooltip-right;
+    @extend .bs-tooltip-end;
   }
   &[x-placement^="bottom"] {
     @extend .bs-tooltip-bottom;
   }
   &[x-placement^="left"] {
-    @extend .bs-tooltip-left;
+    @extend .bs-tooltip-start;
   }
 }
 
index 03c71d1c24da245ca78ce1d3ee8460ffceb4fef2..dacd8b2891033892be4b95a3c54bb68338eb2e59 100644 (file)
@@ -12,7 +12,11 @@ $utilities: map-merge(
     "float": (
       responsive: true,
       property: float,
-      values: left right none
+      values: (
+        start: left,
+        end: right,
+        none: none,
+      )
     ),
     "overflow": (
       property: overflow,
@@ -47,12 +51,14 @@ $utilities: map-merge(
       property: bottom,
       values: $position-values
     ),
-    "left": (
+    "start": (
       property: left,
+      class: start,
       values: $position-values
     ),
-    "right": (
+    "end": (
       property: right,
+      class: end,
       values: $position-values
     ),
     "translate-middle": (
@@ -76,8 +82,9 @@ $utilities: map-merge(
         0: 0,
       )
     ),
-    "border-right": (
+    "border-end": (
       property: border-right,
+      class: border-end,
       values: (
         null: $border-width solid $border-color,
         0: 0,
@@ -90,8 +97,9 @@ $utilities: map-merge(
         0: 0,
       )
     ),
-    "border-left": (
+    "border-start": (
       property: border-left,
+      class: border-start,
       values: (
         null: $border-width solid $border-color,
         0: 0,
@@ -288,10 +296,10 @@ $utilities: map-merge(
       class: mt,
       values: map-merge($spacers, (auto: auto))
     ),
-    "margin-right": (
+    "margin-end": (
       responsive: true,
       property: margin-right,
-      class: mr,
+      class: me,
       values: map-merge($spacers, (auto: auto))
     ),
     "margin-bottom": (
@@ -300,10 +308,10 @@ $utilities: map-merge(
       class: mb,
       values: map-merge($spacers, (auto: auto))
     ),
-    "margin-left": (
+    "margin-start": (
       responsive: true,
       property: margin-left,
-      class: ml,
+      class: ms,
       values: map-merge($spacers, (auto: auto))
     ),
     // Negative margin utilities
@@ -331,10 +339,10 @@ $utilities: map-merge(
       class: mt,
       values: $negative-spacers
     ),
-    "negative-margin-right": (
+    "negative-margin-end": (
       responsive: true,
       property: margin-right,
-      class: mr,
+      class: me,
       values: $negative-spacers
     ),
     "negative-margin-bottom": (
@@ -343,10 +351,10 @@ $utilities: map-merge(
       class: mb,
       values: $negative-spacers
     ),
-    "negative-margin-left": (
+    "negative-margin-start": (
       responsive: true,
       property: margin-left,
-      class: ml,
+      class: ms,
       values: $negative-spacers
     ),
     // Padding utilities
@@ -374,10 +382,10 @@ $utilities: map-merge(
       class: pt,
       values: $spacers
     ),
-    "padding-right": (
+    "padding-end": (
       responsive: true,
       property: padding-right,
-      class: pr,
+      class: pe,
       values: $spacers
     ),
     "padding-bottom": (
@@ -386,10 +394,10 @@ $utilities: map-merge(
       class: pb,
       values: $spacers
     ),
-    "padding-left": (
+    "padding-start": (
       responsive: true,
       property: padding-left,
-      class: pl,
+      class: ps,
       values: $spacers
     ),
     // Text
@@ -424,7 +432,11 @@ $utilities: map-merge(
       responsive: true,
       property: text-align,
       class: text,
-      values: left right center
+      values: (
+        start: left,
+        end: right,
+        center: center,
+      )
     ),
     "color": (
       property: color,
@@ -483,7 +495,8 @@ $utilities: map-merge(
     "word-wrap": (
       property: word-wrap word-break,
       class: text,
-      values: (break: break-word)
+      values: (break: break-word),
+      rtl: false
     ),
     "font-family": (
       property: font-family,
@@ -517,9 +530,9 @@ $utilities: map-merge(
       class: rounded-top,
       values: (null: $border-radius)
     ),
-    "rounded-right": (
+    "rounded-end": (
       property: border-top-right-radius border-bottom-right-radius,
-      class: rounded-right,
+      class: rounded-end,
       values: (null: $border-radius)
     ),
     "rounded-bottom": (
@@ -527,9 +540,9 @@ $utilities: map-merge(
       class: rounded-bottom,
       values: (null: $border-radius)
     ),
-    "rounded-left": (
+    "rounded-start": (
       property: border-bottom-left-radius border-top-left-radius,
-      class: rounded-left,
+      class: rounded-start,
       values: (null: $border-radius)
     ),
     "visibility": (
index 3a551ffb2b5f63bbbb710e8c6b624f50b84285f4..b97e520d7c034904a1721d6e4bc112a8e8917c11 100644 (file)
@@ -679,7 +679,7 @@ $input-transition:                      border-color .15s ease-in-out, box-shado
 
 $form-check-input-width:                  1em !default;
 $form-check-min-height:                   $font-size-base * $line-height-base !default;
-$form-check-padding-left:                 $form-check-input-width + .5em !default;
+$form-check-padding-start:                $form-check-input-width + .5em !default;
 $form-check-margin-bottom:                .125rem !default;
 $form-check-label-color:                  null !default;
 $form-check-label-cursor:                 null !default;
@@ -711,7 +711,7 @@ $form-check-btn-check-disabled-opacity:    $btn-disabled-opacity !default;
 
 $form-switch-color:               rgba(0, 0, 0, .25) !default;
 $form-switch-width:               2em !default;
-$form-switch-padding-left:        $form-switch-width + .5em !default;
+$form-switch-padding-start:       $form-switch-width + .5em !default;
 $form-switch-bg-image:            url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'><circle r='3' fill='#{$form-switch-color}'/></svg>") !default;
 $form-switch-border-radius:       $form-switch-width !default;
 
@@ -722,7 +722,7 @@ $form-switch-checked-color:       $component-active-color !default;
 $form-switch-checked-bg-image:    url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'><circle r='3' fill='#{$form-switch-checked-color}'/></svg>") !default;
 $form-switch-checked-bg-position: right center !default;
 
-$form-check-inline-margin-right:        1rem !default;
+$form-check-inline-margin-end:    1rem !default;
 
 $input-group-addon-padding-y:           $input-padding-y !default;
 $input-group-addon-padding-x:           $input-padding-x !default;
@@ -748,9 +748,9 @@ $form-select-bg-size:               16px 12px !default; // In pixels because ima
 $form-select-indicator-color:       $gray-800 !default;
 $form-select-indicator:             url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><path fill='none' stroke='#{$form-select-indicator-color}' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/></svg>") !default;
 
-$form-select-feedback-icon-padding-right: add(1em * .75, (2 * $form-select-padding-y * .75) + $form-select-padding-x + $form-select-indicator-padding) !default;
-$form-select-feedback-icon-position:      center right ($form-select-padding-x + $form-select-indicator-padding) !default;
-$form-select-feedback-icon-size:          $input-height-inner-half $input-height-inner-half !default;
+$form-select-feedback-icon-padding-end: add(1em * .75, (2 * $form-select-padding-y * .75) + $form-select-padding-x + $form-select-indicator-padding) !default;
+$form-select-feedback-icon-position:    center right ($form-select-padding-x + $form-select-indicator-padding) !default;
+$form-select-feedback-icon-size:        $input-height-inner-half $input-height-inner-half !default;
 
 $form-select-border-width:        $input-border-width !default;
 $form-select-border-color:        $input-border-color !default;
@@ -879,7 +879,7 @@ $navbar-brand-font-size:            $font-size-lg !default;
 $nav-link-height:                   $font-size-base * $line-height-base + $nav-link-padding-y * 2 !default;
 $navbar-brand-height:               $navbar-brand-font-size * $line-height-base !default;
 $navbar-brand-padding-y:            ($nav-link-height - $navbar-brand-height) / 2 !default;
-$navbar-brand-margin-right:         1rem !default;
+$navbar-brand-margin-end:           1rem !default;
 
 $navbar-toggler-padding-y:          .25rem !default;
 $navbar-toggler-padding-x:          .75rem !default;
@@ -969,7 +969,7 @@ $pagination-color:                  $link-color !default;
 $pagination-bg:                     $white !default;
 $pagination-border-width:           $border-width !default;
 $pagination-border-radius:          $border-radius !default;
-$pagination-margin-left:            -$pagination-border-width !default;
+$pagination-margin-start:           -$pagination-border-width !default;
 $pagination-border-color:           $gray-300 !default;
 
 $pagination-focus-color:            $link-hover-color !default;
@@ -1253,6 +1253,7 @@ $breadcrumb-bg:                     null !default;
 $breadcrumb-divider-color:          $gray-600 !default;
 $breadcrumb-active-color:           $gray-600 !default;
 $breadcrumb-divider:                quote("/") !default;
+$breadcrumb-divider-flipped:        $breadcrumb-divider !default;
 $breadcrumb-border-radius:          null !default;
 
 // Carousel
index 3314b57d7ef31e9066e7092022830d3f7d8361c5..1c836bf2f5a7d8c376d2110d4f58de25dcbe2d6b 100644 (file)
@@ -42,23 +42,23 @@ $utilities: map-get-multiple(
     "margin-x",
     "margin-y",
     "margin-top",
-    "margin-right",
+    "margin-end",
     "margin-bottom",
-    "margin-left",
+    "margin-start",
     "negative-margin",
     "negative-margin-x",
     "negative-margin-y",
     "negative-margin-top",
-    "negative-margin-right",
+    "negative-margin-end",
     "negative-margin-bottom",
-    "negative-margin-left",
+    "negative-margin-start",
     "padding",
     "padding-x",
     "padding-y",
     "padding-top",
-    "padding-right",
+    "padding-end",
     "padding-bottom",
-    "padding-left",
+    "padding-start",
   )
 );
 
index 77af825f5f14569f6d103ae889d502600f76c112..1d6d87363dcfd398c264454738d9952dd298e2a5 100644 (file)
@@ -5,12 +5,12 @@
 .form-check {
   display: block;
   min-height: $form-check-min-height;
-  padding-left: $form-check-padding-left;
+  padding-left: $form-check-padding-start;
   margin-bottom: $form-check-margin-bottom;
 
   .form-check-input {
     float: left;
-    margin-left: $form-check-padding-left * -1;
+    margin-left: $form-check-padding-start * -1;
   }
 }
 
 //
 
 .form-switch {
-  padding-left: $form-switch-padding-left;
+  padding-left: $form-switch-padding-start;
 
   .form-check-input {
     width: $form-switch-width;
-    margin-left: $form-switch-padding-left * -1;
+    margin-left: $form-switch-padding-start * -1;
     background-image: escape-svg($form-switch-bg-image);
     background-position: left center;
     @include border-radius($form-switch-border-radius);
 
 .form-check-inline {
   display: inline-block;
-  margin-right: $form-check-inline-margin-right;
+  margin-right: $form-check-inline-margin-end;
 }
 
 .btn-check {
index 2729eccbd506177a908ec46afcc82ea16cd420f4..140052936cbb7d02405f3b774fceefe142ea2952 100644 (file)
   &:not(.has-validation) {
     > :not(:last-child):not(.dropdown-toggle):not(.dropdown-menu),
     > .dropdown-toggle:nth-last-child(n + 3) {
-      @include border-right-radius(0);
+      @include border-end-radius(0);
     }
   }
 
   &.has-validation {
     > :nth-last-child(n + 3):not(.dropdown-toggle):not(.dropdown-menu),
     > .dropdown-toggle:nth-last-child(n + 4) {
-      @include border-right-radius(0);
+      @include border-end-radius(0);
     }
   }
 
 
   > :not(:first-child):not(.dropdown-menu)#{$validation-messages} {
     margin-left: -$input-border-width;
-    @include border-left-radius(0);
+    @include border-start-radius(0);
   }
 }
index 70f57204415285a28dcffca84e9cfcb2b5e5b9dd..819b8abd4c99818053c33d26fc6d7fe15bb5c6fa 100644 (file)
@@ -30,7 +30,7 @@
   }
 }
 
-@mixin border-right-radius($radius: $border-radius) {
+@mixin border-end-radius($radius: $border-radius) {
   @if $enable-rounded {
     border-top-right-radius: valid-radius($radius);
     border-bottom-right-radius: valid-radius($radius);
   }
 }
 
-@mixin border-left-radius($radius: $border-radius) {
+@mixin border-start-radius($radius: $border-radius) {
   @if $enable-rounded {
     border-top-left-radius: valid-radius($radius);
     border-bottom-left-radius: valid-radius($radius);
   }
 }
 
-@mixin border-top-left-radius($radius: $border-radius) {
+@mixin border-top-start-radius($radius: $border-radius) {
   @if $enable-rounded {
     border-top-left-radius: valid-radius($radius);
   }
 }
 
-@mixin border-top-right-radius($radius: $border-radius) {
+@mixin border-top-end-radius($radius: $border-radius) {
   @if $enable-rounded {
     border-top-right-radius: valid-radius($radius);
   }
 }
 
-@mixin border-bottom-right-radius($radius: $border-radius) {
+@mixin border-bottom-end-radius($radius: $border-radius) {
   @if $enable-rounded {
     border-bottom-right-radius: valid-radius($radius);
   }
 }
 
-@mixin border-bottom-left-radius($radius: $border-radius) {
+@mixin border-bottom-start-radius($radius: $border-radius) {
   @if $enable-rounded {
     border-bottom-left-radius: valid-radius($radius);
   }
index 07d72280ce21773a0e9de09f7bfadeed0c97432b..fc1066914d7b3d82e86e6d1d03cb837e92a7083f 100644 (file)
   border-left: $caret-width solid transparent;
 }
 
-@mixin caret-right {
+@mixin caret-end {
   border-top: $caret-width solid transparent;
   border-right: 0;
   border-bottom: $caret-width solid transparent;
   border-left: $caret-width solid;
 }
 
-@mixin caret-left {
+@mixin caret-start {
   border-top: $caret-width solid transparent;
   border-right: $caret-width solid;
   border-bottom: $caret-width solid transparent;
         @include caret-down();
       } @else if $direction == up {
         @include caret-up();
-      } @else if $direction == right {
-        @include caret-right();
+      } @else if $direction == end {
+        @include caret-end();
       }
     }
 
-    @if $direction == left {
+    @if $direction == start {
       &::after {
         display: none;
       }
@@ -51,7 +51,7 @@
         margin-right: $caret-spacing;
         vertical-align: $caret-vertical-align;
         content: "";
-        @include caret-left();
+        @include caret-start();
       }
     }
 
index 9adc0debee4268a0f2705c5d9e4e16eb0890653e..5e4cfd4883e774556d90b98b15ec137b0d73c066 100644 (file)
@@ -79,7 +79,7 @@
       border-color: $color;
 
       @if $enable-validation-icons {
-        padding-right: $form-select-feedback-icon-padding-right;
+        padding-right: $form-select-feedback-icon-padding-end;
         background-image: escape-svg($form-select-indicator), escape-svg($icon);
         background-position: $form-select-bg-position, $form-select-feedback-icon-position;
         background-size: $form-select-bg-size, $form-select-feedback-icon-size;
index 52ad1e121292de52f9acf042fab20af3eef0f8fb..9cb7fd8bcd367660387eac01c876c315bdbd7c45 100644 (file)
@@ -7,16 +7,16 @@
   }
 
   .page-item {
-    @if $pagination-margin-left == (-$pagination-border-width) {
+    @if $pagination-margin-start == (-$pagination-border-width) {
       &:first-child {
         .page-link {
-          @include border-left-radius($border-radius);
+          @include border-start-radius($border-radius);
         }
       }
 
       &:last-child {
         .page-link {
-          @include border-right-radius($border-radius);
+          @include border-end-radius($border-radius);
         }
       }
     } @else {
index 145839a39fefb85b448718f62d5564eddde6ae77..4d2370a0bf3c9244bca1b2b482a6b9f25572eea6 100644 (file)
       }
     }
 
+    $is-rtl: map-get($utility, rtl);
+
     @if $value != null {
+      @if $is-rtl == false {
+        /* rtl:begin:remove */
+      }
       .#{$property-class + $infix + $property-class-modifier} {
         @each $property in $properties {
           #{$property}: $value if($enable-important-utilities, !important, null);
@@ -55,6 +60,9 @@
           }
         }
       }
+      @if $is-rtl == false {
+        /* rtl:end:remove */
+      }
     }
   }
 }