itube-specs 0.0.1046 → 0.0.1047

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,4 +1,4 @@
1
- <!-- Инфо-блок модели: портрет 3:4, псевдонимы, соцсети, шапка с действиями, рождение, описание, сводка-чипсы; «About» и группы параметров — по «Show more» -->
1
+ <!-- Инфо-блок модели: портрет 3:4, псевдонимы, соцсети, шапка с действиями, рождение, описание, сводка-чипсы; «About» и группы параметров — по «Show more», на десктопе свёрнутые доливают колонку до низа портрета -->
2
2
  <template>
3
3
  <section class="info-model">
4
4
  <div class="info-model__aside">
@@ -122,7 +122,8 @@
122
122
 
123
123
  <div
124
124
  v-if="hasMore"
125
- :class="{'--collapsed': !isExpanded, '--about': card.fullDescription}"
125
+ ref="detailsRef"
126
+ :class="{'--collapsed': !isExpanded}"
126
127
  class="info-model__details"
127
128
  >
128
129
  <div
@@ -146,7 +147,9 @@
146
147
 
147
148
  <button
148
149
  v-if="hasMore"
150
+ ref="moreRef"
149
151
  :aria-expanded="isExpanded"
152
+ :class="{'--fit': !hasCut && !isExpanded}"
150
153
  class="info-model__more"
151
154
  type="button"
152
155
  @click="isExpanded = !isExpanded"
@@ -245,6 +248,79 @@ const isExpanded = ref(false);
245
248
 
246
249
  const hasMore = computed(() => groupedParameters.value.length > 0 || Boolean(props.card.fullDescription));
247
250
 
251
+ /** Единицы, которые прячутся только целиком: заголовки, абзацы, плашки. Порядок — документный. */
252
+ const FIT_SELECTOR = 'h3, .info-model__about-text, .info-grid__plain-text, .info-grid__item';
253
+
254
+ /** Если всё целиком выпирает ниже портрета не больше чем на столько — показываем всё без «Show more». */
255
+ const FIT_TOLERANCE = 64;
256
+
257
+ const detailsRef = ref<HTMLElement>();
258
+ const moreRef = ref<HTMLElement>();
259
+ const hasCut = ref(true);
260
+
261
+ /** Строка грида под кнопкой (с её отступами): без «Show more» она достаётся деталям. */
262
+ function getMoreRowHeight(box: HTMLElement) {
263
+ const more = moreRef.value;
264
+ if (!more || !more.offsetParent) return 0;
265
+
266
+ return more.getBoundingClientRect().bottom + Number.parseFloat(getComputedStyle(more).marginBottom)
267
+ - box.getBoundingClientRect().bottom;
268
+ }
269
+
270
+ /**
271
+ * На десктопе высоту свёрнутых деталей задаёт грид — остаток колонки до низа портрета.
272
+ * Всё, что не влезло целиком, прячем; заголовок без единой видимой строки под ним — тоже.
273
+ * Небольшой хвост в пределах допуска не прячем, а вытягиваем блок под него через `min-height`.
274
+ */
275
+ function measureDetails() {
276
+ const box = detailsRef.value;
277
+ if (!box) return;
278
+
279
+ const units = Array.from(box.querySelectorAll<HTMLElement>(FIT_SELECTOR));
280
+ box.style.minHeight = '';
281
+
282
+ if (isExpanded.value || !isChipsOverflowWidth()) {
283
+ units.forEach(el => el.removeAttribute('data-cut'));
284
+ hasCut.value = true;
285
+ return;
286
+ }
287
+
288
+ const { top } = box.getBoundingClientRect();
289
+ const limit = box.clientHeight + 0.5;
290
+ const bottoms = units.map(el => el.getBoundingClientRect().bottom - top);
291
+ const contentHeight = Math.max(0, ...bottoms);
292
+
293
+ const fullLimit = limit + getMoreRowHeight(box);
294
+
295
+ if (contentHeight <= fullLimit + FIT_TOLERANCE) {
296
+ if (contentHeight > fullLimit) box.style.minHeight = `${Math.ceil(contentHeight)}px`;
297
+ units.forEach(el => el.removeAttribute('data-cut'));
298
+ hasCut.value = false;
299
+ return;
300
+ }
301
+
302
+ const visible: boolean[] = [];
303
+ let isOver = false;
304
+
305
+ bottoms.forEach((bottom, index) => {
306
+ isOver ||= bottom > limit;
307
+ visible[index] = !isOver;
308
+ });
309
+
310
+ for (let index = units.length - 1; index >= 0; index--) {
311
+ if (units[index].tagName === 'H3' && !(visible[index + 1] && units[index + 1].tagName !== 'H3')) {
312
+ visible[index] = false;
313
+ }
314
+ }
315
+
316
+ units.forEach((el, index) => el.toggleAttribute('data-cut', !visible[index]));
317
+ hasCut.value = visible.includes(false);
318
+ }
319
+
320
+ useChipsOverflow(detailsRef, measureDetails);
321
+
322
+ watch([isExpanded, () => props.card], () => nextTick(measureDetails));
323
+
248
324
  const isShareOpen = ref(false);
249
325
 
250
326
  const { isAuthorized } = useUser(AuthorizationApiService);
@@ -308,15 +384,14 @@ const shareModelCard = computed<IModelCard>(() => ({
308
384
 
309
385
  @include from-br(sm) {
310
386
  grid-template-columns: 240px minmax(0, 1fr);
311
- grid-template-rows: auto auto auto auto auto auto 1fr;
387
+ grid-template-rows: auto auto auto auto 1fr auto;
312
388
  grid-template-areas:
313
389
  'aside header'
314
390
  'aside born'
315
391
  'aside description'
316
392
  'aside summary'
317
393
  'aside details'
318
- 'aside more'
319
- 'aside .';
394
+ 'aside more';
320
395
  }
321
396
  }
322
397
 
@@ -543,14 +618,16 @@ const shareModelCard = computed<IModelCard>(() => ({
543
618
  @include to-br(sm) {
544
619
  display: none;
545
620
  }
546
- }
547
621
 
548
- .info-model__details.--collapsed:not(.--about) {
549
- display: none;
622
+ @include from-br(sm) {
623
+ align-content: start;
624
+ overflow: clip;
625
+ contain: size;
626
+ }
550
627
  }
551
628
 
552
- .info-model__details.--collapsed .info-model__parameters {
553
- display: none;
629
+ .info-model__details [data-cut] {
630
+ visibility: hidden;
554
631
  }
555
632
 
556
633
  .info-model__about-title {
@@ -592,6 +669,12 @@ const shareModelCard = computed<IModelCard>(() => ({
592
669
  }
593
670
  }
594
671
 
672
+ .info-model__more.--fit {
673
+ @include from-br(sm) {
674
+ display: none;
675
+ }
676
+ }
677
+
595
678
  .info-model__more-icon {
596
679
  transition: var(--transition) transform;
597
680
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "itube-specs",
3
3
  "type": "module",
4
- "version": "0.0.1046",
4
+ "version": "0.0.1047",
5
5
  "main": "./nuxt.config.ts",
6
6
  "types": "./types/index.d.ts",
7
7
  "scripts": {