@feugene/granularity 0.9.1 → 0.9.3

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.
Files changed (22) hide show
  1. package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-DuUQA7nv.js → GrConfirmDialog-Br6WJkLE.js} +1 -1
  2. package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-DuUQA7nv.js.map → GrConfirmDialog-Br6WJkLE.js.map} +1 -1
  3. package/dist/components/GrConfirmDialog/index.js +1 -1
  4. package/dist/components/GrDialog/chunks/{GrDialog-SYcF-4op.js → GrDialog-C7XLxp8e.js} +1 -1
  5. package/dist/components/GrDialog/chunks/{GrDialog-SYcF-4op.js.map → GrDialog-C7XLxp8e.js.map} +1 -1
  6. package/dist/components/GrDialog/index.js +1 -1
  7. package/dist/components/GrImageViewer/chunks/{GrImageViewer-Cn1X-sWS.js → GrImageViewer-CsvXQHM9.js} +203 -50
  8. package/dist/components/GrImageViewer/chunks/GrImageViewer-CsvXQHM9.js.map +1 -0
  9. package/dist/components/GrImageViewer/index.js +1 -1
  10. package/dist/components/GrModal/chunks/{GrModal-2ALC3kwy.js → GrModal-lh0lOmoi.js} +60 -8
  11. package/dist/components/GrModal/chunks/GrModal-lh0lOmoi.js.map +1 -0
  12. package/dist/components/GrModal/index.js +1 -1
  13. package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-23x2nZvv.js → GrPromptDialog-BV4EW3XO.js} +1 -1
  14. package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-23x2nZvv.js.map → GrPromptDialog-BV4EW3XO.js.map} +1 -1
  15. package/dist/components/GrPromptDialog/index.js +1 -1
  16. package/dist/index.js +5 -5
  17. package/dist/types/src/components/GrImageViewer/GrImageViewer.vue.d.ts +30 -0
  18. package/dist/types/src/components/GrModal/GrModal.vue.d.ts +4 -4
  19. package/dist/types/src/components/GrModal/grModalTopStack.d.ts +31 -0
  20. package/package.json +1 -1
  21. package/dist/components/GrImageViewer/chunks/GrImageViewer-Cn1X-sWS.js.map +0 -1
  22. package/dist/components/GrModal/chunks/GrModal-2ALC3kwy.js.map +0 -1
@@ -5,13 +5,13 @@ export interface GrModalProps {
5
5
  closeOnEsc?: boolean;
6
6
  size?: GrModalSize;
7
7
  }
8
- declare var __VLS_47: {}, __VLS_55: {}, __VLS_57: {};
8
+ declare var __VLS_49: {}, __VLS_57: {}, __VLS_59: {};
9
9
  type __VLS_Slots = {} & {
10
- title?: (props: typeof __VLS_47) => any;
10
+ title?: (props: typeof __VLS_49) => any;
11
11
  } & {
12
- description?: (props: typeof __VLS_55) => any;
12
+ description?: (props: typeof __VLS_57) => any;
13
13
  } & {
14
- default?: (props: typeof __VLS_57) => any;
14
+ default?: (props: typeof __VLS_59) => any;
15
15
  };
