Compare commits

...

23 Commits

Author SHA1 Message Date
GeoSot 7a4a3e9fe1 add docs & some changes 2022-10-08 00:14:32 +03:00
Patrick H. Lauke 27e5375912 Update documentation text
* remove the warning about custom errors and tooltips not being accessible ... they now mostly are
* change the phrasing for server-side validation, so that *all* feedback (whether valid or invalid) should have `aria-describedby`
2022-10-08 00:06:56 +03:00
GeoSot aa6a1ece56 Make a form validation handler | handle form messages
add "aria-describedby" attribute on "supported elements" section

add "aria-describedby" attribute on server side succeed validation messages
2022-10-08 00:06:56 +03:00
GeoSot 4cb046a6b8 Boost execute function, being able to handle arguments (#36652) 2022-10-07 15:25:00 +03:00
Daniel Raymond 708a3a0e39 Utilities for overflow and object fit (#36848)
* Added "overflow-x" and "overflow-y"

- Having the same properties as overflow but for just the x and y axises
- Usecase being I want my y axis to be scrollable but not my x axis
- E.g a card with a vertical list of items.

* Added "object-fit" utilities

- The CSS object-fit property is used to specify how an <img> or <video> should be resized to fit its container.
- A responsive alternative to using background-img for a resizable fill/fit image.

* Updated documantation for the overflow utilities

- Now includes docs for `overflow-x` and `overflow-y` utilities

* Placeholder shortcode updated

- Can now choose to render an img tag or svg
- The image contains a base64 svg generated within the template
- example shortcode updated to detect, replace and render preview of 'img' tags as well

* New documentaion for Object Fit added

- Documentation added for the 'object-fit' util

* Updated spell checks issues

* Update object-fit.md

* Update overflow.md

* Update object-fit.md

* Updated markup to address HTML Validation Errors

- error: Bad value  for attribute "src" on element "img": Illegal character in scheme data: space is not allowed.
- info warning: Self-closing tag syntax in text/html documents is widely discouraged; it's unnecessary and interacts badly with other HTML features (e.g., unquoted attribute values). If you're using a tool that injects self-closing tag syntax into all void elements, without any option to prevent it from doing so, then consider switching to a different tool.

* Updated Fix

- Added Legibility to the img markup (example.html)
- Fixed issue with example not working properly (because image closing tag no longer has "/>" )

* update values by step of 0.25 in bundlewatch.config

The following values in .bundlewatch.config.json have been updated:
- ./dist/css/bootstrap-utilities.css
- ./dist/css/bootstrap-utilities.min.css
- ./dist/css/bootstrap.css
- ./dist/css/bootstrap.min.css

Co-authored-by: Daniel O <dobiekwe@byteworks.com.ng>
Co-authored-by: Mark Otto <otto@github.com>
2022-10-06 13:14:11 -07:00
João Tomás 4822984e19 Fix button hover color in cover example
Replaced .btn-secondary class with .btn-light
2022-10-06 13:12:09 -07:00
Gaël Poupard 5029370a10 fix(carousel): RTL translate() direction
Trying to fix #37180
2022-10-06 13:07:49 -07:00
Julien Déramond 9936ed48d7 Add $enable-important-utilities condition in colored links 2022-10-06 13:04:43 -07:00
Julien Déramond 8291746dd4 Rename examples CSS/JS files for consistency 2022-10-06 13:03:55 -07:00
GeoSot 01dc2f5100 fix tooltip/popper disposal inconsistencies (#37235) 2022-10-06 11:31:38 +03:00
Louis-Maxime Piton bf6240dad9 Add an artificial background to floating labels (#37125) 2022-10-06 08:12:00 +02:00
Neeraj Kumar Das 5975ca65c5 Remove duplicate --#{$prefix}offcanvas-zindex (#37257) 2022-10-04 13:38:07 +02:00
franko553 ebf053b792 Correct typo in documentation for hiding elements (#37250) 2022-10-04 09:24:52 +03:00
Julien Déramond b1185b91ea Add new border-radius utilities (#36540)
* Add new border-radius utilities

* Fix bundlewatch

* Fix bundlewatch again

Co-authored-by: Mark Otto <markdotto@gmail.com>
2022-10-03 11:52:41 -07:00
maks fffe0553c2 Add parameters to the caret mixin 2022-10-03 11:19:56 -07:00
Mark Otto ca067371c4 Update bundlewatch 2022-10-03 11:13:36 -07:00
Stefan Haack 2fa7aa0c18 Added breakpoints as css variables (#36095) 2022-10-03 11:04:19 -07:00
maks 9a582767c6 add font-weight-medium(=500) / fw-medium (#36781)
* add font-weight-medium = 500

* Update _utilities.scss
2022-10-03 10:52:02 -07:00
Isabelle Chanclou 838debaad2 Add new css vars for Offcanvas (#36815)
* Add 3 new css vars for Offcanvas feature

* Fix new css variable after review

* Update _offcanvas.scss

Co-authored-by: Mark Otto <otto@github.com>
2022-10-03 10:51:30 -07:00
Vino Rodrigues 18b99f7387 color css vars for .btn-close 2022-10-03 10:48:22 -07:00
Vino Rodrigues 3b95c311ea Add color CSS vars for .alert-link (#36456)
* color css vars for .alert-link

* Update scss/mixins/_alert.scss

Co-authored-by: Mark Otto <otto@github.com>
2022-10-03 10:47:51 -07:00
Julien Déramond 1b3c38d2cd Rename some vars in tab unit tests for consistency (#37248) 2022-10-03 17:44:37 +03:00
Christian Oliff b2f5cf9c43 Minor grammatical fixes
REF:
https://en.wikipedia.org/wiki/Ajax_(programming)
https://en.wikipedia.org/wiki/Server-side
2022-10-03 06:33:52 -07:00
50 changed files with 762 additions and 244 deletions
+11 -11
View File
@@ -6,7 +6,7 @@
},
{
"path": "./dist/css/bootstrap-grid.min.css",
"maxSize": "6.55 kB"
"maxSize": "6.75 kB"
},
{
"path": "./dist/css/bootstrap-reboot.css",
@@ -18,43 +18,43 @@
},
{
"path": "./dist/css/bootstrap-utilities.css",
"maxSize": "8.0 kB"
"maxSize": "8.5 kB"
},
{
"path": "./dist/css/bootstrap-utilities.min.css",
"maxSize": "7.25 kB"
"maxSize": "7.75 kB"
},
{
"path": "./dist/css/bootstrap.css",
"maxSize": "28.75 kB"
"maxSize": "29.5 kB"
},
{
"path": "./dist/css/bootstrap.min.css",
"maxSize": "26.75 kB"
"maxSize": "27.5 kB"
},
{
"path": "./dist/js/bootstrap.bundle.js",
"maxSize": "43.25 kB"
"maxSize": "44.55 kB"
},
{
"path": "./dist/js/bootstrap.bundle.min.js",
"maxSize": "22.75 kB"
"maxSize": "23.75 kB"
},
{
"path": "./dist/js/bootstrap.esm.js",
"maxSize": "28.0 kB"
"maxSize": "29.75 kB"
},
{
"path": "./dist/js/bootstrap.esm.min.js",
"maxSize": "18.5 kB"
"maxSize": "19.25 kB"
},
{
"path": "./dist/js/bootstrap.js",
"maxSize": "28.75 kB"
"maxSize": "30.5 kB"
},
{
"path": "./dist/js/bootstrap.min.js",
"maxSize": "16.25 kB"
"maxSize": "16.75 kB"
}
],
"ci": {
+1
View File
@@ -10,6 +10,7 @@ export { default as Button } from './src/button'
export { default as Carousel } from './src/carousel'
export { default as Collapse } from './src/collapse'
export { default as Dropdown } from './src/dropdown'
export { default as Form } from './src/forms/form'
export { default as Modal } from './src/modal'
export { default as Offcanvas } from './src/offcanvas'
export { default as Popover } from './src/popover'
+2
View File
@@ -9,6 +9,7 @@ import Alert from './src/alert'
import Button from './src/button'
import Carousel from './src/carousel'
import Collapse from './src/collapse'
import Form from './src/forms/form'
import Dropdown from './src/dropdown'
import Modal from './src/modal'
import Offcanvas from './src/offcanvas'
@@ -23,6 +24,7 @@ export default {
Button,
Carousel,
Collapse,
Form,
Dropdown,
Modal,
Offcanvas,
+4
View File
@@ -36,6 +36,10 @@ class BaseComponent extends Config {
}
// Public
getElement() {
return this._element
}
dispose() {
Data.remove(this._element, this.constructor.DATA_KEY)
EventHandler.off(this._element, this.constructor.EVENT_KEY)
+2 -1
View File
@@ -8,6 +8,7 @@
import * as Popper from '@popperjs/core'
import {
defineJQueryPlugin,
execute,
getElement,
getNextActiveElement,
isDisabled,
@@ -319,7 +320,7 @@ class Dropdown extends BaseComponent {
return {
...defaultBsPopperConfig,
...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig)
...execute(this._config.popperConfig, [defaultBsPopperConfig])
}
}
+125
View File
@@ -0,0 +1,125 @@
/**
* --------------------------------------------------------------------------
* Bootstrap (v5.3.0): forms/field.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
import { getUID, isElement } from '../util/index'
import EventHandler from '../dom/event-handler'
import BaseComponent from '../base-component'
import SelectorEngine from '../dom/selector-engine'
import TemplateFactory from '../util/template-factory'
const NAME = 'formField'
const DATA_KEY = 'bs.field'
const EVENT_KEY = `.${DATA_KEY}`
const EVENT_INPUT = `input${EVENT_KEY}`
const CLASS_FIELD_ERROR = 'is-invalid'
const CLASS_FIELD_SUCCESS = 'is-valid'
const ARIA_DESCRIBED_BY = 'aria-describedby'
const Default = {
invalid: '', // invalid message to append
valid: '', // valid message to append
type: 'feedback' // or tooltip
}
const DefaultType = {
invalid: 'string',
valid: 'string',
type: 'string'
}
const MessageTypes = {
ERROR: { prefix: 'invalid', class: CLASS_FIELD_ERROR },
INFO: { prefix: 'info', class: '' },
SUCCESS: { prefix: 'valid', class: CLASS_FIELD_SUCCESS }
}
class FormField extends BaseComponent {
constructor(element, config) {
super(element, config)
if (!isElement(this._element)) {
throw new TypeError(`field "${this._config.name}" not found`)
}
this._tipId = getUID(`${this._config.name}-formTip-`)
this._initialDescribedBy = this._element.getAttribute(ARIA_DESCRIBED_BY) || ''
EventHandler.on(this._element, EVENT_INPUT, () => {
this.clearAppended()
})
}
static get NAME() {
return NAME
}
static get Default() {
return Default
}
static get DefaultType() {
return DefaultType
}
static get MessageTypes() {
return MessageTypes
}
clearAppended() {
const appendedFeedback = SelectorEngine.findOne(`#${this._tipId}`, this._element.parentNode)
if (!appendedFeedback) {
return
}
appendedFeedback.remove()
this._element.classList.remove(CLASS_FIELD_ERROR, CLASS_FIELD_SUCCESS)
if (this._initialDescribedBy) {
this._element.setAttribute(ARIA_DESCRIBED_BY, this._initialDescribedBy)
return
}
this._element.removeAttribute(ARIA_DESCRIBED_BY)
}
appendError(message = this._config.invalid) {
return this.appendFeedback(message, this.constructor.MessageTypes.ERROR)
}
appendSuccess(message = this._config.valid) {
return this.appendFeedback(message, this.constructor.MessageTypes.SUCCESS)
}
appendFeedback(feedback, classes = this.constructor.MessageTypes.INFO) {
if (!feedback) {
return false
}
this.clearAppended()
const config = {
extraClass: `${classes.prefix}-${this._config.type} ${classes.class}`,
content: { div: feedback }
}
feedback = new TemplateFactory(config)
const feedbackElement = feedback.toHtml()
feedbackElement.id = this._tipId
this._element.parentNode.append(feedbackElement)
const describedBy = `${this._initialDescribedBy} ${feedbackElement.id}`.trim()
this._element.setAttribute(ARIA_DESCRIBED_BY, describedBy)
return true
}
name() {
return this._element.name || this._element.id
}
}
export default FormField
+157
View File
@@ -0,0 +1,157 @@
/**
* --------------------------------------------------------------------------
* Bootstrap (v5.3.0): util/form-validation.js
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
* --------------------------------------------------------------------------
*/
import BaseComponent from '../base-component'
import EventHandler from '../dom/event-handler'
import FormField from './form-field'
import SelectorEngine from '../dom/selector-engine'
import { execute } from '../util/index'
const NAME = 'formValidation'
const DATA_KEY = 'bs.formValidation'
const EVENT_KEY = `.${DATA_KEY}`
const EVENT_LOAD_DATA_API = `load${EVENT_KEY}`
const EVENT_SUBMIT = `submit${EVENT_KEY}`
const EVENT_RESET = `reset${EVENT_KEY}`
const CLASS_VALIDATED = 'was-validated'
const SELECTOR_DATA_TOGGLE = 'form[data-bs-toggle="form"]'
const Default = {
type: 'feedback', // or 'tooltip'
validateCallback: null
}
const DefaultType = {
type: 'string',
validateCallback: '(function|null)'
}
class Form extends BaseComponent {
constructor(element, config) {
if (element.tagName !== 'FORM') {
throw new TypeError(`Need to be initialized in form elements. "${element.tagName}" given`)
}
super(element, config)
this._formFields = null // form field instances
}
static get NAME() {
return NAME
}
static get Default() {
return Default
}
static get DefaultType() {
return DefaultType
}
getFields() {
if (!this._formFields) {
this._formFields = this._initializeFields()
}
return this._formFields
}
getField(name) {
return this.getFields().get(name)
}
clear() {
this._element.classList.remove(CLASS_VALIDATED)
// eslint-disable-next-line no-unused-vars
for (const [name, field] of this.getFields()) {
field.clearAppended()
}
}
validate() {
this.clear()
const fetchedErrors = this._fetchErrors()
if (this._element.checkValidity() && !Object.keys(fetchedErrors).length) {
return true
}
for (const [name, field] of this.getFields()) {
this._appendErrorToField(field, fetchedErrors[name] || null)
}
this._element.classList.add(CLASS_VALIDATED)
return false
}
_appendErrorToField(field, givenMessage) {
const element = field.getElement()
if (givenMessage) { // if field is invalid check and return for default message
field.appendError(givenMessage)
return
}
if (element.checkValidity()) { // if field is valid, return first success message
field.appendSuccess()
return
}
if (field.appendError()) { // if field is invalid check and return for default message
return
}
field.appendError(element.validationMessage)
}
_initializeFields() {
const fields = new Map()
const formElements = Array.from(this._element.elements) // the DOM elements
for (const element of formElements) {
const field = FormField.getOrCreateInstance(element, {
type: this._config.type
})
fields.set(field.name(), field)
}
return fields
}
_fetchErrors() {
return execute(this._config.validateCallback, [this], {})
}
}
// On submit we want to auto-validate form
EventHandler.on(document, EVENT_SUBMIT, SELECTOR_DATA_TOGGLE, event => {
const { target } = event
const instance = Form.getOrCreateInstance(target)
if (!target.checkValidity()) {
event.preventDefault()
event.stopPropagation()
}
if (instance.validate()) {
target.submit()
}
})
EventHandler.on(document, EVENT_RESET, SELECTOR_DATA_TOGGLE, event => {
const { target } = event
const instance = Form.getOrCreateInstance(target)
instance.clear()
})
// On load, add `novalidate` attribute to avoid browser validation
EventHandler.on(window, EVENT_LOAD_DATA_API, () => {
for (const el of SelectorEngine.find(SELECTOR_DATA_TOGGLE)) {
el.setAttribute('novalidate', true)
}
})
export default Form
+12 -22
View File
@@ -6,7 +6,7 @@
*/
import * as Popper from '@popperjs/core'
import { defineJQueryPlugin, findShadowRoot, getElement, getUID, isRTL, noop } from './util/index'
import { defineJQueryPlugin, execute, findShadowRoot, getElement, getUID, isRTL, noop } from './util/index'
import { DefaultAllowlist } from './util/sanitizer'
import EventHandler from './dom/event-handler'
import Manipulator from './dom/manipulator'
@@ -172,10 +172,6 @@ class Tooltip extends BaseComponent {
EventHandler.off(this._element.closest(SELECTOR_MODAL), EVENT_MODAL_HIDE, this._hideModalHandler)
if (this.tip) {
this.tip.remove()
}
if (this._element.getAttribute('data-bs-original-title')) {
this._element.setAttribute('title', this._element.getAttribute('data-bs-original-title'))
}
@@ -202,10 +198,7 @@ class Tooltip extends BaseComponent {
}
// todo v6 remove this OR make it optional
if (this.tip) {
this.tip.remove()
this.tip = null
}
this._disposePopper()
const tip = this._getTipElement()
@@ -218,11 +211,7 @@ class Tooltip extends BaseComponent {
EventHandler.trigger(this._element, this.constructor.eventName(EVENT_INSERTED))
}
if (this._popper) {
this._popper.update()
} else {
this._popper = this._createPopper(tip)
}
this._popper = this._createPopper(tip)
tip.classList.add(CLASS_NAME_SHOW)
@@ -281,13 +270,11 @@ class Tooltip extends BaseComponent {
}
if (!this._isHovered) {
tip.remove()
this._disposePopper()
}
this._element.removeAttribute('aria-describedby')
EventHandler.trigger(this._element, this.constructor.eventName(EVENT_HIDDEN))
this._disposePopper()
}
this._queueCallback(complete, this.tip, this._isAnimated())
@@ -383,9 +370,7 @@ class Tooltip extends BaseComponent {
}
_createPopper(tip) {
const placement = typeof this._config.placement === 'function' ?
this._config.placement.call(this, tip, this._element) :
this._config.placement
const placement = execute(this._config.placement, [this, tip, this._element])
const attachment = AttachmentMap[placement.toUpperCase()]
return Popper.createPopper(this._element, tip, this._getPopperConfig(attachment))
}
@@ -405,7 +390,7 @@ class Tooltip extends BaseComponent {
}
_resolvePossibleFunction(arg) {
return typeof arg === 'function' ? arg.call(this._element) : arg
return execute(arg, [this._element])
}
_getPopperConfig(attachment) {
@@ -451,7 +436,7 @@ class Tooltip extends BaseComponent {
return {
...defaultBsPopperConfig,
...(typeof this._config.popperConfig === 'function' ? this._config.popperConfig(defaultBsPopperConfig) : this._config.popperConfig)
...execute(this._config.popperConfig, [defaultBsPopperConfig])
}
}
@@ -612,6 +597,11 @@ class Tooltip extends BaseComponent {
this._popper.destroy()
this._popper = null
}
if (this.tip) {
this.tip.remove()
this.tip = null
}
}
// Static
+2 -4
View File
@@ -249,10 +249,8 @@ const defineJQueryPlugin = plugin => {
})
}
const execute = callback => {
if (typeof callback === 'function') {
callback()
}
const execute = (possibleCallback, args = [], defaultValue = possibleCallback) => {
return typeof possibleCallback === 'function' ? possibleCallback(...args) : defaultValue
}
const executeAfterTransition = (callback, transitionElement, waitForTransition = true) => {
+2 -2
View File
@@ -6,7 +6,7 @@
*/
import { DefaultAllowlist, sanitizeHtml } from './sanitizer'
import { getElement, isElement } from '../util/index'
import { execute, getElement, isElement } from '../util/index'
import SelectorEngine from '../dom/selector-engine'
import Config from './config'
@@ -143,7 +143,7 @@ class TemplateFactory extends Config {
}
_resolvePossibleFunction(arg) {
return typeof arg === 'function' ? arg(this) : arg
return execute(arg, [this])
}
_putElementInTemplate(element, templateElement) {
+9 -9
View File
@@ -603,19 +603,19 @@ describe('Tab', () => {
'</div>'
].join('')
const tabEl = fixtureEl.querySelector('#tab1')
const tabEl1 = fixtureEl.querySelector('#tab1')
const tabEl2 = fixtureEl.querySelector('#tab2')
const tabEl3 = fixtureEl.querySelector('#tab3')
const tabEl4 = fixtureEl.querySelector('#tab4')
const tab = new Tab(tabEl)
const tab1 = new Tab(tabEl1)
const tab2 = new Tab(tabEl2)
const tab3 = new Tab(tabEl3)
const tab4 = new Tab(tabEl4)
const spy1 = spyOn(tab, 'show').and.callThrough()
const spy1 = spyOn(tab1, 'show').and.callThrough()
const spy2 = spyOn(tab2, 'show').and.callThrough()
const spy3 = spyOn(tab3, 'show').and.callThrough()
const spy4 = spyOn(tab4, 'show').and.callThrough()
const spyFocus1 = spyOn(tabEl, 'focus').and.callThrough()
const spyFocus1 = spyOn(tabEl1, 'focus').and.callThrough()
const spyFocus2 = spyOn(tabEl2, 'focus').and.callThrough()
const spyFocus3 = spyOn(tabEl3, 'focus').and.callThrough()
const spyFocus4 = spyOn(tabEl4, 'focus').and.callThrough()
@@ -623,7 +623,7 @@ describe('Tab', () => {
const keydown = createEvent('keydown')
keydown.key = 'ArrowRight'
tabEl.dispatchEvent(keydown)
tabEl1.dispatchEvent(keydown)
expect(spy1).not.toHaveBeenCalled()
expect(spy2).not.toHaveBeenCalled()
expect(spy3).not.toHaveBeenCalled()
@@ -644,19 +644,19 @@ describe('Tab', () => {
'</div>'
].join('')
const tabEl = fixtureEl.querySelector('#tab1')
const tabEl1 = fixtureEl.querySelector('#tab1')
const tabEl2 = fixtureEl.querySelector('#tab2')
const tabEl3 = fixtureEl.querySelector('#tab3')
const tabEl4 = fixtureEl.querySelector('#tab4')
const tab = new Tab(tabEl)
const tab1 = new Tab(tabEl1)
const tab2 = new Tab(tabEl2)
const tab3 = new Tab(tabEl3)
const tab4 = new Tab(tabEl4)
const spy1 = spyOn(tab, 'show').and.callThrough()
const spy1 = spyOn(tab1, 'show').and.callThrough()
const spy2 = spyOn(tab2, 'show').and.callThrough()
const spy3 = spyOn(tab3, 'show').and.callThrough()
const spy4 = spyOn(tab4, 'show').and.callThrough()
const spyFocus1 = spyOn(tabEl, 'focus').and.callThrough()
const spyFocus1 = spyOn(tabEl1, 'focus').and.callThrough()
const spyFocus2 = spyOn(tabEl2, 'focus').and.callThrough()
const spyFocus3 = spyOn(tabEl3, 'focus').and.callThrough()
const spyFocus4 = spyOn(tabEl4, 'focus').and.callThrough()
+19
View File
@@ -631,6 +631,25 @@ describe('Util', () => {
Util.execute(spy)
expect(spy).toHaveBeenCalled()
})
it('should execute if arg is function & return the result', () => {
const functionFoo = (num1, num2 = 10) => num1 + num2
const resultFoo = Util.execute(functionFoo, [4, 5])
expect(resultFoo).toBe(9)
const resultFoo1 = Util.execute(functionFoo, [4])
expect(resultFoo1).toBe(14)
const functionBar = () => 'foo'
const resultBar = Util.execute(functionBar)
expect(resultBar).toBe('foo')
})
it('should not execute if arg is not function & return default argument', () => {
const foo = 'bar'
expect(Util.execute(foo)).toBe('bar')
expect(Util.execute(foo, [], 4)).toBe(4)
})
})
describe('executeAfterTransition', () => {
-3
View File
@@ -42,7 +42,6 @@
display: block;
}
/* rtl:begin:ignore */
.carousel-item-next:not(.carousel-item-start),
.active.carousel-item-end {
transform: translateX(100%);
@@ -53,8 +52,6 @@
transform: translateX(-100%);
}
/* rtl:end:ignore */
//
// Alternate transitions
+18 -9
View File
@@ -4,37 +4,46 @@
// See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile
.btn-close {
--#{$prefix}btn-close-color: #{$btn-close-color};
--#{$prefix}btn-close-bg: #{ escape-svg($btn-close-bg) };
--#{$prefix}btn-close-opacity: #{$btn-close-opacity};
--#{$prefix}btn-close-hover-opacity: #{$btn-close-hover-opacity};
--#{$prefix}btn-close-focus-shadow: #{$btn-close-focus-shadow};
--#{$prefix}btn-close-focus-opacity: #{$btn-close-focus-opacity};
--#{$prefix}btn-close-disabled-opacity: #{$btn-close-disabled-opacity};
--#{$prefix}btn-close-white-filter: #{$btn-close-white-filter};
box-sizing: content-box;
width: $btn-close-width;
height: $btn-close-height;
padding: $btn-close-padding-y $btn-close-padding-x;
color: $btn-close-color;
background: transparent escape-svg($btn-close-bg) center / $btn-close-width auto no-repeat; // include transparent for button elements
color: var(--#{$prefix}btn-close-color);
background: transparent var(--#{$prefix}btn-close-bg) center / $btn-close-width auto no-repeat; // include transparent for button elements
border: 0; // for button elements
@include border-radius();
opacity: $btn-close-opacity;
opacity: var(--#{$prefix}btn-close-opacity);
// Override <a>'s hover style
&:hover {
color: $btn-close-color;
color: var(--#{$prefix}btn-close-color);
text-decoration: none;
opacity: $btn-close-hover-opacity;
opacity: var(--#{$prefix}btn-close-hover-opacity);
}
&:focus {
outline: 0;
box-shadow: $btn-close-focus-shadow;
opacity: $btn-close-focus-opacity;
box-shadow: var(--#{$prefix}btn-close-focus-shadow);
opacity: var(--#{$prefix}btn-close-focus-opacity);
}
&:disabled,
&.disabled {
pointer-events: none;
user-select: none;
opacity: $btn-close-disabled-opacity;
opacity: var(--#{$prefix}btn-close-disabled-opacity);
}
}
.btn-close-white {
filter: $btn-close-white-filter;
filter: var(--#{$prefix}btn-close-white-filter);
}
+4 -2
View File
@@ -12,6 +12,8 @@
--#{$prefix}offcanvas-border-width: #{$offcanvas-border-width};
--#{$prefix}offcanvas-border-color: #{$offcanvas-border-color};
--#{$prefix}offcanvas-box-shadow: #{$offcanvas-box-shadow};
--#{$prefix}offcanvas-transition: #{transform $offcanvas-transition-duration ease-in-out};
--#{$prefix}offcanvas-title-line-height: #{$offcanvas-title-line-height};
// scss-docs-end offcanvas-css-vars
}
@@ -42,7 +44,7 @@
background-clip: padding-box;
outline: 0;
@include box-shadow(var(--#{$prefix}offcanvas-box-shadow));
@include transition(transform $offcanvas-transition-duration ease-in-out);
@include transition(var(--#{$prefix}offcanvas-transition));
&.offcanvas-start {
top: 0;
@@ -134,7 +136,7 @@
.offcanvas-title {
margin-bottom: 0;
line-height: $offcanvas-title-line-height;
line-height: var(--#{$prefix}offcanvas-title-line-height);
}
.offcanvas-body {
+4
View File
@@ -70,4 +70,8 @@
--#{$prefix}code-color: #{$code-color};
--#{$prefix}highlight-bg: #{$mark-bg};
@each $name, $value in $grid-breakpoints {
--#{$prefix}breakpoint-#{$name}: #{$value};
}
}
+69 -6
View File
@@ -22,6 +22,20 @@ $utilities: map-merge(
)
),
// scss-docs-end utils-float
// Object Fit utilities
// scss-docs-start utils-object-fit
"object-fit": (
responsive: true,
property: object-fit,
values: (
contain: contain,
cover: cover,
fill: fill,
scale: scale-down,
none: none,
)
),
// scss-docs-end utils-object-fit
// Opacity utilities
// scss-docs-start utils-opacity
"opacity": (
@@ -40,6 +54,14 @@ $utilities: map-merge(
property: overflow,
values: auto hidden visible scroll,
),
"overflow-x": (
property: overflow-x,
values: auto hidden visible scroll,
),
"overflow-y": (
property: overflow-y,
values: auto hidden visible scroll,
),
// scss-docs-end utils-overflow
// scss-docs-start utils-display
"display": (
@@ -473,11 +495,12 @@ $utilities: map-merge(
property: font-weight,
class: fw,
values: (
light: $font-weight-light,
lighter: $font-weight-lighter,
light: $font-weight-light,
normal: $font-weight-normal,
bold: $font-weight-bold,
medium: $font-weight-medium,
semibold: $font-weight-semibold,
bold: $font-weight-bold,
bolder: $font-weight-bolder
)
),
@@ -614,22 +637,62 @@ $utilities: map-merge(
"rounded-top": (
property: border-top-left-radius border-top-right-radius,
class: rounded-top,
values: (null: var(--#{$prefix}border-radius))
values: (
null: var(--#{$prefix}border-radius),
0: 0,
1: var(--#{$prefix}border-radius-sm),
2: var(--#{$prefix}border-radius),
3: var(--#{$prefix}border-radius-lg),
4: var(--#{$prefix}border-radius-xl),
5: var(--#{$prefix}border-radius-2xl),
circle: 50%,
pill: var(--#{$prefix}border-radius-pill)
)
),
"rounded-end": (
property: border-top-right-radius border-bottom-right-radius,
class: rounded-end,
values: (null: var(--#{$prefix}border-radius))
values: (
null: var(--#{$prefix}border-radius),
0: 0,
1: var(--#{$prefix}border-radius-sm),
2: var(--#{$prefix}border-radius),
3: var(--#{$prefix}border-radius-lg),
4: var(--#{$prefix}border-radius-xl),
5: var(--#{$prefix}border-radius-2xl),
circle: 50%,
pill: var(--#{$prefix}border-radius-pill)
)
),
"rounded-bottom": (
property: border-bottom-right-radius border-bottom-left-radius,
class: rounded-bottom,
values: (null: var(--#{$prefix}border-radius))
values: (
null: var(--#{$prefix}border-radius),
0: 0,
1: var(--#{$prefix}border-radius-sm),
2: var(--#{$prefix}border-radius),
3: var(--#{$prefix}border-radius-lg),
4: var(--#{$prefix}border-radius-xl),
5: var(--#{$prefix}border-radius-2xl),
circle: 50%,
pill: var(--#{$prefix}border-radius-pill)
)
),
"rounded-start": (
property: border-bottom-left-radius border-top-left-radius,
class: rounded-start,
values: (null: var(--#{$prefix}border-radius))
values: (
null: var(--#{$prefix}border-radius),
0: 0,
1: var(--#{$prefix}border-radius-sm),
2: var(--#{$prefix}border-radius),
3: var(--#{$prefix}border-radius-lg),
4: var(--#{$prefix}border-radius-xl),
5: var(--#{$prefix}border-radius-2xl),
circle: 50%,
pill: var(--#{$prefix}border-radius-pill)
)
),
// scss-docs-end utils-border-radius
// scss-docs-start utils-visibility
+2
View File
@@ -556,6 +556,7 @@ $font-size-lg: $font-size-base * 1.25 !default;
$font-weight-lighter: lighter !default;
$font-weight-light: 300 !default;
$font-weight-normal: 400 !default;
$font-weight-medium: 500 !default;
$font-weight-semibold: 600 !default;
$font-weight-bold: 700 !default;
$font-weight-bolder: bolder !default;
@@ -1001,6 +1002,7 @@ $form-floating-padding-x: $input-padding-x !default;
$form-floating-padding-y: 1rem !default;
$form-floating-input-padding-t: 1.625rem !default;
$form-floating-input-padding-b: .625rem !default;
$form-floating-label-height: 1.875em !default;
$form-floating-label-opacity: .65 !default;
$form-floating-label-transform: scale(.85) translateY(-.5rem) translateX(.15rem) !default;
$form-floating-transition: opacity .1s ease-in-out, transform .1s ease-in-out !default;
+11
View File
@@ -1,6 +1,17 @@
.form-floating {
position: relative;
&::before {
position: absolute;
top: $input-border-width;
left: $input-border-width;
width: subtract(100%, add($input-height-inner-quarter, $input-height-inner-half));
height: $form-floating-label-height;
content: "";
background-color: $input-bg;
@include border-radius($input-border-radius);
}
> .form-control,
> .form-control-plaintext,
> .form-select {
+2 -2
View File
@@ -1,11 +1,11 @@
@each $color, $value in $theme-colors {
.link-#{$color} {
color: $value !important; // stylelint-disable-line declaration-no-important
color: $value if($enable-important-utilities, !important, null);
@if $link-shade-percentage != 0 {
&:hover,
&:focus {
color: if(color-contrast($value) == $color-contrast-light, shade-color($value, $link-shade-percentage), tint-color($value, $link-shade-percentage)) !important; // stylelint-disable-line declaration-no-important
color: if(color-contrast($value) == $color-contrast-light, shade-color($value, $link-shade-percentage), tint-color($value, $link-shade-percentage)) if($enable-important-utilities, !important, null);
}
}
}
+2 -1
View File
@@ -3,13 +3,14 @@
--#{$prefix}alert-color: #{$color};
--#{$prefix}alert-bg: #{$background};
--#{$prefix}alert-border-color: #{$border};
--#{$prefix}alert-link-color: #{shade-color($color, 20%)};
@if $enable-gradients {
background-image: var(--#{$prefix}gradient);
}
.alert-link {
color: shade-color($color, 20%);
color: var(--#{$prefix}alert-link-color);
}
}
// scss-docs-end alert-variant-mixin
+30 -25
View File
@@ -1,44 +1,49 @@
// scss-docs-start caret-mixins
@mixin caret-down {
border-top: $caret-width solid;
border-right: $caret-width solid transparent;
@mixin caret-down($width: $caret-width) {
border-top: $width solid;
border-right: $width solid transparent;
border-bottom: 0;
border-left: $caret-width solid transparent;
border-left: $width solid transparent;
}
@mixin caret-up {
@mixin caret-up($width: $caret-width) {
border-top: 0;
border-right: $caret-width solid transparent;
border-bottom: $caret-width solid;
border-left: $caret-width solid transparent;
border-right: $width solid transparent;
border-bottom: $width solid;
border-left: $width solid transparent;
}
@mixin caret-end {
border-top: $caret-width solid transparent;
@mixin caret-end($width: $caret-width) {
border-top: $width solid transparent;
border-right: 0;
border-bottom: $caret-width solid transparent;
border-left: $caret-width solid;
border-bottom: $width solid transparent;
border-left: $width solid;
}
@mixin caret-start {
border-top: $caret-width solid transparent;
border-right: $caret-width solid;
border-bottom: $caret-width solid transparent;
@mixin caret-start($width: $caret-width) {
border-top: $width solid transparent;
border-right: $width solid;
border-bottom: $width solid transparent;
}
@mixin caret($direction: down) {
@mixin caret(
$direction: down,
$width: $caret-width,
$spacing: $caret-spacing,
$vertical-align: $caret-vertical-align
) {
@if $enable-caret {
&::after {
display: inline-block;
margin-left: $caret-spacing;
vertical-align: $caret-vertical-align;
margin-left: $spacing;
vertical-align: $vertical-align;
content: "";
@if $direction == down {
@include caret-down();
@include caret-down($width);
} @else if $direction == up {
@include caret-up();
@include caret-up($width);
} @else if $direction == end {
@include caret-end();
@include caret-end($width);
}
}
@@ -49,10 +54,10 @@
&::before {
display: inline-block;
margin-right: $caret-spacing;
vertical-align: $caret-vertical-align;
margin-right: $spacing;
vertical-align: $vertical-align;
content: "";
@include caret-start();
@include caret-start($width);
}
}
@@ -3,9 +3,7 @@ layout: examples
title: مثال إتمام الشراء
direction: rtl
extra_css:
- "../checkout/form-validation.css"
extra_js:
- src: "../checkout/form-validation.js"
- "../checkout/checkout.css"
body_class: "bg-light"
---
@@ -67,7 +65,7 @@ body_class: "bg-light"
</div>
<div class="col-md-7 col-lg-8">
<h4 class="mb-3">عنوان الفوترة</h4>
<form class="needs-validation" novalidate>
<form data-bs-toggle="form">
<div class="row g-3">
<div class="col-sm-6">
<label for="firstName" class="form-label">الاسم الأول</label>
@@ -1,19 +0,0 @@
// Example starter JavaScript for disabling form submissions if there are invalid fields
(() => {
'use strict'
// Fetch all the forms we want to apply custom Bootstrap validation styles to
const forms = document.querySelectorAll('.needs-validation')
// Loop over them and prevent submission
Array.from(forms).forEach(form => {
form.addEventListener('submit', event => {
if (!form.checkValidity()) {
event.preventDefault()
event.stopPropagation()
}
form.classList.add('was-validated')
}, false)
})
})()
@@ -2,9 +2,7 @@
layout: examples
title: Checkout example
extra_css:
- "form-validation.css"
extra_js:
- src: "form-validation.js"
- "checkout.css"
body_class: "bg-light"
---
@@ -66,7 +64,7 @@ body_class: "bg-light"
</div>
<div class="col-md-7 col-lg-8">
<h4 class="mb-3">Billing address</h4>
<form class="needs-validation" novalidate>
<form data-bs-toggle="form">
<div class="row g-3">
<div class="col-sm-6">
<label for="firstName" class="form-label">First name</label>
@@ -4,9 +4,9 @@
/* Custom default button */
.btn-secondary,
.btn-secondary:hover,
.btn-secondary:focus {
.btn-light,
.btn-light:hover,
.btn-light:focus {
color: #333;
text-shadow: none; /* Prevent inheritance from `body` */
}
@@ -24,7 +24,7 @@ include_js: false
<h1>Cover your page.</h1>
<p class="lead">Cover is a one-page template for building simple and beautiful home pages. Download, edit the text, and add your own fullscreen background photo to make it your own.</p>
<p class="lead">
<a href="#" class="btn btn-lg btn-secondary fw-bold border-white bg-white">Learn more</a>
<a href="#" class="btn btn-lg btn-light fw-bold border-white bg-white">Learn more</a>
</p>
</main>
@@ -2,7 +2,7 @@
layout: examples
title: Fixed top navbar example
extra_css:
- "navbar-top-fixed.css"
- "navbar-fixed.css"
---
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
@@ -2,7 +2,7 @@
layout: examples
title: Top navbar example
extra_css:
- "navbar-top.css"
- "navbar-static.css"
---
<nav class="navbar navbar-expand-md navbar-dark bg-dark mb-4">
@@ -2,7 +2,7 @@
layout: examples
title: Navbar Template
extra_css:
- "navbar.css"
- "navbars-offcanvas.css"
---
<main>
@@ -2,7 +2,7 @@
layout: examples
title: Navbar Template
extra_css:
- "navbar.css"
- "navbars.css"
---
<main>
@@ -2,9 +2,9 @@
layout: examples
title: Offcanvas navbar template
extra_css:
- "offcanvas.css"
- "offcanvas-navbar.css"
extra_js:
- src: "offcanvas.js"
- src: "offcanvas-navbar.js"
body_class: "bg-light"
aliases: "/docs/5.2/examples/offcanvas/"
---
@@ -2,7 +2,7 @@
layout: examples
title: Signin Template
extra_css:
- "signin.css"
- "sign-in.css"
body_class: "text-center"
include_js: false
---
+106 -81
View File
@@ -4,22 +4,15 @@ title: Validation
description: Provide valuable, actionable feedback to your users with HTML5 form validation, via browser default behaviors or custom styles and JavaScript.
group: forms
toc: true
extra_js:
- src: "/docs/5.2/assets/js/validate-forms.js"
async: true
---
{{< callout warning >}}
We are aware that currently the client-side custom validation styles and tooltips are not accessible, since they are not exposed to assistive technologies. While we work on a solution, we'd recommend either using the server-side option or the default browser validation method.
{{< /callout >}}
## How it works
Here's how form validation works with Bootstrap:
- HTML form validation is applied via CSS's two pseudo-classes, `:invalid` and `:valid`. It applies to `<input>`, `<select>`, and `<textarea>` elements.
- Bootstrap scopes the `:invalid` and `:valid` styles to parent `.was-validated` class, usually applied to the `<form>`. Otherwise, any required field without a value shows up as invalid on page load. This way, you may choose when to activate them (typically after form submission is attempted).
- To reset the appearance of the form (for instance, in the case of dynamic form submissions using AJAX), remove the `.was-validated` class from the `<form>` again after submission.
- To reset the appearance of the form (for instance, in the case of dynamic form submissions using Ajax), remove the `.was-validated` class from the `<form>` again after submission.
- As a fallback, `.is-invalid` and `.is-valid` classes may be used instead of the pseudo-classes for [server-side validation](#server-side). They do not require a `.was-validated` parent class.
- Due to constraints in how CSS works, we cannot (at present) apply styles to a `<label>` that comes before a form control in the DOM without the help of custom JavaScript.
- All modern browsers support the [constraint validation API](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#the-constraint-validation-api), a series of JavaScript methods for validating form controls.
@@ -30,80 +23,59 @@ With that in mind, consider the following demos for our custom form validation s
## Custom styles
For custom Bootstrap form validation messages, you'll need to add the `novalidate` boolean attribute to your `<form>`. This disables the browser default feedback tooltips, but still provides access to the form validation APIs in JavaScript. Try to submit the form below; our JavaScript will intercept the submit button and relay feedback to you. When attempting to submit, you'll see the `:invalid` and `:valid` styles applied to your form controls.
For custom Bootstrap form validation messages, you'll need to add the data-bs-toggle="form" `<form>`. This disables the browser default feedback tooltips, but still provides access to the form validation APIs in JavaScript. Try to submit the form below; our JavaScript will intercept the submit button and relay feedback to you. When attempting to submit, you'll see the `:invalid` and `:valid` styles applied to your form controls.
Custom feedback styles apply custom colors, borders, focus styles, and background icons to better communicate feedback. Background icons for `<select>`s are only available with `.form-select`, and not `.form-control`.
{{< example >}}
<form class="row g-3 needs-validation" novalidate>
<form class="row g-3" data-bs-toggle="form">
<div class="col-md-4">
<label for="validationCustom01" class="form-label">First name</label>
<input type="text" class="form-control" id="validationCustom01" value="Mark" required>
<div class="valid-feedback">
Looks good!
</div>
<input type="text" class="form-control" id="validationCustom01" value="Mark" required data-bs-valid="Looks good!" data-bs-invalid="Please, provide a valid Name!">
</div>
<div class="col-md-4">
<label for="validationCustom02" class="form-label">Last name</label>
<input type="text" class="form-control" id="validationCustom02" value="Otto" required>
<div class="valid-feedback">
Looks good!
</div>
<input type="text" class="form-control" id="validationCustom02" value="Otto" required data-bs-valid="Looks good!">
</div>
<div class="col-md-4">
<label for="validationCustomUsername" class="form-label">Username</label>
<div class="input-group has-validation">
<span class="input-group-text" id="inputGroupPrepend">@</span>
<input type="text" class="form-control" id="validationCustomUsername" aria-describedby="inputGroupPrepend" required>
<div class="invalid-feedback">
Please choose a username.
</div>
<input type="text" class="form-control" id="validationCustomUsername" aria-describedby="inputGroupPrepend" required data-bs-invalid="Please choose a username.">
</div>
</div>
<div class="col-md-6">
<label for="validationCustom03" class="form-label">City</label>
<input type="text" class="form-control" id="validationCustom03" required>
<div class="invalid-feedback">
Please provide a valid city.
</div>
<input type="text" class="form-control" id="validationCustom03" required data-bs-invalid="Please provide a valid city.">
</div>
<div class="col-md-3">
<label for="validationCustom04" class="form-label">State</label>
<select class="form-select" id="validationCustom04" required>
<select class="form-select" id="validationCustom04" required data-bs-invalid="Please select a valid state.">
<option selected disabled value="">Choose...</option>
<option>...</option>
</select>
<div class="invalid-feedback">
Please select a valid state.
</div>
</div>
<div class="col-md-3">
<label for="validationCustom05" class="form-label">Zip</label>
<input type="text" class="form-control" id="validationCustom05" required>
<div class="invalid-feedback">
Please provide a valid zip.
</div>
<input type="text" class="form-control" id="validationCustom05" required data-bs-invalid="Please provide a valid zip.">
</div>
<div class="col-12">
<div class="form-check">
<input class="form-check-input" type="checkbox" value="" id="invalidCheck" required>
<input class="form-check-input" type="checkbox" value="" id="invalidCheck" required data-bs-invalid="You must agree before submitting.">
<label class="form-check-label" for="invalidCheck">
Agree to terms and conditions
</label>
<div class="invalid-feedback">
You must agree before submitting.
</div>
</div>
</div>
<div class="col-12">
<button class="btn btn-primary" type="submit">Submit form</button>
<button class="btn btn-danger" type="reset">Reset form</button>
</div>
</form>
{{< /example >}}
{{< example lang="js" show_preview="false" >}}
{{< js.inline >}}
{{- readFile (path.Join "site/static/docs" .Site.Params.docs_version "assets/js/validate-forms.js") -}}
{{< /js.inline >}}
{{< /example >}}
@@ -159,11 +131,11 @@ While these feedback styles cannot be styled with CSS, you can still customize t
</form>
{{< /example >}}
## Server side
## Server-side
We recommend using client-side validation, but in case you require server-side validation, you can indicate invalid and valid form fields with `.is-invalid` and `.is-valid`. Note that `.invalid-feedback` is also supported with these classes.
For invalid fields, ensure that the invalid feedback/error message is associated with the relevant form field using `aria-describedby` (noting that this attribute allows more than one `id` to be referenced, in case the field already points to additional form text).
Ensure that the feedback/error message is associated with the relevant form field using `aria-describedby` (noting that this attribute allows more than one `id` to be referenced, in case the field already points to additional form text).
To fix [issues with border radius](https://github.com/twbs/bootstrap/issues/25110), input groups require an additional `.has-validation` class.
@@ -171,15 +143,15 @@ To fix [issues with border radius](https://github.com/twbs/bootstrap/issues/2511
<form class="row g-3">
<div class="col-md-4">
<label for="validationServer01" class="form-label">First name</label>
<input type="text" class="form-control is-valid" id="validationServer01" value="Mark" required>
<div class="valid-feedback">
<input type="text" class="form-control is-valid" id="validationServer01" value="Mark" aria-describedby="validationServerNameFeedback" required>
<div id="validationServerNameFeedback" class="valid-feedback">
Looks good!
</div>
</div>
<div class="col-md-4">
<label for="validationServer02" class="form-label">Last name</label>
<input type="text" class="form-control is-valid" id="validationServer02" value="Otto" required>
<div class="valid-feedback">
<input type="text" class="form-control is-valid" id="validationServer02" aria-describedby="validationServerLastNameFeedback" value="Otto" required>
<div id="validationServerLastNameFeedback" class="valid-feedback">
Looks good!
</div>
</div>
@@ -246,41 +218,41 @@ Validation styles are available for the following form controls and components:
<form class="was-validated">
<div class="mb-3">
<label for="validationTextarea" class="form-label">Textarea</label>
<textarea class="form-control" id="validationTextarea" placeholder="Required example textarea" required></textarea>
<div class="invalid-feedback">
<textarea class="form-control" id="validationTextarea" placeholder="Required example textarea" aria-describedby="validationSupportedElementsTextArea" required></textarea>
<div id="validationSupportedElementsTextArea" class="invalid-feedback">
Please enter a message in the textarea.
</div>
</div>
<div class="form-check mb-3">
<input type="checkbox" class="form-check-input" id="validationFormCheck1" required>
<input type="checkbox" class="form-check-input" id="validationFormCheck1" aria-describedby="validationSupportedElementsCheckBox" required>
<label class="form-check-label" for="validationFormCheck1">Check this checkbox</label>
<div class="invalid-feedback">Example invalid feedback text</div>
<div id="validationSupportedElementsCheckBox" class="invalid-feedback">Example invalid feedback text</div>
</div>
<div class="form-check">
<input type="radio" class="form-check-input" id="validationFormCheck2" name="radio-stacked" required>
<input type="radio" class="form-check-input" id="validationFormCheck2" name="radio-stacked" aria-describedby="validationSupportedElementsRadio" required>
<label class="form-check-label" for="validationFormCheck2">Toggle this radio</label>
</div>
<div class="form-check mb-3">
<input type="radio" class="form-check-input" id="validationFormCheck3" name="radio-stacked" required>
<input type="radio" class="form-check-input" id="validationFormCheck3" name="radio-stacked" aria-describedby="validationSupportedElementsRadio" required>
<label class="form-check-label" for="validationFormCheck3">Or toggle this other radio</label>
<div class="invalid-feedback">More example invalid feedback text</div>
<div id="validationSupportedElementsRadio" class="invalid-feedback">More example invalid feedback text</div>
</div>
<div class="mb-3">
<select class="form-select" required aria-label="select example">
<select class="form-select" required aria-label="select example" aria-describedby="validationSupportedElementsSelect">
<option value="">Open this select menu</option>
<option value="1">One</option>
<option value="2">Two</option>
<option value="3">Three</option>
</select>
<div class="invalid-feedback">Example invalid select feedback</div>
<div id="validationSupportedElementsSelect" class="invalid-feedback">Example invalid select feedback</div>
</div>
<div class="mb-3">
<input type="file" class="form-control" aria-label="file example" required>
<div class="invalid-feedback">Example invalid form file feedback</div>
<input type="file" class="form-control" aria-label="file example" required aria-describedby="validationSupportedElementsFile">
<div id="validationSupportedElementsFile" class="invalid-feedback">Example invalid form file feedback</div>
</div>
<div class="mb-3">
@@ -294,57 +266,40 @@ Validation styles are available for the following form controls and components:
If your form layout allows it, you can swap the `.{valid|invalid}-feedback` classes for `.{valid|invalid}-tooltip` classes to display validation feedback in a styled tooltip. Be sure to have a parent with `position: relative` on it for tooltip positioning. In the example below, our column classes have this already, but your project may require an alternative setup.
{{< example >}}
<form class="row g-3 needs-validation" novalidate>
<form class="row g-3" data-bs-toggle="form" data-bs-type="tooltip" >
<div class="col-md-4 position-relative">
<label for="validationTooltip01" class="form-label">First name</label>
<input type="text" class="form-control" id="validationTooltip01" value="Mark" required>
<div class="valid-tooltip">
Looks good!
</div>
<input type="text" class="form-control" id="validationTooltip01" value="Mark" required data-bs-valid="Looks good!">
</div>
<div class="col-md-4 position-relative">
<label for="validationTooltip02" class="form-label">Last name</label>
<input type="text" class="form-control" id="validationTooltip02" value="Otto" required>
<div class="valid-tooltip">
Looks good!
</div>
<input type="text" class="form-control" id="validationTooltip02" value="Otto" required data-bs-valid="Looks good!">
</div>
<div class="col-md-4 position-relative">
<label for="validationTooltipUsername" class="form-label">Username</label>
<div class="input-group has-validation">
<span class="input-group-text" id="validationTooltipUsernamePrepend">@</span>
<input type="text" class="form-control" id="validationTooltipUsername" aria-describedby="validationTooltipUsernamePrepend" required>
<div class="invalid-tooltip">
Please choose a unique and valid username.
</div>
<input type="text" class="form-control" id="validationTooltipUsername" aria-describedby="validationTooltipUsernamePrepend" required data-bs-invalid="Please choose a username.">
</div>
</div>
<div class="col-md-6 position-relative">
<label for="validationTooltip03" class="form-label">City</label>
<input type="text" class="form-control" id="validationTooltip03" required>
<div class="invalid-tooltip">
Please provide a valid city.
</div>
<input type="text" class="form-control" id="validationTooltip03" required data-bs-invalid="Please provide a valid city.">
</div>
<div class="col-md-3 position-relative">
<label for="validationTooltip04" class="form-label">State</label>
<select class="form-select" id="validationTooltip04" required>
<select class="form-select" id="validationTooltip04" required data-bs-invalid="Please select a valid state.">
<option selected disabled value="">Choose...</option>
<option>...</option>
</select>
<div class="invalid-tooltip">
Please select a valid state.
</div>
</div>
<div class="col-md-3 position-relative">
<label for="validationTooltip05" class="form-label">Zip</label>
<input type="text" class="form-control" id="validationTooltip05" required>
<div class="invalid-tooltip">
Please provide a valid zip.
</div>
<input type="text" class="form-control" id="validationTooltip05" required data-bs-invalid="Please provide a valid zip.">
</div>
<div class="col-12">
<button class="btn btn-primary" type="submit">Submit form</button>
<button class="btn btn-danger" type="reset">Reset form</button>
</div>
</form>
{{< /example >}}
@@ -378,3 +333,73 @@ Used to iterate over `$form-validation-states` map values to generate our valida
### Customizing
Validation states can be customized via Sass with the `$form-validation-states` map. Located in our `_variables.scss` file, this Sass map is how we generate the default `valid`/`invalid` validation states. Included is a nested map for customizing each state's color, icon, tooltip color, and focus shadow. While no other states are supported by browsers, those using custom styles can easily add more complex form feedback.
## Usage
### Via data attributes
To easily add form validation behavior to you form, add `data-bs-toggle="form"` attribute to the `<form>` element.
### Via JavaScript
Enable manually with:
```js
const formElementList = document.querySelectorAll('form')
const formList = [...formElementList].map(formEl => new bootstrap.Form(formEl))
```
### Options
#### Form
{{< bs-table "table" >}}
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `type` | string | `feedback` | You may pick the kind of feedback. Acceptable values `tooltip` or `feedback` |
| `validateCallback` | function, null | `null` | A callback to execute while trying to check for errors. Can be use for ajax submissions/validations. |
{{< /bs-table >}}
#### Field
{{< bs-table "table" >}}
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `invalid` | string | `''` | invalid message to append |
| `valid` | string | `''` | valid message to append |
{{< /bs-table >}}
### Methods
You can create a form instance using the constructor, for example:
```js
const bsForm = new bootstrap.Form('#myForm', { type: 'tooltip' })
```
#### Form
{{< bs-table "table" >}}
| Method | Description |
| --- | --- |
| `getInstance` | *Static* method which allows you to get the form instance associated with a DOM element. |
| `getOrCreateInstance` | *Static* method which allows you to get the form instance associated with a DOM element, or create a new one in case it wasn't initialized. |
| `getElement` | Returns the DOM element of the instance. |
| `getFields` | Returns all form-fields instances of the form. Array\<FormField\>. |
| `getField('name')` | Searches and return the requested FormField instance or undefined. |
| `clear` | Clears all the form validation results. |
| `validate` | Activates validation process. |
{{< /bs-table >}}
#### Field
{{< bs-table "table" >}}
| Method | Description |
| --- | --- |
| `getInstance` | *Static* method which allows you to get the form Field instance associated with a DOM element |
| `getOrCreateInstance` | *Static* method which allows you to get the form Field instance associated with a DOM element, or create a new one in case it wasn't initialized |
| `getElement` | Returns the DOM element of the instance. |
| `clearAppended` | Clears any appended messages from field. |
| `appendError(message)` | Appends the given message as an error feedback. In case we do not provide a message, it fallbacks to the configuration given `invalid` message. |
| `appendSuccess(message)` | Appends the given message as a success feedback. In case we do not provide a message, it fallbacks to the configuration given `valid` message. |
| `appendFeedback(message)` | Appends the given message as a simple info feedback. |
| `name` | returns the name (fallback to id) of the field as unique identifier between form elements. |
{{< /bs-table >}}
@@ -139,6 +139,15 @@ Use the scaling classes for larger or smaller rounded corners. Sizes range from
{{< placeholder width="75" height="75" class="rounded-5" title="Example extra large rounded image" >}}
{{< /example >}}
{{< example class="bd-example-rounded-utils" >}}
{{< placeholder width="75" height="75" class="rounded-bottom-1" title="Example small rounded image" >}}
{{< placeholder width="75" height="75" class="rounded-start-2" title="Example default left rounded image" >}}
{{< placeholder width="75" height="75" class="rounded-end-circle" title="Example right completely round image" >}}
{{< placeholder width="75" height="75" class="rounded-start-pill" title="Example left rounded pill image" >}}
{{< placeholder width="75" height="75" class="rounded-5 rounded-top-0" title="Example extra large bottom rounded image" >}}
{{< /example >}}
## CSS
### Variables
+2 -2
View File
@@ -64,8 +64,8 @@ To show an element only on a given interval of screen sizes you can combine one
| Hidden only on sm | `.d-sm-none .d-md-block` |
| Hidden only on md | `.d-md-none .d-lg-block` |
| Hidden only on lg | `.d-lg-none .d-xl-block` |
| Hidden only on xl | `.d-xl-none` |
| Hidden only on xxl | `.d-xxl-none .d-xxl-block` |
| Hidden only on xl | `.d-xl-none .d-xxl-block` |
| Hidden only on xxl | `.d-xxl-none` |
| Visible on all | `.d-block` |
| Visible only on xs | `.d-block .d-sm-none` |
| Visible only on sm | `.d-none .d-sm-block .d-md-none` |
@@ -0,0 +1,61 @@
---
layout: docs
title: Object fit
description: Use the object fit utilities to modify how the content of a [replaced element](https://developer.mozilla.org/en-US/docs/Web/CSS/Replaced_element), such as an `<img>` or `<video>`, should be resized to fit its container.
group: utilities
toc: true
---
## How it works
Change the value of the [`object-fit` property](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit) with our responsive `object-fit` utility classes. This property tells the content to fill the parent container in a variety of ways, such as preserving the aspect ratio or stretching to take up as much space as possible.
Classes for the value of `object-fit` are named using the format `.object-fit-{value}`. Choose from the following values:
- `contain`
- `cover`
- `fill`
- `scale` (for scale-down)
- `none`
## Examples
Add the `object-fit-{value}` class to the [replaced element](https://developer.mozilla.org/en-US/docs/Web/CSS/Replaced_element):
{{< example class="d-flex overflow-auto" >}}
{{< placeholder width="140" height="120" class="object-fit-contain border rounded" text="Object fit contain" markup="img" >}}
{{< placeholder width="140" height="120" class="object-fit-cover border rounded" text="Object fit cover" markup="img" >}}
{{< placeholder width="140" height="120" class="object-fit-fill border rounded" text="Object fit fill" markup="img" >}}
{{< placeholder width="140" height="120" class="object-fit-scale border rounded" text="Object fit scale down" markup="img" >}}
{{< placeholder width="140" height="120" class="object-fit-none border rounded" text="Object fit none" markup="img" >}}
{{< /example >}}
## Responsive
Responsive variations also exist for each `object-fit` value using the format `.object-fit-{breakpoint}-{value}`, for the following breakpoint abbreviations: `sm`, `md`, `lg`, `xl`, and `xxl`. Classes can be combined for various effects as you need.
{{< example class="d-flex overflow-auto" >}}
{{< placeholder width="140" height="80" class="object-fit-sm-contain border rounded" text="Contain on sm" markup="img" >}}
{{< placeholder width="140" height="80" class="object-fit-md-contain border rounded" text="Contain on md" markup="img" >}}
{{< placeholder width="140" height="80" class="object-fit-lg-contain border rounded" text="Contain on lg" markup="img" >}}
{{< placeholder width="140" height="80" class="object-fit-xl-contain border rounded" text="Contain on xl" markup="img" >}}
{{< placeholder width="140" height="80" class="object-fit-xxl-contain border rounded" text="Contain on xxl" markup="img" >}}
{{< /example >}}
## Video
The `.object-fit-{value}` and responsive `.object-fit-{breakpoint}-{value}` utilities also work on `<video>` elements.
```html
<video src="..." class="object-fit-contain" autoplay></video>
<video src="..." class="object-fit-cover" autoplay></video>
<video src="..." class="object-fit-fill" autoplay></video>
<video src="..." class="object-fit-scale" autoplay></video>
<video src="..." class="object-fit-none" autoplay></video>
```
## Utilities API
Object fit utilities are declared in our utilities API in `scss/_utilities.scss`. [Learn how to use the utilities API.]({{< docsref "/utilities/api#using-the-api" >}})
{{< scss-docs name="utils-object-fit" file="scss/_utilities.scss" >}}
@@ -3,8 +3,11 @@ layout: docs
title: Overflow
description: Use these shorthand utilities for quickly configuring how content overflows an element.
group: utilities
toc: true
---
## Overflow
Adjust the `overflow` property on the fly with four default values and classes. These classes are not responsive by default.
<div class="bd-example d-md-flex">
@@ -29,6 +32,62 @@ Adjust the `overflow` property on the fly with four default values and classes.
<div class="overflow-scroll">...</div>
```
### `overflow-x`
Adjust the `overflow-x` property to affect the overflow of content horizontally.
<div class="bd-example d-md-flex">
<div class="overflow-x-auto p-3 mb-3 mb-md-0 me-md-3 bg-light w-100" style="max-width: 200px; max-height: 100px; white-space: nowrap;">
<div><code>.overflow-x-auto</code> example on an element</div>
<div> with set width and height dimensions.</div>
</div>
<div class="overflow-x-hidden p-3 mb-3 mb-md-0 me-md-3 bg-light w-100" style="max-width: 200px; max-height: 100px;white-space: nowrap;">
<div><code>.overflow-x-hidden</code> example</div>
<div>on an element with set width and height dimensions.</div>
</div>
<div class="overflow-x-visible p-3 mb-3 mb-md-0 me-md-3 bg-light w-100" style="max-width: 200px; max-height: 100px;white-space: nowrap;">
<div><code>.overflow-x-visible</code> example </div>
<div>on an element with set width and height dimensions.</div>
</div>
<div class="overflow-x-scroll p-3 bg-light w-100" style="max-width: 200px; max-height: 100px;white-space: nowrap;">
<div><code>.overflow-x-scroll</code> example on an element</div>
<div> with set width and height dimensions.</div>
</div>
</div>
```html
<div class="overflow-x-auto">...</div>
<div class="overflow-x-hidden">...</div>
<div class="overflow-x-visible">...</div>
<div class="overflow-x-scroll">...</div>
```
### `overflow-y`
Adjust the `overflow-y` property to affect the overflow of content vertically.
<div class="bd-example d-md-flex">
<div class="overflow-y-auto p-3 mb-3 mb-md-0 me-md-3 bg-light w-100" style="max-width: 200px; max-height: 100px;">
<code>.overflow-y-auto</code> example on an element with set width and height dimensions.
</div>
<div class="overflow-y-hidden p-3 mb-3 mb-md-0 me-md-3 bg-light w-100" style="max-width: 200px; max-height: 100px;">
<code>.overflow-y-hidden</code> example on an element with set width and height dimensions.
</div>
<div class="overflow-y-visible p-3 mb-3 mb-md-0 me-md-3 bg-light w-100" style="max-width: 200px; max-height: 100px;">
<code>.overflow-y-visible</code> example on an element with set width and height dimensions.
</div>
<div class="overflow-y-scroll p-3 bg-light w-100" style="max-width: 200px; max-height: 100px;">
<code>.overflow-y-scroll</code> example on an element with set width and height dimensions.
</div>
</div>
```html
<div class="overflow-y-auto">...</div>
<div class="overflow-y-hidden">...</div>
<div class="overflow-y-visible">...</div>
<div class="overflow-y-scroll">...</div>
```
Using Sass variables, you may customize the overflow utilities by changing the `$overflows` variable in `_variables.scss`.
## Sass
+1
View File
@@ -123,6 +123,7 @@
- title: Flex
- title: Float
- title: Interactions
- title: Object fit
- title: Opacity
- title: Overflow
- title: Position
+3 -1
View File
@@ -17,6 +17,7 @@
{{- $show_markup := .Get "show_markup" | default true -}}
{{- $show_preview := .Get "show_preview" | default true -}}
{{- $input := .Inner -}}
{{- $content := .Inner -}}
<div class="bd-example-snippet bd-code-snippet">
{{- if eq $show_preview true -}}
@@ -40,7 +41,8 @@
</div>
{{- end -}}
{{- $content := replaceRE `<svg class="bd-placeholder-img(?:-lg)?(?: *?bd-placeholder-img-lg)? ?(.*?)".*?<\/svg>\n` `<img src="..." class="$1" alt="...">` $input -}}
{{- $content = replaceRE `<svg class="bd-placeholder-img(?:-lg)?(?: *?bd-placeholder-img-lg)? ?(.*?)".*?<\/svg>` `<img src="..." class="$1" alt="...">` $content -}}
{{- $content = replaceRE `<img class="bd-placeholder-img(?:-lg)?(?: *?bd-placeholder-img-lg)? ?(.*?)".*?>` `<img src="..." class="$1" alt="...">` $content -}}
{{- $content = replaceRE ` (class=" *?")` "" $content -}}
{{- highlight (trim $content "\n") $lang "" -}}
{{- end -}}
+18 -6
View File
@@ -4,11 +4,12 @@
`args` are all optional and can be one of the following:
* title: Used in the SVG `title` tag - default: "Placeholder"
* text: The text to show in the image - default: "width x height"
* class: Class to add to the `svg` - default: "bd-placeholder-img"
* class: Class to add to the `svg` or `img` - default: "bd-placeholder-img"
* color: The text color (foreground) - default: "#dee2e6"
* background: The background color - default: "#868e96"
* width: default: "100%"
* height: default: "180px"
* markup: If it should render `svg` or `img` tags - default: "svg"
*/ -}}
{{- $grays := $.Site.Data.grays -}}
@@ -26,8 +27,19 @@
{{- $show_title := not (eq $title "false") -}}
{{- $show_text := not (eq $text "false") -}}
<svg class="bd-placeholder-img{{ with $class }} {{ . }}{{ end }}" width="{{ $width }}" height="{{ $height }}" xmlns="http://www.w3.org/2000/svg"{{ if (or $show_title $show_text) }} role="img" aria-label="{{ if $show_title }}{{ $title }}{{ if $show_text }}: {{ end }}{{ end }}{{ if ($show_text) }}{{ $text }}{{ end }}"{{ else }} aria-hidden="true"{{ end }} preserveAspectRatio="xMidYMid slice" focusable="false">
{{- if $show_title }}<title>{{ $title }}</title>{{ end -}}
<rect width="100%" height="100%" fill="{{ $background }}"/>
{{- if $show_text }}<text x="50%" y="50%" fill="{{ $color }}" dy=".3em">{{ $text }}</text>{{ end -}}
</svg>
{{- $markup := .Get "markup" | default "svg" -}}
{{- if eq $markup "img" -}}
<img class="bd-placeholder-img{{ with $class }} {{ . }}{{ end }}" alt="{{ $title }} : {{ $text }}" width="{{ $width }}" height="{{ $height }}" src="data:image/svg+xml,%3Csvg%20style='font-size:%201.125rem;%20font-family:system-ui,-apple-system,%22Segoe%20UI%22,Roboto,%22Helvetica%20Neue%22,%22Noto%20Sans%22,%22Liberation%20Sans%22,Arial,sans-serif,%22Apple%20Color%20Emoji%22,%22Segoe%20UI%20Emoji%22,%22Segoe%20UI%20Symbol%22,%22Noto%20Color%20Emoji%22;%20-webkit-user-select:%20none;%20-moz-user-select:%20none;%20user-select:%20none;%20text-anchor:%20middle;'%20width='200'%20height='200'%20xmlns='http://www.w3.org/2000/svg'%3E
{{- if $show_title }}%3Ctitle%3E{{ $title }}%3C/title%3E{{ end -}}
%3Crect%20width='100%25'%20height='100%25'%20fill='%23dee2e6'%3E%3C/rect%3E
{{- if $show_text }}%3Ctext%20x='50%25'%20y='50%25'%20fill='%23868e96'%20dy='.3em'%3E{{ $text }}%3C/text%3E{{ end -}}
%3C/svg%3E">
{{- else -}}
<svg class="bd-placeholder-img{{ with $class }} {{ . }}{{ end }}" width="{{ $width }}" height="{{ $height }}" xmlns="http://www.w3.org/2000/svg"{{ if (or $show_title $show_text) }} role="img" aria-label="{{ if $show_title }}{{ $title }}{{ if $show_text }}: {{ end }}{{ end }}{{ if ($show_text) }}{{ $text }}{{ end }}"{{ else }} aria-hidden="true"{{ end }} preserveAspectRatio="xMidYMid slice" focusable="false">
{{- if $show_title }}<title>{{ $title }}</title>{{ end -}}
<rect width="100%" height="100%" fill="{{ $background }}"/>
{{- if $show_text }}<text x="50%" y="50%" fill="{{ $color }}" dy=".3em">{{ $text }}</text>{{ end -}}
</svg>
{{- end -}}
@@ -1,19 +0,0 @@
// Example starter JavaScript for disabling form submissions if there are invalid fields
(() => {
'use strict'
// Fetch all the forms we want to apply custom Bootstrap validation styles to
const forms = document.querySelectorAll('.needs-validation')
// Loop over them and prevent submission
Array.from(forms).forEach(form => {
form.addEventListener('submit', event => {
if (!form.checkValidity()) {
event.preventDefault()
event.stopPropagation()
}
form.classList.add('was-validated')
}, false)
})
})()