Skip to content

Commit f01a81c

Browse files
committed
live-web-view.form: Added DateTime input.
live-web-view.form: Added Password input. live-web-view.form: Added SelectExtended input. live-web-view.form: Added Switch input.
1 parent 3aa9bda commit f01a81c

File tree

18 files changed

+1169
-17
lines changed

18 files changed

+1169
-17
lines changed

packages/live-web-view/docs/components/FormContainerDoc.lv

Lines changed: 433 additions & 8 deletions
Large diffs are not rendered by default.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import live-web.dom
2+
import live-web.behavior
3+
import live-web-view.button
4+
import live-elements-web-server.view
5+
import live-elements-web-server.style
6+
7+
component DateTimeInput < Div{
8+
id: dateTimeInput
9+
10+
static any[] use = [
11+
ScopedStyle{ src: 'flatpickr/dist/flatpickr.min.css' },
12+
ScopedStyle{ src: './style/datetimeinput.css' process: '../style/CSSProcessor.lv' },
13+
ScopedStyle{ src: '../style/global.css' process: '../style/CSSProcessor.lv' }
14+
]
15+
16+
string name
17+
string placeholder
18+
string value
19+
20+
boolean time: false
21+
boolean date: true
22+
boolean range: false
23+
24+
Object options = ({})
25+
26+
string currentValue: input.currentValue
27+
28+
props : ({
29+
data: {
30+
options: JSON.stringify(dateTimeInput.options)
31+
}
32+
})
33+
34+
string[] extraClasses: []
35+
classes: this.extraClasses.concat([ScopedStyle.className(DateTimeInput)])
36+
37+
Input{
38+
id: input
39+
type: 'text'
40+
name: dateTimeInput.name
41+
placeholder: dateTimeInput.placeholder
42+
value: dateTimeInput.value
43+
}
44+
45+
46+
DOMBehavior{
47+
domReady: async (d) => {
48+
if ( !window.__packages__ ){ window.__packages__ = {} }
49+
if ( !window.__packages__.flatpickr ){
50+
window.__packages__.flatpickr = (await import('flatpickr')).default
51+
}
52+
const flatpickr = window.__packages__.flatpickr
53+
54+
const input = d.querySelector('input')
55+
const options = JSON.parse(d.dataset.options)
56+
57+
const dpick = flatpickr(input, {
58+
appendTo: d,
59+
position: "auto",
60+
static: true,
61+
...options
62+
})
63+
}
64+
}
65+
}

packages/live-web-view/form/FormContainer.lv

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ component FormContainer < Form{
1212
TextInput,
1313
SubmitButton,
1414
Checkbox,
15+
Switch,
16+
SelectInput,
1517
ScopedStyle{ src: './style/form.css' process: '../style/CSSProcessor.lv' },
1618
ScopedStyle{ src: '../style/global.css' process: '../style/CSSProcessor.lv' }
1719
]

packages/live-web-view/form/FormGroup.lv

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,19 @@ import live-elements-web-server.view
44
import live-elements-web-server.style
55

