itube-specs 0.0.874 → 0.0.875

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"
@@ -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.875",
5
5
  "main": "./nuxt.config.ts",
6
6
  "types": "./types/index.d.ts",
7
7
  "scripts": {