@formixru/b2b-ui 2.4.0 → 3.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.
Files changed (34) hide show
  1. package/README.md +2 -2
  2. package/package.json +41 -44
  3. package/src/components/action-link/index.scss +49 -0
  4. package/src/components/action-list/index.scss +6 -15
  5. package/src/components/counter/index.scss +16 -0
  6. package/src/components/dialog/index.js +8 -20
  7. package/src/components/dialog/index.scss +13 -0
  8. package/src/components/form/index.js +37 -37
  9. package/src/components/overlay/index.js +1 -1
  10. package/src/components/overlay/index.scss +4 -0
  11. package/src/components/request/index.js +8 -8
  12. package/src/components/sidebar/index.js +3 -3
  13. package/src/components/sidebar/index.scss +7 -0
  14. package/src/components/tabs/index.js +4 -4
  15. package/src/components/tooltip/index.js +4 -3
  16. package/src/{helpers → utils}/error-handler.js +3 -3
  17. package/src/{helpers → utils}/is-in-viewport.js +2 -2
  18. package/src/{helpers → utils}/mm.js +2 -2
  19. package/src/variables.scss +23 -12
  20. package/CHANGELOG.md +0 -104
  21. package/CONTRIBUTING.md +0 -59
  22. package/src/components/color-input/index.vue +0 -154
  23. package/src/components/date-input/index.vue +0 -240
  24. package/src/components/delete-all/index.scss +0 -44
  25. package/src/components/file-input/index.vue +0 -335
  26. package/src/components/font-input/index.vue +0 -174
  27. package/src/components/quantity-input/index.vue +0 -141
  28. package/src/components/range-input/index.vue +0 -185
  29. package/src/components/suggest-input/index.vue +0 -328
  30. package/src/components/warning-message/index.vue +0 -101
  31. package/vite.config.js +0 -75
  32. /package/src/{helpers → utils}/css-var.js +0 -0
  33. /package/src/{helpers → utils}/get-smart-captcha.js +0 -0
  34. /package/src/{helpers → utils}/scroll-to.js +0 -0
