itube-specs 0.0.363 → 0.0.364

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.
@@ -4,11 +4,11 @@
4
4
  class="s-dropdown"
5
5
  :style="`--max-height: ${getMaxHeight}`"
6
6
  :class="[
7
- { '--top': top },
8
- { '--right': right },
9
- { '--center': center },
10
- { '--open': open },
11
- { '--separate': separateLastChild },
7
+ {'--top': top},
8
+ {'--right': right},
9
+ {'--center': center},
10
+ {'--open': open},
11
+ {'--separate': separateLastChild},
12
12
  ]"
13
13
  @mouseleave="mouseHandler(false)"
14
14
  @mouseover="mouseHandler(true)"
@@ -19,14 +19,13 @@
19
19
  >
20
20
  <slot name="trigger"></slot>
21
21
  </div>
22
-
23
22
  <transition name="s-dropdown" mode="out-in">
24
23
  <dialog
25
24
  v-if="useVIf ? open && $slots.items : true"
26
25
  :style="!useVIf ? { display: (open && $slots.items) ? '' : 'none' } : {}"
27
26
  ref="menuRef"
28
27
  class="s-dropdown__menu"
29
- :class="{ '--open': open }"
28
+ :class="{'--open': open}"
30
29
  @click="close"
31
30
  >
32
31
  <button
@@ -39,13 +38,11 @@
39
38
  type="button"
40
39
  @click="close"
41
40
  >
42
- <SIcon name="close" size="16" />
41
+ <SIcon name="close" size="16"/>
43
42
  </button>
44
-
45
43
  <slot name="header"></slot>
46
-
47
44
  <div class="s-dropdown__menu-items" v-if="$slots.items">
48
- <slot name="items" v-bind="{ class: 's-dropdown__item' }"></slot>
45
+ <slot name="items" v-bind="'s-dropdown__item'"></slot>
49
46
  </div>
50
47
  </dialog>
51
48
  </transition>
@@ -53,12 +50,12 @@
53
50
  </template>
54
51
 
55
52
  <script setup lang="ts">
56
- import { ref, watch, computed, onMounted, onBeforeUnmount, nextTick } from 'vue';
53
+ // На компоненте обязательно вызывать пропсы слота и вешать слот пропс (className) на непосредственных детей
57
54
  import { onClickOutside, useScrollLock } from '@vueuse/core';
58
55
  import { isMobile } from '../../runtime';
59
56
  import type { CssBreakpoints } from '../../types';
60
57
 
61
- const dropdownRef = ref<HTMLElement | null>(null);
58
+ const dropdownRef = ref<HTMLDialogElement | null>(null);
62
59
  const menuRef = ref<HTMLDialogElement | null>(null);
63
60
 
