Skip to content

Commit c7ddff8

Browse files
authored
refactored chassis-control
1 parent 9968c84 commit c7ddff8

File tree

3 files changed

+318
-0
lines changed

3 files changed

+318
-0
lines changed

author-control/styles.css

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
:host {
2+
display: flex;
3+
contain: layout style;
4+
max-width: 100%;
5+
}
6+
7+
:host *,
8+
:host *:before,
9+
:host *:after {
10+
box-sizing: border-box;
11+
}
12+
13+
:host .hidden {
14+
display: none;
15+
visibility: hidden;
16+
opacity: 0;
17+
}
18+
19+
:host([type="field"]) {
20+
flex-direction: column;
21+
}
22+
23+
:host([type="toggle"]) {
24+
align-items: center;
25+
}
26+
27+
:host .label-wrapper {
28+
flex: 1 1 auto;
29+
display: flex;
30+
}
31+
32+
:host .input-wrapper {
33+
display: flex;
34+
align-items: center;
35+
}
36+
37+
:host([type="toggle"]) .input-wrapper {
38+
order: -1;
39+
justify-content: center;
40+
}
41+
42+
:host([type="select"]) {
43+
flex-direction: column;
44+
}

author-control/tag.js

Lines changed: 255 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,255 @@
1+
class AuthorFormControlElement extends HTMLElement {
2+
constructor () {
3+
super()
4+
5+
this.UTIL.defineAttributes({
6+
type: ''
7+
})
8+
9+
this.UTIL.defineProperties({
10+
input: {
11+
private: true
12+
},
13+
14+
fieldInputTypes: {
15+
readonly: true,
16+
private: true,
17+
default: [
18+
'color',
19+
'date',
20+
'datetime-local',
21+
'email',
22+
'file',
23+
'hidden',
24+
'image',
25+
'month',
26+
'number',
27+
'password',
28+
'range',
29+
'reset',
30+
'search',
31+
'submit',
32+
'tel',
33+
'text',
34+
'time',
35+
'url',
36+
'week',
37+
'textarea'
38+
]
39+
},
40+
41+
toggleInputTypes: {
42+
readonly: true,
43+
private: true,
44+
default: [
45+
'checkbox',
46+
'radio'
47+
],
48+
},
49+
50+
supportedTypes: {
51+
readonly: true,
52+
private: true,
53+
default: [
54+
'field',
55+
'toggle',
56+
'select'
57+
]
58+
}
59+
})
60+
61+
this.UTIL.definePrivateMethods({
62+
initDatalist: (input, datalist) => {
63+
this.type = 'field'
64+
65+
if (!customElements.get('author-datalist')) {
66+
console.dir(input);
67+
input.id = this.PRIVATE.guid
68+
datalist.id = `${input.id}_datalist`
69+
input.setAttribute('list', datalist.id)
70+
input.slot = input.slot || 'input'
71+
// select.setAttribute('role', 'menu')
72+
this.PRIVATE.input = input
73+
74+
let titleEls = datalist.querySelectorAll('option[title]')
75+
titleEls.forEach(el => select.removeChild(el))
76+
77+
for (let option of datalist.options) {
78+
if (option.hasAttribute('label') && option.getAttribute('label').trim() === '') {
79+
option.removeAttribute('label')
80+
}
81+
}
82+
83+
return
84+
}
85+
86+
let surrogate = document.createElement('author-datalist')
87+
surrogate.slot = 'input'
88+
89+
for (let attr of datalist.attributes) {
90+
if (attr.specified) {
91+
surrogate.setAttribute(attr.name, attr.value)
92+
93+
if (attr.name === 'autofocus') {
94+
datalist.removeAttribute(attr.name)
95+
}
96+
}
97+
}
98+
99+
this.removeChild(datalist)
100+
this.removeChild(input)
101+
102+
surrogate.inject(input, datalist, this.PRIVATE.guid)
103+
this.appendChild(surrogate)
104+
this.PRIVATE.input = surrogate
105+
},
106+
107+
initInput: input => {
108+
input.slot = input.slot || 'input'
109+
this.PRIVATE.input = input
110+
input.id = this.PRIVATE.guid
111+
112+
if (this.PRIVATE.fieldInputTypes.indexOf(input.type) >= 0) {
113+
this.type = 'field'
114+
}
115+
116+
if (this.PRIVATE.toggleInputTypes.indexOf(input.type) >= 0) {
117+
this.type = 'toggle'
118+
}
119+
},
120+
121+
initLabel: label => {
122+
this.label = label
123+
label.slot = label.slot || 'label'
124+
label.htmlFor = this.PRIVATE.guid
125+
126+
if (this.type === 'select') {
127+
this.label.addEventListener('click', (evt) => {
128+
this.input.focus()
129+
})
130+
}
131+
},
132+
133+
initDefaultSelect: select => {
134+
select.id = this.PRIVATE.guid
135+
select.slot = select.slot || 'input'
136+
select.setAttribute('role', 'menu')
137+
this.PRIVATE.input = select
138+
139+
// Purge incompatible attributes
140+
let titleEls = select.querySelectorAll('option[title]')
141+
titleEls.forEach(el => select.removeChild(el))
142+
143+
for (let option of select.options) {
144+
if (option.hasAttribute('label') && option.getAttribute('label').trim() === '') {
145+
option.removeAttribute('label')
146+
}
147+
}
148+
},
149+
150+
initMultipleSelectMenu: select => {
151+
this.type = 'select'
152+
153+
if (!customElements.get('author-select')) {
154+
return this.PRIVATE.initDefaultSelect(select)
155+
}
156+
157+
this.PRIVATE.initSelectSurrogate(select, document.createElement('author-select'))
158+
},
159+
160+
initSelectSurrogate: (original, surrogate) => {
161+
surrogate.slot = 'input'
162+
surrogate.id = this.PRIVATE.guid
163+
164+
for (let attr of original.attributes) {
165+
if (attr.specified) {
166+
surrogate.setAttribute(attr.name, attr.value)
167+
168+
if (attr.name === 'autofocus') {
169+
original.removeAttribute(attr.name)
170+
}
171+
}
172+
}
173+
174+
this.removeChild(original)
175+
surrogate.inject(original, this.querySelectorAll('label'))
176+
177+
this.appendChild(surrogate)
178+
this.PRIVATE.input = surrogate
179+
},
180+
181+
initSelectMenu: select => {
182+
this.type = 'select'
183+
184+
if (!customElements.get('author-select')) {
185+
return this.PRIVATE.initDefaultSelect(select)
186+
}
187+
188+
this.PRIVATE.initSelectSurrogate(select, document.createElement('author-select'))
189+
}
190+
})
191+
192+
this.UTIL.monitorChildren((mutations, observer) => {
193+
let filtered = mutations.filter(record => record.addedNodes.item(0).nodeType !== 3)
194+
195+
filtered.forEach((record, index, array) => {
196+
let node = record.addedNodes.item(0)
197+
198+
switch (node.nodeName) {
199+
case 'LABEL':
200+
return this.PRIVATE.initLabel(node)
201+
202+
case 'INPUT':
203+
// Check if there is an additional element adjacent to the input
204+
if (array[index + 1] === void 0) {
205+
return this.PRIVATE.initInput(node)
206+
}
207+
208+
let adjacentElement = array[index + 1].addedNodes.item(0)
209+
210+
if (!adjacentElement || adjacentElement.nodeName !== 'DATALIST') {
211+
return this.PRIVATE.initInput(node)
212+
}
213+
214+
return this.PRIVATE.initDatalist(node, adjacentElement)
215+
216+
case 'TEXTAREA':
217+
return this.PRIVATE.initInput(node)
218+
219+
case 'SELECT':
220+
if (!node.multiple) {
221+
return this.PRIVATE.initSelectMenu(node)
222+
}
223+
224+
return this.PRIVATE.initMultipleSelectMenu(node)
225+
226+
default: return
227+
}
228+
})
229+
230+
observer.disconnect()
231+
})
232+
233+
this.UTIL.registerListener(this, 'connected', () => {
234+
this.PRIVATE.guid = this.UTIL.generateGuid('control')
235+
})
236+
}
237+
238+
static get observedAttributes () {
239+
return ['disabled']
240+
}
241+
242+
get input () {
243+
return this.PRIVATE.input
244+
}
245+
246+
set input (input) {
247+
if (this.input) {
248+
return console.warn(`Setting <${this.localName}> child input programmatically is not allowed.`)
249+
}
250+
251+
this.PRIVATE.input = input
252+
}
253+
}
254+
255+
customElements.define('author-control', AuthorFormControlElement)

author-control/template.html

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<slot name="afterbegin"></slot>
2+
3+
<slot name="beforelabelwrapper"></slot>
4+
<div class="label-wrapper">
5+
<slot name="beforelabel"></slot>
6+
<slot name="label"></slot>
7+
<slot name="afterlabel"></slot>
8+
</div>
9+
<slot name="afterlabelwrapper"></slot>
10+
11+
<slot name="beforeinputwrapper"></slot>
12+
<div class="input-wrapper">
13+
<slot name="beforeinput"></slot>
14+
<slot name="input"></slot>
15+
<slot name="afterinput"></slot>
16+
</div>
17+
<slot name="afterinputwrapper"></slot>
18+
19+
<slot name="beforeend"></slot>

0 commit comments

Comments
 (0)