66
component FormGroup < Div{
7+
static Object Layout = {
8+
Row: 0,
9+
Column: 1,
10+
Center: 2
11+
}
12+
Object layout = FormGroup.Layout.Col
13+
714
string[] extraClasses: []
8-
classes: this.extraClasses.concat(ScopedStyle.className(FormGroup))
15+
classes: this.extraClasses.concat(ScopedStyle.className(FormGroup)).concat(
16+
this.layout === FormGroup.Layout.Row
17+
? ['layout-row']
18+
: this.layout === FormGroup.Layout.Center
19+
? ['layout-center']
20+
: []
21+
)
922
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import live-web.dom
2+
import live-web.behavior
3+
import live-web-view.button
4+
import live-elements-web-server.view
5+
import live-elements-web-server.style
6+
7+
component PasswordInput < Div{
8+
id: passwordInput
9+
10+
static any[] use = [
11+
IconButton,
12+
ScopedStyle{ src: './style/passwordinput.css' process: '../style/CSSProcessor.lv' },
13+
ScopedStyle{ src: '../style/global.css' process: '../style/CSSProcessor.lv' }
14+
]
15+
16+
string name
17+
string placeholder
18+
string value
19+
20+
string currentValue: input.currentValue
21+
22+
string[] extraClasses: []
23+
classes: this.extraClasses.concat([ScopedStyle.className(PasswordInput)])
24+
25+
Input{
26+
id: input
27+
type: 'password'
28+
name: passwordInput.name
29+
placeholder: passwordInput.placeholder
30+
value: passwordInput.value
31+
}
32+
Button{
33+
Svg{ props = {width: '19', height: '15', viewBox: '0 0 26 20', fill: 'none', xmlns: 'http://www.w3.org/2000/svg'}
34+
Path{
35+
props = {d: 'M13 8C15.4289 8 17 9.53984 17 11C17 12.4602 15.4289 14 13 14C10.5711 14 9 12.4602 9 11C9 9.53984 10.5711 8 13 8Z', stroke: {'': 'currentColor',width: '2'}}
36+
}
37+
Path{
38+
props = {d: 'M1 11C2.63636 8.33333 7.21818 3 12.4545 3C17.6909 3 23 8 25 10.5', stroke: {'': 'currentColor',width: '2'}}
39+
}
40+
}
41+
42+
Svg{ props = {width: '19', height: '15', viewBox: '0 0 26 20', fill: 'none', xmlns: 'http://www.w3.org/2000/svg'}
43+
Path{
44+
props = {d: 'M13 8C15.4289 8 17 9.53984 17 11C17 12.4602 15.4289 14 13 14C10.5711 14 9 12.4602 9 11C9 9.53984 10.5711 8 13 8Z', stroke: {'': 'currentColor',width: '2'}}
45+
}
46+
Path{
47+
props = {d: 'M1 11C2.63636 8.33333 7.21818 3 12.4545 3C17.6909 3 23 8 25 10.5', stroke: {'': 'currentColor',width: '2'}}
48+
}
49+
Line{
50+
props = {x1: '6.23178', y1: '19.3598', x2: '21.2318', y2: '1.35982', stroke: {'': 'currentColor',width: '2'}}
51+
}
52+
}
53+
54+
}
55+
56+
57+
DOMBehavior{
58+
domReady: d => {
59+
const button = d.querySelector('button')
60+
const input = d.querySelector('input')
61+
button.onclick = e => {
62+
e.preventDefault()
63+
input.type = input.type === 'password' ? 'text' : 'password'
64+
if ( input.type === 'password' ){
65+
button.classList.remove('visible')
66+
} else {
67+
button.classList.add('visible')
68+
}
69+
}
70+
}
71+
72+
}
73+
74+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import live-web.dom
2+
import live-web.behavior
3+
import live-web-view.button
4+
import live-elements-web-server.view
5+
import live-elements-web-server.style
6+
7+
component SelectExtendedInput < Div{
8+
id: selectExtendedInput
9+
10+
static any[] use = [
11+
ScopedStyle{ src: 'choices.js/public/assets/styles/choices.css' },
12+
ScopedStyle{ src: './style/selectextendedinput.css' process: '../style/CSSProcessor.lv' },
13+
ScopedStyle{ src: '../style/global.css' process: '../style/CSSProcessor.lv' }
14+
]
15+
16+
string name
17+
18+
boolean time: false
19+
boolean date: true
20+
boolean range: false
21+
22+
Object options = ({})
23+
24+
string currentValue: input.currentValue
25+
26+
props : ({
27+
data: {
28+
options: JSON.stringify(selectExtendedInput.options)
29+
}
30+
})
31+
32+
string[] extraClasses: []
33+
classes: this.extraClasses.concat([ScopedStyle.className(SelectExtendedInput)])
34+
35+
Select{
36+
id: input
37+
name: selectExtendedInput.name
38+
}
39+
40+
41+
DOMBehavior{
42+
domReady: async (d) => {
43+
if ( !window.__packages__ ){ window.__packages__ = {} }
44+
if ( !window.__packages__.choicesjs ){
45+
window.__packages__.choicesjs = (await import('choices.js')).default
46+
}
47+
const Choices = window.__packages__.choicesjs
48+
const input = d.querySelector('select')
49+
const options = JSON.parse(d.dataset.options)
50+
51+
const chs = new Choices(input, options)
52+
}
53+
}
54+
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import live-web.dom
2+
import live-web.behavior
3+
import live-web
4+
import live-elements-web-server.view
5+
import live-elements-web-server.style
6+
7+
component SelectInput < Div{
8+
id: selectInput
9+
10+
static any[] use = [
11+
ScopedStyle{ src: './style/selectinput.css' process: '../style/CSSProcessor.lv' },
12+
ScopedStyle{ src: '../style/global.css' process: '../style/CSSProcessor.lv' }
13+
]
14+
15+
string[] extraClasses: []
16+
classes: this.extraClasses.concat([ScopedStyle.className(SelectInput)])
17+
18+
string placeholder: 'Select ...'
19+
string name: ''
20+
number tabIndex: 0
21+
string controlId
22+
Object[] options: []
23+
24+
Select{ name: selectInput.name props: ({ tabIndex: selectInput.tabIndex }) glid: selectInput.controlId
25+
children: []
26+
.concat(selectInput.placeholder
27+
? [Option{ value: '' props = { disabled: true, selected: true, hidden: true} T{ text: selectInput.placeholder } }]
28+
: [])
29+
.concat(selectInput.options.map(option => {
30+
return Option{ value: option.value T{ text: option.label } }
31+
}))
32+
}
33+
34+
Span{ classes: ['select-opener']
35+
T{ text: selectInput.placeholder }
36+
Svg{
37+
classes: ['select-caret']
38+
props = {viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor'}
39+
Path{
40+
props = {d: 'M6 9l6 6 6-6', stroke: {width: '2',linecap: 'round',linejoin: 'round'}}
41+
}
42+
}
43+
}
44+
45+
Ul{ classes: ['select-panel']
46+
children: selectInput.options.map(option => {
47+
return Li{ classes: ['select-panel-item'] props = ({ data: { value: option.value } }) T{ text: option.label} }
48+
})
49+
}
50+
51+
DOMBehavior{
52+
domReady: (d) => {
53+
const sel = d.querySelector('select')
54+
const opener = d.querySelector('.select-opener')
55+
const panel = d.querySelector('.select-panel')
56+
const items = Array.from(panel.querySelectorAll('li'))
57+
58+
opener.setAttribute('tabindex', sel.disabled ? '-1' : '0');
59+
60+
function setOpenerText(txt){
61+
const caret = opener.querySelector('.select-caret');
62+
if (caret){
63+
if (!opener.firstChild || opener.firstChild.nodeType !== window.Node.TEXT_NODE) {
64+
opener.insertBefore(document.createTextNode(''), caret);
65+
}
66+
opener.firstChild.nodeValue = txt
67+
} else {
68+
opener.textContent = txt
69+
}
70+
}
71+
72+
function syncFromSelect(){
73+
const opt = sel.options[sel.selectedIndex]
74+
setOpenerText(opt && opt.value ? opt.text : 'Select...')
75+
items.forEach(li => li.classList.toggle('selected', li.dataset.value === sel.value))
76+
}
77+
78+
function open(){
79+
if (!sel.disabled){
80+
d.classList.add('open');
81+
opener.focus();
82+
}
83+
}
84+
function close(){
85+
d.classList.remove('open');
86+
panel.querySelectorAll('.active').forEach(a=>a.classList.remove('active'));
87+
}
88+
89+
function choose(val){
90+
if (val !== sel.value){
91+
sel.value = val
92+
sel.dispatchEvent(new window.Event('change', { bubbles: true }))
93+
}
94+
syncFromSelect()
95+
close()
96+
}
97+
98+
syncFromSelect()
99+
100+
opener.addEventListener('click', () => {
101+
(d.classList.contains('open') ? close() : open())
102+
})
103+
items.forEach(li => li.addEventListener('mousedown', (e) => {
104+
e.preventDefault()
105+
choose(li.dataset.value)
106+
}))
107+
108+
opener.addEventListener('focusout', (e) => {
109+
close()
110+
})
111+
112+
opener.addEventListener('keydown', (e) => {
113+
const openKeys = ['Enter', ' ']
114+
if (e.key === 'Escape') {
115+
close()
116+
}
117+
if (d.classList.contains('open')) {
118+
const current = panel.querySelector('.active') || panel.querySelector('.selected')
119+
const idx = current ? items.indexOf(current) : -1
120+
if (e.key === 'ArrowDown') {
121+
e.preventDefault()
122+
items.forEach(i => i.classList.remove('active'))
123+
items[Math.min(idx + 1, items.length-1)]?.classList.add('active')
124+
}
125+
if (e.key === 'ArrowUp'){
126+
e.preventDefault()
127+
items.forEach(i => i.classList.remove('active'))
128+
items[Math.max(idx - 1, 0)]?.classList.add('active')
129+
}
130+
if (openKeys.includes(e.key) && panel.querySelector('.active')){
131+
e.preventDefault()
132+
choose(panel.querySelector('.active').dataset.value)
133+
}
134+
} else {
135+
if (openKeys.includes(e.key)) {
136+
e.preventDefault()
137+
d.classList.contains('open') ? close() : open()
138+
}
139+
}
140+
})
141+
}
142+
}
143+
144+
}

0 commit comments

Comments
 (0)