itube-specs 0.0.874 → 0.0.876

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.
@@ -26,8 +26,8 @@
26
26
  <UiInput
27
27
  ref="usernameRef"
28
28
  v-model="form.username"
29
- :label="t('login')"
30
- :placeholder="t('login')"
29
+ :label="t('auth.username')"
30
+ :placeholder="t('auth.username')"
31
31
  hide-label
32
32
  :error="error.username"
33
33
  name="login-name"
@@ -8,8 +8,41 @@
8
8
  wide
9
9
  size="l"
10
10
  theme="warning"
11
- @click="deleteProfile"
11
+ @click="isConfirmOpen = true"
12
12
  >{{ t('profile.delete_profile') }}</UiBtn>
13
+
14
+ <transition mode="out-in">
15
+ <UiPopup
16
+ v-if="isConfirmOpen"
17
+ v-model="isConfirmOpen"
18
+ sheet
19
+ >
20
+ <template #title>{{ $t('profile.delete_profile') }}</template>
21
+
22
+ <p class="profile-delete__confirm">{{ $t('confirm_delete_profile') }}</p>
23
+
24
+ <template #footer>
25
+ <div class="profile-delete__buttons">
26
+ <UiBtn
27
+ :disabled="loading"
28
+ wide
29
+ size="l"
30
+ theme="secondary"
31
+ @click="isConfirmOpen = false"
32
+ >{{ $t('cancel') }}
33
+ </UiBtn>
34
+ <UiBtn
35
+ :disabled="loading"
36
+ wide
37
+ size="l"
38
+ theme="warning"
39
+ @click="deleteProfile"
40
+ >{{ $t('yes') }}
41
+ </UiBtn>
42
+ </div>
43
+ </template>
44
+ </UiPopup>
45
+ </transition>
13
46
  </div>
14
47
  </template>
15
48
 
@@ -21,12 +54,14 @@ const { signOut } = useUser(AuthorizationApiService);
21
54
  const { showSuccess } = useSnackbar();
22
55
 
23
56
  const loading = ref(false);
57
+ const isConfirmOpen = ref(false);
24
58
 
