@feugene/granularity 0.42.0 → 0.44.0
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.
- package/README.md +2 -2
- package/dist/chunks/config-BXRmsn3t.js +172 -0
- package/dist/chunks/config-BXRmsn3t.js.map +1 -0
- package/dist/chunks/config-BkQ2jvXv.js +49 -0
- package/dist/chunks/config-BkQ2jvXv.js.map +1 -0
- package/dist/chunks/config-Dg0bSoGw.js +81 -0
- package/dist/chunks/config-Dg0bSoGw.js.map +1 -0
- package/dist/chunks/config-DmED6aJV.js +237 -0
- package/dist/chunks/config-DmED6aJV.js.map +1 -0
- package/dist/chunks/{config-DWL81-aM.js → config-hnoZvcIM.js} +2 -20
- package/dist/chunks/config-hnoZvcIM.js.map +1 -0
- package/dist/chunks/dragSortGeometry-CY-llfrQ.js +87 -0
- package/dist/chunks/dragSortGeometry-CY-llfrQ.js.map +1 -0
- package/dist/chunks/{en-B9E3jO6m.js → en-DiHzFact.js} +34 -1
- package/dist/chunks/en-DiHzFact.js.map +1 -0
- package/dist/chunks/{es-QpzdIEut.js → es-1ZL6NtkB.js} +34 -1
- package/dist/chunks/es-1ZL6NtkB.js.map +1 -0
- package/dist/chunks/{es-DCkqHTlU.js → es-CeKkMxZ_.js} +3 -3
- package/dist/chunks/{es-DCkqHTlU.js.map → es-CeKkMxZ_.js.map} +1 -1
- package/dist/chunks/{generated-B95IBXs6.js → generated-Hxu_sWPs.js} +238 -0
- package/dist/chunks/generated-Hxu_sWPs.js.map +1 -0
- package/dist/chunks/{granular-provider-BG_2XqkS.js → granular-provider-CCNbQa5U.js} +9 -1
- package/dist/chunks/granular-provider-CCNbQa5U.js.map +1 -0
- package/dist/chunks/{ru-D_1Ya489.js → ru-Dr2RYn0P.js} +34 -1
- package/dist/chunks/ru-Dr2RYn0P.js.map +1 -0
- package/dist/chunks/scrollOverflow-B_kEdcol.js +31 -0
- package/dist/chunks/scrollOverflow-B_kEdcol.js.map +1 -0
- package/dist/chunks/tones-CRJAaYyg.js +21 -0
- package/dist/chunks/tones-CRJAaYyg.js.map +1 -0
- package/dist/chunks/useScrollSpy-CIJpw0RI.js +360 -0
- package/dist/chunks/useScrollSpy-CIJpw0RI.js.map +1 -0
- package/dist/components/GrAffix/chunks/GrAffix-CWtC7yi8.js +272 -0
- package/dist/components/GrAffix/chunks/GrAffix-CWtC7yi8.js.map +1 -0
- package/dist/components/GrAffix/index.js +3 -0
- package/dist/components/GrCarousel/chunks/GrCarousel-ByuughtI.js +761 -0
- package/dist/components/GrCarousel/chunks/GrCarousel-ByuughtI.js.map +1 -0
- package/dist/components/GrCarousel/index.js +3 -0
- package/dist/components/GrCarousel/styles.css +1 -0
- package/dist/components/GrColorPicker/chunks/{GrColorPicker-BwzRLs-i.js → GrColorPicker-9mTR-YZ_.js} +1 -1
- package/dist/components/GrColorPicker/chunks/{GrColorPicker-BwzRLs-i.js.map → GrColorPicker-9mTR-YZ_.js.map} +1 -1
- package/dist/components/GrColorPicker/index.js +1 -1
- package/dist/components/GrDataTable/chunks/{GrDataTable-BBe7-uUe.js → GrDataTable-xNSmO_I-.js} +4 -3
- package/dist/components/GrDataTable/chunks/{GrDataTable-BBe7-uUe.js.map → GrDataTable-xNSmO_I-.js.map} +1 -1
- package/dist/components/GrDataTable/index.js +1 -1
- package/dist/components/GrImageViewer/chunks/{GrImageViewer-Dd2gJ-j_.js.map → GrImageViewer-DK-iVHHC.js.map} +1 -1
- package/dist/components/GrImageViewer/index.js +1 -1
- package/dist/components/GrInputTag/chunks/{GrInputTag-BKzm1PKw.js.map → GrInputTag-Ci8AO1ze.js.map} +1 -1
- package/dist/components/GrInputTag/index.js +1 -1
- package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-BLCEWlwg.js → GrJsonViewer-CwCBMh4h.js} +2 -2
- package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-BLCEWlwg.js.map → GrJsonViewer-CwCBMh4h.js.map} +1 -1
- package/dist/components/GrJsonViewer/index.js +1 -1
- package/dist/components/GrNumberInput/chunks/{GrNumberInput-Bm-V59TG.js.map → GrNumberInput-CL5QhOmx.js.map} +1 -1
- package/dist/components/GrNumberInput/index.js +1 -1
- package/dist/components/GrProgressBar/index.js +1 -1
- package/dist/components/GrScrollSpy/chunks/GrScrollSpy-CQCH3zLC.js +207 -0
- package/dist/components/GrScrollSpy/chunks/GrScrollSpy-CQCH3zLC.js.map +1 -0
- package/dist/components/GrScrollSpy/index.js +3 -0
- package/dist/components/GrSlider/chunks/{GrSlider-Db-jACXz.js.map → GrSlider-CO8N9SF6.js.map} +1 -1
- package/dist/components/GrSlider/index.js +1 -1
- package/dist/components/GrSortableList/chunks/{GrSortableList-ce1psLL0.js → GrSortableList-NKriuGm_.js} +2 -1
- package/dist/components/GrSortableList/chunks/{GrSortableList-ce1psLL0.js.map → GrSortableList-NKriuGm_.js.map} +1 -1
- package/dist/components/GrSortableList/index.js +1 -1
- package/dist/components/GrTabs/chunks/{GrTabs-BArefUFv.js → GrTabs-s9RzC1WX.js} +2 -31
- package/dist/components/GrTabs/chunks/GrTabs-s9RzC1WX.js.map +1 -0
- package/dist/components/GrTabs/index.js +1 -1
- package/dist/components/GrTransfer/chunks/GrTransfer-8FA78ny6.js +1368 -0
- package/dist/components/GrTransfer/chunks/GrTransfer-8FA78ny6.js.map +1 -0
- package/dist/components/GrTransfer/index.js +3 -0
- package/dist/components/GrTransfer/styles.css +1 -0
- package/dist/components/GrTree/chunks/{GrTree-BFWFc0Bm.js → GrTree-CkHzgYg1.js} +3 -3
- package/dist/components/GrTree/chunks/{GrTree-BFWFc0Bm.js.map → GrTree-CkHzgYg1.js.map} +1 -1
- package/dist/components/GrTree/index.js +1 -1
- package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-DJa2lYGe.js → GrTreeSelect-DUsv3_mF.js} +1 -1
- package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-DJa2lYGe.js.map → GrTreeSelect-DUsv3_mF.js.map} +1 -1
- package/dist/components/GrTreeSelect/index.js +1 -1
- package/dist/composables/useDragSort.js +179 -1
- package/dist/composables/useDragSort.js.map +1 -0
- package/dist/composables/useScrollSpy.js +2 -0
- package/dist/granular-provider-node.js +1 -1
- package/dist/granular-provider.js +1 -1
- package/dist/i18n/all.js +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/index.js +26 -19
- package/dist/theme.js +1 -1
- package/dist/tokens.js +1 -1
- package/dist/types/components/GrAffix/GrAffix.vue.d.ts +91 -0
- package/dist/types/components/GrAffix/affixState.d.ts +81 -0
- package/dist/types/components/GrAffix/config.d.ts +1 -0
- package/dist/types/components/GrAffix/grAffixStyles.d.ts +46 -0
- package/dist/types/components/GrAffix/index.d.ts +9 -0
- package/dist/types/components/GrAffix/safelist.d.ts +1 -0
- package/dist/types/components/GrAlert/GrAlert.vue.d.ts +1 -1
- package/dist/types/components/GrBadgeWrap/GrBadgeWrap.vue.d.ts +2 -2
- package/dist/types/components/GrButton/GrButton.vue.d.ts +2 -2
- package/dist/types/components/GrCard/GrCard.vue.d.ts +1 -1
- package/dist/types/components/GrCarousel/GrCarousel.vue.d.ts +151 -0
- package/dist/types/components/GrCarousel/GrCarouselSlide.vue.d.ts +53 -0
- package/dist/types/components/GrCarousel/carouselNavigation.d.ts +57 -0
- package/dist/types/components/GrCarousel/composables/useCarouselAutoplay.d.ts +33 -0
- package/dist/types/components/GrCarousel/composables/useCarouselSwipe.d.ts +20 -0
- package/dist/types/components/GrCarousel/config.d.ts +7 -0
- package/dist/types/components/GrCarousel/defaults.d.ts +13 -0
- package/dist/types/components/GrCarousel/grCarouselContext.d.ts +46 -0
- package/dist/types/components/GrCarousel/grCarouselStyles.d.ts +80 -0
- package/dist/types/components/GrCarousel/index.d.ts +12 -0
- package/dist/types/components/GrCarousel/safelist.d.ts +1 -0
- package/dist/types/components/GrCheckbox/GrCheckbox.vue.d.ts +3 -3
- package/dist/types/components/GrCheckboxGroup/GrCheckboxGroup.vue.d.ts +2 -2
- package/dist/types/components/GrChip/GrChip.vue.d.ts +2 -2
- package/dist/types/components/GrChipGroup/GrChipGroup.vue.d.ts +2 -2
- package/dist/types/components/GrCollapse/GrCollapse.vue.d.ts +1 -1
- package/dist/types/components/GrCollapse/GrCollapseItem.vue.d.ts +1 -1
- package/dist/types/components/GrColorPicker/GrColorPicker.vue.d.ts +3 -3
- package/dist/types/components/GrCommandPalette/GrCommandPalette.vue.d.ts +1 -1
- package/dist/types/components/GrConfirmDialog/GrConfirmDialog.vue.d.ts +1 -1
- package/dist/types/components/GrContextMenu/GrContextMenu.vue.d.ts +2 -2
- package/dist/types/components/GrDialog/GrDialog.vue.d.ts +1 -1
- package/dist/types/components/GrDivider/GrDivider.vue.d.ts +2 -2
- package/dist/types/components/GrDrawer/GrDrawer.vue.d.ts +1 -1
- package/dist/types/components/GrDropdown/GrDropdown.vue.d.ts +3 -3
- package/dist/types/components/GrDropdownMenu/GrDropdownMenu.vue.d.ts +3 -3
- package/dist/types/components/GrDropdownMenu/GrDropdownMenuItem.vue.d.ts +1 -1
- package/dist/types/components/GrEmptyState/GrEmptyState.vue.d.ts +1 -1
- package/dist/types/components/GrFileUpload/GrFileUpload.vue.d.ts +1 -1
- package/dist/types/components/GrFormField/GrFormField.vue.d.ts +3 -3
- package/dist/types/components/GrFormFile/GrFormFile.vue.d.ts +2 -2
- package/dist/types/components/GrFormSection/GrFormSection.vue.d.ts +1 -1
- package/dist/types/components/GrIcon/GrIcon.vue.d.ts +1 -1
- package/dist/types/components/GrImageViewer/GrImageViewer.vue.d.ts +3 -3
- package/dist/types/components/GrInput/GrInput.vue.d.ts +4 -4
- package/dist/types/components/GrInputTag/GrInputTag.vue.d.ts +3 -3
- package/dist/types/components/GrKbd/GrKbd.vue.d.ts +1 -1
- package/dist/types/components/GrLink/GrLink.vue.d.ts +1 -1
- package/dist/types/components/GrList/GrListItem.vue.d.ts +2 -2
- package/dist/types/components/GrNavbar/GrNavbar.vue.d.ts +2 -2
- package/dist/types/components/GrNumberInput/GrNumberInput.vue.d.ts +3 -3
- package/dist/types/components/GrPagination/GrPagination.vue.d.ts +1 -1
- package/dist/types/components/GrPopover/GrPopover.vue.d.ts +3 -3
- package/dist/types/components/GrPromptDialog/GrPromptDialog.vue.d.ts +3 -3
- package/dist/types/components/GrRadio/GrRadio.vue.d.ts +2 -2
- package/dist/types/components/GrRadioGroup/GrRadioGroup.vue.d.ts +2 -2
- package/dist/types/components/GrRating/GrRating.vue.d.ts +2 -2
- package/dist/types/components/GrScrollSpy/GrScrollSpy.vue.d.ts +105 -0
- package/dist/types/components/GrScrollSpy/config.d.ts +1 -0
- package/dist/types/components/GrScrollSpy/grScrollSpyStyles.d.ts +26 -0
- package/dist/types/components/GrScrollSpy/index.d.ts +9 -0
- package/dist/types/components/GrScrollSpy/safelist.d.ts +1 -0
- package/dist/types/components/GrScrollSpy/scrollSpyItems.d.ts +44 -0
- package/dist/types/components/GrSegmented/GrSegmented.vue.d.ts +2 -2
- package/dist/types/components/GrSidebar/GrSidebarItem.vue.d.ts +1 -1
- package/dist/types/components/GrSkeleton/GrSkeleton.vue.d.ts +1 -1
- package/dist/types/components/GrSlider/GrSlider.vue.d.ts +3 -3
- package/dist/types/components/GrSplitter/GrSplitter.vue.d.ts +1 -1
- package/dist/types/components/GrStatistic/GrStatistic.vue.d.ts +2 -2
- package/dist/types/components/GrSwitch/GrSwitch.vue.d.ts +3 -3
- package/dist/types/components/GrTable/GrTable.vue.d.ts +1 -1
- package/dist/types/components/GrTabs/GrTabs.vue.d.ts +1 -1
- package/dist/types/components/GrTextarea/GrTextarea.vue.d.ts +2 -2
- package/dist/types/components/GrTimeline/GrTimelineItem.vue.d.ts +2 -2
- package/dist/types/components/GrToaster/GrToaster.vue.d.ts +1 -1
- package/dist/types/components/GrTooltip/GrTooltip.vue.d.ts +3 -3
- package/dist/types/components/GrTransfer/GrTransfer.vue.d.ts +103 -0
- package/dist/types/components/GrTransfer/config.d.ts +1 -0
- package/dist/types/components/GrTransfer/defaults.d.ts +15 -0
- package/dist/types/components/GrTransfer/grTransferStyles.d.ts +127 -0
- package/dist/types/components/GrTransfer/index.d.ts +11 -0
- package/dist/types/components/GrTransfer/safelist.d.ts +1 -0
- package/dist/types/components/GrTransfer/transferGeometry.d.ts +44 -0
- package/dist/types/components/GrTransfer/transferModel.d.ts +68 -0
- package/dist/types/components/GrTransfer/transferSelection.d.ts +36 -0
- package/dist/types/components/GrTransfer/useTransferDrag.d.ts +48 -0
- package/dist/types/composables/internal/scrollSpyGeometry.d.ts +67 -0
- package/dist/types/composables/useScrollSpy.d.ts +59 -0
- package/dist/types/granular-provider/shared.d.ts +4 -0
- package/dist/types/i18n/messages/en.d.ts +64 -0
- package/dist/types/i18n/messages/es.d.ts +64 -0
- package/dist/types/i18n/messages/ru.d.ts +64 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/web-types.json +809 -284
- package/package.json +25 -1
- package/dist/chunks/config-DWL81-aM.js.map +0 -1
- package/dist/chunks/en-B9E3jO6m.js.map +0 -1
- package/dist/chunks/es-QpzdIEut.js.map +0 -1
- package/dist/chunks/generated-B95IBXs6.js.map +0 -1
- package/dist/chunks/granular-provider-BG_2XqkS.js.map +0 -1
- package/dist/chunks/ru-D_1Ya489.js.map +0 -1
- package/dist/chunks/useDragSort-0l47h4yb.js +0 -265
- package/dist/chunks/useDragSort-0l47h4yb.js.map +0 -1
- package/dist/components/GrTabs/chunks/GrTabs-BArefUFv.js.map +0 -1
- package/dist/components/GrImageViewer/chunks/{GrImageViewer-Dd2gJ-j_.js → GrImageViewer-DK-iVHHC.js} +3 -3
- package/dist/components/GrInputTag/chunks/{GrInputTag-BKzm1PKw.js → GrInputTag-Ci8AO1ze.js} +1 -1
- package/dist/components/GrNumberInput/chunks/{GrNumberInput-Bm-V59TG.js → GrNumberInput-CL5QhOmx.js} +1 -1
- package/dist/components/GrSlider/chunks/{GrSlider-Db-jACXz.js → GrSlider-CO8N9SF6.js} +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useScrollSpy-CIJpw0RI.js","names":[],"sources":["../../src/composables/internal/scrollSpyGeometry.ts","../../src/composables/useScrollSpy.ts"],"sourcesContent":["/**\n * Арифметика положения чтения: чистые функции без DOM и без реактивности.\n *\n * Вынесена отдельно по той же причине, что и геометрия перестановки в\n * `dragSortGeometry.ts`: ошибка здесь проявляется не исключением, а\n * «подсветился не тот раздел», и поймать это можно только тестом на числах. В\n * jsdom проверить нечем вовсе — там нет ни прокрутки, ни раскладки.\n *\n * Композабл `useScrollSpy` сводит `DOMRect` к числам и зовёт эти функции; что\n * значит активный раздел для разметки, решает уже компонент.\n */\n\n/** Верх раздела в координатах вьюпорта. */\nexport interface ScrollSpyRect {\n id: string\n top: number\n}\n\n/**\n * Допуск линии активации.\n *\n * `GrAffix` от эпсилона отказался осознанно — там рядом стоит CSS, у которого\n * его нет, и расхождение с ним было бы дороже дребезга. Здесь параллельного\n * источника истины нет: единственный потребитель числа — этот модуль. А повод\n * конкретный: прокрутка ставится так, чтобы верх раздела совпал с линией, но\n * браузеры округляют смещение, и приземление на `line + 0.5` дало бы «кликнул\n * третий раздел — подсветился второй».\n */\nconst SNAP_EPSILON = 2\n\n/**\n * Какой раздел читают сейчас.\n *\n * Правило — «последний, чей верх выше линии», а не «самый видимый». Максимум\n * доли пересечения ошибается двумя способами: длинный раздел выигрывает у\n * короткого независимо от того, что читают, и подсветка отскакивает назад,\n * когда следующий раздел только показался снизу. Здесь ответ — заголовок,\n * ближайший сверху к линии чтения.\n *\n * Порядок массива на ответ не влияет: выбор идёт по геометрии. Это снимает\n * класс дефектов, где объявленный список разошёлся с порядком разделов на\n * странице — подсветку в таком случае невозможно отладить.\n *\n * `atEnd` побеждает правило линии, и без него последний раздел недостижим:\n * раздел короче остатка скроллпорта никогда не доводит свой верх до линии,\n * потому что прокрутка кончается раньше.\n */\nexport function activeSectionId(\n rects: readonly ScrollSpyRect[],\n line: number,\n atEnd: boolean,\n): string | null {\n // Пустой вход — это сразу три случая: пустой список, ни одного элемента в\n // DOM, наблюдение выключено. Все три означают «сказать нечего».\n if (rects.length === 0)\n return null\n\n let best: ScrollSpyRect | null = null\n\n for (const rect of rects) {\n if (!atEnd && rect.top - line > SNAP_EPSILON)\n continue\n\n if (best === null || rect.top > best.top)\n best = rect\n }\n\n return best?.id ?? null\n}\n\n/**\n * `rootMargin` наблюдателя: верх корня поджимается на отступ, и тогда его край\n * совпадает с линией активации.\n *\n * Единицы только `px`: `rootMargin` произвольных длин не принимает, поэтому\n * отступ приходит сюда уже замеренным.\n */\nexport function scrollSpyRootMargin(offsetPx: number): string {\n const px = Number.isFinite(offsetPx) ? Math.max(0, Math.round(offsetPx)) : 0\n const negative = -px\n // Число выносится в переменную, чтобы литерал остался одним токеном: гейт\n // safelist разбирает шаблонные строки по пробелам, а обломок вида `-${px}px`\n // принимает за утилиту UnoCSS — которой он, по совпадению, и оказывается.\n const inset = `${negative}px`\n const zero = '0px'\n\n return [inset, zero, zero, zero].join(' ')\n}\n\n/**\n * Куда прокрутить скроллпорт, чтобы верх раздела встал на линию активации.\n *\n * Своя арифметика вместо `scrollIntoView` — по трём независимым причинам:\n * тот двигает всех предков (у карусели это стоило прыжка страницы на каждом\n * шаге автопрокрутки), не умеет наш отступ, и в jsdom отсутствует вовсе, то\n * есть клик стал бы непроверяемым.\n */\nexport function scrollSpyTargetTop(\n scrollTop: number,\n sectionTop: number,\n portTop: number,\n offset: number,\n maxScrollTop: number,\n): number {\n const upper = Math.max(0, Number.isFinite(maxScrollTop) ? maxScrollTop : 0)\n const target = scrollTop + (sectionTop - portTop) - Math.max(0, offset)\n\n return Math.min(upper, Math.max(0, target))\n}\n\n/**\n * Прокручивается ли предок.\n *\n * Читается вычисленный `overflow-y`, а не объявленный: при\n * `overflow-x: hidden; overflow-y: visible` спецификация превращает вычисленный\n * `overflow-y` в `auto`, то есть предок действительно становится скроллпортом.\n */\nexport function isScrollableOverflow(overflowY: string): boolean {\n return overflowY === 'auto' || overflowY === 'scroll' || overflowY === 'overlay'\n}\n\n/**\n * Доехали ли до дна.\n *\n * `maxScrollTop > 1` — обязательное условие, а не перестраховка: содержимое\n * короче скроллпорта даёт «дно» на нулевой прокрутке, и правило `atEnd`\n * подсветило бы последний раздел на странице, где позиции чтения нет вовсе.\n */\nexport function isScrolledToEnd(scrollTop: number, maxScrollTop: number): boolean {\n return maxScrollTop > 1 && scrollTop >= maxScrollTop - SNAP_EPSILON\n}\n","import type { Ref } from 'vue'\nimport { onMounted, onScopeDispose, readonly, ref, watch } from 'vue'\n\nimport {\n activeSectionId,\n isScrollableOverflow,\n isScrolledToEnd,\n scrollSpyRootMargin,\n scrollSpyTargetTop,\n} from './internal/scrollSpyGeometry'\n\n/**\n * Отслеживание того, до какого раздела дочитали.\n *\n * Композабл владеет всем, что следует из **положения прокрутки**: линией\n * активации, выбором раздела, прокруткой к нему и закрепом на время этой\n * прокрутки. Всё, что следует из того, что пункт оглавления является\n * **ссылкой** — разметка, `aria-current`, модификаторы клика, — остаётся у\n * компонента `GrScrollSpy`.\n *\n * ```ts\n * const spy = useScrollSpy({\n * sections: () => ids.value,\n * offset: () => headerHeight.value,\n * onChange: id => emit('activeChange', id),\n * })\n * ```\n */\n\nexport interface UseScrollSpyOptions {\n /**\n * Идентификаторы разделов. Порядок задаёт порядок пунктов у потребителя, но\n * на ответ не влияет: активный выбирается по геометрии.\n */\n sections: () => readonly string[]\n /**\n * Элемент раздела. По умолчанию — `document.getElementById`.\n *\n * Дефолт не догадка: идентификаторы разделов и есть цели ссылок `#id`, то\n * есть DOM-id по определению жанра.\n */\n elementFor?: (id: string) => HTMLElement | null | undefined\n /** Скроллпорт. Не задан — ищется от первого раздела, не найден — документ. */\n scroller?: () => HTMLElement | null | undefined\n /**\n * Отступ линии активации от верха скроллпорта, **в пикселях**.\n *\n * Именно число, а не CSS-длина: резолв `4rem` или `var(...)` требует\n * `getComputedStyle`, то есть DOM, — а композабл обязан оставаться\n * проверяемым без него. Длину резолвит потребитель.\n */\n offset?: () => number\n /** Наблюдение выключено: `active` сбрасывается в `null`. */\n disabled?: () => boolean\n /** Активный сменился. Зовётся на смене, а не на каждой записи наблюдателя. */\n onChange?: (id: string | null, previous: string | null) => void\n}\n\nexport interface UseScrollSpyReturn {\n /** Активный раздел. `null` — линия ещё не дошла до первого. */\n active: Readonly<Ref<string | null>>\n /** Прокрутить к разделу и закрепить его активным до следующей прокрутки. */\n scrollTo: (id: string, options?: { behavior?: ScrollBehavior }) => void\n /** Перечитать отступ, скроллпорт и геометрию. */\n refresh: () => void\n /** Закрепить активным вручную: страницу прокручивает не оглавление. */\n pin: (id: string | null) => void\n}\n\n/**\n * Порог простоя прокрутки, после которого она считается осевшей.\n *\n * Не догадка о длительности анимации — длину плавной прокрутки браузер\n * выбирает сам от расстояния, и любой фиксированный таймаут либо короток, либо\n * длинен. Это стандартный полифилл `scrollend`: порог обязан превышать\n * межкадровый интервал потока событий с запасом на джанк и оставаться короче\n * осмысленной паузы пользователя.\n */\nconst SETTLE_IDLE_MS = 120\n\n/** Насколько близко к цели считается «доехали». */\nconst SETTLE_EPSILON = 2\n\nexport function useScrollSpy(options: UseScrollSpyOptions): UseScrollSpyReturn {\n const active = ref<string | null>(null)\n\n let observer: IntersectionObserver | null = null\n /** Идёт ли наблюдение: без него выводить активный раздел не из чего. */\n let observing = false\n /** Элемент, чей `scrollTop` двигаем. Для окна — `document.scrollingElement`. */\n let portEl: HTMLElement | null = null\n /** Корень наблюдателя: `null` для вьюпорта. */\n let portRoot: Element | null = null\n /** Куда вешать `scroll`: на элемент или на документ. */\n let portEvents: EventTarget | null = null\n let isDocumentPort = true\n\n let appliedOffsetPx = 0\n let lastAtEnd = false\n\n let pinnedId: string | null = null\n let pendingTop: number | null = null\n let settled = true\n let idleTimer: ReturnType<typeof setTimeout> | null = null\n\n const warnedMissing = new Set<string>()\n\n function element(id: string): HTMLElement | null {\n const custom = options.elementFor?.(id)\n if (custom !== undefined)\n return custom ?? null\n\n return typeof document === 'undefined' ? null : document.getElementById(id)\n }\n\n function portTop(): number {\n if (!portEl)\n return 0\n\n return isDocumentPort ? 0 : portEl.getBoundingClientRect().top\n }\n\n function portMaxScroll(): number {\n return portEl ? portEl.scrollHeight - portEl.clientHeight : 0\n }\n\n function measureOffset(): number {\n const value = options.offset?.() ?? 0\n\n return Number.isFinite(value) ? Math.max(0, value) : 0\n }\n\n /**\n * Скроллпорт ищется от **первого раздела**, а не от места вызова: оглавление\n * обычно стоит рядом с содержимым, отдельной колонкой, и обход предков от\n * него привёл бы не туда. Скроллпорт определяют разделы, за ними и следим.\n *\n * Документ представляем через `document.scrollingElement`, а не через\n * `window`: так остаётся один кодовый путь для окна и для коробки, и клик\n * становится проверяемым — `window.scrollTo` в jsdom заглушка, печатающая\n * «Not implemented», а `Element.scrollTop` там обычное хранимое свойство.\n */\n function resolvePort(): void {\n const explicit = options.scroller?.()\n\n if (explicit) {\n portEl = explicit\n portRoot = explicit\n portEvents = explicit\n isDocumentPort = false\n\n return\n }\n\n if (typeof document !== 'undefined' && typeof getComputedStyle === 'function') {\n const ids = options.sections()\n const first = ids.length > 0 ? element(ids[0]) : null\n let current = first?.parentElement ?? null\n\n // До `<body>`, не включая его: `useScrollLock` вешает на него\n // `overflow: hidden` на время открытой модалки.\n while (current && current !== document.body) {\n if (isScrollableOverflow(getComputedStyle(current).overflowY)) {\n portEl = current\n portRoot = current\n portEvents = current\n isDocumentPort = false\n\n return\n }\n\n current = current.parentElement\n }\n }\n\n // `scrollingElement` предпочтителен, но существует не везде — в jsdom его нет\n // вовсе, и без фолбэка прокрутка молча не выполнялась бы, а тест этого не\n // увидел бы: ранний выход исключения не бросает.\n portEl = (typeof document === 'undefined'\n ? null\n : document.scrollingElement ?? document.documentElement) as HTMLElement | null\n // `null`, а не `documentElement`: по спецификации это разные корни, и Safari\n // исторически расходился на втором.\n portRoot = null\n portEvents = typeof document === 'undefined' ? null : document\n isDocumentPort = true\n }\n\n function setActive(next: string | null): void {\n if (next === active.value)\n return\n\n const previous = active.value\n active.value = next\n options.onChange?.(next, previous)\n }\n\n function warnMissing(id: string): void {\n if (!__GR_DEV__ || warnedMissing.has(id))\n return\n\n warnedMissing.add(id)\n console.warn(\n `[granularity] useScrollSpy: раздела #${id} нет в документе — пункт оглавления `\n + 'останется неактивным, а клик по нему ничего не сделает. Обычно это опечатка в '\n + 'идентификаторе; если раздел дорисовывается позже, предупреждение можно не читать.',\n )\n }\n\n function recompute(): void {\n if (pinnedId !== null)\n return\n\n // Не наблюдаем — значит не знаем. Один замер на монтировании был бы верен\n // ровно до первой прокрутки, а дальше подсветка врала бы молча.\n if (!observing) {\n setActive(null)\n\n return\n }\n\n const rects: { id: string, top: number }[] = []\n\n for (const id of options.sections()) {\n const el = element(id)\n\n if (!el) {\n warnMissing(id)\n continue\n }\n\n rects.push({ id, top: el.getBoundingClientRect().top })\n }\n\n setActive(activeSectionId(rects, portTop() + appliedOffsetPx, lastAtEnd))\n }\n\n function clearIdleTimer(): void {\n if (idleTimer !== null) {\n clearTimeout(idleTimer)\n idleTimer = null\n }\n }\n\n function restartIdleTimer(): void {\n clearIdleTimer()\n idleTimer = setTimeout(() => {\n idleTimer = null\n settled = true\n }, SETTLE_IDLE_MS)\n }\n\n function unpin(): void {\n if (pinnedId === null)\n return\n\n pinnedId = null\n pendingTop = null\n settled = true\n clearIdleTimer()\n recompute()\n }\n\n function onScroll(): void {\n const top = portEl?.scrollTop ?? 0\n const atEnd = isScrolledToEnd(top, portMaxScroll())\n\n if (atEnd !== lastAtEnd) {\n lastAtEnd = atEnd\n recompute()\n }\n\n if (pinnedId === null)\n return\n\n // Отпускаем на первом событии прокрутки **после** того, как она осела. Это\n // и есть ответ на перетаскивание полосы прокрутки: ни `wheel`, ни\n // `touchstart` там не приходят, а `scroll` есть всегда.\n if (settled) {\n unpin()\n\n return\n }\n\n if (pendingTop !== null && Math.abs(top - pendingTop) <= SETTLE_EPSILON) {\n settled = true\n\n return\n }\n\n if (!hasScrollEnd())\n restartIdleTimer()\n }\n\n function onScrollEnd(): void {\n settled = true\n }\n\n /**\n * Пользователь перехватил прокрутку. Закреп снимается немедленно: браузер\n * плавную прокрутку в этот момент отменяет, и цели мы не достигнем никогда.\n */\n function onUserScrollIntent(): void {\n unpin()\n }\n\n function hasScrollEnd(): boolean {\n return typeof window !== 'undefined' && 'onscrollend' in window\n }\n\n function prefersReducedMotion(): boolean {\n return typeof window !== 'undefined'\n && window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches === true\n }\n\n function detach(): void {\n observer?.disconnect()\n observer = null\n observing = false\n clearIdleTimer()\n\n if (portEvents) {\n portEvents.removeEventListener('scroll', onScroll)\n portEvents.removeEventListener('scrollend', onScrollEnd)\n portEvents.removeEventListener('wheel', onUserScrollIntent)\n portEvents.removeEventListener('touchstart', onUserScrollIntent)\n portEvents.removeEventListener('keydown', onUserScrollIntent)\n }\n }\n\n function attach(): void {\n detach()\n\n if (options.disabled?.() === true) {\n pinnedId = null\n pendingTop = null\n setActive(null)\n\n return\n }\n\n resolvePort()\n appliedOffsetPx = measureOffset()\n lastAtEnd = isScrolledToEnd(portEl?.scrollTop ?? 0, portMaxScroll())\n\n if (portEvents) {\n portEvents.addEventListener('scroll', onScroll, { passive: true })\n portEvents.addEventListener('scrollend', onScrollEnd)\n portEvents.addEventListener('wheel', onUserScrollIntent, { passive: true })\n portEvents.addEventListener('touchstart', onUserScrollIntent, { passive: true })\n portEvents.addEventListener('keydown', onUserScrollIntent)\n }\n\n // Наблюдателя может не быть вовсе (сервер, jsdom, старый движок). Тогда\n // подсветки нет, и это правильная деградация: подсветить первый раздел\n // значило бы поставить `aria-current` на пункт, который активным не\n // является, — то есть солгать диктору. Прокрутка по клику при этом работает.\n if (typeof IntersectionObserver === 'undefined')\n return\n\n observing = true\n observer = new IntersectionObserver(handle, {\n root: portRoot,\n rootMargin: scrollSpyRootMargin(appliedOffsetPx),\n // Второй порог удвоил бы вызовы, не создав ни одной новой развилки:\n // ответ считается по прямоугольникам, а не по доле пересечения.\n threshold: 0,\n })\n\n for (const id of options.sections()) {\n const el = element(id)\n\n if (el)\n observer.observe(el)\n else\n warnMissing(id)\n }\n }\n\n function handle(): void {\n recompute()\n\n // Самоизлечение: dev-сервер UnoCSS отдаёт правила по требованию, и в момент\n // монтирования CSS-длина отступа могла ещё не разрешиться — тогда линия\n // собрана из нуля. Пересобираем, как только замер разошёлся с применённым.\n if (measureOffset() !== appliedOffsetPx)\n attach()\n }\n\n function scrollTo(id: string, scrollOptions?: { behavior?: ScrollBehavior }): void {\n const el = element(id)\n\n if (!el || !portEl) {\n warnMissing(id)\n\n return\n }\n\n appliedOffsetPx = measureOffset()\n\n const top = scrollSpyTargetTop(\n portEl.scrollTop,\n el.getBoundingClientRect().top,\n portTop(),\n appliedOffsetPx,\n portMaxScroll(),\n )\n\n // Подсветка переезжает сразу, до всякой прокрутки: иначе она добежала бы по\n // промежуточным пунктам, пока плавная прокрутка едет к цели.\n pin(id)\n pendingTop = top\n settled = false\n\n const behavior = scrollOptions?.behavior ?? (prefersReducedMotion() ? 'auto' : 'smooth')\n\n // Не `portEl.scrollTo?.(…) ?? (portEl.scrollTop = top)`: метод возвращает\n // `undefined`, и правая часть выполнилась бы всегда.\n if (typeof portEl.scrollTo === 'function')\n portEl.scrollTo({ top, behavior })\n else\n portEl.scrollTop = top\n\n if (!hasScrollEnd())\n restartIdleTimer()\n }\n\n function pin(id: string | null): void {\n if (id === null) {\n unpin()\n\n return\n }\n\n pinnedId = id\n setActive(id)\n }\n\n function refresh(): void {\n attach()\n recompute()\n }\n\n onMounted(refresh)\n onScopeDispose(detach)\n\n // `flush: 'post'` обязателен: до обновления DOM новых разделов ещё нет, и\n // наблюдатель повесился бы на пустоту.\n watch(\n () => [options.sections(), options.disabled?.(), options.scroller?.()] as const,\n () => {\n if (pinnedId !== null && !options.sections().includes(pinnedId))\n unpin()\n\n refresh()\n },\n { flush: 'post' },\n )\n\n return { active: readonly(active), scrollTo, refresh, pin }\n}\n"],"mappings":";;;;;;;;;;;;AA4BA,IAAM,eAAe;;;;;;;;;;;;;;;;;;AAmBrB,SAAgB,gBACd,OACA,MACA,OACe;CAGf,IAAI,MAAM,WAAW,GACnB,OAAO;CAET,IAAI,OAA6B;CAEjC,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,CAAC,SAAS,KAAK,MAAM,OAAO,cAC9B;EAEF,IAAI,SAAS,QAAQ,KAAK,MAAM,KAAK,KACnC,OAAO;CACX;CAEA,OAAO,MAAM,MAAM;AACrB;;;;;;;;AASA,SAAgB,oBAAoB,UAA0B;CAM5D,MAAM,QAAQ,GAAG,EALN,OAAO,SAAS,QAAQ,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,QAAQ,CAAC,IAAI,GAKjD;CAC1B,MAAM,OAAO;CAEb,OAAO;EAAC;EAAO;EAAM;EAAM;CAAI,CAAC,CAAC,KAAK,GAAG;AAC3C;;;;;;;;;AAUA,SAAgB,mBACd,WACA,YACA,SACA,QACA,cACQ;CACR,MAAM,QAAQ,KAAK,IAAI,GAAG,OAAO,SAAS,YAAY,IAAI,eAAe,CAAC;CAC1E,MAAM,SAAS,aAAa,aAAa,WAAW,KAAK,IAAI,GAAG,MAAM;CAEtE,OAAO,KAAK,IAAI,OAAO,KAAK,IAAI,GAAG,MAAM,CAAC;AAC5C;;;;;;;;AASA,SAAgB,qBAAqB,WAA4B;CAC/D,OAAO,cAAc,UAAU,cAAc,YAAY,cAAc;AACzE;;;;;;;;AASA,SAAgB,gBAAgB,WAAmB,cAA+B;CAChF,OAAO,eAAe,KAAK,aAAa,eAAe;AACzD;;;;;;;;;;;;ACpDA,IAAM,iBAAiB;;AAGvB,IAAM,iBAAiB;AAEvB,SAAgB,aAAa,SAAkD;CAC7E,MAAM,SAAS,IAAmB,IAAI;CAEtC,IAAI,WAAwC;;CAE5C,IAAI,YAAY;;CAEhB,IAAI,SAA6B;;CAEjC,IAAI,WAA2B;;CAE/B,IAAI,aAAiC;CACrC,IAAI,iBAAiB;CAErB,IAAI,kBAAkB;CACtB,IAAI,YAAY;CAEhB,IAAI,WAA0B;CAC9B,IAAI,aAA4B;CAChC,IAAI,UAAU;CACd,IAAI,YAAkD;CAEtD,MAAM,gCAAgB,IAAI,IAAY;CAEtC,SAAS,QAAQ,IAAgC;EAC/C,MAAM,SAAS,QAAQ,aAAa,EAAE;EACtC,IAAI,WAAW,KAAA,GACb,OAAO,UAAU;EAEnB,OAAO,OAAO,aAAa,cAAc,OAAO,SAAS,eAAe,EAAE;CAC5E;CAEA,SAAS,UAAkB;EACzB,IAAI,CAAC,QACH,OAAO;EAET,OAAO,iBAAiB,IAAI,OAAO,sBAAsB,CAAC,CAAC;CAC7D;CAEA,SAAS,gBAAwB;EAC/B,OAAO,SAAS,OAAO,eAAe,OAAO,eAAe;CAC9D;CAEA,SAAS,gBAAwB;EAC/B,MAAM,QAAQ,QAAQ,SAAS,KAAK;EAEpC,OAAO,OAAO,SAAS,KAAK,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI;CACvD;;;;;;;;;;;CAYA,SAAS,cAAoB;EAC3B,MAAM,WAAW,QAAQ,WAAW;EAEpC,IAAI,UAAU;GACZ,SAAS;GACT,WAAW;GACX,aAAa;GACb,iBAAiB;GAEjB;EACF;EAEA,IAAI,OAAO,aAAa,eAAe,OAAO,qBAAqB,YAAY;GAC7E,MAAM,MAAM,QAAQ,SAAS;GAE7B,IAAI,WADU,IAAI,SAAS,IAAI,QAAQ,IAAI,EAAE,IAAI,KAAA,EAC5B,iBAAiB;GAItC,OAAO,WAAW,YAAY,SAAS,MAAM;IAC3C,IAAI,qBAAqB,iBAAiB,OAAO,CAAC,CAAC,SAAS,GAAG;KAC7D,SAAS;KACT,WAAW;KACX,aAAa;KACb,iBAAiB;KAEjB;IACF;IAEA,UAAU,QAAQ;GACpB;EACF;EAKA,SAAU,OAAO,aAAa,cAC1B,OACA,SAAS,oBAAoB,SAAS;EAG1C,WAAW;EACX,aAAa,OAAO,aAAa,cAAc,OAAO;EACtD,iBAAiB;CACnB;CAEA,SAAS,UAAU,MAA2B;EAC5C,IAAI,SAAS,OAAO,OAClB;EAEF,MAAM,WAAW,OAAO;EACxB,OAAO,QAAQ;EACf,QAAQ,WAAW,MAAM,QAAQ;CACnC;CAEA,SAAS,YAAY,IAAkB;EACrC,IAAI,EAAA,QAAA,IAAA,aAAA,iBAAe,cAAc,IAAI,EAAE,GACrC;EAEF,cAAc,IAAI,EAAE;EACpB,QAAQ,KACN,wCAAwC,GAAG,oMAG7C;CACF;CAEA,SAAS,YAAkB;EACzB,IAAI,aAAa,MACf;EAIF,IAAI,CAAC,WAAW;GACd,UAAU,IAAI;GAEd;EACF;EAEA,MAAM,QAAuC,CAAC;EAE9C,KAAK,MAAM,MAAM,QAAQ,SAAS,GAAG;GACnC,MAAM,KAAK,QAAQ,EAAE;GAErB,IAAI,CAAC,IAAI;IACP,YAAY,EAAE;IACd;GACF;GAEA,MAAM,KAAK;IAAE;IAAI,KAAK,GAAG,sBAAsB,CAAC,CAAC;GAAI,CAAC;EACxD;EAEA,UAAU,gBAAgB,OAAO,QAAQ,IAAI,iBAAiB,SAAS,CAAC;CAC1E;CAEA,SAAS,iBAAuB;EAC9B,IAAI,cAAc,MAAM;GACtB,aAAa,SAAS;GACtB,YAAY;EACd;CACF;CAEA,SAAS,mBAAyB;EAChC,eAAe;EACf,YAAY,iBAAiB;GAC3B,YAAY;GACZ,UAAU;EACZ,GAAG,cAAc;CACnB;CAEA,SAAS,QAAc;EACrB,IAAI,aAAa,MACf;EAEF,WAAW;EACX,aAAa;EACb,UAAU;EACV,eAAe;EACf,UAAU;CACZ;CAEA,SAAS,WAAiB;EACxB,MAAM,MAAM,QAAQ,aAAa;EACjC,MAAM,QAAQ,gBAAgB,KAAK,cAAc,CAAC;EAElD,IAAI,UAAU,WAAW;GACvB,YAAY;GACZ,UAAU;EACZ;EAEA,IAAI,aAAa,MACf;EAKF,IAAI,SAAS;GACX,MAAM;GAEN;EACF;EAEA,IAAI,eAAe,QAAQ,KAAK,IAAI,MAAM,UAAU,KAAK,gBAAgB;GACvE,UAAU;GAEV;EACF;EAEA,IAAI,CAAC,aAAa,GAChB,iBAAiB;CACrB;CAEA,SAAS,cAAoB;EAC3B,UAAU;CACZ;;;;;CAMA,SAAS,qBAA2B;EAClC,MAAM;CACR;CAEA,SAAS,eAAwB;EAC/B,OAAO,OAAO,WAAW,eAAe,iBAAiB;CAC3D;CAEA,SAAS,uBAAgC;EACvC,OAAO,OAAO,WAAW,eACpB,OAAO,aAAa,kCAAkC,CAAC,EAAE,YAAY;CAC5E;CAEA,SAAS,SAAe;EACtB,UAAU,WAAW;EACrB,WAAW;EACX,YAAY;EACZ,eAAe;EAEf,IAAI,YAAY;GACd,WAAW,oBAAoB,UAAU,QAAQ;GACjD,WAAW,oBAAoB,aAAa,WAAW;GACvD,WAAW,oBAAoB,SAAS,kBAAkB;GAC1D,WAAW,oBAAoB,cAAc,kBAAkB;GAC/D,WAAW,oBAAoB,WAAW,kBAAkB;EAC9D;CACF;CAEA,SAAS,SAAe;EACtB,OAAO;EAEP,IAAI,QAAQ,WAAW,MAAM,MAAM;GACjC,WAAW;GACX,aAAa;GACb,UAAU,IAAI;GAEd;EACF;EAEA,YAAY;EACZ,kBAAkB,cAAc;EAChC,YAAY,gBAAgB,QAAQ,aAAa,GAAG,cAAc,CAAC;EAEnE,IAAI,YAAY;GACd,WAAW,iBAAiB,UAAU,UAAU,EAAE,SAAS,KAAK,CAAC;GACjE,WAAW,iBAAiB,aAAa,WAAW;GACpD,WAAW,iBAAiB,SAAS,oBAAoB,EAAE,SAAS,KAAK,CAAC;GAC1E,WAAW,iBAAiB,cAAc,oBAAoB,EAAE,SAAS,KAAK,CAAC;GAC/E,WAAW,iBAAiB,WAAW,kBAAkB;EAC3D;EAMA,IAAI,OAAO,yBAAyB,aAClC;EAEF,YAAY;EACZ,WAAW,IAAI,qBAAqB,QAAQ;GAC1C,MAAM;GACN,YAAY,oBAAoB,eAAe;GAG/C,WAAW;EACb,CAAC;EAED,KAAK,MAAM,MAAM,QAAQ,SAAS,GAAG;GACnC,MAAM,KAAK,QAAQ,EAAE;GAErB,IAAI,IACF,SAAS,QAAQ,EAAE;QAEnB,YAAY,EAAE;EAClB;CACF;CAEA,SAAS,SAAe;EACtB,UAAU;EAKV,IAAI,cAAc,MAAM,iBACtB,OAAO;CACX;CAEA,SAAS,SAAS,IAAY,eAAqD;EACjF,MAAM,KAAK,QAAQ,EAAE;EAErB,IAAI,CAAC,MAAM,CAAC,QAAQ;GAClB,YAAY,EAAE;GAEd;EACF;EAEA,kBAAkB,cAAc;EAEhC,MAAM,MAAM,mBACV,OAAO,WACP,GAAG,sBAAsB,CAAC,CAAC,KAC3B,QAAQ,GACR,iBACA,cAAc,CAChB;EAIA,IAAI,EAAE;EACN,aAAa;EACb,UAAU;EAEV,MAAM,WAAW,eAAe,aAAa,qBAAqB,IAAI,SAAS;EAI/E,IAAI,OAAO,OAAO,aAAa,YAC7B,OAAO,SAAS;GAAE;GAAK;EAAS,CAAC;OAEjC,OAAO,YAAY;EAErB,IAAI,CAAC,aAAa,GAChB,iBAAiB;CACrB;CAEA,SAAS,IAAI,IAAyB;EACpC,IAAI,OAAO,MAAM;GACf,MAAM;GAEN;EACF;EAEA,WAAW;EACX,UAAU,EAAE;CACd;CAEA,SAAS,UAAgB;EACvB,OAAO;EACP,UAAU;CACZ;CAEA,UAAU,OAAO;CACjB,eAAe,MAAM;CAIrB,YACQ;EAAC,QAAQ,SAAS;EAAG,QAAQ,WAAW;EAAG,QAAQ,WAAW;CAAC,SAC/D;EACJ,IAAI,aAAa,QAAQ,CAAC,QAAQ,SAAS,CAAC,CAAC,SAAS,QAAQ,GAC5D,MAAM;EAER,QAAQ;CACV,GACA,EAAE,OAAO,OAAO,CAClB;CAEA,OAAO;EAAE,QAAQ,SAAS,MAAM;EAAG;EAAU;EAAS;CAAI;AAC5D"}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { i as grAffixRootClass, r as affixSentinelClass } from "../../../chunks/config-Dg0bSoGw.js";
|
|
2
|
+
import { Fragment, computed, createCommentVNode, createElementBlock, createElementVNode, defineComponent, mergeProps, normalizeClass, onBeforeUnmount, onMounted, openBlock, ref, renderSlot, unref, watch } from "vue";
|
|
3
|
+
//#region src/components/GrAffix/affixState.ts
|
|
4
|
+
/** Предок делает из себя скроллпорт и прокручивается: `sticky` внутри работает. */
|
|
5
|
+
var SCROLLABLE = /* @__PURE__ */ new Set([
|
|
6
|
+
"auto",
|
|
7
|
+
"scroll",
|
|
8
|
+
"overlay"
|
|
9
|
+
]);
|
|
10
|
+
/** Предок делает из себя скроллпорт, но прокручивать его нечем: `sticky` внутри мёртв. */
|
|
11
|
+
var CLIPPING = /* @__PURE__ */ new Set(["hidden", "clip"]);
|
|
12
|
+
/**
|
|
13
|
+
* Значение пропа `offset` в CSS-длину для инлайновой переменной.
|
|
14
|
+
*
|
|
15
|
+
* Строка уходит как есть и намеренно не валидируется: `4rem`,
|
|
16
|
+
* `var(--gr-navbar-height)`, `calc(var(--gr-navbar-height) + 8px)` обязаны работать — иначе проп
|
|
17
|
+
* проигрывает голому `style`. Отрицательное число зажимается в ноль: отрицательный
|
|
18
|
+
* порог у `sticky` прячет панель за край, и это всегда ошибка, а не приём.
|
|
19
|
+
*
|
|
20
|
+
* `undefined` означает «не писать переменную вовсе» — тогда класс падает на
|
|
21
|
+
* `var(--gr-affix-offset, 0px)`, то есть на каскад.
|
|
22
|
+
*/
|
|
23
|
+
function affixOffsetLength(value) {
|
|
24
|
+
if (value == null) return void 0;
|
|
25
|
+
if (typeof value === "number") {
|
|
26
|
+
if (!Number.isFinite(value)) return void 0;
|
|
27
|
+
return `${Math.max(0, value)}px`;
|
|
28
|
+
}
|
|
29
|
+
const trimmed = value.trim();
|
|
30
|
+
return trimmed === "" ? void 0 : trimmed;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* `rootMargin` наблюдателя: поджимает ровно тот край, к которому прилипаем, ровно
|
|
34
|
+
* на отступ. Тогда `rootBounds` записи — это и есть линия прилипания, и состояние
|
|
35
|
+
* читается сравнением двух чисел.
|
|
36
|
+
*
|
|
37
|
+
* Единицы только `px`: `rootMargin` процентов не по осям и произвольных длин не
|
|
38
|
+
* принимает, поэтому отступ приходит сюда уже замеренным.
|
|
39
|
+
*/
|
|
40
|
+
function affixRootMargin(placement, offsetPx) {
|
|
41
|
+
const inset = `${-(Number.isFinite(offsetPx) ? Math.max(0, Math.round(offsetPx)) : 0)}px`;
|
|
42
|
+
const zero = "0px";
|
|
43
|
+
return placement === "top" ? [
|
|
44
|
+
inset,
|
|
45
|
+
zero,
|
|
46
|
+
zero,
|
|
47
|
+
zero
|
|
48
|
+
].join(" ") : [
|
|
49
|
+
zero,
|
|
50
|
+
zero,
|
|
51
|
+
inset,
|
|
52
|
+
zero
|
|
53
|
+
].join(" ");
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Прилипло ли — по одной записи наблюдателя.
|
|
57
|
+
*
|
|
58
|
+
* Считаем по прямоугольникам, а не по `isIntersecting`: тот означает сразу две
|
|
59
|
+
* разные вещи — «сентинел ещё не доехал до линии» и «уже уехал за неё», — и
|
|
60
|
+
* различает их только геометрия, которую запись и так несёт.
|
|
61
|
+
*
|
|
62
|
+
* `rootBounds` уже поджат `rootMargin`'ом (так определено спецификацией), поэтому
|
|
63
|
+
* его край и есть линия прилипания.
|
|
64
|
+
*/
|
|
65
|
+
function isAffixStuck(entry, placement, previous) {
|
|
66
|
+
const bounds = entry.rootBounds;
|
|
67
|
+
if (!bounds) return previous;
|
|
68
|
+
return placement === "top" ? entry.boundingClientRect.top < bounds.top : entry.boundingClientRect.bottom > bounds.bottom;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Один проход по цепочке предков, два ответа: root наблюдателя и повод для
|
|
72
|
+
* dev-предупреждения.
|
|
73
|
+
*
|
|
74
|
+
* Читается вычисленный `overflow-y`, а не объявленный и не классы, и это не
|
|
75
|
+
* придирка: при `overflow-x: hidden; overflow-y: visible` спецификация превращает
|
|
76
|
+
* вычисленный `overflow-y` в `auto` — предок действительно становится рабочим
|
|
77
|
+
* скроллпортом. Проверка по классу `overflow-x-hidden` дала бы здесь ложную
|
|
78
|
+
* тревогу на совершенно исправной вёрстке.
|
|
79
|
+
*
|
|
80
|
+
* Обход останавливается на первом скроллере: всё выше него к прилипанию отношения
|
|
81
|
+
* не имеет, поэтому и клипер над скроллером виновным не считается. Ровно такая
|
|
82
|
+
* пара есть в витрине — внешняя панель `overflow-hidden`, внутри `overflow-y-auto`.
|
|
83
|
+
*/
|
|
84
|
+
function scanAffixAncestors(ancestors) {
|
|
85
|
+
let clipperLabel = null;
|
|
86
|
+
for (let index = 0; index < ancestors.length; index += 1) {
|
|
87
|
+
const { overflowY, label } = ancestors[index];
|
|
88
|
+
if (SCROLLABLE.has(overflowY)) return {
|
|
89
|
+
scrollerIndex: index,
|
|
90
|
+
clipperLabel
|
|
91
|
+
};
|
|
92
|
+
if (clipperLabel === null && CLIPPING.has(overflowY)) clipperLabel = label;
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
scrollerIndex: -1,
|
|
96
|
+
clipperLabel
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
//#region src/components/GrAffix/GrAffix.vue?vue&type=script&setup=true&lang.ts
|
|
101
|
+
var _hoisted_1 = ["data-placement", "data-stuck"];
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/components/GrAffix/GrAffix.vue
|
|
104
|
+
var GrAffix_default = /* @__PURE__ */ defineComponent({
|
|
105
|
+
inheritAttrs: false,
|
|
106
|
+
__name: "GrAffix",
|
|
107
|
+
props: {
|
|
108
|
+
placement: { default: "top" },
|
|
109
|
+
offset: { default: void 0 },
|
|
110
|
+
disabled: {
|
|
111
|
+
type: Boolean,
|
|
112
|
+
default: false
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
emits: ["stickyChange"],
|
|
116
|
+
setup(__props, { expose: __expose, emit: __emit }) {
|
|
117
|
+
const props = __props;
|
|
118
|
+
const emit = __emit;
|
|
119
|
+
const boxEl = ref(null);
|
|
120
|
+
const sentinelTopEl = ref(null);
|
|
121
|
+
const sentinelBottomEl = ref(null);
|
|
122
|
+
const sentinelEl = computed(() => props.placement === "top" ? sentinelTopEl.value : sentinelBottomEl.value);
|
|
123
|
+
const stuck = ref(false);
|
|
124
|
+
/**
|
|
125
|
+
* Наблюдателя в этой среде нет вовсе (сервер, jsdom, старый движок). Тогда
|
|
126
|
+
* `sticky` продолжает работать — его делает CSS, — но знание о состоянии
|
|
127
|
+
* пропадает, и поверхность включается всегда. Прилипшая прозрачная панель
|
|
128
|
+
* пропускает сквозь себя строки; всегда непрозрачная просто чуть более
|
|
129
|
+
* декоративна, чем нужно. Из двух деградаций выбрана вторая.
|
|
130
|
+
*/
|
|
131
|
+
const observerMissing = ref(false);
|
|
132
|
+
const surfaceOn = computed(() => !props.disabled && (stuck.value || observerMissing.value));
|
|
133
|
+
const rootClass = computed(() => grAffixRootClass(props.placement, !props.disabled, surfaceOn.value));
|
|
134
|
+
const rootStyle = computed(() => {
|
|
135
|
+
const length = affixOffsetLength(props.offset);
|
|
136
|
+
return length === void 0 ? void 0 : { "--gr-affix-offset": length };
|
|
137
|
+
});
|
|
138
|
+
let observer = null;
|
|
139
|
+
let appliedOffsetPx = 0;
|
|
140
|
+
let warned = false;
|
|
141
|
+
/**
|
|
142
|
+
* Отступ берётся из вычисленного стиля, а не из пропа, по трём причинам:
|
|
143
|
+
* браузер уже разрешил `4rem`/`var()`/`calc()` в used value, и свой парсер CSS
|
|
144
|
+
* не нужен; отступ может прийти не из пропа вовсе, а из каскада; у `sticky`
|
|
145
|
+
* `getComputedStyle().top` возвращает заданный порог прилипания, а не смещённую
|
|
146
|
+
* позицию, то есть ровно нужное число.
|
|
147
|
+
*/
|
|
148
|
+
function measureOffsetPx() {
|
|
149
|
+
const box = boxEl.value;
|
|
150
|
+
if (!box || typeof getComputedStyle !== "function") return 0;
|
|
151
|
+
const parsed = Number.parseFloat(getComputedStyle(box)[props.placement]);
|
|
152
|
+
return Number.isFinite(parsed) ? Math.max(0, parsed) : 0;
|
|
153
|
+
}
|
|
154
|
+
function describeElement(el) {
|
|
155
|
+
const tag = el.tagName.toLowerCase();
|
|
156
|
+
const classes = el.getAttribute("class")?.trim().split(/\s+/).slice(0, 2) ?? [];
|
|
157
|
+
return classes.length > 0 ? `<${tag} class="${classes.join(" ")}…">` : `<${tag}>`;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Цепочка предков до `<body>`, не включая его.
|
|
161
|
+
*
|
|
162
|
+
* Верхняя граница выбрана не по вкусу: `useScrollLock` вешает
|
|
163
|
+
* `overflow: hidden` на `body` на всё время открытой модалки, и дойди обход
|
|
164
|
+
* туда — каждый аффикс внутри `GrModal` объявлялся бы сломанным, причём только
|
|
165
|
+
* в этом состоянии, то есть через раз.
|
|
166
|
+
*/
|
|
167
|
+
function collectAncestors(box) {
|
|
168
|
+
const chain = [];
|
|
169
|
+
if (typeof getComputedStyle !== "function") return chain;
|
|
170
|
+
let current = box.parentElement;
|
|
171
|
+
while (current && current !== document.body) {
|
|
172
|
+
chain.push({
|
|
173
|
+
el: current,
|
|
174
|
+
info: {
|
|
175
|
+
overflowY: getComputedStyle(current).overflowY,
|
|
176
|
+
label: describeElement(current)
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
current = current.parentElement;
|
|
180
|
+
}
|
|
181
|
+
return chain;
|
|
182
|
+
}
|
|
183
|
+
function detach() {
|
|
184
|
+
observer?.disconnect();
|
|
185
|
+
observer = null;
|
|
186
|
+
}
|
|
187
|
+
function attach() {
|
|
188
|
+
detach();
|
|
189
|
+
if (props.disabled) {
|
|
190
|
+
stuck.value = false;
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (typeof IntersectionObserver === "undefined") {
|
|
194
|
+
observerMissing.value = true;
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const box = boxEl.value;
|
|
198
|
+
const sentinel = sentinelEl.value;
|
|
199
|
+
if (!box || !sentinel) return;
|
|
200
|
+
const chain = collectAncestors(box);
|
|
201
|
+
const scan = scanAffixAncestors(chain.map((item) => item.info));
|
|
202
|
+
const root = scan.scrollerIndex >= 0 ? chain[scan.scrollerIndex].el : null;
|
|
203
|
+
appliedOffsetPx = measureOffsetPx();
|
|
204
|
+
observer = new IntersectionObserver(handle, {
|
|
205
|
+
root,
|
|
206
|
+
rootMargin: affixRootMargin(props.placement, appliedOffsetPx),
|
|
207
|
+
threshold: 0
|
|
208
|
+
});
|
|
209
|
+
observer.observe(sentinel);
|
|
210
|
+
if (process.env.NODE_ENV !== "production" && !warned && scan.clipperLabel) {
|
|
211
|
+
warned = true;
|
|
212
|
+
console.warn(`[granularity] GrAffix: предок ${scan.clipperLabel} задаёт \`overflow\`, отличный от \`visible\`, и прокручивать его нечем — он становится скроллпортом, внутри которого \`position: sticky\` не двигается. Снимите \`overflow\` с этого предка либо сделайте его настоящим скроллером (\`overflow-y: auto\` плюс ограничение высоты).`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
function handle(entries) {
|
|
216
|
+
const entry = entries[entries.length - 1];
|
|
217
|
+
if (!entry) return;
|
|
218
|
+
const next = isAffixStuck(entry, props.placement, stuck.value);
|
|
219
|
+
if (next !== stuck.value) {
|
|
220
|
+
stuck.value = next;
|
|
221
|
+
emit("stickyChange", next);
|
|
222
|
+
}
|
|
223
|
+
if (measureOffsetPx() !== appliedOffsetPx) attach();
|
|
224
|
+
}
|
|
225
|
+
/** Перечитать отступ и пересобрать наблюдателя: `vh` после ресайза, смена высоты шапки. */
|
|
226
|
+
function remeasure() {
|
|
227
|
+
attach();
|
|
228
|
+
}
|
|
229
|
+
onMounted(attach);
|
|
230
|
+
onBeforeUnmount(detach);
|
|
231
|
+
watch(() => [
|
|
232
|
+
props.placement,
|
|
233
|
+
props.offset,
|
|
234
|
+
props.disabled
|
|
235
|
+
], attach, { flush: "post" });
|
|
236
|
+
__expose({
|
|
237
|
+
stuck,
|
|
238
|
+
remeasure
|
|
239
|
+
});
|
|
240
|
+
return (_ctx, _cache) => {
|
|
241
|
+
return openBlock(), createElementBlock(Fragment, null, [
|
|
242
|
+
!__props.disabled && __props.placement === "top" ? (openBlock(), createElementBlock("div", {
|
|
243
|
+
key: 0,
|
|
244
|
+
ref_key: "sentinelTopEl",
|
|
245
|
+
ref: sentinelTopEl,
|
|
246
|
+
"data-gr-affix-sentinel": "",
|
|
247
|
+
"aria-hidden": "true",
|
|
248
|
+
class: normalizeClass(unref(affixSentinelClass).top)
|
|
249
|
+
}, null, 2)) : createCommentVNode("", true),
|
|
250
|
+
createElementVNode("div", mergeProps({
|
|
251
|
+
ref_key: "boxEl",
|
|
252
|
+
ref: boxEl,
|
|
253
|
+
"data-gr-affix": "",
|
|
254
|
+
"data-placement": __props.placement,
|
|
255
|
+
"data-stuck": stuck.value ? "true" : void 0,
|
|
256
|
+
class: rootClass.value,
|
|
257
|
+
style: rootStyle.value
|
|
258
|
+
}, _ctx.$attrs), [renderSlot(_ctx.$slots, "default", { stuck: stuck.value })], 16, _hoisted_1),
|
|
259
|
+
!__props.disabled && __props.placement === "bottom" ? (openBlock(), createElementBlock("div", {
|
|
260
|
+
key: 1,
|
|
261
|
+
ref_key: "sentinelBottomEl",
|
|
262
|
+
ref: sentinelBottomEl,
|
|
263
|
+
"data-gr-affix-sentinel": "",
|
|
264
|
+
"aria-hidden": "true",
|
|
265
|
+
class: normalizeClass(unref(affixSentinelClass).bottom)
|
|
266
|
+
}, null, 2)) : createCommentVNode("", true)
|
|
267
|
+
], 64);
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
//#endregion
|
|
272
|
+
export { GrAffix_default as t };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GrAffix-CWtC7yi8.js","names":["$attrs"],"sources":["../../../../src/components/GrAffix/affixState.ts","../../../../src/components/GrAffix/GrAffix.vue","../../../../src/components/GrAffix/GrAffix.vue"],"sourcesContent":["/**\n * Арифметика липкой панели. Без Vue и без DOM — проверяется без монтирования.\n *\n * Вынесено сюда всё, что в jsdom не проверяется в принципе: там нет раскладки,\n * `getBoundingClientRect` отдаёт нули, а `getComputedStyle(el).top` при классовой\n * вёрстке всегда `auto`, потому что UnoCSS в тестах не исполняется. Через\n * смонтированный компонент ни одна ветка ниже не достижима — здесь достижимы все.\n */\n\nexport const GR_AFFIX_PLACEMENTS = ['top', 'bottom'] as const\n\nexport type GrAffixPlacement = typeof GR_AFFIX_PLACEMENTS[number]\n\n/** Предок делает из себя скроллпорт и прокручивается: `sticky` внутри работает. */\nconst SCROLLABLE = new Set(['auto', 'scroll', 'overlay'])\n\n/** Предок делает из себя скроллпорт, но прокручивать его нечем: `sticky` внутри мёртв. */\nconst CLIPPING = new Set(['hidden', 'clip'])\n\n/**\n * Значение пропа `offset` в CSS-длину для инлайновой переменной.\n *\n * Строка уходит как есть и намеренно не валидируется: `4rem`,\n * `var(--gr-navbar-height)`, `calc(var(--gr-navbar-height) + 8px)` обязаны работать — иначе проп\n * проигрывает голому `style`. Отрицательное число зажимается в ноль: отрицательный\n * порог у `sticky` прячет панель за край, и это всегда ошибка, а не приём.\n *\n * `undefined` означает «не писать переменную вовсе» — тогда класс падает на\n * `var(--gr-affix-offset, 0px)`, то есть на каскад.\n */\nexport function affixOffsetLength(value: number | string | undefined): string | undefined {\n if (value == null)\n return undefined\n\n if (typeof value === 'number') {\n if (!Number.isFinite(value))\n return undefined\n\n // Число выносится в переменную, чтобы литерал остался одним токеном:\n // safelist-гейт разбирает шаблонные строки по пробелам и обломок вроде\n // `value)}px` принимает за утилиту UnoCSS — которой он, по совпадению, и\n // оказывается.\n const px = Math.max(0, value)\n\n return `${px}px`\n }\n\n const trimmed = value.trim()\n\n return trimmed === '' ? undefined : trimmed\n}\n\n/**\n * `rootMargin` наблюдателя: поджимает ровно тот край, к которому прилипаем, ровно\n * на отступ. Тогда `rootBounds` записи — это и есть линия прилипания, и состояние\n * читается сравнением двух чисел.\n *\n * Единицы только `px`: `rootMargin` процентов не по осям и произвольных длин не\n * принимает, поэтому отступ приходит сюда уже замеренным.\n */\nexport function affixRootMargin(placement: GrAffixPlacement, offsetPx: number): string {\n const px = Number.isFinite(offsetPx) ? Math.max(0, Math.round(offsetPx)) : 0\n const negative = -px\n const inset = `${negative}px`\n const zero = '0px'\n\n return placement === 'top'\n ? [inset, zero, zero, zero].join(' ')\n : [zero, zero, inset, zero].join(' ')\n}\n\n/** Прямоугольники одной записи наблюдателя — ровно то, что из неё читается. */\nexport interface GrAffixEntryGeometry {\n boundingClientRect: { top: number, bottom: number }\n rootBounds: { top: number, bottom: number } | null\n}\n\n/**\n * Прилипло ли — по одной записи наблюдателя.\n *\n * Считаем по прямоугольникам, а не по `isIntersecting`: тот означает сразу две\n * разные вещи — «сентинел ещё не доехал до линии» и «уже уехал за неё», — и\n * различает их только геометрия, которую запись и так несёт.\n *\n * `rootBounds` уже поджат `rootMargin`'ом (так определено спецификацией), поэтому\n * его край и есть линия прилипания.\n */\nexport function isAffixStuck(\n entry: GrAffixEntryGeometry,\n placement: GrAffixPlacement,\n previous: boolean,\n): boolean {\n const bounds = entry.rootBounds\n // Пустой `rootBounds` бывает в кросс-доменном фрейме. Гадать там не на чем, и\n // прошлое состояние честнее выдуманного.\n if (!bounds)\n return previous\n\n return placement === 'top'\n ? entry.boundingClientRect.top < bounds.top\n : entry.boundingClientRect.bottom > bounds.bottom\n}\n\n/** Один предок в том виде, в каком его видит компонент. */\nexport interface GrAffixAncestor {\n /** Что вернул `getComputedStyle(el).overflowY`. */\n overflowY: string\n /** Метка для текста предупреждения: тег плюс классы. */\n label: string\n}\n\nexport interface GrAffixScrollScan {\n /** Индекс предка-скроллера в переданном списке; `-1` — прокручивается вьюпорт. */\n scrollerIndex: number\n /** Предок, который стал скроллпортом раньше скроллера: внутри него `sticky` мёртв. */\n clipperLabel: string | null\n}\n\n/**\n * Один проход по цепочке предков, два ответа: root наблюдателя и повод для\n * dev-предупреждения.\n *\n * Читается вычисленный `overflow-y`, а не объявленный и не классы, и это не\n * придирка: при `overflow-x: hidden; overflow-y: visible` спецификация превращает\n * вычисленный `overflow-y` в `auto` — предок действительно становится рабочим\n * скроллпортом. Проверка по классу `overflow-x-hidden` дала бы здесь ложную\n * тревогу на совершенно исправной вёрстке.\n *\n * Обход останавливается на первом скроллере: всё выше него к прилипанию отношения\n * не имеет, поэтому и клипер над скроллером виновным не считается. Ровно такая\n * пара есть в витрине — внешняя панель `overflow-hidden`, внутри `overflow-y-auto`.\n */\nexport function scanAffixAncestors(ancestors: readonly GrAffixAncestor[]): GrAffixScrollScan {\n let clipperLabel: string | null = null\n\n for (let index = 0; index < ancestors.length; index += 1) {\n const { overflowY, label } = ancestors[index]\n\n if (SCROLLABLE.has(overflowY))\n return { scrollerIndex: index, clipperLabel }\n\n if (clipperLabel === null && CLIPPING.has(overflowY))\n clipperLabel = label\n }\n\n return { scrollerIndex: -1, clipperLabel }\n}\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\n\nimport {\n type GrAffixAncestor,\n type GrAffixPlacement,\n affixOffsetLength,\n affixRootMargin,\n isAffixStuck,\n scanAffixAncestors,\n} from './affixState'\nimport { affixSentinelClass, grAffixRootClass } from './grAffixStyles'\n\n/**\n * GrAffix — панель, прилипающая к краю при прокрутке: заголовок раздела,\n * оглавление, кнопки «Сохранить/Отмена» под длинной формой.\n *\n * Позиционирует `position: sticky`; наблюдатель нужен только чтобы **узнать**,\n * прилипла ли панель, — от этого зависят фон, тень и содержимое. Отлипшая панель\n * стоит внутри формы и обязана выглядеть её частью, поэтому поверхность\n * включается вместе с состоянием, а не всегда, как у `GrNavbar` и `GrTable`.\n *\n * Корень компонента — сама липкая коробка, а не обёртка вокруг неё: `sticky`\n * зажат своим containing block, и обёртка высотой в коробку не дала бы ей ехать\n * вовсе. Сентинел — сосед коробки, отмечающий её место в потоке.\n */\nexport interface GrAffixProps {\n /** Край, к которому прилипает панель. */\n placement?: GrAffixPlacement\n /**\n * Отступ от края в прилипшем состоянии. Число — пиксели, строка — как есть\n * (`4rem`, `var(--gr-navbar-height)`, `calc(…)`).\n *\n * Общий отступ для группы панелей задаётся не пропом, а токеном\n * `--gr-affix-offset` на контейнере или на `:root`: наблюдатель читает\n * вычисленный стиль и подхватывает его так же, как значение пропа.\n */\n offset?: number | string\n /**\n * Прилипание выключено: разметка та же, `sticky` не ставится.\n *\n * Именно проп, а не `v-if` снаружи: размонтирование убило бы фокус на кнопке\n * панели и состояние вложенных контролов.\n */\n disabled?: boolean\n}\n\nexport interface GrAffixEmits {\n (e: 'stickyChange', stuck: boolean): void\n}\n\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<GrAffixProps>(), {\n placement: 'top',\n offset: undefined,\n disabled: false,\n})\n\nconst emit = defineEmits<GrAffixEmits>()\n\ndefineSlots<{\n /** Содержимое панели. `stuck` позволяет менять его в прилипшем виде. */\n default?: (props: { stuck: boolean }) => unknown\n}>()\n\nconst boxEl = ref<HTMLElement | null>(null)\nconst sentinelTopEl = ref<HTMLElement | null>(null)\nconst sentinelBottomEl = ref<HTMLElement | null>(null)\n\nconst sentinelEl = computed(() => (\n props.placement === 'top' ? sentinelTopEl.value : sentinelBottomEl.value\n))\n\nconst stuck = ref(false)\n\n/**\n * Наблюдателя в этой среде нет вовсе (сервер, jsdom, старый движок). Тогда\n * `sticky` продолжает работать — его делает CSS, — но знание о состоянии\n * пропадает, и поверхность включается всегда. Прилипшая прозрачная панель\n * пропускает сквозь себя строки; всегда непрозрачная просто чуть более\n * декоративна, чем нужно. Из двух деградаций выбрана вторая.\n */\nconst observerMissing = ref(false)\n\nconst surfaceOn = computed(() => !props.disabled && (stuck.value || observerMissing.value))\n\nconst rootClass = computed(() => grAffixRootClass(props.placement, !props.disabled, surfaceOn.value))\n\nconst rootStyle = computed(() => {\n const length = affixOffsetLength(props.offset)\n\n return length === undefined ? undefined : { '--gr-affix-offset': length }\n})\n\nlet observer: IntersectionObserver | null = null\nlet appliedOffsetPx = 0\nlet warned = false\n\n/**\n * Отступ берётся из вычисленного стиля, а не из пропа, по трём причинам:\n * браузер уже разрешил `4rem`/`var()`/`calc()` в used value, и свой парсер CSS\n * не нужен; отступ может прийти не из пропа вовсе, а из каскада; у `sticky`\n * `getComputedStyle().top` возвращает заданный порог прилипания, а не смещённую\n * позицию, то есть ровно нужное число.\n */\nfunction measureOffsetPx(): number {\n const box = boxEl.value\n if (!box || typeof getComputedStyle !== 'function')\n return 0\n\n const parsed = Number.parseFloat(getComputedStyle(box)[props.placement])\n\n return Number.isFinite(parsed) ? Math.max(0, parsed) : 0\n}\n\nfunction describeElement(el: HTMLElement): string {\n const tag = el.tagName.toLowerCase()\n const classes = el.getAttribute('class')?.trim().split(/\\s+/).slice(0, 2) ?? []\n\n return classes.length > 0 ? `<${tag} class=\"${classes.join(' ')}…\">` : `<${tag}>`\n}\n\n/**\n * Цепочка предков до `<body>`, не включая его.\n *\n * Верхняя граница выбрана не по вкусу: `useScrollLock` вешает\n * `overflow: hidden` на `body` на всё время открытой модалки, и дойди обход\n * туда — каждый аффикс внутри `GrModal` объявлялся бы сломанным, причём только\n * в этом состоянии, то есть через раз.\n */\nfunction collectAncestors(box: HTMLElement): { el: HTMLElement, info: GrAffixAncestor }[] {\n const chain: { el: HTMLElement, info: GrAffixAncestor }[] = []\n\n if (typeof getComputedStyle !== 'function')\n return chain\n\n let current = box.parentElement\n\n while (current && current !== document.body) {\n chain.push({\n el: current,\n info: { overflowY: getComputedStyle(current).overflowY, label: describeElement(current) },\n })\n current = current.parentElement\n }\n\n return chain\n}\n\nfunction detach(): void {\n observer?.disconnect()\n observer = null\n}\n\nfunction attach(): void {\n detach()\n\n if (props.disabled) {\n stuck.value = false\n return\n }\n\n if (typeof IntersectionObserver === 'undefined') {\n observerMissing.value = true\n return\n }\n\n const box = boxEl.value\n const sentinel = sentinelEl.value\n if (!box || !sentinel)\n return\n\n const chain = collectAncestors(box)\n const scan = scanAffixAncestors(chain.map(item => item.info))\n\n // `null`, а не `document.documentElement`: по спецификации это разные корни,\n // и Safari исторически расходился на втором.\n const root = scan.scrollerIndex >= 0 ? chain[scan.scrollerIndex].el : null\n\n appliedOffsetPx = measureOffsetPx()\n\n observer = new IntersectionObserver(handle, {\n root,\n rootMargin: affixRootMargin(props.placement, appliedOffsetPx),\n // Сентинел высотой в пиксель пересекает границу целиком — промежуточных\n // долей у него не бывает, и второй порог дал бы вдвое больше вызовов без\n // единой новой развилки.\n threshold: 0,\n })\n observer.observe(sentinel)\n\n if (__GR_DEV__ && !warned && scan.clipperLabel) {\n warned = true\n console.warn(\n `[granularity] GrAffix: предок ${scan.clipperLabel} задаёт \\`overflow\\`, отличный от `\n + '`visible`, и прокручивать его нечем — он становится скроллпортом, внутри которого '\n + '`position: sticky` не двигается. Снимите `overflow` с этого предка либо сделайте его '\n + 'настоящим скроллером (`overflow-y: auto` плюс ограничение высоты).',\n )\n }\n}\n\nfunction handle(entries: IntersectionObserverEntry[]): void {\n const entry = entries[entries.length - 1]\n if (!entry)\n return\n\n const next = isAffixStuck(entry, props.placement, stuck.value)\n\n // Дедуп по предыдущему булеву — единственная защита от дребезга у границы:\n // гистерезиса нет намеренно, любой эпсилон разошёлся бы с CSS, у которого его\n // нет, и дал бы кадр, где `data-stuck` уже стоит, а панель ещё едет.\n if (next !== stuck.value) {\n stuck.value = next\n emit('stickyChange', next)\n }\n\n // Самоизлечение: в dev-режиме UnoCSS отдаёт правила по требованию, и в момент\n // монтирования `top` мог ещё не разрешиться — тогда `rootMargin` собран из\n // нуля. Пересобираем наблюдателя, как только замер разошёлся с применённым;\n // повторный вызов даст то же число и цикла не будет.\n if (measureOffsetPx() !== appliedOffsetPx)\n attach()\n}\n\n/** Перечитать отступ и пересобрать наблюдателя: `vh` после ресайза, смена высоты шапки. */\nfunction remeasure(): void {\n attach()\n}\n\nonMounted(attach)\nonBeforeUnmount(detach)\n\n// `flush: 'post'` обязателен: при смене края сентинел переезжает на другую\n// сторону коробки, и до обновления DOM ссылка вела бы на снятый узел.\nwatch(() => [props.placement, props.offset, props.disabled] as const, attach, { flush: 'post' })\n\ndefineExpose({ stuck, remeasure })\n</script>\n\n<template>\n <div\n v-if=\"!disabled && placement === 'top'\"\n ref=\"sentinelTopEl\"\n data-gr-affix-sentinel\n aria-hidden=\"true\"\n :class=\"affixSentinelClass.top\"\n />\n\n <div\n ref=\"boxEl\"\n data-gr-affix\n :data-placement=\"placement\"\n :data-stuck=\"stuck ? 'true' : undefined\"\n :class=\"rootClass\"\n :style=\"rootStyle\"\n v-bind=\"$attrs\"\n >\n <slot :stuck=\"stuck\" />\n </div>\n\n <div\n v-if=\"!disabled && placement === 'bottom'\"\n ref=\"sentinelBottomEl\"\n data-gr-affix-sentinel\n aria-hidden=\"true\"\n :class=\"affixSentinelClass.bottom\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\n\nimport {\n type GrAffixAncestor,\n type GrAffixPlacement,\n affixOffsetLength,\n affixRootMargin,\n isAffixStuck,\n scanAffixAncestors,\n} from './affixState'\nimport { affixSentinelClass, grAffixRootClass } from './grAffixStyles'\n\n/**\n * GrAffix — панель, прилипающая к краю при прокрутке: заголовок раздела,\n * оглавление, кнопки «Сохранить/Отмена» под длинной формой.\n *\n * Позиционирует `position: sticky`; наблюдатель нужен только чтобы **узнать**,\n * прилипла ли панель, — от этого зависят фон, тень и содержимое. Отлипшая панель\n * стоит внутри формы и обязана выглядеть её частью, поэтому поверхность\n * включается вместе с состоянием, а не всегда, как у `GrNavbar` и `GrTable`.\n *\n * Корень компонента — сама липкая коробка, а не обёртка вокруг неё: `sticky`\n * зажат своим containing block, и обёртка высотой в коробку не дала бы ей ехать\n * вовсе. Сентинел — сосед коробки, отмечающий её место в потоке.\n */\nexport interface GrAffixProps {\n /** Край, к которому прилипает панель. */\n placement?: GrAffixPlacement\n /**\n * Отступ от края в прилипшем состоянии. Число — пиксели, строка — как есть\n * (`4rem`, `var(--gr-navbar-height)`, `calc(…)`).\n *\n * Общий отступ для группы панелей задаётся не пропом, а токеном\n * `--gr-affix-offset` на контейнере или на `:root`: наблюдатель читает\n * вычисленный стиль и подхватывает его так же, как значение пропа.\n */\n offset?: number | string\n /**\n * Прилипание выключено: разметка та же, `sticky` не ставится.\n *\n * Именно проп, а не `v-if` снаружи: размонтирование убило бы фокус на кнопке\n * панели и состояние вложенных контролов.\n */\n disabled?: boolean\n}\n\nexport interface GrAffixEmits {\n (e: 'stickyChange', stuck: boolean): void\n}\n\ndefineOptions({ inheritAttrs: false })\n\nconst props = withDefaults(defineProps<GrAffixProps>(), {\n placement: 'top',\n offset: undefined,\n disabled: false,\n})\n\nconst emit = defineEmits<GrAffixEmits>()\n\ndefineSlots<{\n /** Содержимое панели. `stuck` позволяет менять его в прилипшем виде. */\n default?: (props: { stuck: boolean }) => unknown\n}>()\n\nconst boxEl = ref<HTMLElement | null>(null)\nconst sentinelTopEl = ref<HTMLElement | null>(null)\nconst sentinelBottomEl = ref<HTMLElement | null>(null)\n\nconst sentinelEl = computed(() => (\n props.placement === 'top' ? sentinelTopEl.value : sentinelBottomEl.value\n))\n\nconst stuck = ref(false)\n\n/**\n * Наблюдателя в этой среде нет вовсе (сервер, jsdom, старый движок). Тогда\n * `sticky` продолжает работать — его делает CSS, — но знание о состоянии\n * пропадает, и поверхность включается всегда. Прилипшая прозрачная панель\n * пропускает сквозь себя строки; всегда непрозрачная просто чуть более\n * декоративна, чем нужно. Из двух деградаций выбрана вторая.\n */\nconst observerMissing = ref(false)\n\nconst surfaceOn = computed(() => !props.disabled && (stuck.value || observerMissing.value))\n\nconst rootClass = computed(() => grAffixRootClass(props.placement, !props.disabled, surfaceOn.value))\n\nconst rootStyle = computed(() => {\n const length = affixOffsetLength(props.offset)\n\n return length === undefined ? undefined : { '--gr-affix-offset': length }\n})\n\nlet observer: IntersectionObserver | null = null\nlet appliedOffsetPx = 0\nlet warned = false\n\n/**\n * Отступ берётся из вычисленного стиля, а не из пропа, по трём причинам:\n * браузер уже разрешил `4rem`/`var()`/`calc()` в used value, и свой парсер CSS\n * не нужен; отступ может прийти не из пропа вовсе, а из каскада; у `sticky`\n * `getComputedStyle().top` возвращает заданный порог прилипания, а не смещённую\n * позицию, то есть ровно нужное число.\n */\nfunction measureOffsetPx(): number {\n const box = boxEl.value\n if (!box || typeof getComputedStyle !== 'function')\n return 0\n\n const parsed = Number.parseFloat(getComputedStyle(box)[props.placement])\n\n return Number.isFinite(parsed) ? Math.max(0, parsed) : 0\n}\n\nfunction describeElement(el: HTMLElement): string {\n const tag = el.tagName.toLowerCase()\n const classes = el.getAttribute('class')?.trim().split(/\\s+/).slice(0, 2) ?? []\n\n return classes.length > 0 ? `<${tag} class=\"${classes.join(' ')}…\">` : `<${tag}>`\n}\n\n/**\n * Цепочка предков до `<body>`, не включая его.\n *\n * Верхняя граница выбрана не по вкусу: `useScrollLock` вешает\n * `overflow: hidden` на `body` на всё время открытой модалки, и дойди обход\n * туда — каждый аффикс внутри `GrModal` объявлялся бы сломанным, причём только\n * в этом состоянии, то есть через раз.\n */\nfunction collectAncestors(box: HTMLElement): { el: HTMLElement, info: GrAffixAncestor }[] {\n const chain: { el: HTMLElement, info: GrAffixAncestor }[] = []\n\n if (typeof getComputedStyle !== 'function')\n return chain\n\n let current = box.parentElement\n\n while (current && current !== document.body) {\n chain.push({\n el: current,\n info: { overflowY: getComputedStyle(current).overflowY, label: describeElement(current) },\n })\n current = current.parentElement\n }\n\n return chain\n}\n\nfunction detach(): void {\n observer?.disconnect()\n observer = null\n}\n\nfunction attach(): void {\n detach()\n\n if (props.disabled) {\n stuck.value = false\n return\n }\n\n if (typeof IntersectionObserver === 'undefined') {\n observerMissing.value = true\n return\n }\n\n const box = boxEl.value\n const sentinel = sentinelEl.value\n if (!box || !sentinel)\n return\n\n const chain = collectAncestors(box)\n const scan = scanAffixAncestors(chain.map(item => item.info))\n\n // `null`, а не `document.documentElement`: по спецификации это разные корни,\n // и Safari исторически расходился на втором.\n const root = scan.scrollerIndex >= 0 ? chain[scan.scrollerIndex].el : null\n\n appliedOffsetPx = measureOffsetPx()\n\n observer = new IntersectionObserver(handle, {\n root,\n rootMargin: affixRootMargin(props.placement, appliedOffsetPx),\n // Сентинел высотой в пиксель пересекает границу целиком — промежуточных\n // долей у него не бывает, и второй порог дал бы вдвое больше вызовов без\n // единой новой развилки.\n threshold: 0,\n })\n observer.observe(sentinel)\n\n if (__GR_DEV__ && !warned && scan.clipperLabel) {\n warned = true\n console.warn(\n `[granularity] GrAffix: предок ${scan.clipperLabel} задаёт \\`overflow\\`, отличный от `\n + '`visible`, и прокручивать его нечем — он становится скроллпортом, внутри которого '\n + '`position: sticky` не двигается. Снимите `overflow` с этого предка либо сделайте его '\n + 'настоящим скроллером (`overflow-y: auto` плюс ограничение высоты).',\n )\n }\n}\n\nfunction handle(entries: IntersectionObserverEntry[]): void {\n const entry = entries[entries.length - 1]\n if (!entry)\n return\n\n const next = isAffixStuck(entry, props.placement, stuck.value)\n\n // Дедуп по предыдущему булеву — единственная защита от дребезга у границы:\n // гистерезиса нет намеренно, любой эпсилон разошёлся бы с CSS, у которого его\n // нет, и дал бы кадр, где `data-stuck` уже стоит, а панель ещё едет.\n if (next !== stuck.value) {\n stuck.value = next\n emit('stickyChange', next)\n }\n\n // Самоизлечение: в dev-режиме UnoCSS отдаёт правила по требованию, и в момент\n // монтирования `top` мог ещё не разрешиться — тогда `rootMargin` собран из\n // нуля. Пересобираем наблюдателя, как только замер разошёлся с применённым;\n // повторный вызов даст то же число и цикла не будет.\n if (measureOffsetPx() !== appliedOffsetPx)\n attach()\n}\n\n/** Перечитать отступ и пересобрать наблюдателя: `vh` после ресайза, смена высоты шапки. */\nfunction remeasure(): void {\n attach()\n}\n\nonMounted(attach)\nonBeforeUnmount(detach)\n\n// `flush: 'post'` обязателен: при смене края сентинел переезжает на другую\n// сторону коробки, и до обновления DOM ссылка вела бы на снятый узел.\nwatch(() => [props.placement, props.offset, props.disabled] as const, attach, { flush: 'post' })\n\ndefineExpose({ stuck, remeasure })\n</script>\n\n<template>\n <div\n v-if=\"!disabled && placement === 'top'\"\n ref=\"sentinelTopEl\"\n data-gr-affix-sentinel\n aria-hidden=\"true\"\n :class=\"affixSentinelClass.top\"\n />\n\n <div\n ref=\"boxEl\"\n data-gr-affix\n :data-placement=\"placement\"\n :data-stuck=\"stuck ? 'true' : undefined\"\n :class=\"rootClass\"\n :style=\"rootStyle\"\n v-bind=\"$attrs\"\n >\n <slot :stuck=\"stuck\" />\n </div>\n\n <div\n v-if=\"!disabled && placement === 'bottom'\"\n ref=\"sentinelBottomEl\"\n data-gr-affix-sentinel\n aria-hidden=\"true\"\n :class=\"affixSentinelClass.bottom\"\n />\n</template>\n"],"mappings":"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"}
|