itube-specs 0.0.363 → 0.0.365

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