64
61
  const props = withDefaults(defineProps<{
@@ -70,77 +67,57 @@ const props = withDefaults(defineProps<{
70
67
  openByClick?: boolean;
71
68
  useVIf?: boolean;
72
69
  }>(), {
73
- useVIf: false,
70
+ useVIf: false
74
71
  });
75
72
 
76
73
  const open = ref(false);
77
74
 
78
75
  const breakpoints = useAppConfig().cssBreakpoints as Record<CssBreakpoints, number>;
79
76
 
80
- // SSR-safe: тело страницы доступно только на клиенте
81
- const bodyElement = ref<HTMLElement | null>(null);
82
- const bodyScrollLocked = useScrollLock(bodyElement);
83
-
84
- onMounted(() => {
85
- bodyElement.value = document.body;
86
- });
77
+ // Блокируем скролл body при открытии на мобильных
78
+ const bodyScrollLocked = useScrollLock(document.body);
87
79
 
88
- // Закрытие дропдауна
89
80
  function close() {
90
81
  open.value = false;
91
82
 
92
- if (isMobile(breakpoints).value && process.client) {
83
+ if (isMobile(breakpoints).value) {
93
84
  nextTick(() => {
94
- menuRef.value?.close();
95
- bodyScrollLocked.value = false;
85
+ menuRef.value?.close?.();
86
+ bodyScrollLocked.value = false; // разблокируем
96
87
  });
97
88
  }
98
89
  }
99
90
 
100
- // Открытие по клику или ховеру
101
91
  function openDropdown() {
102
92
  if (props.openByClick) {
103
93
  open.value = !open.value;
104
94
  }
105
95
 
106
- if (isMobile(breakpoints).value && process.client) {
96
+ if (isMobile(breakpoints).value) {
107
97
  open.value = true;
108
98
 
109
99
  nextTick(() => {
110
100
  if (menuRef.value?.showModal) {
111
101
  menuRef.value.showModal();
112
- bodyScrollLocked.value = true;
102
+ bodyScrollLocked.value = true; // блокируем
113
103
  }
114
104
  });
115
105
  }
116
106
  }
117
107
 
118
- // Ховер-логика только на десктопе и без openByClick
119
- function mouseHandler(isEnter: boolean) {
120
- if (!isMobile(breakpoints).value && !props.openByClick && process.client) {
121
- open.value = isEnter;
108
+ watch(open, (isOpen) => {
109
+ if (isOpen) {
110
+ onClickOutside(dropdownRef, () => {
111
+ close();
112
+ });
122
113
  }
123
- }
124
-
125
- // Клик вне дропдауна
126
- watch(
127
- open,
128
- (isOpen) => {
129
- if (isOpen && process.client && dropdownRef.value) {
130
- onClickOutside(dropdownRef.value, () => {
131
- close();
132
- });
133
- }
134
- },
135
- { immediate: true }
136
- );
114
+ }, { immediate: true });
137
115
 
138
- // Гарантированная разблокировка скролла при уходе со страницы
139
- onBeforeUnmount(() => {
140
- if (process.client) {
141
- bodyScrollLocked.value = false;
116
+ function mouseHandler(event: boolean) {
117
+ if (!isMobile(breakpoints).value && !props.openByClick) {
118
+ open.value = event;
142
119
  }
143
- });
120
+ }
144
121
 
145
122
  const getMaxHeight = computed(() => {
146
123
  return props.maxHeight ? `${props.maxHeight}px` : 'none';
@@ -2,13 +2,16 @@
2
2
  <dialog
3
3
  ref="notification"
4
4
  class="s-notification"
5
- :class="{ '--popup': popup }"
5
+ :class="{'--popup': popup}"
6
6
  @click="closeByBackdropClick"
7
7
  @cancel="close"
8
8
  >
9
9
  <div class="s-notification__wrapper">
10
10
  <header class="s-notification__header">
11
- <p v-if="$slots.title" class="s-notification__title">
11
+ <p
12
+ v-if="$slots.title"
13
+ class="s-notification__title"
14
+ >
12
15
  <slot name="title"></slot>
13
16
  </p>
14
17
  <button
@@ -18,13 +21,16 @@
18
21
  aria-label="close"
19
22
  @click="close"
20
23
  >
21
- <SIcon class="s-notification__close-icon" name="close" size="16" />
24
+ <SIcon class="s-notification__close-icon" name="close" size="16"/>
22
25
  </button>
23
26
  </header>
24
27
  <div class="s-notification__content">
25
28
  <slot></slot>
26
29
  </div>
27
- <div v-if="$slots.footer" class="s-notification__footer">
30
+ <div
31
+ v-if="$slots.footer"
32
+ class="s-notification__footer"
33
+ >
28
34
  <slot name="footer"></slot>
29
35
  </div>
30
36
  </div>
@@ -32,71 +38,58 @@
32
38
  </template>
33
39
 
34
40
  <script setup lang="ts">
35
- import { ref, watch, onMounted, onBeforeUnmount } from 'vue'
36
- import { useScrollLock } from '@vueuse/core'
37
- import { onBackdropClick } from '../../runtime'
41
+ import { ref, watch, onBeforeUnmount } from 'vue';
42
+ import { useScrollLock } from '@vueuse/core';
43
+ import { onBackdropClick } from '../../runtime'; // оставляем, если используется где-то ещё
38
44
 
39
- const notification = ref<HTMLDialogElement | null>(null)
45
+ const notification = ref<HTMLDialogElement | null>(null);
40
46
 
41
47
  const emit = defineEmits<{
42
- (e: 'update:modelValue', value: boolean): void
43
- (e: 'close'): void
44
- }>()
48
+ (eventName: 'update:modelValue', value: boolean): void
49
+ (eventName: 'close'): void
50
+ }>();
45
51
 
46
52
  const props = defineProps<{
47
53
  modelValue: boolean
48
54
  popup?: boolean
49
- }>()
50
-
51
- // Создаём элемент ТОЛЬКО на клиенте
52
- const bodyElement = ref<HTMLElement | null>(null)
53
- const isBodyScrollLocked = useScrollLock(bodyElement)
55
+ }>();
54
56
 
55
- // Инициализируем body только после монтирования на клиенте
56
- onMounted(() => {
57
- bodyElement.value = document.body
58
- })
57
+ // Блокировка скролла тела страницы (body) при открытом модальном окне
58
+ const bodyElement = ref(document.body);
59
+ const isBodyScrollLocked = useScrollLock(bodyElement);
59
60
 
60
- // Реактивно управляем открытием/закрытием и блокировкой скролла
61
61
  watch(
62
62
  () => props.modelValue,
63
- (isOpen) => {
64
- if (!notification.value) return
65
-
66
- if (isOpen && props.popup) {
67
- notification.value.showModal()
68
- // Блокировка только если мы уже на клиенте и body доступен
69
- if (process.client && bodyElement.value) {
70
- isBodyScrollLocked.value = true
71
- }
72
- } else if (!isOpen && props.popup) {
73
- notification.value.close()
74
- if (process.client) {
75
- isBodyScrollLocked.value = false
76
- }
77
- } else if (isOpen && !props.popup) {
78
- notification.value.show()
79
- } else if (!isOpen && !props.popup) {
80
- notification.value.close()
63
+ (newVal) => {
64
+ if (newVal && props.popup) {
65
+ // Открываем dialog и блокируем скролл
66
+ notification.value?.showModal();
67
+ isBodyScrollLocked.value = true;
68
+ } else if (!newVal && props.popup) {
69
+ // Закрываем и разблокируем
70
+ notification.value?.close();
71
+ isBodyScrollLocked.value = false;
72
+ } else if (newVal && !props.popup) {
73
+ notification.value?.show();
74
+ } else if (!newVal && !props.popup) {
75
+ notification.value?.close();
81
76
  }
82
77
  },
83
78
  { immediate: true }
84
- )
79
+ );
85
80
 
86
- // Гарантированная разблокировка при уходе со страницы
81
+ // Дополнительная очистка при размонтировании (на всякий случай)
87
82
  onBeforeUnmount(() => {
88
- if (process.client) {
89
- isBodyScrollLocked.value = false
90
- }
91
- })
83
+ isBodyScrollLocked.value = false;
84
+ });
92
85
 
93
86
  function close() {
94
- emit('close')
95
- emit('update:modelValue', false)
87
+ emit('close');
88
+ emit('update:modelValue', false);
96
89
  }
97
90
 
98
91
  function closeByBackdropClick(event: Event) {
99
- const target = event.target as HTMLDialogElement
100
- onBackdropClick(target, close)
92
+ const target = event.target as HTMLDialogElement;
93
+ onBackdropClick(target, close);
101
94
  }
102
95
  </script>
@@ -1,143 +1,146 @@
1
1
  <template>
2
- <dialog
3
- ref="popupRef"
4
- class="s-popup"
5
- :class="[
6
- { '--sheet': sheet },
7
- { '--transparent-backdrop': transparentBackdrop },
8
- { '--aside': $slots.aside }
9
- ]"
10
- @click="closeByBackdropClick"
11
- @cancel="close"
12
- >
13
- <div class="s-popup__wrapper">
14
- <div v-if="sheet" class="s-popup__sheet">
15
- <button
16
- class="s-popup__sheet-handler"
17
- type="button"
18
- @click="close"
19
- ></button>
20
- </div>
2
+ <dialog
3
+ ref="popupRef"
4
+ class="s-popup"
5
+ :class="[
6
+ {'--sheet': sheet},
7
+ {'--transparent-backdrop': transparentBackdrop},
8
+ {'--aside': $slots.aside}
9
+ ]"
10
+ @click="closeByBackdropClick"
11
+ @cancel="close"
12
+ >
13
+ <div class="s-popup__wrapper">
14
+ <div
15
+ v-if="sheet"
16
+ class="s-popup__sheet"
17
+ >
18
+ <button
19
+ class="s-popup__sheet-handler"
20
+ type="button"
21
+ @click="close"
22
+ ></button>
23
+ </div>
21
24
 
22
- <aside
23
- v-if="$slots.aside && !isMobile(breakpoints).value"
24
- class="s-popup__aside"
25
+ <aside
26
+ v-if="$slots.aside && !isMobile(breakpoints).value"
27
+ class="s-popup__aside"
28
+ >
29
+ <slot name="aside"></slot>
30
+ </aside>
31
+
32
+ <header
33
+ class="s-popup__header"
34
+ >
35
+ <button
36
+ v-if="back"
37
+ class="s-popup__back"
38
+ type="button"
39
+ @click="$emit('back')"
25
40
  >
26
- <slot name="aside"></slot>
27
- </aside>
28
-
29
- <header class="s-popup__header">
30
- <button
31
- v-if="back"
32
- class="s-popup__back"
33
- type="button"
34
- @click="$emit('back')"
35
- >
36
- <SIcon name="angle-left" size="24" />
37
- </button>
38
-
39
- <div v-if="$slots.title" class="s-popup__title">
40
- <slot name="title"></slot>
41
- </div>
42
-
43
- <button
44
- class="s-popup__close"
45
- type="button"
46
- @click="close"
47
- >
48
- <SIcon name="close" size="24" />
49
- </button>
50
- </header>
51
-
52
- <div v-if="$slots.fixedContent" class="s-popup__subheader">
53
- <slot name="fixedContent"></slot>
54
- </div>
55
-
56
- <div v-if="$slots.default" class="s-popup__content">
57
- <slot></slot>
58
- </div>
59
-
60
- <div v-if="$slots.footer" class="s-popup__footer">
61
- <slot name="footer"></slot>
41
+ <SIcon name="angle-left" size="24" />
42
+ </button>
43
+ <div
44
+ v-if="$slots.title"
45
+ class="s-popup__title"
46
+ >
47
+ <slot name="title"></slot>
62
48
  </div>
49
+ <button
50
+ class="s-popup__close"
51
+ type="button"
52
+ @click="close"
53
+ >
54
+ <SIcon name="close" size="24" />
55
+ </button>
56
+ </header>
57
+ <div
58
+ v-if="$slots.fixedContent"
59
+ class="s-popup__subheader"
60
+ >
61
+ <slot name="fixedContent"></slot>
63
62
  </div>
64
-
65
- <transition mode="out-in">
66
- <SSnackbar v-if="snackbarText && isSnackBarInPopup" />
67
- </transition>
68
- </dialog>
63
+ <div
64
+ v-if="$slots.default"
65
+ class="s-popup__content"
66
+ >
67
+ <slot></slot>
68
+ </div>
69
+ <div
70
+ v-if="$slots.footer"
71
+ class="s-popup__footer"
72
+ >
73
+ <slot name="footer"></slot>
74
+ </div>
75
+ </div>
76
+
77
+ <transition mode="out-in">
78
+ <SSnackbar
79
+ v-if="snackbarText && isSnackBarInPopup"
80
+ />
81
+ </transition>
82
+ </dialog>
69
83
  </template>
70
84
 
71
85
  <script setup lang="ts">
72
- import { ref, watch, nextTick, onMounted, onBeforeUnmount } from 'vue';
73
- import { useScrollLock } from '@vueuse/core';
74
86
  import { onBackdropClick, isMobile } from '../../runtime';
75
87
  import type { CssBreakpoints } from '../../types';
88
+ import { useScrollLock } from '@vueuse/core';
76
89
 
77
- const popupRef = ref<HTMLDialogElement | null>(null);
90
+ const {resetSnackbar} = useSnackbar();
78
91
 
92
+ const breakpoints = useAppConfig().cssBreakpoints as Record<CssBreakpoints, number>;
93
+ const popupRef = ref()
79
94
  const emit = defineEmits<{
80
- (e: 'update:modelValue', value: boolean): void;
81
- (e: 'back'): void;
82
- (e: 'close'): void;
83
- }>();
95
+ (eventName: 'update:modelValue', value: boolean): void
96
+ (eventName: 'back'): void
97
+ (eventName: 'close'): void
98
+ }>()
84
99
 
85
100
  const props = defineProps<{
86
- modelValue: boolean;
87
- back?: boolean;
88
- transparentBackdrop?: boolean;
89
- sheet?: boolean;
90
- title?: string;
91
- notModal?: boolean;
92
- }>();
93
-
94
- const breakpoints = useAppConfig().cssBreakpoints as Record<CssBreakpoints, number>;
101
+ modelValue: boolean
102
+ back?: boolean
103
+ transparentBackdrop?: boolean
104
+ sheet?: boolean
105
+ title?: string
106
+ notModal?: boolean
107
+ }>()
108
+
109
+ const isLocked = useScrollLock(
110
+ typeof document !== 'undefined' ? document.body : null
111
+ );
95
112
 
96
- // SSR-safe scroll lock
97
- const bodyElement = ref<HTMLElement | null>(null);
98
- const isLocked = useScrollLock(bodyElement);
113
+ watch(
114
+ () => props.modelValue,
115
+ (newVal) => {
116
+ if (newVal) {
117
+ nextTick(() => {
118
+ isLocked.value = true;
119
+
120
+ if (props.notModal) {
121
+ popupRef.value?.show();
122
+ } else {
123
+ popupRef.value?.showModal();
124
+ }
125
+ popupRef.value?.focus({ preventScroll: true });
126
+ });
127
+ } else {
128
+ isLocked.value = false;
129
+ }
130
+ },
131
+ { immediate: true }
132
+ );
99
133
 
100
134
  onMounted(() => {
101
- bodyElement.value = document.body;
102
-
103
- // Включаем режим отображения снакбара внутри попапа
104
135
  isSnackBarInPopup.value = true;
105
136
  resetSnackbar();
106
137
  });
107
138
 
108
139
  onBeforeUnmount(() => {
109
140
  isSnackBarInPopup.value = false;
110
- if (process.client) {
111
- isLocked.value = false;
112
- }
141
+ isLocked.value = false;
113
142
  });
114
143
 
115
- // Реактивное открытие/закрытие + блокировка скролла
116
- watch(
117
- () => props.modelValue,
118
- async (isOpen) => {
119
- if (!process.client || !popupRef.value) return;
120
-
121
- if (isOpen) {
122
- await nextTick(); // гарантируем, что dialog уже в DOM
123
-
124
- if (props.notModal) {
125
- popupRef.value.show();
126
- } else {
127
- popupRef.value.showModal();
128
- }
129
-
130
- popupRef.value.focus({ preventScroll: true });
131
- isLocked.value = true;
132
- } else {
133
- isLocked.value = false;
134
- // dialog автоматически закрывается при удалении из DOM, но на всякий случай
135
- popupRef.value.close();
136
- }
137
- },
138
- { immediate: true }
139
- );
140
-
141
144
  function close() {
142
145
  emit('close');
143
146
  emit('update:modelValue', false);
@@ -148,6 +151,5 @@ function closeByBackdropClick(event: Event) {
148
151
  onBackdropClick(target, close);
149
152
  }
150
153
 
151
- // Доступ к состоянию снакбара
152
- const { snackbarText, isSnackBarInPopup, resetSnackbar } = useSnackbar();
154
+ const { snackbarText, isSnackBarInPopup } = useSnackbar();
153
155
  </script>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "itube-specs",
3
3
  "type": "module",
4
- "version": "0.0.363",
4
+ "version": "0.0.364",
5
5
  "main": "./nuxt.config.ts",
6
6
  "types": "./types/index.d.ts",
7
7
  "scripts": {