@natachah/vanilla-frontend 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitlab-ci.yml +40 -0
- package/LICENSE.md +7 -0
- package/README.md +11 -0
- package/docs/index.html +36 -0
- package/docs/main.js +32 -0
- package/docs/pages/components/badge.html +154 -0
- package/docs/pages/components/button.html +186 -0
- package/docs/pages/components/card.html +184 -0
- package/docs/pages/components/dialog.html +334 -0
- package/docs/pages/components/disclosure.html +310 -0
- package/docs/pages/components/dropdown.html +255 -0
- package/docs/pages/components/form.html +331 -0
- package/docs/pages/components/list.html +140 -0
- package/docs/pages/components/loading.html +58 -0
- package/docs/pages/components/media.html +130 -0
- package/docs/pages/components/nav.html +119 -0
- package/docs/pages/components/progress.html +47 -0
- package/docs/pages/components/slider.html +311 -0
- package/docs/pages/components/table.html +168 -0
- package/docs/pages/javascript/autofill.html +170 -0
- package/docs/pages/javascript/checkall.html +59 -0
- package/docs/pages/javascript/comfort.html +134 -0
- package/docs/pages/javascript/consent.html +112 -0
- package/docs/pages/javascript/cookie.html +81 -0
- package/docs/pages/javascript/form.html +199 -0
- package/docs/pages/javascript/scroll.html +209 -0
- package/docs/pages/javascript/sidebar.html +53 -0
- package/docs/pages/javascript/sortable.html +148 -0
- package/docs/pages/javascript/toggle.html +191 -0
- package/docs/pages/javascript/tree.html +221 -0
- package/docs/pages/layout/grid.html +201 -0
- package/docs/pages/layout/reset.html +53 -0
- package/docs/pages/layout/typography.html +324 -0
- package/docs/pages/quick-start/conventions.html +112 -0
- package/docs/pages/quick-start/customization.html +187 -0
- package/docs/pages/quick-start/installation.html +95 -0
- package/docs/pages/quick-start/mixins.html +228 -0
- package/docs/pages/test.html +15 -0
- package/docs/src/js/demo.js +98 -0
- package/docs/src/js/doc-code.js +102 -0
- package/docs/src/js/doc-demo.js +14 -0
- package/docs/src/js/doc-layout.js +108 -0
- package/docs/src/scss/demo.scss +77 -0
- package/docs/src/scss/layout.scss +160 -0
- package/docs/src/scss/style.scss +278 -0
- package/docs/vite.config.mjs +23 -0
- package/esbuild.mjs +25 -0
- package/js/_autofill.js +131 -0
- package/js/_check-all.js +77 -0
- package/js/_comfort.js +174 -0
- package/js/_consent.js +84 -0
- package/js/_dialog.js +164 -0
- package/js/_dropdown.js +101 -0
- package/js/_scroll.js +184 -0
- package/js/_sidebar.js +97 -0
- package/js/_slider.js +249 -0
- package/js/_sortable.js +143 -0
- package/js/_tabpanel.js +88 -0
- package/js/_toggle.js +123 -0
- package/js/_tree.js +85 -0
- package/js/tests/autofill.test.js +157 -0
- package/js/tests/base-component.test.js +108 -0
- package/js/tests/check-all.test.js +88 -0
- package/js/tests/comfort.test.js +219 -0
- package/js/tests/consent.test.js +84 -0
- package/js/tests/cookie.test.js +102 -0
- package/js/tests/dialog.test.js +189 -0
- package/js/tests/dropdown.test.js +115 -0
- package/js/tests/form-helper.test.js +155 -0
- package/js/tests/scroll.test.js +203 -0
- package/js/tests/sidebar.test.js +99 -0
- package/js/tests/slider.test.js +307 -0
- package/js/tests/sortable.test.js +124 -0
- package/js/tests/tabpanel.test.js +114 -0
- package/js/tests/toggle.test.js +190 -0
- package/js/tests/tree.test.js +165 -0
- package/js/utilities/_base-component.js +101 -0
- package/js/utilities/_cookie.js +98 -0
- package/js/utilities/_error.js +80 -0
- package/js/utilities/_form-helper.js +101 -0
- package/package.json +42 -0
- package/scss/_badge.scss +37 -0
- package/scss/_button.scss +34 -0
- package/scss/_card.scss +122 -0
- package/scss/_dialog.scss +116 -0
- package/scss/_disclosure.scss +101 -0
- package/scss/_dropdown.scss +68 -0
- package/scss/_form.scss +197 -0
- package/scss/_grid.scss +40 -0
- package/scss/_group.scss +57 -0
- package/scss/_list.scss +18 -0
- package/scss/_loading.scss +49 -0
- package/scss/_media.scss +37 -0
- package/scss/_nav.scss +72 -0
- package/scss/_progress.scss +40 -0
- package/scss/_slider.scss +35 -0
- package/scss/_table.scss +36 -0
- package/scss/utilities/_mixin.scss +322 -0
- package/scss/utilities/_reset.scss +145 -0
- package/scss/utilities/_typography.scss +107 -0
- package/scss/vanilla-frontend.scss +23 -0
- package/scss/variables/_root.scss +70 -0
- package/scss/variables/_setting.scss +63 -0
- package/vitest.config.js +7 -0
package/js/_sortable.js
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ------------------------------------------------------------------
|
|
3
|
+
* Sortable
|
|
4
|
+
* ------------------------------------------------------------------
|
|
5
|
+
* This class enable the functionality to sort a list or a table
|
|
6
|
+
*
|
|
7
|
+
* @author Natacha Herth
|
|
8
|
+
* @version 0.0.1
|
|
9
|
+
* @copyright Natacha Herth, design & web development
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import BaseComponent from './utilities/_base-component'
|
|
13
|
+
import ErrorMessage from './utilities/_error'
|
|
14
|
+
|
|
15
|
+
export default class Sortable extends BaseComponent {
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Creates an instance
|
|
19
|
+
*
|
|
20
|
+
* @param {HTMLElement} el - The HTML element
|
|
21
|
+
* @param {object} options - The custom options
|
|
22
|
+
* @constructor
|
|
23
|
+
*/
|
|
24
|
+
constructor(el, options = {}) {
|
|
25
|
+
|
|
26
|
+
// Run the SUPER constructor from BaseComponent
|
|
27
|
+
super(el, options)
|
|
28
|
+
|
|
29
|
+
// Define the properties
|
|
30
|
+
this._withHandle = this._element.querySelector('[data-handle]') ? true : false
|
|
31
|
+
this._current = null
|
|
32
|
+
|
|
33
|
+
// Init the event listener
|
|
34
|
+
this.#init()
|
|
35
|
+
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Init the event listener
|
|
40
|
+
*
|
|
41
|
+
* @private
|
|
42
|
+
*/
|
|
43
|
+
#init() {
|
|
44
|
+
|
|
45
|
+
// Prevent default animation at the drop
|
|
46
|
+
this._element.addEventListener('dragover', (e) => e.preventDefault())
|
|
47
|
+
|
|
48
|
+
// Events directly on the items
|
|
49
|
+
this.items.forEach((item) => {
|
|
50
|
+
|
|
51
|
+
// Toggle the [dragable] attribute
|
|
52
|
+
item.addEventListener('mousedown', (e) => { if (!this._withHandle || e.target.hasAttribute('data-handle')) item.draggable = true })
|
|
53
|
+
item.addEventListener('mouseup', () => item.draggable = false)
|
|
54
|
+
|
|
55
|
+
// Drag and drop events
|
|
56
|
+
item.addEventListener('dragstart', () => this.#drag(item))
|
|
57
|
+
item.addEventListener('dragenter', () => this.#move(item))
|
|
58
|
+
item.addEventListener('dragend', () => this.#drop(item))
|
|
59
|
+
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get the list of the items
|
|
66
|
+
*
|
|
67
|
+
* @return {object}
|
|
68
|
+
*/
|
|
69
|
+
get items() {
|
|
70
|
+
return this._element.querySelectorAll('[draggable]')
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Drag an item
|
|
75
|
+
*
|
|
76
|
+
* @param {HTMLElement} item - The current item
|
|
77
|
+
* @private
|
|
78
|
+
*/
|
|
79
|
+
#drag(item) {
|
|
80
|
+
|
|
81
|
+
// Check for errors
|
|
82
|
+
if (!(item instanceof HTMLElement)) throw new Error(ErrorMessage.instanceOf('item', 'HTMLElement'))
|
|
83
|
+
|
|
84
|
+
// Prevent drag on text selection
|
|
85
|
+
if (!item.draggable) return
|
|
86
|
+
|
|
87
|
+
// Set the current property
|
|
88
|
+
this._current = item
|
|
89
|
+
|
|
90
|
+
// Add the attribute on the current
|
|
91
|
+
item.setAttribute('aria-grabbed', true)
|
|
92
|
+
|
|
93
|
+
// Run custom event
|
|
94
|
+
this.emmitEvent('drag', { items: this.items, current: item })
|
|
95
|
+
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* When dragging an item, move it before or after an element
|
|
100
|
+
*
|
|
101
|
+
* @param {HTMLElement} item - The current item
|
|
102
|
+
* @private
|
|
103
|
+
*/
|
|
104
|
+
#move(item) {
|
|
105
|
+
|
|
106
|
+
// Check for errors
|
|
107
|
+
if (!(item instanceof HTMLElement)) throw new Error(ErrorMessage.instanceOf('item', 'HTMLElement'))
|
|
108
|
+
|
|
109
|
+
// Do nothing if there is NO current
|
|
110
|
+
if (!this._current) return
|
|
111
|
+
|
|
112
|
+
// Move the item
|
|
113
|
+
const position = item === this._element.firstElementChild ? 'beforebegin' : 'afterend'
|
|
114
|
+
item.insertAdjacentElement(position, this._current)
|
|
115
|
+
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Drop an item
|
|
120
|
+
*
|
|
121
|
+
* @param {HTMLElement} item - The current item
|
|
122
|
+
* @private
|
|
123
|
+
*/
|
|
124
|
+
#drop(item) {
|
|
125
|
+
|
|
126
|
+
// Check for errors
|
|
127
|
+
if (!(item instanceof HTMLElement)) throw new Error(ErrorMessage.instanceOf('item', 'HTMLElement'))
|
|
128
|
+
|
|
129
|
+
// Reset the current property
|
|
130
|
+
this._current = null
|
|
131
|
+
|
|
132
|
+
// Remove the attribute on the current
|
|
133
|
+
item.removeAttribute('aria-grabbed', true)
|
|
134
|
+
|
|
135
|
+
// Reset draggable attribute
|
|
136
|
+
item.draggable = false
|
|
137
|
+
|
|
138
|
+
// Run custom event
|
|
139
|
+
this.emmitEvent('drop', { items: this.items, current: item })
|
|
140
|
+
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
}
|
package/js/_tabpanel.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ------------------------------------------------------------------
|
|
3
|
+
* Tabpanel
|
|
4
|
+
* ------------------------------------------------------------------
|
|
5
|
+
* This class enable the functionality to toggles multiple tabpanel
|
|
6
|
+
*
|
|
7
|
+
* @author Natacha Herth
|
|
8
|
+
* @version 0.0.1
|
|
9
|
+
* @copyright Natacha Herth, design & web development
|
|
10
|
+
*
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import BaseComponent from './utilities/_base-component'
|
|
14
|
+
import ErrorMessage from "./utilities/_error"
|
|
15
|
+
|
|
16
|
+
export default class Tabpanel extends BaseComponent {
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Creates an instance
|
|
20
|
+
*
|
|
21
|
+
* @param {HTMLElement} el - The HTML element
|
|
22
|
+
* @param {object} options - The custom options
|
|
23
|
+
* @constructor
|
|
24
|
+
*/
|
|
25
|
+
constructor(el, options = {}) {
|
|
26
|
+
|
|
27
|
+
// Run the SUPER constructor from BaseComponent
|
|
28
|
+
super(el, options)
|
|
29
|
+
|
|
30
|
+
// Check for errors
|
|
31
|
+
if (!el.querySelector('[role="tab"]')) throw new Error(ErrorMessage.exist('button [role="tab"]'))
|
|
32
|
+
if (!el.querySelector('[role="tabpanel"]')) throw new Error(ErrorMessage.exist('div [role="tabpanel"]'))
|
|
33
|
+
|
|
34
|
+
// Define the properties
|
|
35
|
+
this._tabs = this._element.querySelectorAll('[role="tab"]')
|
|
36
|
+
this._panels = this._element.querySelectorAll('[role="tabpanel"]')
|
|
37
|
+
this._current = null
|
|
38
|
+
|
|
39
|
+
// Init the event listener
|
|
40
|
+
this.#init()
|
|
41
|
+
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Init the event listener
|
|
46
|
+
*
|
|
47
|
+
* @private
|
|
48
|
+
*/
|
|
49
|
+
#init() {
|
|
50
|
+
|
|
51
|
+
// Init first
|
|
52
|
+
if (!this._current) this.#change(this._element.querySelector('[role="tab"][aria-selected="true"]') ?? this._tabs[0])
|
|
53
|
+
|
|
54
|
+
// CLICK tabs
|
|
55
|
+
this._tabs.forEach(tab => tab.addEventListener('click', () => this.#change(tab)))
|
|
56
|
+
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Change the tabpanel
|
|
61
|
+
* @param {HTMLElement} button - A <button> element
|
|
62
|
+
*
|
|
63
|
+
* @private
|
|
64
|
+
*/
|
|
65
|
+
#change(button) {
|
|
66
|
+
|
|
67
|
+
// Stop here if the tab is already the current one
|
|
68
|
+
if (button === this._current) return
|
|
69
|
+
|
|
70
|
+
// Get the [aria-controls] atribute
|
|
71
|
+
const arialist = button.hasAttribute('aria-controls') ? button.getAttribute('aria-controls') : null
|
|
72
|
+
if (!arialist) return
|
|
73
|
+
|
|
74
|
+
// Change the current current
|
|
75
|
+
this._current = button
|
|
76
|
+
|
|
77
|
+
// Change the [aria-selected] attribute on tabs
|
|
78
|
+
this._tabs.forEach((tab) => tab.setAttribute('aria-selected', tab === this._current))
|
|
79
|
+
|
|
80
|
+
// Change the [hidden] attribute on tabpanels
|
|
81
|
+
this._panels.forEach((panel) => panel.hidden = panel.id !== arialist)
|
|
82
|
+
|
|
83
|
+
// Run event after changed
|
|
84
|
+
this.emmitEvent('changed', { current: this._current })
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
}
|
package/js/_toggle.js
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ------------------------------------------------------------------
|
|
3
|
+
* Toggle
|
|
4
|
+
* ------------------------------------------------------------------
|
|
5
|
+
* This class enable the functionality to un/collapse some element by another
|
|
6
|
+
*
|
|
7
|
+
* @author Natacha Herth
|
|
8
|
+
* @version 0.0.1
|
|
9
|
+
* @copyright Natacha Herth, design & web development
|
|
10
|
+
*
|
|
11
|
+
* * NOTE: Check availability of the ariaControlsElements to select the #ids
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import BaseComponent from './utilities/_base-component'
|
|
15
|
+
|
|
16
|
+
export default class Toggle extends BaseComponent {
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Creates an instance
|
|
20
|
+
*
|
|
21
|
+
* @param {HTMLElement} el - The HTML element
|
|
22
|
+
* @param {object} options - The custom options
|
|
23
|
+
* @constructor
|
|
24
|
+
*/
|
|
25
|
+
constructor(el, options = {}) {
|
|
26
|
+
|
|
27
|
+
// Run the SUPER constructor from BaseComponent
|
|
28
|
+
super(el, options)
|
|
29
|
+
|
|
30
|
+
// Define the properties
|
|
31
|
+
this._type = this._element.tagName == 'SELECT' ? 'select' : this._element.getAttribute('type') ?? 'button'
|
|
32
|
+
|
|
33
|
+
const arialist = this._element.hasAttribute('aria-controls') ? this._element.getAttribute('aria-controls').split(' ').filter(id => document.getElementById(id)) : []
|
|
34
|
+
this._toggables = arialist.map(id => document.getElementById(id))
|
|
35
|
+
|
|
36
|
+
// Init the event listener
|
|
37
|
+
this.#init()
|
|
38
|
+
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Init the event listener
|
|
43
|
+
*
|
|
44
|
+
* @private
|
|
45
|
+
*/
|
|
46
|
+
#init() {
|
|
47
|
+
|
|
48
|
+
// On first load run method #toggle()
|
|
49
|
+
this.#toggle()
|
|
50
|
+
|
|
51
|
+
if (this._type === 'button') {
|
|
52
|
+
|
|
53
|
+
// CLICK for <button> and change the [aria-pressed] attribute
|
|
54
|
+
this._element.addEventListener('click', () => {
|
|
55
|
+
this._element.setAttribute('aria-pressed', this.value === 'false')
|
|
56
|
+
this.#toggle()
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
} else {
|
|
60
|
+
|
|
61
|
+
// CHANGE for <checkbox> <radio> <select>
|
|
62
|
+
this._element.addEventListener('change', () => this.#toggle())
|
|
63
|
+
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Define the value: <checkbox> and <radio> = :checked, <button> = [aria-pressed], <else> = value
|
|
70
|
+
*
|
|
71
|
+
* @return {string}
|
|
72
|
+
* @private
|
|
73
|
+
*/
|
|
74
|
+
get value() {
|
|
75
|
+
|
|
76
|
+
switch (this._type) {
|
|
77
|
+
case 'button':
|
|
78
|
+
return (this._element.getAttribute('aria-pressed') === 'true').toString()
|
|
79
|
+
|
|
80
|
+
case 'checkbox':
|
|
81
|
+
case 'radio':
|
|
82
|
+
return this._element.checked.toString()
|
|
83
|
+
|
|
84
|
+
default:
|
|
85
|
+
return this._element.value
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Toggle the [hidden] and [aria-expanded] attributes
|
|
92
|
+
*
|
|
93
|
+
* @private
|
|
94
|
+
*/
|
|
95
|
+
#toggle() {
|
|
96
|
+
|
|
97
|
+
// Define the group value for <select>
|
|
98
|
+
const groupValue = this._type === 'select' && this._element.querySelector('option:checked') ? this._element.querySelector('option:checked').parentElement.label ?? null : null
|
|
99
|
+
|
|
100
|
+
// Check for each toggables elements
|
|
101
|
+
this._toggables.forEach(toggable => {
|
|
102
|
+
|
|
103
|
+
// Define if there is a [data-toggle-when] attribute, otherwise check on the true value
|
|
104
|
+
const toggleValue = toggable.getAttribute('data-toggle-when') ?? 'true'
|
|
105
|
+
|
|
106
|
+
// Toggle the [hidden] attribute
|
|
107
|
+
toggable.hidden = this.value !== toggleValue && groupValue !== toggleValue
|
|
108
|
+
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
// Update the [aria-expanded] attribute if there is any toggables visible
|
|
112
|
+
const areAllHidden = this._toggables.every(el => el.hidden)
|
|
113
|
+
this._element.setAttribute('aria-expanded', !areAllHidden)
|
|
114
|
+
|
|
115
|
+
// If the _element is <radio>, emmit event 'change' on <radio> with same [name]
|
|
116
|
+
if (this._type === 'radio' && this._element.checked) document.querySelectorAll(`input[name="${this._element.name}"]:not(:checked)`).forEach(radio => radio.dispatchEvent(new Event('change')))
|
|
117
|
+
|
|
118
|
+
// Emmit event
|
|
119
|
+
this.emmitEvent('changed')
|
|
120
|
+
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
}
|
package/js/_tree.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ------------------------------------------------------------------
|
|
3
|
+
* Tree
|
|
4
|
+
* ------------------------------------------------------------------
|
|
5
|
+
* This class enable the functionality to expand/contract element as a tree
|
|
6
|
+
*
|
|
7
|
+
* @author Natacha Herth
|
|
8
|
+
* @version 0.0.1
|
|
9
|
+
* @copyright Natacha Herth, design & web development
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import BaseComponent from './utilities/_base-component'
|
|
13
|
+
|
|
14
|
+
export default class Tree extends BaseComponent {
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Creates an instance
|
|
18
|
+
*
|
|
19
|
+
* @param {HTMLElement} el - The HTML element
|
|
20
|
+
* @param {object} options - The custom options
|
|
21
|
+
* @constructor
|
|
22
|
+
*/
|
|
23
|
+
constructor(el, options = {}) {
|
|
24
|
+
|
|
25
|
+
// Run the SUPER constructor from BaseComponent
|
|
26
|
+
super(el, options)
|
|
27
|
+
|
|
28
|
+
// Define the properties
|
|
29
|
+
this._type = this._element.role == 'tree' ? 'list' : 'grid'
|
|
30
|
+
|
|
31
|
+
this._withHandle = this._element.querySelector('[data-handle]') ? true : false
|
|
32
|
+
|
|
33
|
+
this._items = this._element.querySelectorAll('[aria-expanded]')
|
|
34
|
+
|
|
35
|
+
// Init the event listener
|
|
36
|
+
this.#init()
|
|
37
|
+
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Init the event listener
|
|
42
|
+
*
|
|
43
|
+
* @private
|
|
44
|
+
*/
|
|
45
|
+
#init() {
|
|
46
|
+
|
|
47
|
+
this._items.forEach(item => item.addEventListener('click', (e) => {
|
|
48
|
+
if (!this._withHandle || e.target.hasAttribute('data-handle')) {
|
|
49
|
+
e.stopPropagation()
|
|
50
|
+
this.#toggle(item)
|
|
51
|
+
}
|
|
52
|
+
}))
|
|
53
|
+
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Toggle the [hidden] and [aria-expanded] attributes
|
|
58
|
+
*
|
|
59
|
+
* @param {HTMLElement} item - The current item
|
|
60
|
+
* @private
|
|
61
|
+
*/
|
|
62
|
+
#toggle(item) {
|
|
63
|
+
|
|
64
|
+
// Check for errors
|
|
65
|
+
if (!(item instanceof HTMLElement)) throw new Error(ErrorMessage.instanceOf('item', 'HTMLElement'))
|
|
66
|
+
|
|
67
|
+
// Update the [aria-expanded]
|
|
68
|
+
const isExpanded = item.getAttribute('aria-expanded') === 'true'
|
|
69
|
+
item.setAttribute('aria-expanded', !isExpanded)
|
|
70
|
+
|
|
71
|
+
// Get children to toggle
|
|
72
|
+
const children = item.hasAttribute('aria-owns') ? item.getAttribute('aria-owns').split(' ').filter(id => document.getElementById(id)).map(id => document.getElementById(id)) : []
|
|
73
|
+
|
|
74
|
+
// Toggle the [hidden] attribute on the children
|
|
75
|
+
children.forEach(child => child.hidden = isExpanded)
|
|
76
|
+
|
|
77
|
+
// If type grid, collapse the subchildren
|
|
78
|
+
if (this._type === 'grid' && isExpanded) children.filter(child => child.hasAttribute('aria-expanded') && child.getAttribute('aria-expanded') === 'true').forEach(child => this.#toggle(child))
|
|
79
|
+
|
|
80
|
+
// Emmit event
|
|
81
|
+
this.emmitEvent('changed', { isOpen: !isExpanded })
|
|
82
|
+
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ------------------------------------------------------------------
|
|
3
|
+
* TEST for the _autofill.js
|
|
4
|
+
* ------------------------------------------------------------------
|
|
5
|
+
* The test will take care of:
|
|
6
|
+
* - Constructor: Passing the correct parameters
|
|
7
|
+
* - Constructor: Return the correct properties
|
|
8
|
+
* - #Init(): Change <select> will set a value in another input
|
|
9
|
+
* - #Init(): Change <input> will set a value in another input
|
|
10
|
+
* - #Init(): Change <input type="file"> will set a value in another input
|
|
11
|
+
* - #ByOption(): Emmit the autofill:changed event
|
|
12
|
+
* - #byFile(): Emmit the autofill:changed event
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { describe, test, expect, beforeAll, vi } from "vitest"
|
|
16
|
+
import { fireEvent } from "@testing-library/dom"
|
|
17
|
+
import Autofill from "../_autofill"
|
|
18
|
+
import ErrorMessage from "../utilities/_error"
|
|
19
|
+
|
|
20
|
+
let fakeSelect, fakeList, fakeFile, fakeAutofillSelect, fakeAutofillList, fakeAutofillFile
|
|
21
|
+
/**
|
|
22
|
+
* Before all tests
|
|
23
|
+
*
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
beforeAll(() => {
|
|
27
|
+
|
|
28
|
+
document.body.innerHTML =
|
|
29
|
+
'<select id="fakeSelect" aria-controls="fakeSelectInput">' +
|
|
30
|
+
'<option value="null">--</option>' +
|
|
31
|
+
'<option value="cat" data-id="1">Cat</option>' +
|
|
32
|
+
'</select>' +
|
|
33
|
+
'<input id="fakeSelectInput" type="text" data-autofill="id">' +
|
|
34
|
+
|
|
35
|
+
'<input id="fakeList" list="fakeDataList" aria-controls="fakeListInput">' +
|
|
36
|
+
'<datalist id="fakeDataList">' +
|
|
37
|
+
'<option data-id="1" value="cat">' +
|
|
38
|
+
'<option data-id="2" value="dog">' +
|
|
39
|
+
'</datalist>' +
|
|
40
|
+
'<input id="fakeListInput" type="text" data-autofill="id">' +
|
|
41
|
+
|
|
42
|
+
'<input id="fakeFile" type="file" aria-controls="fakeFileInput fakeFileName fakeFileExt">' +
|
|
43
|
+
'<input id="fakeFileInput" type="text" data-autofill="name" >' +
|
|
44
|
+
'<input id="fakeFileName" type="text" data-autofill="filename">' +
|
|
45
|
+
'<input id="fakeFileExt" type="text" data-autofill="extension">'
|
|
46
|
+
|
|
47
|
+
fakeSelect = document.getElementById('fakeSelect')
|
|
48
|
+
fakeList = document.getElementById('fakeList')
|
|
49
|
+
fakeFile = document.getElementById('fakeFile')
|
|
50
|
+
|
|
51
|
+
fakeAutofillSelect = new Autofill(fakeSelect)
|
|
52
|
+
fakeAutofillList = new Autofill(fakeList)
|
|
53
|
+
fakeAutofillFile = new Autofill(fakeFile)
|
|
54
|
+
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
describe('Structure of the class', () => {
|
|
58
|
+
|
|
59
|
+
test('Constructor: Passing the correct parameters', () => {
|
|
60
|
+
expect(() => new Autofill('make-error')).toThrowError(ErrorMessage.instanceOf('el', 'HTMLElement'))
|
|
61
|
+
expect(() => new Autofill(document.createElement('button'))).toThrowError(ErrorMessage.typeOf('el', 'input|select'))
|
|
62
|
+
expect(() => new Autofill(document.createElement('select'))).toThrowError(ErrorMessage.withAttribute('el', 'aria-controls'))
|
|
63
|
+
const fakeWrongInput = document.createElement('input')
|
|
64
|
+
fakeWrongInput.setAttribute('aria-controls', 'something')
|
|
65
|
+
fakeWrongInput.type = 'checkbox'
|
|
66
|
+
expect(() => new Autofill(fakeWrongInput)).toThrowError(ErrorMessage.typeOf('type', 'text|file'))
|
|
67
|
+
fakeWrongInput.type = 'text'
|
|
68
|
+
expect(() => new Autofill(fakeWrongInput)).toThrowError(ErrorMessage.withAttribute('input', 'list'))
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test('Constructor: Return the correct properties', () => {
|
|
72
|
+
expect(fakeAutofillSelect).toHaveProperty('_fields')
|
|
73
|
+
expect(fakeAutofillSelect).toHaveProperty('_type')
|
|
74
|
+
expect(fakeAutofillSelect._fields).toBeTypeOf('object')
|
|
75
|
+
expect(fakeAutofillSelect._fields).toStrictEqual([...document.querySelectorAll('#fakeSelectInput')])
|
|
76
|
+
expect(fakeAutofillSelect._type).toBe('select')
|
|
77
|
+
expect(fakeAutofillList._fields).toBeTypeOf('object')
|
|
78
|
+
expect(fakeAutofillList._fields).toStrictEqual([...document.querySelectorAll('#fakeListInput')])
|
|
79
|
+
expect(fakeAutofillList._type).toBe('datalist')
|
|
80
|
+
expect(fakeAutofillFile._fields).toBeTypeOf('object')
|
|
81
|
+
expect(fakeAutofillFile._fields).toStrictEqual([...document.querySelectorAll('#fakeFileInput,#fakeFileName,#fakeFileExt')])
|
|
82
|
+
expect(fakeAutofillFile._type).toBe('file')
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
describe('#Init()', () => {
|
|
88
|
+
|
|
89
|
+
test('Change <select> will set a value in another input', () => {
|
|
90
|
+
const fakeInput = document.getElementById('fakeSelectInput')
|
|
91
|
+
expect(fakeSelect.value).toBe('null')
|
|
92
|
+
expect(fakeInput.value).toBe('')
|
|
93
|
+
fireEvent.change(fakeSelect, { target: { value: 'cat' } })
|
|
94
|
+
expect(fakeSelect.value).toBe('cat')
|
|
95
|
+
expect(fakeInput.value).toBe('1')
|
|
96
|
+
fireEvent.change(fakeSelect, { target: { value: 'null' } })
|
|
97
|
+
expect(fakeSelect.value).toBe('null')
|
|
98
|
+
expect(fakeInput.value).toBe('')
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test('Change <input> will set a value in another input', () => {
|
|
102
|
+
const fakeInput = document.getElementById('fakeListInput')
|
|
103
|
+
expect(fakeList.value).toBe('')
|
|
104
|
+
expect(fakeInput.value).toBe('')
|
|
105
|
+
fireEvent.change(fakeList, { target: { value: 'dog' } })
|
|
106
|
+
expect(fakeList.value).toBe('dog')
|
|
107
|
+
expect(fakeInput.value).toBe('2')
|
|
108
|
+
fireEvent.change(fakeList, { target: { value: 'cat' } })
|
|
109
|
+
expect(fakeList.value).toBe('cat')
|
|
110
|
+
expect(fakeInput.value).toBe('1')
|
|
111
|
+
fireEvent.change(fakeList, { target: { value: '' } })
|
|
112
|
+
expect(fakeList.value).toBe('')
|
|
113
|
+
expect(fakeInput.value).toBe('')
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
test('Change <input type="file"> will set a value in another input', () => {
|
|
117
|
+
const fakeFileInput = document.getElementById('fakeFileInput')
|
|
118
|
+
const fakeFileName = document.getElementById('fakeFileName')
|
|
119
|
+
const fakeFileExt = document.getElementById('fakeFileExt')
|
|
120
|
+
const fakeImage = new File(["my testing file"], "my-file.jpg", {
|
|
121
|
+
type: "image/jpeg",
|
|
122
|
+
})
|
|
123
|
+
expect(fakeFileInput.value).toBe('')
|
|
124
|
+
expect(fakeFileName.value).toBe('')
|
|
125
|
+
expect(fakeFileExt.value).toBe('')
|
|
126
|
+
fireEvent.change(fakeFile, { target: { files: [fakeImage] } })
|
|
127
|
+
expect(fakeFileInput.value).toBe('my-file.jpg')
|
|
128
|
+
expect(fakeFileName.value).toBe('my-file')
|
|
129
|
+
expect(fakeFileExt.value).toBe('jpg')
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
describe('#ByOption()', () => {
|
|
135
|
+
|
|
136
|
+
test('Emmit the autofill:changed event', () => {
|
|
137
|
+
const eventSpy = vi.spyOn(fakeAutofillSelect, 'emmitEvent')
|
|
138
|
+
expect(eventSpy).not.toHaveBeenCalled()
|
|
139
|
+
fireEvent.change(fakeSelect, { target: { value: 'cat' } })
|
|
140
|
+
expect(eventSpy).toHaveBeenCalledWith('changed', { current: document.querySelector('option[value="cat"]') })
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
describe('#ByFile()', () => {
|
|
146
|
+
|
|
147
|
+
test('Emmit the autofill:changed event', () => {
|
|
148
|
+
const fakeImage = new File(["my testing file"], "my-file.jpg", {
|
|
149
|
+
type: "image/jpeg",
|
|
150
|
+
})
|
|
151
|
+
const eventSpy = vi.spyOn(fakeAutofillFile, 'emmitEvent')
|
|
152
|
+
expect(eventSpy).not.toHaveBeenCalled()
|
|
153
|
+
fireEvent.change(fakeFile, { target: { files: [fakeImage] } })
|
|
154
|
+
expect(eventSpy).toHaveBeenCalledWith('changed', { current: fakeImage })
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
})
|