march-ui 0.0.5 → 0.0.6

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/Form/index.css CHANGED
@@ -73,6 +73,7 @@
73
73
  }
74
74
 
75
75
  .form__body .textfield,
76
+ .form__body .select,
76
77
  .form__body .button,
77
78
  .form__body .notice {
78
79
  max-width: none;
package/README.md CHANGED
@@ -81,6 +81,7 @@ Now open `index.html` in your favorite browser.
81
81
  - `Icon`
82
82
  - `Button`
83
83
  - `TextField`
84
+ - `Select`
84
85
  - `ContextMenu`
85
86
  - `MenuItem`
86
87
  - `Splitter`
@@ -0,0 +1,96 @@
1
+ html {
2
+
3
+ /* light theme */
4
+
5
+ --theme-light-select-foreground-color: var(--theme-light-foreground-color);
6
+ --theme-light-select-background-color: hsl(0,0%,95%);
7
+ --theme-light-select-border-color: hsl(0,0%,60%);
8
+ --theme-light-select-icon-color: hsl(0,0%,50%);
9
+ --theme-light-select-foreground-color-focused: var(--theme-light-foreground-color);
10
+ --theme-light-select-background-color-focused: hsl(0,0%,97%);
11
+ --theme-light-select-border-color-focused: hsl(0,0%,27%);
12
+ --theme-light-select-icon-color-focused: hsl(0,0%,25%);
13
+ --theme-light-select-foreground-color-disabled: hsl(0,0%,60%);
14
+ --theme-light-select-background-color-disabled: hsl(0,0%,97%);
15
+ --theme-light-select-border-color-disabled: hsl(0,0%,85%);
16
+ --theme-light-select-icon-color-disabled: hsl(0,0%,65%);
17
+
18
+ /* dark theme */
19
+
20
+ --theme-dark-select-foreground-color: var(--theme-dark-foreground-color);
21
+ --theme-dark-select-background-color: hsl(0,0%,16%);
22
+ --theme-dark-select-border-color: hsl(0,0%,33%);
23
+ --theme-dark-select-icon-color: hsl(0,0%,50%);
24
+ --theme-dark-select-foreground-color-focused: var(--theme-dark-foreground-color);
25
+ --theme-dark-select-background-color-focused: hsl(0,0%,18%);
26
+ --theme-dark-select-border-color-focused: hsl(0,0%,47%);
27
+ --theme-dark-select-icon-color-focused: hsl(0,0%,75%);
28
+ --theme-dark-select-foreground-color-disabled: hsl(0,0%,50%);
29
+ --theme-dark-select-background-color-disabled: hsl(0,0%,18%);
30
+ --theme-dark-select-border-color-disabled: hsl(0,0%,25%);
31
+ --theme-dark-select-icon-color-disabled: hsl(0,0%,30%);
32
+ }
33
+
34
+ .theme--light {
35
+ --select-foreground-color: var(--theme-light-select-foreground-color);
36
+ --select-background-color: var(--theme-light-select-background-color);
37
+ --select-border-color: var(--theme-light-select-border-color);
38
+ --select-icon-color: var(--theme-light-select-icon-color);
39
+ --select-foreground-color-focused: var(--theme-light-select-foreground-color-focused);
40
+ --select-background-color-focused: var(--theme-light-select-background-color-focused);
41
+ --select-border-color-focused: var(--theme-light-select-border-color-focused);
42
+ --select-icon-color-focused: var(--theme-light-select-icon-color-focused);
43
+ --select-foreground-color-disabled: var(--theme-light-select-foreground-color-disabled);
44
+ --select-background-color-disabled: var(--theme-light-select-background-color-disabled);
45
+ --select-border-color-disabled: var(--theme-light-select-border-color-disabled);
46
+ --select-icon-color-disabled: var(--theme-light-select-icon-color-disabled);
47
+ }
48
+
49
+ .theme--dark {
50
+ --select-foreground-color: var(--theme-dark-select-foreground-color);
51
+ --select-background-color: var(--theme-dark-select-background-color);
52
+ --select-border-color: var(--theme-dark-select-border-color);
53
+ --select-icon-color: var(--theme-dark-select-icon-color);
54
+ --select-foreground-color-focused: var(--theme-dark-select-foreground-color-focused);
55
+ --select-background-color-focused: var(--theme-dark-select-background-color-focused);
56
+ --select-border-color-focused: var(--theme-dark-select-border-color-focused);
57
+ --select-icon-color-focused: var(--theme-dark-select-icon-color-focused);
58
+ --select-foreground-color-disabled: var(--theme-dark-select-foreground-color-disabled);
59
+ --select-background-color-disabled: var(--theme-dark-select-background-color-disabled);
60
+ --select-border-color-disabled: var(--theme-dark-select-border-color-disabled);
61
+ --select-icon-color-disabled: var(--theme-dark-select-icon-color-disabled);
62
+ }
63
+
64
+ @media (prefers-color-scheme: light) {
65
+ html {
66
+ --select-foreground-color: var(--theme-light-select-foreground-color);
67
+ --select-background-color: var(--theme-light-select-background-color);
68
+ --select-border-color: var(--theme-light-select-border-color);
69
+ --select-icon-color: var(--theme-light-select-icon-color);
70
+ --select-foreground-color-focused: var(--theme-light-select-foreground-color-focused);
71
+ --select-background-color-focused: var(--theme-light-select-background-color-focused);
72
+ --select-border-color-focused: var(--theme-light-select-border-color-focused);
73
+ --select-icon-color-focused: var(--theme-light-select-icon-color-focused);
74
+ --select-foreground-color-disabled: var(--theme-light-select-foreground-color-disabled);
75
+ --select-background-color-disabled: var(--theme-light-select-background-color-disabled);
76
+ --select-border-color-disabled: var(--theme-light-select-border-color-disabled);
77
+ --select-icon-color-disabled: var(--theme-light-select-icon-color-disabled);
78
+ }
79
+ }
80
+
81
+ @media (prefers-color-scheme: dark) {
82
+ html {
83
+ --select-foreground-color: var(--theme-dark-select-foreground-color);
84
+ --select-background-color: var(--theme-dark-select-background-color);
85
+ --select-border-color: var(--theme-dark-select-border-color);
86
+ --select-icon-color: var(--theme-dark-select-icon-color);
87
+ --select-foreground-color-focused: var(--theme-dark-select-foreground-color-focused);
88
+ --select-background-color-focused: var(--theme-dark-select-background-color-focused);
89
+ --select-border-color-focused: var(--theme-dark-select-border-color-focused);
90
+ --select-icon-color-focused: var(--theme-dark-select-icon-color-focused);
91
+ --select-foreground-color-disabled: var(--theme-dark-select-foreground-color-disabled);
92
+ --select-background-color-disabled: var(--theme-dark-select-background-color-disabled);
93
+ --select-border-color-disabled: var(--theme-dark-select-border-color-disabled);
94
+ --select-icon-color-disabled: var(--theme-dark-select-icon-color-disabled);
95
+ }
96
+ }
@@ -0,0 +1,192 @@
1
+ .select {
2
+ display: flex;
3
+ flex-direction: column;
4
+ width: 100%;
5
+ max-width: 24em;
6
+ box-sizing: border-box;
7
+ gap: 0.25em;
8
+ -webkit-tap-highlight-color: transparent;
9
+ }
10
+
11
+ .select__field {
12
+ display: flex;
13
+ flex-direction: row;
14
+ gap: 0.5em;
15
+ padding-left: 0.5em;
16
+ padding-right: 0.5em;
17
+ min-height: 3em;
18
+ border-radius: 0.5em;
19
+ box-sizing: border-box;
20
+ border: 1px solid var(--select-border-color);
21
+ background-color: var(--select-background-color);
22
+ color: var(--select-foreground-color);
23
+ }
24
+
25
+ .select--readonly {
26
+ pointer-events: none;
27
+ }
28
+
29
+ .select--disabled {
30
+ pointer-events: none;
31
+ }
32
+
33
+ .select--disabled > .select__field {
34
+ border-color: var(--select-border-color-disabled);
35
+ background-color: var(--select-background-color-disabled);
36
+ color: var(--select-foreground-color-disabled);
37
+ }
38
+
39
+ .select--disabled .select__icon > *,
40
+ .select--disabled .select__state > * {
41
+ color: var(--select-icon-color-disabled);
42
+ }
43
+
44
+ .select--focused > .select__field {
45
+ border-color: var(--select-border-color-focused);
46
+ background-color: var(--select-background-color-focused);
47
+ color: var(--select-foreground-color-focused);
48
+ }
49
+
50
+ .select--focused > .select__title {
51
+ opacity: 1;
52
+ }
53
+
54
+ .select--focused > .select__hint {
55
+ opacity: 0.75;
56
+ }
57
+
58
+ .select__icon,
59
+ .select__state {
60
+ box-sizing: border-box;
61
+ flex-shrink: 0;
62
+ flex-grow: 0;
63
+ display: flex;
64
+ align-items: center;
65
+ justify-content: center;
66
+ color: var(--select-icon-color);
67
+ }
68
+
69
+ .select__state {
70
+ display: none;
71
+ }
72
+
73
+ .select__icon > *,
74
+ .select__state > * {
75
+ font-size: 1.5em;
76
+ color: inherit;
77
+ }
78
+
79
+ .select__input {
80
+ box-sizing: border-box;
81
+ width: 100%;
82
+ flex-shrink: 1;
83
+ flex-grow: 1;
84
+ }
85
+
86
+ .select__input select {
87
+ display: block;
88
+ border: 0;
89
+ outline: 0;
90
+ padding: 0;
91
+ background-color: transparent;
92
+ font-family: inherit;
93
+ font-size: inherit;
94
+ width: 100%;
95
+ height: 100%;
96
+ appearance: none;
97
+ cursor: text;
98
+ }
99
+
100
+ .select__input option {
101
+ color: var(--select-foreground-color);
102
+ background-color: var(--select-background-color);
103
+ }
104
+
105
+ .select__input option[selected], .select__input option[selected]:hover {
106
+ color: var(--select-foreground-color-focused);
107
+ background-color: var(--select-background-color-focused);
108
+ }
109
+
110
+ .select__hint {
111
+ font-size: 0.85em;
112
+ opacity: 0.5;
113
+ padding-left: 0.5em;
114
+ }
115
+
116
+ .select__hint--invisible {
117
+ display: none;
118
+ }
119
+
120
+ .select__title {
121
+ opacity: 0.75;
122
+ }
123
+
124
+ .select.select--required > .select__title::after {
125
+ content: '*';
126
+ color: var(--error-color);
127
+ }
128
+
129
+ .select__state--loading {
130
+ animation-name: spin;
131
+ animation-iteration-count: infinite;
132
+ animation-duration: 0.25s;
133
+ animation-timing-function: linear;
134
+ }
135
+
136
+ .select--valid .select__state--valid {
137
+ display: flex;
138
+ color: var(--success-color);
139
+ }
140
+
141
+ .select--invalid .select__state--invalid {
142
+ display: flex;
143
+ color: var(--error-color);
144
+ }
145
+
146
+ .select--loading .select__state--loading {
147
+ display: flex;
148
+ }
149
+
150
+ .select--valid .select__input,
151
+ .select--valid .select__title,
152
+ .select--valid .select__hint {
153
+ color: var(--success-color);
154
+ }
155
+
156
+ .select--valid .select__field {
157
+ border-color: var(--success-color);
158
+ }
159
+
160
+ .select--focused.select--valid .select__input,
161
+ .select--focused.select--valid .select__title,
162
+ .select--focused.select--valid .select__hint {
163
+ color: var(--success-color-focused);
164
+ }
165
+
166
+ .select--focused.select--valid .select__field {
167
+ border-color: var(--success-color-focused);
168
+ }
169
+
170
+ .select--invalid .select__input,
171
+ .select--invalid .select__title,
172
+ .select--invalid .select__hint {
173
+ color: var(--error-color);
174
+ }
175
+
176
+ .select.select--invalid .select__field {
177
+ border-color: var(--error-color);
178
+ }
179
+
180
+ .select--focused.select--invalid .select__input,
181
+ .select--focused.select--invalid .select__title,
182
+ .select--focused.select--invalid .select__hint {
183
+ color: var(--error-color-focused);
184
+ }
185
+
186
+ .select--focused.select.select--invalid .select__field {
187
+ border-color: var(--error-color-focused);
188
+ }
189
+
190
+ .select--focused .select__icon > svg {
191
+ color: var(--select-icon-color-focused);
192
+ }
@@ -0,0 +1,3 @@
1
+ import type { VNode, Component } from '../Component';
2
+
3
+ export declare function Select(vnode: VNode): Component;
@@ -0,0 +1,195 @@
1
+ import './index.css'
2
+ import './colors.css'
3
+ import classNames from 'classnames'
4
+
5
+ marchUI.Select = {
6
+ messages: {
7
+ invalidValue: 'Invalid value',
8
+ pleaseWait: 'Please wait...'
9
+ }
10
+ }
11
+
12
+ const doNothing = () => {}
13
+
14
+ const defaultAttributes = {
15
+ selected: '',
16
+ items: [],
17
+ onblur: doNothing,
18
+ onfocus: doNothing,
19
+ oninput: doNothing,
20
+ onerror: doNothing,
21
+ validityDelay: 0,
22
+ validate: inputElement => {
23
+ inputElement.setCustomValidity('')
24
+ return { valid: inputElement.checkValidity() }
25
+ }
26
+ }
27
+
28
+ const Select = ({ attrs }) => {
29
+ let {
30
+ focused, state, hint,
31
+ onblur, onfocus, oninput, onerror,
32
+ validityDelay, validate, novalidate
33
+ } = {
34
+ ...defaultAttributes,
35
+ ...attrs
36
+ }
37
+ let timeoutId
38
+ let validationRequestId = 0
39
+ let validationProcess = false
40
+ const validateAsync = (requestId, inputElement) => () => {
41
+ Promise.resolve(validate(inputElement))
42
+ .then(result => {
43
+ if (validationRequestId === requestId) { // response is actual
44
+ const { valid, message } = result
45
+ state = valid ? 'valid' : 'invalid'
46
+ if (message !== undefined) {
47
+ inputElement.setCustomValidity(message)
48
+ hint = message
49
+ } else if (valid) {
50
+ inputElement.setCustomValidity('')
51
+ hint = undefined
52
+ }
53
+ m.redraw()
54
+ }
55
+ })
56
+ .catch(error => {
57
+ state = 'invalid'
58
+ onerror(error)
59
+ m.redraw()
60
+ })
61
+ .finally(() => {
62
+ validationProcess = false
63
+ })
64
+ }
65
+ const validateInput = (inputElement, useRedraw) => {
66
+ if (!novalidate && !validationProcess) {
67
+ validationProcess = true
68
+ const prevState = state
69
+ state = 'loading'
70
+ hint = undefined
71
+ if (prevState !== state && useRedraw && validityDelay > 0) {
72
+ m.redraw()
73
+ }
74
+ const requestId = ++ validationRequestId
75
+ clearTimeout(timeoutId)
76
+ if (validityDelay > 0) {
77
+ timeoutId = setTimeout(
78
+ validateAsync(requestId, inputElement),
79
+ validityDelay
80
+ )
81
+ } else {
82
+ validateAsync(requestId, inputElement)()
83
+ }
84
+ }
85
+ }
86
+ const onblurHandler = evt => {
87
+ focused = false
88
+ return onblur(evt)
89
+ }
90
+ const onfocusHandler = evt => {
91
+ focused = true
92
+ return onfocus(evt)
93
+ }
94
+ const oninputHandler = evt => {
95
+ const result = oninput(evt)
96
+ validateInput(evt.target)
97
+ return result
98
+ }
99
+ const subscriptions = {}
100
+ const onFormValid = () => {
101
+ state = 'valid'
102
+ }
103
+ return {
104
+ onremove() {
105
+ Object.values(subscriptions)
106
+ .forEach(unsubscribe => unsubscribe())
107
+ },
108
+ onupdate({ dom }) {
109
+ const inputElement = dom.querySelector('select')
110
+ if (state === 'valid') {
111
+ inputElement.setCustomValidity('')
112
+ } else if (state === 'invalid') {
113
+ inputElement.setCustomValidity(hint || marchUI.Select.messages.invalidValue)
114
+ } else if (state === 'loading') {
115
+ inputElement.setCustomValidity(marchUI.Select.messages.pleaseWait)
116
+ }
117
+ },
118
+ view({ attrs }) {
119
+ const {
120
+ title, iconLeft, iconRight, hint: hintNext, required, readonly, disabled,
121
+ onblur: onblurNext, onfocus: onfocusNext, oninput: oninputNext,
122
+ validate: validateNext, pattern,
123
+ state: stateNext,
124
+ items, selected,
125
+ formRef,
126
+ ...rest
127
+ } = {
128
+ ...defaultAttributes,
129
+ ...attrs
130
+ }
131
+ onblur = onblurNext
132
+ onfocus = onfocusNext
133
+ oninput = oninputNext
134
+ validate = validateNext
135
+ if (stateNext !== undefined) {
136
+ state = stateNext
137
+ }
138
+ if (hintNext !== undefined) {
139
+ hint = hintNext
140
+ }
141
+ const classes = classNames('select', {
142
+ 'select--focused': focused,
143
+ 'select--required': required,
144
+ 'select--readonly': readonly,
145
+ 'select--disabled': disabled,
146
+ 'select--invalid': state === 'invalid',
147
+ 'select--valid': state === 'valid',
148
+ 'select--loading': state === 'loading'
149
+ })
150
+ const attributes = {
151
+ ...(required ? { required } : {}),
152
+ ...(readonly ? { readonly } : {}),
153
+ ...(disabled ? { disabled } : {}),
154
+ ...rest
155
+ }
156
+ if (formRef) {
157
+ subscriptions.formValid = formRef.subscribe('formValid', onFormValid)
158
+ }
159
+ return (
160
+ <label class={classes}>
161
+ {title && <div class="select__title">{title}</div>}
162
+ <div class="select__field">
163
+ {iconLeft && <div class="select__icon">{iconLeft}</div>}
164
+ <div class="select__input">
165
+ <select {...attributes}
166
+ onblur={onblurHandler}
167
+ onfocus={onfocusHandler}
168
+ oninput={oninputHandler}
169
+ >
170
+ {items.map(item => (
171
+ <option value={item.value} selected={item.value === selected}>
172
+ {item.name}
173
+ </option>
174
+ ))}
175
+ </select>
176
+ </div>
177
+ <div class="select__state select__state--valid">
178
+ <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M21 7L9 19l-5.5-5.5l1.41-1.41L9 16.17L19.59 5.59z"/></svg>
179
+ </div>
180
+ <div class="select__state select__state--invalid">
181
+ <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M11 15h2v2h-2zm0-8h2v6h-2zm1-5C6.47 2 2 6.5 2 12a10 10 0 0 0 10 10a10 10 0 0 0 10-10A10 10 0 0 0 12 2m0 18a8 8 0 0 1-8-8a8 8 0 0 1 8-8a8 8 0 0 1 8 8a8 8 0 0 1-8 8"/></svg>
182
+ </div>
183
+ <div class="select__state select__state--loading">
184
+ <svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" viewBox="0 0 24 24"><path fill="currentColor" d="M12 4V2A10 10 0 0 0 2 12h2a8 8 0 0 1 8-8"/></svg>
185
+ </div>
186
+ {iconRight && <div class="select__icon">{iconRight}</div>}
187
+ </div>
188
+ {hint && <div class="select__hint">{hint}</div>}
189
+ </label>
190
+ )
191
+ }
192
+ }
193
+ }
194
+
195
+ export default Select
package/index.d.ts CHANGED
@@ -7,6 +7,7 @@ export type { MenuItem } from './MenuItem/index.d.ts'
7
7
  export type { Notice } from './Notice/index.d.ts'
8
8
  export type { Splitter } from './Splitter/index.d.ts'
9
9
  export type { TextField } from './TextField/index.d.ts'
10
+ export type { Select } from './Select/index.d.ts'
10
11
  export type { ThemeToggler } from './ThemeToggler/index.d.ts'
11
12
  export type { Tab } from './Tab/index.d.ts'
12
13
  export type { Tabs } from './Tabs/index.d.ts'
package/index.js CHANGED
@@ -8,6 +8,7 @@ export { default as MenuItem } from './MenuItem'
8
8
  export { default as Notice } from './Notice'
9
9
  export { default as Splitter } from './Splitter'
10
10
  export { default as TextField } from './TextField'
11
+ export { default as Select } from './Select'
11
12
  export { default as ThemeToggler } from './ThemeToggler'
12
13
  export { default as Tab } from './Tab'
13
14
  export { default as Tabs } from './Tabs'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "march-ui",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "Lightweight UI-kit for Mithril.js",
5
5
  "type": "module",
6
6
  "main": "index.js",