From f4d85517b9d55d270efd324b62aff1de061cf6e9 Mon Sep 17 00:00:00 2001 From: patrickhlauke Date: Mon, 17 Jun 2019 23:45:07 +0100 Subject: [PATCH 01/35] Add initial tabindex handling for tabs set `tabindex` as well as `aria-selected` on tabs --- js/src/tab.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/src/tab.js b/js/src/tab.js index b9db64baa413..65dc3602b8f6 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -183,12 +183,14 @@ class Tab { if (active.getAttribute('role') === 'tab') { active.setAttribute('aria-selected', false) + active.setAttribute('tabindex', '-1') } } element.classList.add(ClassName.ACTIVE) if (element.getAttribute('role') === 'tab') { element.setAttribute('aria-selected', true) + element.setAttribute('tabindex', '0') } reflow(element) From 7c90656c13c8b1d0fb2d0a4ffaf8a06b46d6111e Mon Sep 17 00:00:00 2001 From: patrickhlauke Date: Tue, 18 Jun 2019 00:02:45 +0100 Subject: [PATCH 02/35] WIP add initial keyboard handling and initialisation - initialisation happens very inelegantly - could do with some advice on how to move this to a function (should it be public, private, static?) - keyboard handling works, but seems to get confused when switching too quickly between tabs sometimes - activating tabs currently done via click() - there must be a nicer way to do this...how? fire _activate? which parameters to pass? --- js/src/tab.js | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/js/src/tab.js b/js/src/tab.js index 65dc3602b8f6..dc0acec2a5aa 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -29,13 +29,19 @@ const VERSION = '4.3.1' const DATA_KEY = 'bs.tab' const EVENT_KEY = `.${DATA_KEY}` const DATA_API_KEY = '.data-api' +const ARROW_LEFT_KEYCODE = 37 // KeyboardEvent.which value for left arrow key +const ARROW_UP_KEYCODE = 38 // KeyboardEvent.which value for up arrow key +const ARROW_RIGHT_KEYCODE = 39 // KeyboardEvent.which value for right arrow key +const ARROW_DOWN_KEYCODE = 40 // KeyboardEvent.which value for down arrow key const Event = { HIDE: `hide${EVENT_KEY}`, HIDDEN: `hidden${EVENT_KEY}`, SHOW: `show${EVENT_KEY}`, SHOWN: `shown${EVENT_KEY}`, - CLICK_DATA_API: `click${EVENT_KEY}${DATA_API_KEY}` + CLICK_DATA_API: `click${EVENT_KEY}${DATA_API_KEY}`, + KEYDOWN_DATA_API: `keydown${EVENT_KEY}${DATA_API_KEY}`, + LOAD_DATA_API: `load${EVENT_KEY}${DATA_API_KEY}` } const ClassName = { @@ -52,6 +58,7 @@ const Selector = { ACTIVE: '.active', ACTIVE_UL: ':scope > li > .active', DATA_TOGGLE: '[data-toggle="tab"], [data-toggle="pill"], [data-toggle="list"]', + TABLIST: '[role="tablist"]', DROPDOWN_TOGGLE: '.dropdown-toggle', DROPDOWN_ACTIVE_CHILD: ':scope > .dropdown-menu .active' } @@ -231,6 +238,48 @@ class Tab { }) } + static _dataApiKeydownHandler(event) { + const tablist = SelectorEngine.closest(event.target, Selector.TABLIST) + let tablistorientation = tablist.getAttribute('aria-orientation') + if (tablistorientation !== 'vertical') { + tablistorientation = 'horizontal' + } + + if ((tablistorientation === 'horizontal' && event.which !== ARROW_LEFT_KEYCODE && event.which !== ARROW_RIGHT_KEYCODE) || (tablistorientation === 'vertical' && event.which !== ARROW_UP_KEYCODE && event.which !== ARROW_DOWN_KEYCODE)) { + return + } + + event.preventDefault() + event.stopPropagation() + + if (this.disabled || this.classList.contains(ClassName.DISABLED)) { + return + } + + const tabs = makeArray(SelectorEngine.find(Selector.DATA_TOGGLE, tablist)) + + if (!tabs.length) { + return + } + + let index = tabs.indexOf(event.target) + + if ((event.which === ARROW_LEFT_KEYCODE || event.which === ARROW_UP_KEYCODE) && index > 0) { // Left / Up + index-- + } + + if ((event.which === ARROW_RIGHT_KEYCODE || event.which === ARROW_DOWN_KEYCODE) && index < tabs.length - 1) { // Right / Down + index++ + } + + if (index < 0) { + index = 0 + } + + tabs[index].focus() + tabs[index].click() // WIP naive way of doing this? any better way? calling _activate or something? + } + static _getInstance(element) { return Data.getData(element, DATA_KEY) } @@ -242,6 +291,36 @@ class Tab { * ------------------------------------------------------------------------ */ +EventHandler.on(window, Event.LOAD_DATA_API, () => { + const tablists = makeArray(SelectorEngine.find(Selector.TABLIST)) + if (tablists.length === 0) { + return + } + + // iterate over all found sets of tab lists + for (let i = 0; i < tablists.length; i++) { + const tabs = makeArray(SelectorEngine.find(Selector.DATA_TOGGLE, tablists[i])) + let selectedTabFound = false + + // iterate over each tab in the tablist, make sure they have correct tabindex/aria-selected + for (let j = 0; j < tabs.length; j++) { + if (tabs[j].hasAttribute('aria-selected') && tabs[j].getAttribute('aria-selected') === 'true' && selectedTabFound === false) { + tabs[j].setAttribute('tabindex', '0') + selectedTabFound = true + } else { + tabs[j].setAttribute('tabindex', '-1') + tabs[j].setAttribute('aria-selected', 'false') + } + } + + // if none of the tabs were explicitly marked as selected, pick first one + if (selectedTabFound === false) { + tabs[0].setAttribute('tabindex', '0') + tabs[0].setAttribute('aria-selected', 'true') + } + } +}) +EventHandler.on(document, Event.KEYDOWN_DATA_API, Selector.DATA_TOGGLE, Tab._dataApiKeydownHandler) EventHandler.on(document, Event.CLICK_DATA_API, Selector.DATA_TOGGLE, function (event) { event.preventDefault() From 33df950b276a31d044dfacbbd19145e11e6e4afe Mon Sep 17 00:00:00 2001 From: patrickhlauke Date: Tue, 18 Jun 2019 20:19:53 +0100 Subject: [PATCH 03/35] Remove dropdown-in-tabs related code - there's no ARIA pattern that would allow dropdowns in tablists; it cannot be expressed accessibly to AT users. it also has serious usability drawbacks. we're already saying in the docs it should not be used ... this goes a step further and removes the handling for it --- js/src/tab.js | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/js/src/tab.js b/js/src/tab.js index dc0acec2a5aa..bde07818c855 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -45,7 +45,6 @@ const Event = { } const ClassName = { - DROPDOWN_MENU: 'dropdown-menu', ACTIVE: 'active', DISABLED: 'disabled', FADE: 'fade', @@ -53,14 +52,11 @@ const ClassName = { } const Selector = { - DROPDOWN: '.dropdown', NAV_LIST_GROUP: '.nav, .list-group', ACTIVE: '.active', ACTIVE_UL: ':scope > li > .active', DATA_TOGGLE: '[data-toggle="tab"], [data-toggle="pill"], [data-toggle="list"]', - TABLIST: '[role="tablist"]', - DROPDOWN_TOGGLE: '.dropdown-toggle', - DROPDOWN_ACTIVE_CHILD: ':scope > .dropdown-menu .active' + TABLIST: '[role="tablist"]' } /** @@ -182,12 +178,6 @@ class Tab { if (active) { active.classList.remove(ClassName.ACTIVE) - const dropdownChild = SelectorEngine.findOne(Selector.DROPDOWN_ACTIVE_CHILD, active.parentNode) - - if (dropdownChild) { - dropdownChild.classList.remove(ClassName.ACTIVE) - } - if (active.getAttribute('role') === 'tab') { active.setAttribute('aria-selected', false) active.setAttribute('tabindex', '-1') @@ -206,17 +196,6 @@ class Tab { element.classList.add(ClassName.SHOW) } - if (element.parentNode && element.parentNode.classList.contains(ClassName.DROPDOWN_MENU)) { - const dropdownElement = SelectorEngine.closest(element, Selector.DROPDOWN) - - if (dropdownElement) { - makeArray(SelectorEngine.find(Selector.DROPDOWN_TOGGLE)) - .forEach(dropdown => dropdown.classList.add(ClassName.ACTIVE)) - } - - element.setAttribute('aria-expanded', true) - } - if (callback) { callback() } From 78737b239f6acba32a7129395000ffa8339a2fb7 Mon Sep 17 00:00:00 2001 From: patrickhlauke Date: Tue, 18 Jun 2019 20:32:52 +0100 Subject: [PATCH 04/35] Add explicit note about aria-orientation, add all missing role="presentation" `role="presentation"` is needed as otherwise AT (like NVDA) will get confused/won't be able to "count" the total number of tabs properly --- site/content/docs/4.3/components/navs.md | 44 ++++++++++++------------ 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/site/content/docs/4.3/components/navs.md b/site/content/docs/4.3/components/navs.md index 5281fcdaff86..f3df6d2409d7 100644 --- a/site/content/docs/4.3/components/navs.md +++ b/site/content/docs/4.3/components/navs.md @@ -307,17 +307,17 @@ Use the tab JavaScript plugin—include it individually or through the compiled Dynamic tabbed interfaces, as described in the [WAI ARIA Authoring Practices](https://www.w3.org/TR/wai-aria-practices/#tabpanel), require `role="tablist"`, `role="tab"`, `role="tabpanel"`, and additional `aria-` attributes in order to convey their structure, functionality and current state to users of assistive technologies (such as screen readers). -Note that dynamic tabbed interfaces should not contain dropdown menus, as this causes both usability and accessibility issues. From a usability perspective, the fact that the currently displayed tab's trigger element is not immediately visible (as it's inside the closed dropdown menu) can cause confusion. From an accessibility point of view, there is currently no sensible way to map this sort of construct to a standard WAI ARIA pattern, meaning that it cannot be easily made understandable to users of assistive technologies. +Note that dynamic tabbed interfaces can't contain dropdown menus, as this causes both usability and accessibility issues. From a usability perspective, the fact that the currently displayed tab's trigger element is not immediately visible (as it's inside the closed dropdown menu) can cause confusion. From an accessibility point of view, there is currently no sensible way to map this sort of construct to a standard WAI ARIA pattern, meaning that it cannot be easily made understandable to users of assistive technologies.
@@ -336,13 +336,13 @@ Note that dynamic tabbed interfaces should not contain dropdown menus, {{< highlight html >}} @@ -395,13 +395,13 @@ The tabs plugin also works with pills.
@@ -420,13 +420,13 @@ The tabs plugin also works with pills. {{< highlight html >}} @@ -437,7 +437,7 @@ The tabs plugin also works with pills.
{{< /highlight >}} -And with vertical pills. +When making vertical tab panels, make sure to include `aria-orientation="vertical"` to switch to the appropriate keyboard behavior (switching tabs with the up and down cursor keys).
@@ -496,16 +496,16 @@ You can activate a tab or pill navigation without writing any JavaScript by simp {{< highlight html >}} @@ -564,16 +564,16 @@ Activates a tab element and content container. Tab should have either a `data-ta {{< highlight html >}} From 53e954c834b6b61ceeb971a123163a3f9f8fd180 Mon Sep 17 00:00:00 2001 From: patrickhlauke Date: Wed, 19 Jun 2019 10:18:35 +0100 Subject: [PATCH 05/35] Initial code tweaks based on comments --- js/src/tab.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/js/src/tab.js b/js/src/tab.js index bde07818c855..0d1dc6656f6d 100644 --- a/js/src/tab.js +++ b/js/src/tab.js @@ -59,6 +59,11 @@ const Selector = { TABLIST: '[role="tablist"]' } +const Orientation = { + VERTICAL: 'vertical', + HORIZONTAL: 'horizontal' +} + /** * ------------------------------------------------------------------------ * Class Definition @@ -219,12 +224,12 @@ class Tab { static _dataApiKeydownHandler(event) { const tablist = SelectorEngine.closest(event.target, Selector.TABLIST) - let tablistorientation = tablist.getAttribute('aria-orientation') - if (tablistorientation !== 'vertical') { - tablistorientation = 'horizontal' + let tabListOrientation = tablist.getAttribute('aria-orientation') + if (tabListOrientation !== Orientation.VERTICAL) { + tabListOrientation = Orientation.HORIZONTAL } - if ((tablistorientation === 'horizontal' && event.which !== ARROW_LEFT_KEYCODE && event.which !== ARROW_RIGHT_KEYCODE) || (tablistorientation === 'vertical' && event.which !== ARROW_UP_KEYCODE && event.which !== ARROW_DOWN_KEYCODE)) { + if ((tabListOrientation === Orientation.HORIZONTAL && event.which !== ARROW_LEFT_KEYCODE && event.which !== ARROW_RIGHT_KEYCODE) || (tabListOrientation === Orientation.VERTICAL && event.which !== ARROW_UP_KEYCODE && event.which !== ARROW_DOWN_KEYCODE)) { return } @@ -272,7 +277,7 @@ class Tab { EventHandler.on(window, Event.LOAD_DATA_API, () => { const tablists = makeArray(SelectorEngine.find(Selector.TABLIST)) - if (tablists.length === 0) { + if (!tablists.length) { return } @@ -283,7 +288,7 @@ EventHandler.on(window, Event.LOAD_DATA_API, () => { // iterate over each tab in the tablist, make sure they have correct tabindex/aria-selected for (let j = 0; j < tabs.length; j++) { - if (tabs[j].hasAttribute('aria-selected') && tabs[j].getAttribute('aria-selected') === 'true' && selectedTabFound === false) { + if (tabs[j].getAttribute('aria-selected') === 'true' && !selectedTabFound) { tabs[j].setAttribute('tabindex', '0') selectedTabFound = true } else { @@ -293,7 +298,7 @@ EventHandler.on(window, Event.LOAD_DATA_API, () => { } // if none of the tabs were explicitly marked as selected, pick first one - if (selectedTabFound === false) { + if (!selectedTabFound) { tabs[0].setAttribute('tabindex', '0') tabs[0].setAttribute('aria-selected', 'true') } From d722edcb2996bc22acb0cf4f6f4d18d0c7ea3065 Mon Sep 17 00:00:00 2001 From: patrickhlauke Date: Wed, 19 Jun 2019 10:48:56 +0100 Subject: [PATCH 06/35] Remove dropdown-in-tabs related unit tests Since that logic has now been expunged --- js/tests/unit/tab.js | 73 +++++++++++++++----------------------------- 1 file changed, 24 insertions(+), 49 deletions(-) diff --git a/js/tests/unit/tab.js b/js/tests/unit/tab.js index 827fb707c166..628c2008a8c7 100644 --- a/js/tests/unit/tab.js +++ b/js/tests/unit/tab.js @@ -180,35 +180,6 @@ $(function () { .bootstrapTab('show') }) - QUnit.test('show and shown events should reference correct relatedTarget', function (assert) { - assert.expect(2) - var done = assert.async() - - var dropHTML = - '' - - $(dropHTML) - .find('ul > li:first-child a') - .bootstrapTab('show') - .end() - .find('ul > li:last-child a') - .on('show.bs.tab', function (e) { - assert.strictEqual(e.relatedTarget.hash, '#a1-1', 'references correct element as relatedTarget') - }) - .on('shown.bs.tab', function (e) { - assert.strictEqual(e.relatedTarget.hash, '#a1-1', 'references correct element as relatedTarget') - done() - }) - .bootstrapTab('show') - }) - QUnit.test('should fire hide and hidden events', function (assert) { assert.expect(2) var done = assert.async() @@ -265,6 +236,30 @@ $(function () { .bootstrapTab('show') }) + QUnit.test('show and shown events should reference correct relatedTarget', function (assert) { + assert.expect(2) + var done = assert.async() + + var tabsHTML = '' + + $(tabsHTML) + .find('li:first-child a') + .bootstrapTab('show') + .end() + .find('li:last-child a') + .on('show.bs.tab', function (e) { + assert.strictEqual(e.relatedTarget.hash, '#home', 'references correct element as relatedTarget') + }) + .on('shown.bs.tab', function (e) { + assert.strictEqual(e.relatedTarget.hash, '#home', 'references correct element as relatedTarget') + done() + }) + .bootstrapTab('show') + }) + QUnit.test('hide and hidden events contain correct relatedTarget', function (assert) { assert.expect(2) var done = assert.async() @@ -327,26 +322,6 @@ $(function () { assert.ok($tabs.find('li:last-child a').hasClass('active')) }) - QUnit.test('selected tab should deactivate previous selected link in dropdown', function (assert) { - assert.expect(3) - var tabsHTML = '' - var $tabs = $(tabsHTML).appendTo('#qunit-fixture') - - $tabs.find('li:first-child a')[0].click() - assert.ok($tabs.find('li:first-child a').hasClass('active')) - assert.notOk($tabs.find('li:last-child a').hasClass('active')) - assert.notOk($tabs.find('li:last-child .dropdown-menu a:first-child').hasClass('active')) - }) - QUnit.test('Nested tabs', function (assert) { assert.expect(2) var done = assert.async() From 701977bf4ee28e662c302b8e6bd24d7f1cffaf84 Mon Sep 17 00:00:00 2001 From: patrickhlauke Date: Wed, 19 Jun 2019 10:54:19 +0100 Subject: [PATCH 07/35] Make note about dropdowns in tabs an actual callout --- site/content/docs/4.3/components/navs.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/site/content/docs/4.3/components/navs.md b/site/content/docs/4.3/components/navs.md index f3df6d2409d7..edea7b0aa072 100644 --- a/site/content/docs/4.3/components/navs.md +++ b/site/content/docs/4.3/components/navs.md @@ -307,7 +307,9 @@ Use the tab JavaScript plugin—include it individually or through the compiled Dynamic tabbed interfaces, as described in the [WAI ARIA Authoring Practices](https://www.w3.org/TR/wai-aria-practices/#tabpanel), require `role="tablist"`, `role="tab"`, `role="tabpanel"`, and additional `aria-` attributes in order to convey their structure, functionality and current state to users of assistive technologies (such as screen readers). -Note that dynamic tabbed interfaces can't contain dropdown menus, as this causes both usability and accessibility issues. From a usability perspective, the fact that the currently displayed tab's trigger element is not immediately visible (as it's inside the closed dropdown menu) can cause confusion. From an accessibility point of view, there is currently no sensible way to map this sort of construct to a standard WAI ARIA pattern, meaning that it cannot be easily made understandable to users of assistive technologies. +{{< callout warning >}} +Dynamic tabbed interfaces can't contain dropdown menus, as this causes both usability and accessibility issues. From a usability perspective, the fact that the currently displayed tab's trigger element is not immediately visible (as it's inside the closed dropdown menu) can cause confusion. From an accessibility point of view, there is currently no sensible way to map this sort of construct to a standard WAI ARIA pattern, meaning that it cannot be easily made understandable to users of assistive technologies. +{{< /callout >}}
-
+

Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.

Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.

@@ -116,14 +79,6 @@

Tabs without fade (no initially active pane)

Food truck fixie locavore, accusamus mcsweeney's marfa nulla single-origin coffee squid. Exercitation +1 labore velit, blog sartorial PBR leggings next level wes anderson artisan four loko farm-to-table craft beer twee. Qui photo booth letterpress, commodo enim craft beer mlkshk aliquip jean shorts ullamco ad vinyl cillum PBR. Homo nostrud organic, assumenda labore aesthetic magna delectus mollit. Keytar helvetica VHS salvia yr, vero magna velit sapiente labore stumptown. Vegan fanny pack odio cillum wes anderson 8-bit, sustainable jean shorts beard ut DIY ethical culpa terry richardson biodiesel. Art party scenester stumptown, tumblr butcher vero sint qui sapiente accusamus tattooed echo park.

Food truck fixie locavore, accusamus mcsweeney's marfa nulla single-origin coffee squid. Exercitation +1 labore velit, blog sartorial PBR leggings next level wes anderson artisan four loko farm-to-table craft beer twee. Qui photo booth letterpress, commodo enim craft beer mlkshk aliquip jean shorts ullamco ad vinyl cillum PBR. Homo nostrud organic, assumenda labore aesthetic magna delectus mollit. Keytar helvetica VHS salvia yr, vero magna velit sapiente labore stumptown. Vegan fanny pack odio cillum wes anderson 8-bit, sustainable jean shorts beard ut DIY ethical culpa terry richardson biodiesel. Art party scenester stumptown, tumblr butcher vero sint qui sapiente accusamus tattooed echo park.

-
-

Etsy mixtape wayfarers, ethical wes anderson tofu before they sold out mcsweeney's organic lomo retro fanny pack lo-fi farm-to-table readymade. Messenger bag gentrify pitchfork tattooed craft beer, iphone skateboard locavore carles etsy salvia banksy hoodie helvetica. DIY synth PBR banksy irony. Leggings gentrify squid 8-bit cred pitchfork. Williamsburg banh mi whatever gluten-free, carles pitchfork biodiesel fixie etsy retro mlkshk vice blog. Scenester cred you probably haven't heard of them, vinyl craft beer blog stumptown. Pitchfork sustainable tofu synth chambray yr.

-

Etsy mixtape wayfarers, ethical wes anderson tofu before they sold out mcsweeney's organic lomo retro fanny pack lo-fi farm-to-table readymade. Messenger bag gentrify pitchfork tattooed craft beer, iphone skateboard locavore carles etsy salvia banksy hoodie helvetica. DIY synth PBR banksy irony. Leggings gentrify squid 8-bit cred pitchfork. Williamsburg banh mi whatever gluten-free, carles pitchfork biodiesel fixie etsy retro mlkshk vice blog. Scenester cred you probably haven't heard of them, vinyl craft beer blog stumptown. Pitchfork sustainable tofu synth chambray yr.

-
-
-

Trust fund seitan letterpress, keytar raw denim keffiyeh etsy art party before they sold out master cleanse gluten-free squid scenester freegan cosby sweater. Fanny pack portland seitan DIY, art party locavore wolf cliche high life echo park Austin. Cred vinyl keffiyeh DIY salvia PBR, banh mi before they sold out farm-to-table VHS viral locavore cosby sweater. Lomo wolf viral, mustache readymade thundercats keffiyeh craft beer marfa ethical. Wolf salvia freegan, sartorial keffiyeh echo park vegan.

-

Trust fund seitan letterpress, keytar raw denim keffiyeh etsy art party before they sold out master cleanse gluten-free squid scenester freegan cosby sweater. Fanny pack portland seitan DIY, art party locavore wolf cliche high life echo park Austin. Cred vinyl keffiyeh DIY salvia PBR, banh mi before they sold out farm-to-table VHS viral locavore cosby sweater. Lomo wolf viral, mustache readymade thundercats keffiyeh craft beer marfa ethical. Wolf salvia freegan, sartorial keffiyeh echo park vegan.

-

Tabs with fade (no initially active pane)

@@ -135,13 +90,6 @@

Tabs with fade (no initially active pane)

-
@@ -153,53 +101,30 @@

Tabs with fade (no initially active pane)

Food truck fixie locavore, accusamus mcsweeney's marfa nulla single-origin coffee squid. Exercitation +1 labore velit, blog sartorial PBR leggings next level wes anderson artisan four loko farm-to-table craft beer twee. Qui photo booth letterpress, commodo enim craft beer mlkshk aliquip jean shorts ullamco ad vinyl cillum PBR. Homo nostrud organic, assumenda labore aesthetic magna delectus mollit. Keytar helvetica VHS salvia yr, vero magna velit sapiente labore stumptown. Vegan fanny pack odio cillum wes anderson 8-bit, sustainable jean shorts beard ut DIY ethical culpa terry richardson biodiesel. Art party scenester stumptown, tumblr butcher vero sint qui sapiente accusamus tattooed echo park.

Food truck fixie locavore, accusamus mcsweeney's marfa nulla single-origin coffee squid. Exercitation +1 labore velit, blog sartorial PBR leggings next level wes anderson artisan four loko farm-to-table craft beer twee. Qui photo booth letterpress, commodo enim craft beer mlkshk aliquip jean shorts ullamco ad vinyl cillum PBR. Homo nostrud organic, assumenda labore aesthetic magna delectus mollit. Keytar helvetica VHS salvia yr, vero magna velit sapiente labore stumptown. Vegan fanny pack odio cillum wes anderson 8-bit, sustainable jean shorts beard ut DIY ethical culpa terry richardson biodiesel. Art party scenester stumptown, tumblr butcher vero sint qui sapiente accusamus tattooed echo park.

-
-

Etsy mixtape wayfarers, ethical wes anderson tofu before they sold out mcsweeney's organic lomo retro fanny pack lo-fi farm-to-table readymade. Messenger bag gentrify pitchfork tattooed craft beer, iphone skateboard locavore carles etsy salvia banksy hoodie helvetica. DIY synth PBR banksy irony. Leggings gentrify squid 8-bit cred pitchfork. Williamsburg banh mi whatever gluten-free, carles pitchfork biodiesel fixie etsy retro mlkshk vice blog. Scenester cred you probably haven't heard of them, vinyl craft beer blog stumptown. Pitchfork sustainable tofu synth chambray yr.

-

Etsy mixtape wayfarers, ethical wes anderson tofu before they sold out mcsweeney's organic lomo retro fanny pack lo-fi farm-to-table readymade. Messenger bag gentrify pitchfork tattooed craft beer, iphone skateboard locavore carles etsy salvia banksy hoodie helvetica. DIY synth PBR banksy irony. Leggings gentrify squid 8-bit cred pitchfork. Williamsburg banh mi whatever gluten-free, carles pitchfork biodiesel fixie etsy retro mlkshk vice blog. Scenester cred you probably haven't heard of them, vinyl craft beer blog stumptown. Pitchfork sustainable tofu synth chambray yr.

-
-
-

Trust fund seitan letterpress, keytar raw denim keffiyeh etsy art party before they sold out master cleanse gluten-free squid scenester freegan cosby sweater. Fanny pack portland seitan DIY, art party locavore wolf cliche high life echo park Austin. Cred vinyl keffiyeh DIY salvia PBR, banh mi before they sold out farm-to-table VHS viral locavore cosby sweater. Lomo wolf viral, mustache readymade thundercats keffiyeh craft beer marfa ethical. Wolf salvia freegan, sartorial keffiyeh echo park vegan.

-

Trust fund seitan letterpress, keytar raw denim keffiyeh etsy art party before they sold out master cleanse gluten-free squid scenester freegan cosby sweater. Fanny pack portland seitan DIY, art party locavore wolf cliche high life echo park Austin. Cred vinyl keffiyeh DIY salvia PBR, banh mi before they sold out farm-to-table VHS viral locavore cosby sweater. Lomo wolf viral, mustache readymade thundercats keffiyeh craft beer marfa ethical. Wolf salvia freegan, sartorial keffiyeh echo park vegan.

-

Tabs with nav (with fade)

- + -
-
+
+

Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.

Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.

-
+

Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.

Raw denim you probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi qui.

-
-

Etsy mixtape wayfarers, ethical wes anderson tofu before they sold out mcsweeney's organic lomo retro fanny pack lo-fi farm-to-table readymade. Messenger bag gentrify pitchfork tattooed craft beer, iphone skateboard locavore carles etsy salvia banksy hoodie helvetica. DIY synth PBR banksy irony. Leggings gentrify squid 8-bit cred pitchfork. Williamsburg banh mi whatever gluten-free, carles pitchfork biodiesel fixie etsy retro mlkshk vice blog. Scenester cred you probably haven't heard of them, vinyl craft beer blog stumptown. Pitchfork sustainable tofu synth chambray yr.

-

Etsy mixtape wayfarers, ethical wes anderson tofu before they sold out mcsweeney's organic lomo retro fanny pack lo-fi farm-to-table readymade. Messenger bag gentrify pitchfork tattooed craft beer, iphone skateboard locavore carles etsy salvia banksy hoodie helvetica. DIY synth PBR banksy irony. Leggings gentrify squid 8-bit cred pitchfork. Williamsburg banh mi whatever gluten-free, carles pitchfork biodiesel fixie etsy retro mlkshk vice blog. Scenester cred you probably haven't heard of them, vinyl craft beer blog stumptown. Pitchfork sustainable tofu synth chambray yr.

-
-
-

Trust fund seitan letterpress, keytar raw denim keffiyeh etsy art party before they sold out master cleanse gluten-free squid scenester freegan cosby sweater. Fanny pack portland seitan DIY, art party locavore wolf cliche high life echo park Austin. Cred vinyl keffiyeh DIY salvia PBR, banh mi before they sold out farm-to-table VHS viral locavore cosby sweater. Lomo wolf viral, mustache readymade thundercats keffiyeh craft beer marfa ethical. Wolf salvia freegan, sartorial keffiyeh echo park vegan.

-

Trust fund seitan letterpress, keytar raw denim keffiyeh etsy art party before they sold out master cleanse gluten-free squid scenester freegan cosby sweater. Fanny pack portland seitan DIY, art party locavore wolf cliche high life echo park Austin. Cred vinyl keffiyeh DIY salvia PBR, banh mi before they sold out farm-to-table VHS viral locavore cosby sweater. Lomo wolf viral, mustache readymade thundercats keffiyeh craft beer marfa ethical. Wolf salvia freegan, sartorial keffiyeh echo park vegan.

-
-

Tabs with list-group (with fade)

+

Tabs with list-group (vertical, with fade)

-
+
Home Profile Messages From 76f87e2bef6c1db6ccbe51640549f907160f5953 Mon Sep 17 00:00:00 2001 From: Mark Otto Date: Tue, 9 Jul 2019 14:30:07 -0500 Subject: [PATCH 27/35] Fixes #28994 --- site/content/docs/4.3/components/forms.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/content/docs/4.3/components/forms.md b/site/content/docs/4.3/components/forms.md index 659385a738cf..83564dd22f75 100644 --- a/site/content/docs/4.3/components/forms.md +++ b/site/content/docs/4.3/components/forms.md @@ -419,7 +419,7 @@ At times, you maybe need to use margin or padding utilities to create that perfe
-
+
From 349eeeb532181517f7a9bb72ae4a713517f57b5b Mon Sep 17 00:00:00 2001 From: XhmikosR Date: Wed, 10 Jul 2019 10:45:27 +0300 Subject: [PATCH 28/35] Fix typo. (#29008) --- site/content/docs/4.3/components/modal.md | 2 +- site/content/docs/4.3/components/popovers.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/site/content/docs/4.3/components/modal.md b/site/content/docs/4.3/components/modal.md index df969bb4ca78..08fd00fd0814 100644 --- a/site/content/docs/4.3/components/modal.md +++ b/site/content/docs/4.3/components/modal.md @@ -826,7 +826,7 @@ Destroys an element's modal. {{< highlight js >}} var myModalEl = document.getElementById('myModal') -var modal = bootstrap.Modal._getInstance(myModalEl) // Return a Bootstrap modal instance +var modal = bootstrap.Modal._getInstance(myModalEl) // Returns a Bootstrap modal instance {{< /highlight >}} ### Events diff --git a/site/content/docs/4.3/components/popovers.md b/site/content/docs/4.3/components/popovers.md index fc82c3c6bff8..fa2d7c6468a8 100644 --- a/site/content/docs/4.3/components/popovers.md +++ b/site/content/docs/4.3/components/popovers.md @@ -351,7 +351,7 @@ Updates the position of an element's popover. {{< highlight js >}} var exampleTriggerEl = document.getElementById('example') -var popover = bootstrap.Popover._getInstance(exampleTriggerEl) // Return a Bootstrap popover instance +var popover = bootstrap.Popover._getInstance(exampleTriggerEl) // Returns a Bootstrap popover instance {{< /highlight >}} ### Events From e81f20cb0e8034ae987a389ea9e66df60c1f74c8 Mon Sep 17 00:00:00 2001 From: Johann-S Date: Tue, 9 Jul 2019 10:19:00 +0200 Subject: [PATCH 29/35] separate file for our polyfills to have lighter plugins --- build/build-plugins.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/build/build-plugins.js b/build/build-plugins.js index 33574b888837..3d570cd369fe 100644 --- a/build/build-plugins.js +++ b/build/build-plugins.js @@ -31,6 +31,7 @@ const bsPlugins = { Data: path.resolve(__dirname, '../js/src/dom/data.js'), EventHandler: path.resolve(__dirname, '../js/src/dom/event-handler.js'), Manipulator: path.resolve(__dirname, '../js/src/dom/manipulator.js'), + Polyfill: path.resolve(__dirname, '../js/src/dom/polyfill.js'), SelectorEngine: path.resolve(__dirname, '../js/src/dom/selector-engine.js'), Alert: path.resolve(__dirname, '../js/src/alert.js'), Button: path.resolve(__dirname, '../js/src/button.js'), @@ -69,13 +70,16 @@ function getConfigByPluginKey(pluginKey) { pluginKey === 'Data' || pluginKey === 'Manipulator' || pluginKey === 'EventHandler' || + pluginKey === 'Polyfill' || pluginKey === 'SelectorEngine' || pluginKey === 'Util' || pluginKey === 'Sanitizer' ) { return { - external: [], - globals: {} + external: [bsPlugins.Polyfill], + globals: { + [bsPlugins.Polyfill]: 'Polyfill' + } } } @@ -144,6 +148,7 @@ const domObjects = [ 'Data', 'EventHandler', 'Manipulator', + 'Polyfill', 'SelectorEngine' ] From bfe5438af2b53a6a3d4d14ff477073de45b669cb Mon Sep 17 00:00:00 2001 From: Martijn Cuppens Date: Fri, 12 Jul 2019 12:15:50 +0200 Subject: [PATCH 30/35] Remove attribute selectors (#28988) --- scss/_buttons.scss | 9 --------- 1 file changed, 9 deletions(-) diff --git a/scss/_buttons.scss b/scss/_buttons.scss index 2adce28b47f6..1fbcd0f863d2 100644 --- a/scss/_buttons.scss +++ b/scss/_buttons.scss @@ -126,12 +126,3 @@ fieldset:disabled a.btn { margin-top: $btn-block-spacing-y; } } - -// Specificity overrides -input[type="submit"], -input[type="reset"], -input[type="button"] { - &.btn-block { - width: 100%; - } -} From cc5353da66af36c92c6ac5edc511104999115383 Mon Sep 17 00:00:00 2001 From: Shohei Yoshida Date: Fri, 12 Jul 2019 19:23:43 +0900 Subject: [PATCH 31/35] Drop support for .form-control-plaintext inside .input-group (#28972) * Revert "Fix readonly-plain-text with button addon (#25871)" This reverts commit ff40e00323f9256d8dfb678d13b57bfb01ac7354. * Add migration comment --- scss/_input-group.scss | 1 - site/content/docs/4.3/migration.md | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/scss/_input-group.scss b/scss/_input-group.scss index 082f0464c550..c4daddf0c42b 100644 --- a/scss/_input-group.scss +++ b/scss/_input-group.scss @@ -12,7 +12,6 @@ width: 100%; > .form-control, - > .form-control-plaintext, > .custom-select, > .custom-file { position: relative; // For focus state's z-index diff --git a/site/content/docs/4.3/migration.md b/site/content/docs/4.3/migration.md index 3db2a451fb08..7a91b3daf675 100644 --- a/site/content/docs/4.3/migration.md +++ b/site/content/docs/4.3/migration.md @@ -56,6 +56,7 @@ Changes to Reboot, typography, tables, and more. ## Forms +- Dropped support for `.form-control-plaintext` inside `.input-group` - **Todo:** Move forms documentation to it's own top-level section - **Todo:** Rearrange source Sass files (under `scss/forms/`) - **Todo:** Combine native and custom checkboxes and radios From 79d538703c8fbdc27eff5aa9202a4c718e1c6522 Mon Sep 17 00:00:00 2001 From: Mark Otto Date: Fri, 12 Jul 2019 16:52:33 -0500 Subject: [PATCH 32/35] v5: Forms update (#28450) * Initial spike of consolidated form checks * Stub out forms rearrangement - Prepping to drop non-custom file and range inputs - Prepping to merge custom and native checks and radios (with switches) - Prepping to merge custom select with form select - Moving docs arround so forms has it's own area given volume of CSS * Move input group Sass file to forms subdir * Start to split and move the docs around * Simpler imports * Copyediting * delete overview file * Remove commented out code * remove the custom-forms import * rewrite flex-check as form-check, replace all custom properties * Remove old forms doc * stub out new subpage link section * update migration guide * Update nav, forms overview in page nav, and descriptions * fix check bg position * fix margin-top calculation * rename .custom-select to .form-select * Update validation styles for new checks * add some vertical margin, fix inline checks * fix docs examples * better way to do this contents stuff, redo the toc while i'm at it * page restyle for docs while here * un-callout that, edit text * redo padding on toc * fix toc * start to cleanup checks docs * Rewrite Markdown tables into HTML * Redesign tables, redo their docs * Replace Open Iconic icons with custom Bootstrap icons * Redesign the docs navbar, add a subheader, redo the sidebar * Redesign docs homepage a bit * Simplify table style overrides for docs tables * Simplify docs typography for page titles and reading line length * Stub out icons page * Part of sidebar update, remove migration from nav.yml * Move toc CSS to separate partial * Change appearance of overview page * fix sidebar arrow direction * Add footer to docs layout * Update descriptions * Drop the .form-group class for margin utilities * Remove lingering form-group-margin-bottom var * improve footer spacing * add headings to range page * uncomment form range css * Rename .custom-range to .form-range * Drop unused docs var * Uncomment the comment * Remove unused variable * Fix radio image sizing * Reboot update: reset horizontal ul and ol padding * de-dupe IDs * tweak toc styles * nvm, fix dropdown versions stuff * remove sidebar nav toggle for now * broken html * fix more broken html, move css * scss linting * comment out broken helper docs * scope styles * scope styles * Fixes #25540 and fixes #26407 for v5 only * Update sidebar once more * Match new sidenav order * fix syntax error * Rename custom-file to form-file, update paths, update migration docs for previous changes in #28696 * rename back * fix size and alignment * rename that back too --- scss/_custom-forms.scss | 514 ------- scss/_forms.scss | 343 +---- scss/_reboot.scss | 6 + scss/_tables.scss | 8 +- scss/_variables.scss | 260 ++-- scss/bootstrap.scss | 2 - scss/forms/_form-check.scss | 116 ++ scss/forms/_form-control.scss | 115 ++ scss/forms/_form-file.scss | 72 + scss/forms/_form-range.scss | 142 ++ scss/forms/_form-select.scss | 76 + scss/{ => forms}/_input-group.scss | 36 +- scss/forms/_labels.scss | 27 + scss/forms/_layout.scss | 101 ++ scss/forms/_validation.scss | 10 + scss/mixins/_forms.scss | 55 +- site/content/docs/4.3/components/alerts.md | 65 +- site/content/docs/4.3/components/buttons.md | 30 +- site/content/docs/4.3/components/carousel.md | 63 +- site/content/docs/4.3/components/collapse.md | 43 +- site/content/docs/4.3/components/dropdowns.md | 108 +- site/content/docs/4.3/components/forms.md | 1319 ----------------- site/content/docs/4.3/components/icons.md | 21 + .../content/docs/4.3/components/list-group.md | 2 +- site/content/docs/4.3/components/modal.md | 8 +- site/content/docs/4.3/components/navs.md | 4 +- site/content/docs/4.3/components/popovers.md | 6 +- site/content/docs/4.3/components/scrollspy.md | 6 +- site/content/docs/4.3/components/toasts.md | 6 +- site/content/docs/4.3/components/tooltips.md | 6 +- site/content/docs/4.3/content/reboot.md | 2 +- site/content/docs/4.3/content/tables.md | 198 ++- site/content/docs/4.3/forms/checks.md | 217 +++ site/content/docs/4.3/forms/file.md | 37 + site/content/docs/4.3/forms/form-control.md | 75 + .../4.3/{components => forms}/input-group.md | 50 +- site/content/docs/4.3/forms/layout.md | 388 +++++ site/content/docs/4.3/forms/overview.md | 123 ++ site/content/docs/4.3/forms/range.md | 34 + site/content/docs/4.3/forms/select.md | 62 + site/content/docs/4.3/forms/validation.md | 404 +++++ .../4.3/getting-started/browsers-devices.md | 64 +- .../docs/4.3/getting-started/build-tools.md | 53 +- .../docs/4.3/getting-started/contents.md | 32 +- site/content/docs/4.3/layout/grid.md | 24 +- site/content/docs/4.3/migration.md | 23 +- site/content/docs/4.3/utilities/display.md | 72 +- site/data/nav.yml | 21 +- site/layouts/_default/docs.html | 50 +- site/layouts/_default/single.html | 25 +- site/layouts/partials/docs-navbar.html | 24 +- site/layouts/partials/docs-search.html | 3 + site/layouts/partials/docs-sidebar.html | 31 +- site/layouts/partials/docs-versions.html | 19 + site/layouts/partials/footer.html | 4 +- .../partials/home/masthead-followup.html | 119 +- site/layouts/partials/home/masthead.html | 32 +- .../partials/icons/booticon-chevron-right.svg | 1 + .../partials/icons/bootstrap-logo-solid.svg | 1 + .../docs/4.3/assets/js/src/application.js | 14 + .../static/docs/4.3/assets/scss/_content.scss | 40 +- .../docs/4.3/assets/scss/_masthead.scss | 42 +- site/static/docs/4.3/assets/scss/_nav.scss | 49 +- .../static/docs/4.3/assets/scss/_sidebar.scss | 151 +- site/static/docs/4.3/assets/scss/_toc.scss | 25 + site/static/docs/4.3/assets/scss/docs.scss | 15 + 66 files changed, 3169 insertions(+), 2925 deletions(-) delete mode 100644 scss/_custom-forms.scss create mode 100644 scss/forms/_form-check.scss create mode 100644 scss/forms/_form-control.scss create mode 100644 scss/forms/_form-file.scss create mode 100644 scss/forms/_form-range.scss create mode 100644 scss/forms/_form-select.scss rename scss/{ => forms}/_input-group.scss (87%) create mode 100644 scss/forms/_labels.scss create mode 100644 scss/forms/_layout.scss create mode 100644 scss/forms/_validation.scss delete mode 100644 site/content/docs/4.3/components/forms.md create mode 100644 site/content/docs/4.3/components/icons.md create mode 100644 site/content/docs/4.3/forms/checks.md create mode 100644 site/content/docs/4.3/forms/file.md create mode 100644 site/content/docs/4.3/forms/form-control.md rename site/content/docs/4.3/{components => forms}/input-group.md (88%) create mode 100644 site/content/docs/4.3/forms/layout.md create mode 100644 site/content/docs/4.3/forms/overview.md create mode 100644 site/content/docs/4.3/forms/range.md create mode 100644 site/content/docs/4.3/forms/select.md create mode 100644 site/content/docs/4.3/forms/validation.md create mode 100644 site/layouts/partials/docs-search.html create mode 100644 site/layouts/partials/docs-versions.html create mode 100644 site/layouts/partials/icons/booticon-chevron-right.svg create mode 100644 site/layouts/partials/icons/bootstrap-logo-solid.svg create mode 100644 site/static/docs/4.3/assets/scss/_toc.scss diff --git a/scss/_custom-forms.scss b/scss/_custom-forms.scss deleted file mode 100644 index ce49f3060aff..000000000000 --- a/scss/_custom-forms.scss +++ /dev/null @@ -1,514 +0,0 @@ -// Embedded icons from Open Iconic. -// Released under MIT and copyright 2014 Waybury. -// https://useiconic.com/open - - -// Checkboxes and radios -// -// Base class takes care of all the key behavioral aspects. - -.custom-control { - position: relative; - display: block; - min-height: $font-size-base * $line-height-base; - padding-left: $custom-control-gutter + $custom-control-indicator-size; -} - -.custom-control-inline { - display: inline-flex; - margin-right: $custom-control-spacer-x; -} - -.custom-control-input { - position: absolute; - z-index: -1; // Put the input behind the label so it doesn't overlay text - opacity: 0; - - &:checked ~ .custom-control-label::before { - color: $custom-control-indicator-checked-color; - border-color: $custom-control-indicator-checked-border-color; - @include gradient-bg($custom-control-indicator-checked-bg); - @include box-shadow($custom-control-indicator-checked-box-shadow); - } - - &:focus ~ .custom-control-label::before { - // the mixin is not used here to make sure there is feedback - @if $enable-shadows { - box-shadow: $input-box-shadow, $input-focus-box-shadow; - } @else { - box-shadow: $custom-control-indicator-focus-box-shadow; - } - } - - &:focus:not(:checked) ~ .custom-control-label::before { - border-color: $custom-control-indicator-focus-border-color; - } - - &:not(:disabled):active ~ .custom-control-label::before { - color: $custom-control-indicator-active-color; - background-color: $custom-control-indicator-active-bg; - border-color: $custom-control-indicator-active-border-color; - @include box-shadow($custom-control-indicator-active-box-shadow); - } - - // Use disabled attribute instead of :disabled pseudo-class - // Workaround for: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/11295231 - &[disabled] { - ~ .custom-control-label { - color: $custom-control-label-disabled-color; - - &::before { - background-color: $custom-control-indicator-disabled-bg; - } - } - } -} - -// Custom control indicators -// -// Build the custom controls out of pseudo-elements. - -.custom-control-label { - position: relative; - margin-bottom: 0; - color: $custom-control-label-color; - vertical-align: top; - - // Background-color and (when enabled) gradient - &::before { - position: absolute; - top: ($font-size-base * $line-height-base - $custom-control-indicator-size) / 2; - left: -($custom-control-gutter + $custom-control-indicator-size); - display: block; - width: $custom-control-indicator-size; - height: $custom-control-indicator-size; - pointer-events: none; - content: ""; - background-color: $custom-control-indicator-bg; - border: $custom-control-indicator-border-color solid $custom-control-indicator-border-width; - @include box-shadow($custom-control-indicator-box-shadow); - } - - // Foreground (icon) - &::after { - position: absolute; - top: ($font-size-base * $line-height-base - $custom-control-indicator-size) / 2; - left: -($custom-control-gutter + $custom-control-indicator-size); - display: block; - width: $custom-control-indicator-size; - height: $custom-control-indicator-size; - content: ""; - background: no-repeat 50% / #{$custom-control-indicator-bg-size}; - } -} - - -// Checkboxes -// -// Tweak just a few things for checkboxes. - -.custom-checkbox { - .custom-control-label::before { - @include border-radius($custom-checkbox-indicator-border-radius); - } - - .custom-control-input:checked ~ .custom-control-label { - &::after { - background-image: $custom-checkbox-indicator-icon-checked; - } - } - - .custom-control-input:indeterminate ~ .custom-control-label { - &::before { - border-color: $custom-checkbox-indicator-indeterminate-border-color; - @include gradient-bg($custom-checkbox-indicator-indeterminate-bg); - @include box-shadow($custom-checkbox-indicator-indeterminate-box-shadow); - } - &::after { - background-image: $custom-checkbox-indicator-icon-indeterminate; - } - } - - .custom-control-input:disabled { - &:checked ~ .custom-control-label::before { - background-color: $custom-control-indicator-checked-disabled-bg; - } - &:indeterminate ~ .custom-control-label::before { - background-color: $custom-control-indicator-checked-disabled-bg; - } - } -} - -// Radios -// -// Tweak just a few things for radios. - -.custom-radio { - .custom-control-label::before { - // stylelint-disable-next-line property-blacklist - border-radius: $custom-radio-indicator-border-radius; - } - - .custom-control-input:checked ~ .custom-control-label { - &::after { - background-image: $custom-radio-indicator-icon-checked; - } - } - - .custom-control-input:disabled { - &:checked ~ .custom-control-label::before { - background-color: $custom-control-indicator-checked-disabled-bg; - } - } -} - - -// switches -// -// Tweak a few things for switches - -.custom-switch { - padding-left: $custom-switch-width + $custom-control-gutter; - - .custom-control-label { - &::before { - left: -($custom-switch-width + $custom-control-gutter); - width: $custom-switch-width; - pointer-events: all; - // stylelint-disable-next-line property-blacklist - border-radius: $custom-switch-indicator-border-radius; - } - - &::after { - top: calc(#{(($font-size-base * $line-height-base - $custom-control-indicator-size) / 2)} + #{$custom-control-indicator-border-width * 2}); - left: calc(#{-($custom-switch-width + $custom-control-gutter)} + #{$custom-control-indicator-border-width * 2}); - width: $custom-switch-indicator-size; - height: $custom-switch-indicator-size; - background-color: $custom-control-indicator-border-color; - // stylelint-disable-next-line property-blacklist - border-radius: $custom-switch-indicator-border-radius; - @include transition(transform .15s ease-in-out, $custom-forms-transition); - } - } - - .custom-control-input:checked ~ .custom-control-label { - &::after { - background-color: $custom-control-indicator-bg; - transform: translateX($custom-switch-width - $custom-control-indicator-size); - } - } - - .custom-control-input:disabled { - &:checked ~ .custom-control-label::before { - background-color: $custom-control-indicator-checked-disabled-bg; - } - } -} - - -// Select -// -// Replaces the browser default select with a custom one, mostly pulled from -// https://primer.github.io/. -// - -.custom-select { - display: inline-block; - width: 100%; - height: $custom-select-height; - padding: $custom-select-padding-y ($custom-select-padding-x + $custom-select-indicator-padding) $custom-select-padding-y $custom-select-padding-x; - font-family: $custom-select-font-family; - @include font-size($custom-select-font-size); - font-weight: $custom-select-font-weight; - line-height: $custom-select-line-height; - color: $custom-select-color; - vertical-align: middle; - background: $custom-select-background; - background-color: $custom-select-bg; - border: $custom-select-border-width solid $custom-select-border-color; - @include border-radius($custom-select-border-radius, 0); - @include box-shadow($custom-select-box-shadow); - appearance: none; - - &:focus { - border-color: $custom-select-focus-border-color; - outline: 0; - @if $enable-shadows { - box-shadow: $custom-select-box-shadow, $custom-select-focus-box-shadow; - } @else { - box-shadow: $custom-select-focus-box-shadow; - } - - &::-ms-value { - // For visual consistency with other platforms/browsers, - // suppress the default white text on blue background highlight given to - // the selected option text when the (still closed) s in some browsers, due to the limited stylability of ``s in IE10+. - &::-ms-expand { - background-color: transparent; - border: 0; - } - - // Customize the `:focus` state to imitate native WebKit styles. - @include form-control-focus($ignore-warning: true); - - // Placeholder - &::placeholder { - color: $input-placeholder-color; - // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526. - opacity: 1; - } - - // Disabled and read-only inputs - // - // HTML5 says that controls under a fieldset > legend:first-child won't be - // disabled if the fieldset is disabled. Due to implementation difficulty, we - // don't honor that edge case; we style them as disabled anyway. - &:disabled, - &[readonly] { - background-color: $input-disabled-bg; - // iOS fix for unreadable disabled content; see https://github.com/twbs/bootstrap/issues/11655. - opacity: 1; - } -} - -select.form-control { - &:focus::-ms-value { - // Suppress the nested default white text on blue background highlight given to - // the selected option text when the (still closed) s in some browsers, due to the limited stylability of ``s in IE10+. + &::-ms-expand { + background-color: transparent; + border: 0; + } + + // Customize the `:focus` state to imitate native WebKit styles. + &:focus { + color: $input-focus-color; + background-color: $input-focus-bg; + border-color: $input-focus-border-color; + outline: 0; + // Avoid using mixin so we can pass custom focus shadow properly + @if $enable-shadows { + box-shadow: $input-box-shadow, $input-focus-box-shadow; + } @else { + box-shadow: $input-focus-box-shadow; + } + } + + // Placeholder + &::placeholder { + color: $input-placeholder-color; + // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526. + opacity: 1; + } + + // Disabled and read-only inputs + // + // HTML5 says that controls under a fieldset > legend:first-child won't be + // disabled if the fieldset is disabled. Due to implementation difficulty, we + // don't honor that edge case; we style them as disabled anyway. + &:disabled, + &[readonly] { + background-color: $input-disabled-bg; + // iOS fix for unreadable disabled content; see https://github.com/twbs/bootstrap/issues/11655. + opacity: 1; + } +} + +// Readonly controls as plain text +// +// Apply class to a readonly input to make it appear like regular plain +// text (without any border, background color, focus indicator) + +.form-control-plaintext { + display: block; + width: 100%; + padding: $input-padding-y 0; + margin-bottom: 0; // match inputs if this class comes on inputs with default margins + line-height: $input-line-height; + color: $input-plaintext-color; + background-color: transparent; + border: solid transparent; + border-width: $input-border-width 0; + + &.form-control-sm, + &.form-control-lg { + padding-right: 0; + padding-left: 0; + } +} + +// Form control sizing +// +// Build on `.form-control` with modifier classes to decrease or increase the +// height and font-size of form controls. +// +// Repeated in `_input_group.scss` to avoid Sass extend issues. + +.form-control-sm { + height: $input-height-sm; + padding: $input-padding-y-sm $input-padding-x-sm; + @include font-size($input-font-size-sm); + line-height: $input-line-height-sm; + @include border-radius($input-border-radius-sm); +} + +.form-control-lg { + height: $input-height-lg; + padding: $input-padding-y-lg $input-padding-x-lg; + @include font-size($input-font-size-lg); + line-height: $input-line-height-lg; + @include border-radius($input-border-radius-lg); +} + +textarea.form-control { + height: auto; +} diff --git a/scss/forms/_form-file.scss b/scss/forms/_form-file.scss new file mode 100644 index 000000000000..bbc38a9f81e8 --- /dev/null +++ b/scss/forms/_form-file.scss @@ -0,0 +1,72 @@ +.form-file { + position: relative; + display: inline-block; + width: 100%; + height: $form-file-height; + margin-bottom: 0; +} + +.form-file-input { + position: relative; + z-index: 2; + width: 100%; + height: $form-file-height; + margin: 0; + opacity: 0; + + &:focus ~ .form-file-label { + border-color: $form-file-focus-border-color; + box-shadow: $form-file-focus-box-shadow; + } + + // Use disabled attribute instead of :disabled pseudo-class + // Workaround for: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/11295231 + &[disabled] ~ .form-file-label { + background-color: $form-file-disabled-bg; + } +} + +.form-file-label { + position: absolute; + top: 0; + right: 0; + left: 0; + z-index: 1; + display: flex; + height: $form-file-height; + border-color: $form-file-border-color; + @include border-radius($form-file-border-radius); + @include box-shadow($form-file-box-shadow); +} + +.form-file-text { + display: block; + flex-grow: 1; + padding: $form-file-padding-y $form-file-padding-x; + overflow: hidden; + font-family: $form-file-font-family; + font-weight: $form-file-font-weight; + line-height: $form-file-line-height; + color: $form-file-color; + text-overflow: ellipsis; + white-space: nowrap; + background-color: $form-file-bg; + border-color: inherit; + border-style: solid; + border-width: $form-file-border-width; + @include border-left-radius(inherit); +} + +.form-file-button { + display: block; + flex-shrink: 0; + padding: $form-file-padding-y $form-file-padding-x; + margin-left: -$form-file-border-width; + line-height: $form-file-line-height; + color: $form-file-button-color; + @include gradient-bg($form-file-button-bg); + border-color: inherit; + border-style: solid; + border-width: $form-file-border-width; + @include border-right-radius(inherit); +} diff --git a/scss/forms/_form-range.scss b/scss/forms/_form-range.scss new file mode 100644 index 000000000000..b54d4d6aafbd --- /dev/null +++ b/scss/forms/_form-range.scss @@ -0,0 +1,142 @@ +// Range +// +// Style range inputs the same across browsers. Vendor-specific rules for pseudo +// elements cannot be mixed. As such, there are no shared styles for focus or +// active states on prefixed selectors. + +.form-range { + width: 100%; + height: calc(#{$form-range-thumb-height} + #{$form-range-thumb-focus-box-shadow-width * 2}); + padding: 0; // Need to reset padding + background-color: transparent; + appearance: none; + + &:focus { + outline: none; + + // Pseudo-elements must be split across multiple rulesets to have an effect. + // No box-shadow() mixin for focus accessibility. + &::-webkit-slider-thumb { box-shadow: $form-range-thumb-focus-box-shadow; } + &::-moz-range-thumb { box-shadow: $form-range-thumb-focus-box-shadow; } + &::-ms-thumb { box-shadow: $form-range-thumb-focus-box-shadow; } + } + + &::-moz-focus-outer { + border: 0; + } + + &::-webkit-slider-thumb { + width: $form-range-thumb-width; + height: $form-range-thumb-height; + margin-top: ($form-range-track-height - $form-range-thumb-height) / 2; // Webkit specific + @include gradient-bg($form-range-thumb-bg); + border: $form-range-thumb-border; + @include border-radius($form-range-thumb-border-radius); + @include box-shadow($form-range-thumb-box-shadow); + @include transition($custom-forms-transition); + appearance: none; + + &:active { + @include gradient-bg($form-range-thumb-active-bg); + } + } + + &::-webkit-slider-runnable-track { + width: $form-range-track-width; + height: $form-range-track-height; + color: transparent; // Why? + cursor: $form-range-track-cursor; + background-color: $form-range-track-bg; + border-color: transparent; + @include border-radius($form-range-track-border-radius); + @include box-shadow($form-range-track-box-shadow); + } + + &::-moz-range-thumb { + width: $form-range-thumb-width; + height: $form-range-thumb-height; + @include gradient-bg($form-range-thumb-bg); + border: $form-range-thumb-border; + @include border-radius($form-range-thumb-border-radius); + @include box-shadow($form-range-thumb-box-shadow); + @include transition($custom-forms-transition); + appearance: none; + + &:active { + @include gradient-bg($form-range-thumb-active-bg); + } + } + + &::-moz-range-track { + width: $form-range-track-width; + height: $form-range-track-height; + color: transparent; + cursor: $form-range-track-cursor; + background-color: $form-range-track-bg; + border-color: transparent; // Firefox specific? + @include border-radius($form-range-track-border-radius); + @include box-shadow($form-range-track-box-shadow); + } + + &::-ms-thumb { + width: $form-range-thumb-width; + height: $form-range-thumb-height; + margin-top: 0; // Edge specific + margin-right: $form-range-thumb-focus-box-shadow-width; // Workaround that overflowed box-shadow is hidden. + margin-left: $form-range-thumb-focus-box-shadow-width; // Workaround that overflowed box-shadow is hidden. + @include gradient-bg($form-range-thumb-bg); + border: $form-range-thumb-border; + @include border-radius($form-range-thumb-border-radius); + @include box-shadow($form-range-thumb-box-shadow); + @include transition($custom-forms-transition); + appearance: none; + + &:active { + @include gradient-bg($form-range-thumb-active-bg); + } + } + + &::-ms-track { + width: $form-range-track-width; + height: $form-range-track-height; + color: transparent; + cursor: $form-range-track-cursor; + background-color: transparent; + border-color: transparent; + border-width: $form-range-thumb-height / 2; + @include box-shadow($form-range-track-box-shadow); + } + + &::-ms-fill-lower { + background-color: $form-range-track-bg; + @include border-radius($form-range-track-border-radius); + } + + &::-ms-fill-upper { + margin-right: 15px; // arbitrary? + background-color: $form-range-track-bg; + @include border-radius($form-range-track-border-radius); + } + + &:disabled { + &::-webkit-slider-thumb { + background-color: $form-range-thumb-disabled-bg; + } + + &::-webkit-slider-runnable-track { + cursor: default; + } + + &::-moz-range-thumb { + background-color: $form-range-thumb-disabled-bg; + } + + &::-moz-range-track { + cursor: default; + } + + &::-ms-thumb { + background-color: $form-range-thumb-disabled-bg; + } + } +} diff --git a/scss/forms/_form-select.scss b/scss/forms/_form-select.scss new file mode 100644 index 000000000000..e0b69c04f7b2 --- /dev/null +++ b/scss/forms/_form-select.scss @@ -0,0 +1,76 @@ +// Select +// +// Replaces the browser default select with a custom one, mostly pulled from +// https://primer.github.io/. + +.form-select { + display: inline-block; + width: 100%; + height: $form-select-height; + padding: $form-select-padding-y ($form-select-padding-x + $form-select-indicator-padding) $form-select-padding-y $form-select-padding-x; + font-family: $form-select-font-family; + @include font-size($form-select-font-size); + font-weight: $form-select-font-weight; + line-height: $form-select-line-height; + color: $form-select-color; + vertical-align: middle; + background: $form-select-background; + background-color: $form-select-bg; + border: $form-select-border-width solid $form-select-border-color; + @include border-radius($form-select-border-radius, 0); + @include box-shadow($form-select-box-shadow); + appearance: none; + + &:focus { + border-color: $form-select-focus-border-color; + outline: 0; + @if $enable-shadows { + box-shadow: $form-select-box-shadow, $form-select-focus-box-shadow; + } @else { + box-shadow: $form-select-focus-box-shadow; + } + + &::-ms-value { + // For visual consistency with other platforms/browsers, + // suppress the default white text on blue background highlight given to + // the selected option text when the (still closed) - We'll never share your email with anyone else. -
-
- - -
-
- - -
- - -{{< /example >}} - -## Form controls - -Textual form controls—like ``s, ` -
- -{{< /example >}} - -For file inputs, swap the `.form-control` for `.form-control-file`. - -{{< example >}} -
-
- - -
-
-{{< /example >}} - -### Sizing - -Set heights using classes like `.form-control-lg` and `.form-control-sm`. - -{{< example >}} - - - -{{< /example >}} - -{{< example >}} - - - -{{< /example >}} - -### Readonly - -Add the `readonly` boolean attribute on an input to prevent modification of the input's value. Read-only inputs appear lighter (just like disabled inputs), but retain the standard cursor. - -{{< example >}} - -{{< /example >}} - -### Readonly plain text - -If you want to have `` elements in your form styled as plain text, use the `.form-control-plaintext` class to remove the default form field styling and preserve the correct margin and padding. - -{{< example >}} -
-
- -
- -
-
-
- -
- -
-
-
-{{< /example >}} - -{{< example >}} -
-
- - -
-
- - -
- -
-{{< /example >}} - -## Range Inputs - -Set horizontally scrollable range inputs using `.form-control-range`. - -{{< example >}} -
-
- - -
-
-{{< /example >}} - -## Checkboxes and radios - -Default checkboxes and radios are improved upon with the help of `.form-check`, **a single class for both input types that improves the layout and behavior of their HTML elements**. Checkboxes are for selecting one or several options in a list, while radios are for selecting one option from many. - -Disabled checkboxes and radios are supported. The `disabled` attribute will apply a lighter color to help indicate the input's state. - -Checkboxes and radios use are built to support HTML-based form validation and provide concise, accessible labels. As such, our ``s and `
- @@ -292,7 +292,7 @@ Input groups include support for custom selects and custom file inputs. Browser
- @@ -301,7 +301,7 @@ Input groups include support for custom selects and custom file inputs. Browser
- @@ -320,21 +320,21 @@ Input groups include support for custom selects and custom file inputs. Browser
Upload
-
- -
-
- -