@feugene/granularity 0.42.0 → 0.43.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 +3 -3
- package/dist/chunks/config-BXRmsn3t.js +172 -0
- package/dist/chunks/config-BXRmsn3t.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-C_QkUTRL.js} +32 -1
- package/dist/chunks/en-C_QkUTRL.js.map +1 -0
- package/dist/chunks/{es-DCkqHTlU.js → es-B4t_3Wnh.js} +3 -3
- package/dist/chunks/{es-DCkqHTlU.js.map → es-B4t_3Wnh.js.map} +1 -1
- package/dist/chunks/{es-QpzdIEut.js → es-DemSEhZE.js} +32 -1
- package/dist/chunks/es-DemSEhZE.js.map +1 -0
- package/dist/chunks/{generated-B95IBXs6.js → generated-VcxJTo_q.js} +210 -0
- package/dist/chunks/generated-VcxJTo_q.js.map +1 -0
- package/dist/chunks/{granular-provider-BG_2XqkS.js → granular-provider-BohXQcNt.js} +7 -1
- package/dist/chunks/granular-provider-BohXQcNt.js.map +1 -0
- package/dist/chunks/{ru-D_1Ya489.js → ru-CLUErjhY.js} +32 -1
- package/dist/chunks/ru-CLUErjhY.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/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/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/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 +23 -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/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/granular-provider/shared.d.ts +3 -0
- package/dist/types/i18n/messages/en.d.ts +58 -0
- package/dist/types/i18n/messages/es.d.ts +58 -0
- package/dist/types/i18n/messages/ru.d.ts +58 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/web-types.json +718 -288
- package/package.json +17 -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":"GrCarousel-ByuughtI.js","names":[],"sources":["../../../../src/components/GrCarousel/carouselNavigation.ts","../../../../src/components/GrCarousel/composables/useCarouselAutoplay.ts","../../../../src/components/GrCarousel/composables/useCarouselSwipe.ts","../../../../src/components/GrCarousel/grCarouselContext.ts","../../../../src/components/GrCarousel/GrCarousel.vue","../../../../src/components/GrCarousel/GrCarousel.vue","../../../../src/components/GrCarousel/GrCarouselSlide.vue","../../../../src/components/GrCarousel/GrCarouselSlide.vue"],"sourcesContent":["/** Сколько пикселей должен пройти указатель, чтобы это считалось листанием. */\nexport const SWIPE_THRESHOLD_PX = 60\n\n/**\n * Во сколько раз горизонталь обязана превысить вертикаль. Ниже этого жест\n * считается вертикальной прокруткой страницы и ленту не двигает: палец редко\n * идёт строго по оси, и без фильтра карусель перехватывала бы скролл.\n */\nexport const SWIPE_AXIS_RATIO = 1.5\n\n/**\n * Доля ширины вьюпорта, начиная с которой жест засчитывается. Работает вместе\n * с {@link SWIPE_THRESHOLD_PX} по максимуму: 60px на герое шириной 1200px —\n * это нервно, а на узкой карточке доля выродилась бы в пару пикселей.\n */\nexport const SWIPE_THRESHOLD_RATIO = 0.15\n\n/** Во сколько раз вязче идёт лента, когда листать в эту сторону уже некуда. */\nexport const SWIPE_RESISTANCE = 4\n\n/**\n * Логическое направление жеста: `1` — вперёд, `-1` — назад, `0` — не свайп.\n *\n * `dx` физический, в пикселях экрана; разворот под RTL — забота вызывающего,\n * модуль про направление письма не знает. Палец, ушедший влево, листает вперёд:\n * содержимое едет за пальцем, как в нативной прокрутке.\n *\n * Отдельным модулем, потому что в jsdom раскладки нет и через смонтированный\n * компонент ни одна ветка не проверяется — тот же довод, что у\n * {@link resolveScrollOverflow}.\n */\nexport function resolveSwipeDirection(\n dx: number,\n dy: number,\n threshold: number = SWIPE_THRESHOLD_PX,\n): -1 | 0 | 1 {\n if (Math.abs(dx) < threshold)\n return 0\n\n if (Math.abs(dx) < Math.abs(dy) * SWIPE_AXIS_RATIO)\n return 0\n\n return dx < 0 ? 1 : -1\n}\n\n/**\n * Порог жеста для ленты шириной `width`. При нулевой ширине — а в jsdom она\n * всегда нулевая — вырождается в пиксельный минимум, и жест остаётся\n * проверяемым.\n */\nexport function swipeThresholdFor(width: number): number {\n return Math.max(SWIPE_THRESHOLD_PX, width * SWIPE_THRESHOLD_RATIO)\n}\n\n/** Смещение ленты за пальцем: за краем — вязкое, иначе один в один. */\nexport function resistOffset(delta: number, atEdge: boolean): number {\n return atEdge ? delta / SWIPE_RESISTANCE : delta\n}\n\n/**\n * Индекс, зажатый в границы ленты. Пустая лента даёт `0`: слайда с таким\n * номером нет, но отрицательный индекс уехал бы в CSS-переменную трека.\n */\nexport function clampIndex(index: number, count: number): number {\n if (count <= 0)\n return 0\n\n if (!Number.isFinite(index))\n return 0\n\n return Math.min(Math.max(Math.trunc(index), 0), count - 1)\n}\n\n/**\n * Шаг по ленте. Без `loop` на краю возвращает тот же индекс — вызывающий по\n * этому признаку и гасит стрелку, отдельного предиката не нужно.\n */\nexport function stepIndex(index: number, delta: number, count: number, loop: boolean): number {\n if (count <= 0)\n return 0\n\n const current = clampIndex(index, count)\n const next = current + delta\n\n if (loop)\n return ((next % count) + count) % count\n\n return clampIndex(next, count)\n}\n\n/**\n * Насколько прокрутить полосу переключателей, чтобы кадр был виден.\n *\n * Считается своей арифметикой, а не `scrollIntoView`: тот прокручивает **всех**\n * предков, включая документ, — и карусель, уехавшая под сгиб, на каждом шаге\n * автопрокрутки утаскивала бы страницу обратно к себе. Здесь двигается только\n * сама полоса.\n *\n * Движение минимальное: элемент уже виден — `scrollLeft` не меняется вовсе.\n */\nexport function stripScrollLeft(\n scrollLeft: number,\n viewport: number,\n itemStart: number,\n itemEnd: number,\n padding = 0,\n): number {\n if (viewport <= 0)\n return scrollLeft\n\n if (itemStart - padding < scrollLeft)\n return Math.max(0, itemStart - padding)\n\n if (itemEnd + padding > scrollLeft + viewport)\n return itemEnd + padding - viewport\n\n return scrollLeft\n}\n","import { computed, onScopeDispose, ref, watchEffect } from 'vue'\nimport type { ComputedRef } from 'vue'\n\nexport interface UseCarouselAutoplayOptions {\n /** Пауза между слайдами, мс. */\n interval: () => number\n /** Показ включён: проп задан и его никто не остановил. */\n enabled: () => boolean\n /**\n * Отсчёт приостановлен — курсор над лентой, идёт жест, вкладка в фоне.\n * Это **не** остановка: остаток сохраняется, и по снятию паузы лента\n * досчитывает его, а не начинает интервал заново.\n */\n paused: () => boolean\n /** Шаг ленты. */\n advance: () => void\n}\n\nexport interface UseCarouselAutoplayReturn {\n /** Заведён ли отсчёт прямо сейчас. Пауза его снимает, намерение показа — нет. */\n running: ComputedRef<boolean>\n /** Забыть остаток и отсчитать интервал заново — после смены слайда извне. */\n restart: () => void\n}\n\n/**\n * Таймер автопрокрутки с паузой по остатку.\n *\n * Модель взята у `useToast` (`{ handle, remaining, startedAt }`), и `setInterval`\n * здесь не годится принципиально: паузу, сохраняющую остаток, на нём не\n * выразить — курсор, снятый за полсекунды до шага, откладывал бы шаг на полный\n * интервал.\n *\n * Композабл локальный: публичный потребовал бы записей в трёх реестрах пакета,\n * а переиспользовать его сегодня некому.\n */\nexport function useCarouselAutoplay(options: UseCarouselAutoplayOptions): UseCarouselAutoplayReturn {\n let handle: number | null = null\n let remaining = 0\n let startedAt = 0\n\n const armed = ref(false)\n\n function clear(): void {\n if (handle !== null) {\n clearTimeout(handle)\n handle = null\n }\n armed.value = false\n }\n\n function arm(): void {\n // На сервере таймеру взяться неоткуда, и заводить его там нечего.\n if (typeof window === 'undefined' || remaining <= 0)\n return\n\n startedAt = Date.now()\n armed.value = true\n handle = window.setTimeout(() => {\n handle = null\n remaining = options.interval()\n options.advance()\n\n // Шаг мог упереться в край без `loop` — тогда показ уже выключен.\n if (options.enabled() && !options.paused())\n arm()\n else clear()\n }, remaining)\n }\n\n function pause(): void {\n if (handle === null) {\n armed.value = false\n return\n }\n\n clearTimeout(handle)\n handle = null\n armed.value = false\n remaining = Math.max(0, remaining - (Date.now() - startedAt))\n }\n\n function restart(): void {\n clear()\n remaining = options.interval()\n if (options.enabled() && !options.paused())\n arm()\n }\n\n watchEffect(() => {\n // `interval()` читается ради зависимости: смена паузы обязана перезавести таймер.\n const ms = options.interval()\n\n if (!options.enabled() || options.paused() || ms <= 0) {\n pause()\n return\n }\n\n if (handle !== null)\n return\n\n if (remaining <= 0)\n remaining = ms\n\n arm()\n })\n\n onScopeDispose(clear)\n\n return { running: computed(() => armed.value), restart }\n}\n","import { ref } from 'vue'\nimport type { Ref } from 'vue'\n\nimport { useDragGesture } from '../../../composables/useDragGesture'\nimport {\n resistOffset,\n resolveSwipeDirection,\n SWIPE_AXIS_RATIO,\n swipeThresholdFor,\n} from '../carouselNavigation'\n\n/**\n * Что внутри слайда остаётся нажатием, а не превращается в протяжку. Ссылка и\n * кнопка в онбординг-слайде — обычный интерактив, и жест их перехватывать\n * не вправе (приём `GrToaster`).\n */\nconst INTERACTIVE_SELECTOR = 'a[href], button, input, textarea, select, summary, [role=\"button\"], [contenteditable]'\n\nexport interface UseCarouselSwipeOptions {\n disabled: () => boolean\n /** Вьюпорт: у него спрашиваются ширина и направление письма. */\n viewport: () => HTMLElement | null\n /** Порог пройден. Направление логическое: `1` — вперёд, `-1` — назад. */\n onSwipe: (direction: 1 | -1) => void\n /** Листать в эту сторону некуда — жест идёт вязко. */\n atEdge: (direction: 1 | -1) => boolean\n /** Жест начался: показ пора ставить на паузу. */\n onStart?: () => void\n}\n\nexport interface UseCarouselSwipeReturn {\n isDragging: Ref<boolean>\n /** Смещение ленты за указателем, px. Физическое: зеркалит его CSS, не JS. */\n offset: Ref<number>\n /** Вешается на `@pointerdown` вьюпорта. */\n start: (event: PointerEvent) => void\n}\n\nexport function useCarouselSwipe(options: UseCarouselSwipeOptions): UseCarouselSwipeReturn {\n const offset = ref(0)\n\n let startX = 0\n let startY = 0\n let threshold = 0\n let rtl = false\n\n function reset(): void {\n offset.value = 0\n }\n\n const drag = useDragGesture({\n disabled: options.disabled,\n onStart: (event) => {\n const target = event.target as HTMLElement | null\n if (target?.closest(INTERACTIVE_SELECTOR))\n return false\n\n const viewport = options.viewport()\n if (!viewport)\n return false\n\n startX = event.clientX\n startY = event.clientY\n offset.value = 0\n\n // Замеры снимаются один раз на жест и в обработчике, а не в `setup`:\n // на сервере ни ширины, ни направления письма не существует.\n threshold = swipeThresholdFor(viewport.getBoundingClientRect().width)\n rtl = getComputedStyle(viewport).direction === 'rtl'\n\n options.onStart?.()\n },\n onMove: (event) => {\n // `useDragGesture` намеренно не зовёт `preventDefault`, а без него\n // протяжка выделяет текст слайда.\n event.preventDefault()\n\n const dx = event.clientX - startX\n const dy = event.clientY - startY\n\n // Пока жест выглядит вертикальным, лента не двигается: это прокрутка\n // страницы, и перехватывать её карусель не вправе.\n if (Math.abs(dx) < Math.abs(dy) * SWIPE_AXIS_RATIO) {\n offset.value = 0\n return\n }\n\n const logical = physicalToLogical(dx < 0 ? 1 : -1)\n offset.value = resistOffset(dx, options.atEdge(logical))\n },\n onEnd: (event) => {\n const direction = event\n ? resolveSwipeDirection(event.clientX - startX, event.clientY - startY, threshold)\n : 0\n\n reset()\n\n if (direction !== 0)\n options.onSwipe(physicalToLogical(direction))\n },\n // Обрыв — не жест: указатель забрал браузер, а не пользователь довёл\n // протяжку. Кадр возвращается на место, слайд не меняется.\n onCancel: reset,\n })\n\n /** В RTL содержимое течёт справа налево, и палец влево листает назад. */\n function physicalToLogical(direction: 1 | -1): 1 | -1 {\n return rtl ? (direction === 1 ? -1 : 1) : direction\n }\n\n return { isDragging: drag.isDragging, offset, start: drag.start }\n}\n","import type { ComputedRef, InjectionKey } from 'vue'\n\n/** Что слайд сообщает о себе ленте при регистрации. */\nexport interface GrCarouselSlideEntry {\n /** `useId()` слайда. Он же его DOM-id и ключ реестра. */\n id: string\n /** Имя слайда: подпись его переключателя и `aria-label` самого слайда. */\n label: () => string | undefined\n /** URL миниатюры. */\n thumbnailSrc: () => string | undefined\n /**\n * Дал ли слайд свою разметку миниатюры. Геттер, а не снимок: слот, появившийся\n * позже, снимком в `setup` не увиделся бы.\n */\n hasThumbnail: () => boolean\n /**\n * Своя разметка миниатюры. Рендерит её **полоса переключателей**, а не слайд:\n * узел уезжает наверх ссылкой. Ссылка стабильна — иначе `:is` перемонтировал\n * бы миниатюру на каждый рендер.\n */\n thumbnail: () => unknown\n /**\n * Корневой узел. Геттер, а не значение: регистрация идёт в `setup`, когда\n * элемента ещё нет. По нему же восстанавливается порядок слайдов в документе.\n */\n el: () => HTMLElement | null\n}\n\nexport interface GrCarouselContext {\n /** Регистрация слайда. Возвращает отписку. */\n register: (entry: GrCarouselSlideEntry) => () => void\n /** Позиция слайда в ленте; `-1`, пока он не зарегистрирован. */\n indexOf: (id: string) => number\n isCurrent: (id: string) => boolean\n total: ComputedRef<number>\n /**\n * `tabpanel`, когда есть полоса переключателей, иначе `group`. Роль слайда\n * зависит от наличия переключателя, а не от его вида: `tab` требует парного\n * `tabpanel`, и без полосы привязать его было бы не к чему.\n */\n slideRole: ComputedRef<'tabpanel' | 'group'>\n slideRoledescription: ComputedRef<string>\n /** id переключателя для `aria-labelledby` слайда; `undefined` без полосы. */\n tabIdFor: (id: string) => string | undefined\n /** «N из M» — имя слайда, когда своего `label` у него нет. */\n positionLabel: (id: string) => string\n}\n\nexport const GR_CAROUSEL_CONTEXT: InjectionKey<GrCarouselContext> = Symbol('GR_CAROUSEL_CONTEXT')\n","<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, provide, ref, watch, watchEffect } from 'vue'\nimport type { ComponentPublicInstance } from 'vue'\n\nimport { useAnnouncer } from '../../composables/useAnnouncer'\nimport { useFocusWithin } from '../../composables/internal/useFocusWithin'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrComponentProp } from '../GrConfigProvider/context'\nimport { resolveScrollOverflow } from '../shared/scrollOverflow'\nimport type { GrScrollOverflow } from '../shared/scrollOverflow'\nimport { clampIndex, stepIndex, stripScrollLeft } from './carouselNavigation'\nimport { useCarouselAutoplay } from './composables/useCarouselAutoplay'\nimport { useCarouselSwipe } from './composables/useCarouselSwipe'\nimport { GR_CAROUSEL_CONTEXT } from './grCarouselContext'\nimport type { GrCarouselSlideEntry } from './grCarouselContext'\nimport {\n carouselIconClass,\n carouselRootBase,\n carouselThumbFallbackClass,\n carouselThumbImageClass,\n carouselToggleClass,\n carouselTrackBase,\n carouselViewportBase,\n carouselViewportSwipeClass,\n grCarouselControlClass,\n grCarouselIndicatorClass,\n grCarouselIndicatorsClass,\n} from './grCarouselStyles'\nimport type { GrCarouselActivationMode, GrCarouselIndicators } from './grCarouselStyles'\nimport type { GrTone } from '../shared/tones'\n\nimport IconChevronLeft from '~icons/lucide/chevron-left'\nimport IconChevronRight from '~icons/lucide/chevron-right'\nimport IconPause from '~icons/lucide/pause'\nimport IconPlay from '~icons/lucide/play'\n\nexport interface GrCarouselProps {\n /** Индекс текущего кадра. Не передан — лента ведёт позицию сама. */\n modelValue?: number\n /** Запустить показ. Под `prefers-reduced-motion` не стартует — тумблер остаётся. */\n autoplay?: boolean\n /** Пауза между кадрами, мс. */\n autoplayInterval?: number\n /** Замкнуть ленту. Выключено — стрелки на краях гаснут. */\n loop?: boolean\n /** Вид переключателя кадров. `none` меняет и роль самих слайдов. */\n indicators?: GrCarouselIndicators\n /** Тон текущего переключателя по общей шкале пакета. */\n tone?: GrTone\n /** Листает ли стрелка по переключателям сразу или только двигает фокус. */\n activationMode?: GrCarouselActivationMode\n /** Стрелки «назад/вперёд». */\n arrows?: boolean\n /** Листание протяжкой указателя. */\n swipe?: boolean\n /** Сделать карусель ориентиром страницы (`role=\"region\"`). */\n landmark?: boolean\n /** Имя карусели. APG требует его: `aria-roledescription` именем не считается. */\n ariaLabel?: string\n /** Имя из заголовка на странице. Сильнее `ariaLabel`. */\n ariaLabelledby?: string\n prevLabel?: string\n nextLabel?: string\n playLabel?: string\n pauseLabel?: string\n /** Имя полосы переключателей. */\n indicatorsLabel?: string\n}\n\nexport interface GrCarouselEmits {\n (e: 'update:modelValue', index: number): void\n}\n\nconst props = withDefaults(defineProps<GrCarouselProps>(), {\n modelValue: undefined,\n autoplay: false,\n autoplayInterval: 5000,\n loop: true,\n // Дефолт живёт в резолвере: Vue подставил бы свой раньше `GrConfigProvider`,\n // и «пользователь передал» стало бы неотличимо от «сработал дефолт».\n indicators: undefined,\n tone: undefined,\n activationMode: undefined,\n arrows: true,\n swipe: true,\n landmark: false,\n ariaLabel: undefined,\n ariaLabelledby: undefined,\n prevLabel: undefined,\n nextLabel: undefined,\n playLabel: undefined,\n pauseLabel: undefined,\n indicatorsLabel: undefined,\n})\n\nconst emit = defineEmits<GrCarouselEmits>()\n\ndefineSlots<{\n /** Кадры ленты — `GrCarouselSlide`. */\n default?: () => unknown\n /**\n * Своя иконка кнопки «назад». Заменяет содержимое кнопки, но не её саму:\n * имя, `aria-disabled` и поведение на краю остаются за компонентом.\n */\n prev?: (props: { disabled: boolean }) => unknown\n /** Своя иконка кнопки «вперёд». */\n next?: (props: { disabled: boolean }) => unknown\n}>()\n\nconst { t } = useGranularityTranslations()\nconst { announce } = useAnnouncer()\n\nconst resolvedIndicators = useGrComponentProp('GrCarousel', 'indicators', () => props.indicators, 'dots')\nconst resolvedActivation = useGrComponentProp('GrCarousel', 'activationMode', () => props.activationMode, 'automatic')\nconst resolvedTone = useGrComponentProp('GrCarousel', 'tone', () => props.tone, 'primary')\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst viewportEl = ref<HTMLElement | null>(null)\nconst indicatorsEl = ref<HTMLElement | null>(null)\nconst tabRefs = ref<(HTMLElement | null)[]>([])\n\nconst entries = ref<GrCarouselSlideEntry[]>([])\nconst total = computed(() => entries.value.length)\n\nlet reorderScheduled = false\n\n/**\n * Порядок регистрации совпадает с документом только при первом монтировании:\n * кадр, появившийся посреди ленты позже, встал бы в конец, и переключатели\n * разъехались бы с лентой. Пересортировка откладывается на такт — к этому\n * моменту узлы уже в DOM.\n */\nfunction scheduleReorder(): void {\n if (reorderScheduled || typeof document === 'undefined')\n return\n\n reorderScheduled = true\n void nextTick(() => {\n reorderScheduled = false\n entries.value = [...entries.value].sort((left, right) => {\n const a = left.el()\n const b = right.el()\n if (!a || !b)\n return 0\n return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1\n })\n })\n}\n\nfunction register(entry: GrCarouselSlideEntry): () => void {\n entries.value.push(entry)\n scheduleReorder()\n\n return () => {\n // Поиск по `id`, а не по ссылке: пересортировка выше пересобирает массив\n // через spread реактивного прокси, и элементы в нём — уже прокси, а не те\n // объекты, что пришли в `register`. `indexOf` по исходной ссылке вернул бы\n // `-1`, и кадр остался бы в полосе после размонтирования.\n const index = entries.value.findIndex(item => item.id === entry.id)\n if (index >= 0)\n entries.value.splice(index, 1)\n scheduleReorder()\n }\n}\n\nconst internalIndex = ref(0)\nconst currentIndex = computed(() => clampIndex(props.modelValue ?? internalIndex.value, total.value))\n\nfunction setIndex(next: number): void {\n const value = clampIndex(next, total.value)\n if (value === currentIndex.value)\n return\n\n internalIndex.value = value\n emit('update:modelValue', value)\n}\n\nconst showIndicators = computed(() => resolvedIndicators.value !== 'none' && total.value > 1)\nconst showArrows = computed(() => props.arrows && total.value > 1)\nconst isThumbnails = computed(() => resolvedIndicators.value === 'thumbnails')\n\nconst atStart = computed(() => !props.loop && currentIndex.value <= 0)\nconst atEnd = computed(() => !props.loop && currentIndex.value >= total.value - 1)\n\nconst reducedMotion = ref(false)\nconst hovered = ref(false)\nconst documentHidden = ref(false)\n\n/** `null` — тумблер не трогали, и решает проп. */\nconst userIntent = ref<boolean | null>(null)\n\nconst playing = computed(() => total.value > 1\n && (userIntent.value ?? (props.autoplay && !reducedMotion.value)))\n\n/**\n * Живой регион читает кадр, только когда лента стоит: у движущейся карусели\n * объявление каждого кадра превратило бы страницу в неработающую.\n */\nconst liveMode = computed<'off' | 'polite'>(() => (playing.value ? 'off' : 'polite'))\n\nconst swipeGesture = useCarouselSwipe({\n disabled: () => !props.swipe || total.value <= 1,\n viewport: () => viewportEl.value,\n atEdge: direction => (direction === 1 ? atEnd.value : atStart.value),\n onStart: stopAutoplay,\n onSwipe: (direction) => {\n void commitUserNavigation(stepIndex(currentIndex.value, direction, total.value, props.loop))\n },\n})\n\nconst autoplayTimer = useCarouselAutoplay({\n interval: () => props.autoplayInterval,\n enabled: () => playing.value,\n paused: () => hovered.value || documentHidden.value || swipeGesture.isDragging.value,\n advance: () => {\n const next = stepIndex(currentIndex.value, 1, total.value, props.loop)\n // Упёрлись в край без `loop` — показ окончен, а не зациклился.\n if (next === currentIndex.value) {\n userIntent.value = false\n return\n }\n setIndex(next)\n },\n})\n\nfunction stopAutoplay(): void {\n if (!playing.value)\n return\n\n userIntent.value = false\n announce(t('gr.carousel.autoplayStopped', 'Automatic slide show stopped'))\n}\n\n/**\n * `aria-live` обязан стать `polite` **раньше**, чем поменяется содержимое:\n * иначе мутация случится, пока регион ещё `off`, и не прозвучит вовсе.\n */\nasync function commitUserNavigation(next: number): Promise<void> {\n if (playing.value) {\n stopAutoplay()\n await nextTick()\n }\n\n setIndex(next)\n}\n\nfunction toggleAutoplay(): void {\n if (playing.value) {\n stopAutoplay()\n return\n }\n\n userIntent.value = true\n autoplayTimer.restart()\n}\n\nfunction goTo(index: number): void {\n void commitUserNavigation(clampIndex(index, total.value))\n}\n\nfunction step(delta: 1 | -1): void {\n void commitUserNavigation(stepIndex(currentIndex.value, delta, total.value, props.loop))\n}\n\nconst roving = useRovingFocus<number>({\n items: () => entries.value.map((_, index) => index),\n elementFor: index => tabRefs.value[index],\n orientation: () => 'horizontal',\n // Кольцо полосы замкнуто независимо от `loop`: тот управляет стрелками и\n // показом, а выбрать любой кадр напрямую можно всегда.\n wrap: () => true,\n initialKey: () => (total.value > 0 ? currentIndex.value : undefined),\n // Прокрутка до фокуса, а не после: иначе браузер доскроллит по-своему и\n // полоса дёрнется дважды.\n beforeFocus: index => scrollTabIntoView(index),\n})\n\n// Лента укоротилась под текущим кадром: модель потребителя и картинка разошлись\n// бы молча, поэтому индекс доводится до края и эмитится.\nwatch(total, (count) => {\n if (count === 0) {\n roving.reset()\n return\n }\n\n const requested = props.modelValue ?? internalIndex.value\n const clamped = clampIndex(requested, count)\n if (clamped !== requested) {\n internalIndex.value = clamped\n emit('update:modelValue', clamped)\n }\n})\n\n/**\n * Прокрутка **полосы**, а не страницы.\n *\n * `scrollIntoView` двигает всех предков, включая документ: карусель, уехавшая\n * под сгиб, на каждом шаге автопрокрутки утаскивала бы страницу обратно к себе.\n * Поэтому позиция считается сама и присваивается `scrollLeft` полосы.\n */\nfunction scrollTabIntoView(index: number): void {\n const strip = indicatorsEl.value\n const tab = tabRefs.value[index]\n if (!strip || !tab)\n return\n\n const next = stripScrollLeft(\n strip.scrollLeft,\n strip.clientWidth,\n tab.offsetLeft,\n tab.offsetLeft + tab.offsetWidth,\n )\n\n if (next !== strip.scrollLeft)\n strip.scrollLeft = next\n}\n\nfunction setTabRef(el: Element | ComponentPublicInstance | null, index: number): void {\n tabRefs.value[index] = el as HTMLElement | null\n}\n\nfunction onIndicatorKeydown(event: KeyboardEvent): void {\n if (total.value === 0)\n return\n\n const current = roving.rovingKey.value ?? 0\n\n if ((event.key === 'Enter' || event.key === ' ') && resolvedActivation.value === 'manual') {\n event.preventDefault()\n void commitUserNavigation(current)\n return\n }\n\n // Стрелки, `Home`, `End` ведёт примитив. Выбор не вешается на его `onMove`:\n // клик тоже зовёт `focusKey`, и модель менялась бы дважды.\n if (!roving.handleNavigationKeys(event))\n return\n\n if (resolvedActivation.value !== 'manual')\n void commitUserNavigation(roving.rovingKey.value ?? current)\n}\n\nfunction onIndicatorClick(index: number): void {\n roving.setActive(index)\n void commitUserNavigation(index)\n}\n\nconst { onFocusIn, onFocusOut } = useFocusWithin(rootEl, { enter: stopAutoplay })\n\nconst stripOverflow = ref<GrScrollOverflow>('none')\nlet resizeObserver: ResizeObserver | null = null\nlet measureScheduled = false\n\nfunction measureOverflow(): void {\n const el = indicatorsEl.value\n\n if (!el || !isThumbnails.value) {\n stripOverflow.value = 'none'\n return\n }\n\n stripOverflow.value = resolveScrollOverflow(el.scrollLeft, el.scrollWidth, el.clientWidth)\n}\n\nfunction scheduleMeasure(): void {\n if (measureScheduled)\n return\n\n measureScheduled = true\n void nextTick(() => {\n measureScheduled = false\n measureOverflow()\n })\n}\n\nfunction onVisibilityChange(): void {\n documentHidden.value = document.hidden\n}\n\nonMounted(() => {\n // Браузерный API в теле `setup` разошёлся бы с серверным рендером.\n reducedMotion.value = typeof window !== 'undefined'\n && window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches === true\n\n document.addEventListener('visibilitychange', onVisibilityChange)\n scheduleMeasure()\n\n // На сервере и в jsdom `ResizeObserver` отсутствует — измерять там нечего.\n if (typeof ResizeObserver === 'undefined')\n return\n\n resizeObserver = new ResizeObserver(scheduleMeasure)\n if (indicatorsEl.value)\n resizeObserver.observe(indicatorsEl.value)\n})\n\nonBeforeUnmount(() => {\n document.removeEventListener('visibilitychange', onVisibilityChange)\n resizeObserver?.disconnect()\n resizeObserver = null\n})\n\nwatch([total, isThumbnails], scheduleMeasure)\n\n// Кадр мог смениться не фокусом — показом или свайпом; переключатель всё равно\n// обязан оказаться на виду.\nwatch(currentIndex, async (index) => {\n await nextTick()\n scrollTabIntoView(index)\n})\n\nfunction entryAt(index: number): GrCarouselSlideEntry | undefined {\n return entries.value[index]\n}\n\nfunction positionLabelAt(index: number): string {\n return t('gr.carousel.slidePosition', '{index} of {total}', {\n index: index + 1,\n total: total.value,\n })\n}\n\nfunction indicatorName(index: number): string {\n return entryAt(index)?.label() ?? positionLabelAt(index)\n}\n\nfunction tabDomId(index: number): string {\n return `${entryAt(index)?.id ?? index}-tab`\n}\n\nconst trackStyle = computed(() => {\n const style: Record<string, string> = {\n '--gr-carousel-index': String(currentIndex.value),\n '--gr-carousel-drag': `${swipeGesture.offset.value}px`,\n }\n\n // На время жеста лента идёт за пальцем один в один: переход догонял бы курсор.\n if (swipeGesture.isDragging.value)\n style.transition = 'none'\n\n return style\n})\n\nprovide(GR_CAROUSEL_CONTEXT, {\n register,\n indexOf: id => entries.value.findIndex(entry => entry.id === id),\n isCurrent: id => entries.value[currentIndex.value]?.id === id,\n total,\n slideRole: computed(() => (showIndicators.value ? 'tabpanel' : 'group')),\n slideRoledescription: computed(() => t('gr.carousel.slideRoledescription', 'slide')),\n tabIdFor: (id) => {\n if (!showIndicators.value)\n return undefined\n const index = entries.value.findIndex(entry => entry.id === id)\n return index >= 0 ? tabDomId(index) : undefined\n },\n positionLabel: (id) => {\n const index = entries.value.findIndex(entry => entry.id === id)\n return positionLabelAt(index >= 0 ? index : 0)\n },\n})\n\nif (__GR_DEV__) {\n watchEffect(() => {\n if (!props.ariaLabel && !props.ariaLabelledby) {\n console.warn(\n '[granularity] GrCarousel: у карусели нет доступного имени — передайте '\n + '`ariaLabel` или `ariaLabelledby`. `aria-roledescription` именем не является.',\n )\n }\n\n if (!props.arrows && resolvedIndicators.value === 'none' && total.value > 1) {\n console.warn(\n '[granularity] GrCarousel: `arrows: false` вместе с `indicators: \"none\"` '\n + 'не оставляет ни одного способа сменить кадр с клавиатуры.',\n )\n }\n\n if (isThumbnails.value && total.value > 0\n && entries.value.every(entry => !entry.hasThumbnail() && !entry.thumbnailSrc())) {\n console.warn(\n '[granularity] GrCarousel: `indicators=\"thumbnails\"`, но ни один слайд не дал '\n + 'миниатюру — задайте проп `thumbnail` или слот `#thumbnail` у `GrCarouselSlide`.',\n )\n }\n })\n}\n\ndefineExpose({\n /** Текущий кадр — единственный способ прочитать позицию без `v-model`. */\n index: computed(() => currentIndex.value),\n /** Число кадров: состав живёт в слоте, снаружи его посчитать нечем. */\n count: total,\n /** Идёт ли показ. Пауза под курсором его не выключает. */\n playing: computed(() => playing.value),\n next: () => step(1),\n prev: () => step(-1),\n goTo,\n play: () => {\n if (!playing.value)\n toggleAutoplay()\n },\n pause: stopAutoplay,\n})\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n data-gr-carousel\n :class=\"carouselRootBase\"\n :role=\"landmark ? 'region' : 'group'\"\n :aria-roledescription=\"t('gr.carousel.roledescription', 'carousel')\"\n :aria-label=\"ariaLabelledby ? undefined : ariaLabel\"\n :aria-labelledby=\"ariaLabelledby\"\n @mouseenter=\"hovered = true\"\n @mouseleave=\"hovered = false\"\n @focusin=\"onFocusIn\"\n @focusout=\"onFocusOut\"\n >\n <div\n ref=\"viewportEl\"\n data-gr-carousel-viewport\n :class=\"[carouselViewportBase, props.swipe ? carouselViewportSwipeClass : '']\"\n :aria-live=\"liveMode\"\n aria-atomic=\"true\"\n @pointerdown=\"swipeGesture.start\"\n >\n <div\n data-gr-carousel-track\n :class=\"carouselTrackBase\"\n :style=\"trackStyle\"\n >\n <slot />\n </div>\n\n <button\n v-if=\"props.autoplay && total > 1\"\n type=\"button\"\n data-gr-carousel-toggle\n :class=\"carouselToggleClass\"\n :aria-label=\"playing\n ? (pauseLabel ?? t('gr.carousel.pause', 'Stop automatic slide show'))\n : (playLabel ?? t('gr.carousel.play', 'Start automatic slide show'))\"\n @click=\"toggleAutoplay\"\n >\n <component :is=\"playing ? IconPause : IconPlay\" :class=\"carouselIconClass\" aria-hidden=\"true\" />\n </button>\n\n <button\n v-if=\"showArrows\"\n type=\"button\"\n data-gr-carousel-prev\n :class=\"grCarouselControlClass('prev', atStart)\"\n :aria-disabled=\"atStart ? 'true' : undefined\"\n :aria-label=\"prevLabel ?? t('gr.carousel.previous', 'Previous slide')\"\n @click=\"atStart ? undefined : step(-1)\"\n >\n <slot name=\"prev\" :disabled=\"atStart\">\n <IconChevronLeft :class=\"carouselIconClass\" aria-hidden=\"true\" />\n </slot>\n </button>\n\n <button\n v-if=\"showArrows\"\n type=\"button\"\n data-gr-carousel-next\n :class=\"grCarouselControlClass('next', atEnd)\"\n :aria-disabled=\"atEnd ? 'true' : undefined\"\n :aria-label=\"nextLabel ?? t('gr.carousel.next', 'Next slide')\"\n @click=\"atEnd ? undefined : step(1)\"\n >\n <slot name=\"next\" :disabled=\"atEnd\">\n <IconChevronRight :class=\"carouselIconClass\" aria-hidden=\"true\" />\n </slot>\n </button>\n </div>\n\n <div\n v-if=\"showIndicators\"\n ref=\"indicatorsEl\"\n role=\"tablist\"\n data-gr-carousel-indicators\n :data-variant=\"resolvedIndicators\"\n :data-overflow=\"stripOverflow\"\n :class=\"grCarouselIndicatorsClass(resolvedIndicators)\"\n :aria-label=\"indicatorsLabel ?? t('gr.carousel.indicators', 'Slides')\"\n @keydown=\"onIndicatorKeydown\"\n @scroll.passive=\"scheduleMeasure\"\n >\n <button\n v-for=\"(entry, index) in entries\"\n :id=\"tabDomId(index)\"\n :key=\"entry.id\"\n :ref=\"el => setTabRef(el, index)\"\n type=\"button\"\n role=\"tab\"\n data-gr-carousel-indicator\n :aria-selected=\"index === currentIndex ? 'true' : 'false'\"\n :aria-controls=\"entry.id\"\n :aria-label=\"indicatorName(index)\"\n :tabindex=\"roving.tabindexFor(index)\"\n :class=\"grCarouselIndicatorClass(resolvedIndicators, index === currentIndex, resolvedTone)\"\n @click=\"onIndicatorClick(index)\"\n >\n <component :is=\"entry.thumbnail\" v-if=\"isThumbnails && entry.hasThumbnail()\" />\n <img\n v-else-if=\"isThumbnails && entry.thumbnailSrc()\"\n :src=\"entry.thumbnailSrc()\"\n alt=\"\"\n loading=\"lazy\"\n decoding=\"async\"\n :class=\"carouselThumbImageClass\"\n >\n <span v-else-if=\"isThumbnails\" :class=\"carouselThumbFallbackClass\">{{ index + 1 }}</span>\n </button>\n </div>\n </div>\n</template>\n\n<style>\n[data-gr-carousel-track] {\n transform: translateX(calc(var(--gr-carousel-index, 0) * -100% + var(--gr-carousel-drag, 0px)));\n}\n\n/*\n * Индекс зеркалится, смещение жеста — нет: палец двигает ленту на столько же\n * физических пикселей независимо от направления письма.\n */\n[dir='rtl'] [data-gr-carousel-track] {\n transform: translateX(calc(var(--gr-carousel-index, 0) * 100% + var(--gr-carousel-drag, 0px)));\n}\n\n[data-gr-carousel-indicators][data-variant='thumbnails'] {\n /*\n * `scrollIntoView({ inline: 'nearest' })` прижимает миниатюру вплотную к краю.\n * Без отступа прокрутки фокус-кольцо оказалось бы ровно под затуханием.\n */\n scroll-padding-inline: var(--gr-carousel-thumbs-fade, 1.5rem);\n}\n\n[data-gr-carousel-indicators][data-overflow='start'] {\n --gr-carousel-thumbs-mask: linear-gradient(to right, transparent 0, #000 var(--gr-carousel-thumbs-fade, 1.5rem));\n}\n\n[data-gr-carousel-indicators][data-overflow='end'] {\n --gr-carousel-thumbs-mask: linear-gradient(to right, #000 calc(100% - var(--gr-carousel-thumbs-fade, 1.5rem)), transparent 100%);\n}\n\n[data-gr-carousel-indicators][data-overflow='both'] {\n --gr-carousel-thumbs-mask: linear-gradient(\n to right,\n transparent 0,\n #000 var(--gr-carousel-thumbs-fade, 1.5rem),\n #000 calc(100% - var(--gr-carousel-thumbs-fade, 1.5rem)),\n transparent 100%\n );\n}\n\n[dir='rtl'] [data-gr-carousel-indicators][data-overflow='start'] {\n --gr-carousel-thumbs-mask: linear-gradient(to left, transparent 0, #000 var(--gr-carousel-thumbs-fade, 1.5rem));\n}\n\n[dir='rtl'] [data-gr-carousel-indicators][data-overflow='end'] {\n --gr-carousel-thumbs-mask: linear-gradient(to left, #000 calc(100% - var(--gr-carousel-thumbs-fade, 1.5rem)), transparent 100%);\n}\n\n[data-gr-carousel-indicators]:is([data-overflow='start'], [data-overflow='end'], [data-overflow='both']) {\n -webkit-mask-image: var(--gr-carousel-thumbs-mask);\n mask-image: var(--gr-carousel-thumbs-mask);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, provide, ref, watch, watchEffect } from 'vue'\nimport type { ComponentPublicInstance } from 'vue'\n\nimport { useAnnouncer } from '../../composables/useAnnouncer'\nimport { useFocusWithin } from '../../composables/internal/useFocusWithin'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrComponentProp } from '../GrConfigProvider/context'\nimport { resolveScrollOverflow } from '../shared/scrollOverflow'\nimport type { GrScrollOverflow } from '../shared/scrollOverflow'\nimport { clampIndex, stepIndex, stripScrollLeft } from './carouselNavigation'\nimport { useCarouselAutoplay } from './composables/useCarouselAutoplay'\nimport { useCarouselSwipe } from './composables/useCarouselSwipe'\nimport { GR_CAROUSEL_CONTEXT } from './grCarouselContext'\nimport type { GrCarouselSlideEntry } from './grCarouselContext'\nimport {\n carouselIconClass,\n carouselRootBase,\n carouselThumbFallbackClass,\n carouselThumbImageClass,\n carouselToggleClass,\n carouselTrackBase,\n carouselViewportBase,\n carouselViewportSwipeClass,\n grCarouselControlClass,\n grCarouselIndicatorClass,\n grCarouselIndicatorsClass,\n} from './grCarouselStyles'\nimport type { GrCarouselActivationMode, GrCarouselIndicators } from './grCarouselStyles'\nimport type { GrTone } from '../shared/tones'\n\nimport IconChevronLeft from '~icons/lucide/chevron-left'\nimport IconChevronRight from '~icons/lucide/chevron-right'\nimport IconPause from '~icons/lucide/pause'\nimport IconPlay from '~icons/lucide/play'\n\nexport interface GrCarouselProps {\n /** Индекс текущего кадра. Не передан — лента ведёт позицию сама. */\n modelValue?: number\n /** Запустить показ. Под `prefers-reduced-motion` не стартует — тумблер остаётся. */\n autoplay?: boolean\n /** Пауза между кадрами, мс. */\n autoplayInterval?: number\n /** Замкнуть ленту. Выключено — стрелки на краях гаснут. */\n loop?: boolean\n /** Вид переключателя кадров. `none` меняет и роль самих слайдов. */\n indicators?: GrCarouselIndicators\n /** Тон текущего переключателя по общей шкале пакета. */\n tone?: GrTone\n /** Листает ли стрелка по переключателям сразу или только двигает фокус. */\n activationMode?: GrCarouselActivationMode\n /** Стрелки «назад/вперёд». */\n arrows?: boolean\n /** Листание протяжкой указателя. */\n swipe?: boolean\n /** Сделать карусель ориентиром страницы (`role=\"region\"`). */\n landmark?: boolean\n /** Имя карусели. APG требует его: `aria-roledescription` именем не считается. */\n ariaLabel?: string\n /** Имя из заголовка на странице. Сильнее `ariaLabel`. */\n ariaLabelledby?: string\n prevLabel?: string\n nextLabel?: string\n playLabel?: string\n pauseLabel?: string\n /** Имя полосы переключателей. */\n indicatorsLabel?: string\n}\n\nexport interface GrCarouselEmits {\n (e: 'update:modelValue', index: number): void\n}\n\nconst props = withDefaults(defineProps<GrCarouselProps>(), {\n modelValue: undefined,\n autoplay: false,\n autoplayInterval: 5000,\n loop: true,\n // Дефолт живёт в резолвере: Vue подставил бы свой раньше `GrConfigProvider`,\n // и «пользователь передал» стало бы неотличимо от «сработал дефолт».\n indicators: undefined,\n tone: undefined,\n activationMode: undefined,\n arrows: true,\n swipe: true,\n landmark: false,\n ariaLabel: undefined,\n ariaLabelledby: undefined,\n prevLabel: undefined,\n nextLabel: undefined,\n playLabel: undefined,\n pauseLabel: undefined,\n indicatorsLabel: undefined,\n})\n\nconst emit = defineEmits<GrCarouselEmits>()\n\ndefineSlots<{\n /** Кадры ленты — `GrCarouselSlide`. */\n default?: () => unknown\n /**\n * Своя иконка кнопки «назад». Заменяет содержимое кнопки, но не её саму:\n * имя, `aria-disabled` и поведение на краю остаются за компонентом.\n */\n prev?: (props: { disabled: boolean }) => unknown\n /** Своя иконка кнопки «вперёд». */\n next?: (props: { disabled: boolean }) => unknown\n}>()\n\nconst { t } = useGranularityTranslations()\nconst { announce } = useAnnouncer()\n\nconst resolvedIndicators = useGrComponentProp('GrCarousel', 'indicators', () => props.indicators, 'dots')\nconst resolvedActivation = useGrComponentProp('GrCarousel', 'activationMode', () => props.activationMode, 'automatic')\nconst resolvedTone = useGrComponentProp('GrCarousel', 'tone', () => props.tone, 'primary')\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst viewportEl = ref<HTMLElement | null>(null)\nconst indicatorsEl = ref<HTMLElement | null>(null)\nconst tabRefs = ref<(HTMLElement | null)[]>([])\n\nconst entries = ref<GrCarouselSlideEntry[]>([])\nconst total = computed(() => entries.value.length)\n\nlet reorderScheduled = false\n\n/**\n * Порядок регистрации совпадает с документом только при первом монтировании:\n * кадр, появившийся посреди ленты позже, встал бы в конец, и переключатели\n * разъехались бы с лентой. Пересортировка откладывается на такт — к этому\n * моменту узлы уже в DOM.\n */\nfunction scheduleReorder(): void {\n if (reorderScheduled || typeof document === 'undefined')\n return\n\n reorderScheduled = true\n void nextTick(() => {\n reorderScheduled = false\n entries.value = [...entries.value].sort((left, right) => {\n const a = left.el()\n const b = right.el()\n if (!a || !b)\n return 0\n return a.compareDocumentPosition(b) & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1\n })\n })\n}\n\nfunction register(entry: GrCarouselSlideEntry): () => void {\n entries.value.push(entry)\n scheduleReorder()\n\n return () => {\n // Поиск по `id`, а не по ссылке: пересортировка выше пересобирает массив\n // через spread реактивного прокси, и элементы в нём — уже прокси, а не те\n // объекты, что пришли в `register`. `indexOf` по исходной ссылке вернул бы\n // `-1`, и кадр остался бы в полосе после размонтирования.\n const index = entries.value.findIndex(item => item.id === entry.id)\n if (index >= 0)\n entries.value.splice(index, 1)\n scheduleReorder()\n }\n}\n\nconst internalIndex = ref(0)\nconst currentIndex = computed(() => clampIndex(props.modelValue ?? internalIndex.value, total.value))\n\nfunction setIndex(next: number): void {\n const value = clampIndex(next, total.value)\n if (value === currentIndex.value)\n return\n\n internalIndex.value = value\n emit('update:modelValue', value)\n}\n\nconst showIndicators = computed(() => resolvedIndicators.value !== 'none' && total.value > 1)\nconst showArrows = computed(() => props.arrows && total.value > 1)\nconst isThumbnails = computed(() => resolvedIndicators.value === 'thumbnails')\n\nconst atStart = computed(() => !props.loop && currentIndex.value <= 0)\nconst atEnd = computed(() => !props.loop && currentIndex.value >= total.value - 1)\n\nconst reducedMotion = ref(false)\nconst hovered = ref(false)\nconst documentHidden = ref(false)\n\n/** `null` — тумблер не трогали, и решает проп. */\nconst userIntent = ref<boolean | null>(null)\n\nconst playing = computed(() => total.value > 1\n && (userIntent.value ?? (props.autoplay && !reducedMotion.value)))\n\n/**\n * Живой регион читает кадр, только когда лента стоит: у движущейся карусели\n * объявление каждого кадра превратило бы страницу в неработающую.\n */\nconst liveMode = computed<'off' | 'polite'>(() => (playing.value ? 'off' : 'polite'))\n\nconst swipeGesture = useCarouselSwipe({\n disabled: () => !props.swipe || total.value <= 1,\n viewport: () => viewportEl.value,\n atEdge: direction => (direction === 1 ? atEnd.value : atStart.value),\n onStart: stopAutoplay,\n onSwipe: (direction) => {\n void commitUserNavigation(stepIndex(currentIndex.value, direction, total.value, props.loop))\n },\n})\n\nconst autoplayTimer = useCarouselAutoplay({\n interval: () => props.autoplayInterval,\n enabled: () => playing.value,\n paused: () => hovered.value || documentHidden.value || swipeGesture.isDragging.value,\n advance: () => {\n const next = stepIndex(currentIndex.value, 1, total.value, props.loop)\n // Упёрлись в край без `loop` — показ окончен, а не зациклился.\n if (next === currentIndex.value) {\n userIntent.value = false\n return\n }\n setIndex(next)\n },\n})\n\nfunction stopAutoplay(): void {\n if (!playing.value)\n return\n\n userIntent.value = false\n announce(t('gr.carousel.autoplayStopped', 'Automatic slide show stopped'))\n}\n\n/**\n * `aria-live` обязан стать `polite` **раньше**, чем поменяется содержимое:\n * иначе мутация случится, пока регион ещё `off`, и не прозвучит вовсе.\n */\nasync function commitUserNavigation(next: number): Promise<void> {\n if (playing.value) {\n stopAutoplay()\n await nextTick()\n }\n\n setIndex(next)\n}\n\nfunction toggleAutoplay(): void {\n if (playing.value) {\n stopAutoplay()\n return\n }\n\n userIntent.value = true\n autoplayTimer.restart()\n}\n\nfunction goTo(index: number): void {\n void commitUserNavigation(clampIndex(index, total.value))\n}\n\nfunction step(delta: 1 | -1): void {\n void commitUserNavigation(stepIndex(currentIndex.value, delta, total.value, props.loop))\n}\n\nconst roving = useRovingFocus<number>({\n items: () => entries.value.map((_, index) => index),\n elementFor: index => tabRefs.value[index],\n orientation: () => 'horizontal',\n // Кольцо полосы замкнуто независимо от `loop`: тот управляет стрелками и\n // показом, а выбрать любой кадр напрямую можно всегда.\n wrap: () => true,\n initialKey: () => (total.value > 0 ? currentIndex.value : undefined),\n // Прокрутка до фокуса, а не после: иначе браузер доскроллит по-своему и\n // полоса дёрнется дважды.\n beforeFocus: index => scrollTabIntoView(index),\n})\n\n// Лента укоротилась под текущим кадром: модель потребителя и картинка разошлись\n// бы молча, поэтому индекс доводится до края и эмитится.\nwatch(total, (count) => {\n if (count === 0) {\n roving.reset()\n return\n }\n\n const requested = props.modelValue ?? internalIndex.value\n const clamped = clampIndex(requested, count)\n if (clamped !== requested) {\n internalIndex.value = clamped\n emit('update:modelValue', clamped)\n }\n})\n\n/**\n * Прокрутка **полосы**, а не страницы.\n *\n * `scrollIntoView` двигает всех предков, включая документ: карусель, уехавшая\n * под сгиб, на каждом шаге автопрокрутки утаскивала бы страницу обратно к себе.\n * Поэтому позиция считается сама и присваивается `scrollLeft` полосы.\n */\nfunction scrollTabIntoView(index: number): void {\n const strip = indicatorsEl.value\n const tab = tabRefs.value[index]\n if (!strip || !tab)\n return\n\n const next = stripScrollLeft(\n strip.scrollLeft,\n strip.clientWidth,\n tab.offsetLeft,\n tab.offsetLeft + tab.offsetWidth,\n )\n\n if (next !== strip.scrollLeft)\n strip.scrollLeft = next\n}\n\nfunction setTabRef(el: Element | ComponentPublicInstance | null, index: number): void {\n tabRefs.value[index] = el as HTMLElement | null\n}\n\nfunction onIndicatorKeydown(event: KeyboardEvent): void {\n if (total.value === 0)\n return\n\n const current = roving.rovingKey.value ?? 0\n\n if ((event.key === 'Enter' || event.key === ' ') && resolvedActivation.value === 'manual') {\n event.preventDefault()\n void commitUserNavigation(current)\n return\n }\n\n // Стрелки, `Home`, `End` ведёт примитив. Выбор не вешается на его `onMove`:\n // клик тоже зовёт `focusKey`, и модель менялась бы дважды.\n if (!roving.handleNavigationKeys(event))\n return\n\n if (resolvedActivation.value !== 'manual')\n void commitUserNavigation(roving.rovingKey.value ?? current)\n}\n\nfunction onIndicatorClick(index: number): void {\n roving.setActive(index)\n void commitUserNavigation(index)\n}\n\nconst { onFocusIn, onFocusOut } = useFocusWithin(rootEl, { enter: stopAutoplay })\n\nconst stripOverflow = ref<GrScrollOverflow>('none')\nlet resizeObserver: ResizeObserver | null = null\nlet measureScheduled = false\n\nfunction measureOverflow(): void {\n const el = indicatorsEl.value\n\n if (!el || !isThumbnails.value) {\n stripOverflow.value = 'none'\n return\n }\n\n stripOverflow.value = resolveScrollOverflow(el.scrollLeft, el.scrollWidth, el.clientWidth)\n}\n\nfunction scheduleMeasure(): void {\n if (measureScheduled)\n return\n\n measureScheduled = true\n void nextTick(() => {\n measureScheduled = false\n measureOverflow()\n })\n}\n\nfunction onVisibilityChange(): void {\n documentHidden.value = document.hidden\n}\n\nonMounted(() => {\n // Браузерный API в теле `setup` разошёлся бы с серверным рендером.\n reducedMotion.value = typeof window !== 'undefined'\n && window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches === true\n\n document.addEventListener('visibilitychange', onVisibilityChange)\n scheduleMeasure()\n\n // На сервере и в jsdom `ResizeObserver` отсутствует — измерять там нечего.\n if (typeof ResizeObserver === 'undefined')\n return\n\n resizeObserver = new ResizeObserver(scheduleMeasure)\n if (indicatorsEl.value)\n resizeObserver.observe(indicatorsEl.value)\n})\n\nonBeforeUnmount(() => {\n document.removeEventListener('visibilitychange', onVisibilityChange)\n resizeObserver?.disconnect()\n resizeObserver = null\n})\n\nwatch([total, isThumbnails], scheduleMeasure)\n\n// Кадр мог смениться не фокусом — показом или свайпом; переключатель всё равно\n// обязан оказаться на виду.\nwatch(currentIndex, async (index) => {\n await nextTick()\n scrollTabIntoView(index)\n})\n\nfunction entryAt(index: number): GrCarouselSlideEntry | undefined {\n return entries.value[index]\n}\n\nfunction positionLabelAt(index: number): string {\n return t('gr.carousel.slidePosition', '{index} of {total}', {\n index: index + 1,\n total: total.value,\n })\n}\n\nfunction indicatorName(index: number): string {\n return entryAt(index)?.label() ?? positionLabelAt(index)\n}\n\nfunction tabDomId(index: number): string {\n return `${entryAt(index)?.id ?? index}-tab`\n}\n\nconst trackStyle = computed(() => {\n const style: Record<string, string> = {\n '--gr-carousel-index': String(currentIndex.value),\n '--gr-carousel-drag': `${swipeGesture.offset.value}px`,\n }\n\n // На время жеста лента идёт за пальцем один в один: переход догонял бы курсор.\n if (swipeGesture.isDragging.value)\n style.transition = 'none'\n\n return style\n})\n\nprovide(GR_CAROUSEL_CONTEXT, {\n register,\n indexOf: id => entries.value.findIndex(entry => entry.id === id),\n isCurrent: id => entries.value[currentIndex.value]?.id === id,\n total,\n slideRole: computed(() => (showIndicators.value ? 'tabpanel' : 'group')),\n slideRoledescription: computed(() => t('gr.carousel.slideRoledescription', 'slide')),\n tabIdFor: (id) => {\n if (!showIndicators.value)\n return undefined\n const index = entries.value.findIndex(entry => entry.id === id)\n return index >= 0 ? tabDomId(index) : undefined\n },\n positionLabel: (id) => {\n const index = entries.value.findIndex(entry => entry.id === id)\n return positionLabelAt(index >= 0 ? index : 0)\n },\n})\n\nif (__GR_DEV__) {\n watchEffect(() => {\n if (!props.ariaLabel && !props.ariaLabelledby) {\n console.warn(\n '[granularity] GrCarousel: у карусели нет доступного имени — передайте '\n + '`ariaLabel` или `ariaLabelledby`. `aria-roledescription` именем не является.',\n )\n }\n\n if (!props.arrows && resolvedIndicators.value === 'none' && total.value > 1) {\n console.warn(\n '[granularity] GrCarousel: `arrows: false` вместе с `indicators: \"none\"` '\n + 'не оставляет ни одного способа сменить кадр с клавиатуры.',\n )\n }\n\n if (isThumbnails.value && total.value > 0\n && entries.value.every(entry => !entry.hasThumbnail() && !entry.thumbnailSrc())) {\n console.warn(\n '[granularity] GrCarousel: `indicators=\"thumbnails\"`, но ни один слайд не дал '\n + 'миниатюру — задайте проп `thumbnail` или слот `#thumbnail` у `GrCarouselSlide`.',\n )\n }\n })\n}\n\ndefineExpose({\n /** Текущий кадр — единственный способ прочитать позицию без `v-model`. */\n index: computed(() => currentIndex.value),\n /** Число кадров: состав живёт в слоте, снаружи его посчитать нечем. */\n count: total,\n /** Идёт ли показ. Пауза под курсором его не выключает. */\n playing: computed(() => playing.value),\n next: () => step(1),\n prev: () => step(-1),\n goTo,\n play: () => {\n if (!playing.value)\n toggleAutoplay()\n },\n pause: stopAutoplay,\n})\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n data-gr-carousel\n :class=\"carouselRootBase\"\n :role=\"landmark ? 'region' : 'group'\"\n :aria-roledescription=\"t('gr.carousel.roledescription', 'carousel')\"\n :aria-label=\"ariaLabelledby ? undefined : ariaLabel\"\n :aria-labelledby=\"ariaLabelledby\"\n @mouseenter=\"hovered = true\"\n @mouseleave=\"hovered = false\"\n @focusin=\"onFocusIn\"\n @focusout=\"onFocusOut\"\n >\n <div\n ref=\"viewportEl\"\n data-gr-carousel-viewport\n :class=\"[carouselViewportBase, props.swipe ? carouselViewportSwipeClass : '']\"\n :aria-live=\"liveMode\"\n aria-atomic=\"true\"\n @pointerdown=\"swipeGesture.start\"\n >\n <div\n data-gr-carousel-track\n :class=\"carouselTrackBase\"\n :style=\"trackStyle\"\n >\n <slot />\n </div>\n\n <button\n v-if=\"props.autoplay && total > 1\"\n type=\"button\"\n data-gr-carousel-toggle\n :class=\"carouselToggleClass\"\n :aria-label=\"playing\n ? (pauseLabel ?? t('gr.carousel.pause', 'Stop automatic slide show'))\n : (playLabel ?? t('gr.carousel.play', 'Start automatic slide show'))\"\n @click=\"toggleAutoplay\"\n >\n <component :is=\"playing ? IconPause : IconPlay\" :class=\"carouselIconClass\" aria-hidden=\"true\" />\n </button>\n\n <button\n v-if=\"showArrows\"\n type=\"button\"\n data-gr-carousel-prev\n :class=\"grCarouselControlClass('prev', atStart)\"\n :aria-disabled=\"atStart ? 'true' : undefined\"\n :aria-label=\"prevLabel ?? t('gr.carousel.previous', 'Previous slide')\"\n @click=\"atStart ? undefined : step(-1)\"\n >\n <slot name=\"prev\" :disabled=\"atStart\">\n <IconChevronLeft :class=\"carouselIconClass\" aria-hidden=\"true\" />\n </slot>\n </button>\n\n <button\n v-if=\"showArrows\"\n type=\"button\"\n data-gr-carousel-next\n :class=\"grCarouselControlClass('next', atEnd)\"\n :aria-disabled=\"atEnd ? 'true' : undefined\"\n :aria-label=\"nextLabel ?? t('gr.carousel.next', 'Next slide')\"\n @click=\"atEnd ? undefined : step(1)\"\n >\n <slot name=\"next\" :disabled=\"atEnd\">\n <IconChevronRight :class=\"carouselIconClass\" aria-hidden=\"true\" />\n </slot>\n </button>\n </div>\n\n <div\n v-if=\"showIndicators\"\n ref=\"indicatorsEl\"\n role=\"tablist\"\n data-gr-carousel-indicators\n :data-variant=\"resolvedIndicators\"\n :data-overflow=\"stripOverflow\"\n :class=\"grCarouselIndicatorsClass(resolvedIndicators)\"\n :aria-label=\"indicatorsLabel ?? t('gr.carousel.indicators', 'Slides')\"\n @keydown=\"onIndicatorKeydown\"\n @scroll.passive=\"scheduleMeasure\"\n >\n <button\n v-for=\"(entry, index) in entries\"\n :id=\"tabDomId(index)\"\n :key=\"entry.id\"\n :ref=\"el => setTabRef(el, index)\"\n type=\"button\"\n role=\"tab\"\n data-gr-carousel-indicator\n :aria-selected=\"index === currentIndex ? 'true' : 'false'\"\n :aria-controls=\"entry.id\"\n :aria-label=\"indicatorName(index)\"\n :tabindex=\"roving.tabindexFor(index)\"\n :class=\"grCarouselIndicatorClass(resolvedIndicators, index === currentIndex, resolvedTone)\"\n @click=\"onIndicatorClick(index)\"\n >\n <component :is=\"entry.thumbnail\" v-if=\"isThumbnails && entry.hasThumbnail()\" />\n <img\n v-else-if=\"isThumbnails && entry.thumbnailSrc()\"\n :src=\"entry.thumbnailSrc()\"\n alt=\"\"\n loading=\"lazy\"\n decoding=\"async\"\n :class=\"carouselThumbImageClass\"\n >\n <span v-else-if=\"isThumbnails\" :class=\"carouselThumbFallbackClass\">{{ index + 1 }}</span>\n </button>\n </div>\n </div>\n</template>\n\n<style>\n[data-gr-carousel-track] {\n transform: translateX(calc(var(--gr-carousel-index, 0) * -100% + var(--gr-carousel-drag, 0px)));\n}\n\n/*\n * Индекс зеркалится, смещение жеста — нет: палец двигает ленту на столько же\n * физических пикселей независимо от направления письма.\n */\n[dir='rtl'] [data-gr-carousel-track] {\n transform: translateX(calc(var(--gr-carousel-index, 0) * 100% + var(--gr-carousel-drag, 0px)));\n}\n\n[data-gr-carousel-indicators][data-variant='thumbnails'] {\n /*\n * `scrollIntoView({ inline: 'nearest' })` прижимает миниатюру вплотную к краю.\n * Без отступа прокрутки фокус-кольцо оказалось бы ровно под затуханием.\n */\n scroll-padding-inline: var(--gr-carousel-thumbs-fade, 1.5rem);\n}\n\n[data-gr-carousel-indicators][data-overflow='start'] {\n --gr-carousel-thumbs-mask: linear-gradient(to right, transparent 0, #000 var(--gr-carousel-thumbs-fade, 1.5rem));\n}\n\n[data-gr-carousel-indicators][data-overflow='end'] {\n --gr-carousel-thumbs-mask: linear-gradient(to right, #000 calc(100% - var(--gr-carousel-thumbs-fade, 1.5rem)), transparent 100%);\n}\n\n[data-gr-carousel-indicators][data-overflow='both'] {\n --gr-carousel-thumbs-mask: linear-gradient(\n to right,\n transparent 0,\n #000 var(--gr-carousel-thumbs-fade, 1.5rem),\n #000 calc(100% - var(--gr-carousel-thumbs-fade, 1.5rem)),\n transparent 100%\n );\n}\n\n[dir='rtl'] [data-gr-carousel-indicators][data-overflow='start'] {\n --gr-carousel-thumbs-mask: linear-gradient(to left, transparent 0, #000 var(--gr-carousel-thumbs-fade, 1.5rem));\n}\n\n[dir='rtl'] [data-gr-carousel-indicators][data-overflow='end'] {\n --gr-carousel-thumbs-mask: linear-gradient(to left, #000 calc(100% - var(--gr-carousel-thumbs-fade, 1.5rem)), transparent 100%);\n}\n\n[data-gr-carousel-indicators]:is([data-overflow='start'], [data-overflow='end'], [data-overflow='both']) {\n -webkit-mask-image: var(--gr-carousel-thumbs-mask);\n mask-image: var(--gr-carousel-thumbs-mask);\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, inject, onBeforeUnmount, ref, useId, useSlots } from 'vue'\n\nimport { GR_CAROUSEL_CONTEXT } from './grCarouselContext'\nimport { carouselSlideBase } from './grCarouselStyles'\n\nexport interface GrCarouselSlideProps {\n /** Имя слайда: подпись его переключателя и доступное имя самого кадра. */\n label?: string\n /** URL миниатюры для `indicators=\"thumbnails\"`. Слот `#thumbnail` сильнее. */\n thumbnail?: string\n}\n\nconst props = withDefaults(defineProps<GrCarouselSlideProps>(), {\n label: undefined,\n thumbnail: undefined,\n})\n\ndefineSlots<{\n /** Содержимое кадра. */\n default?: () => unknown\n /**\n * Своя разметка миниатюры. Рендерит её полоса переключателей `GrCarousel`,\n * поэтому `<slot name=\"thumbnail\">` здесь не стоит: узел уезжает наверх при\n * регистрации. Появится он тут — понадобится запись в `DOMAIN_SLOTS` гейта\n * `slotContract.test.ts`.\n */\n thumbnail?: () => unknown\n}>()\n\nconst carousel = inject(GR_CAROUSEL_CONTEXT)\n\nif (!carousel) {\n throw new Error('GrCarouselSlide must be used inside GrCarousel')\n}\n\nconst slots = useSlots()\nconst slideId = useId()\nconst rootEl = ref<HTMLElement | null>(null)\n\n// Ссылка создаётся один раз: `:is` со стабильной ссылкой не перемонтирует\n// миниатюру на каждый рендер полосы.\nfunction renderThumbnail(): unknown {\n return slots.thumbnail?.()\n}\n\nonBeforeUnmount(carousel.register({\n id: slideId,\n label: () => props.label,\n thumbnailSrc: () => props.thumbnail,\n hasThumbnail: () => Boolean(slots.thumbnail),\n thumbnail: renderThumbnail,\n el: () => rootEl.value,\n}))\n\nconst isCurrent = computed(() => carousel.isCurrent(slideId))\nconst labelledBy = computed(() => carousel.tabIdFor(slideId))\nconst accessibleName = computed(() => props.label ?? carousel.positionLabel(slideId))\n</script>\n\n<template>\n <div\n :id=\"slideId\"\n ref=\"rootEl\"\n data-gr-carousel-slide\n :class=\"carouselSlideBase\"\n :role=\"carousel!.slideRole.value\"\n :aria-roledescription=\"carousel!.slideRoledescription.value\"\n :aria-labelledby=\"labelledBy\"\n :aria-label=\"labelledBy ? undefined : accessibleName\"\n :inert=\"isCurrent ? undefined : true\"\n >\n <slot />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, inject, onBeforeUnmount, ref, useId, useSlots } from 'vue'\n\nimport { GR_CAROUSEL_CONTEXT } from './grCarouselContext'\nimport { carouselSlideBase } from './grCarouselStyles'\n\nexport interface GrCarouselSlideProps {\n /** Имя слайда: подпись его переключателя и доступное имя самого кадра. */\n label?: string\n /** URL миниатюры для `indicators=\"thumbnails\"`. Слот `#thumbnail` сильнее. */\n thumbnail?: string\n}\n\nconst props = withDefaults(defineProps<GrCarouselSlideProps>(), {\n label: undefined,\n thumbnail: undefined,\n})\n\ndefineSlots<{\n /** Содержимое кадра. */\n default?: () => unknown\n /**\n * Своя разметка миниатюры. Рендерит её полоса переключателей `GrCarousel`,\n * поэтому `<slot name=\"thumbnail\">` здесь не стоит: узел уезжает наверх при\n * регистрации. Появится он тут — понадобится запись в `DOMAIN_SLOTS` гейта\n * `slotContract.test.ts`.\n */\n thumbnail?: () => unknown\n}>()\n\nconst carousel = inject(GR_CAROUSEL_CONTEXT)\n\nif (!carousel) {\n throw new Error('GrCarouselSlide must be used inside GrCarousel')\n}\n\nconst slots = useSlots()\nconst slideId = useId()\nconst rootEl = ref<HTMLElement | null>(null)\n\n// Ссылка создаётся один раз: `:is` со стабильной ссылкой не перемонтирует\n// миниатюру на каждый рендер полосы.\nfunction renderThumbnail(): unknown {\n return slots.thumbnail?.()\n}\n\nonBeforeUnmount(carousel.register({\n id: slideId,\n label: () => props.label,\n thumbnailSrc: () => props.thumbnail,\n hasThumbnail: () => Boolean(slots.thumbnail),\n thumbnail: renderThumbnail,\n el: () => rootEl.value,\n}))\n\nconst isCurrent = computed(() => carousel.isCurrent(slideId))\nconst labelledBy = computed(() => carousel.tabIdFor(slideId))\nconst accessibleName = computed(() => props.label ?? carousel.positionLabel(slideId))\n</script>\n\n<template>\n <div\n :id=\"slideId\"\n ref=\"rootEl\"\n data-gr-carousel-slide\n :class=\"carouselSlideBase\"\n :role=\"carousel!.slideRole.value\"\n :aria-roledescription=\"carousel!.slideRoledescription.value\"\n :aria-labelledby=\"labelledBy\"\n :aria-label=\"labelledBy ? undefined : accessibleName\"\n :inert=\"isCurrent ? undefined : true\"\n >\n <slot />\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { n as GrCarousel_default, t as GrCarouselSlide_default } from "./chunks/GrCarousel-ByuughtI.js";
|
|
2
|
+
import { n as grCarouselSafelist, t as grCarouselConfig } from "../../chunks/config-BXRmsn3t.js";
|
|
3
|
+
export { GrCarousel_default as GrCarousel, GrCarousel_default as default, GrCarouselSlide_default as GrCarouselSlide, grCarouselConfig, grCarouselSafelist };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[data-gr-carousel-track]{transform:translateX(calc(var(--gr-carousel-index,0) * -100% + var(--gr-carousel-drag,0px)))}[dir=rtl] [data-gr-carousel-track]{transform:translateX(calc(var(--gr-carousel-index,0) * 100% + var(--gr-carousel-drag,0px)))}[data-gr-carousel-indicators][data-variant=thumbnails]{scroll-padding-inline:var(--gr-carousel-thumbs-fade,1.5rem)}[data-gr-carousel-indicators][data-overflow=start]{--gr-carousel-thumbs-mask:linear-gradient(to right, transparent 0, #000 var(--gr-carousel-thumbs-fade,1.5rem))}[data-gr-carousel-indicators][data-overflow=end]{--gr-carousel-thumbs-mask:linear-gradient(to right, #000 calc(100% - var(--gr-carousel-thumbs-fade,1.5rem)), transparent 100%)}[data-gr-carousel-indicators][data-overflow=both]{--gr-carousel-thumbs-mask:linear-gradient(to right, transparent 0, #000 var(--gr-carousel-thumbs-fade,1.5rem), #000 calc(100% - var(--gr-carousel-thumbs-fade,1.5rem)), transparent 100%)}[dir=rtl] [data-gr-carousel-indicators][data-overflow=start]{--gr-carousel-thumbs-mask:linear-gradient(to left, transparent 0, #000 var(--gr-carousel-thumbs-fade,1.5rem))}[dir=rtl] [data-gr-carousel-indicators][data-overflow=end]{--gr-carousel-thumbs-mask:linear-gradient(to left, #000 calc(100% - var(--gr-carousel-thumbs-fade,1.5rem)), transparent 100%)}[data-gr-carousel-indicators]:is([data-overflow=start],[data-overflow=end],[data-overflow=both]){-webkit-mask-image:var(--gr-carousel-thumbs-mask);mask-image:var(--gr-carousel-thumbs-mask)}
|
package/dist/components/GrColorPicker/chunks/{GrColorPicker-BwzRLs-i.js → GrColorPicker-9mTR-YZ_.js}
RENAMED
|
@@ -4,7 +4,7 @@ import { n as useGrFormFieldContext } from "../../../chunks/context-DdNIxa1w.js"
|
|
|
4
4
|
import { useGrFormControl } from "../../../composables/useGrFormControl.js";
|
|
5
5
|
import { t as GrInput_default } from "../../GrInput/chunks/GrInput-TFeBm3gQ.js";
|
|
6
6
|
import { t as GrPopover_default } from "../../GrPopover/chunks/GrPopover-CcWvr1ZA.js";
|
|
7
|
-
import { t as GrSlider_default } from "../../GrSlider/chunks/GrSlider-
|
|
7
|
+
import { t as GrSlider_default } from "../../GrSlider/chunks/GrSlider-CO8N9SF6.js";
|
|
8
8
|
import { a as grColorPickerPresetClass, c as previewClass, d as rowValueClass, f as swatchBaseClass, h as triggerValueClass, i as grColorPickerPanelClass, l as rowClass, m as triggerSwatchSizeBySize, o as grColorPickerTriggerClass, p as swatchFillClass, r as checkerClass, s as presetsGridClass, u as rowLabelClass } from "../../../chunks/config-DIDz_E5m.js";
|
|
9
9
|
import { Fragment, computed, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, isRef, mergeProps, nextTick, normalizeClass, normalizeStyle, openBlock, ref, renderList, toDisplayString, unref, useId, watch, withCtx, withKeys } from "vue";
|
|
10
10
|
import '../styles.css';//#region src/components/GrColorPicker/color.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GrColorPicker-BwzRLs-i.js","names":[],"sources":["../../../../src/components/GrColorPicker/color.ts","../../../../src/components/GrColorPicker/GrColorPicker.vue","../../../../src/components/GrColorPicker/GrColorPicker.vue"],"sourcesContent":["/**\n * Арифметика цвета для `GrColorPicker` — чистый модуль без Vue.\n *\n * Модель компонента — hex-строка (в такой форме цвет лежит в токенах темы),\n * а панель работает в HSL: у оттенка, насыщенности и светлоты есть шкала, по\n * которой можно вести слайдер, а у каналов RGB её нет.\n *\n * Свой парсер, а не `__tests__/cssContrast.ts`: тот — инфраструктура гейтов, и\n * тянуть тестовый модуль в поставляемый код нельзя.\n */\n\nexport type GrHsla = {\n /** Оттенок, 0…359 (круговой: 360 — это 0). */\n h: number\n /** Насыщенность, 0…100. */\n s: number\n /** Светлота, 0…100. */\n l: number\n /** Непрозрачность, 0…1. */\n a: number\n}\n\ntype Rgb = { r: number, g: number, b: number }\n\nconst HEX_PATTERN = /^#?([0-9a-f]{3,8})$/i\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max)\n}\n\n/**\n * Оттенок цикличен: 360° — тот же цвет, что 0°.\n *\n * Округления здесь нет намеренно: `#3b82f6` даёт 217.22°, и округлив его до\n * целого, обратное преобразование вернуло бы `#3b83f6` — компонент молча\n * переписывал бы чужой цвет при первом же открытии панели. Целые градусы\n * появляются там, где они и нужны, — на шкале слайдера (`normalizeHsla`).\n */\nfunction normalizeHue(hue: number): number {\n if (!Number.isFinite(hue))\n return 0\n return ((hue % 360) + 360) % 360\n}\n\n/** Короткая форма `#abc` разворачивается в `#aabbcc` — так её читает и CSS. */\nfunction expandShorthand(hex: string): string | null {\n if (hex.length === 3 || hex.length === 4)\n return [...hex].map(char => char + char).join('')\n\n if (hex.length === 6 || hex.length === 8)\n return hex\n\n return null\n}\n\nfunction rgbToHsl({ r, g, b }: Rgb): { h: number, s: number, l: number } {\n const red = r / 255\n const green = g / 255\n const blue = b / 255\n\n const max = Math.max(red, green, blue)\n const min = Math.min(red, green, blue)\n const delta = max - min\n const lightness = (max + min) / 2\n\n if (delta === 0)\n return { h: 0, s: 0, l: lightness * 100 }\n\n const saturation = delta / (1 - Math.abs(2 * lightness - 1))\n\n let hue: number\n if (max === red)\n hue = ((green - blue) / delta) % 6\n else if (max === green)\n hue = (blue - red) / delta + 2\n else hue = (red - green) / delta + 4\n\n return { h: normalizeHue(hue * 60), s: saturation * 100, l: lightness * 100 }\n}\n\nfunction hslToRgb({ h, s, l }: Omit<GrHsla, 'a'>): Rgb {\n const saturation = clamp(s, 0, 100) / 100\n const lightness = clamp(l, 0, 100) / 100\n const hue = normalizeHue(h)\n\n const chroma = (1 - Math.abs(2 * lightness - 1)) * saturation\n const second = chroma * (1 - Math.abs(((hue / 60) % 2) - 1))\n const match = lightness - chroma / 2\n\n const [red, green, blue] = ((): [number, number, number] => {\n if (hue < 60)\n return [chroma, second, 0]\n if (hue < 120)\n return [second, chroma, 0]\n if (hue < 180)\n return [0, chroma, second]\n if (hue < 240)\n return [0, second, chroma]\n if (hue < 300)\n return [second, 0, chroma]\n return [chroma, 0, second]\n })()\n\n return {\n r: Math.round((red + match) * 255),\n g: Math.round((green + match) * 255),\n b: Math.round((blue + match) * 255),\n }\n}\n\nfunction toHexPair(value: number): string {\n return clamp(Math.round(value), 0, 255).toString(16).padStart(2, '0')\n}\n\n/**\n * Разбирает hex любой из четырёх форм (`#abc`, `#abcd`, `#aabbcc`, `#aabbccdd`),\n * регистр и решётка необязательны.\n *\n * Мусор возвращает `null`, а не бросает: значение приходит из модели снаружи и\n * из поля ввода, и упавший компонент — худший ответ на опечатку.\n */\nexport function parseHexColor(value: unknown): GrHsla | null {\n if (typeof value !== 'string')\n return null\n\n const match = HEX_PATTERN.exec(value.trim())\n if (!match)\n return null\n\n const hex = expandShorthand(match[1])\n if (!hex)\n return null\n\n const rgb: Rgb = {\n r: Number.parseInt(hex.slice(0, 2), 16),\n g: Number.parseInt(hex.slice(2, 4), 16),\n b: Number.parseInt(hex.slice(4, 6), 16),\n }\n\n const alpha = hex.length === 8 ? Number.parseInt(hex.slice(6, 8), 16) / 255 : 1\n\n return { ...rgbToHsl(rgb), a: alpha }\n}\n\n/**\n * Собирает hex обратно. `withAlpha: false` альфу отбрасывает, а не округляет до\n * непрозрачного: компонент без пропа `alpha` не вправе отдавать восьмизначную\n * форму, которую потребитель не ждёт.\n */\nexport function formatHexColor(color: GrHsla, withAlpha = false): string {\n const { r, g, b } = hslToRgb(color)\n const base = `#${toHexPair(r)}${toHexPair(g)}${toHexPair(b)}`\n\n if (!withAlpha)\n return base\n\n return `${base}${toHexPair(clamp(color.a, 0, 1) * 255)}`\n}\n\n/** Короткая запись числа: целое остаётся целым, дробь обрезается до сотых. */\nfunction trim(value: number): number {\n return Math.round(value * 100) / 100\n}\n\n/** CSS-запись цвета для превью и градиентов. */\nexport function hslaToCss(color: GrHsla): string {\n const h = trim(normalizeHue(color.h))\n const s = trim(clamp(color.s, 0, 100))\n const l = trim(clamp(color.l, 0, 100))\n\n return `hsl(${h} ${s}% ${l}% / ${trim(clamp(color.a, 0, 1))})`\n}\n\n/** Приводит произвольный объект к валидному диапазону каналов. */\nexport function normalizeHsla(color: GrHsla): GrHsla {\n return {\n h: Math.round(normalizeHue(color.h)) % 360,\n s: clamp(Math.round(color.s), 0, 100),\n l: clamp(Math.round(color.l), 0, 100),\n a: clamp(color.a, 0, 1),\n }\n}\n","<script setup lang=\"ts\">\n/**\n * GrColorPicker — поле выбора цвета: образец с текущим значением, панель с\n * оттенком, насыщенностью, светлотой и прозрачностью, поле hex и палитра.\n *\n * Прямой сценарий — настройки темы и брендирования, поэтому модель хранит hex:\n * в такой форме цвет лежит в токенах и его понимает CSS.\n *\n * A11y: каждый канал — отдельный `GrSlider`, то есть настоящий `role=\"slider\"`\n * с полной клавиатурой и `aria-valuetext`. Двумерная область saturation/value\n * не берётся намеренно: это свой виджет со своей клавиатурой по двум осям, и\n * доступность там пришлось бы собирать с нуля.\n */\nimport { computed, nextTick, ref, useId, watch } from 'vue'\n\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport GrInput from '../GrInput/GrInput.vue'\nimport GrPopover from '../GrPopover/GrPopover.vue'\nimport GrSlider from '../GrSlider/GrSlider.vue'\n\nimport { formatHexColor, hslaToCss, normalizeHsla, parseHexColor, type GrHsla } from './color'\nimport {\n checkerClass,\n grColorPickerPanelClass,\n grColorPickerPresetClass,\n grColorPickerTriggerClass,\n presetsGridClass,\n previewClass,\n rowClass,\n rowLabelClass,\n rowValueClass,\n swatchBaseClass,\n swatchFillClass,\n triggerSwatchSizeBySize,\n triggerValueClass,\n type GrColorPickerSize,\n} from './grColorPickerStyles'\n\nexport type { GrColorPickerSize } from './grColorPickerStyles'\n\nexport interface GrColorPickerProps {\n /** Цвет в hex: `#RRGGBB`, а при `alpha` — `#RRGGBBAA`. Мусор не роняет компонент. */\n modelValue: string\n /** Четвёртый слайдер и восьмизначная форма hex. */\n alpha?: boolean\n /** Палитра быстрого выбора. Пусто — блок не рендерится. */\n presets?: string[]\n size?: GrColorPickerSize\n /** Контролируемое состояние панели (`v-model:open`). */\n open?: boolean\n /** Сторона, с которой раскрывается панель. */\n placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end'\n /** Имя для нативной формы: значение уходит скрытым полем. */\n name?: string\n disabled?: boolean\n /** Только для чтения: цвет видно, панель открывается, но значение не меняется. */\n readonly?: boolean\n invalid?: boolean\n required?: boolean\n ariaLabel?: string\n}\n\nexport interface GrColorPickerEmits {\n (e: 'update:modelValue', value: string): void\n (e: 'change', value: string): void\n (e: 'update:open', value: boolean): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(defineProps<GrColorPickerProps>(), {\n alpha: false,\n presets: () => [],\n // Дефолт живёт в резолвере: Vue подставил бы свой раньше, чем компонент\n // заглянет в `GrConfigProvider`.\n size: undefined,\n open: undefined,\n placement: 'bottom-start',\n name: undefined,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n ariaLabel: undefined,\n})\n\nconst emit = defineEmits<GrColorPickerEmits>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrColorPicker' })\n\nconst field = useGrFormFieldContext()\nconst fieldId = computed(() => field?.id.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n // «Ввод не принимается» — `disabled` или `readonly`: значение видно, но не меняется.\n locked: isLocked,\n} = useGrFormControl(() => props)\n\n/**\n * `aria-required` и `aria-readonly` роль `button` не поддерживает — axe роняет\n * это как critical `aria-allowed-attr`. Состояния уходят в описание триггера:\n * иначе они пропали бы для диктора совсем (маркер `*` у `GrFormField`\n * декоративен и скрыт).\n */\nconst stateHintId = useId()\nconst stateHint = computed(() => [\n isRequired.value ? t('gr.form.required', 'This field is required') : undefined,\n isReadonly.value ? t('gr.form.readonly', 'Read only') : undefined,\n].filter(Boolean).join('. ') || undefined)\n\nconst describedBy = computed(() => [\n field?.describedById.value,\n stateHint.value ? stateHintId : undefined,\n].filter(Boolean).join(' ') || undefined)\n\nconst triggerEl = ref<HTMLElement | null>(null)\n\nfunction focus(): void {\n triggerEl.value?.focus()\n}\n\nfunction blur(): void {\n triggerEl.value?.blur()\n}\n\ndefineExpose({ focus, blur })\n\nconst FALLBACK: GrHsla = { h: 0, s: 0, l: 0, a: 1 }\n\n/**\n * Рабочее состояние панели.\n *\n * Держать его отдельно от модели обязательно: hex — проекция с потерями. У\n * серого нет оттенка, и без собственного состояния бегунок оттенка прыгал бы\n * обратно на 0° при каждом движении.\n */\nconst state = ref<GrHsla>(parseHexColor(props.modelValue) ?? FALLBACK)\n\n/** Последнее отданное значение: вернувшись в проп, оно не должно сбрасывать состояние. */\nlet lastEmitted = ''\n\nwatch(() => props.modelValue, (next) => {\n if (next === lastEmitted)\n return\n\n const parsed = parseHexColor(next)\n if (parsed)\n state.value = parsed\n})\n\nconst cssColor = computed(() => hslaToCss(state.value))\nconst hexValue = computed(() => formatHexColor(state.value, props.alpha))\n\nfunction commit(next: GrHsla): void {\n if (isLocked.value)\n return\n\n state.value = normalizeHsla(next)\n\n const hex = formatHexColor(state.value, props.alpha)\n lastEmitted = hex\n emit('update:modelValue', hex)\n emit('change', hex)\n}\n\n/** Канал панели: слайдер читает состояние и коммитит новое значение целиком. */\nfunction channel(key: 'h' | 's' | 'l' | 'a', scale = 1) {\n return computed<number>({\n get: () => Math.round(state.value[key] * scale),\n set: value => commit({ ...state.value, [key]: value / scale }),\n })\n}\n\nconst hue = channel('h')\nconst saturation = channel('s')\nconst lightness = channel('l')\n// Альфа хранится долей, а слайдер ходит по процентам — иначе шаг был бы 0.01.\nconst opacity = channel('a', 100)\n\nconst degrees = (value: number): string => `${value}°`\nconst percent = (value: number): string => `${value}%`\n\n/** Текст поля hex редактируется свободно и коммитится только валидным. */\nconst hexDraft = ref(hexValue.value)\n\nwatch(hexValue, (next) => {\n hexDraft.value = next\n})\n\nfunction commitHex(): void {\n const parsed = parseHexColor(hexDraft.value)\n\n // Мусор откатывается к текущему значению: держать в поле невалидный текст\n // после коммита — значит показывать цвет, которого нет.\n //\n // Откат идёт следующим тиком намеренно: вернув значение синхронно, мы отдали\n // бы Vue то же самое, что он уже отрисовал, — патча не случилось бы, и в поле\n // остался бы набранный мусор.\n if (!parsed) {\n void nextTick(() => {\n hexDraft.value = hexValue.value\n })\n return\n }\n\n commit(props.alpha ? parsed : { ...parsed, a: state.value.a })\n}\n\nconst isOpen = ref(false)\nconst panelOpen = computed({\n get: () => props.open ?? isOpen.value,\n set: (value) => {\n isOpen.value = value\n emit('update:open', value)\n },\n})\n\nconst presetList = computed(() => props.presets.filter(preset => parseHexColor(preset) !== null))\n\nfunction isPresetSelected(preset: string): boolean {\n const parsed = parseHexColor(preset)\n return parsed ? formatHexColor(parsed, props.alpha) === hexValue.value : false\n}\n\nfunction selectPreset(preset: string): void {\n const parsed = parseHexColor(preset)\n if (parsed)\n commit(parsed)\n}\n\nconst triggerClass = computed(() => grColorPickerTriggerClass({\n size: resolvedSize.value,\n disabled: isDisabled.value,\n invalid: isInvalid.value,\n}))\n\nconst panelClass = computed(() => grColorPickerPanelClass(resolvedSize.value))\nconst swatchSizeClass = computed(() => triggerSwatchSizeBySize[resolvedSize.value])\n\n/**\n * Градиенты дорожек. Через `--gr-slider-rail` их не подать: хук уходит в\n * `background-color`, а градиент там не работает — поэтому значения приезжают\n * переменными, а красит дорожки собственный `<style>` компонента.\n *\n * Переменные висят на самой панели, а не на корне: панель уезжает в портал, и\n * наследование от корня до неё не доходит.\n */\nconst trackVars = computed<Record<string, string>>(() => {\n const { h, s, l } = state.value\n const solid = hslaToCss({ h, s, l, a: 1 })\n\n return {\n '--gr-color-picker-track-hue': 'linear-gradient(to right, hsl(0 100% 50%), hsl(60 100% 50%), hsl(120 100% 50%), hsl(180 100% 50%), hsl(240 100% 50%), hsl(300 100% 50%), hsl(360 100% 50%))',\n '--gr-color-picker-track-saturation': `linear-gradient(to right, hsl(${Math.round(h)} 0% ${Math.round(l)}%), hsl(${Math.round(h)} 100% ${Math.round(l)}%))`,\n '--gr-color-picker-track-lightness': `linear-gradient(to right, hsl(${Math.round(h)} ${Math.round(s)}% 0%), hsl(${Math.round(h)} ${Math.round(s)}% 50%), hsl(${Math.round(h)} ${Math.round(s)}% 100%))`,\n '--gr-color-picker-track-alpha': `linear-gradient(to right, transparent, ${solid})`,\n }\n})\n</script>\n\n<template>\n <div data-gr-color-picker>\n <!-- Значение для нативной формы уходит скрытым полем: интерактивного\n контрола внутри виджета быть не должно. -->\n <input v-if=\"name\" type=\"hidden\" :name=\"name\" :value=\"hexValue\">\n\n <span v-if=\"stateHint\" :id=\"stateHintId\" class=\"sr-only\">{{ stateHint }}</span>\n\n <GrPopover\n v-model:open=\"panelOpen\"\n block\n :size=\"resolvedSize\"\n :placement=\"placement\"\n :disabled=\"isDisabled\"\n :aria-label=\"t('gr.colorPicker.panelLabel', 'Color picker')\"\n :close-on-content-click=\"false\"\n >\n <template #trigger=\"{ triggerProps }\">\n <button\n :id=\"fieldId\"\n ref=\"triggerEl\"\n v-bind=\"triggerProps\"\n data-gr-color-picker-trigger\n type=\"button\"\n :class=\"triggerClass\"\n :aria-label=\"ariaLabel\"\n :aria-describedby=\"describedBy\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n @focus=\"emit('focus', $event)\"\n @blur=\"emit('blur', $event)\"\n >\n <span\n data-gr-color-picker-swatch\n aria-hidden=\"true\"\n :class=\"[swatchBaseClass, swatchSizeClass, checkerClass]\"\n >\n <span :class=\"swatchFillClass\" :style=\"{ background: cssColor }\" />\n </span>\n\n <span :class=\"triggerValueClass\">{{ hexValue }}</span>\n </button>\n </template>\n\n <template #content>\n <div data-gr-color-picker-panel :class=\"panelClass\" :style=\"trackVars\">\n <div\n data-gr-color-picker-preview\n aria-hidden=\"true\"\n :class=\"[previewClass, checkerClass]\"\n >\n <span :class=\"swatchFillClass\" :style=\"{ background: cssColor }\" />\n </div>\n\n <div data-gr-color-picker-channel=\"hue\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">H</span>\n <GrSlider\n v-model=\"hue\"\n :min=\"0\"\n :max=\"359\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"degrees\"\n :aria-label=\"t('gr.colorPicker.hue', 'Hue')\"\n />\n <span :class=\"rowValueClass\">{{ degrees(hue) }}</span>\n </div>\n\n <div data-gr-color-picker-channel=\"saturation\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">S</span>\n <GrSlider\n v-model=\"saturation\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"percent\"\n :aria-label=\"t('gr.colorPicker.saturation', 'Saturation')\"\n />\n <span :class=\"rowValueClass\">{{ percent(saturation) }}</span>\n </div>\n\n <div data-gr-color-picker-channel=\"lightness\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">L</span>\n <GrSlider\n v-model=\"lightness\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"percent\"\n :aria-label=\"t('gr.colorPicker.lightness', 'Lightness')\"\n />\n <span :class=\"rowValueClass\">{{ percent(lightness) }}</span>\n </div>\n\n <div v-if=\"alpha\" data-gr-color-picker-channel=\"alpha\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">A</span>\n <GrSlider\n v-model=\"opacity\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"percent\"\n :aria-label=\"t('gr.colorPicker.opacity', 'Opacity')\"\n />\n <span :class=\"rowValueClass\">{{ percent(opacity) }}</span>\n </div>\n\n <GrInput\n v-model=\"hexDraft\"\n data-gr-color-picker-hex\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :aria-label=\"t('gr.colorPicker.hexLabel', 'Hex value')\"\n @change=\"commitHex\"\n @keydown.enter=\"commitHex\"\n />\n\n <div\n v-if=\"presetList.length\"\n data-gr-color-picker-presets\n :class=\"presetsGridClass\"\n role=\"group\"\n :aria-label=\"t('gr.colorPicker.presetsLabel', 'Color presets')\"\n >\n <button\n v-for=\"preset in presetList\"\n :key=\"preset\"\n data-gr-color-picker-preset\n type=\"button\"\n :class=\"[grColorPickerPresetClass(isPresetSelected(preset)), checkerClass]\"\n :aria-label=\"t('gr.colorPicker.swatchLabel', 'Color {color}', { color: preset })\"\n :aria-pressed=\"isPresetSelected(preset) ? 'true' : 'false'\"\n :disabled=\"isLocked\"\n @click=\"selectPreset(preset)\"\n >\n <span :class=\"swatchFillClass\" :style=\"{ background: preset }\" />\n </button>\n </div>\n </div>\n </template>\n </GrPopover>\n </div>\n</template>\n\n<style>\n/*\n * Шахматка под прозрачным цветом. Двумя `conic-gradient` — в утилите такое не\n * выражается, а без подложки прозрачный образец сливается с панелью.\n */\n.gr-color-picker-checker {\n background-image:\n conic-gradient(\n from 90deg,\n var(--gr-color-picker-checker-light, var(--gr-bg)) 0 25%,\n var(--gr-color-picker-checker-dark, var(--gr-muted)) 0 50%,\n var(--gr-color-picker-checker-light, var(--gr-bg)) 0 75%,\n var(--gr-color-picker-checker-dark, var(--gr-muted)) 0\n );\n background-size:\n calc(var(--gr-color-picker-checker-size, 6px) * 2)\n calc(var(--gr-color-picker-checker-size, 6px) * 2);\n /* Рамка образца полупрозрачна, и без обрезки шахматка просвечивает сквозь неё. */\n background-clip: padding-box;\n}\n\n/*\n * Дорожки каналов. Красим `[data-gr-slider-track]` потому, что хук\n * `--gr-slider-rail` уходит в `background-color`, а градиент там не работает.\n * Сами градиенты компонент подаёт переменными — CSS остаётся статичной.\n */\n[data-gr-color-picker-channel] [data-gr-slider-track] {\n border-radius: var(--gr-radius-full);\n}\n\n[data-gr-color-picker-channel='hue'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-hue);\n}\n\n[data-gr-color-picker-channel='saturation'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-saturation);\n}\n\n[data-gr-color-picker-channel='lightness'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-lightness);\n}\n\n[data-gr-color-picker-channel='alpha'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-alpha);\n}\n\n/* Заливка канала прозрачна: шкалу показывает сама дорожка. */\n[data-gr-color-picker-channel] [data-gr-slider-fill] {\n background-color: transparent;\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * GrColorPicker — поле выбора цвета: образец с текущим значением, панель с\n * оттенком, насыщенностью, светлотой и прозрачностью, поле hex и палитра.\n *\n * Прямой сценарий — настройки темы и брендирования, поэтому модель хранит hex:\n * в такой форме цвет лежит в токенах и его понимает CSS.\n *\n * A11y: каждый канал — отдельный `GrSlider`, то есть настоящий `role=\"slider\"`\n * с полной клавиатурой и `aria-valuetext`. Двумерная область saturation/value\n * не берётся намеренно: это свой виджет со своей клавиатурой по двум осям, и\n * доступность там пришлось бы собирать с нуля.\n */\nimport { computed, nextTick, ref, useId, watch } from 'vue'\n\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport GrInput from '../GrInput/GrInput.vue'\nimport GrPopover from '../GrPopover/GrPopover.vue'\nimport GrSlider from '../GrSlider/GrSlider.vue'\n\nimport { formatHexColor, hslaToCss, normalizeHsla, parseHexColor, type GrHsla } from './color'\nimport {\n checkerClass,\n grColorPickerPanelClass,\n grColorPickerPresetClass,\n grColorPickerTriggerClass,\n presetsGridClass,\n previewClass,\n rowClass,\n rowLabelClass,\n rowValueClass,\n swatchBaseClass,\n swatchFillClass,\n triggerSwatchSizeBySize,\n triggerValueClass,\n type GrColorPickerSize,\n} from './grColorPickerStyles'\n\nexport type { GrColorPickerSize } from './grColorPickerStyles'\n\nexport interface GrColorPickerProps {\n /** Цвет в hex: `#RRGGBB`, а при `alpha` — `#RRGGBBAA`. Мусор не роняет компонент. */\n modelValue: string\n /** Четвёртый слайдер и восьмизначная форма hex. */\n alpha?: boolean\n /** Палитра быстрого выбора. Пусто — блок не рендерится. */\n presets?: string[]\n size?: GrColorPickerSize\n /** Контролируемое состояние панели (`v-model:open`). */\n open?: boolean\n /** Сторона, с которой раскрывается панель. */\n placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end'\n /** Имя для нативной формы: значение уходит скрытым полем. */\n name?: string\n disabled?: boolean\n /** Только для чтения: цвет видно, панель открывается, но значение не меняется. */\n readonly?: boolean\n invalid?: boolean\n required?: boolean\n ariaLabel?: string\n}\n\nexport interface GrColorPickerEmits {\n (e: 'update:modelValue', value: string): void\n (e: 'change', value: string): void\n (e: 'update:open', value: boolean): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(defineProps<GrColorPickerProps>(), {\n alpha: false,\n presets: () => [],\n // Дефолт живёт в резолвере: Vue подставил бы свой раньше, чем компонент\n // заглянет в `GrConfigProvider`.\n size: undefined,\n open: undefined,\n placement: 'bottom-start',\n name: undefined,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n ariaLabel: undefined,\n})\n\nconst emit = defineEmits<GrColorPickerEmits>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrColorPicker' })\n\nconst field = useGrFormFieldContext()\nconst fieldId = computed(() => field?.id.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n // «Ввод не принимается» — `disabled` или `readonly`: значение видно, но не меняется.\n locked: isLocked,\n} = useGrFormControl(() => props)\n\n/**\n * `aria-required` и `aria-readonly` роль `button` не поддерживает — axe роняет\n * это как critical `aria-allowed-attr`. Состояния уходят в описание триггера:\n * иначе они пропали бы для диктора совсем (маркер `*` у `GrFormField`\n * декоративен и скрыт).\n */\nconst stateHintId = useId()\nconst stateHint = computed(() => [\n isRequired.value ? t('gr.form.required', 'This field is required') : undefined,\n isReadonly.value ? t('gr.form.readonly', 'Read only') : undefined,\n].filter(Boolean).join('. ') || undefined)\n\nconst describedBy = computed(() => [\n field?.describedById.value,\n stateHint.value ? stateHintId : undefined,\n].filter(Boolean).join(' ') || undefined)\n\nconst triggerEl = ref<HTMLElement | null>(null)\n\nfunction focus(): void {\n triggerEl.value?.focus()\n}\n\nfunction blur(): void {\n triggerEl.value?.blur()\n}\n\ndefineExpose({ focus, blur })\n\nconst FALLBACK: GrHsla = { h: 0, s: 0, l: 0, a: 1 }\n\n/**\n * Рабочее состояние панели.\n *\n * Держать его отдельно от модели обязательно: hex — проекция с потерями. У\n * серого нет оттенка, и без собственного состояния бегунок оттенка прыгал бы\n * обратно на 0° при каждом движении.\n */\nconst state = ref<GrHsla>(parseHexColor(props.modelValue) ?? FALLBACK)\n\n/** Последнее отданное значение: вернувшись в проп, оно не должно сбрасывать состояние. */\nlet lastEmitted = ''\n\nwatch(() => props.modelValue, (next) => {\n if (next === lastEmitted)\n return\n\n const parsed = parseHexColor(next)\n if (parsed)\n state.value = parsed\n})\n\nconst cssColor = computed(() => hslaToCss(state.value))\nconst hexValue = computed(() => formatHexColor(state.value, props.alpha))\n\nfunction commit(next: GrHsla): void {\n if (isLocked.value)\n return\n\n state.value = normalizeHsla(next)\n\n const hex = formatHexColor(state.value, props.alpha)\n lastEmitted = hex\n emit('update:modelValue', hex)\n emit('change', hex)\n}\n\n/** Канал панели: слайдер читает состояние и коммитит новое значение целиком. */\nfunction channel(key: 'h' | 's' | 'l' | 'a', scale = 1) {\n return computed<number>({\n get: () => Math.round(state.value[key] * scale),\n set: value => commit({ ...state.value, [key]: value / scale }),\n })\n}\n\nconst hue = channel('h')\nconst saturation = channel('s')\nconst lightness = channel('l')\n// Альфа хранится долей, а слайдер ходит по процентам — иначе шаг был бы 0.01.\nconst opacity = channel('a', 100)\n\nconst degrees = (value: number): string => `${value}°`\nconst percent = (value: number): string => `${value}%`\n\n/** Текст поля hex редактируется свободно и коммитится только валидным. */\nconst hexDraft = ref(hexValue.value)\n\nwatch(hexValue, (next) => {\n hexDraft.value = next\n})\n\nfunction commitHex(): void {\n const parsed = parseHexColor(hexDraft.value)\n\n // Мусор откатывается к текущему значению: держать в поле невалидный текст\n // после коммита — значит показывать цвет, которого нет.\n //\n // Откат идёт следующим тиком намеренно: вернув значение синхронно, мы отдали\n // бы Vue то же самое, что он уже отрисовал, — патча не случилось бы, и в поле\n // остался бы набранный мусор.\n if (!parsed) {\n void nextTick(() => {\n hexDraft.value = hexValue.value\n })\n return\n }\n\n commit(props.alpha ? parsed : { ...parsed, a: state.value.a })\n}\n\nconst isOpen = ref(false)\nconst panelOpen = computed({\n get: () => props.open ?? isOpen.value,\n set: (value) => {\n isOpen.value = value\n emit('update:open', value)\n },\n})\n\nconst presetList = computed(() => props.presets.filter(preset => parseHexColor(preset) !== null))\n\nfunction isPresetSelected(preset: string): boolean {\n const parsed = parseHexColor(preset)\n return parsed ? formatHexColor(parsed, props.alpha) === hexValue.value : false\n}\n\nfunction selectPreset(preset: string): void {\n const parsed = parseHexColor(preset)\n if (parsed)\n commit(parsed)\n}\n\nconst triggerClass = computed(() => grColorPickerTriggerClass({\n size: resolvedSize.value,\n disabled: isDisabled.value,\n invalid: isInvalid.value,\n}))\n\nconst panelClass = computed(() => grColorPickerPanelClass(resolvedSize.value))\nconst swatchSizeClass = computed(() => triggerSwatchSizeBySize[resolvedSize.value])\n\n/**\n * Градиенты дорожек. Через `--gr-slider-rail` их не подать: хук уходит в\n * `background-color`, а градиент там не работает — поэтому значения приезжают\n * переменными, а красит дорожки собственный `<style>` компонента.\n *\n * Переменные висят на самой панели, а не на корне: панель уезжает в портал, и\n * наследование от корня до неё не доходит.\n */\nconst trackVars = computed<Record<string, string>>(() => {\n const { h, s, l } = state.value\n const solid = hslaToCss({ h, s, l, a: 1 })\n\n return {\n '--gr-color-picker-track-hue': 'linear-gradient(to right, hsl(0 100% 50%), hsl(60 100% 50%), hsl(120 100% 50%), hsl(180 100% 50%), hsl(240 100% 50%), hsl(300 100% 50%), hsl(360 100% 50%))',\n '--gr-color-picker-track-saturation': `linear-gradient(to right, hsl(${Math.round(h)} 0% ${Math.round(l)}%), hsl(${Math.round(h)} 100% ${Math.round(l)}%))`,\n '--gr-color-picker-track-lightness': `linear-gradient(to right, hsl(${Math.round(h)} ${Math.round(s)}% 0%), hsl(${Math.round(h)} ${Math.round(s)}% 50%), hsl(${Math.round(h)} ${Math.round(s)}% 100%))`,\n '--gr-color-picker-track-alpha': `linear-gradient(to right, transparent, ${solid})`,\n }\n})\n</script>\n\n<template>\n <div data-gr-color-picker>\n <!-- Значение для нативной формы уходит скрытым полем: интерактивного\n контрола внутри виджета быть не должно. -->\n <input v-if=\"name\" type=\"hidden\" :name=\"name\" :value=\"hexValue\">\n\n <span v-if=\"stateHint\" :id=\"stateHintId\" class=\"sr-only\">{{ stateHint }}</span>\n\n <GrPopover\n v-model:open=\"panelOpen\"\n block\n :size=\"resolvedSize\"\n :placement=\"placement\"\n :disabled=\"isDisabled\"\n :aria-label=\"t('gr.colorPicker.panelLabel', 'Color picker')\"\n :close-on-content-click=\"false\"\n >\n <template #trigger=\"{ triggerProps }\">\n <button\n :id=\"fieldId\"\n ref=\"triggerEl\"\n v-bind=\"triggerProps\"\n data-gr-color-picker-trigger\n type=\"button\"\n :class=\"triggerClass\"\n :aria-label=\"ariaLabel\"\n :aria-describedby=\"describedBy\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n @focus=\"emit('focus', $event)\"\n @blur=\"emit('blur', $event)\"\n >\n <span\n data-gr-color-picker-swatch\n aria-hidden=\"true\"\n :class=\"[swatchBaseClass, swatchSizeClass, checkerClass]\"\n >\n <span :class=\"swatchFillClass\" :style=\"{ background: cssColor }\" />\n </span>\n\n <span :class=\"triggerValueClass\">{{ hexValue }}</span>\n </button>\n </template>\n\n <template #content>\n <div data-gr-color-picker-panel :class=\"panelClass\" :style=\"trackVars\">\n <div\n data-gr-color-picker-preview\n aria-hidden=\"true\"\n :class=\"[previewClass, checkerClass]\"\n >\n <span :class=\"swatchFillClass\" :style=\"{ background: cssColor }\" />\n </div>\n\n <div data-gr-color-picker-channel=\"hue\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">H</span>\n <GrSlider\n v-model=\"hue\"\n :min=\"0\"\n :max=\"359\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"degrees\"\n :aria-label=\"t('gr.colorPicker.hue', 'Hue')\"\n />\n <span :class=\"rowValueClass\">{{ degrees(hue) }}</span>\n </div>\n\n <div data-gr-color-picker-channel=\"saturation\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">S</span>\n <GrSlider\n v-model=\"saturation\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"percent\"\n :aria-label=\"t('gr.colorPicker.saturation', 'Saturation')\"\n />\n <span :class=\"rowValueClass\">{{ percent(saturation) }}</span>\n </div>\n\n <div data-gr-color-picker-channel=\"lightness\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">L</span>\n <GrSlider\n v-model=\"lightness\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"percent\"\n :aria-label=\"t('gr.colorPicker.lightness', 'Lightness')\"\n />\n <span :class=\"rowValueClass\">{{ percent(lightness) }}</span>\n </div>\n\n <div v-if=\"alpha\" data-gr-color-picker-channel=\"alpha\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">A</span>\n <GrSlider\n v-model=\"opacity\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"percent\"\n :aria-label=\"t('gr.colorPicker.opacity', 'Opacity')\"\n />\n <span :class=\"rowValueClass\">{{ percent(opacity) }}</span>\n </div>\n\n <GrInput\n v-model=\"hexDraft\"\n data-gr-color-picker-hex\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :aria-label=\"t('gr.colorPicker.hexLabel', 'Hex value')\"\n @change=\"commitHex\"\n @keydown.enter=\"commitHex\"\n />\n\n <div\n v-if=\"presetList.length\"\n data-gr-color-picker-presets\n :class=\"presetsGridClass\"\n role=\"group\"\n :aria-label=\"t('gr.colorPicker.presetsLabel', 'Color presets')\"\n >\n <button\n v-for=\"preset in presetList\"\n :key=\"preset\"\n data-gr-color-picker-preset\n type=\"button\"\n :class=\"[grColorPickerPresetClass(isPresetSelected(preset)), checkerClass]\"\n :aria-label=\"t('gr.colorPicker.swatchLabel', 'Color {color}', { color: preset })\"\n :aria-pressed=\"isPresetSelected(preset) ? 'true' : 'false'\"\n :disabled=\"isLocked\"\n @click=\"selectPreset(preset)\"\n >\n <span :class=\"swatchFillClass\" :style=\"{ background: preset }\" />\n </button>\n </div>\n </div>\n </template>\n </GrPopover>\n </div>\n</template>\n\n<style>\n/*\n * Шахматка под прозрачным цветом. Двумя `conic-gradient` — в утилите такое не\n * выражается, а без подложки прозрачный образец сливается с панелью.\n */\n.gr-color-picker-checker {\n background-image:\n conic-gradient(\n from 90deg,\n var(--gr-color-picker-checker-light, var(--gr-bg)) 0 25%,\n var(--gr-color-picker-checker-dark, var(--gr-muted)) 0 50%,\n var(--gr-color-picker-checker-light, var(--gr-bg)) 0 75%,\n var(--gr-color-picker-checker-dark, var(--gr-muted)) 0\n );\n background-size:\n calc(var(--gr-color-picker-checker-size, 6px) * 2)\n calc(var(--gr-color-picker-checker-size, 6px) * 2);\n /* Рамка образца полупрозрачна, и без обрезки шахматка просвечивает сквозь неё. */\n background-clip: padding-box;\n}\n\n/*\n * Дорожки каналов. Красим `[data-gr-slider-track]` потому, что хук\n * `--gr-slider-rail` уходит в `background-color`, а градиент там не работает.\n * Сами градиенты компонент подаёт переменными — CSS остаётся статичной.\n */\n[data-gr-color-picker-channel] [data-gr-slider-track] {\n border-radius: var(--gr-radius-full);\n}\n\n[data-gr-color-picker-channel='hue'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-hue);\n}\n\n[data-gr-color-picker-channel='saturation'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-saturation);\n}\n\n[data-gr-color-picker-channel='lightness'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-lightness);\n}\n\n[data-gr-color-picker-channel='alpha'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-alpha);\n}\n\n/* Заливка канала прозрачна: шкалу показывает сама дорожка. */\n[data-gr-color-picker-channel] [data-gr-slider-fill] {\n background-color: transparent;\n}\n</style>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GrColorPicker-9mTR-YZ_.js","names":[],"sources":["../../../../src/components/GrColorPicker/color.ts","../../../../src/components/GrColorPicker/GrColorPicker.vue","../../../../src/components/GrColorPicker/GrColorPicker.vue"],"sourcesContent":["/**\n * Арифметика цвета для `GrColorPicker` — чистый модуль без Vue.\n *\n * Модель компонента — hex-строка (в такой форме цвет лежит в токенах темы),\n * а панель работает в HSL: у оттенка, насыщенности и светлоты есть шкала, по\n * которой можно вести слайдер, а у каналов RGB её нет.\n *\n * Свой парсер, а не `__tests__/cssContrast.ts`: тот — инфраструктура гейтов, и\n * тянуть тестовый модуль в поставляемый код нельзя.\n */\n\nexport type GrHsla = {\n /** Оттенок, 0…359 (круговой: 360 — это 0). */\n h: number\n /** Насыщенность, 0…100. */\n s: number\n /** Светлота, 0…100. */\n l: number\n /** Непрозрачность, 0…1. */\n a: number\n}\n\ntype Rgb = { r: number, g: number, b: number }\n\nconst HEX_PATTERN = /^#?([0-9a-f]{3,8})$/i\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max)\n}\n\n/**\n * Оттенок цикличен: 360° — тот же цвет, что 0°.\n *\n * Округления здесь нет намеренно: `#3b82f6` даёт 217.22°, и округлив его до\n * целого, обратное преобразование вернуло бы `#3b83f6` — компонент молча\n * переписывал бы чужой цвет при первом же открытии панели. Целые градусы\n * появляются там, где они и нужны, — на шкале слайдера (`normalizeHsla`).\n */\nfunction normalizeHue(hue: number): number {\n if (!Number.isFinite(hue))\n return 0\n return ((hue % 360) + 360) % 360\n}\n\n/** Короткая форма `#abc` разворачивается в `#aabbcc` — так её читает и CSS. */\nfunction expandShorthand(hex: string): string | null {\n if (hex.length === 3 || hex.length === 4)\n return [...hex].map(char => char + char).join('')\n\n if (hex.length === 6 || hex.length === 8)\n return hex\n\n return null\n}\n\nfunction rgbToHsl({ r, g, b }: Rgb): { h: number, s: number, l: number } {\n const red = r / 255\n const green = g / 255\n const blue = b / 255\n\n const max = Math.max(red, green, blue)\n const min = Math.min(red, green, blue)\n const delta = max - min\n const lightness = (max + min) / 2\n\n if (delta === 0)\n return { h: 0, s: 0, l: lightness * 100 }\n\n const saturation = delta / (1 - Math.abs(2 * lightness - 1))\n\n let hue: number\n if (max === red)\n hue = ((green - blue) / delta) % 6\n else if (max === green)\n hue = (blue - red) / delta + 2\n else hue = (red - green) / delta + 4\n\n return { h: normalizeHue(hue * 60), s: saturation * 100, l: lightness * 100 }\n}\n\nfunction hslToRgb({ h, s, l }: Omit<GrHsla, 'a'>): Rgb {\n const saturation = clamp(s, 0, 100) / 100\n const lightness = clamp(l, 0, 100) / 100\n const hue = normalizeHue(h)\n\n const chroma = (1 - Math.abs(2 * lightness - 1)) * saturation\n const second = chroma * (1 - Math.abs(((hue / 60) % 2) - 1))\n const match = lightness - chroma / 2\n\n const [red, green, blue] = ((): [number, number, number] => {\n if (hue < 60)\n return [chroma, second, 0]\n if (hue < 120)\n return [second, chroma, 0]\n if (hue < 180)\n return [0, chroma, second]\n if (hue < 240)\n return [0, second, chroma]\n if (hue < 300)\n return [second, 0, chroma]\n return [chroma, 0, second]\n })()\n\n return {\n r: Math.round((red + match) * 255),\n g: Math.round((green + match) * 255),\n b: Math.round((blue + match) * 255),\n }\n}\n\nfunction toHexPair(value: number): string {\n return clamp(Math.round(value), 0, 255).toString(16).padStart(2, '0')\n}\n\n/**\n * Разбирает hex любой из четырёх форм (`#abc`, `#abcd`, `#aabbcc`, `#aabbccdd`),\n * регистр и решётка необязательны.\n *\n * Мусор возвращает `null`, а не бросает: значение приходит из модели снаружи и\n * из поля ввода, и упавший компонент — худший ответ на опечатку.\n */\nexport function parseHexColor(value: unknown): GrHsla | null {\n if (typeof value !== 'string')\n return null\n\n const match = HEX_PATTERN.exec(value.trim())\n if (!match)\n return null\n\n const hex = expandShorthand(match[1])\n if (!hex)\n return null\n\n const rgb: Rgb = {\n r: Number.parseInt(hex.slice(0, 2), 16),\n g: Number.parseInt(hex.slice(2, 4), 16),\n b: Number.parseInt(hex.slice(4, 6), 16),\n }\n\n const alpha = hex.length === 8 ? Number.parseInt(hex.slice(6, 8), 16) / 255 : 1\n\n return { ...rgbToHsl(rgb), a: alpha }\n}\n\n/**\n * Собирает hex обратно. `withAlpha: false` альфу отбрасывает, а не округляет до\n * непрозрачного: компонент без пропа `alpha` не вправе отдавать восьмизначную\n * форму, которую потребитель не ждёт.\n */\nexport function formatHexColor(color: GrHsla, withAlpha = false): string {\n const { r, g, b } = hslToRgb(color)\n const base = `#${toHexPair(r)}${toHexPair(g)}${toHexPair(b)}`\n\n if (!withAlpha)\n return base\n\n return `${base}${toHexPair(clamp(color.a, 0, 1) * 255)}`\n}\n\n/** Короткая запись числа: целое остаётся целым, дробь обрезается до сотых. */\nfunction trim(value: number): number {\n return Math.round(value * 100) / 100\n}\n\n/** CSS-запись цвета для превью и градиентов. */\nexport function hslaToCss(color: GrHsla): string {\n const h = trim(normalizeHue(color.h))\n const s = trim(clamp(color.s, 0, 100))\n const l = trim(clamp(color.l, 0, 100))\n\n return `hsl(${h} ${s}% ${l}% / ${trim(clamp(color.a, 0, 1))})`\n}\n\n/** Приводит произвольный объект к валидному диапазону каналов. */\nexport function normalizeHsla(color: GrHsla): GrHsla {\n return {\n h: Math.round(normalizeHue(color.h)) % 360,\n s: clamp(Math.round(color.s), 0, 100),\n l: clamp(Math.round(color.l), 0, 100),\n a: clamp(color.a, 0, 1),\n }\n}\n","<script setup lang=\"ts\">\n/**\n * GrColorPicker — поле выбора цвета: образец с текущим значением, панель с\n * оттенком, насыщенностью, светлотой и прозрачностью, поле hex и палитра.\n *\n * Прямой сценарий — настройки темы и брендирования, поэтому модель хранит hex:\n * в такой форме цвет лежит в токенах и его понимает CSS.\n *\n * A11y: каждый канал — отдельный `GrSlider`, то есть настоящий `role=\"slider\"`\n * с полной клавиатурой и `aria-valuetext`. Двумерная область saturation/value\n * не берётся намеренно: это свой виджет со своей клавиатурой по двум осям, и\n * доступность там пришлось бы собирать с нуля.\n */\nimport { computed, nextTick, ref, useId, watch } from 'vue'\n\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport GrInput from '../GrInput/GrInput.vue'\nimport GrPopover from '../GrPopover/GrPopover.vue'\nimport GrSlider from '../GrSlider/GrSlider.vue'\n\nimport { formatHexColor, hslaToCss, normalizeHsla, parseHexColor, type GrHsla } from './color'\nimport {\n checkerClass,\n grColorPickerPanelClass,\n grColorPickerPresetClass,\n grColorPickerTriggerClass,\n presetsGridClass,\n previewClass,\n rowClass,\n rowLabelClass,\n rowValueClass,\n swatchBaseClass,\n swatchFillClass,\n triggerSwatchSizeBySize,\n triggerValueClass,\n type GrColorPickerSize,\n} from './grColorPickerStyles'\n\nexport type { GrColorPickerSize } from './grColorPickerStyles'\n\nexport interface GrColorPickerProps {\n /** Цвет в hex: `#RRGGBB`, а при `alpha` — `#RRGGBBAA`. Мусор не роняет компонент. */\n modelValue: string\n /** Четвёртый слайдер и восьмизначная форма hex. */\n alpha?: boolean\n /** Палитра быстрого выбора. Пусто — блок не рендерится. */\n presets?: string[]\n size?: GrColorPickerSize\n /** Контролируемое состояние панели (`v-model:open`). */\n open?: boolean\n /** Сторона, с которой раскрывается панель. */\n placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end'\n /** Имя для нативной формы: значение уходит скрытым полем. */\n name?: string\n disabled?: boolean\n /** Только для чтения: цвет видно, панель открывается, но значение не меняется. */\n readonly?: boolean\n invalid?: boolean\n required?: boolean\n ariaLabel?: string\n}\n\nexport interface GrColorPickerEmits {\n (e: 'update:modelValue', value: string): void\n (e: 'change', value: string): void\n (e: 'update:open', value: boolean): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(defineProps<GrColorPickerProps>(), {\n alpha: false,\n presets: () => [],\n // Дефолт живёт в резолвере: Vue подставил бы свой раньше, чем компонент\n // заглянет в `GrConfigProvider`.\n size: undefined,\n open: undefined,\n placement: 'bottom-start',\n name: undefined,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n ariaLabel: undefined,\n})\n\nconst emit = defineEmits<GrColorPickerEmits>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrColorPicker' })\n\nconst field = useGrFormFieldContext()\nconst fieldId = computed(() => field?.id.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n // «Ввод не принимается» — `disabled` или `readonly`: значение видно, но не меняется.\n locked: isLocked,\n} = useGrFormControl(() => props)\n\n/**\n * `aria-required` и `aria-readonly` роль `button` не поддерживает — axe роняет\n * это как critical `aria-allowed-attr`. Состояния уходят в описание триггера:\n * иначе они пропали бы для диктора совсем (маркер `*` у `GrFormField`\n * декоративен и скрыт).\n */\nconst stateHintId = useId()\nconst stateHint = computed(() => [\n isRequired.value ? t('gr.form.required', 'This field is required') : undefined,\n isReadonly.value ? t('gr.form.readonly', 'Read only') : undefined,\n].filter(Boolean).join('. ') || undefined)\n\nconst describedBy = computed(() => [\n field?.describedById.value,\n stateHint.value ? stateHintId : undefined,\n].filter(Boolean).join(' ') || undefined)\n\nconst triggerEl = ref<HTMLElement | null>(null)\n\nfunction focus(): void {\n triggerEl.value?.focus()\n}\n\nfunction blur(): void {\n triggerEl.value?.blur()\n}\n\ndefineExpose({ focus, blur })\n\nconst FALLBACK: GrHsla = { h: 0, s: 0, l: 0, a: 1 }\n\n/**\n * Рабочее состояние панели.\n *\n * Держать его отдельно от модели обязательно: hex — проекция с потерями. У\n * серого нет оттенка, и без собственного состояния бегунок оттенка прыгал бы\n * обратно на 0° при каждом движении.\n */\nconst state = ref<GrHsla>(parseHexColor(props.modelValue) ?? FALLBACK)\n\n/** Последнее отданное значение: вернувшись в проп, оно не должно сбрасывать состояние. */\nlet lastEmitted = ''\n\nwatch(() => props.modelValue, (next) => {\n if (next === lastEmitted)\n return\n\n const parsed = parseHexColor(next)\n if (parsed)\n state.value = parsed\n})\n\nconst cssColor = computed(() => hslaToCss(state.value))\nconst hexValue = computed(() => formatHexColor(state.value, props.alpha))\n\nfunction commit(next: GrHsla): void {\n if (isLocked.value)\n return\n\n state.value = normalizeHsla(next)\n\n const hex = formatHexColor(state.value, props.alpha)\n lastEmitted = hex\n emit('update:modelValue', hex)\n emit('change', hex)\n}\n\n/** Канал панели: слайдер читает состояние и коммитит новое значение целиком. */\nfunction channel(key: 'h' | 's' | 'l' | 'a', scale = 1) {\n return computed<number>({\n get: () => Math.round(state.value[key] * scale),\n set: value => commit({ ...state.value, [key]: value / scale }),\n })\n}\n\nconst hue = channel('h')\nconst saturation = channel('s')\nconst lightness = channel('l')\n// Альфа хранится долей, а слайдер ходит по процентам — иначе шаг был бы 0.01.\nconst opacity = channel('a', 100)\n\nconst degrees = (value: number): string => `${value}°`\nconst percent = (value: number): string => `${value}%`\n\n/** Текст поля hex редактируется свободно и коммитится только валидным. */\nconst hexDraft = ref(hexValue.value)\n\nwatch(hexValue, (next) => {\n hexDraft.value = next\n})\n\nfunction commitHex(): void {\n const parsed = parseHexColor(hexDraft.value)\n\n // Мусор откатывается к текущему значению: держать в поле невалидный текст\n // после коммита — значит показывать цвет, которого нет.\n //\n // Откат идёт следующим тиком намеренно: вернув значение синхронно, мы отдали\n // бы Vue то же самое, что он уже отрисовал, — патча не случилось бы, и в поле\n // остался бы набранный мусор.\n if (!parsed) {\n void nextTick(() => {\n hexDraft.value = hexValue.value\n })\n return\n }\n\n commit(props.alpha ? parsed : { ...parsed, a: state.value.a })\n}\n\nconst isOpen = ref(false)\nconst panelOpen = computed({\n get: () => props.open ?? isOpen.value,\n set: (value) => {\n isOpen.value = value\n emit('update:open', value)\n },\n})\n\nconst presetList = computed(() => props.presets.filter(preset => parseHexColor(preset) !== null))\n\nfunction isPresetSelected(preset: string): boolean {\n const parsed = parseHexColor(preset)\n return parsed ? formatHexColor(parsed, props.alpha) === hexValue.value : false\n}\n\nfunction selectPreset(preset: string): void {\n const parsed = parseHexColor(preset)\n if (parsed)\n commit(parsed)\n}\n\nconst triggerClass = computed(() => grColorPickerTriggerClass({\n size: resolvedSize.value,\n disabled: isDisabled.value,\n invalid: isInvalid.value,\n}))\n\nconst panelClass = computed(() => grColorPickerPanelClass(resolvedSize.value))\nconst swatchSizeClass = computed(() => triggerSwatchSizeBySize[resolvedSize.value])\n\n/**\n * Градиенты дорожек. Через `--gr-slider-rail` их не подать: хук уходит в\n * `background-color`, а градиент там не работает — поэтому значения приезжают\n * переменными, а красит дорожки собственный `<style>` компонента.\n *\n * Переменные висят на самой панели, а не на корне: панель уезжает в портал, и\n * наследование от корня до неё не доходит.\n */\nconst trackVars = computed<Record<string, string>>(() => {\n const { h, s, l } = state.value\n const solid = hslaToCss({ h, s, l, a: 1 })\n\n return {\n '--gr-color-picker-track-hue': 'linear-gradient(to right, hsl(0 100% 50%), hsl(60 100% 50%), hsl(120 100% 50%), hsl(180 100% 50%), hsl(240 100% 50%), hsl(300 100% 50%), hsl(360 100% 50%))',\n '--gr-color-picker-track-saturation': `linear-gradient(to right, hsl(${Math.round(h)} 0% ${Math.round(l)}%), hsl(${Math.round(h)} 100% ${Math.round(l)}%))`,\n '--gr-color-picker-track-lightness': `linear-gradient(to right, hsl(${Math.round(h)} ${Math.round(s)}% 0%), hsl(${Math.round(h)} ${Math.round(s)}% 50%), hsl(${Math.round(h)} ${Math.round(s)}% 100%))`,\n '--gr-color-picker-track-alpha': `linear-gradient(to right, transparent, ${solid})`,\n }\n})\n</script>\n\n<template>\n <div data-gr-color-picker>\n <!-- Значение для нативной формы уходит скрытым полем: интерактивного\n контрола внутри виджета быть не должно. -->\n <input v-if=\"name\" type=\"hidden\" :name=\"name\" :value=\"hexValue\">\n\n <span v-if=\"stateHint\" :id=\"stateHintId\" class=\"sr-only\">{{ stateHint }}</span>\n\n <GrPopover\n v-model:open=\"panelOpen\"\n block\n :size=\"resolvedSize\"\n :placement=\"placement\"\n :disabled=\"isDisabled\"\n :aria-label=\"t('gr.colorPicker.panelLabel', 'Color picker')\"\n :close-on-content-click=\"false\"\n >\n <template #trigger=\"{ triggerProps }\">\n <button\n :id=\"fieldId\"\n ref=\"triggerEl\"\n v-bind=\"triggerProps\"\n data-gr-color-picker-trigger\n type=\"button\"\n :class=\"triggerClass\"\n :aria-label=\"ariaLabel\"\n :aria-describedby=\"describedBy\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n @focus=\"emit('focus', $event)\"\n @blur=\"emit('blur', $event)\"\n >\n <span\n data-gr-color-picker-swatch\n aria-hidden=\"true\"\n :class=\"[swatchBaseClass, swatchSizeClass, checkerClass]\"\n >\n <span :class=\"swatchFillClass\" :style=\"{ background: cssColor }\" />\n </span>\n\n <span :class=\"triggerValueClass\">{{ hexValue }}</span>\n </button>\n </template>\n\n <template #content>\n <div data-gr-color-picker-panel :class=\"panelClass\" :style=\"trackVars\">\n <div\n data-gr-color-picker-preview\n aria-hidden=\"true\"\n :class=\"[previewClass, checkerClass]\"\n >\n <span :class=\"swatchFillClass\" :style=\"{ background: cssColor }\" />\n </div>\n\n <div data-gr-color-picker-channel=\"hue\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">H</span>\n <GrSlider\n v-model=\"hue\"\n :min=\"0\"\n :max=\"359\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"degrees\"\n :aria-label=\"t('gr.colorPicker.hue', 'Hue')\"\n />\n <span :class=\"rowValueClass\">{{ degrees(hue) }}</span>\n </div>\n\n <div data-gr-color-picker-channel=\"saturation\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">S</span>\n <GrSlider\n v-model=\"saturation\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"percent\"\n :aria-label=\"t('gr.colorPicker.saturation', 'Saturation')\"\n />\n <span :class=\"rowValueClass\">{{ percent(saturation) }}</span>\n </div>\n\n <div data-gr-color-picker-channel=\"lightness\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">L</span>\n <GrSlider\n v-model=\"lightness\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"percent\"\n :aria-label=\"t('gr.colorPicker.lightness', 'Lightness')\"\n />\n <span :class=\"rowValueClass\">{{ percent(lightness) }}</span>\n </div>\n\n <div v-if=\"alpha\" data-gr-color-picker-channel=\"alpha\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">A</span>\n <GrSlider\n v-model=\"opacity\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"percent\"\n :aria-label=\"t('gr.colorPicker.opacity', 'Opacity')\"\n />\n <span :class=\"rowValueClass\">{{ percent(opacity) }}</span>\n </div>\n\n <GrInput\n v-model=\"hexDraft\"\n data-gr-color-picker-hex\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :aria-label=\"t('gr.colorPicker.hexLabel', 'Hex value')\"\n @change=\"commitHex\"\n @keydown.enter=\"commitHex\"\n />\n\n <div\n v-if=\"presetList.length\"\n data-gr-color-picker-presets\n :class=\"presetsGridClass\"\n role=\"group\"\n :aria-label=\"t('gr.colorPicker.presetsLabel', 'Color presets')\"\n >\n <button\n v-for=\"preset in presetList\"\n :key=\"preset\"\n data-gr-color-picker-preset\n type=\"button\"\n :class=\"[grColorPickerPresetClass(isPresetSelected(preset)), checkerClass]\"\n :aria-label=\"t('gr.colorPicker.swatchLabel', 'Color {color}', { color: preset })\"\n :aria-pressed=\"isPresetSelected(preset) ? 'true' : 'false'\"\n :disabled=\"isLocked\"\n @click=\"selectPreset(preset)\"\n >\n <span :class=\"swatchFillClass\" :style=\"{ background: preset }\" />\n </button>\n </div>\n </div>\n </template>\n </GrPopover>\n </div>\n</template>\n\n<style>\n/*\n * Шахматка под прозрачным цветом. Двумя `conic-gradient` — в утилите такое не\n * выражается, а без подложки прозрачный образец сливается с панелью.\n */\n.gr-color-picker-checker {\n background-image:\n conic-gradient(\n from 90deg,\n var(--gr-color-picker-checker-light, var(--gr-bg)) 0 25%,\n var(--gr-color-picker-checker-dark, var(--gr-muted)) 0 50%,\n var(--gr-color-picker-checker-light, var(--gr-bg)) 0 75%,\n var(--gr-color-picker-checker-dark, var(--gr-muted)) 0\n );\n background-size:\n calc(var(--gr-color-picker-checker-size, 6px) * 2)\n calc(var(--gr-color-picker-checker-size, 6px) * 2);\n /* Рамка образца полупрозрачна, и без обрезки шахматка просвечивает сквозь неё. */\n background-clip: padding-box;\n}\n\n/*\n * Дорожки каналов. Красим `[data-gr-slider-track]` потому, что хук\n * `--gr-slider-rail` уходит в `background-color`, а градиент там не работает.\n * Сами градиенты компонент подаёт переменными — CSS остаётся статичной.\n */\n[data-gr-color-picker-channel] [data-gr-slider-track] {\n border-radius: var(--gr-radius-full);\n}\n\n[data-gr-color-picker-channel='hue'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-hue);\n}\n\n[data-gr-color-picker-channel='saturation'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-saturation);\n}\n\n[data-gr-color-picker-channel='lightness'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-lightness);\n}\n\n[data-gr-color-picker-channel='alpha'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-alpha);\n}\n\n/* Заливка канала прозрачна: шкалу показывает сама дорожка. */\n[data-gr-color-picker-channel] [data-gr-slider-fill] {\n background-color: transparent;\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * GrColorPicker — поле выбора цвета: образец с текущим значением, панель с\n * оттенком, насыщенностью, светлотой и прозрачностью, поле hex и палитра.\n *\n * Прямой сценарий — настройки темы и брендирования, поэтому модель хранит hex:\n * в такой форме цвет лежит в токенах и его понимает CSS.\n *\n * A11y: каждый канал — отдельный `GrSlider`, то есть настоящий `role=\"slider\"`\n * с полной клавиатурой и `aria-valuetext`. Двумерная область saturation/value\n * не берётся намеренно: это свой виджет со своей клавиатурой по двум осям, и\n * доступность там пришлось бы собирать с нуля.\n */\nimport { computed, nextTick, ref, useId, watch } from 'vue'\n\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport GrInput from '../GrInput/GrInput.vue'\nimport GrPopover from '../GrPopover/GrPopover.vue'\nimport GrSlider from '../GrSlider/GrSlider.vue'\n\nimport { formatHexColor, hslaToCss, normalizeHsla, parseHexColor, type GrHsla } from './color'\nimport {\n checkerClass,\n grColorPickerPanelClass,\n grColorPickerPresetClass,\n grColorPickerTriggerClass,\n presetsGridClass,\n previewClass,\n rowClass,\n rowLabelClass,\n rowValueClass,\n swatchBaseClass,\n swatchFillClass,\n triggerSwatchSizeBySize,\n triggerValueClass,\n type GrColorPickerSize,\n} from './grColorPickerStyles'\n\nexport type { GrColorPickerSize } from './grColorPickerStyles'\n\nexport interface GrColorPickerProps {\n /** Цвет в hex: `#RRGGBB`, а при `alpha` — `#RRGGBBAA`. Мусор не роняет компонент. */\n modelValue: string\n /** Четвёртый слайдер и восьмизначная форма hex. */\n alpha?: boolean\n /** Палитра быстрого выбора. Пусто — блок не рендерится. */\n presets?: string[]\n size?: GrColorPickerSize\n /** Контролируемое состояние панели (`v-model:open`). */\n open?: boolean\n /** Сторона, с которой раскрывается панель. */\n placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end'\n /** Имя для нативной формы: значение уходит скрытым полем. */\n name?: string\n disabled?: boolean\n /** Только для чтения: цвет видно, панель открывается, но значение не меняется. */\n readonly?: boolean\n invalid?: boolean\n required?: boolean\n ariaLabel?: string\n}\n\nexport interface GrColorPickerEmits {\n (e: 'update:modelValue', value: string): void\n (e: 'change', value: string): void\n (e: 'update:open', value: boolean): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(defineProps<GrColorPickerProps>(), {\n alpha: false,\n presets: () => [],\n // Дефолт живёт в резолвере: Vue подставил бы свой раньше, чем компонент\n // заглянет в `GrConfigProvider`.\n size: undefined,\n open: undefined,\n placement: 'bottom-start',\n name: undefined,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n ariaLabel: undefined,\n})\n\nconst emit = defineEmits<GrColorPickerEmits>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrColorPicker' })\n\nconst field = useGrFormFieldContext()\nconst fieldId = computed(() => field?.id.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n // «Ввод не принимается» — `disabled` или `readonly`: значение видно, но не меняется.\n locked: isLocked,\n} = useGrFormControl(() => props)\n\n/**\n * `aria-required` и `aria-readonly` роль `button` не поддерживает — axe роняет\n * это как critical `aria-allowed-attr`. Состояния уходят в описание триггера:\n * иначе они пропали бы для диктора совсем (маркер `*` у `GrFormField`\n * декоративен и скрыт).\n */\nconst stateHintId = useId()\nconst stateHint = computed(() => [\n isRequired.value ? t('gr.form.required', 'This field is required') : undefined,\n isReadonly.value ? t('gr.form.readonly', 'Read only') : undefined,\n].filter(Boolean).join('. ') || undefined)\n\nconst describedBy = computed(() => [\n field?.describedById.value,\n stateHint.value ? stateHintId : undefined,\n].filter(Boolean).join(' ') || undefined)\n\nconst triggerEl = ref<HTMLElement | null>(null)\n\nfunction focus(): void {\n triggerEl.value?.focus()\n}\n\nfunction blur(): void {\n triggerEl.value?.blur()\n}\n\ndefineExpose({ focus, blur })\n\nconst FALLBACK: GrHsla = { h: 0, s: 0, l: 0, a: 1 }\n\n/**\n * Рабочее состояние панели.\n *\n * Держать его отдельно от модели обязательно: hex — проекция с потерями. У\n * серого нет оттенка, и без собственного состояния бегунок оттенка прыгал бы\n * обратно на 0° при каждом движении.\n */\nconst state = ref<GrHsla>(parseHexColor(props.modelValue) ?? FALLBACK)\n\n/** Последнее отданное значение: вернувшись в проп, оно не должно сбрасывать состояние. */\nlet lastEmitted = ''\n\nwatch(() => props.modelValue, (next) => {\n if (next === lastEmitted)\n return\n\n const parsed = parseHexColor(next)\n if (parsed)\n state.value = parsed\n})\n\nconst cssColor = computed(() => hslaToCss(state.value))\nconst hexValue = computed(() => formatHexColor(state.value, props.alpha))\n\nfunction commit(next: GrHsla): void {\n if (isLocked.value)\n return\n\n state.value = normalizeHsla(next)\n\n const hex = formatHexColor(state.value, props.alpha)\n lastEmitted = hex\n emit('update:modelValue', hex)\n emit('change', hex)\n}\n\n/** Канал панели: слайдер читает состояние и коммитит новое значение целиком. */\nfunction channel(key: 'h' | 's' | 'l' | 'a', scale = 1) {\n return computed<number>({\n get: () => Math.round(state.value[key] * scale),\n set: value => commit({ ...state.value, [key]: value / scale }),\n })\n}\n\nconst hue = channel('h')\nconst saturation = channel('s')\nconst lightness = channel('l')\n// Альфа хранится долей, а слайдер ходит по процентам — иначе шаг был бы 0.01.\nconst opacity = channel('a', 100)\n\nconst degrees = (value: number): string => `${value}°`\nconst percent = (value: number): string => `${value}%`\n\n/** Текст поля hex редактируется свободно и коммитится только валидным. */\nconst hexDraft = ref(hexValue.value)\n\nwatch(hexValue, (next) => {\n hexDraft.value = next\n})\n\nfunction commitHex(): void {\n const parsed = parseHexColor(hexDraft.value)\n\n // Мусор откатывается к текущему значению: держать в поле невалидный текст\n // после коммита — значит показывать цвет, которого нет.\n //\n // Откат идёт следующим тиком намеренно: вернув значение синхронно, мы отдали\n // бы Vue то же самое, что он уже отрисовал, — патча не случилось бы, и в поле\n // остался бы набранный мусор.\n if (!parsed) {\n void nextTick(() => {\n hexDraft.value = hexValue.value\n })\n return\n }\n\n commit(props.alpha ? parsed : { ...parsed, a: state.value.a })\n}\n\nconst isOpen = ref(false)\nconst panelOpen = computed({\n get: () => props.open ?? isOpen.value,\n set: (value) => {\n isOpen.value = value\n emit('update:open', value)\n },\n})\n\nconst presetList = computed(() => props.presets.filter(preset => parseHexColor(preset) !== null))\n\nfunction isPresetSelected(preset: string): boolean {\n const parsed = parseHexColor(preset)\n return parsed ? formatHexColor(parsed, props.alpha) === hexValue.value : false\n}\n\nfunction selectPreset(preset: string): void {\n const parsed = parseHexColor(preset)\n if (parsed)\n commit(parsed)\n}\n\nconst triggerClass = computed(() => grColorPickerTriggerClass({\n size: resolvedSize.value,\n disabled: isDisabled.value,\n invalid: isInvalid.value,\n}))\n\nconst panelClass = computed(() => grColorPickerPanelClass(resolvedSize.value))\nconst swatchSizeClass = computed(() => triggerSwatchSizeBySize[resolvedSize.value])\n\n/**\n * Градиенты дорожек. Через `--gr-slider-rail` их не подать: хук уходит в\n * `background-color`, а градиент там не работает — поэтому значения приезжают\n * переменными, а красит дорожки собственный `<style>` компонента.\n *\n * Переменные висят на самой панели, а не на корне: панель уезжает в портал, и\n * наследование от корня до неё не доходит.\n */\nconst trackVars = computed<Record<string, string>>(() => {\n const { h, s, l } = state.value\n const solid = hslaToCss({ h, s, l, a: 1 })\n\n return {\n '--gr-color-picker-track-hue': 'linear-gradient(to right, hsl(0 100% 50%), hsl(60 100% 50%), hsl(120 100% 50%), hsl(180 100% 50%), hsl(240 100% 50%), hsl(300 100% 50%), hsl(360 100% 50%))',\n '--gr-color-picker-track-saturation': `linear-gradient(to right, hsl(${Math.round(h)} 0% ${Math.round(l)}%), hsl(${Math.round(h)} 100% ${Math.round(l)}%))`,\n '--gr-color-picker-track-lightness': `linear-gradient(to right, hsl(${Math.round(h)} ${Math.round(s)}% 0%), hsl(${Math.round(h)} ${Math.round(s)}% 50%), hsl(${Math.round(h)} ${Math.round(s)}% 100%))`,\n '--gr-color-picker-track-alpha': `linear-gradient(to right, transparent, ${solid})`,\n }\n})\n</script>\n\n<template>\n <div data-gr-color-picker>\n <!-- Значение для нативной формы уходит скрытым полем: интерактивного\n контрола внутри виджета быть не должно. -->\n <input v-if=\"name\" type=\"hidden\" :name=\"name\" :value=\"hexValue\">\n\n <span v-if=\"stateHint\" :id=\"stateHintId\" class=\"sr-only\">{{ stateHint }}</span>\n\n <GrPopover\n v-model:open=\"panelOpen\"\n block\n :size=\"resolvedSize\"\n :placement=\"placement\"\n :disabled=\"isDisabled\"\n :aria-label=\"t('gr.colorPicker.panelLabel', 'Color picker')\"\n :close-on-content-click=\"false\"\n >\n <template #trigger=\"{ triggerProps }\">\n <button\n :id=\"fieldId\"\n ref=\"triggerEl\"\n v-bind=\"triggerProps\"\n data-gr-color-picker-trigger\n type=\"button\"\n :class=\"triggerClass\"\n :aria-label=\"ariaLabel\"\n :aria-describedby=\"describedBy\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n @focus=\"emit('focus', $event)\"\n @blur=\"emit('blur', $event)\"\n >\n <span\n data-gr-color-picker-swatch\n aria-hidden=\"true\"\n :class=\"[swatchBaseClass, swatchSizeClass, checkerClass]\"\n >\n <span :class=\"swatchFillClass\" :style=\"{ background: cssColor }\" />\n </span>\n\n <span :class=\"triggerValueClass\">{{ hexValue }}</span>\n </button>\n </template>\n\n <template #content>\n <div data-gr-color-picker-panel :class=\"panelClass\" :style=\"trackVars\">\n <div\n data-gr-color-picker-preview\n aria-hidden=\"true\"\n :class=\"[previewClass, checkerClass]\"\n >\n <span :class=\"swatchFillClass\" :style=\"{ background: cssColor }\" />\n </div>\n\n <div data-gr-color-picker-channel=\"hue\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">H</span>\n <GrSlider\n v-model=\"hue\"\n :min=\"0\"\n :max=\"359\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"degrees\"\n :aria-label=\"t('gr.colorPicker.hue', 'Hue')\"\n />\n <span :class=\"rowValueClass\">{{ degrees(hue) }}</span>\n </div>\n\n <div data-gr-color-picker-channel=\"saturation\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">S</span>\n <GrSlider\n v-model=\"saturation\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"percent\"\n :aria-label=\"t('gr.colorPicker.saturation', 'Saturation')\"\n />\n <span :class=\"rowValueClass\">{{ percent(saturation) }}</span>\n </div>\n\n <div data-gr-color-picker-channel=\"lightness\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">L</span>\n <GrSlider\n v-model=\"lightness\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"percent\"\n :aria-label=\"t('gr.colorPicker.lightness', 'Lightness')\"\n />\n <span :class=\"rowValueClass\">{{ percent(lightness) }}</span>\n </div>\n\n <div v-if=\"alpha\" data-gr-color-picker-channel=\"alpha\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">A</span>\n <GrSlider\n v-model=\"opacity\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"percent\"\n :aria-label=\"t('gr.colorPicker.opacity', 'Opacity')\"\n />\n <span :class=\"rowValueClass\">{{ percent(opacity) }}</span>\n </div>\n\n <GrInput\n v-model=\"hexDraft\"\n data-gr-color-picker-hex\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :aria-label=\"t('gr.colorPicker.hexLabel', 'Hex value')\"\n @change=\"commitHex\"\n @keydown.enter=\"commitHex\"\n />\n\n <div\n v-if=\"presetList.length\"\n data-gr-color-picker-presets\n :class=\"presetsGridClass\"\n role=\"group\"\n :aria-label=\"t('gr.colorPicker.presetsLabel', 'Color presets')\"\n >\n <button\n v-for=\"preset in presetList\"\n :key=\"preset\"\n data-gr-color-picker-preset\n type=\"button\"\n :class=\"[grColorPickerPresetClass(isPresetSelected(preset)), checkerClass]\"\n :aria-label=\"t('gr.colorPicker.swatchLabel', 'Color {color}', { color: preset })\"\n :aria-pressed=\"isPresetSelected(preset) ? 'true' : 'false'\"\n :disabled=\"isLocked\"\n @click=\"selectPreset(preset)\"\n >\n <span :class=\"swatchFillClass\" :style=\"{ background: preset }\" />\n </button>\n </div>\n </div>\n </template>\n </GrPopover>\n </div>\n</template>\n\n<style>\n/*\n * Шахматка под прозрачным цветом. Двумя `conic-gradient` — в утилите такое не\n * выражается, а без подложки прозрачный образец сливается с панелью.\n */\n.gr-color-picker-checker {\n background-image:\n conic-gradient(\n from 90deg,\n var(--gr-color-picker-checker-light, var(--gr-bg)) 0 25%,\n var(--gr-color-picker-checker-dark, var(--gr-muted)) 0 50%,\n var(--gr-color-picker-checker-light, var(--gr-bg)) 0 75%,\n var(--gr-color-picker-checker-dark, var(--gr-muted)) 0\n );\n background-size:\n calc(var(--gr-color-picker-checker-size, 6px) * 2)\n calc(var(--gr-color-picker-checker-size, 6px) * 2);\n /* Рамка образца полупрозрачна, и без обрезки шахматка просвечивает сквозь неё. */\n background-clip: padding-box;\n}\n\n/*\n * Дорожки каналов. Красим `[data-gr-slider-track]` потому, что хук\n * `--gr-slider-rail` уходит в `background-color`, а градиент там не работает.\n * Сами градиенты компонент подаёт переменными — CSS остаётся статичной.\n */\n[data-gr-color-picker-channel] [data-gr-slider-track] {\n border-radius: var(--gr-radius-full);\n}\n\n[data-gr-color-picker-channel='hue'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-hue);\n}\n\n[data-gr-color-picker-channel='saturation'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-saturation);\n}\n\n[data-gr-color-picker-channel='lightness'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-lightness);\n}\n\n[data-gr-color-picker-channel='alpha'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-alpha);\n}\n\n/* Заливка канала прозрачна: шкалу показывает сама дорожка. */\n[data-gr-color-picker-channel] [data-gr-slider-fill] {\n background-color: transparent;\n}\n</style>\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { t as GrColorPicker_default } from "./chunks/GrColorPicker-
|
|
1
|
+
import { t as GrColorPicker_default } from "./chunks/GrColorPicker-9mTR-YZ_.js";
|
|
2
2
|
import { n as grColorPickerSafelist, t as grColorPickerConfig } from "../../chunks/config-DIDz_E5m.js";
|
|
3
3
|
export { GrColorPicker_default as GrColorPicker, GrColorPicker_default as default, grColorPickerConfig, grColorPickerSafelist };
|
package/dist/components/GrDataTable/chunks/{GrDataTable-BBe7-uUe.js → GrDataTable-xNSmO_I-.js}
RENAMED
|
@@ -4,11 +4,12 @@ import { t as loader_circle_default } from "../../../chunks/loader-circle-CfVLz7
|
|
|
4
4
|
import { t as GrIcon_default } from "../../GrIcon/chunks/GrIcon-DTZIgmje.js";
|
|
5
5
|
import { useVirtualList } from "../../../composables/useVirtualList.js";
|
|
6
6
|
import { useRovingFocus } from "../../../composables/useRovingFocus.js";
|
|
7
|
-
import {
|
|
7
|
+
import { useAnnouncer } from "../../../composables/useAnnouncer.js";
|
|
8
8
|
import { useDragGesture } from "../../../composables/useDragGesture.js";
|
|
9
|
+
import { t as GrCheckbox_default } from "../../GrCheckbox/chunks/GrCheckbox-Dp201frb.js";
|
|
9
10
|
import { t as GrTable_default } from "../../GrTable/chunks/GrTable-iwzHvlMC.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
11
|
+
import { i as moveItem, r as insertionIndex } from "../../../chunks/dragSortGeometry-CY-llfrQ.js";
|
|
12
|
+
import { useDragSort } from "../../../composables/useDragSort.js";
|
|
12
13
|
import { C as sortIconSizes, S as selectColumnWidths, T as summaryRowClass, _ as headerTextSizes, a as columnDropAfterClass, b as rowSelectedClass, c as columnHandleClass, d as columnPinnedRightEdgeClass, f as columnResizerClass, g as headerGaps, h as columnResizerLineClass, i as columnDraggingClass, l as columnPinnedClass, m as columnResizerLineActiveClass, o as columnDropBeforeClass, p as columnResizerHoverClass, r as cellPaddings, s as columnHandleActiveClass, u as columnPinnedLeftEdgeClass, v as placeholderPaddings, w as spinnerSizes, x as selectCheckboxSizes, y as rowHeightEstimates } from "../../../chunks/config-DeyT9dJl.js";
|
|
13
14
|
import { t as arrow_up_default } from "../../../chunks/arrow-up-C_G4jg8M.js";
|
|
14
15
|
import { t as grip_vertical_default } from "../../../chunks/grip-vertical-lRha2S7q.js";
|