fu-kit 0.7.2 → 1.0.1

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.
@@ -1,312 +1,312 @@
1
- <template>
2
- <label v-click-away="onClickAway" class="ui-select-x" v-bind="$attrs">
3
- <input
4
- ref="refSearch"
5
- v-model="search"
6
- :placeholder="placeholder"
7
- class="ui-select-x_input"
8
- spellcheck="false"
9
- tabindex="0"
10
- @focus="onTextFocus"
11
- @keydown="onTextKeydown"
12
- />
13
- <span v-show="filteredItems.length" ref="refList" class="ui-select-x_list" @keydown="onArrows">
14
- <button
15
- v-for="(e) in filteredItems"
16
- :class="{'_selected': e.value === model}"
17
- class="ui-select-x_list-item"
18
- tabindex="-1"
19
- type="button"
20
- @click="onSelect($event,e)"
21
- >
22
- {{ e.label }}
23
- </button>
24
- </span>
25
- </label>
26
- </template>
27
-
28
- <script>
29
- import { computed, defineComponent, nextTick, ref, watch } from 'vue'
30
- import { directive as clickAway } from 'vue3-click-away'
31
-
32
- import UiText from './UiText.vue'
33
- import UiButton from './UiButton.vue'
34
-
35
- export default defineComponent({
36
- name: 'ui-select-x',
37
- components: { UiButton, UiText },
38
- directives: { clickAway },
39
- props: {
40
- modelValue: { type: [ String, Number ], default: '' },
41
- options: { type: Array, default: [] },
42
- allowCustom: { type: Boolean, default: false },
43
- placeholder: { type: String },
44
- },
45
- emits: [ 'update:modelValue', 'select' ],
46
- setup (props, { emit }) {
47
- const refSearch = ref(null)
48
- const refList = ref(null)
49
- const search = ref(props.modelValue || '')
50
- const model = ref(props.modelValue)
51
-
52
- watch(() => props.modelValue, (value) => {
53
- model.value = value
54
- search.value = value
55
- })
56
-
57
- const normalItems = computed(() => {
58
- return props.options.map(o => {
59
- if (typeof o === 'object' && o !== null && !Array.isArray(o)) {
60
- return o
61
- } else {
62
- return { value: o, label: o }
63
- }
64
- })
65
- })
66
-
67
- const filteredItems = computed(() => {
68
- if (!search.value || search.value === model.value) return normalItems.value
69
- return normalItems.value.filter((i) => String(i.label).includes(search.value))
70
- })
71
-
72
- const selectedItem = computed(() => normalItems.value.find(o => model.value === o.value))
73
-
74
- const onSelect = (event, item) => {
75
- search.value = item.value
76
- model.value = item.value
77
- emit('update:modelValue', model.value)
78
- emit('select', model.value)
79
-
80
- event.target.blur()
81
- }
82
-
83
- const onTextFocus = async (e) => {
84
- search.value = model.value
85
- await nextTick()
86
- e.target.setSelectionRange(0, -1)
87
- }
88
-
89
- const onTextKeydown = (e) => {
90
- if (![ 'ArrowDown', 'ArrowUp', 'Enter', 'Escape' ].includes(e.key)) return
91
-
92
- if (e.key === 'Escape') return document.activeElement.blur()
93
-
94
- const nodes = Array.prototype.slice.call(refList.value.children)
95
-
96
- if (e.key === 'Enter') {
97
- if (nodes[0]) {
98
- nodes[0].click()
99
- } else if (props.allowCustom) {
100
- onSelect(e, { label: search.value, value: search.value })
101
- } else {
102
- search.value = model.value
103
- }
104
-
105
- e.preventDefault()
106
- e.target.blur()
107
-
108
- return
109
- }
110
-
111
- if (!nodes.length) return
112
-
113
- switch (e.key) {
114
- case 'ArrowDown':
115
- e.preventDefault()
116
- nodes[0].focus()
117
- break
118
- case 'ArrowUp':
119
- e.preventDefault()
120
- nodes[nodes.length - 1].focus()
121
- break
122
- }
123
-
124
- e.preventDefault()
125
- }
126
-
127
- const onArrows = (e) => {
128
- if (![ 'ArrowDown', 'ArrowUp', 'Escape' ].includes(e.key)) return
129
-
130
- if (e.key === 'Escape') return document.activeElement.blur()
131
-
132
- if (refList.value !== document.activeElement.parentElement) return
133
-
134
- switch (e.key) {
135
- case 'ArrowDown':
136
- e.preventDefault()
137
- focusJump(+1)
138
- break
139
- case 'ArrowUp':
140
- e.preventDefault()
141
- focusJump(-1)
142
- break
143
- }
144
- }
145
-
146
- const focusJump = (next = -1) => {
147
- const foElm = document.activeElement.parentElement
148
- if (foElm !== refList.value && foElm === refSearch.value) return
149
-
150
- const nodes = Array.prototype.slice.call(refList.value.children)
151
- const liRef = document.activeElement
152
- const fi = nodes.indexOf(liRef)
153
-
154
-
155
- if (next > 0) {
156
- if (fi === nodes.length - 1) return nodes[0].focus()
157
- if (nodes[fi + 1]) nodes[fi + 1].focus()
158
- } else if (next < 0) {
159
- if (fi === 0) return nodes[nodes.length - 1].focus()
160
- if (nodes[fi - 1]) nodes[fi - 1].focus()
161
- }
162
- }
163
-
164
- const onClickAway = () => {
165
- search.value = model.value
166
- }
167
-
168
- return {
169
- refSearch,
170
- refList,
171
- search,
172
- model,
173
- filteredItems,
174
- selectedItem,
175
- onTextKeydown,
176
- onArrows,
177
- onSelect,
178
- onTextFocus,
179
- onClickAway,
180
- }
181
- },
182
- })
183
- </script>
184
-
185
- <style lang="scss" scoped>
186
- @import "../../scss";
187
-
188
- .ui-select-x {
189
- @include typo(200);
190
-
191
- padding: 0;
192
- display: flex;
193
- box-sizing: border-box;
194
- align-items: center;
195
- justify-content: stretch;
196
- border-style: var(--ui-lt-border-style);
197
- border-width: var(--ui-lt-border-width);
198
- border-color: var(--ui-pal-lateral);
199
- border-radius: var(--ui-lt-border-radius);
200
- transition-duration: 240ms;
201
- transition-timing-function: ease-in-out;
202
- transition-property: border-color, box-shadow;
203
- height: var(--ui-lt-h);
204
- position: relative;
205
- background-color: var(--ui-pal-bg);
206
-
207
- &_input {
208
- @include typo(200);
209
- padding: spacing(100, 300);
210
-
211
- font-family: var(--typo-font-ui);
212
- color: var(--ui-pal-text);
213
- caret-color: var(--ui-pal);
214
- min-height: min(100%);
215
- border: none;
216
- outline: none;
217
- background: transparent;
218
- box-sizing: border-box;
219
- flex: 1;
220
- display: block;
221
- min-width: 0;
222
- margin: 0;
223
-
224
- &:focus {
225
- outline: none;
226
- }
227
-
228
- &::selection {
229
- background-color: var(--ui-pal);
230
- color: var(--ui-pal-text-select);
231
- }
232
-
233
- &::placeholder {
234
- color: var(--ui-pal-placeholder);
235
- }
236
- }
237
-
238
- &_list {
239
- @include scrollbar-awesome();
240
-
241
- overflow: auto;
242
- max-height: 30vh;
243
- max-width: 100vw;
244
- display: none;
245
- flex-direction: column;
246
- justify-content: stretch;
247
- border-width: var(--ui-lt-border-width);
248
- border-radius: var(--ui-lt-border-radius);
249
- border-color: var(--ui-pal);
250
- border-style: solid;
251
- position: absolute;
252
- left: calc(var(--ui-lt-border-width) * -1);
253
- top: 100%;
254
- min-width: calc(100% + var(--ui-lt-border-width) * 2);
255
- background: var(--ui-pal-bg);
256
- margin-top: spacing(200);
257
- z-index: var(--lt-z-pop);
258
-
259
- &-item {
260
- padding: spacing(200, 300);
261
- @include typo(200, 300);
262
-
263
- border: 0 none;
264
- text-decoration: none;
265
- border-radius: 0;
266
- justify-content: left;
267
- text-align: left;
268
- font-weight: inherit;
269
- font-family: var(--typo-font-ui);
270
- background: transparent;
271
- outline: none;
272
- color: var(--ui-pal-text);
273
-
274
- &:hover {
275
- background-color: var(--ui-pal-lateral);
276
- color: var(--ui-pal-text);
277
- }
278
-
279
- &._selected {
280
- color: var(--ui-pal-acc);
281
- background-color: var(--ui-pal);
282
- }
283
-
284
- &:focus {
285
- color: var(--ui-pal-acc);
286
- background-color: var(--ui-pal);
287
- }
288
- }
289
- }
290
-
291
- &:focus-within &_list {
292
- display: flex;
293
- }
294
-
295
- &:hover {
296
- outline: none;
297
- box-shadow: 0 5px 12px -4px rgb(var(--rgb-front), 0.2);
298
- }
299
-
300
- &:focus-within {
301
- outline: none;
302
- box-shadow: 0 0 0 0 var(--ui-pal);
303
- border-color: var(--ui-pal);
304
- }
305
-
306
- &._disabled {
307
- border: var(--ui-lt-border-width) var(--ui-lt-disabled-border-style) var(--ui-pal-disabled-border);
308
- background: transparent;
309
- box-shadow: none;
310
- }
311
- }
312
- </style>
1
+ <template>
2
+ <label v-click-away="onClickAway" class="ui-select-x" v-bind="$attrs">
3
+ <input
4
+ ref="refSearch"
5
+ v-model="search"
6
+ :placeholder="placeholder"
7
+ class="ui-select-x_input"
8
+ spellcheck="false"
9
+ tabindex="0"
10
+ @focus="onTextFocus"
11
+ @keydown="onTextKeydown"
12
+ />
13
+ <span v-show="filteredItems.length" ref="refList" class="ui-select-x_list" @keydown="onArrows">
14
+ <button
15
+ v-for="(e) in filteredItems"
16
+ :class="{'_selected': e.value === model}"
17
+ class="ui-select-x_list-item"
18
+ tabindex="-1"
19
+ type="button"
20
+ @click="onSelect($event,e)"
21
+ >
22
+ {{ e.label }}
23
+ </button>
24
+ </span>
25
+ </label>
26
+ </template>
27
+
28
+ <script>
29
+ import { computed, defineComponent, nextTick, ref, watch } from 'vue'
30
+ import { directive as clickAway } from 'vue3-click-away'
31
+
32
+ import UiText from './UiText.vue'
33
+ import UiButton from './UiButton.vue'
34
+
35
+ export default defineComponent({
36
+ name: 'ui-select-x',
37
+ components: { UiButton, UiText },
38
+ directives: { clickAway },
39
+ props: {
40
+ modelValue: { type: [ String, Number ], default: '' },
41
+ options: { type: Array, default: [] },
42
+ allowCustom: { type: Boolean, default: false },
43
+ placeholder: { type: String },
44
+ },
45
+ emits: [ 'update:modelValue', 'select' ],
46
+ setup (props, { emit }) {
47
+ const refSearch = ref(null)
48
+ const refList = ref(null)
49
+ const search = ref(props.modelValue || '')
50
+ const model = ref(props.modelValue)
51
+
52
+ watch(() => props.modelValue, (value) => {
53
+ model.value = value
54
+ search.value = value
55
+ })
56
+
57
+ const normalItems = computed(() => {
58
+ return props.options.map(o => {
59
+ if (typeof o === 'object' && o !== null && !Array.isArray(o)) {
60
+ return o
61
+ } else {
62
+ return { value: o, label: o }
63
+ }
64
+ })
65
+ })
66
+
67
+ const filteredItems = computed(() => {
68
+ if (!search.value || search.value === model.value) return normalItems.value
69
+ return normalItems.value.filter((i) => String(i.label).includes(search.value))
70
+ })
71
+
72
+ const selectedItem = computed(() => normalItems.value.find(o => model.value === o.value))
73
+
74
+ const onSelect = (event, item) => {
75
+ search.value = item.value
76
+ model.value = item.value
77
+ emit('update:modelValue', model.value)
78
+ emit('select', model.value)
79
+
80
+ event.target.blur()
81
+ }
82
+
83
+ const onTextFocus = async (e) => {
84
+ search.value = model.value
85
+ await nextTick()
86
+ e.target.setSelectionRange(0, -1)
87
+ }
88
+
89
+ const onTextKeydown = (e) => {
90
+ if (![ 'ArrowDown', 'ArrowUp', 'Enter', 'Escape' ].includes(e.key)) return
91
+
92
+ if (e.key === 'Escape') return document.activeElement.blur()
93
+
94
+ const nodes = Array.prototype.slice.call(refList.value.children)
95
+
96
+ if (e.key === 'Enter') {
97
+ if (nodes[0]) {
98
+ nodes[0].click()
99
+ } else if (props.allowCustom) {
100
+ onSelect(e, { label: search.value, value: search.value })
101
+ } else {
102
+ search.value = model.value
103
+ }
104
+
105
+ e.preventDefault()
106
+ e.target.blur()
107
+
108
+ return
109
+ }
110
+
111
+ if (!nodes.length) return
112
+
113
+ switch (e.key) {
114
+ case 'ArrowDown':
115
+ e.preventDefault()
116
+ nodes[0].focus()
117
+ break
118
+ case 'ArrowUp':
119
+ e.preventDefault()
120
+ nodes[nodes.length - 1].focus()
121
+ break
122
+ }
123
+
124
+ e.preventDefault()
125
+ }
126
+
127
+ const onArrows = (e) => {
128
+ if (![ 'ArrowDown', 'ArrowUp', 'Escape' ].includes(e.key)) return
129
+
130
+ if (e.key === 'Escape') return document.activeElement.blur()
131
+
132
+ if (refList.value !== document.activeElement.parentElement) return
133
+
134
+ switch (e.key) {
135
+ case 'ArrowDown':
136
+ e.preventDefault()
137
+ focusJump(+1)
138
+ break
139
+ case 'ArrowUp':
140
+ e.preventDefault()
141
+ focusJump(-1)
142
+ break
143
+ }
144
+ }
145
+
146
+ const focusJump = (next = -1) => {
147
+ const foElm = document.activeElement.parentElement
148
+ if (foElm !== refList.value && foElm === refSearch.value) return
149
+
150
+ const nodes = Array.prototype.slice.call(refList.value.children)
151
+ const liRef = document.activeElement
152
+ const fi = nodes.indexOf(liRef)
153
+
154
+
155
+ if (next > 0) {
156
+ if (fi === nodes.length - 1) return nodes[0].focus()
157
+ if (nodes[fi + 1]) nodes[fi + 1].focus()
158
+ } else if (next < 0) {
159
+ if (fi === 0) return nodes[nodes.length - 1].focus()
160
+ if (nodes[fi - 1]) nodes[fi - 1].focus()
161
+ }
162
+ }
163
+
164
+ const onClickAway = () => {
165
+ search.value = model.value
166
+ }
167
+
168
+ return {
169
+ refSearch,
170
+ refList,
171
+ search,
172
+ model,
173
+ filteredItems,
174
+ selectedItem,
175
+ onTextKeydown,
176
+ onArrows,
177
+ onSelect,
178
+ onTextFocus,
179
+ onClickAway,
180
+ }
181
+ },
182
+ })
183
+ </script>
184
+
185
+ <style lang="scss" scoped>
186
+ @import "../../scss";
187
+
188
+ .ui-select-x {
189
+ @include typo(200);
190
+
191
+ padding: 0;
192
+ display: flex;
193
+ box-sizing: border-box;
194
+ align-items: center;
195
+ justify-content: stretch;
196
+ border-style: var(--ui-lt-border-style);
197
+ border-width: var(--ui-lt-border-width);
198
+ border-color: var(--ui-pal-lateral);
199
+ border-radius: var(--ui-lt-border-radius);
200
+ transition-duration: 240ms;
201
+ transition-timing-function: ease-in-out;
202
+ transition-property: border-color, box-shadow;
203
+ height: var(--ui-lt-h);
204
+ position: relative;
205
+ background-color: var(--ui-pal-bg);
206
+
207
+ &_input {
208
+ @include typo(200);
209
+ padding: spacing(100, 300);
210
+
211
+ font-family: var(--typo-font-ui);
212
+ color: var(--ui-pal-text);
213
+ caret-color: var(--ui-pal);
214
+ min-height: min(100%);
215
+ border: none;
216
+ outline: none;
217
+ background: transparent;
218
+ box-sizing: border-box;
219
+ flex: 1;
220
+ display: block;
221
+ min-width: 0;
222
+ margin: 0;
223
+
224
+ &:focus {
225
+ outline: none;
226
+ }
227
+
228
+ &::selection {
229
+ background-color: var(--ui-pal);
230
+ color: var(--ui-pal-text-select);
231
+ }
232
+
233
+ &::placeholder {
234
+ color: var(--ui-pal-placeholder);
235
+ }
236
+ }
237
+
238
+ &_list {
239
+ @include scrollbar-awesome();
240
+
241
+ overflow: auto;
242
+ max-height: 30vh;
243
+ max-width: 100vw;
244
+ display: none;
245
+ flex-direction: column;
246
+ justify-content: stretch;
247
+ border-width: var(--ui-lt-border-width);
248
+ border-radius: var(--ui-lt-border-radius);
249
+ border-color: var(--ui-pal);
250
+ border-style: solid;
251
+ position: absolute;
252
+ left: calc(var(--ui-lt-border-width) * -1);
253
+ top: 100%;
254
+ min-width: calc(100% + var(--ui-lt-border-width) * 2);
255
+ background: var(--ui-pal-bg);
256
+ margin-top: spacing(200);
257
+ z-index: var(--lt-z-pop);
258
+
259
+ &-item {
260
+ padding: spacing(200, 300);
261
+ @include typo(200, 300);
262
+
263
+ border: 0 none;
264
+ text-decoration: none;
265
+ border-radius: 0;
266
+ justify-content: left;
267
+ text-align: left;
268
+ font-weight: inherit;
269
+ font-family: var(--typo-font-ui);
270
+ background: transparent;
271
+ outline: none;
272
+ color: var(--ui-pal-text);
273
+
274
+ &:hover {
275
+ background-color: var(--ui-pal-lateral);
276
+ color: var(--ui-pal-text);
277
+ }
278
+
279
+ &._selected {
280
+ color: var(--ui-pal-acc);
281
+ background-color: var(--ui-pal);
282
+ }
283
+
284
+ &:focus {
285
+ color: var(--ui-pal-acc);
286
+ background-color: var(--ui-pal);
287
+ }
288
+ }
289
+ }
290
+
291
+ &:focus-within &_list {
292
+ display: flex;
293
+ }
294
+
295
+ &:hover {
296
+ outline: none;
297
+ box-shadow: 0 5px 12px -4px rgb(var(--rgb-front), 0.2);
298
+ }
299
+
300
+ &:focus-within {
301
+ outline: none;
302
+ box-shadow: 0 0 0 0 var(--ui-pal);
303
+ border-color: var(--ui-pal);
304
+ }
305
+
306
+ &._disabled {
307
+ border: var(--ui-lt-border-width) var(--ui-lt-disabled-border-style) var(--ui-pal-disabled-border);
308
+ background: transparent;
309
+ box-shadow: none;
310
+ }
311
+ }
312
+ </style>