Skip to content

Commit 6475498

Browse files
committed
Merge branch 'dev' into feat-multi-select
# Conflicts: # scss/coreui.scss
2 parents 0bf39bf + d9540d2 commit 6475498

38 files changed

+590
-86
lines changed

js/index.esm.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import Carousel from './src/carousel'
1212
import ClassToggler from './src/class-toggler'
1313
import Collapse from './src/collapse'
1414
import Dropdown from './src/dropdown'
15+
import LoadingButton from './src/loading-button'
1516
import Modal from './src/modal'
1617
import Popover from './src/popover'
1718
import Scrollspy from './src/scrollspy'
@@ -29,6 +30,7 @@ export {
2930
ClassToggler,
3031
Collapse,
3132
Dropdown,
33+
LoadingButton,
3234
Modal,
3335
Popover,
3436
Scrollspy,

js/index.umd.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import Carousel from './src/carousel'
1212
import ClassToggler from './src/class-toggler'
1313
import Collapse from './src/collapse'
1414
import Dropdown from './src/dropdown'
15+
import LoadingButton from './src/loading-button'
1516
import Modal from './src/modal'
1617
import Popover from './src/popover'
1718
import Scrollspy from './src/scrollspy'
@@ -30,6 +31,7 @@ export default {
3031
ClassToggler,
3132
Collapse,
3233
Dropdown,
34+
LoadingButton,
3335
Modal,
3436
Popover,
3537
Scrollspy,

js/src/loading-button.js

Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
/**
2+
* --------------------------------------------------------------------------
3+
* CoreUI (v3.4.0): loading-button.js
4+
* Licensed under MIT (https://coreui.io/license)
5+
* --------------------------------------------------------------------------
6+
*/
7+
8+
import {
9+
getjQuery,
10+
TRANSITION_END,
11+
emulateTransitionEnd,
12+
getTransitionDurationFromElement,
13+
typeCheckConfig
14+
} from './util/index'
15+
import Data from './dom/data'
16+
import EventHandler from './dom/event-handler'
17+
import Manipulator from './dom/manipulator'
18+
19+
/**
20+
* ------------------------------------------------------------------------
21+
* Constants
22+
* ------------------------------------------------------------------------
23+
*/
24+
25+
const NAME = 'loading-button'
26+
const VERSION = '3.4.0'
27+
const DATA_KEY = 'coreui.loading-button'
28+
const EVENT_KEY = `.${DATA_KEY}`
29+
const DATA_API_KEY = '.data-api'
30+
31+
const MAX_PERCENT = 100
32+
const MILLISECONDS = 10
33+
const PROGRESS_BAR_BG_COLOR_LIGHT = 'rgba(255, 255, 255, .2)'
34+
const PROGRESS_BAR_BG_COLOR_DARK = 'rgba(0, 0, 0, .2)'
35+
36+
const SELECTOR_COMPONENT = '[data-coreui="loading-button"]'
37+
38+
const EVENT_START = `start${EVENT_KEY}`
39+
const EVENT_STOP = `stop${EVENT_KEY}`
40+
const EVENT_COMPLETE = `complete${EVENT_KEY}`
41+
const EVENT_LOAD_DATA_API = `load${EVENT_KEY}${DATA_API_KEY}`
42+
43+
const CLASS_NAME_LOADING_BUTTON_LOADING = 'c-loading-button-loading'
44+
const CLASS_NAME_LOADING_BUTTON_PROGRESS = 'c-loading-button-progress'
45+
const CLASS_NAME_LOADING_BUTTON_SPINNER = 'c-loading-button-spinner'
46+
47+
const Default = {
48+
percent: 0,
49+
progress: false,
50+
spinner: true,
51+
spinnerType: 'border',
52+
timeout: 1000
53+
}
54+
55+
const DefaultType = {
56+
percent: 'number',
57+
progress: 'boolean',
58+
spinner: 'boolean',
59+
spinnerType: 'string',
60+
timeout: 'number'
61+
}
62+
63+
/**
64+
* ------------------------------------------------------------------------
65+
* Class Definition
66+
* ------------------------------------------------------------------------
67+
*/
68+
69+
class LoadingButton {
70+
constructor(element, config) {
71+
this._element = element
72+
this._config = this._getConfig(config)
73+
this._pause = false
74+
this._percent = this._config.percent
75+
this._timeout = this._config.timeout
76+
this._progressBar = null
77+
this._spinner = null
78+
this._state = 'idle'
79+
80+
if (this._element) {
81+
Data.setData(element, DATA_KEY, this)
82+
}
83+
}
84+
85+
// Getters
86+
87+
static get VERSION() {
88+
return VERSION
89+
}
90+
91+
static get Default() {
92+
return Default
93+
}
94+
95+
static get DefaultType() {
96+
return DefaultType
97+
}
98+
99+
// Public
100+
101+
start() {
102+
if (this._state !== 'loading') {
103+
this._createSpinner()
104+
this._createProgressBar()
105+
106+
setTimeout(() => {
107+
this._element.classList.add(CLASS_NAME_LOADING_BUTTON_LOADING)
108+
this._loading()
109+
EventHandler.trigger(this._element, EVENT_START)
110+
}, 1)
111+
}
112+
}
113+
114+
stop() {
115+
this._element.classList.remove(CLASS_NAME_LOADING_BUTTON_LOADING)
116+
const stoped = () => {
117+
this._removeSpinner()
118+
this._removeProgressBar()
119+
this._state = 'idle'
120+
121+
EventHandler.trigger(this._element, EVENT_STOP)
122+
if (this._percent >= 100) {
123+
EventHandler.trigger(this._element, EVENT_COMPLETE)
124+
}
125+
126+
this._percent = this._config.percent
127+
this._timeout = this._config.timeout
128+
}
129+
130+
if (this._spinner) {
131+
const transitionDuration = getTransitionDurationFromElement(this._spinner)
132+
133+
EventHandler.one(this._spinner, TRANSITION_END, stoped)
134+
emulateTransitionEnd(this._spinner, transitionDuration)
135+
return
136+
}
137+
138+
stoped()
139+
}
140+
141+
pause() {
142+
this._pause = true
143+
this._state = 'pause'
144+
}
145+
146+
resume() {
147+
this._pause = false
148+
this._loading()
149+
}
150+
151+
complete() {
152+
this._timeout = 1000
153+
}
154+
155+
updatePercent(percent) {
156+
const diff = (this._percent - percent) / 100
157+
this._timeout *= (1 + diff)
158+
this._percent = percent
159+
}
160+
161+
dispose() {
162+
Data.removeData(this._element, DATA_KEY)
163+
this._element = null
164+
}
165+
166+
update(config) { // public method
167+
this._config = this._getConfig(config)
168+
}
169+
170+
_getConfig(config) {
171+
config = {
172+
...this.constructor.Default,
173+
...Manipulator.getDataAttributes(this._element),
174+
...config
175+
}
176+
177+
typeCheckConfig(
178+
NAME,
179+
config,
180+
this.constructor.DefaultType
181+
)
182+
183+
return config
184+
}
185+
186+
_loading() {
187+
const progress = setInterval(() => {
188+
this._state = 'loading'
189+
if (this._percent >= MAX_PERCENT) {
190+
this.stop()
191+
clearInterval(progress)
192+
return
193+
}
194+
195+
if (this._pause) {
196+
clearInterval(progress)
197+
return
198+
}
199+
200+
const frames = this._timeout / (MAX_PERCENT - this._percent) / MILLISECONDS
201+
this._percent = Math.round((this._percent + (1 / frames)) * 100) / 100
202+
this._timeout -= MILLISECONDS
203+
this._animateProgressBar()
204+
}, MILLISECONDS)
205+
}
206+
207+
_createProgressBar() {
208+
if (this._config.progress) {
209+
const progress = document.createElement('div')
210+
progress.classList.add(CLASS_NAME_LOADING_BUTTON_PROGRESS)
211+
progress.setAttribute('role', 'progressbar')
212+
progress.setAttribute('aria-hidden', 'true')
213+
progress.style.backgroundColor = this._progressBarBg()
214+
215+
this._element.insertBefore(progress, this._element.firstChild)
216+
this._progressBar = progress
217+
}
218+
}
219+
220+
_createSpinner() {
221+
if (this._config.spinner) {
222+
const spinner = document.createElement('span')
223+
const type = this._config.spinnerType
224+
spinner.classList.add(CLASS_NAME_LOADING_BUTTON_SPINNER, `spinner-${type}`, `spinner-${type}-sm`)
225+
spinner.setAttribute('role', 'status')
226+
spinner.setAttribute('aria-hidden', 'true')
227+
this._element.insertBefore(spinner, this._element.firstChild)
228+
this._spinner = spinner
229+
}
230+
}
231+
232+
_removeProgressBar() {
233+
if (this._config.progress) {
234+
this._progressBar.remove()
235+
this._progressBar = null
236+
}
237+
}
238+
239+
_removeSpinner() {
240+
if (this._config.spinner) {
241+
this._spinner.remove()
242+
this._spinner = null
243+
}
244+
}
245+
246+
_progressBarBg() {
247+
// The yiq lightness value that determines when the lightness of color changes from "dark" to "light". Acceptable values are between 0 and 255.
248+
const yiqContrastedThreshold = 150
249+
const color = window.getComputedStyle(this._element).getPropertyValue('background-color') === 'rgba(0, 0, 0, 0)' ? 'rgb(255, 255, 255)' : window.getComputedStyle(this._element).getPropertyValue('background-color')
250+
251+
const rgb = color.match(/^rgb?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i)
252+
253+
const r = parseInt(rgb[1], 10)
254+
const g = parseInt(rgb[2], 10)
255+
const b = parseInt(rgb[3], 10)
256+
257+
const yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000
258+
259+
if (yiq > yiqContrastedThreshold) {
260+
return PROGRESS_BAR_BG_COLOR_DARK
261+
}
262+
263+
return PROGRESS_BAR_BG_COLOR_LIGHT
264+
}
265+
266+
_animateProgressBar() {
267+
if (this._config.progress) {
268+
this._progressBar.style.width = `${this._percent}%`
269+
}
270+
}
271+
272+
// Static
273+
274+
static loadingButtonInterface(element, config, par) {
275+
let data = Data.getData(element, DATA_KEY)
276+
if (!data) {
277+
data = typeof config === 'object' ? new LoadingButton(element, config) : new LoadingButton(element)
278+
// data.start()
279+
}
280+
281+
if (typeof config === 'string') {
282+
if (typeof data[config] === 'undefined') {
283+
throw new TypeError(`No method named "${config}"`)
284+
}
285+
286+
// eslint-disable-next-line default-case
287+
switch (config) {
288+
case 'update':
289+
data[config](par)
290+
break
291+
case 'dispose':
292+
case 'start':
293+
case 'stop':
294+
case 'pause':
295+
case 'resume':
296+
case 'complete':
297+
case 'updatePercent':
298+
data[config]()
299+
break
300+
}
301+
}
302+
}
303+
304+
static jQueryInterface(config, par) {
305+
return this.each(function () {
306+
LoadingButton.loadingButtonInterface(this, config, par)
307+
})
308+
}
309+
310+
static getInstance(element) {
311+
return Data.getData(element, DATA_KEY)
312+
}
313+
}
314+
315+
/**
316+
* ------------------------------------------------------------------------
317+
* Data Api implementation
318+
* ------------------------------------------------------------------------
319+
*/
320+
321+
EventHandler.on(window, EVENT_LOAD_DATA_API, () => {
322+
// eslint-disable-next-line unicorn/prefer-spread
323+
Array.from(document.querySelectorAll(SELECTOR_COMPONENT)).forEach(element => {
324+
LoadingButton.loadingButtonInterface(element, Manipulator.getDataAttributes(element))
325+
})
326+
})
327+
328+
const $ = getjQuery()
329+
330+
/**
331+
* ------------------------------------------------------------------------
332+
* jQuery
333+
* ------------------------------------------------------------------------
334+
* add .loadingbutton to jQuery only if jQuery is present
335+
*/
336+
337+
/* istanbul ignore if */
338+
if ($) {
339+
const JQUERY_NO_CONFLICT = $.fn[NAME]
340+
$.fn[NAME] = LoadingButton.jQueryInterface
341+
$.fn[NAME].Constructor = LoadingButton
342+
$.fn[NAME].noConflict = () => {
343+
$.fn[NAME] = JQUERY_NO_CONFLICT
344+
return LoadingButton.jQueryInterface
345+
}
346+
}
347+
348+
export default LoadingButton

scss/_app.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@
77
background-color: themes-get-value("body-bg");
88

99
--color: #{themes-get-value("body-color")};
10+
--elevation-base-color: #{themes-get-value("elevation-base-color")};
1011
}
1112
}

0 commit comments

Comments
 (0)