16
16
  declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPropTypes<__VLS_WithDefaults<__VLS_TypePropsToOption<GrModalProps>, {
17
17
  closeOnBackdrop: boolean;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * grModalTopStack — общий стек открытых `GrModal` для вычисления «верхнего»
3
+ * (последнего открытого) окна независимо от дерева рендера.
4
+ *
5
+ * Зачем нужен:
6
+ * HeadlessUI ловит фокус (`FocusTrap` с `FocusLock`) у каждого открытого
7
+ * `<Dialog>` и помечает остальную часть страницы как inert. Когда модалки
8
+ * рендерятся в разных деревьях (например, диалоги `useDialogService`
9
+ * монтируются отдельным `render()` в `document.body`), их HeadlessUI-стек не
10
+ * общий: каждый `<Dialog>` считает себя верхним и продолжает удерживать фокус.
11
+ * В итоге нижнее окно «ворует» фокус у верхнего диалога — нельзя, например,
12
+ * сфокусировать input в открытом поверх `prompt`.
13
+ *
14
+ * Решение: единый стек регистраций. Верхней считается последняя
15
+ * зарегистрированная модалка. Все нижние окна уведомляются (`setTopmost(false)`)
16
+ * и помечают свой корень `inert`, освобождая фокус для верхнего окна. Когда
17
+ * верхнее окно закрывается, следующая по стеку модалка снова становится
18
+ * верхней (`setTopmost(true)`) и снимает `inert`.
19
+ */
20
+ export interface GrModalTopEntry {
21
+ /** Уникальный id зарегистрированной модалки. */
22
+ id: number;
23
+ /** Колбэк-уведомление: является ли модалка сейчас верхней. */
24
+ setTopmost: (isTopmost: boolean) => void;
25
+ }
26
+ /** Регистрирует открытую модалку как верхнюю и возвращает её id. */
27
+ export declare function pushGrModalTop(entry: Omit<GrModalTopEntry, 'id'>): number;
28
+ /** Снимает модалку со стека (при закрытии или размонтировании). */
29
+ export declare function removeGrModalTop(id: number): void;
30
+ /** Тестовая/служебная очистка стека. */
31
+ export declare function resetGrModalTopStack(): void;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@feugene/granularity",
3
3
  "description": "Granularity design system package with Vue 3 components, consumed via the `@feugene/unocss-preset-granular` preset.",
4
- "version": "0.9.1",
4
+ "version": "0.9.3",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": {
7
7
  "name": "Evgeniy Fureev",
@@ -1 +0,0 @@
1
- {"version":3,"file":"GrImageViewer-Cn1X-sWS.js","names":["$slots"],"sources":["../../../../src/components/GrImageViewer/GrImageViewer.vue","../../../../src/components/GrImageViewer/GrImageViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { Dialog, DialogPanel, TransitionChild, TransitionRoot } from '@headlessui/vue'\n\n/**\n * Usage:\n *\n * <GrImageViewer\n * v-model=\"open\"\n * :url-list=\"images\"\n * :initial-index=\"0\"\n * show-progress\n * hide-on-click-modal\n * />\n */\nconst props = withDefaults(\n defineProps<{\n modelValue: boolean\n urlList: string[]\n initialIndex?: number\n zoomRate?: number\n minScale?: number\n maxScale?: number\n hideOnClickModal?: boolean\n closeOnPressEscape?: boolean\n showProgress?: boolean\n showZoomValue?: boolean\n zIndex?: number\n /** i18n: aria-label кнопки закрытия. */\n closeLabel?: string\n /** i18n: aria-label кнопки «предыдущее изображение». */\n prevLabel?: string\n /** i18n: aria-label кнопки «следующее изображение». */\n nextLabel?: string\n /** i18n: aria-label кнопки «увеличить». */\n zoomInLabel?: string\n /** i18n: aria-label кнопки «уменьшить». */\n zoomOutLabel?: string\n /** i18n: aria-label кнопки «сбросить масштаб». */\n resetZoomLabel?: string\n /** i18n: aria-label кнопки «повернуть влево». */\n rotateLeftLabel?: string\n /** i18n: aria-label кнопки «повернуть вправо». */\n rotateRightLabel?: string\n /** i18n: текст в пустом состоянии (нет изображений). */\n emptyText?: string\n }>(),\n {\n initialIndex: 0,\n zoomRate: 1.2,\n minScale: 0.5,\n maxScale: 5,\n hideOnClickModal: false,\n closeOnPressEscape: true,\n showProgress: false,\n showZoomValue: true,\n zIndex: undefined,\n closeLabel: 'Close image viewer',\n prevLabel: 'Previous image',\n nextLabel: 'Next image',\n zoomInLabel: 'Zoom in',\n zoomOutLabel: 'Zoom out',\n resetZoomLabel: 'Reset zoom',\n rotateLeftLabel: 'Rotate left',\n rotateRightLabel: 'Rotate right',\n emptyText: 'No image',\n },\n)\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: boolean): void\n (e: 'close'): void\n (e: 'switch', newIndex: number): void\n (e: 'rotate', deg: number): void\n}>()\n\ntype GrImageViewerToolbarActions = {\n close: () => void\n prev: () => void\n next: () => void\n zoomIn: () => void\n zoomOut: () => void\n reset: () => void\n rotateLeft: () => void\n rotateRight: () => void\n}\n\nconst DEFAULT_Z_INDEX = 2000\nconst TRAILING_ZERO_DECIMAL_RE = /\\.0$/\n\nconst open = computed(() => props.modelValue)\nconst total = computed(() => props.urlList.length)\nconst hasImages = computed(() => total.value > 0)\n\nconst currentIndex = ref(0)\nconst scale = ref(1)\nconst rotation = ref(0)\nconst offsetX = ref(0)\nconst offsetY = ref(0)\n\nconst currentUrl = computed(() => {\n if (!hasImages.value) {\n return undefined\n }\n\n return props.urlList[currentIndex.value]\n})\n\nconst displayIndex = computed(() => {\n if (!hasImages.value) {\n return 0\n }\n\n return currentIndex.value + 1\n})\n\nconst viewerStyle = computed(() => ({\n zIndex: String(Number.isFinite(props.zIndex) ? props.zIndex : DEFAULT_Z_INDEX),\n}))\n\nconst imageStyle = computed(() => ({\n transform: `translate3d(${offsetX.value}px, ${offsetY.value}px, 0) scale(${scale.value}) rotate(${rotation.value}deg)`,\n}))\n\nconst zoomValueText = computed(() => {\n const zoomPercent = scale.value * 100\n\n if (Number.isInteger(zoomPercent)) {\n return String(zoomPercent)\n }\n\n return zoomPercent.toFixed(1).replace(TRAILING_ZERO_DECIMAL_RE, '')\n})\n\nfunction normalizeIndex(value: number): number {\n if (!hasImages.value) {\n return 0\n }\n\n const normalized = Number.isFinite(value) ? Math.trunc(value) : 0\n\n return ((normalized % total.value) + total.value) % total.value\n}\n\nfunction resetTransform(): void {\n scale.value = 1\n rotation.value = 0\n offsetX.value = 0\n offsetY.value = 0\n}\n\nfunction setIndex(nextIndex: number, options?: { emitSwitch?: boolean }): void {\n const normalizedIndex = normalizeIndex(nextIndex)\n\n if (normalizedIndex === currentIndex.value) {\n return\n }\n\n currentIndex.value = normalizedIndex\n resetTransform()\n\n if (options?.emitSwitch !== false) {\n emit('switch', normalizedIndex)\n }\n}\n\nfunction syncIndexFromInitial(): void {\n if (!hasImages.value) {\n currentIndex.value = 0\n return\n }\n\n currentIndex.value = normalizeIndex(props.initialIndex)\n resetTransform()\n}\n\nfunction preloadAt(index: number): void {\n const url = props.urlList[index]\n\n if (!url) {\n return\n }\n\n const image = new Image()\n image.decoding = 'async'\n image.src = url\n}\n\nfunction closeViewer(): void {\n emit('update:modelValue', false)\n emit('close')\n}\n\nfunction prev(): void {\n setIndex(currentIndex.value - 1)\n}\n\nfunction next(): void {\n setIndex(currentIndex.value + 1)\n}\n\nfunction zoomIn(): void {\n scale.value = Math.min(props.maxScale, Number((scale.value * props.zoomRate).toFixed(4)))\n}\n\nfunction zoomOut(): void {\n scale.value = Math.max(props.minScale, Number((scale.value / props.zoomRate).toFixed(4)))\n}\n\nfunction rotateLeft(): void {\n rotation.value -= 90\n emit('rotate', rotation.value)\n}\n\nfunction rotateRight(): void {\n rotation.value += 90\n emit('rotate', rotation.value)\n}\n\nconst toolbarActions: GrImageViewerToolbarActions = {\n close: closeViewer,\n prev,\n next,\n zoomIn,\n zoomOut,\n reset: resetTransform,\n rotateLeft,\n rotateRight,\n}\n\nfunction onBackdropClick(): void {\n if (!props.hideOnClickModal) {\n return\n }\n\n closeViewer()\n}\n\nfunction onKeydown(event: KeyboardEvent): void {\n switch (event.key) {\n case 'Escape':\n if (!props.closeOnPressEscape) {\n return\n }\n\n event.preventDefault()\n closeViewer()\n return\n\n case 'ArrowLeft':\n event.preventDefault()\n prev()\n return\n\n case 'ArrowRight':\n event.preventDefault()\n next()\n return\n\n case '+':\n case '=':\n case 'Add':\n event.preventDefault()\n zoomIn()\n return\n\n case '-':\n case '_':\n case 'Subtract':\n event.preventDefault()\n zoomOut()\n return\n\n case '0':\n event.preventDefault()\n resetTransform()\n break\n\n default:\n }\n}\n\nwatch(\n () => props.modelValue,\n (isOpen) => {\n if (!isOpen) {\n return\n }\n\n syncIndexFromInitial()\n },\n { immediate: true },\n)\n\nwatch(\n () => props.initialIndex,\n () => {\n if (!props.modelValue) {\n return\n }\n\n syncIndexFromInitial()\n },\n)\n\nwatch(\n () => props.urlList,\n () => {\n if (!props.modelValue) {\n return\n }\n\n syncIndexFromInitial()\n },\n { deep: true },\n)\n\nwatch(\n currentIndex,\n (index) => {\n if (!hasImages.value || total.value < 2) {\n return\n }\n\n preloadAt(normalizeIndex(index - 1))\n preloadAt(normalizeIndex(index + 1))\n },\n { immediate: true },\n)\n</script>\n\n<template>\n <teleport to=\"body\">\n <TransitionRoot :show=\"open\" as=\"template\">\n <Dialog\n as=\"div\"\n class=\"fixed inset-0\"\n :style=\"viewerStyle\"\n role=\"dialog\"\n aria-modal=\"true\"\n :static=\"true\"\n @keydown=\"onKeydown\"\n >\n <div class=\"fixed inset-0 overflow-hidden\">\n <TransitionChild\n as=\"template\"\n enter=\"duration-150 ease-out\"\n enter-from=\"opacity-0\"\n enter-to=\"opacity-100\"\n leave=\"duration-120 ease-in\"\n leave-from=\"opacity-100\"\n leave-to=\"opacity-0\"\n >\n <div\n data-ds-image-viewer-overlay\n class=\"absolute inset-0 bg-black/60 backdrop-blur-sm\"\n aria-hidden=\"true\"\n @click=\"onBackdropClick\"\n />\n </TransitionChild>\n\n <TransitionChild\n as=\"template\"\n enter=\"duration-180 ease-out\"\n enter-from=\"opacity-0 scale-98\"\n enter-to=\"opacity-100 scale-100\"\n leave=\"duration-130 ease-in\"\n leave-from=\"opacity-100 scale-100\"\n leave-to=\"opacity-0 scale-98\"\n >\n <DialogPanel\n data-ds-image-viewer-panel\n class=\"relative z-10 h-full w-full outline-none\"\n tabindex=\"-1\"\n >\n <div class=\"pointer-events-none absolute inset-x-0 top-0 z-30 px-3 py-3 sm:px-6\">\n <div class=\"pointer-events-auto flex items-center justify-between gap-3\">\n <div class=\"flex items-center gap-2\">\n <div\n v-if=\"showProgress\"\n data-ds-image-viewer-progress\n class=\"rounded-full bg-black/35 px-3 py-1 text-xs font-600 text-white/95 sm:text-sm\"\n >\n {{ displayIndex }} / {{ total }}\n </div>\n\n <div\n v-if=\"showZoomValue\"\n data-ds-image-viewer-zoom-value\n class=\"rounded-full bg-black/35 px-3 py-1 text-xs font-700 text-white/95 sm:text-sm\"\n >\n {{ zoomValueText }}%\n </div>\n </div>\n\n <button\n type=\"button\"\n data-ds-image-viewer-close\n :aria-label=\"closeLabel\"\n class=\"h-11 w-11 flex items-center justify-center rounded-full border border-white/20 bg-black/35 text-white transition-colors hover:bg-black/55 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"closeViewer\"\n >\n <span aria-hidden=\"true\" class=\"text-lg leading-none\">✕</span>\n </button>\n </div>\n </div>\n\n <div class=\"relative z-10 h-full w-full flex items-center justify-center px-16 py-16 sm:px-24 sm:py-20\">\n <button\n v-if=\"total > 1\"\n type=\"button\"\n data-ds-image-viewer-prev\n :aria-label=\"prevLabel\"\n class=\"absolute left-3 top-1/2 z-20 h-12 w-12 -translate-y-1/2 flex items-center justify-center rounded-full border border-white/20 bg-black/35 text-white transition-colors hover:bg-black/55 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80 sm:left-6\"\n @click=\"prev\"\n >\n <span aria-hidden=\"true\" class=\"text-xl leading-none\">‹</span>\n </button>\n\n <button\n v-if=\"total > 1\"\n type=\"button\"\n data-ds-image-viewer-next\n :aria-label=\"nextLabel\"\n class=\"absolute right-3 top-1/2 z-20 h-12 w-12 -translate-y-1/2 flex items-center justify-center rounded-full border border-white/20 bg-black/35 text-white transition-colors hover:bg-black/55 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80 sm:right-6\"\n @click=\"next\"\n >\n <span aria-hidden=\"true\" class=\"text-xl leading-none\">›</span>\n </button>\n\n <div class=\"h-full w-full flex items-center justify-center\">\n <img\n v-if=\"currentUrl\"\n data-ds-image-viewer-image\n :src=\"currentUrl\"\n alt=\"\"\n draggable=\"false\"\n class=\"max-h-full max-w-full select-none object-contain will-change-transform transition-transform duration-150 ease-out\"\n :style=\"imageStyle\"\n >\n\n <div\n v-else\n class=\"rounded-[var(--ds-radius-xl)] border border-white/20 bg-black/25 px-4 py-3 text-sm text-white/80\"\n >\n {{ emptyText }}\n </div>\n </div>\n </div>\n\n <div class=\"pointer-events-none absolute inset-x-0 bottom-0 z-30 px-3 pb-3 sm:px-6 sm:pb-6\">\n <div class=\"pointer-events-auto mx-auto w-full max-w-max\">\n <slot\n name=\"toolbar\"\n :index=\"currentIndex\"\n :display-index=\"displayIndex\"\n :total=\"total\"\n :scale=\"scale\"\n :rotation=\"rotation\"\n :actions=\"toolbarActions\"\n >\n <div class=\"rounded-full border border-white/20 bg-black/35 p-1 backdrop-blur-sm flex items-center gap-1\">\n <button\n type=\"button\"\n data-ds-image-viewer-zoom-out\n :aria-label=\"zoomOutLabel\"\n class=\"h-11 min-w-11 px-2 flex items-center justify-center rounded-full text-sm font-600 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"zoomOut\"\n >\n −\n </button>\n\n <button\n type=\"button\"\n data-ds-image-viewer-zoom-reset\n :aria-label=\"resetZoomLabel\"\n class=\"h-11 min-w-11 px-3 flex items-center justify-center rounded-full text-xs font-700 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"resetTransform\"\n >\n 100%\n </button>\n\n <button\n type=\"button\"\n data-ds-image-viewer-zoom-in\n :aria-label=\"zoomInLabel\"\n class=\"h-11 min-w-11 px-2 flex items-center justify-center rounded-full text-sm font-600 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"zoomIn\"\n >\n +\n </button>\n\n <template v-if=\"$slots['toolbar-actions']\">\n <div class=\"mx-1 h-6 w-px bg-white/25\" aria-hidden=\"true\" />\n\n <slot\n name=\"toolbar-actions\"\n :index=\"currentIndex\"\n :display-index=\"displayIndex\"\n :total=\"total\"\n :scale=\"scale\"\n :rotation=\"rotation\"\n :actions=\"toolbarActions\"\n />\n\n <div class=\"mx-1 h-6 w-px bg-white/25\" aria-hidden=\"true\" />\n </template>\n\n <div v-else class=\"mx-1 h-6 w-px bg-white/25\" aria-hidden=\"true\" />\n\n <button\n type=\"button\"\n data-ds-image-viewer-rotate-left\n :aria-label=\"rotateLeftLabel\"\n class=\"h-11 min-w-11 px-2 flex items-center justify-center rounded-full text-sm font-600 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"rotateLeft\"\n >\n ↺\n </button>\n\n <button\n type=\"button\"\n data-ds-image-viewer-rotate-right\n :aria-label=\"rotateRightLabel\"\n class=\"h-11 min-w-11 px-2 flex items-center justify-center rounded-full text-sm font-600 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"rotateRight\"\n >\n ↻\n </button>\n </div>\n </slot>\n </div>\n </div>\n </DialogPanel>\n </TransitionChild>\n </div>\n </Dialog>\n </TransitionRoot>\n </teleport>\n</template>","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\nimport { Dialog, DialogPanel, TransitionChild, TransitionRoot } from '@headlessui/vue'\n\n/**\n * Usage:\n *\n * <GrImageViewer\n * v-model=\"open\"\n * :url-list=\"images\"\n * :initial-index=\"0\"\n * show-progress\n * hide-on-click-modal\n * />\n */\nconst props = withDefaults(\n defineProps<{\n modelValue: boolean\n urlList: string[]\n initialIndex?: number\n zoomRate?: number\n minScale?: number\n maxScale?: number\n hideOnClickModal?: boolean\n closeOnPressEscape?: boolean\n showProgress?: boolean\n showZoomValue?: boolean\n zIndex?: number\n /** i18n: aria-label кнопки закрытия. */\n closeLabel?: string\n /** i18n: aria-label кнопки «предыдущее изображение». */\n prevLabel?: string\n /** i18n: aria-label кнопки «следующее изображение». */\n nextLabel?: string\n /** i18n: aria-label кнопки «увеличить». */\n zoomInLabel?: string\n /** i18n: aria-label кнопки «уменьшить». */\n zoomOutLabel?: string\n /** i18n: aria-label кнопки «сбросить масштаб». */\n resetZoomLabel?: string\n /** i18n: aria-label кнопки «повернуть влево». */\n rotateLeftLabel?: string\n /** i18n: aria-label кнопки «повернуть вправо». */\n rotateRightLabel?: string\n /** i18n: текст в пустом состоянии (нет изображений). */\n emptyText?: string\n }>(),\n {\n initialIndex: 0,\n zoomRate: 1.2,\n minScale: 0.5,\n maxScale: 5,\n hideOnClickModal: false,\n closeOnPressEscape: true,\n showProgress: false,\n showZoomValue: true,\n zIndex: undefined,\n closeLabel: 'Close image viewer',\n prevLabel: 'Previous image',\n nextLabel: 'Next image',\n zoomInLabel: 'Zoom in',\n zoomOutLabel: 'Zoom out',\n resetZoomLabel: 'Reset zoom',\n rotateLeftLabel: 'Rotate left',\n rotateRightLabel: 'Rotate right',\n emptyText: 'No image',\n },\n)\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: boolean): void\n (e: 'close'): void\n (e: 'switch', newIndex: number): void\n (e: 'rotate', deg: number): void\n}>()\n\ntype GrImageViewerToolbarActions = {\n close: () => void\n prev: () => void\n next: () => void\n zoomIn: () => void\n zoomOut: () => void\n reset: () => void\n rotateLeft: () => void\n rotateRight: () => void\n}\n\nconst DEFAULT_Z_INDEX = 2000\nconst TRAILING_ZERO_DECIMAL_RE = /\\.0$/\n\nconst open = computed(() => props.modelValue)\nconst total = computed(() => props.urlList.length)\nconst hasImages = computed(() => total.value > 0)\n\nconst currentIndex = ref(0)\nconst scale = ref(1)\nconst rotation = ref(0)\nconst offsetX = ref(0)\nconst offsetY = ref(0)\n\nconst currentUrl = computed(() => {\n if (!hasImages.value) {\n return undefined\n }\n\n return props.urlList[currentIndex.value]\n})\n\nconst displayIndex = computed(() => {\n if (!hasImages.value) {\n return 0\n }\n\n return currentIndex.value + 1\n})\n\nconst viewerStyle = computed(() => ({\n zIndex: String(Number.isFinite(props.zIndex) ? props.zIndex : DEFAULT_Z_INDEX),\n}))\n\nconst imageStyle = computed(() => ({\n transform: `translate3d(${offsetX.value}px, ${offsetY.value}px, 0) scale(${scale.value}) rotate(${rotation.value}deg)`,\n}))\n\nconst zoomValueText = computed(() => {\n const zoomPercent = scale.value * 100\n\n if (Number.isInteger(zoomPercent)) {\n return String(zoomPercent)\n }\n\n return zoomPercent.toFixed(1).replace(TRAILING_ZERO_DECIMAL_RE, '')\n})\n\nfunction normalizeIndex(value: number): number {\n if (!hasImages.value) {\n return 0\n }\n\n const normalized = Number.isFinite(value) ? Math.trunc(value) : 0\n\n return ((normalized % total.value) + total.value) % total.value\n}\n\nfunction resetTransform(): void {\n scale.value = 1\n rotation.value = 0\n offsetX.value = 0\n offsetY.value = 0\n}\n\nfunction setIndex(nextIndex: number, options?: { emitSwitch?: boolean }): void {\n const normalizedIndex = normalizeIndex(nextIndex)\n\n if (normalizedIndex === currentIndex.value) {\n return\n }\n\n currentIndex.value = normalizedIndex\n resetTransform()\n\n if (options?.emitSwitch !== false) {\n emit('switch', normalizedIndex)\n }\n}\n\nfunction syncIndexFromInitial(): void {\n if (!hasImages.value) {\n currentIndex.value = 0\n return\n }\n\n currentIndex.value = normalizeIndex(props.initialIndex)\n resetTransform()\n}\n\nfunction preloadAt(index: number): void {\n const url = props.urlList[index]\n\n if (!url) {\n return\n }\n\n const image = new Image()\n image.decoding = 'async'\n image.src = url\n}\n\nfunction closeViewer(): void {\n emit('update:modelValue', false)\n emit('close')\n}\n\nfunction prev(): void {\n setIndex(currentIndex.value - 1)\n}\n\nfunction next(): void {\n setIndex(currentIndex.value + 1)\n}\n\nfunction zoomIn(): void {\n scale.value = Math.min(props.maxScale, Number((scale.value * props.zoomRate).toFixed(4)))\n}\n\nfunction zoomOut(): void {\n scale.value = Math.max(props.minScale, Number((scale.value / props.zoomRate).toFixed(4)))\n}\n\nfunction rotateLeft(): void {\n rotation.value -= 90\n emit('rotate', rotation.value)\n}\n\nfunction rotateRight(): void {\n rotation.value += 90\n emit('rotate', rotation.value)\n}\n\nconst toolbarActions: GrImageViewerToolbarActions = {\n close: closeViewer,\n prev,\n next,\n zoomIn,\n zoomOut,\n reset: resetTransform,\n rotateLeft,\n rotateRight,\n}\n\nfunction onBackdropClick(): void {\n if (!props.hideOnClickModal) {\n return\n }\n\n closeViewer()\n}\n\nfunction onKeydown(event: KeyboardEvent): void {\n switch (event.key) {\n case 'Escape':\n if (!props.closeOnPressEscape) {\n return\n }\n\n event.preventDefault()\n closeViewer()\n return\n\n case 'ArrowLeft':\n event.preventDefault()\n prev()\n return\n\n case 'ArrowRight':\n event.preventDefault()\n next()\n return\n\n case '+':\n case '=':\n case 'Add':\n event.preventDefault()\n zoomIn()\n return\n\n case '-':\n case '_':\n case 'Subtract':\n event.preventDefault()\n zoomOut()\n return\n\n case '0':\n event.preventDefault()\n resetTransform()\n break\n\n default:\n }\n}\n\nwatch(\n () => props.modelValue,\n (isOpen) => {\n if (!isOpen) {\n return\n }\n\n syncIndexFromInitial()\n },\n { immediate: true },\n)\n\nwatch(\n () => props.initialIndex,\n () => {\n if (!props.modelValue) {\n return\n }\n\n syncIndexFromInitial()\n },\n)\n\nwatch(\n () => props.urlList,\n () => {\n if (!props.modelValue) {\n return\n }\n\n syncIndexFromInitial()\n },\n { deep: true },\n)\n\nwatch(\n currentIndex,\n (index) => {\n if (!hasImages.value || total.value < 2) {\n return\n }\n\n preloadAt(normalizeIndex(index - 1))\n preloadAt(normalizeIndex(index + 1))\n },\n { immediate: true },\n)\n</script>\n\n<template>\n <teleport to=\"body\">\n <TransitionRoot :show=\"open\" as=\"template\">\n <Dialog\n as=\"div\"\n class=\"fixed inset-0\"\n :style=\"viewerStyle\"\n role=\"dialog\"\n aria-modal=\"true\"\n :static=\"true\"\n @keydown=\"onKeydown\"\n >\n <div class=\"fixed inset-0 overflow-hidden\">\n <TransitionChild\n as=\"template\"\n enter=\"duration-150 ease-out\"\n enter-from=\"opacity-0\"\n enter-to=\"opacity-100\"\n leave=\"duration-120 ease-in\"\n leave-from=\"opacity-100\"\n leave-to=\"opacity-0\"\n >\n <div\n data-ds-image-viewer-overlay\n class=\"absolute inset-0 bg-black/60 backdrop-blur-sm\"\n aria-hidden=\"true\"\n @click=\"onBackdropClick\"\n />\n </TransitionChild>\n\n <TransitionChild\n as=\"template\"\n enter=\"duration-180 ease-out\"\n enter-from=\"opacity-0 scale-98\"\n enter-to=\"opacity-100 scale-100\"\n leave=\"duration-130 ease-in\"\n leave-from=\"opacity-100 scale-100\"\n leave-to=\"opacity-0 scale-98\"\n >\n <DialogPanel\n data-ds-image-viewer-panel\n class=\"relative z-10 h-full w-full outline-none\"\n tabindex=\"-1\"\n >\n <div class=\"pointer-events-none absolute inset-x-0 top-0 z-30 px-3 py-3 sm:px-6\">\n <div class=\"pointer-events-auto flex items-center justify-between gap-3\">\n <div class=\"flex items-center gap-2\">\n <div\n v-if=\"showProgress\"\n data-ds-image-viewer-progress\n class=\"rounded-full bg-black/35 px-3 py-1 text-xs font-600 text-white/95 sm:text-sm\"\n >\n {{ displayIndex }} / {{ total }}\n </div>\n\n <div\n v-if=\"showZoomValue\"\n data-ds-image-viewer-zoom-value\n class=\"rounded-full bg-black/35 px-3 py-1 text-xs font-700 text-white/95 sm:text-sm\"\n >\n {{ zoomValueText }}%\n </div>\n </div>\n\n <button\n type=\"button\"\n data-ds-image-viewer-close\n :aria-label=\"closeLabel\"\n class=\"h-11 w-11 flex items-center justify-center rounded-full border border-white/20 bg-black/35 text-white transition-colors hover:bg-black/55 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"closeViewer\"\n >\n <span aria-hidden=\"true\" class=\"text-lg leading-none\">✕</span>\n </button>\n </div>\n </div>\n\n <div class=\"relative z-10 h-full w-full flex items-center justify-center px-16 py-16 sm:px-24 sm:py-20\">\n <button\n v-if=\"total > 1\"\n type=\"button\"\n data-ds-image-viewer-prev\n :aria-label=\"prevLabel\"\n class=\"absolute left-3 top-1/2 z-20 h-12 w-12 -translate-y-1/2 flex items-center justify-center rounded-full border border-white/20 bg-black/35 text-white transition-colors hover:bg-black/55 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80 sm:left-6\"\n @click=\"prev\"\n >\n <span aria-hidden=\"true\" class=\"text-xl leading-none\">‹</span>\n </button>\n\n <button\n v-if=\"total > 1\"\n type=\"button\"\n data-ds-image-viewer-next\n :aria-label=\"nextLabel\"\n class=\"absolute right-3 top-1/2 z-20 h-12 w-12 -translate-y-1/2 flex items-center justify-center rounded-full border border-white/20 bg-black/35 text-white transition-colors hover:bg-black/55 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80 sm:right-6\"\n @click=\"next\"\n >\n <span aria-hidden=\"true\" class=\"text-xl leading-none\">›</span>\n </button>\n\n <div class=\"h-full w-full flex items-center justify-center\">\n <img\n v-if=\"currentUrl\"\n data-ds-image-viewer-image\n :src=\"currentUrl\"\n alt=\"\"\n draggable=\"false\"\n class=\"max-h-full max-w-full select-none object-contain will-change-transform transition-transform duration-150 ease-out\"\n :style=\"imageStyle\"\n >\n\n <div\n v-else\n class=\"rounded-[var(--ds-radius-xl)] border border-white/20 bg-black/25 px-4 py-3 text-sm text-white/80\"\n >\n {{ emptyText }}\n </div>\n </div>\n </div>\n\n <div class=\"pointer-events-none absolute inset-x-0 bottom-0 z-30 px-3 pb-3 sm:px-6 sm:pb-6\">\n <div class=\"pointer-events-auto mx-auto w-full max-w-max\">\n <slot\n name=\"toolbar\"\n :index=\"currentIndex\"\n :display-index=\"displayIndex\"\n :total=\"total\"\n :scale=\"scale\"\n :rotation=\"rotation\"\n :actions=\"toolbarActions\"\n >\n <div class=\"rounded-full border border-white/20 bg-black/35 p-1 backdrop-blur-sm flex items-center gap-1\">\n <button\n type=\"button\"\n data-ds-image-viewer-zoom-out\n :aria-label=\"zoomOutLabel\"\n class=\"h-11 min-w-11 px-2 flex items-center justify-center rounded-full text-sm font-600 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"zoomOut\"\n >\n −\n </button>\n\n <button\n type=\"button\"\n data-ds-image-viewer-zoom-reset\n :aria-label=\"resetZoomLabel\"\n class=\"h-11 min-w-11 px-3 flex items-center justify-center rounded-full text-xs font-700 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"resetTransform\"\n >\n 100%\n </button>\n\n <button\n type=\"button\"\n data-ds-image-viewer-zoom-in\n :aria-label=\"zoomInLabel\"\n class=\"h-11 min-w-11 px-2 flex items-center justify-center rounded-full text-sm font-600 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"zoomIn\"\n >\n +\n </button>\n\n <template v-if=\"$slots['toolbar-actions']\">\n <div class=\"mx-1 h-6 w-px bg-white/25\" aria-hidden=\"true\" />\n\n <slot\n name=\"toolbar-actions\"\n :index=\"currentIndex\"\n :display-index=\"displayIndex\"\n :total=\"total\"\n :scale=\"scale\"\n :rotation=\"rotation\"\n :actions=\"toolbarActions\"\n />\n\n <div class=\"mx-1 h-6 w-px bg-white/25\" aria-hidden=\"true\" />\n </template>\n\n <div v-else class=\"mx-1 h-6 w-px bg-white/25\" aria-hidden=\"true\" />\n\n <button\n type=\"button\"\n data-ds-image-viewer-rotate-left\n :aria-label=\"rotateLeftLabel\"\n class=\"h-11 min-w-11 px-2 flex items-center justify-center rounded-full text-sm font-600 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"rotateLeft\"\n >\n ↺\n </button>\n\n <button\n type=\"button\"\n data-ds-image-viewer-rotate-right\n :aria-label=\"rotateRightLabel\"\n class=\"h-11 min-w-11 px-2 flex items-center justify-center rounded-full text-sm font-600 text-white transition-colors hover:bg-white/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/80\"\n @click=\"rotateRight\"\n >\n ↻\n </button>\n </div>\n </slot>\n </div>\n </div>\n </DialogPanel>\n </TransitionChild>\n </div>\n </Dialog>\n </TransitionRoot>\n </teleport>\n</template>"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"GrModal-2ALC3kwy.js","names":["$slots"],"sources":["../../../../src/components/GrModal/grModalEscStack.ts","../../../../src/components/GrModal/GrModal.vue","../../../../src/components/GrModal/GrModal.vue"],"sourcesContent":["/**\n * grModalEscStack — общий стек открытых `GrModal` для корректной обработки Esc.\n *\n * Зачем нужен:\n * HeadlessUI вешает обработчик Escape на `window` у каждого открытого `<Dialog>`.\n * Когда модалки рендерятся в разных деревьях приложения (например, диалоги\n * `useDialogService` монтируются отдельным `render()` в `document.body`), их\n * HeadlessUI-стек не общий, и каждый `<Dialog>` считает себя верхним. В итоге\n * Esc перехватывает обработчик той модалки, что смонтирована раньше (нижней),\n * а реально верхнее окно (последнее открытое) остаётся.\n *\n * Решение: единый capture-обработчик на `window`, который срабатывает раньше\n * обработчиков HeadlessUI, гасит событие (`stopImmediatePropagation`) и\n * закрывает только верхнюю (последнюю зарегистрированную) модалку. Так Esc\n * всегда адресуется верхнему окну независимо от дерева рендера.\n */\n\nexport interface GrModalEscEntry {\n /** Уникальный id зарегистрированной модалки. */\n id: number\n /** Должна ли модалка закрываться по Esc (реактивный геттер `closeOnEsc`). */\n shouldClose: () => boolean\n /** Закрыть модалку (эмит `update:modelValue=false`). */\n close: () => void\n}\n\nconst stack: GrModalEscEntry[] = []\nlet listening = false\nlet nextId = 1\n\nfunction handleKeydown(event: KeyboardEvent): void {\n if (event.key !== 'Escape') return\n if (stack.length === 0) return\n\n // Верхняя модалка — последняя зарегистрированная.\n const top = stack[stack.length - 1]\n\n // Перехватываем Esc раньше HeadlessUI и нижних модалок: даже если верхнее\n // окно не закрывается (`closeOnEsc=false`), событие не должно «проваливаться»\n // к нижним окнам.\n event.preventDefault()\n event.stopPropagation()\n if (typeof event.stopImmediatePropagation === 'function')\n event.stopImmediatePropagation()\n\n if (top.shouldClose()) top.close()\n}\n\nfunction startListening(): void {\n if (listening) return\n if (typeof window === 'undefined') return\n // Capture-фаза, чтобы опередить window-обработчик Escape HeadlessUI.\n window.addEventListener('keydown', handleKeydown, true)\n listening = true\n}\n\nfunction stopListening(): void {\n if (!listening) return\n if (typeof window === 'undefined') return\n window.removeEventListener('keydown', handleKeydown, true)\n listening = false\n}\n\n/** Регистрирует открытую модалку как верхнюю и возвращает её id. */\nexport function pushGrModalEsc(entry: Omit<GrModalEscEntry, 'id'>): number {\n const id = nextId++\n stack.push({ ...entry, id })\n startListening()\n return id\n}\n\n/** Снимает модалку со стека (при закрытии или размонтировании). */\nexport function removeGrModalEsc(id: number): void {\n const index = stack.findIndex(item => item.id === id)\n if (index >= 0) stack.splice(index, 1)\n if (stack.length === 0) stopListening()\n}\n\n/** Тестовая/служебная очистка стека. */\nexport function resetGrModalEscStack(): void {\n stack.splice(0, stack.length)\n stopListening()\n}\n","<script setup lang=\"ts\">\n/**\n * GrModal — DS-примитив модального окна на базе `@headlessui/vue`.\n *\n * Публичный API:\n * - `modelValue` (v-model) — открыто/закрыто;\n * - `size` — размер панели (`sm | md | lg | xl | full`);\n * - `closeOnBackdrop` (default: true) — закрывать при клике по оверлею;\n * - `closeOnEsc` (default: true) — закрывать по Esc;\n * - слоты `#title` / `#description` — если переданы, оборачиваются в\n * `DialogTitle` / `DialogDescription` (связь через `aria-labelledby` /\n * `aria-describedby` ставится HeadlessUI автоматически).\n *\n * Esc обрабатывается через общий стек открытых модалок (`grModalEscStack`):\n * единый capture-обработчик на `window` закрывает только верхнюю (последнюю\n * открытую) модалку и опережает window-обработчик Escape HeadlessUI. Это\n * чинит кейс, когда поверх `GrModal` открыт диалог `useDialogService` (другое\n * дерево рендера): Esc закрывает именно верхний диалог, а не нижнее окно.\n * Клик по оверлею (backdrop) по-прежнему идёт через `@close` HeadlessUI;\n * источник закрытия различается через ref `closeReason`.\n */\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { Dialog, DialogDescription, DialogPanel, DialogTitle, TransitionChild, TransitionRoot } from '@headlessui/vue'\n\nimport { pushGrModalEsc, removeGrModalEsc } from './grModalEscStack'\n\nimport {\n type GrModalSize,\n getGrModalPanelClass,\n layout,\n overlay as overlayClass,\n overlayTransition,\n panelTransition,\n root as rootClass,\n shell,\n} from './grModalStyles'\n\nexport interface GrModalProps {\n modelValue: boolean\n closeOnBackdrop?: boolean\n closeOnEsc?: boolean\n size?: GrModalSize\n}\n\nconst props = withDefaults(defineProps<GrModalProps>(), {\n closeOnBackdrop: true,\n closeOnEsc: true,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: boolean): void\n}>()\n\nconst panelClass = computed(() => getGrModalPanelClass(props.size))\n\n// SSR-guard: на сервере `document.body` недоступен — отключаем teleport,\n// а в клиенте включаем после маунта.\nconst isClient = typeof window !== 'undefined'\n\n// Источник закрытия от HeadlessUI `<Dialog>` — либо Esc, либо клик по оверлею.\n// Различаем их, чтобы `closeOnBackdrop` и `closeOnEsc` работали независимо.\nconst closeReason = ref<'backdrop' | 'esc' | null>(null)\n\nfunction close(): void {\n emit('update:modelValue', false)\n}\n\nfunction onDialogClose(): void {\n const reason = closeReason.value\n closeReason.value = null\n\n if (reason === 'esc') {\n if (props.closeOnEsc) close()\n return\n }\n\n // По умолчанию (клик по оверлею или программный вызов `@close`)\n // уважаем `closeOnBackdrop`.\n if (props.closeOnBackdrop) close()\n}\n\nfunction onRootKeydown(event: KeyboardEvent): void {\n if (event.key === 'Escape') closeReason.value = 'esc'\n}\n\n// ————— Esc-стек: гарантирует, что Esc закрывает именно верхнюю (последнюю\n// открытую) модалку, даже если окна живут в разных деревьях рендера\n// (например, диалоги `useDialogService` поверх `GrModal`).\nlet escEntryId: number | null = null\n\nfunction registerEsc(): void {\n if (escEntryId !== null) return\n escEntryId = pushGrModalEsc({\n shouldClose: () => props.closeOnEsc,\n close,\n })\n}\n\nfunction unregisterEsc(): void {\n if (escEntryId === null) return\n removeGrModalEsc(escEntryId)\n escEntryId = null\n}\n\nfunction onOverlayPointerDown(): void {\n closeReason.value = 'backdrop'\n}\n\n// ————— Scroll lock на `<body>` на время открытия.\n// HeadlessUI Vue этого не делает автоматически, а фон скроллится —\n// для DS-примитива это мешающий UX.\nlet savedBodyOverflow: string | null = null\n\nfunction lockBodyScroll(): void {\n if (!isClient) return\n if (savedBodyOverflow !== null) return\n savedBodyOverflow = document.body.style.overflow\n document.body.style.overflow = 'hidden'\n}\n\nfunction unlockBodyScroll(): void {\n if (!isClient) return\n if (savedBodyOverflow === null) return\n document.body.style.overflow = savedBodyOverflow\n savedBodyOverflow = null\n}\n\nwatch(\n () => props.modelValue,\n (value) => {\n if (value) {\n lockBodyScroll()\n registerEsc()\n }\n else {\n unlockBodyScroll()\n unregisterEsc()\n }\n },\n { immediate: true },\n)\n\nonBeforeUnmount(() => {\n unlockBodyScroll()\n unregisterEsc()\n})\n\n</script>\n\n<template>\n <teleport to=\"body\" :disabled=\"!isClient\">\n <TransitionRoot :show=\"modelValue\" as=\"template\">\n <Dialog\n as=\"div\"\n :class=\"rootClass\"\n @close=\"onDialogClose\"\n @keydown.capture=\"onRootKeydown\"\n >\n <div :class=\"shell\">\n <div :class=\"layout\">\n <TransitionChild\n as=\"template\"\n :enter=\"overlayTransition.enter\"\n :enter-from=\"overlayTransition.enterFrom\"\n :enter-to=\"overlayTransition.enterTo\"\n :leave=\"overlayTransition.leave\"\n :leave-from=\"overlayTransition.leaveFrom\"\n :leave-to=\"overlayTransition.leaveTo\"\n >\n <div\n data-ds-modal-overlay\n :class=\"overlayClass\"\n aria-hidden=\"true\"\n @pointerdown=\"onOverlayPointerDown\"\n />\n </TransitionChild>\n\n <TransitionChild\n as=\"template\"\n :enter=\"panelTransition.enter\"\n :enter-from=\"panelTransition.enterFrom\"\n :enter-to=\"panelTransition.enterTo\"\n :leave=\"panelTransition.leave\"\n :leave-from=\"panelTransition.leaveFrom\"\n :leave-to=\"panelTransition.leaveTo\"\n >\n <DialogPanel\n data-ds-modal-panel\n tabindex=\"-1\"\n :class=\"panelClass\"\n >\n <DialogTitle v-if=\"$slots.title\" as=\"div\" data-ds-modal-title>\n <slot name=\"title\" />\n </DialogTitle>\n <DialogDescription v-if=\"$slots.description\" as=\"div\" data-ds-modal-description>\n <slot name=\"description\" />\n </DialogDescription>\n <slot />\n </DialogPanel>\n </TransitionChild>\n </div>\n </div>\n </Dialog>\n </TransitionRoot>\n </teleport>\n</template>\n","<script setup lang=\"ts\">\n/**\n * GrModal — DS-примитив модального окна на базе `@headlessui/vue`.\n *\n * Публичный API:\n * - `modelValue` (v-model) — открыто/закрыто;\n * - `size` — размер панели (`sm | md | lg | xl | full`);\n * - `closeOnBackdrop` (default: true) — закрывать при клике по оверлею;\n * - `closeOnEsc` (default: true) — закрывать по Esc;\n * - слоты `#title` / `#description` — если переданы, оборачиваются в\n * `DialogTitle` / `DialogDescription` (связь через `aria-labelledby` /\n * `aria-describedby` ставится HeadlessUI автоматически).\n *\n * Esc обрабатывается через общий стек открытых модалок (`grModalEscStack`):\n * единый capture-обработчик на `window` закрывает только верхнюю (последнюю\n * открытую) модалку и опережает window-обработчик Escape HeadlessUI. Это\n * чинит кейс, когда поверх `GrModal` открыт диалог `useDialogService` (другое\n * дерево рендера): Esc закрывает именно верхний диалог, а не нижнее окно.\n * Клик по оверлею (backdrop) по-прежнему идёт через `@close` HeadlessUI;\n * источник закрытия различается через ref `closeReason`.\n */\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { Dialog, DialogDescription, DialogPanel, DialogTitle, TransitionChild, TransitionRoot } from '@headlessui/vue'\n\nimport { pushGrModalEsc, removeGrModalEsc } from './grModalEscStack'\n\nimport {\n type GrModalSize,\n getGrModalPanelClass,\n layout,\n overlay as overlayClass,\n overlayTransition,\n panelTransition,\n root as rootClass,\n shell,\n} from './grModalStyles'\n\nexport interface GrModalProps {\n modelValue: boolean\n closeOnBackdrop?: boolean\n closeOnEsc?: boolean\n size?: GrModalSize\n}\n\nconst props = withDefaults(defineProps<GrModalProps>(), {\n closeOnBackdrop: true,\n closeOnEsc: true,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: boolean): void\n}>()\n\nconst panelClass = computed(() => getGrModalPanelClass(props.size))\n\n// SSR-guard: на сервере `document.body` недоступен — отключаем teleport,\n// а в клиенте включаем после маунта.\nconst isClient = typeof window !== 'undefined'\n\n// Источник закрытия от HeadlessUI `<Dialog>` — либо Esc, либо клик по оверлею.\n// Различаем их, чтобы `closeOnBackdrop` и `closeOnEsc` работали независимо.\nconst closeReason = ref<'backdrop' | 'esc' | null>(null)\n\nfunction close(): void {\n emit('update:modelValue', false)\n}\n\nfunction onDialogClose(): void {\n const reason = closeReason.value\n closeReason.value = null\n\n if (reason === 'esc') {\n if (props.closeOnEsc) close()\n return\n }\n\n // По умолчанию (клик по оверлею или программный вызов `@close`)\n // уважаем `closeOnBackdrop`.\n if (props.closeOnBackdrop) close()\n}\n\nfunction onRootKeydown(event: KeyboardEvent): void {\n if (event.key === 'Escape') closeReason.value = 'esc'\n}\n\n// ————— Esc-стек: гарантирует, что Esc закрывает именно верхнюю (последнюю\n// открытую) модалку, даже если окна живут в разных деревьях рендера\n// (например, диалоги `useDialogService` поверх `GrModal`).\nlet escEntryId: number | null = null\n\nfunction registerEsc(): void {\n if (escEntryId !== null) return\n escEntryId = pushGrModalEsc({\n shouldClose: () => props.closeOnEsc,\n close,\n })\n}\n\nfunction unregisterEsc(): void {\n if (escEntryId === null) return\n removeGrModalEsc(escEntryId)\n escEntryId = null\n}\n\nfunction onOverlayPointerDown(): void {\n closeReason.value = 'backdrop'\n}\n\n// ————— Scroll lock на `<body>` на время открытия.\n// HeadlessUI Vue этого не делает автоматически, а фон скроллится —\n// для DS-примитива это мешающий UX.\nlet savedBodyOverflow: string | null = null\n\nfunction lockBodyScroll(): void {\n if (!isClient) return\n if (savedBodyOverflow !== null) return\n savedBodyOverflow = document.body.style.overflow\n document.body.style.overflow = 'hidden'\n}\n\nfunction unlockBodyScroll(): void {\n if (!isClient) return\n if (savedBodyOverflow === null) return\n document.body.style.overflow = savedBodyOverflow\n savedBodyOverflow = null\n}\n\nwatch(\n () => props.modelValue,\n (value) => {\n if (value) {\n lockBodyScroll()\n registerEsc()\n }\n else {\n unlockBodyScroll()\n unregisterEsc()\n }\n },\n { immediate: true },\n)\n\nonBeforeUnmount(() => {\n unlockBodyScroll()\n unregisterEsc()\n})\n\n</script>\n\n<template>\n <teleport to=\"body\" :disabled=\"!isClient\">\n <TransitionRoot :show=\"modelValue\" as=\"template\">\n <Dialog\n as=\"div\"\n :class=\"rootClass\"\n @close=\"onDialogClose\"\n @keydown.capture=\"onRootKeydown\"\n >\n <div :class=\"shell\">\n <div :class=\"layout\">\n <TransitionChild\n as=\"template\"\n :enter=\"overlayTransition.enter\"\n :enter-from=\"overlayTransition.enterFrom\"\n :enter-to=\"overlayTransition.enterTo\"\n :leave=\"overlayTransition.leave\"\n :leave-from=\"overlayTransition.leaveFrom\"\n :leave-to=\"overlayTransition.leaveTo\"\n >\n <div\n data-ds-modal-overlay\n :class=\"overlayClass\"\n aria-hidden=\"true\"\n @pointerdown=\"onOverlayPointerDown\"\n />\n </TransitionChild>\n\n <TransitionChild\n as=\"template\"\n :enter=\"panelTransition.enter\"\n :enter-from=\"panelTransition.enterFrom\"\n :enter-to=\"panelTransition.enterTo\"\n :leave=\"panelTransition.leave\"\n :leave-from=\"panelTransition.leaveFrom\"\n :leave-to=\"panelTransition.leaveTo\"\n >\n <DialogPanel\n data-ds-modal-panel\n tabindex=\"-1\"\n :class=\"panelClass\"\n >\n <DialogTitle v-if=\"$slots.title\" as=\"div\" data-ds-modal-title>\n <slot name=\"title\" />\n </DialogTitle>\n <DialogDescription v-if=\"$slots.description\" as=\"div\" data-ds-modal-description>\n <slot name=\"description\" />\n </DialogDescription>\n <slot />\n </DialogPanel>\n </TransitionChild>\n </div>\n </div>\n </Dialog>\n </TransitionRoot>\n </teleport>\n</template>\n"],"mappings":"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"}