25
59
  async function deleteProfile() {
26
60
  try {
27
61
  loading.value = true;
28
62
  await AuthorizationApiService.deleteProfile();
29
- showSuccess(t('snackbar.profile_deleted'));
63
+ isConfirmOpen.value = false;
64
+ showSuccess('profile_deleted');
30
65
  signOut();
31
66
  } catch (err) {
32
67
  console.log(err, 'error delete profile');
@@ -59,4 +94,20 @@ async function deleteProfile() {
59
94
  .profile-delete__button {
60
95
  height: 50px;
61
96
  }
97
+
98
+ .profile-delete__confirm {
99
+ @include font-sm;
100
+
101
+ text-align: center;
102
+ max-width: 400px;
103
+ }
104
+
105
+ .profile-delete__buttons {
106
+ display: grid;
107
+ gap: 8px;
108
+
109
+ @include from-br(sm) {
110
+ grid-template-columns: 1fr 1fr;
111
+ }
112
+ }
62
113
  </style>
@@ -196,7 +196,7 @@ async function deleteAvatar() {
196
196
  form.value.avatar = '';
197
197
  await getProfile(true);
198
198
  avatarPreview.value = '';
199
- showSuccess(t('snackbar.avatar_deleted'));
199
+ showSuccess('avatar_deleted');
200
200
  } finally {
201
201
  deletingAvatar.value = false;
202
202
  }
@@ -9,6 +9,7 @@
9
9
  class="ui-expand-row"
10
10
  >
11
11
  <div
12
+ ref="wrapperRef"
12
13
  :class="[{ '--more-show': isElementsOverflow }]"
13
14
  class="ui-expand-row__wrapper"
14
15
  >
@@ -20,8 +21,8 @@
20
21
  </slot>
21
22
  <slot>
22
23
  <UiChips
23
- v-for="item in itemsResult"
24
- :key="item.title"
24
+ v-for="(item, index) in items"
25
+ :key="`${index}-${item.title}`"
25
26
  :item="item"
26
27
  :alphabet="alphabet"
27
28
  is-link
@@ -51,19 +52,14 @@
51
52
  <script setup lang="ts">
52
53
  import type { IChipsItem } from '../../types';
53
54
 
54
- const props = withDefaults(
55
- defineProps<{
56
- items?: IChipsItem[];
57
- alphabet?: boolean;
58
- hideMore?: boolean;
59
- mini?: boolean;
60
- mobileExpand?: boolean;
61
- maximumButtons?: number;
62
- preText?: string;
63
- }>(), {
64
- maximumButtons: 20, // вычислено на глаз, приблизительно
65
- }
66
- );
55
+ defineProps<{
56
+ items?: IChipsItem[];
57
+ alphabet?: boolean;
58
+ hideMore?: boolean;
59
+ mini?: boolean;
60
+ mobileExpand?: boolean;
61
+ preText?: string;
62
+ }>();
67
63
 
68
64
  const { t } = useI18n();
69
65
 
@@ -71,28 +67,52 @@ const emit = defineEmits<{
71
67
  (eventName: 'more', eventValue: boolean): void
72
68
  }>();
73
69
 
74
- const containerRef = ref();
70
+ const containerRef = ref<HTMLElement>();
75
71
 
76
- const isExpandOpened = ref(false);
72
+ const wrapperRef = ref<HTMLElement>();
77
73
 
78
- const itemsResult = computed(() => {
79
- return props.items?.slice(
80
- 0,
81
- !props.alphabet && !isExpandOpened.value
82
- ? props.maximumButtons
83
- : props.items.length,
84
- );
85
- });
74
+ const isExpandOpened = ref(false);
86
75
 
87
76
  const moreButtonName = computed(() => isExpandOpened.value ? t('less') : t('more'));
88
77
 
89
78
  const isElementsOverflow = ref(false);
90
79
 
80
+ /**
81
+ * Обрезку делает CSS (`max-height: var(--chips-height)` + `overflow: hidden`), здесь только
82
+ * определяем, вылез ли контент за одну строку, — от этого зависит кнопка «еще».
83
+ */
91
84
  function checkIsElementOverflow() {
92
- isElementsOverflow.value = !!(
93
- containerRef.value &&
94
- containerRef.value.scrollHeight > containerRef.value.clientHeight
95
- );
85
+ // в раскрытом виде контейнер тянется под контент, замер дал бы ложное «переполнения нет»
86
+ if (!containerRef.value || isExpandOpened.value) {
87
+ return;
88
+ }
89
+
90
+ isElementsOverflow.value = containerRef.value.scrollHeight > containerRef.value.clientHeight;
91
+ }
92
+
93
+ /**
94
+ * Обрезанные строки остаются в разметке (нужны краулеру), но не должны ловить таб-фокус
95
+ * и попадать в дерево доступности — гасим их через `inert`.
96
+ */
97
+ function updateHiddenItemsInertness() {
98
+ if (!containerRef.value || !wrapperRef.value) {
99
+ return;
100
+ }
101
+
102
+ const visibleHeight = containerRef.value.clientHeight;
103
+
104
+ for (const child of Array.from(wrapperRef.value.children) as HTMLElement[]) {
105
+ if (child.classList.contains('ui-expand-row__more-wrapper')) {
106
+ continue;
107
+ }
108
+
109
+ child.inert = !isExpandOpened.value && child.offsetTop + child.offsetHeight > visibleHeight + 1;
110
+ }
111
+ }
112
+
113
+ function measure() {
114
+ checkIsElementOverflow();
115
+ updateHiddenItemsInertness();
96
116
  }
97
117
 
98
118
  function onMoreClick() {
@@ -100,18 +120,23 @@ function onMoreClick() {
100
120
  emit('more', isExpandOpened.value);
101
121
  }
102
122
 
103
- onMounted(() => {
104
- requestAnimationFrame(() => checkIsElementOverflow());
105
- });
123
+ watch(isExpandOpened, () => nextTick(updateHiddenItemsInertness));
124
+
125
+ let resizeObserver: ResizeObserver | null = null;
106
126
 
107
- let checkTimer: ReturnType<typeof setTimeout> | null = null;
127
+ onMounted(() => {
128
+ if (!wrapperRef.value) {
129
+ return;
130
+ }
108
131
 
109
- watch(() => useRoute().query, () => {
110
- checkTimer = setTimeout(() => checkIsElementOverflow(), 100);
132
+ // следим за внутренней обёрткой: её высота меняется и от ресайза, и от смены набора чипсов
133
+ resizeObserver = new ResizeObserver(() => measure());
134
+ resizeObserver.observe(wrapperRef.value);
111
135
  });
112
136
 
113
137
  onBeforeUnmount(() => {
114
- if (checkTimer) clearTimeout(checkTimer);
138
+ resizeObserver?.disconnect();
139
+ resizeObserver = null;
115
140
  });
116
141
  </script>
117
142
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "itube-specs",
3
3
  "type": "module",
4
- "version": "0.0.874",
4
+ "version": "0.0.876",
5
5
  "main": "./nuxt.config.ts",
6
6
  "types": "./types/index.d.ts",
7
7
  "scripts": {
@@ -108,16 +108,6 @@ const items = computed<Array<IDataItem>>(() => {
108
108
  text: data.value.gender?.title ?? '',
109
109
  icon: 'double-user'
110
110
  },
111
- {
112
- title: t('profile.gender'),
113
- text: data.value.gender?.title ?? '',
114
- icon: 'double-user'
115
- },
116
- {
117
- title: t('profile.gender'),
118
- text: data.value.gender?.title ?? '',
119
- icon: 'double-user'
120
- },
121
111
  ];
122
112
  });
123
113