@@ -1,185 +0,0 @@
1
- <template>
2
- <div v-once ref="$root" class="range-input">
3
- <div class="range-input__wrapper">
4
- <input
5
- ref="$from"
6
- class="range-input__input input"
7
- type="number"
8
- :placeholder="`от ${initMin}`"
9
- :min="initMin"
10
- :value="initFromValue"
11
- :step="initStep"
12
- :name="fromName"
13
- :disabled="initDisabled"
14
- />
15
- <input
16
- ref="$to"
17
- class="range-input__input input"
18
- type="number"
19
- :placeholder="`до ${initMax}`"
20
- :max="initMax"
21
- :value="initToValue"
22
- :step="initStep"
23
- :name="toName"
24
- :disabled="initDisabled"
25
- />
26
- </div>
27
- <div ref="$track" class="range-input__track"></div>
28
- </div>
29
- </template>
30
-
31
- <script setup>
32
- import noUiSlider from 'nouislider';
33
- import { onMounted, ref, toRefs } from 'vue';
34
-
35
- const emit = defineEmits(['input']);
36
-
37
- const props = defineProps({
38
- fromName: {
39
- type: String,
40
- default: '',
41
- },
42
- toName: {
43
- type: String,
44
- default: '',
45
- },
46
- initFromValue: {
47
- type: Number,
48
- default: undefined,
49
- },
50
- initToValue: {
51
- type: Number,
52
- default: undefined,
53
- },
54
- initMin: {
55
- type: Number,
56
- required: true,
57
- },
58
- initMax: {
59
- type: Number,
60
- required: true,
61
- },
62
- initStep: {
63
- type: Number,
64
- default: 1,
65
- },
66
- initDisabled: {
67
- type: Boolean,
68
- },
69
- throttle: {
70
- type: Number,
71
- default: 1000,
72
- },
73
- });
74
-
75
- const { fromName, toName, initFromValue, initToValue, initMin, initMax, initStep, initDisabled, throttle } =
76
- toRefs(props);
77
-
78
- const $root = ref();
79
- const $track = ref();
80
- const $from = ref();
81
- const $to = ref();
82
-
83
- onMounted(() => {
84
- const setTrackValue = () => {
85
- const min = Number($from.value.min);
86
- const max = Number($to.value.max);
87
-
88
- let from;
89
- let to;
90
-
91
- // Иначе, если мин. и макс. одинаковые, ползунок перемещается в конец трека
92
- if (min !== max) {
93
- from = $from.value.value.length > 0 ? Number($from.value.value) : min;
94
- to = $to.value.value.length > 0 ? Number($to.value.value) : max;
95
- }
96
-
97
- $track.value.noUiSlider.set([from, to]);
98
- };
99
-
100
- const setFromTo = () => {
101
- const value = $track.value.noUiSlider.get();
102
- if ($from.value.value.length > 0 || Number(value[0]) > Number($from.value.min)) {
103
- $from.value.value = Number(value[0]);
104
- }
105
- if ($to.value.value.length > 0 || Number(value[1]) < Number($to.value.max)) {
106
- $to.value.value = Number(value[1]);
107
- }
108
- };
109
-
110
- noUiSlider.create($track.value, {
111
- start: [
112
- initFromValue.value === undefined ? initMin.value : initFromValue.value,
113
- initToValue.value === undefined ? initMax.value : initToValue.value,
114
- ],
115
- step: initStep.value,
116
- range: {
117
- min: [initMin.value],
118
- max: [initMax.value],
119
- },
120
- connect: true,
121
- });
122
-
123
- if (initDisabled.value) {
124
- $track.value.noUiSlider.target.setAttribute('disabled', true);
125
- }
126
-
127
- $track.value.noUiSlider.on('slide', () => {
128
- setFromTo();
129
- });
130
-
131
- $track.value.noUiSlider.on('change', () => {
132
- emit('input');
133
- });
134
-
135
- let throttleTimeout;
136
-
137
- for (const $el of [$from.value, $to.value]) {
138
- // Обновляем параметры ползунка при изменении атрибутов полей
139
- const observer = new MutationObserver((mutations) => {
140
- for (const { attributeName } of mutations) {
141
- if (attributeName === 'step') {
142
- $track.value.noUiSlider.updateOptions({
143
- step: Number($el.step),
144
- });
145
- } else if (attributeName === 'disabled') {
146
- if ($el.disabled) {
147
- $track.value.noUiSlider.target.setAttribute('disabled', true);
148
- } else {
149
- $track.value.noUiSlider.target.removeAttribute('disabled');
150
- }
151
- } else {
152
- $track.value.noUiSlider.updateOptions({
153
- range: {
154
- min: [Number($from.value.min)],
155
- max: [Number($to.value.max)],
156
- },
157
- });
158
- setTrackValue();
159
- // Обновляем подсказки от/до
160
- $from.value.placeholder = `от ${$from.value.min}`;
161
- $to.value.placeholder = `до ${$to.value.max}`;
162
- }
163
- }
164
- });
165
- observer.observe($el, {
166
- attributes: true,
167
- attributeFilter: ['min', 'max', 'step', 'disabled'],
168
- });
169
-
170
- $el.addEventListener('input', () => {
171
- setTrackValue();
172
- const $filterItem = $root.value.closest('.filter__item');
173
- $filterItem._tooltip?.hide();
174
- clearTimeout(throttleTimeout);
175
- throttleTimeout = setTimeout(() => {
176
- emit('input');
177
- }, throttle.value);
178
- });
179
-
180
- $el.addEventListener('change', () => {
181
- setFromTo();
182
- });
183
- }
184
- });
185
- </script>
@@ -1,328 +0,0 @@
1
- <template>
2
- <div ref="$root" class="suggest-input" @keydown.esc="esc()">
3
- <input
4
- :id="id"
5
- ref="$input"
6
- v-model="model"
7
- class="suggest-input__input input"
8
- :class="[
9
- showSuggests ? 'suggest-input__input_active' : '',
10
- small ? 'input_small' : '',
11
- error ? 'input_error' : '',
12
- ...inputCssClasses,
13
- ]"
14
- type="text"
15
- :name="name"
16
- :required="required"
17
- :disabled="disabled"
18
- :placeholder="placeholder"
19
- autocomplete="off"
20
- @input="handleInput()"
21
- @focusin="focusin()"
22
- @focusout="focusout($event)"
23
- @keydown="arrowNavigation($event)"
24
- />
25
- <div v-show="isLoading" class="suggest-input__loader"><div class="spinner"></div></div>
26
- <button v-show="isClearVisible" ref="$clear" class="suggest-input__clear" type="button" @click="clear()">
27
- <svg class="suggest-input__clear-icon"><use :href="`${ICONS_PATH}#close`" /></svg>
28
- </button>
29
- <div v-show="showSuggests" ref="$list" class="suggest-input__list" @mouseover="focusSuggestIndex = -1">
30
- <template v-if="suggests.length > 0">
31
- <div
32
- v-for="(item, index) in formattedSuggests"
33
- :key="index"
34
- class="suggest-input__list-item"
35
- :class="{ 'suggest-input__list-item_focus': index === focusSuggestIndex }"
36
- tabindex="0"
37
- @click="select(item)"
38
- >
39
- <span v-if="hightlightMatches" v-html="item.formatted_label"></span>
40
- <span v-else v-html="item.label"></span>
41
- </div>
42
- </template>
43
- <div v-else class="suggest-input__not-found" v-html="notFoundMessage"></div>
44
- </div>
45
- <slot name="content"></slot>
46
- </div>
47
- </template>
48
-
49
- <script setup>
50
- /* globals BX */
51
-
52
- import { computed, nextTick, onMounted, ref, toRefs, watch } from 'vue';
53
-
54
- import { errorHandler } from '../../helpers/error-handler.js';
55
-
56
- const ICONS_PATH = window.ICONS_PATH;
57
-
58
- const emit = defineEmits(['focusin', 'focusout', 'select']);
59
-
60
- const model = defineModel({ type: String, default: '' });
61
-
62
- const props = defineProps({
63
- id: {
64
- type: String,
65
- default: '',
66
- },
67
- name: {
68
- type: String,
69
- default: '',
70
- },
71
- placeholder: {
72
- type: String,
73
- default: ' ',
74
- },
75
- required: {
76
- type: Boolean,
77
- },
78
- disabled: {
79
- type: Boolean,
80
- },
81
- action: {
82
- type: String,
83
- required: true,
84
- },
85
- queryName: {
86
- type: String,
87
- default: 'query',
88
- },
89
- minlength: {
90
- type: Number,
91
- default: 3,
92
- },
93
- throttle: {
94
- type: Number,
95
- default: 1000,
96
- },
97
- inputCssClasses: {
98
- type: Array,
99
- default() {
100
- return [];
101
- },
102
- },
103
- hightlightMatches: {
104
- type: Boolean,
105
- },
106
- notFoundMessage: {
107
- type: String,
108
- default: 'По&nbsp;вашему запросу ничего не&nbsp;найдено',
109
- },
110
- small: {
111
- type: Boolean,
112
- },
113
- error: {
114
- type: Boolean,
115
- },
116
- });
117
-
118
- const {
119
- id,
120
- name,
121
- placeholder,
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
-
139
- const suggests = ref([]);
140
- const formattedSuggests = ref([]);
141
- const focusSuggestIndex = ref(-1);
142
-
143
- watch(suggests, () => {
144
- focusSuggestIndex.value = -1;
145
- const regexp = new RegExp(model.value, 'i');
146
- formattedSuggests.value = suggests.value.map((item) => {
147
- if (hightlightMatches.value) {
148
- const match = regexp.exec(item.label);
149
- item.formatted_label = match ? item.label.replace(match[0], `<mark>${match[0]}</mark>`) : item.label;
150
- }
151
- return item;
152
- });
153
- });
154
-
155
- const showSuggests = ref(false);
156
- const itemHeight = ref(0);
157
-
158
- watch(showSuggests, async (value) => {
159
- if (value && suggests.value.length > 0) {
160
- await nextTick();
161
- itemHeight.value = $list.value.querySelector('.suggest-input__list-item').offsetHeight;
162
- } else {
163
- suggests.value = [];
164
- await nextTick();
165
- itemHeight.value = 0;
166
- }
167
- });
168
-
169
- const maxItemsWithoutScroll = computed(() => {
170
- if (itemHeight.value) {
171
- return Math.floor($list.value.offsetHeight / itemHeight.value);
172
- }
173
- return 0;
174
- });
175
-
176
- const indexesToScroll = computed(() => {
177
- if (maxItemsWithoutScroll.value) {
178
- const indexes = [0];
179
- for (const index of suggests.value.keys()) {
180
- const nextIndex = indexes.at(-1) + maxItemsWithoutScroll.value;
181
- if (nextIndex > suggests.value.length) {
182
- break;
183
- }
184
- if (index === nextIndex) {
185
- indexes.push(index);
186
- }
187
- }
188
- return indexes;
189
- }
190
- return [];
191
- });
192
-
193
- const isLoading = ref(false);
194
- const isClearVisible = computed(() => model.value.length >= minlength.value && !isLoading.value);
195
-
196
- let throttleTimeout;
197
-
198
- const handleInput = () => {
199
- clearTimeout(throttleTimeout);
200
- if (model.value.length >= minlength.value) {
201
- throttleTimeout = setTimeout(async () => {
202
- isLoading.value = true;
203
- const url = new URL(action.value, location.origin);
204
- url.searchParams.append(queryName.value, model.value);
205
- try {
206
- const response = await fetch(url, {
207
- headers: {
208
- 'X-Requested-With': 'XMLHttpRequest',
209
- 'X-Session-Id': BX.message('bitrix_sessid'),
210
- },
211
- });
212
- if (!response.ok) {
213
- throw response;
214
- }
215
- const data = await response.json();
216
- isLoading.value = false;
217
- suggests.value = data;
218
- showSuggests.value = true;
219
- } catch (error) {
220
- if (error.status === 401) {
221
- location.reload();
222
- } else {
223
- isLoading.value = false;
224
- errorHandler(error);
225
- }
226
- }
227
- }, throttle.value);
228
- } else {
229
- showSuggests.value = false;
230
- }
231
- };
232
-
233
- const arrowNavigation = (event) => {
234
- if (showSuggests.value) {
235
- if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
236
- const selectionStart = $input.value.selectionStart;
237
- const selectionEnd = $input.value.selectionEnd;
238
- setTimeout(() => {
239
- $input.value.setSelectionRange(selectionStart, selectionEnd);
240
- });
241
- switch (event.key) {
242
- case 'ArrowDown': {
243
- focusSuggestIndex.value =
244
- suggests.value.length === focusSuggestIndex.value + 1 ? 0 : focusSuggestIndex.value + 1;
245
- if (
246
- focusSuggestIndex.value !== 0 &&
247
- focusSuggestIndex.value !== suggests.value.length - 1 &&
248
- indexesToScroll.value.includes(focusSuggestIndex.value)
249
- ) {
250
- $list.value.scrollTop += maxItemsWithoutScroll.value * itemHeight.value;
251
- } else if (focusSuggestIndex.value === 0) {
252
- $list.value.scrollTop = 0;
253
- }
254
- break;
255
- }
256
- case 'ArrowUp': {
257
- focusSuggestIndex.value =
258
- focusSuggestIndex.value <= 0 ? suggests.value.length - 1 : focusSuggestIndex.value - 1;
259
- if (
260
- focusSuggestIndex.value !== 0 &&
261
- focusSuggestIndex.value !== suggests.value.length - 1 &&
262
- indexesToScroll.value.includes(focusSuggestIndex.value + 1)
263
- ) {
264
- $list.value.scrollTop -= maxItemsWithoutScroll.value * itemHeight.value;
265
- } else if (focusSuggestIndex.value === suggests.value.length - 1) {
266
- $list.value.scrollTop = suggests.value.length * itemHeight.value;
267
- }
268
- break;
269
- }
270
- }
271
- }
272
- if (event.key === 'Enter' && focusSuggestIndex.value >= 0) {
273
- // Ждём, чтобы событие нажатия Enter сработало у других обработчиков (например, в формах)
274
- setTimeout(() => {
275
- select(suggests.value[focusSuggestIndex.value]);
276
- });
277
- }
278
- }
279
- };
280
-
281
- const focusin = async () => {
282
- emit('focusin');
283
- await nextTick();
284
- handleInput();
285
- };
286
-
287
- const focusout = async ({ relatedTarget }) => {
288
- if (!relatedTarget || (!$list.value.contains(relatedTarget) && !$clear.value.contains(relatedTarget))) {
289
- showSuggests.value = false;
290
- }
291
- await nextTick();
292
- emit('focusout');
293
- };
294
-
295
- const esc = () => {
296
- // Ждём, чтобы событие нажатия Esc сработало у других обработчиков (например, в диалоговых окнах)
297
- setTimeout(() => {
298
- showSuggests.value = false;
299
- });
300
- };
301
-
302
- const clear = () => {
303
- clearTimeout(throttleTimeout);
304
- model.value = '';
305
- showSuggests.value = false;
306
- };
307
-
308
- const select = ({ label, value }) => {
309
- model.value = label;
310
- showSuggests.value = false;
311
- emit('select', {
312
- input: model.value,
313
- value,
314
- });
315
- };
316
-
317
- onMounted(() => {
318
- $root.value.closest('.form')?.addEventListener('keydown', (event) => {
319
- if (event.key === 'Enter' && focusSuggestIndex.value >= 0) {
320
- event.preventDefault();
321
- }
322
- });
323
- });
324
-
325
- defineExpose({
326
- clear,
327
- });
328
- </script>
@@ -1,101 +0,0 @@
1
- <template>
2
- <div class="warning-message" :class="classes">
3
- <svg v-if="!noIcon" class="warning-message__icon"><use :href="`${ICONS_PATH}#${icon}`" /></svg>
4
- <div class="warning-message__wrapper">
5
- <div class="warning-message__content">
6
- <div v-if="title" class="warning-message__title" v-html="title"></div>
7
- <div v-if="descr" class="warning-message__descr" v-html="descr"></div>
8
- </div>
9
- <a
10
- v-if="actionUrl.length > 0"
11
- class="warning-message__action button"
12
- :class="{
13
- button_inverted: solid && type !== 'light-info',
14
- button_alt: !solid || type === 'light-info',
15
- }"
16
- :href="actionUrl"
17
- >
18
- <svg v-if="actionIcon.length > 0" class="button__icon">
19
- <use :href="`${ICONS_PATH}#${actionIcon}`" />
20
- </svg>
21
- Ссылка
22
- </a>
23
- </div>
24
- </div>
25
- </template>
26
-
27
- <script setup>
28
- import { computed, toRefs } from 'vue';
29
-
30
- const ICONS_PATH = window.ICONS_PATH;
31
-
32
- const props = defineProps({
33
- type: {
34
- type: String,
35
- default: 'warning',
36
- validator(value) {
37
- return ['warning', 'success', 'info', 'light-info', 'error'].includes(value);
38
- },
39
- },
40
- small: {
41
- type: Boolean,
42
- },
43
- solid: {
44
- type: Boolean,
45
- },
46
- title: {
47
- type: String,
48
- default: '',
49
- },
50
- descr: {
51
- type: String,
52
- default: '',
53
- },
54
- actionUrl: {
55
- type: String,
56
- default: '',
57
- },
58
- actionIcon: {
59
- type: String,
60
- default: '',
61
- },
62
- noIcon: {
63
- type: Boolean,
64
- },
65
- });
66
-
67
- const { type, small, solid, title, descr, actionUrl, actionIcon, noIcon } = toRefs(props);
68
-
69
- const classes = computed(() => {
70
- return {
71
- 'warning-message_small': small.value,
72
- 'warning-message_solid': solid.value,
73
- 'warning-message_success': type.value === 'success',
74
- 'warning-message_info': type.value === 'info',
75
- 'warning-message_light-info': type.value === 'light-info',
76
- 'warning-message_error': type.value === 'error',
77
- };
78
- });
79
-
80
- const icon = computed(() => {
81
- let icon;
82
- switch (type.value) {
83
- case 'success': {
84
- icon = 'check';
85
- break;
86
- }
87
- case 'info': {
88
- icon = 'info';
89
- break;
90
- }
91
- case 'error': {
92
- icon = 'delete';
93
- break;
94
- }
95
- default: {
96
- icon = 'warning';
97
- }
98
- }
99
- return icon;
100
- });
101
- </script>
package/vite.config.js DELETED
@@ -1,75 +0,0 @@
1
- import { readFileSync } from 'node:fs';
2
- import { createRequire } from 'node:module';
3
- import path from 'node:path';
4
- import { fileURLToPath, pathToFileURL } from 'node:url';
5
-
6
- import vue from '@vitejs/plugin-vue';
7
- import { defineConfig } from 'vite';
8
-
9
- const require = createRequire(import.meta.url);
10
-
11
- export default defineConfig({
12
- plugins: [
13
- vue({
14
- template: {
15
- transformAssetUrls: {
16
- video: [],
17
- source: [],
18
- img: [],
19
- image: [],
20
- use: [],
21
- },
22
- },
23
- }),
24
- ],
25
- css: {
26
- preprocessorOptions: {
27
- scss: {
28
- api: 'modern-compiler',
29
- importers: [
30
- {
31
- canonicalize(url, context) {
32
- if (url.startsWith('pkg:')) {
33
- return pathToFileURL(require.resolve(url.replace('pkg:', '')));
34
- }
35
- // Обработка относительных путей внутри pkg:
36
- if (context.containingUrl?.href.includes('node_modules')) {
37
- const basePath = path.dirname(fileURLToPath(context.containingUrl));
38
- const resolvedPath = path.resolve(basePath, url);
39
-
40
- // Пробуем найти файл с нужным названием (проверка на пустую строку нужна для случаев, когда в URL уже стоит файл с расширением)
41
- const extensions = ['', '.scss'];
42
- for (const ext of extensions) {
43
- const fullPath = resolvedPath + ext;
44
- try {
45
- if (readFileSync(fullPath)) {
46
- return pathToFileURL(fullPath);
47
- }
48
- } catch {} // eslint-disable-line no-empty
49
- }
50
-
51
- // Если не нашли файл с нужным названием, пробуем найти index
52
- for (const ext of ['.scss']) {
53
- const indexPath = path.join(resolvedPath, `index${ext}`);
54
- try {
55
- if (readFileSync(indexPath)) {
56
- return pathToFileURL(indexPath);
57
- }
58
- } catch {} // eslint-disable-line no-empty
59
- }
60
- }
61
- // eslint-disable-next-line unicorn/no-null
62
- return null;
63
- },
64
- load(canonicalUrl) {
65
- return {
66
- contents: readFileSync(fileURLToPath(canonicalUrl), 'utf8'),
67
- syntax: 'scss',
68
- };
69
- },
70
- },
71
- ],
72
- },
73
- },
74
- },
75
- });
File without changes
File without changes
File without changes