@formixru/b2b-ui 3.6.1 → 4.0.0
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@formixru/b2b-ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"@advdominion/pluralize": "^1.0.2",
|
|
30
30
|
"@advdominion/scroll-to": "^1.0.1",
|
|
31
31
|
"@advdominion/stylelint-config": "^1.1.0",
|
|
32
|
-
"@advdominion/tooltip": "^
|
|
32
|
+
"@advdominion/tooltip": "^7.0.1",
|
|
33
33
|
"@floating-ui/dom": "^1.7.6",
|
|
34
34
|
"@popperjs/core": "^2.11.8",
|
|
35
35
|
"@prettier/plugin-php": "^0.25.0",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"@advdominion/get-scrollbar-width": "^2.0.0",
|
|
74
74
|
"@advdominion/pluralize": "^1.0.0",
|
|
75
75
|
"@advdominion/scroll-to": "^1.0.0",
|
|
76
|
-
"@advdominion/tooltip": "^
|
|
76
|
+
"@advdominion/tooltip": "^7.0.0",
|
|
77
77
|
"color": "^5.0.0",
|
|
78
78
|
"dayjs": "^1.0.0",
|
|
79
79
|
"es-toolkit": "^1.0.0",
|
package/src/base.scss
CHANGED
|
@@ -26,6 +26,13 @@
|
|
|
26
26
|
--easing-deceleration: cubic-bezier(0, 0, 0.2, 1);
|
|
27
27
|
--easing-acceleration: cubic-bezier(0.4, 0, 1, 1);
|
|
28
28
|
|
|
29
|
+
--input-delay: 300ms;
|
|
30
|
+
--change-delay: 900ms;
|
|
31
|
+
--loader-delay: 600ms;
|
|
32
|
+
--loader-delay-min: 300ms;
|
|
33
|
+
--loader-min-time: 600ms;
|
|
34
|
+
--fallback-timeout: 3000ms;
|
|
35
|
+
|
|
29
36
|
@each $key, $value in $breakpoints {
|
|
30
37
|
--breakpoint-#{$key}: #{$value};
|
|
31
38
|
}
|
|
@@ -27,11 +27,24 @@ export const request = async ({
|
|
|
27
27
|
url = '',
|
|
28
28
|
method = 'GET',
|
|
29
29
|
data = {},
|
|
30
|
+
signal,
|
|
30
31
|
loader = true,
|
|
31
32
|
showLoaderDelay,
|
|
32
|
-
hideLoaderOnSuccess = true,
|
|
33
|
+
hideLoaderOnSuccess: initialHideLoaderOnSuccess = true,
|
|
33
34
|
interceptData = true,
|
|
35
|
+
...unknownParams
|
|
34
36
|
}) => {
|
|
37
|
+
let hideLoaderOnSuccess = initialHideLoaderOnSuccess;
|
|
38
|
+
|
|
39
|
+
const unknownKeys = Object.keys(unknownParams);
|
|
40
|
+
if (unknownKeys.length > 0) {
|
|
41
|
+
if (unknownKeys.length === 1) {
|
|
42
|
+
console.warn(`В функцию request передан неизвестный параметр: ${unknownKeys.join(', ')}`);
|
|
43
|
+
} else {
|
|
44
|
+
console.warn(`В функцию request переданы неизвестные параметры: ${unknownKeys.join(', ')}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
35
48
|
const reqUrl = new URL(url, location.origin);
|
|
36
49
|
const options = {
|
|
37
50
|
method,
|
|
@@ -39,7 +52,9 @@ export const request = async ({
|
|
|
39
52
|
'X-Requested-With': 'XMLHttpRequest',
|
|
40
53
|
'X-Session-Id': BX.message('bitrix_sessid'),
|
|
41
54
|
},
|
|
55
|
+
signal,
|
|
42
56
|
};
|
|
57
|
+
|
|
43
58
|
if (method === 'GET') {
|
|
44
59
|
let entries;
|
|
45
60
|
if (data instanceof FormData) {
|
|
@@ -60,9 +75,11 @@ export const request = async ({
|
|
|
60
75
|
options.headers['Content-Type'] = 'application/json';
|
|
61
76
|
}
|
|
62
77
|
}
|
|
78
|
+
|
|
63
79
|
if (loader) {
|
|
64
80
|
showLoader(showLoaderDelay);
|
|
65
81
|
}
|
|
82
|
+
|
|
66
83
|
try {
|
|
67
84
|
const response = await fetch(reqUrl, options);
|
|
68
85
|
if (!response.ok) {
|
|
@@ -78,9 +95,6 @@ export const request = async ({
|
|
|
78
95
|
data = { content: raw };
|
|
79
96
|
}
|
|
80
97
|
|
|
81
|
-
const shouldHideLoader = loader && hideLoaderOnSuccess;
|
|
82
|
-
let isLoaderHidden = false;
|
|
83
|
-
|
|
84
98
|
if (isString(data.redirect)) {
|
|
85
99
|
if (data.redirect) {
|
|
86
100
|
location.assign(data.redirect);
|
|
@@ -102,14 +116,14 @@ export const request = async ({
|
|
|
102
116
|
if ($dialog) {
|
|
103
117
|
$dialog.querySelector('.dialog__container').innerHTML = data.dialog.content;
|
|
104
118
|
// Вызываем hideLoader так же как в основном компоненте - до функции show()
|
|
105
|
-
if (
|
|
119
|
+
if (loader) {
|
|
106
120
|
hideLoader();
|
|
107
|
-
|
|
121
|
+
hideLoaderOnSuccess = false;
|
|
108
122
|
}
|
|
109
123
|
await showDialog($dialog);
|
|
110
124
|
initRequestContent($dialog);
|
|
111
125
|
} else {
|
|
112
|
-
console.
|
|
126
|
+
console.warn(`Диалоговое окно с id ${elementId?.replace(/^#/, '')} не найдено`);
|
|
113
127
|
}
|
|
114
128
|
}
|
|
115
129
|
|
|
@@ -145,14 +159,14 @@ export const request = async ({
|
|
|
145
159
|
if ($sidebar) {
|
|
146
160
|
$sidebar.innerHTML = data.sidebar.content;
|
|
147
161
|
// Вызываем hideLoader так же как в основном компоненте - до функции show()
|
|
148
|
-
if (
|
|
162
|
+
if (loader) {
|
|
149
163
|
hideLoader();
|
|
150
|
-
|
|
164
|
+
hideLoaderOnSuccess = false;
|
|
151
165
|
}
|
|
152
166
|
await showSidebar($sidebar);
|
|
153
167
|
initRequestContent($sidebar);
|
|
154
168
|
} else {
|
|
155
|
-
console.
|
|
169
|
+
console.warn(`Сайдбар с id ${elementId?.replace(/^#/, '')} не найден`);
|
|
156
170
|
}
|
|
157
171
|
}
|
|
158
172
|
|
|
@@ -187,7 +201,7 @@ export const request = async ({
|
|
|
187
201
|
if ($trigger) {
|
|
188
202
|
$trigger.click();
|
|
189
203
|
} else {
|
|
190
|
-
console.
|
|
204
|
+
console.warn(`Элемент с селектором ${data.trigger} не найден`);
|
|
191
205
|
if (loader) {
|
|
192
206
|
hideLoader();
|
|
193
207
|
}
|
|
@@ -195,7 +209,7 @@ export const request = async ({
|
|
|
195
209
|
}
|
|
196
210
|
}
|
|
197
211
|
|
|
198
|
-
if (
|
|
212
|
+
if (loader && hideLoaderOnSuccess) {
|
|
199
213
|
hideLoader(isString(data.redirect) ? 3000 : undefined);
|
|
200
214
|
}
|
|
201
215
|
|
|
@@ -19,7 +19,7 @@ $b: '.suggest-input';
|
|
|
19
19
|
border-top-right-radius: $input-radius;
|
|
20
20
|
container-type: size;
|
|
21
21
|
height: $input-height - $input-border-width * 2;
|
|
22
|
-
padding: math.div($input-height -
|
|
22
|
+
padding: math.div($input-height - 20px, 2);
|
|
23
23
|
position: absolute;
|
|
24
24
|
right: $input-border-width;
|
|
25
25
|
top: $input-border-width;
|
|
@@ -28,7 +28,7 @@ $b: '.suggest-input';
|
|
|
28
28
|
|
|
29
29
|
.input_small ~ & {
|
|
30
30
|
height: $input-small-height - $input-border-width * 2;
|
|
31
|
-
padding: math.div($input-small-height -
|
|
31
|
+
padding: math.div($input-small-height - 16px, 2);
|
|
32
32
|
width: $input-small-height - $input-border-width * 2;
|
|
33
33
|
}
|
|
34
34
|
}
|
|
@@ -65,12 +65,12 @@ $b: '.suggest-input';
|
|
|
65
65
|
|
|
66
66
|
&__clear-icon {
|
|
67
67
|
fill: currentcolor;
|
|
68
|
-
height:
|
|
69
|
-
width:
|
|
68
|
+
height: 16px;
|
|
69
|
+
width: 16px;
|
|
70
70
|
|
|
71
71
|
.input_small ~ #{$b}__clear & {
|
|
72
|
-
height:
|
|
73
|
-
width:
|
|
72
|
+
height: 12px;
|
|
73
|
+
width: 12px;
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
|
|
@@ -90,9 +90,9 @@ $b: '.suggest-input';
|
|
|
90
90
|
@include dark-border-disable((top));
|
|
91
91
|
|
|
92
92
|
@if $input-radius != 0 {
|
|
93
|
-
margin-top: -
|
|
94
|
-
max-height: $suggest-input-item-height * 5 +
|
|
95
|
-
padding-top:
|
|
93
|
+
margin-top: -8px;
|
|
94
|
+
max-height: $suggest-input-item-height * 5 + 8px;
|
|
95
|
+
padding-top: 8px;
|
|
96
96
|
} @else {
|
|
97
97
|
max-height: $suggest-input-item-height * 5;
|
|
98
98
|
}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div ref="$root" class="suggest-input" @keydown.esc="esc()">
|
|
3
3
|
<input
|
|
4
|
-
:id="id"
|
|
4
|
+
:id="props.id"
|
|
5
5
|
ref="$input"
|
|
6
6
|
v-model="model"
|
|
7
7
|
class="suggest-input__input input"
|
|
8
8
|
:class="[
|
|
9
9
|
showSuggests ? 'suggest-input__input_active' : '',
|
|
10
|
-
small ? 'input_small' : '',
|
|
11
|
-
error ? 'input_error' : '',
|
|
12
|
-
...inputCssClasses,
|
|
10
|
+
props.small ? 'input_small' : '',
|
|
11
|
+
props.error ? 'input_error' : '',
|
|
12
|
+
...props.inputCssClasses,
|
|
13
13
|
]"
|
|
14
14
|
type="text"
|
|
15
|
-
:name="name"
|
|
16
|
-
:required="required"
|
|
17
|
-
:disabled="disabled"
|
|
18
|
-
:placeholder="placeholder"
|
|
15
|
+
:name="props.name"
|
|
16
|
+
:required="props.required"
|
|
17
|
+
:disabled="props.disabled"
|
|
18
|
+
:placeholder="props.placeholder"
|
|
19
19
|
autocomplete="off"
|
|
20
20
|
@input="handleInput()"
|
|
21
21
|
@focusin="focusin()"
|
|
@@ -36,22 +36,22 @@
|
|
|
36
36
|
tabindex="0"
|
|
37
37
|
@click="select(item)"
|
|
38
38
|
>
|
|
39
|
-
<span v-if="hightlightMatches" v-html="item.formatted_label"></span>
|
|
39
|
+
<span v-if="props.hightlightMatches" v-html="item.formatted_label"></span>
|
|
40
40
|
<span v-else v-html="item.label"></span>
|
|
41
41
|
</div>
|
|
42
42
|
</template>
|
|
43
|
-
<div v-else class="suggest-input__not-found" v-html="notFoundMessage"></div>
|
|
43
|
+
<div v-else class="suggest-input__not-found" v-html="props.notFoundMessage"></div>
|
|
44
44
|
</div>
|
|
45
45
|
<slot name="content"></slot>
|
|
46
46
|
</div>
|
|
47
47
|
</template>
|
|
48
48
|
|
|
49
49
|
<script setup>
|
|
50
|
-
|
|
50
|
+
import { cssVar } from '@advdominion/css-var';
|
|
51
|
+
import { debounce } from 'es-toolkit';
|
|
52
|
+
import { computed, nextTick, onMounted, ref, useTemplateRef, watch } from 'vue';
|
|
51
53
|
|
|
52
|
-
import {
|
|
53
|
-
|
|
54
|
-
import { errorHandler } from '../../utils/error-handler.js';
|
|
54
|
+
import { request } from '../request/index.js';
|
|
55
55
|
|
|
56
56
|
const ICONS_PATH = window.ICONS_PATH;
|
|
57
57
|
|
|
@@ -90,9 +90,11 @@ const props = defineProps({
|
|
|
90
90
|
type: Number,
|
|
91
91
|
default: 3,
|
|
92
92
|
},
|
|
93
|
-
|
|
93
|
+
inputDelay: {
|
|
94
94
|
type: Number,
|
|
95
|
-
default
|
|
95
|
+
default() {
|
|
96
|
+
return cssVar('--input-delay');
|
|
97
|
+
},
|
|
96
98
|
},
|
|
97
99
|
inputCssClasses: {
|
|
98
100
|
type: Array,
|
|
@@ -113,28 +115,30 @@ const props = defineProps({
|
|
|
113
115
|
error: {
|
|
114
116
|
type: Boolean,
|
|
115
117
|
},
|
|
118
|
+
loaderDelay: {
|
|
119
|
+
type: Number,
|
|
120
|
+
default() {
|
|
121
|
+
return cssVar('--loader-delay-min');
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
loaderMinTime: {
|
|
125
|
+
type: Number,
|
|
126
|
+
default() {
|
|
127
|
+
return cssVar('--loader-min-time');
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
suggestsTimeout: {
|
|
131
|
+
type: Number,
|
|
132
|
+
default() {
|
|
133
|
+
return cssVar('--fallback-timeout');
|
|
134
|
+
},
|
|
135
|
+
},
|
|
116
136
|
});
|
|
117
137
|
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
required,
|
|
123
|
-
disabled,
|
|
124
|
-
action,
|
|
125
|
-
queryName,
|
|
126
|
-
minlength,
|
|
127
|
-
throttle,
|
|
128
|
-
inputCssClasses,
|
|
129
|
-
hightlightMatches,
|
|
130
|
-
small,
|
|
131
|
-
notFoundMessage,
|
|
132
|
-
} = toRefs(props);
|
|
133
|
-
|
|
134
|
-
const $root = ref();
|
|
135
|
-
const $input = ref();
|
|
136
|
-
const $list = ref();
|
|
137
|
-
const $clear = ref();
|
|
138
|
+
const $root = useTemplateRef('$root');
|
|
139
|
+
const $input = useTemplateRef('$input');
|
|
140
|
+
const $list = useTemplateRef('$list');
|
|
141
|
+
const $clear = useTemplateRef('$clear');
|
|
138
142
|
|
|
139
143
|
const suggests = ref([]);
|
|
140
144
|
const formattedSuggests = ref([]);
|
|
@@ -144,7 +148,7 @@ watch(suggests, () => {
|
|
|
144
148
|
focusSuggestIndex.value = -1;
|
|
145
149
|
const regexp = new RegExp(model.value, 'i');
|
|
146
150
|
formattedSuggests.value = suggests.value.map((item) => {
|
|
147
|
-
if (hightlightMatches
|
|
151
|
+
if (props.hightlightMatches) {
|
|
148
152
|
const match = regexp.exec(item.label);
|
|
149
153
|
item.formatted_label = match ? item.label.replace(match[0], `<mark>${match[0]}</mark>`) : item.label;
|
|
150
154
|
}
|
|
@@ -191,41 +195,84 @@ const indexesToScroll = computed(() => {
|
|
|
191
195
|
});
|
|
192
196
|
|
|
193
197
|
const isLoading = ref(false);
|
|
194
|
-
const isClearVisible = computed(() => model.value.length >= minlength
|
|
198
|
+
const isClearVisible = computed(() => model.value.length >= props.minlength && !isLoading.value);
|
|
195
199
|
|
|
196
|
-
let
|
|
200
|
+
let controllerDaData;
|
|
201
|
+
let abortTimeout;
|
|
202
|
+
let loaderTimeout;
|
|
203
|
+
let loaderStartTime = 0;
|
|
197
204
|
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
205
|
+
const getSuggests = async (query) => {
|
|
206
|
+
const currentController = new AbortController();
|
|
207
|
+
controllerDaData = currentController;
|
|
208
|
+
|
|
209
|
+
clearTimeout(abortTimeout);
|
|
210
|
+
abortTimeout = setTimeout(() => {
|
|
211
|
+
currentController.abort();
|
|
212
|
+
}, props.suggestsTimeout);
|
|
213
|
+
|
|
214
|
+
// Добавляем небольшую задержку, чтобы лодер не показывался на быстрых ответах
|
|
215
|
+
clearTimeout(loaderTimeout);
|
|
216
|
+
loaderTimeout = setTimeout(() => {
|
|
217
|
+
if (currentController === controllerDaData) {
|
|
202
218
|
isLoading.value = true;
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
219
|
+
loaderStartTime = Date.now();
|
|
220
|
+
}
|
|
221
|
+
}, props.loaderDelay);
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
const url = new URL(props.action, location.origin);
|
|
225
|
+
url.searchParams.append(props.queryName, query);
|
|
226
|
+
|
|
227
|
+
const data = await request({
|
|
228
|
+
url,
|
|
229
|
+
loader: false,
|
|
230
|
+
interceptData: false,
|
|
231
|
+
signal: currentController.signal,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
if (currentController !== controllerDaData) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (data) {
|
|
239
|
+
suggests.value = data;
|
|
240
|
+
showSuggests.value = true;
|
|
241
|
+
}
|
|
242
|
+
} finally {
|
|
243
|
+
if (currentController === controllerDaData) {
|
|
244
|
+
clearTimeout(abortTimeout);
|
|
245
|
+
controllerDaData = undefined;
|
|
246
|
+
|
|
247
|
+
clearTimeout(loaderTimeout);
|
|
248
|
+
if (isLoading.value) {
|
|
249
|
+
// Если показали лоадер, то убеждаемся, что он будет показываться какое-то мин. время, чтобы избежать миганий
|
|
250
|
+
const displayTime = Date.now() - loaderStartTime;
|
|
251
|
+
const remainingTime = Math.max(0, props.loaderMinTime - displayTime);
|
|
252
|
+
setTimeout(() => {
|
|
223
253
|
isLoading.value = false;
|
|
224
|
-
|
|
225
|
-
}
|
|
254
|
+
loaderStartTime = 0;
|
|
255
|
+
}, remainingTime);
|
|
226
256
|
}
|
|
227
|
-
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const _debouncedGetSuggests = debounce(getSuggests, props.inputDelay);
|
|
262
|
+
|
|
263
|
+
const cancelRequest = () => {
|
|
264
|
+
_debouncedGetSuggests.cancel();
|
|
265
|
+
controllerDaData?.abort();
|
|
266
|
+
clearTimeout(abortTimeout);
|
|
267
|
+
clearTimeout(loaderTimeout);
|
|
268
|
+
isLoading.value = false;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const handleInput = () => {
|
|
272
|
+
if (model.value.length >= props.minlength) {
|
|
273
|
+
_debouncedGetSuggests(model.value);
|
|
228
274
|
} else {
|
|
275
|
+
cancelRequest();
|
|
229
276
|
showSuggests.value = false;
|
|
230
277
|
}
|
|
231
278
|
};
|
|
@@ -238,6 +285,7 @@ const focusin = async () => {
|
|
|
238
285
|
|
|
239
286
|
const focusout = async ({ relatedTarget }) => {
|
|
240
287
|
if (!relatedTarget || (!$list.value.contains(relatedTarget) && !$clear.value.contains(relatedTarget))) {
|
|
288
|
+
cancelRequest();
|
|
241
289
|
showSuggests.value = false;
|
|
242
290
|
}
|
|
243
291
|
await nextTick();
|
|
@@ -245,6 +293,7 @@ const focusout = async ({ relatedTarget }) => {
|
|
|
245
293
|
};
|
|
246
294
|
|
|
247
295
|
const esc = () => {
|
|
296
|
+
cancelRequest();
|
|
248
297
|
// Ждём, чтобы событие нажатия Esc сработало у других обработчиков (например, в диалоговых окнах)
|
|
249
298
|
setTimeout(() => {
|
|
250
299
|
showSuggests.value = false;
|
|
@@ -252,12 +301,13 @@ const esc = () => {
|
|
|
252
301
|
};
|
|
253
302
|
|
|
254
303
|
const clear = () => {
|
|
255
|
-
|
|
304
|
+
cancelRequest();
|
|
256
305
|
model.value = '';
|
|
257
306
|
showSuggests.value = false;
|
|
258
307
|
};
|
|
259
308
|
|
|
260
309
|
const select = ({ label, value }) => {
|
|
310
|
+
cancelRequest();
|
|
261
311
|
model.value = label;
|
|
262
312
|
showSuggests.value = false;
|
|
263
313
|
emit('select', {
|