Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .bundlewatch.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,27 +34,27 @@
},
{
"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": {
Expand Down
1 change: 1 addition & 0 deletions js/index.esm.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 2 additions & 0 deletions js/index.umd.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -23,6 +24,7 @@ export default {
Button,
Carousel,
Collapse,
Form,
Dropdown,
Modal,
Offcanvas,
Expand Down
4 changes: 4 additions & 0 deletions js/src/base-component.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
125 changes: 125 additions & 0 deletions js/src/forms/form-field.js
Original file line number Diff line number Diff line change
@@ -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 changes: 157 additions & 0 deletions js/src/forms/form.js
Original file line number Diff line number Diff line change
@@ -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

4 changes: 1 addition & 3 deletions site/content/docs/5.2/examples/checkout-rtl/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
direction: rtl
extra_css:
- "../checkout/checkout.css"
extra_js:
- src: "../checkout/checkout.js"
body_class: "bg-light"
---

Expand Down Expand Up @@ -67,7 +65,7 @@ <h6 class="my-0">رمز ترويجي</h6>
</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>
Expand Down
19 changes: 0 additions & 19 deletions site/content/docs/5.2/examples/checkout/checkout.js

This file was deleted.

Loading