@feugene/granularity 0.31.1 → 0.32.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.
Files changed (52) hide show
  1. package/dist/chunks/{overlayStack-C8cfsqXa.js → overlayStack-0CZS99Da.js} +40 -6
  2. package/dist/chunks/overlayStack-0CZS99Da.js.map +1 -0
  3. package/dist/chunks/{useModalOverlay-Cv5hF63W.js → useModalOverlay-D__CqLGn.js} +3 -0
  4. package/dist/chunks/useModalOverlay-D__CqLGn.js.map +1 -0
  5. package/dist/components/GrColorPicker/chunks/{GrColorPicker-B20gQ6AR.js → GrColorPicker-p2XH_8cE.js} +1 -1
  6. package/dist/components/GrColorPicker/chunks/{GrColorPicker-B20gQ6AR.js.map → GrColorPicker-p2XH_8cE.js.map} +1 -1
  7. package/dist/components/GrColorPicker/index.js +1 -1
  8. package/dist/components/GrCommandPalette/chunks/{GrCommandPalette-Cd0Yo3Dw.js → GrCommandPalette-BXbh3q7O.js} +1 -1
  9. package/dist/components/GrCommandPalette/chunks/{GrCommandPalette-Cd0Yo3Dw.js.map → GrCommandPalette-BXbh3q7O.js.map} +1 -1
  10. package/dist/components/GrCommandPalette/index.js +1 -1
  11. package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-DOsbRhoZ.js → GrConfirmDialog-CUP43q__.js} +1 -1
  12. package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-DOsbRhoZ.js.map → GrConfirmDialog-CUP43q__.js.map} +1 -1
  13. package/dist/components/GrConfirmDialog/index.js +1 -1
  14. package/dist/components/GrContextMenu/chunks/{GrContextMenu-_FKwQvuQ.js → GrContextMenu-ClkVjdcP.js} +1 -1
  15. package/dist/components/GrContextMenu/chunks/{GrContextMenu-_FKwQvuQ.js.map → GrContextMenu-ClkVjdcP.js.map} +1 -1
  16. package/dist/components/GrContextMenu/index.js +1 -1
  17. package/dist/components/GrDialog/chunks/{GrDialog-S1Vl-OhX.js → GrDialog-BiMJ9_T4.js} +1 -1
  18. package/dist/components/GrDialog/chunks/{GrDialog-S1Vl-OhX.js.map → GrDialog-BiMJ9_T4.js.map} +1 -1
  19. package/dist/components/GrDialog/index.js +1 -1
  20. package/dist/components/GrDialogService/chunks/{GrDialogService-Cuhc4YLp.js → GrDialogService-BxNK6crD.js} +2 -2
  21. package/dist/components/GrDialogService/chunks/{GrDialogService-Cuhc4YLp.js.map → GrDialogService-BxNK6crD.js.map} +1 -1
  22. package/dist/components/GrDialogService/index.js +1 -1
  23. package/dist/components/GrDrawer/chunks/{GrDrawer-C0YqVE9e.js → GrDrawer-Ce2l3zaG.js} +5 -4
  24. package/dist/components/GrDrawer/chunks/GrDrawer-Ce2l3zaG.js.map +1 -0
  25. package/dist/components/GrDrawer/index.js +1 -1
  26. package/dist/components/GrImageViewer/chunks/{GrImageViewer-CyVN6Zpc.js → GrImageViewer-CG4vUBg_.js} +4 -3
  27. package/dist/components/GrImageViewer/chunks/GrImageViewer-CG4vUBg_.js.map +1 -0
  28. package/dist/components/GrImageViewer/index.js +1 -1
  29. package/dist/components/GrModal/chunks/{GrModal-CCoWZuIl.js → GrModal-D0mf1SAx.js} +3 -2
  30. package/dist/components/GrModal/chunks/GrModal-D0mf1SAx.js.map +1 -0
  31. package/dist/components/GrModal/index.js +1 -1
  32. package/dist/components/GrPopover/chunks/{GrPopover-CofG-Tfe.js → GrPopover-faJanmGs.js} +1 -1
  33. package/dist/components/GrPopover/chunks/{GrPopover-CofG-Tfe.js.map → GrPopover-faJanmGs.js.map} +1 -1
  34. package/dist/components/GrPopover/index.js +1 -1
  35. package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-BuJQMZLM.js → GrPromptDialog-C6QGC7QJ.js} +1 -1
  36. package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-BuJQMZLM.js.map → GrPromptDialog-C6QGC7QJ.js.map} +1 -1
  37. package/dist/components/GrPromptDialog/index.js +1 -1
  38. package/dist/composables/useFloating.js +2 -22
  39. package/dist/composables/useFloating.js.map +1 -1
  40. package/dist/composables/useOverlayLayer.js +10 -3
  41. package/dist/composables/useOverlayLayer.js.map +1 -1
  42. package/dist/index.js +11 -11
  43. package/dist/types/composables/internal/overlayStack.d.ts +30 -0
  44. package/dist/types/composables/internal/useModalOverlay.d.ts +14 -0
  45. package/dist/types/composables/useOverlayLayer.d.ts +8 -0
  46. package/dist/web-types.json +1 -1
  47. package/package.json +1 -1
  48. package/dist/chunks/overlayStack-C8cfsqXa.js.map +0 -1
  49. package/dist/chunks/useModalOverlay-Cv5hF63W.js.map +0 -1
  50. package/dist/components/GrDrawer/chunks/GrDrawer-C0YqVE9e.js.map +0 -1
  51. package/dist/components/GrImageViewer/chunks/GrImageViewer-CyVN6Zpc.js.map +0 -1
  52. package/dist/components/GrModal/chunks/GrModal-CCoWZuIl.js.map +0 -1
@@ -1,3 +1,3 @@
1
- import { t as GrPopover_default } from "./chunks/GrPopover-CofG-Tfe.js";
1
+ import { t as GrPopover_default } from "./chunks/GrPopover-faJanmGs.js";
2
2
  import { n as grPopoverSafelist, t as grPopoverConfig } from "../../chunks/config-BTwYMQaz2.js";
3
3
  export { GrPopover_default as GrPopover, GrPopover_default as default, grPopoverConfig, grPopoverSafelist };
@@ -2,7 +2,7 @@ import { i as useGrComponentProp } from "../../../chunks/context-CTbuwcYs.js";
2
2
  import { n as useGranularityTranslations } from "../../../chunks/granularityI18n-BC5zsj_d.js";
3
3
  import { t as GrButton_default } from "../../GrButton/chunks/GrButton-7GP0zObj.js";
4
4
  import { t as GrInput_default } from "../../GrInput/chunks/GrInput-BLywRZUN.js";
5
- import { t as GrDialog_default } from "../../GrDialog/chunks/GrDialog-S1Vl-OhX.js";
5
+ import { t as GrDialog_default } from "../../GrDialog/chunks/GrDialog-BiMJ9_T4.js";
6
6
  import { t as GrResponseErrorBanner_default } from "../../GrResponseErrorBanner/chunks/GrResponseErrorBanner-DMeOWlzj.js";
7
7
  import { t as GrFormField_default } from "../../GrFormField/chunks/GrFormField-DSylU7IU.js";
8
8
  import { t as GrTextarea_default } from "../../GrTextarea/chunks/GrTextarea-DinXG12-.js";
@@ -1 +1 @@
1
- {"version":3,"file":"GrPromptDialog-BuJQMZLM.js","names":[],"sources":["../../../../src/components/GrPromptDialog/GrPromptDialog.vue","../../../../src/components/GrPromptDialog/GrPromptDialog.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * GrPromptDialog — GR-примитив диалога ввода значения поверх `GrDialog`.\n *\n * Синхронизирован с `GrDialog` по набору проп-проксирования:\n * `size`, `closeOnBackdrop`, `closeOnEsc`, `showHeader`, `showCloseButton`,\n * `headerConfig`, `footerConfig`, `bodyConfig`, `closeLabel`.\n *\n * Поведение `required=true` (по умолчанию): пустое значение подсвечивается\n * ошибкой только после первого «касания» (blur или попытка confirm),\n * чтобы не шуметь при открытии.\n *\n * Проверки сверх `required` описываются пропом `rules` — тем же движком, что и\n * у `GrForm`: третий частный случай валидации в пакете заводить незачем.\n */\nimport { useGrComponentProp } from '../GrConfigProvider/context'\nimport { computed, ref, watch } from 'vue'\n\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrDialog from '../GrDialog/GrDialog.vue'\nimport GrFormField from '../GrFormField/GrFormField.vue'\nimport GrInput from '../GrInput/GrInput.vue'\nimport GrTextarea from '../GrTextarea/GrTextarea.vue'\nimport GrResponseErrorBanner from '../GrResponseErrorBanner/GrResponseErrorBanner.vue'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n// Именованные импорты чистых функций: разметки не тянут, поэтому в\n// `config.dependencies` не объявляются (см. правило про зависимости).\nimport { createGrFormMessageResolver, rulesForTrigger, runFieldRules } from '../GrForm/validation'\nimport type { GrFormRule, GrFormTrigger } from '../GrForm/validation'\nimport type { GrButtonSize, GrButtonTone, GrButtonVariant } from '../GrButton'\nimport type { GrDialogSectionConfig, GrDialogSize } from '../GrDialog'\n\n/**\n * Тип поля выводим из самого `GrInput`, а не заводим ему именованный алиас:\n * алиас в пропе `GrInput` заменил бы в таблице API витрины список допустимых\n * значений на своё имя — документация стала бы беднее ради удобства обёртки.\n */\ntype GrInputType = NonNullable<InstanceType<typeof GrInput>['$props']['type']>\nimport type { ResponseErrorInfo } from '../GrResponseErrorBanner'\nimport type { InputHTMLAttributes } from 'vue'\n\nexport interface GrPromptDialogProps {\n modelValue: boolean\n value: string\n title?: string\n description?: string\n label?: string\n placeholder?: string\n closeOnBackdrop?: boolean\n closeOnEsc?: boolean\n showHeader?: boolean\n showCloseButton?: boolean\n size?: GrDialogSize\n headerConfig?: GrDialogSectionConfig\n footerConfig?: GrDialogSectionConfig\n bodyConfig?: GrDialogSectionConfig\n /** A11y-лейбл кнопки закрытия (i18n). */\n closeLabel?: string\n buttonSize?: GrButtonSize\n confirmText?: string\n cancelText?: string\n /** Текст ошибки для пустого значения при `required=true` (i18n). */\n requiredErrorText?: string\n confirmVariant?: GrButtonVariant\n confirmTone?: GrButtonTone\n required?: boolean\n /** Тип однострочного поля. В многострочном режиме не применяется. */\n inputType?: GrInputType\n /** Программная клавиатура на мобильных. */\n inputmode?: InputHTMLAttributes['inputmode']\n /** Ограничение длины; со `showCount` рисуется счётчик. */\n maxlength?: number\n showCount?: boolean\n /** Многострочный ввод: вместо `GrInput` рисуется `GrTextarea`. */\n multiline?: boolean\n /** Высота многострочного поля в строках. */\n rows?: number\n /** Автоподбор высоты многострочного поля под содержимое. */\n autosize?: boolean\n /**\n * Правила проверки значения — те же, что у `GrForm` (`required`, `type`,\n * `min`/`max`/`len`, `pattern`, свой в т.ч. асинхронный `validator`).\n * Прогоняются на blur (после первого касания) и на подтверждении.\n */\n rules?: GrFormRule | GrFormRule[]\n /**\n * Структура ошибки ответа сервера для показа общим блоком в теле диалога\n * (через `GrResponseErrorBanner`). `null` — блок скрыт.\n */\n error?: ResponseErrorInfo | null\n /**\n * Внешняя ошибка поля ввода (например, серверная валидация). Имеет приоритет\n * над встроенной проверкой. `null`/`undefined` — нет внешней ошибки.\n */\n fieldError?: string | null\n /** Состояние загрузки кнопки Confirm (async-`onConfirm` in-flight). */\n confirmLoading?: boolean\n /** Принудительно дизейблит кнопку Confirm. */\n confirmDisabled?: boolean\n /**\n * Закрывать ли диалог автоматически по клику Confirm. По умолчанию `true`.\n * `false` — отдаёт управление закрытием наружу (нужно `useDialogService`).\n */\n closeOnConfirm?: boolean\n /**\n * Запрет закрытия «мягкими» способами (Esc, клик по бэкдропу), пока идёт\n * подтверждение или проверка `rules`. Кнопка закрытия и «Отмена» остаются:\n * окно без единого выхода — ловушка.\n */\n persistent?: boolean\n}\n\nexport interface GrPromptDialogEmits {\n (e: 'update:modelValue', value: boolean): void\n (e: 'update:value', value: string): void\n (e: 'confirm', value: string): void\n (e: 'cancel'): void\n}\n\nimport './defaults'\n\nconst props = withDefaults(defineProps<GrPromptDialogProps>(), {\n title: undefined,\n description: undefined,\n label: undefined,\n placeholder: undefined,\n closeOnBackdrop: true,\n closeOnEsc: true,\n showHeader: true,\n showCloseButton: true,\n // Дефолт `size` живёт в резолвере ниже, а не здесь: Vue подставил бы его\n // до того, как компонент заглянет в `GrConfigProvider`.\n size: undefined,\n headerConfig: undefined,\n footerConfig: undefined,\n bodyConfig: undefined,\n closeLabel: undefined,\n buttonSize: undefined,\n confirmText: undefined,\n cancelText: undefined,\n requiredErrorText: undefined,\n confirmVariant: 'primary',\n confirmTone: 'primary',\n required: true,\n inputType: 'text',\n inputmode: undefined,\n maxlength: undefined,\n showCount: false,\n multiline: false,\n rows: undefined,\n autosize: false,\n rules: undefined,\n error: null,\n fieldError: null,\n confirmLoading: false,\n confirmDisabled: false,\n closeOnConfirm: true,\n persistent: false,\n})\n\n// Эффективный размер: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentProp('GrPromptDialog', 'size', () => props.size, 'md')\n\nconst emit = defineEmits<GrPromptDialogEmits>()\n\n// Дефолты берём из общего i18n-блока пакета (fallback — англ.), а не хардкодим.\nconst { t } = useGranularityTranslations()\nconst resolvedTitle = computed(() => props.title ?? t('gr.dialog.prompt.title', 'Prompt'))\nconst resolvedLabel = computed(() => props.label ?? t('gr.dialog.prompt.label', 'Value'))\nconst resolvedRequiredErrorText = computed(() => props.requiredErrorText ?? t('gr.dialog.prompt.required', 'Enter a value.'))\nconst resolvedCloseLabel = computed(() => props.closeLabel ?? t('gr.common.close', 'Close'))\nconst resolvedConfirmText = computed(() => props.confirmText ?? t('gr.common.confirm', 'Confirm'))\nconst resolvedCancelText = computed(() => props.cancelText ?? t('gr.common.cancel', 'Cancel'))\n\nconst open = computed({\n get: () => props.modelValue,\n set: (v: boolean) => emit('update:modelValue', v),\n})\n\nconst valueModel = computed({\n get: () => props.value,\n set: (v: string) => emit('update:value', v),\n})\n\nconst touched = ref(false)\nconst inputRef = ref<InstanceType<typeof GrInput> | null>(null)\nconst textareaRef = ref<InstanceType<typeof GrTextarea> | null>(null)\n\n/** Ошибка последнего прогона `rules`; `required` считается отдельно и мгновенно. */\nconst ruleError = ref<string | undefined>(undefined)\nconst validating = ref(false)\n\n// Сторож устаревших прогонов: асинхронное правило может ответить после того,\n// как значение уже сменилось, и дописать чужую ошибку.\nlet validationRun = 0\n\nwatch(\n () => props.modelValue,\n (isOpen) => {\n if (!isOpen)\n return\n\n touched.value = false\n ruleError.value = undefined\n validationRun += 1\n },\n)\n\nconst resolveMessage = createGrFormMessageResolver(t)\n\nconst normalizedRules = computed<GrFormRule[]>(() => {\n if (!props.rules)\n return []\n return Array.isArray(props.rules) ? props.rules : [props.rules]\n})\n\nconst requiredError = computed(() => {\n if (!props.required)\n return undefined\n return valueModel.value.trim().length > 0 ? undefined : resolvedRequiredErrorText.value\n})\n\nconst canConfirm = computed(() => !requiredError.value)\n\nconst validationError = computed(() => {\n if (!touched.value)\n return undefined\n return requiredError.value ?? ruleError.value\n})\n\n// Внешняя ошибка поля (серверная валидация) имеет приоритет над встроенной.\nconst fieldErrorMessage = computed(() => props.fieldError ?? validationError.value ?? undefined)\n\n/**\n * Прогоняет `rules` для триггера. Возвращает сообщение или `undefined`.\n * `required` здесь не участвует: он синхронный и считается отдельно, чтобы\n * пустое поле блокировало кнопку без похода в асинхронный движок.\n */\nasync function runRules(trigger: GrFormTrigger): Promise<string | undefined> {\n const rules = rulesForTrigger(normalizedRules.value, trigger)\n if (!rules.length)\n return undefined\n\n const run = ++validationRun\n validating.value = true\n try {\n const message = await runFieldRules(valueModel.value, rules, { value: valueModel.value }, resolveMessage)\n // Ответ устарел — значение или сам диалог успели смениться.\n if (run !== validationRun)\n return undefined\n\n ruleError.value = message\n return message\n }\n finally {\n if (run === validationRun)\n validating.value = false\n }\n}\n\nfunction onBlur(): void {\n touched.value = true\n void runRules('blur')\n}\n\n// Пока подтверждение или проверка в полёте, случайное движение не должно\n// оборвать операцию.\nconst softCloseBlocked = computed(() => props.persistent && (props.confirmLoading || validating.value))\nconst resolvedCloseOnBackdrop = computed(() => (softCloseBlocked.value ? false : props.closeOnBackdrop))\nconst resolvedCloseOnEsc = computed(() => (softCloseBlocked.value ? false : props.closeOnEsc))\n\nfunction onCancel(): void {\n emit('cancel')\n emit('update:modelValue', false)\n}\n\nfunction focusField(): void {\n if (props.multiline)\n textareaRef.value?.focus()\n else inputRef.value?.focus()\n}\n\nasync function onConfirm(): Promise<void> {\n touched.value = true\n\n if (!canConfirm.value) {\n focusField()\n return\n }\n\n if (await runRules('submit')) {\n focusField()\n return\n }\n\n emit('confirm', valueModel.value)\n if (props.closeOnConfirm)\n emit('update:modelValue', false)\n}\n\n/**\n * `Enter` подтверждает — базовое ожидание от окна, единственный смысл которого\n * ввести одно значение. В многострочном режиме `Enter` остаётся переводом\n * строки, поэтому обработчик висит только на однострочном поле.\n */\nfunction onEnter(): void {\n void onConfirm()\n}\n\n// Фокус в поле, а не на панель: диалог существует ровно ради ввода.\n//\n// Через `initialFocus` у `GrModal` это не решается: элемент рождается внутри\n// поддерева диалога, и возврат его же пропом наверх замыкает рендер в цикл\n// («Maximum recursive updates» — проверено). Поэтому фокус ставит содержимое,\n// после отрисовки (`flush: 'post'` + кадр), то есть заведомо позже, чем\n// ловушка фокуса `GrModal` переводит фокус на панель.\n// Источник — не только `modelValue`, но и само поле: содержимое панели\n// появляется на такт позже открытия, и фокус по фиксированному `nextTick`\n// приходился на момент, когда фокусировать ещё нечего.\nwatch(\n [() => props.modelValue, inputRef, textareaRef],\n () => {\n if (!props.modelValue)\n return\n focusField()\n },\n // `immediate`: окно могут смонтировать уже открытым — тогда смены пропа не\n // будет вовсе, и без этого фокус остался бы на панели.\n { immediate: true, flush: 'post' },\n)\n\ndefineSlots<{\n /** Содержимое диалога вместо пропа `message`. */\n default?: () => any\n /** Разбор ошибки вместо встроенного баннера. */\n error?: (props: { error: ResponseErrorInfo | null }) => any\n /** Кнопки диалога вместо пары «отмена и подтверждение». */\n footer?: () => any\n}>()\n</script>\n\n<template>\n <GrDialog\n v-model=\"open\"\n :title=\"resolvedTitle\"\n :size=\"resolvedSize\"\n :close-on-backdrop=\"resolvedCloseOnBackdrop\"\n :close-on-esc=\"resolvedCloseOnEsc\"\n :show-header=\"showHeader\"\n :show-close-button=\"showCloseButton\"\n :header-config=\"headerConfig\"\n :footer-config=\"footerConfig\"\n :body-config=\"bodyConfig\"\n :close-label=\"resolvedCloseLabel\"\n >\n <div class=\"grid gap-4\">\n <slot>\n <div v-if=\"description\" class=\"text-[length:var(--gr-control-text-md)] leading-[var(--gr-control-leading-md)] text-[var(--gr-muted-fg)]\">\n {{ description }}\n </div>\n </slot>\n\n <!-- `id` полю не задаём: `GrFormField` генерирует его сам, а поле читает\n из контекста. Литеральный id ломал два открытых диалога сразу. -->\n <GrFormField :label=\"resolvedLabel\" :error=\"fieldErrorMessage\">\n <GrTextarea\n v-if=\"multiline\"\n ref=\"textareaRef\"\n v-model=\"valueModel\"\n data-testid=\"gr-prompt-input\"\n :placeholder=\"placeholder\"\n :rows=\"rows\"\n :autosize=\"autosize\"\n :maxlength=\"maxlength\"\n :show-count=\"showCount\"\n :invalid=\"!!fieldErrorMessage\"\n @blur=\"onBlur\"\n />\n <GrInput\n v-else\n ref=\"inputRef\"\n v-model=\"valueModel\"\n data-testid=\"gr-prompt-input\"\n :type=\"inputType\"\n :inputmode=\"inputmode\"\n :placeholder=\"placeholder\"\n :maxlength=\"maxlength\"\n :show-count=\"showCount\"\n :invalid=\"!!fieldErrorMessage\"\n @blur=\"onBlur\"\n @keydown.enter=\"onEnter\"\n />\n </GrFormField>\n\n <slot name=\"error\" :error=\"error\">\n <GrResponseErrorBanner v-if=\"error\" :error=\"error\" :can-dismiss=\"false\" />\n </slot>\n </div>\n\n <template #footer>\n <slot name=\"footer\">\n <div class=\"flex items-center justify-end gap-3\">\n <GrButton data-testid=\"gr-prompt-cancel\" variant=\"outline\" :size=\"buttonSize\" @click=\"onCancel\">\n {{ resolvedCancelText }}\n </GrButton>\n <GrButton\n data-testid=\"gr-prompt-confirm\"\n :variant=\"confirmVariant\"\n :tone=\"confirmTone\"\n :size=\"buttonSize\"\n :loading=\"confirmLoading || validating\"\n :disabled=\"confirmDisabled || (touched && !canConfirm)\"\n @click=\"onConfirm\"\n >\n {{ resolvedConfirmText }}\n </GrButton>\n </div>\n </slot>\n </template>\n </GrDialog>\n</template>\n","<script setup lang=\"ts\">\n/**\n * GrPromptDialog — GR-примитив диалога ввода значения поверх `GrDialog`.\n *\n * Синхронизирован с `GrDialog` по набору проп-проксирования:\n * `size`, `closeOnBackdrop`, `closeOnEsc`, `showHeader`, `showCloseButton`,\n * `headerConfig`, `footerConfig`, `bodyConfig`, `closeLabel`.\n *\n * Поведение `required=true` (по умолчанию): пустое значение подсвечивается\n * ошибкой только после первого «касания» (blur или попытка confirm),\n * чтобы не шуметь при открытии.\n *\n * Проверки сверх `required` описываются пропом `rules` — тем же движком, что и\n * у `GrForm`: третий частный случай валидации в пакете заводить незачем.\n */\nimport { useGrComponentProp } from '../GrConfigProvider/context'\nimport { computed, ref, watch } from 'vue'\n\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrDialog from '../GrDialog/GrDialog.vue'\nimport GrFormField from '../GrFormField/GrFormField.vue'\nimport GrInput from '../GrInput/GrInput.vue'\nimport GrTextarea from '../GrTextarea/GrTextarea.vue'\nimport GrResponseErrorBanner from '../GrResponseErrorBanner/GrResponseErrorBanner.vue'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n// Именованные импорты чистых функций: разметки не тянут, поэтому в\n// `config.dependencies` не объявляются (см. правило про зависимости).\nimport { createGrFormMessageResolver, rulesForTrigger, runFieldRules } from '../GrForm/validation'\nimport type { GrFormRule, GrFormTrigger } from '../GrForm/validation'\nimport type { GrButtonSize, GrButtonTone, GrButtonVariant } from '../GrButton'\nimport type { GrDialogSectionConfig, GrDialogSize } from '../GrDialog'\n\n/**\n * Тип поля выводим из самого `GrInput`, а не заводим ему именованный алиас:\n * алиас в пропе `GrInput` заменил бы в таблице API витрины список допустимых\n * значений на своё имя — документация стала бы беднее ради удобства обёртки.\n */\ntype GrInputType = NonNullable<InstanceType<typeof GrInput>['$props']['type']>\nimport type { ResponseErrorInfo } from '../GrResponseErrorBanner'\nimport type { InputHTMLAttributes } from 'vue'\n\nexport interface GrPromptDialogProps {\n modelValue: boolean\n value: string\n title?: string\n description?: string\n label?: string\n placeholder?: string\n closeOnBackdrop?: boolean\n closeOnEsc?: boolean\n showHeader?: boolean\n showCloseButton?: boolean\n size?: GrDialogSize\n headerConfig?: GrDialogSectionConfig\n footerConfig?: GrDialogSectionConfig\n bodyConfig?: GrDialogSectionConfig\n /** A11y-лейбл кнопки закрытия (i18n). */\n closeLabel?: string\n buttonSize?: GrButtonSize\n confirmText?: string\n cancelText?: string\n /** Текст ошибки для пустого значения при `required=true` (i18n). */\n requiredErrorText?: string\n confirmVariant?: GrButtonVariant\n confirmTone?: GrButtonTone\n required?: boolean\n /** Тип однострочного поля. В многострочном режиме не применяется. */\n inputType?: GrInputType\n /** Программная клавиатура на мобильных. */\n inputmode?: InputHTMLAttributes['inputmode']\n /** Ограничение длины; со `showCount` рисуется счётчик. */\n maxlength?: number\n showCount?: boolean\n /** Многострочный ввод: вместо `GrInput` рисуется `GrTextarea`. */\n multiline?: boolean\n /** Высота многострочного поля в строках. */\n rows?: number\n /** Автоподбор высоты многострочного поля под содержимое. */\n autosize?: boolean\n /**\n * Правила проверки значения — те же, что у `GrForm` (`required`, `type`,\n * `min`/`max`/`len`, `pattern`, свой в т.ч. асинхронный `validator`).\n * Прогоняются на blur (после первого касания) и на подтверждении.\n */\n rules?: GrFormRule | GrFormRule[]\n /**\n * Структура ошибки ответа сервера для показа общим блоком в теле диалога\n * (через `GrResponseErrorBanner`). `null` — блок скрыт.\n */\n error?: ResponseErrorInfo | null\n /**\n * Внешняя ошибка поля ввода (например, серверная валидация). Имеет приоритет\n * над встроенной проверкой. `null`/`undefined` — нет внешней ошибки.\n */\n fieldError?: string | null\n /** Состояние загрузки кнопки Confirm (async-`onConfirm` in-flight). */\n confirmLoading?: boolean\n /** Принудительно дизейблит кнопку Confirm. */\n confirmDisabled?: boolean\n /**\n * Закрывать ли диалог автоматически по клику Confirm. По умолчанию `true`.\n * `false` — отдаёт управление закрытием наружу (нужно `useDialogService`).\n */\n closeOnConfirm?: boolean\n /**\n * Запрет закрытия «мягкими» способами (Esc, клик по бэкдропу), пока идёт\n * подтверждение или проверка `rules`. Кнопка закрытия и «Отмена» остаются:\n * окно без единого выхода — ловушка.\n */\n persistent?: boolean\n}\n\nexport interface GrPromptDialogEmits {\n (e: 'update:modelValue', value: boolean): void\n (e: 'update:value', value: string): void\n (e: 'confirm', value: string): void\n (e: 'cancel'): void\n}\n\nimport './defaults'\n\nconst props = withDefaults(defineProps<GrPromptDialogProps>(), {\n title: undefined,\n description: undefined,\n label: undefined,\n placeholder: undefined,\n closeOnBackdrop: true,\n closeOnEsc: true,\n showHeader: true,\n showCloseButton: true,\n // Дефолт `size` живёт в резолвере ниже, а не здесь: Vue подставил бы его\n // до того, как компонент заглянет в `GrConfigProvider`.\n size: undefined,\n headerConfig: undefined,\n footerConfig: undefined,\n bodyConfig: undefined,\n closeLabel: undefined,\n buttonSize: undefined,\n confirmText: undefined,\n cancelText: undefined,\n requiredErrorText: undefined,\n confirmVariant: 'primary',\n confirmTone: 'primary',\n required: true,\n inputType: 'text',\n inputmode: undefined,\n maxlength: undefined,\n showCount: false,\n multiline: false,\n rows: undefined,\n autosize: false,\n rules: undefined,\n error: null,\n fieldError: null,\n confirmLoading: false,\n confirmDisabled: false,\n closeOnConfirm: true,\n persistent: false,\n})\n\n// Эффективный размер: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentProp('GrPromptDialog', 'size', () => props.size, 'md')\n\nconst emit = defineEmits<GrPromptDialogEmits>()\n\n// Дефолты берём из общего i18n-блока пакета (fallback — англ.), а не хардкодим.\nconst { t } = useGranularityTranslations()\nconst resolvedTitle = computed(() => props.title ?? t('gr.dialog.prompt.title', 'Prompt'))\nconst resolvedLabel = computed(() => props.label ?? t('gr.dialog.prompt.label', 'Value'))\nconst resolvedRequiredErrorText = computed(() => props.requiredErrorText ?? t('gr.dialog.prompt.required', 'Enter a value.'))\nconst resolvedCloseLabel = computed(() => props.closeLabel ?? t('gr.common.close', 'Close'))\nconst resolvedConfirmText = computed(() => props.confirmText ?? t('gr.common.confirm', 'Confirm'))\nconst resolvedCancelText = computed(() => props.cancelText ?? t('gr.common.cancel', 'Cancel'))\n\nconst open = computed({\n get: () => props.modelValue,\n set: (v: boolean) => emit('update:modelValue', v),\n})\n\nconst valueModel = computed({\n get: () => props.value,\n set: (v: string) => emit('update:value', v),\n})\n\nconst touched = ref(false)\nconst inputRef = ref<InstanceType<typeof GrInput> | null>(null)\nconst textareaRef = ref<InstanceType<typeof GrTextarea> | null>(null)\n\n/** Ошибка последнего прогона `rules`; `required` считается отдельно и мгновенно. */\nconst ruleError = ref<string | undefined>(undefined)\nconst validating = ref(false)\n\n// Сторож устаревших прогонов: асинхронное правило может ответить после того,\n// как значение уже сменилось, и дописать чужую ошибку.\nlet validationRun = 0\n\nwatch(\n () => props.modelValue,\n (isOpen) => {\n if (!isOpen)\n return\n\n touched.value = false\n ruleError.value = undefined\n validationRun += 1\n },\n)\n\nconst resolveMessage = createGrFormMessageResolver(t)\n\nconst normalizedRules = computed<GrFormRule[]>(() => {\n if (!props.rules)\n return []\n return Array.isArray(props.rules) ? props.rules : [props.rules]\n})\n\nconst requiredError = computed(() => {\n if (!props.required)\n return undefined\n return valueModel.value.trim().length > 0 ? undefined : resolvedRequiredErrorText.value\n})\n\nconst canConfirm = computed(() => !requiredError.value)\n\nconst validationError = computed(() => {\n if (!touched.value)\n return undefined\n return requiredError.value ?? ruleError.value\n})\n\n// Внешняя ошибка поля (серверная валидация) имеет приоритет над встроенной.\nconst fieldErrorMessage = computed(() => props.fieldError ?? validationError.value ?? undefined)\n\n/**\n * Прогоняет `rules` для триггера. Возвращает сообщение или `undefined`.\n * `required` здесь не участвует: он синхронный и считается отдельно, чтобы\n * пустое поле блокировало кнопку без похода в асинхронный движок.\n */\nasync function runRules(trigger: GrFormTrigger): Promise<string | undefined> {\n const rules = rulesForTrigger(normalizedRules.value, trigger)\n if (!rules.length)\n return undefined\n\n const run = ++validationRun\n validating.value = true\n try {\n const message = await runFieldRules(valueModel.value, rules, { value: valueModel.value }, resolveMessage)\n // Ответ устарел — значение или сам диалог успели смениться.\n if (run !== validationRun)\n return undefined\n\n ruleError.value = message\n return message\n }\n finally {\n if (run === validationRun)\n validating.value = false\n }\n}\n\nfunction onBlur(): void {\n touched.value = true\n void runRules('blur')\n}\n\n// Пока подтверждение или проверка в полёте, случайное движение не должно\n// оборвать операцию.\nconst softCloseBlocked = computed(() => props.persistent && (props.confirmLoading || validating.value))\nconst resolvedCloseOnBackdrop = computed(() => (softCloseBlocked.value ? false : props.closeOnBackdrop))\nconst resolvedCloseOnEsc = computed(() => (softCloseBlocked.value ? false : props.closeOnEsc))\n\nfunction onCancel(): void {\n emit('cancel')\n emit('update:modelValue', false)\n}\n\nfunction focusField(): void {\n if (props.multiline)\n textareaRef.value?.focus()\n else inputRef.value?.focus()\n}\n\nasync function onConfirm(): Promise<void> {\n touched.value = true\n\n if (!canConfirm.value) {\n focusField()\n return\n }\n\n if (await runRules('submit')) {\n focusField()\n return\n }\n\n emit('confirm', valueModel.value)\n if (props.closeOnConfirm)\n emit('update:modelValue', false)\n}\n\n/**\n * `Enter` подтверждает — базовое ожидание от окна, единственный смысл которого\n * ввести одно значение. В многострочном режиме `Enter` остаётся переводом\n * строки, поэтому обработчик висит только на однострочном поле.\n */\nfunction onEnter(): void {\n void onConfirm()\n}\n\n// Фокус в поле, а не на панель: диалог существует ровно ради ввода.\n//\n// Через `initialFocus` у `GrModal` это не решается: элемент рождается внутри\n// поддерева диалога, и возврат его же пропом наверх замыкает рендер в цикл\n// («Maximum recursive updates» — проверено). Поэтому фокус ставит содержимое,\n// после отрисовки (`flush: 'post'` + кадр), то есть заведомо позже, чем\n// ловушка фокуса `GrModal` переводит фокус на панель.\n// Источник — не только `modelValue`, но и само поле: содержимое панели\n// появляется на такт позже открытия, и фокус по фиксированному `nextTick`\n// приходился на момент, когда фокусировать ещё нечего.\nwatch(\n [() => props.modelValue, inputRef, textareaRef],\n () => {\n if (!props.modelValue)\n return\n focusField()\n },\n // `immediate`: окно могут смонтировать уже открытым — тогда смены пропа не\n // будет вовсе, и без этого фокус остался бы на панели.\n { immediate: true, flush: 'post' },\n)\n\ndefineSlots<{\n /** Содержимое диалога вместо пропа `message`. */\n default?: () => any\n /** Разбор ошибки вместо встроенного баннера. */\n error?: (props: { error: ResponseErrorInfo | null }) => any\n /** Кнопки диалога вместо пары «отмена и подтверждение». */\n footer?: () => any\n}>()\n</script>\n\n<template>\n <GrDialog\n v-model=\"open\"\n :title=\"resolvedTitle\"\n :size=\"resolvedSize\"\n :close-on-backdrop=\"resolvedCloseOnBackdrop\"\n :close-on-esc=\"resolvedCloseOnEsc\"\n :show-header=\"showHeader\"\n :show-close-button=\"showCloseButton\"\n :header-config=\"headerConfig\"\n :footer-config=\"footerConfig\"\n :body-config=\"bodyConfig\"\n :close-label=\"resolvedCloseLabel\"\n >\n <div class=\"grid gap-4\">\n <slot>\n <div v-if=\"description\" class=\"text-[length:var(--gr-control-text-md)] leading-[var(--gr-control-leading-md)] text-[var(--gr-muted-fg)]\">\n {{ description }}\n </div>\n </slot>\n\n <!-- `id` полю не задаём: `GrFormField` генерирует его сам, а поле читает\n из контекста. Литеральный id ломал два открытых диалога сразу. -->\n <GrFormField :label=\"resolvedLabel\" :error=\"fieldErrorMessage\">\n <GrTextarea\n v-if=\"multiline\"\n ref=\"textareaRef\"\n v-model=\"valueModel\"\n data-testid=\"gr-prompt-input\"\n :placeholder=\"placeholder\"\n :rows=\"rows\"\n :autosize=\"autosize\"\n :maxlength=\"maxlength\"\n :show-count=\"showCount\"\n :invalid=\"!!fieldErrorMessage\"\n @blur=\"onBlur\"\n />\n <GrInput\n v-else\n ref=\"inputRef\"\n v-model=\"valueModel\"\n data-testid=\"gr-prompt-input\"\n :type=\"inputType\"\n :inputmode=\"inputmode\"\n :placeholder=\"placeholder\"\n :maxlength=\"maxlength\"\n :show-count=\"showCount\"\n :invalid=\"!!fieldErrorMessage\"\n @blur=\"onBlur\"\n @keydown.enter=\"onEnter\"\n />\n </GrFormField>\n\n <slot name=\"error\" :error=\"error\">\n <GrResponseErrorBanner v-if=\"error\" :error=\"error\" :can-dismiss=\"false\" />\n </slot>\n </div>\n\n <template #footer>\n <slot name=\"footer\">\n <div class=\"flex items-center justify-end gap-3\">\n <GrButton data-testid=\"gr-prompt-cancel\" variant=\"outline\" :size=\"buttonSize\" @click=\"onCancel\">\n {{ resolvedCancelText }}\n </GrButton>\n <GrButton\n data-testid=\"gr-prompt-confirm\"\n :variant=\"confirmVariant\"\n :tone=\"confirmTone\"\n :size=\"buttonSize\"\n :loading=\"confirmLoading || validating\"\n :disabled=\"confirmDisabled || (touched && !canConfirm)\"\n @click=\"onConfirm\"\n >\n {{ resolvedConfirmText }}\n </GrButton>\n </div>\n </slot>\n </template>\n </GrDialog>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"GrPromptDialog-C6QGC7QJ.js","names":[],"sources":["../../../../src/components/GrPromptDialog/GrPromptDialog.vue","../../../../src/components/GrPromptDialog/GrPromptDialog.vue"],"sourcesContent":["<script setup lang=\"ts\">\n/**\n * GrPromptDialog — GR-примитив диалога ввода значения поверх `GrDialog`.\n *\n * Синхронизирован с `GrDialog` по набору проп-проксирования:\n * `size`, `closeOnBackdrop`, `closeOnEsc`, `showHeader`, `showCloseButton`,\n * `headerConfig`, `footerConfig`, `bodyConfig`, `closeLabel`.\n *\n * Поведение `required=true` (по умолчанию): пустое значение подсвечивается\n * ошибкой только после первого «касания» (blur или попытка confirm),\n * чтобы не шуметь при открытии.\n *\n * Проверки сверх `required` описываются пропом `rules` — тем же движком, что и\n * у `GrForm`: третий частный случай валидации в пакете заводить незачем.\n */\nimport { useGrComponentProp } from '../GrConfigProvider/context'\nimport { computed, ref, watch } from 'vue'\n\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrDialog from '../GrDialog/GrDialog.vue'\nimport GrFormField from '../GrFormField/GrFormField.vue'\nimport GrInput from '../GrInput/GrInput.vue'\nimport GrTextarea from '../GrTextarea/GrTextarea.vue'\nimport GrResponseErrorBanner from '../GrResponseErrorBanner/GrResponseErrorBanner.vue'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n// Именованные импорты чистых функций: разметки не тянут, поэтому в\n// `config.dependencies` не объявляются (см. правило про зависимости).\nimport { createGrFormMessageResolver, rulesForTrigger, runFieldRules } from '../GrForm/validation'\nimport type { GrFormRule, GrFormTrigger } from '../GrForm/validation'\nimport type { GrButtonSize, GrButtonTone, GrButtonVariant } from '../GrButton'\nimport type { GrDialogSectionConfig, GrDialogSize } from '../GrDialog'\n\n/**\n * Тип поля выводим из самого `GrInput`, а не заводим ему именованный алиас:\n * алиас в пропе `GrInput` заменил бы в таблице API витрины список допустимых\n * значений на своё имя — документация стала бы беднее ради удобства обёртки.\n */\ntype GrInputType = NonNullable<InstanceType<typeof GrInput>['$props']['type']>\nimport type { ResponseErrorInfo } from '../GrResponseErrorBanner'\nimport type { InputHTMLAttributes } from 'vue'\n\nexport interface GrPromptDialogProps {\n modelValue: boolean\n value: string\n title?: string\n description?: string\n label?: string\n placeholder?: string\n closeOnBackdrop?: boolean\n closeOnEsc?: boolean\n showHeader?: boolean\n showCloseButton?: boolean\n size?: GrDialogSize\n headerConfig?: GrDialogSectionConfig\n footerConfig?: GrDialogSectionConfig\n bodyConfig?: GrDialogSectionConfig\n /** A11y-лейбл кнопки закрытия (i18n). */\n closeLabel?: string\n buttonSize?: GrButtonSize\n confirmText?: string\n cancelText?: string\n /** Текст ошибки для пустого значения при `required=true` (i18n). */\n requiredErrorText?: string\n confirmVariant?: GrButtonVariant\n confirmTone?: GrButtonTone\n required?: boolean\n /** Тип однострочного поля. В многострочном режиме не применяется. */\n inputType?: GrInputType\n /** Программная клавиатура на мобильных. */\n inputmode?: InputHTMLAttributes['inputmode']\n /** Ограничение длины; со `showCount` рисуется счётчик. */\n maxlength?: number\n showCount?: boolean\n /** Многострочный ввод: вместо `GrInput` рисуется `GrTextarea`. */\n multiline?: boolean\n /** Высота многострочного поля в строках. */\n rows?: number\n /** Автоподбор высоты многострочного поля под содержимое. */\n autosize?: boolean\n /**\n * Правила проверки значения — те же, что у `GrForm` (`required`, `type`,\n * `min`/`max`/`len`, `pattern`, свой в т.ч. асинхронный `validator`).\n * Прогоняются на blur (после первого касания) и на подтверждении.\n */\n rules?: GrFormRule | GrFormRule[]\n /**\n * Структура ошибки ответа сервера для показа общим блоком в теле диалога\n * (через `GrResponseErrorBanner`). `null` — блок скрыт.\n */\n error?: ResponseErrorInfo | null\n /**\n * Внешняя ошибка поля ввода (например, серверная валидация). Имеет приоритет\n * над встроенной проверкой. `null`/`undefined` — нет внешней ошибки.\n */\n fieldError?: string | null\n /** Состояние загрузки кнопки Confirm (async-`onConfirm` in-flight). */\n confirmLoading?: boolean\n /** Принудительно дизейблит кнопку Confirm. */\n confirmDisabled?: boolean\n /**\n * Закрывать ли диалог автоматически по клику Confirm. По умолчанию `true`.\n * `false` — отдаёт управление закрытием наружу (нужно `useDialogService`).\n */\n closeOnConfirm?: boolean\n /**\n * Запрет закрытия «мягкими» способами (Esc, клик по бэкдропу), пока идёт\n * подтверждение или проверка `rules`. Кнопка закрытия и «Отмена» остаются:\n * окно без единого выхода — ловушка.\n */\n persistent?: boolean\n}\n\nexport interface GrPromptDialogEmits {\n (e: 'update:modelValue', value: boolean): void\n (e: 'update:value', value: string): void\n (e: 'confirm', value: string): void\n (e: 'cancel'): void\n}\n\nimport './defaults'\n\nconst props = withDefaults(defineProps<GrPromptDialogProps>(), {\n title: undefined,\n description: undefined,\n label: undefined,\n placeholder: undefined,\n closeOnBackdrop: true,\n closeOnEsc: true,\n showHeader: true,\n showCloseButton: true,\n // Дефолт `size` живёт в резолвере ниже, а не здесь: Vue подставил бы его\n // до того, как компонент заглянет в `GrConfigProvider`.\n size: undefined,\n headerConfig: undefined,\n footerConfig: undefined,\n bodyConfig: undefined,\n closeLabel: undefined,\n buttonSize: undefined,\n confirmText: undefined,\n cancelText: undefined,\n requiredErrorText: undefined,\n confirmVariant: 'primary',\n confirmTone: 'primary',\n required: true,\n inputType: 'text',\n inputmode: undefined,\n maxlength: undefined,\n showCount: false,\n multiline: false,\n rows: undefined,\n autosize: false,\n rules: undefined,\n error: null,\n fieldError: null,\n confirmLoading: false,\n confirmDisabled: false,\n closeOnConfirm: true,\n persistent: false,\n})\n\n// Эффективный размер: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentProp('GrPromptDialog', 'size', () => props.size, 'md')\n\nconst emit = defineEmits<GrPromptDialogEmits>()\n\n// Дефолты берём из общего i18n-блока пакета (fallback — англ.), а не хардкодим.\nconst { t } = useGranularityTranslations()\nconst resolvedTitle = computed(() => props.title ?? t('gr.dialog.prompt.title', 'Prompt'))\nconst resolvedLabel = computed(() => props.label ?? t('gr.dialog.prompt.label', 'Value'))\nconst resolvedRequiredErrorText = computed(() => props.requiredErrorText ?? t('gr.dialog.prompt.required', 'Enter a value.'))\nconst resolvedCloseLabel = computed(() => props.closeLabel ?? t('gr.common.close', 'Close'))\nconst resolvedConfirmText = computed(() => props.confirmText ?? t('gr.common.confirm', 'Confirm'))\nconst resolvedCancelText = computed(() => props.cancelText ?? t('gr.common.cancel', 'Cancel'))\n\nconst open = computed({\n get: () => props.modelValue,\n set: (v: boolean) => emit('update:modelValue', v),\n})\n\nconst valueModel = computed({\n get: () => props.value,\n set: (v: string) => emit('update:value', v),\n})\n\nconst touched = ref(false)\nconst inputRef = ref<InstanceType<typeof GrInput> | null>(null)\nconst textareaRef = ref<InstanceType<typeof GrTextarea> | null>(null)\n\n/** Ошибка последнего прогона `rules`; `required` считается отдельно и мгновенно. */\nconst ruleError = ref<string | undefined>(undefined)\nconst validating = ref(false)\n\n// Сторож устаревших прогонов: асинхронное правило может ответить после того,\n// как значение уже сменилось, и дописать чужую ошибку.\nlet validationRun = 0\n\nwatch(\n () => props.modelValue,\n (isOpen) => {\n if (!isOpen)\n return\n\n touched.value = false\n ruleError.value = undefined\n validationRun += 1\n },\n)\n\nconst resolveMessage = createGrFormMessageResolver(t)\n\nconst normalizedRules = computed<GrFormRule[]>(() => {\n if (!props.rules)\n return []\n return Array.isArray(props.rules) ? props.rules : [props.rules]\n})\n\nconst requiredError = computed(() => {\n if (!props.required)\n return undefined\n return valueModel.value.trim().length > 0 ? undefined : resolvedRequiredErrorText.value\n})\n\nconst canConfirm = computed(() => !requiredError.value)\n\nconst validationError = computed(() => {\n if (!touched.value)\n return undefined\n return requiredError.value ?? ruleError.value\n})\n\n// Внешняя ошибка поля (серверная валидация) имеет приоритет над встроенной.\nconst fieldErrorMessage = computed(() => props.fieldError ?? validationError.value ?? undefined)\n\n/**\n * Прогоняет `rules` для триггера. Возвращает сообщение или `undefined`.\n * `required` здесь не участвует: он синхронный и считается отдельно, чтобы\n * пустое поле блокировало кнопку без похода в асинхронный движок.\n */\nasync function runRules(trigger: GrFormTrigger): Promise<string | undefined> {\n const rules = rulesForTrigger(normalizedRules.value, trigger)\n if (!rules.length)\n return undefined\n\n const run = ++validationRun\n validating.value = true\n try {\n const message = await runFieldRules(valueModel.value, rules, { value: valueModel.value }, resolveMessage)\n // Ответ устарел — значение или сам диалог успели смениться.\n if (run !== validationRun)\n return undefined\n\n ruleError.value = message\n return message\n }\n finally {\n if (run === validationRun)\n validating.value = false\n }\n}\n\nfunction onBlur(): void {\n touched.value = true\n void runRules('blur')\n}\n\n// Пока подтверждение или проверка в полёте, случайное движение не должно\n// оборвать операцию.\nconst softCloseBlocked = computed(() => props.persistent && (props.confirmLoading || validating.value))\nconst resolvedCloseOnBackdrop = computed(() => (softCloseBlocked.value ? false : props.closeOnBackdrop))\nconst resolvedCloseOnEsc = computed(() => (softCloseBlocked.value ? false : props.closeOnEsc))\n\nfunction onCancel(): void {\n emit('cancel')\n emit('update:modelValue', false)\n}\n\nfunction focusField(): void {\n if (props.multiline)\n textareaRef.value?.focus()\n else inputRef.value?.focus()\n}\n\nasync function onConfirm(): Promise<void> {\n touched.value = true\n\n if (!canConfirm.value) {\n focusField()\n return\n }\n\n if (await runRules('submit')) {\n focusField()\n return\n }\n\n emit('confirm', valueModel.value)\n if (props.closeOnConfirm)\n emit('update:modelValue', false)\n}\n\n/**\n * `Enter` подтверждает — базовое ожидание от окна, единственный смысл которого\n * ввести одно значение. В многострочном режиме `Enter` остаётся переводом\n * строки, поэтому обработчик висит только на однострочном поле.\n */\nfunction onEnter(): void {\n void onConfirm()\n}\n\n// Фокус в поле, а не на панель: диалог существует ровно ради ввода.\n//\n// Через `initialFocus` у `GrModal` это не решается: элемент рождается внутри\n// поддерева диалога, и возврат его же пропом наверх замыкает рендер в цикл\n// («Maximum recursive updates» — проверено). Поэтому фокус ставит содержимое,\n// после отрисовки (`flush: 'post'` + кадр), то есть заведомо позже, чем\n// ловушка фокуса `GrModal` переводит фокус на панель.\n// Источник — не только `modelValue`, но и само поле: содержимое панели\n// появляется на такт позже открытия, и фокус по фиксированному `nextTick`\n// приходился на момент, когда фокусировать ещё нечего.\nwatch(\n [() => props.modelValue, inputRef, textareaRef],\n () => {\n if (!props.modelValue)\n return\n focusField()\n },\n // `immediate`: окно могут смонтировать уже открытым — тогда смены пропа не\n // будет вовсе, и без этого фокус остался бы на панели.\n { immediate: true, flush: 'post' },\n)\n\ndefineSlots<{\n /** Содержимое диалога вместо пропа `message`. */\n default?: () => any\n /** Разбор ошибки вместо встроенного баннера. */\n error?: (props: { error: ResponseErrorInfo | null }) => any\n /** Кнопки диалога вместо пары «отмена и подтверждение». */\n footer?: () => any\n}>()\n</script>\n\n<template>\n <GrDialog\n v-model=\"open\"\n :title=\"resolvedTitle\"\n :size=\"resolvedSize\"\n :close-on-backdrop=\"resolvedCloseOnBackdrop\"\n :close-on-esc=\"resolvedCloseOnEsc\"\n :show-header=\"showHeader\"\n :show-close-button=\"showCloseButton\"\n :header-config=\"headerConfig\"\n :footer-config=\"footerConfig\"\n :body-config=\"bodyConfig\"\n :close-label=\"resolvedCloseLabel\"\n >\n <div class=\"grid gap-4\">\n <slot>\n <div v-if=\"description\" class=\"text-[length:var(--gr-control-text-md)] leading-[var(--gr-control-leading-md)] text-[var(--gr-muted-fg)]\">\n {{ description }}\n </div>\n </slot>\n\n <!-- `id` полю не задаём: `GrFormField` генерирует его сам, а поле читает\n из контекста. Литеральный id ломал два открытых диалога сразу. -->\n <GrFormField :label=\"resolvedLabel\" :error=\"fieldErrorMessage\">\n <GrTextarea\n v-if=\"multiline\"\n ref=\"textareaRef\"\n v-model=\"valueModel\"\n data-testid=\"gr-prompt-input\"\n :placeholder=\"placeholder\"\n :rows=\"rows\"\n :autosize=\"autosize\"\n :maxlength=\"maxlength\"\n :show-count=\"showCount\"\n :invalid=\"!!fieldErrorMessage\"\n @blur=\"onBlur\"\n />\n <GrInput\n v-else\n ref=\"inputRef\"\n v-model=\"valueModel\"\n data-testid=\"gr-prompt-input\"\n :type=\"inputType\"\n :inputmode=\"inputmode\"\n :placeholder=\"placeholder\"\n :maxlength=\"maxlength\"\n :show-count=\"showCount\"\n :invalid=\"!!fieldErrorMessage\"\n @blur=\"onBlur\"\n @keydown.enter=\"onEnter\"\n />\n </GrFormField>\n\n <slot name=\"error\" :error=\"error\">\n <GrResponseErrorBanner v-if=\"error\" :error=\"error\" :can-dismiss=\"false\" />\n </slot>\n </div>\n\n <template #footer>\n <slot name=\"footer\">\n <div class=\"flex items-center justify-end gap-3\">\n <GrButton data-testid=\"gr-prompt-cancel\" variant=\"outline\" :size=\"buttonSize\" @click=\"onCancel\">\n {{ resolvedCancelText }}\n </GrButton>\n <GrButton\n data-testid=\"gr-prompt-confirm\"\n :variant=\"confirmVariant\"\n :tone=\"confirmTone\"\n :size=\"buttonSize\"\n :loading=\"confirmLoading || validating\"\n :disabled=\"confirmDisabled || (touched && !canConfirm)\"\n @click=\"onConfirm\"\n >\n {{ resolvedConfirmText }}\n </GrButton>\n </div>\n </slot>\n </template>\n </GrDialog>\n</template>\n","<script setup lang=\"ts\">\n/**\n * GrPromptDialog — GR-примитив диалога ввода значения поверх `GrDialog`.\n *\n * Синхронизирован с `GrDialog` по набору проп-проксирования:\n * `size`, `closeOnBackdrop`, `closeOnEsc`, `showHeader`, `showCloseButton`,\n * `headerConfig`, `footerConfig`, `bodyConfig`, `closeLabel`.\n *\n * Поведение `required=true` (по умолчанию): пустое значение подсвечивается\n * ошибкой только после первого «касания» (blur или попытка confirm),\n * чтобы не шуметь при открытии.\n *\n * Проверки сверх `required` описываются пропом `rules` — тем же движком, что и\n * у `GrForm`: третий частный случай валидации в пакете заводить незачем.\n */\nimport { useGrComponentProp } from '../GrConfigProvider/context'\nimport { computed, ref, watch } from 'vue'\n\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrDialog from '../GrDialog/GrDialog.vue'\nimport GrFormField from '../GrFormField/GrFormField.vue'\nimport GrInput from '../GrInput/GrInput.vue'\nimport GrTextarea from '../GrTextarea/GrTextarea.vue'\nimport GrResponseErrorBanner from '../GrResponseErrorBanner/GrResponseErrorBanner.vue'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n// Именованные импорты чистых функций: разметки не тянут, поэтому в\n// `config.dependencies` не объявляются (см. правило про зависимости).\nimport { createGrFormMessageResolver, rulesForTrigger, runFieldRules } from '../GrForm/validation'\nimport type { GrFormRule, GrFormTrigger } from '../GrForm/validation'\nimport type { GrButtonSize, GrButtonTone, GrButtonVariant } from '../GrButton'\nimport type { GrDialogSectionConfig, GrDialogSize } from '../GrDialog'\n\n/**\n * Тип поля выводим из самого `GrInput`, а не заводим ему именованный алиас:\n * алиас в пропе `GrInput` заменил бы в таблице API витрины список допустимых\n * значений на своё имя — документация стала бы беднее ради удобства обёртки.\n */\ntype GrInputType = NonNullable<InstanceType<typeof GrInput>['$props']['type']>\nimport type { ResponseErrorInfo } from '../GrResponseErrorBanner'\nimport type { InputHTMLAttributes } from 'vue'\n\nexport interface GrPromptDialogProps {\n modelValue: boolean\n value: string\n title?: string\n description?: string\n label?: string\n placeholder?: string\n closeOnBackdrop?: boolean\n closeOnEsc?: boolean\n showHeader?: boolean\n showCloseButton?: boolean\n size?: GrDialogSize\n headerConfig?: GrDialogSectionConfig\n footerConfig?: GrDialogSectionConfig\n bodyConfig?: GrDialogSectionConfig\n /** A11y-лейбл кнопки закрытия (i18n). */\n closeLabel?: string\n buttonSize?: GrButtonSize\n confirmText?: string\n cancelText?: string\n /** Текст ошибки для пустого значения при `required=true` (i18n). */\n requiredErrorText?: string\n confirmVariant?: GrButtonVariant\n confirmTone?: GrButtonTone\n required?: boolean\n /** Тип однострочного поля. В многострочном режиме не применяется. */\n inputType?: GrInputType\n /** Программная клавиатура на мобильных. */\n inputmode?: InputHTMLAttributes['inputmode']\n /** Ограничение длины; со `showCount` рисуется счётчик. */\n maxlength?: number\n showCount?: boolean\n /** Многострочный ввод: вместо `GrInput` рисуется `GrTextarea`. */\n multiline?: boolean\n /** Высота многострочного поля в строках. */\n rows?: number\n /** Автоподбор высоты многострочного поля под содержимое. */\n autosize?: boolean\n /**\n * Правила проверки значения — те же, что у `GrForm` (`required`, `type`,\n * `min`/`max`/`len`, `pattern`, свой в т.ч. асинхронный `validator`).\n * Прогоняются на blur (после первого касания) и на подтверждении.\n */\n rules?: GrFormRule | GrFormRule[]\n /**\n * Структура ошибки ответа сервера для показа общим блоком в теле диалога\n * (через `GrResponseErrorBanner`). `null` — блок скрыт.\n */\n error?: ResponseErrorInfo | null\n /**\n * Внешняя ошибка поля ввода (например, серверная валидация). Имеет приоритет\n * над встроенной проверкой. `null`/`undefined` — нет внешней ошибки.\n */\n fieldError?: string | null\n /** Состояние загрузки кнопки Confirm (async-`onConfirm` in-flight). */\n confirmLoading?: boolean\n /** Принудительно дизейблит кнопку Confirm. */\n confirmDisabled?: boolean\n /**\n * Закрывать ли диалог автоматически по клику Confirm. По умолчанию `true`.\n * `false` — отдаёт управление закрытием наружу (нужно `useDialogService`).\n */\n closeOnConfirm?: boolean\n /**\n * Запрет закрытия «мягкими» способами (Esc, клик по бэкдропу), пока идёт\n * подтверждение или проверка `rules`. Кнопка закрытия и «Отмена» остаются:\n * окно без единого выхода — ловушка.\n */\n persistent?: boolean\n}\n\nexport interface GrPromptDialogEmits {\n (e: 'update:modelValue', value: boolean): void\n (e: 'update:value', value: string): void\n (e: 'confirm', value: string): void\n (e: 'cancel'): void\n}\n\nimport './defaults'\n\nconst props = withDefaults(defineProps<GrPromptDialogProps>(), {\n title: undefined,\n description: undefined,\n label: undefined,\n placeholder: undefined,\n closeOnBackdrop: true,\n closeOnEsc: true,\n showHeader: true,\n showCloseButton: true,\n // Дефолт `size` живёт в резолвере ниже, а не здесь: Vue подставил бы его\n // до того, как компонент заглянет в `GrConfigProvider`.\n size: undefined,\n headerConfig: undefined,\n footerConfig: undefined,\n bodyConfig: undefined,\n closeLabel: undefined,\n buttonSize: undefined,\n confirmText: undefined,\n cancelText: undefined,\n requiredErrorText: undefined,\n confirmVariant: 'primary',\n confirmTone: 'primary',\n required: true,\n inputType: 'text',\n inputmode: undefined,\n maxlength: undefined,\n showCount: false,\n multiline: false,\n rows: undefined,\n autosize: false,\n rules: undefined,\n error: null,\n fieldError: null,\n confirmLoading: false,\n confirmDisabled: false,\n closeOnConfirm: true,\n persistent: false,\n})\n\n// Эффективный размер: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentProp('GrPromptDialog', 'size', () => props.size, 'md')\n\nconst emit = defineEmits<GrPromptDialogEmits>()\n\n// Дефолты берём из общего i18n-блока пакета (fallback — англ.), а не хардкодим.\nconst { t } = useGranularityTranslations()\nconst resolvedTitle = computed(() => props.title ?? t('gr.dialog.prompt.title', 'Prompt'))\nconst resolvedLabel = computed(() => props.label ?? t('gr.dialog.prompt.label', 'Value'))\nconst resolvedRequiredErrorText = computed(() => props.requiredErrorText ?? t('gr.dialog.prompt.required', 'Enter a value.'))\nconst resolvedCloseLabel = computed(() => props.closeLabel ?? t('gr.common.close', 'Close'))\nconst resolvedConfirmText = computed(() => props.confirmText ?? t('gr.common.confirm', 'Confirm'))\nconst resolvedCancelText = computed(() => props.cancelText ?? t('gr.common.cancel', 'Cancel'))\n\nconst open = computed({\n get: () => props.modelValue,\n set: (v: boolean) => emit('update:modelValue', v),\n})\n\nconst valueModel = computed({\n get: () => props.value,\n set: (v: string) => emit('update:value', v),\n})\n\nconst touched = ref(false)\nconst inputRef = ref<InstanceType<typeof GrInput> | null>(null)\nconst textareaRef = ref<InstanceType<typeof GrTextarea> | null>(null)\n\n/** Ошибка последнего прогона `rules`; `required` считается отдельно и мгновенно. */\nconst ruleError = ref<string | undefined>(undefined)\nconst validating = ref(false)\n\n// Сторож устаревших прогонов: асинхронное правило может ответить после того,\n// как значение уже сменилось, и дописать чужую ошибку.\nlet validationRun = 0\n\nwatch(\n () => props.modelValue,\n (isOpen) => {\n if (!isOpen)\n return\n\n touched.value = false\n ruleError.value = undefined\n validationRun += 1\n },\n)\n\nconst resolveMessage = createGrFormMessageResolver(t)\n\nconst normalizedRules = computed<GrFormRule[]>(() => {\n if (!props.rules)\n return []\n return Array.isArray(props.rules) ? props.rules : [props.rules]\n})\n\nconst requiredError = computed(() => {\n if (!props.required)\n return undefined\n return valueModel.value.trim().length > 0 ? undefined : resolvedRequiredErrorText.value\n})\n\nconst canConfirm = computed(() => !requiredError.value)\n\nconst validationError = computed(() => {\n if (!touched.value)\n return undefined\n return requiredError.value ?? ruleError.value\n})\n\n// Внешняя ошибка поля (серверная валидация) имеет приоритет над встроенной.\nconst fieldErrorMessage = computed(() => props.fieldError ?? validationError.value ?? undefined)\n\n/**\n * Прогоняет `rules` для триггера. Возвращает сообщение или `undefined`.\n * `required` здесь не участвует: он синхронный и считается отдельно, чтобы\n * пустое поле блокировало кнопку без похода в асинхронный движок.\n */\nasync function runRules(trigger: GrFormTrigger): Promise<string | undefined> {\n const rules = rulesForTrigger(normalizedRules.value, trigger)\n if (!rules.length)\n return undefined\n\n const run = ++validationRun\n validating.value = true\n try {\n const message = await runFieldRules(valueModel.value, rules, { value: valueModel.value }, resolveMessage)\n // Ответ устарел — значение или сам диалог успели смениться.\n if (run !== validationRun)\n return undefined\n\n ruleError.value = message\n return message\n }\n finally {\n if (run === validationRun)\n validating.value = false\n }\n}\n\nfunction onBlur(): void {\n touched.value = true\n void runRules('blur')\n}\n\n// Пока подтверждение или проверка в полёте, случайное движение не должно\n// оборвать операцию.\nconst softCloseBlocked = computed(() => props.persistent && (props.confirmLoading || validating.value))\nconst resolvedCloseOnBackdrop = computed(() => (softCloseBlocked.value ? false : props.closeOnBackdrop))\nconst resolvedCloseOnEsc = computed(() => (softCloseBlocked.value ? false : props.closeOnEsc))\n\nfunction onCancel(): void {\n emit('cancel')\n emit('update:modelValue', false)\n}\n\nfunction focusField(): void {\n if (props.multiline)\n textareaRef.value?.focus()\n else inputRef.value?.focus()\n}\n\nasync function onConfirm(): Promise<void> {\n touched.value = true\n\n if (!canConfirm.value) {\n focusField()\n return\n }\n\n if (await runRules('submit')) {\n focusField()\n return\n }\n\n emit('confirm', valueModel.value)\n if (props.closeOnConfirm)\n emit('update:modelValue', false)\n}\n\n/**\n * `Enter` подтверждает — базовое ожидание от окна, единственный смысл которого\n * ввести одно значение. В многострочном режиме `Enter` остаётся переводом\n * строки, поэтому обработчик висит только на однострочном поле.\n */\nfunction onEnter(): void {\n void onConfirm()\n}\n\n// Фокус в поле, а не на панель: диалог существует ровно ради ввода.\n//\n// Через `initialFocus` у `GrModal` это не решается: элемент рождается внутри\n// поддерева диалога, и возврат его же пропом наверх замыкает рендер в цикл\n// («Maximum recursive updates» — проверено). Поэтому фокус ставит содержимое,\n// после отрисовки (`flush: 'post'` + кадр), то есть заведомо позже, чем\n// ловушка фокуса `GrModal` переводит фокус на панель.\n// Источник — не только `modelValue`, но и само поле: содержимое панели\n// появляется на такт позже открытия, и фокус по фиксированному `nextTick`\n// приходился на момент, когда фокусировать ещё нечего.\nwatch(\n [() => props.modelValue, inputRef, textareaRef],\n () => {\n if (!props.modelValue)\n return\n focusField()\n },\n // `immediate`: окно могут смонтировать уже открытым — тогда смены пропа не\n // будет вовсе, и без этого фокус остался бы на панели.\n { immediate: true, flush: 'post' },\n)\n\ndefineSlots<{\n /** Содержимое диалога вместо пропа `message`. */\n default?: () => any\n /** Разбор ошибки вместо встроенного баннера. */\n error?: (props: { error: ResponseErrorInfo | null }) => any\n /** Кнопки диалога вместо пары «отмена и подтверждение». */\n footer?: () => any\n}>()\n</script>\n\n<template>\n <GrDialog\n v-model=\"open\"\n :title=\"resolvedTitle\"\n :size=\"resolvedSize\"\n :close-on-backdrop=\"resolvedCloseOnBackdrop\"\n :close-on-esc=\"resolvedCloseOnEsc\"\n :show-header=\"showHeader\"\n :show-close-button=\"showCloseButton\"\n :header-config=\"headerConfig\"\n :footer-config=\"footerConfig\"\n :body-config=\"bodyConfig\"\n :close-label=\"resolvedCloseLabel\"\n >\n <div class=\"grid gap-4\">\n <slot>\n <div v-if=\"description\" class=\"text-[length:var(--gr-control-text-md)] leading-[var(--gr-control-leading-md)] text-[var(--gr-muted-fg)]\">\n {{ description }}\n </div>\n </slot>\n\n <!-- `id` полю не задаём: `GrFormField` генерирует его сам, а поле читает\n из контекста. Литеральный id ломал два открытых диалога сразу. -->\n <GrFormField :label=\"resolvedLabel\" :error=\"fieldErrorMessage\">\n <GrTextarea\n v-if=\"multiline\"\n ref=\"textareaRef\"\n v-model=\"valueModel\"\n data-testid=\"gr-prompt-input\"\n :placeholder=\"placeholder\"\n :rows=\"rows\"\n :autosize=\"autosize\"\n :maxlength=\"maxlength\"\n :show-count=\"showCount\"\n :invalid=\"!!fieldErrorMessage\"\n @blur=\"onBlur\"\n />\n <GrInput\n v-else\n ref=\"inputRef\"\n v-model=\"valueModel\"\n data-testid=\"gr-prompt-input\"\n :type=\"inputType\"\n :inputmode=\"inputmode\"\n :placeholder=\"placeholder\"\n :maxlength=\"maxlength\"\n :show-count=\"showCount\"\n :invalid=\"!!fieldErrorMessage\"\n @blur=\"onBlur\"\n @keydown.enter=\"onEnter\"\n />\n </GrFormField>\n\n <slot name=\"error\" :error=\"error\">\n <GrResponseErrorBanner v-if=\"error\" :error=\"error\" :can-dismiss=\"false\" />\n </slot>\n </div>\n\n <template #footer>\n <slot name=\"footer\">\n <div class=\"flex items-center justify-end gap-3\">\n <GrButton data-testid=\"gr-prompt-cancel\" variant=\"outline\" :size=\"buttonSize\" @click=\"onCancel\">\n {{ resolvedCancelText }}\n </GrButton>\n <GrButton\n data-testid=\"gr-prompt-confirm\"\n :variant=\"confirmVariant\"\n :tone=\"confirmTone\"\n :size=\"buttonSize\"\n :loading=\"confirmLoading || validating\"\n :disabled=\"confirmDisabled || (touched && !canConfirm)\"\n @click=\"onConfirm\"\n >\n {{ resolvedConfirmText }}\n </GrButton>\n </div>\n </slot>\n </template>\n </GrDialog>\n</template>\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- import { t as GrPromptDialog_default } from "./chunks/GrPromptDialog-BuJQMZLM.js";
1
+ import { t as GrPromptDialog_default } from "./chunks/GrPromptDialog-C6QGC7QJ.js";
2
2
  import { t as grPromptDialogConfig } from "../../chunks/config-C0Ea9ePP.js";
3
3
  export { GrPromptDialog_default as GrPromptDialog, GrPromptDialog_default as default, grPromptDialogConfig };
@@ -1,4 +1,4 @@
1
- import { n as openModalCount } from "../chunks/overlayStack-C8cfsqXa.js";
1
+ import { t as floatingLayerZIndex } from "../chunks/overlayStack-0CZS99Da.js";
2
2
  import { nextTick, onBeforeUnmount, ref, watch } from "vue";
3
3
  import { autoUpdate, computePosition, flip, offset, shift, size } from "@floating-ui/dom";
4
4
  //#region src/composables/useFloating.ts
@@ -9,26 +9,6 @@ import { autoUpdate, computePosition, flip, offset, shift, size } from "@floatin
9
9
  * официального `@floating-ui/vue` (`useFloating` → `roundByDPR`), чтобы не тянуть весь
10
10
  * пакет ради одной утилиты.
11
11
  */
12
- /**
13
- * Высота панели: своя шкала снаружи и подъём над окном внутри него.
14
- *
15
- * Панель телепортируется в общий портал, то есть становится **соседом** корня
16
- * модального окна, а не его потомком: собственный stacking-контекст окна её не
17
- * накрывает, и статический `--gr-z-dropdown` (1000) оставлял её под окном
18
- * (`--gr-z-modal`, 1100). Поэтому высоту берём из стека слоёв — единственного
19
- * места, которое знает, что сейчас открыто.
20
- *
21
- * Слагаемое — **число** модальных слоёв, а не шаг шкалы: вложенные окна дают
22
- * +2, а полноэкранная загрузка (1150) и тосты (1200) остаются сверху.
23
- *
24
- * Обратный случай — «поповер снаружи не должен перекрывать окно» — не страдает:
25
- * пока окно открыто, страница под ним в `inert`, и открыть там поповер нечем.
26
- * Ненулевой счётчик означает ровно «панель открыта изнутри окна».
27
- */
28
- function floatingZIndex(zIndexVar) {
29
- const modals = openModalCount();
30
- return modals > 0 ? `calc(var(--gr-z-modal) + ${modals})` : `var(${zIndexVar})`;
31
- }
32
12
  function getDpr(element) {
33
13
  if (typeof window === "undefined") return 1;
34
14
  return (element.ownerDocument?.defaultView ?? window).devicePixelRatio || 1;
@@ -133,7 +113,7 @@ function useFloating(reference, floatingEl, open, options = {}) {
133
113
  position: "fixed",
134
114
  left: `${roundByDpr(floating, x)}px`,
135
115
  top: `${roundByDpr(floating, y)}px`,
136
- zIndex: floatingZIndex(options.zIndexVar ?? "--gr-z-dropdown")
116
+ zIndex: floatingLayerZIndex(options.zIndexVar ?? "--gr-z-dropdown")
137
117
  };
138
118
  }
139
119
  function stop() {
@@ -1 +1 @@
1
- {"version":3,"file":"useFloating.js","names":[],"sources":["../../src/composables/useFloating.ts"],"sourcesContent":["import type { CSSProperties, Ref } from 'vue'\nimport type { Middleware, Placement, VirtualElement } from '@floating-ui/dom'\nimport { autoUpdate, computePosition, flip, offset as offsetMiddleware, shift, size as sizeMiddleware } from '@floating-ui/dom'\nimport { nextTick, onBeforeUnmount, ref, watch } from 'vue'\n\nimport { openModalCount } from './internal/overlayStack'\n\nexport type UseFloatingPlacement = Placement\n\n/**\n * Device pixel ratio плавающего элемента. Нужен, чтобы округлять координаты к сетке\n * физических пикселей (см. `roundByDPR`) — иначе субпиксельные `left/top` дают размытый\n * текст/границы панели на не-целочисленных позициях и HiDPI-экранах. Подход перенят у\n * официального `@floating-ui/vue` (`useFloating` → `roundByDPR`), чтобы не тянуть весь\n * пакет ради одной утилиты.\n */\n/**\n * Высота панели: своя шкала снаружи и подъём над окном внутри него.\n *\n * Панель телепортируется в общий портал, то есть становится **соседом** корня\n * модального окна, а не его потомком: собственный stacking-контекст окна её не\n * накрывает, и статический `--gr-z-dropdown` (1000) оставлял её под окном\n * (`--gr-z-modal`, 1100). Поэтому высоту берём из стека слоёв — единственного\n * места, которое знает, что сейчас открыто.\n *\n * Слагаемое — **число** модальных слоёв, а не шаг шкалы: вложенные окна дают\n * +2, а полноэкранная загрузка (1150) и тосты (1200) остаются сверху.\n *\n * Обратный случай — «поповер снаружи не должен перекрывать окно» — не страдает:\n * пока окно открыто, страница под ним в `inert`, и открыть там поповер нечем.\n * Ненулевой счётчик означает ровно «панель открыта изнутри окна».\n */\nfunction floatingZIndex(zIndexVar: string): string {\n const modals = openModalCount()\n\n return modals > 0 ? `calc(var(--gr-z-modal) + ${modals})` : `var(${zIndexVar})`\n}\n\nfunction getDpr(element: HTMLElement): number {\n if (typeof window === 'undefined')\n return 1\n\n const win = element.ownerDocument?.defaultView ?? window\n return win.devicePixelRatio || 1\n}\n\nfunction roundByDpr(element: HTMLElement, value: number): number {\n const dpr = getDpr(element)\n return Math.round(value * dpr) / dpr\n}\n\n/**\n * Прямоугольник в координатах вьюпорта — якорь, которого нет в DOM.\n *\n * Точка курсора описывается прямоугольником `0×0`. Размеры необязательны именно\n * поэтому: контекстное меню, открытое с клавиатуры, встаёт у **прямоугольника**\n * сфокусированной строки, а не у её угла, и переворачивается вместе с ней.\n */\nexport interface GrFloatingAnchorRect {\n x: number\n y: number\n /** По умолчанию 0. */\n width?: number\n /** По умолчанию 0. */\n height?: number\n}\n\n/** Ссылка позиционирования: элемент, виртуальный якорь или ничего. */\nexport type UseFloatingReference = HTMLElement | VirtualElement | null\n\n/**\n * Виртуальный якорь: объект создаётся один раз, координаты читает геттер на\n * каждом измерении.\n *\n * Стабильность объекта здесь не стилистика, а условие работы: `useFloating`\n * пересоздаёт подписку `autoUpdate` при смене самой ссылки, поэтому новый объект\n * на каждое движение курсора рвал бы и заводил её заново. С одним объектом смена\n * точки — это просто `update()`.\n */\nexport function createFloatingAnchor(\n get: () => GrFloatingAnchorRect | null | undefined,\n options: { contextElement?: () => Element | null | undefined } = {},\n): VirtualElement {\n return {\n getBoundingClientRect() {\n const rect = get() ?? { x: 0, y: 0 }\n const width = rect.width ?? 0\n const height = rect.height ?? 0\n\n return {\n x: rect.x,\n y: rect.y,\n width,\n height,\n top: rect.y,\n left: rect.x,\n right: rect.x + width,\n bottom: rect.y + height,\n }\n },\n get contextElement() {\n return options.contextElement?.() ?? undefined\n },\n }\n}\n\nexport interface UseFloatingOptions {\n /**\n * Предпочитаемое место панели относительно триггера. По умолчанию `bottom-start`.\n * Можно передать функцию-геттер, если место зависит от реактивного пропа\n * (например, `align`) — она вызывается заново при каждом пересчёте позиции.\n */\n placement?: UseFloatingPlacement | (() => UseFloatingPlacement)\n /** Зазор между триггером и панелью, px. По умолчанию `8`. */\n offsetPx?: number\n /** Минимальный отступ от края viewport при flip/shift, px. По умолчанию `8`. */\n boundaryPadding?: number\n /**\n * Согласовать ширину панели с шириной триггера (актуально для Select/TreeSelect).\n * Не годится для Dropdown/Tooltip, где ширина панели определяется контентом.\n * - `true` — точная ширина (`width = ширина триггера`);\n * - `'min'` — панель растёт по контенту (`width: max-content`), но не уже триггера\n * (`min-width = ширина триггера`) — режим `GrSelect view=\"link\"`;\n * - функция-геттер — как и `placement`, для реактивной зависимости от пропа.\n *\n * С виртуальным якорем не работает: ширина точки равна нулю.\n */\n matchWidth?: boolean | 'min' | (() => boolean | 'min')\n /**\n * CSS-переменная шкалы слоёв (см. `styles/tokens.css`). По умолчанию\n * `--gr-z-dropdown`. Действует, пока панель открыта вне модального окна: над\n * окном высоту задаёт стек слоёв (см. {@link floatingZIndex}).\n */\n zIndexVar?: string\n}\n\nexport interface UseFloatingReturn {\n /** Готовый объект для `:style` на плавающем элементе. */\n floatingStyle: Ref<CSSProperties>\n /**\n * Итоговое место панели ПОСЛЕ применения `flip` — может отличаться от запрошенного\n * `placement`, если места не хватило и панель перевернуло на противоположную сторону.\n * Полезно для выбора `transform-origin`/направления transition в потребителе.\n */\n resolvedPlacement: Ref<Placement>\n /** Принудительный пересчёт позиции (например, после изменения контента панели). */\n update: () => void\n}\n\n/**\n * Единый движок позиционирования floating-панелей (Dropdown/Select/TreeSelect/Tooltip)\n * поверх `@floating-ui/dom`: flip (переворот при нехватке места), shift (клэмп к границам\n * viewport), опциональный `size`-мидлвар для растягивания по ширине триггера.\n *\n * Пересчёт положения при скролле/ресайзе делает `autoUpdate` из `@floating-ui/dom` —\n * он сам использует `ResizeObserver`/`IntersectionObserver` и пассивные слушатели вместо\n * наивного враппера на каждый `scroll`/`resize`, поэтому отдельный rAF-throttling здесь\n * не нужен (в отличие от прежней ручной реализации в `GrSelect`/`GrDropdown`).\n *\n * Требует, чтобы `floatingEl` был примонтирован в DOM на момент открытия (обычно —\n * `<teleport to=\"body\">` + `v-show`, а не `v-if`): позиционирование не может измерить\n * элемент, которого нет в дереве.\n */\nexport function useFloating(\n // Объединение, а не `Ref<HTMLElement | VirtualElement | null>`: аксессорный\n // `Ref` проверяется контравариантно по записи, и расширение элементного типа\n // отняло бы у шести нынешних потребителей право передавать свой `Ref` как есть.\n reference: Ref<HTMLElement | null> | (() => UseFloatingReference),\n floatingEl: Ref<HTMLElement | null>,\n open: Ref<boolean>,\n options: UseFloatingOptions = {},\n): UseFloatingReturn {\n const readReference: () => UseFloatingReference\n = typeof reference === 'function' ? reference : () => reference.value\n\n const floatingStyle = ref<CSSProperties>({\n position: 'fixed',\n top: '0px',\n left: '0px',\n })\n function resolveRequestedPlacement(): Placement {\n return typeof options.placement === 'function' ? options.placement() : options.placement ?? 'bottom-start'\n }\n\n function resolveMatchWidth(): boolean | 'min' {\n return typeof options.matchWidth === 'function' ? options.matchWidth() : options.matchWidth ?? false\n }\n\n const resolvedPlacement = ref<Placement>(resolveRequestedPlacement())\n\n let stopAutoUpdate: (() => void) | null = null\n\n async function update(): Promise<void> {\n const reference = readReference()\n const floating = floatingEl.value\n if (!reference || !floating)\n return\n\n const middleware: Middleware[] = [\n offsetMiddleware(options.offsetPx ?? 8),\n flip({ padding: options.boundaryPadding ?? 8 }),\n shift({ padding: options.boundaryPadding ?? 8 }),\n ]\n\n const matchWidth = resolveMatchWidth()\n if (matchWidth) {\n middleware.push(\n sizeMiddleware({\n apply({ rects, elements }) {\n if (matchWidth === 'min') {\n Object.assign(elements.floating.style, {\n width: 'max-content',\n minWidth: `${rects.reference.width}px`,\n })\n return\n }\n\n Object.assign(elements.floating.style, {\n width: `${rects.reference.width}px`,\n minWidth: '',\n })\n },\n }),\n )\n }\n\n const { x, y, placement } = await computePosition(reference, floating, {\n placement: resolveRequestedPlacement(),\n strategy: 'fixed',\n middleware,\n })\n\n resolvedPlacement.value = placement\n floatingStyle.value = {\n position: 'fixed',\n left: `${roundByDpr(floating, x)}px`,\n top: `${roundByDpr(floating, y)}px`,\n zIndex: floatingZIndex(options.zIndexVar ?? '--gr-z-dropdown'),\n }\n }\n\n function stop(): void {\n stopAutoUpdate?.()\n stopAutoUpdate = null\n }\n\n function start(): void {\n stop()\n const reference = readReference()\n const floating = floatingEl.value\n if (!reference || !floating)\n return\n\n // `autoUpdate` подписывается на `ResizeObserver`/`IntersectionObserver` для\n // последующих пересчётов, но не гарантирует немедленный вызов `update` —\n // особенно в средах без этих API (например, jsdom в тестах). Считаем позицию\n // сразу, чтобы панель не «мигала» в 0,0 до первого scroll/resize.\n void update()\n stopAutoUpdate = autoUpdate(reference, floating, () => void update())\n }\n\n // На какую ссылку подписан текущий `autoUpdate`. Отличает смену самого якоря\n // (подписку пересоздать) от смены его координат (достаточно `update()`): у\n // виртуального якоря объект один на всё время жизни.\n let subscribedTo: UseFloatingReference = null\n\n watch(\n [open, () => readReference()],\n ([isOpen, nextReference]) => {\n if (!isOpen || !nextReference) {\n stop()\n subscribedTo = null\n return\n }\n\n if (nextReference === subscribedTo && stopAutoUpdate)\n return\n\n // `floatingEl` монтируется в том же тике, что и `open` переключается\n // (v-show уже в DOM, но teleport/переход могут ещё не отработать) —\n // ждём тик, чтобы `getBoundingClientRect` не читал нулевые размеры.\n // Перепроверка `open`: панель могли закрыть, пока `start()` ждал в\n // очереди, — `stop()` уже отработал, и снимать подписку было бы некому.\n void nextTick(() => {\n if (!open.value)\n return\n subscribedTo = readReference()\n start()\n })\n },\n { immediate: true },\n )\n\n onBeforeUnmount(stop)\n\n return {\n floatingStyle,\n resolvedPlacement,\n update: () => void update(),\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useFloating.js","names":[],"sources":["../../src/composables/useFloating.ts"],"sourcesContent":["import type { CSSProperties, Ref } from 'vue'\nimport type { Middleware, Placement, VirtualElement } from '@floating-ui/dom'\nimport { autoUpdate, computePosition, flip, offset as offsetMiddleware, shift, size as sizeMiddleware } from '@floating-ui/dom'\nimport { nextTick, onBeforeUnmount, ref, watch } from 'vue'\n\nimport { floatingLayerZIndex } from './internal/overlayStack'\n\nexport type UseFloatingPlacement = Placement\n\n/**\n * Device pixel ratio плавающего элемента. Нужен, чтобы округлять координаты к сетке\n * физических пикселей (см. `roundByDPR`) — иначе субпиксельные `left/top` дают размытый\n * текст/границы панели на не-целочисленных позициях и HiDPI-экранах. Подход перенят у\n * официального `@floating-ui/vue` (`useFloating` → `roundByDPR`), чтобы не тянуть весь\n * пакет ради одной утилиты.\n */\nfunction getDpr(element: HTMLElement): number {\n if (typeof window === 'undefined')\n return 1\n\n const win = element.ownerDocument?.defaultView ?? window\n return win.devicePixelRatio || 1\n}\n\nfunction roundByDpr(element: HTMLElement, value: number): number {\n const dpr = getDpr(element)\n return Math.round(value * dpr) / dpr\n}\n\n/**\n * Прямоугольник в координатах вьюпорта — якорь, которого нет в DOM.\n *\n * Точка курсора описывается прямоугольником `0×0`. Размеры необязательны именно\n * поэтому: контекстное меню, открытое с клавиатуры, встаёт у **прямоугольника**\n * сфокусированной строки, а не у её угла, и переворачивается вместе с ней.\n */\nexport interface GrFloatingAnchorRect {\n x: number\n y: number\n /** По умолчанию 0. */\n width?: number\n /** По умолчанию 0. */\n height?: number\n}\n\n/** Ссылка позиционирования: элемент, виртуальный якорь или ничего. */\nexport type UseFloatingReference = HTMLElement | VirtualElement | null\n\n/**\n * Виртуальный якорь: объект создаётся один раз, координаты читает геттер на\n * каждом измерении.\n *\n * Стабильность объекта здесь не стилистика, а условие работы: `useFloating`\n * пересоздаёт подписку `autoUpdate` при смене самой ссылки, поэтому новый объект\n * на каждое движение курсора рвал бы и заводил её заново. С одним объектом смена\n * точки — это просто `update()`.\n */\nexport function createFloatingAnchor(\n get: () => GrFloatingAnchorRect | null | undefined,\n options: { contextElement?: () => Element | null | undefined } = {},\n): VirtualElement {\n return {\n getBoundingClientRect() {\n const rect = get() ?? { x: 0, y: 0 }\n const width = rect.width ?? 0\n const height = rect.height ?? 0\n\n return {\n x: rect.x,\n y: rect.y,\n width,\n height,\n top: rect.y,\n left: rect.x,\n right: rect.x + width,\n bottom: rect.y + height,\n }\n },\n get contextElement() {\n return options.contextElement?.() ?? undefined\n },\n }\n}\n\nexport interface UseFloatingOptions {\n /**\n * Предпочитаемое место панели относительно триггера. По умолчанию `bottom-start`.\n * Можно передать функцию-геттер, если место зависит от реактивного пропа\n * (например, `align`) — она вызывается заново при каждом пересчёте позиции.\n */\n placement?: UseFloatingPlacement | (() => UseFloatingPlacement)\n /** Зазор между триггером и панелью, px. По умолчанию `8`. */\n offsetPx?: number\n /** Минимальный отступ от края viewport при flip/shift, px. По умолчанию `8`. */\n boundaryPadding?: number\n /**\n * Согласовать ширину панели с шириной триггера (актуально для Select/TreeSelect).\n * Не годится для Dropdown/Tooltip, где ширина панели определяется контентом.\n * - `true` — точная ширина (`width = ширина триггера`);\n * - `'min'` — панель растёт по контенту (`width: max-content`), но не уже триггера\n * (`min-width = ширина триггера`) — режим `GrSelect view=\"link\"`;\n * - функция-геттер — как и `placement`, для реактивной зависимости от пропа.\n *\n * С виртуальным якорем не работает: ширина точки равна нулю.\n */\n matchWidth?: boolean | 'min' | (() => boolean | 'min')\n /**\n * CSS-переменная шкалы слоёв (см. `styles/tokens.css`). По умолчанию\n * `--gr-z-dropdown`. Действует, пока панель открыта вне модального окна: над\n * окном высоту задаёт стек слоёв (см. {@link floatingZIndex}).\n */\n zIndexVar?: string\n}\n\nexport interface UseFloatingReturn {\n /** Готовый объект для `:style` на плавающем элементе. */\n floatingStyle: Ref<CSSProperties>\n /**\n * Итоговое место панели ПОСЛЕ применения `flip` — может отличаться от запрошенного\n * `placement`, если места не хватило и панель перевернуло на противоположную сторону.\n * Полезно для выбора `transform-origin`/направления transition в потребителе.\n */\n resolvedPlacement: Ref<Placement>\n /** Принудительный пересчёт позиции (например, после изменения контента панели). */\n update: () => void\n}\n\n/**\n * Единый движок позиционирования floating-панелей (Dropdown/Select/TreeSelect/Tooltip)\n * поверх `@floating-ui/dom`: flip (переворот при нехватке места), shift (клэмп к границам\n * viewport), опциональный `size`-мидлвар для растягивания по ширине триггера.\n *\n * Пересчёт положения при скролле/ресайзе делает `autoUpdate` из `@floating-ui/dom` —\n * он сам использует `ResizeObserver`/`IntersectionObserver` и пассивные слушатели вместо\n * наивного враппера на каждый `scroll`/`resize`, поэтому отдельный rAF-throttling здесь\n * не нужен (в отличие от прежней ручной реализации в `GrSelect`/`GrDropdown`).\n *\n * Требует, чтобы `floatingEl` был примонтирован в DOM на момент открытия (обычно —\n * `<teleport to=\"body\">` + `v-show`, а не `v-if`): позиционирование не может измерить\n * элемент, которого нет в дереве.\n */\nexport function useFloating(\n // Объединение, а не `Ref<HTMLElement | VirtualElement | null>`: аксессорный\n // `Ref` проверяется контравариантно по записи, и расширение элементного типа\n // отняло бы у шести нынешних потребителей право передавать свой `Ref` как есть.\n reference: Ref<HTMLElement | null> | (() => UseFloatingReference),\n floatingEl: Ref<HTMLElement | null>,\n open: Ref<boolean>,\n options: UseFloatingOptions = {},\n): UseFloatingReturn {\n const readReference: () => UseFloatingReference\n = typeof reference === 'function' ? reference : () => reference.value\n\n const floatingStyle = ref<CSSProperties>({\n position: 'fixed',\n top: '0px',\n left: '0px',\n })\n function resolveRequestedPlacement(): Placement {\n return typeof options.placement === 'function' ? options.placement() : options.placement ?? 'bottom-start'\n }\n\n function resolveMatchWidth(): boolean | 'min' {\n return typeof options.matchWidth === 'function' ? options.matchWidth() : options.matchWidth ?? false\n }\n\n const resolvedPlacement = ref<Placement>(resolveRequestedPlacement())\n\n let stopAutoUpdate: (() => void) | null = null\n\n async function update(): Promise<void> {\n const reference = readReference()\n const floating = floatingEl.value\n if (!reference || !floating)\n return\n\n const middleware: Middleware[] = [\n offsetMiddleware(options.offsetPx ?? 8),\n flip({ padding: options.boundaryPadding ?? 8 }),\n shift({ padding: options.boundaryPadding ?? 8 }),\n ]\n\n const matchWidth = resolveMatchWidth()\n if (matchWidth) {\n middleware.push(\n sizeMiddleware({\n apply({ rects, elements }) {\n if (matchWidth === 'min') {\n Object.assign(elements.floating.style, {\n width: 'max-content',\n minWidth: `${rects.reference.width}px`,\n })\n return\n }\n\n Object.assign(elements.floating.style, {\n width: `${rects.reference.width}px`,\n minWidth: '',\n })\n },\n }),\n )\n }\n\n const { x, y, placement } = await computePosition(reference, floating, {\n placement: resolveRequestedPlacement(),\n strategy: 'fixed',\n middleware,\n })\n\n resolvedPlacement.value = placement\n floatingStyle.value = {\n position: 'fixed',\n left: `${roundByDpr(floating, x)}px`,\n top: `${roundByDpr(floating, y)}px`,\n zIndex: floatingLayerZIndex(options.zIndexVar ?? '--gr-z-dropdown'),\n }\n }\n\n function stop(): void {\n stopAutoUpdate?.()\n stopAutoUpdate = null\n }\n\n function start(): void {\n stop()\n const reference = readReference()\n const floating = floatingEl.value\n if (!reference || !floating)\n return\n\n // `autoUpdate` подписывается на `ResizeObserver`/`IntersectionObserver` для\n // последующих пересчётов, но не гарантирует немедленный вызов `update` —\n // особенно в средах без этих API (например, jsdom в тестах). Считаем позицию\n // сразу, чтобы панель не «мигала» в 0,0 до первого scroll/resize.\n void update()\n stopAutoUpdate = autoUpdate(reference, floating, () => void update())\n }\n\n // На какую ссылку подписан текущий `autoUpdate`. Отличает смену самого якоря\n // (подписку пересоздать) от смены его координат (достаточно `update()`): у\n // виртуального якоря объект один на всё время жизни.\n let subscribedTo: UseFloatingReference = null\n\n watch(\n [open, () => readReference()],\n ([isOpen, nextReference]) => {\n if (!isOpen || !nextReference) {\n stop()\n subscribedTo = null\n return\n }\n\n if (nextReference === subscribedTo && stopAutoUpdate)\n return\n\n // `floatingEl` монтируется в том же тике, что и `open` переключается\n // (v-show уже в DOM, но teleport/переход могут ещё не отработать) —\n // ждём тик, чтобы `getBoundingClientRect` не читал нулевые размеры.\n // Перепроверка `open`: панель могли закрыть, пока `start()` ждал в\n // очереди, — `stop()` уже отработал, и снимать подписку было бы некому.\n void nextTick(() => {\n if (!open.value)\n return\n subscribedTo = readReference()\n start()\n })\n },\n { immediate: true },\n )\n\n onBeforeUnmount(stop)\n\n return {\n floatingStyle,\n resolvedPlacement,\n update: () => void update(),\n }\n}\n"],"mappings":";;;;;;;;;;;AAgBA,SAAS,OAAO,SAA8B;CAC5C,IAAI,OAAO,WAAW,aACpB,OAAO;CAGT,QADY,QAAQ,eAAe,eAAe,OAAA,CACvC,oBAAoB;AACjC;AAEA,SAAS,WAAW,SAAsB,OAAuB;CAC/D,MAAM,MAAM,OAAO,OAAO;CAC1B,OAAO,KAAK,MAAM,QAAQ,GAAG,IAAI;AACnC;;;;;;;;;;AA8BA,SAAgB,qBACd,KACA,UAAiE,CAAC,GAClD;CAChB,OAAO;EACL,wBAAwB;GACtB,MAAM,OAAO,IAAI,KAAK;IAAE,GAAG;IAAG,GAAG;GAAE;GACnC,MAAM,QAAQ,KAAK,SAAS;GAC5B,MAAM,SAAS,KAAK,UAAU;GAE9B,OAAO;IACL,GAAG,KAAK;IACR,GAAG,KAAK;IACR;IACA;IACA,KAAK,KAAK;IACV,MAAM,KAAK;IACX,OAAO,KAAK,IAAI;IAChB,QAAQ,KAAK,IAAI;GACnB;EACF;EACA,IAAI,iBAAiB;GACnB,OAAO,QAAQ,iBAAiB,KAAK,KAAA;EACvC;CACF;AACF;;;;;;;;;;;;;;;AA2DA,SAAgB,YAId,WACA,YACA,MACA,UAA8B,CAAC,GACZ;CACnB,MAAM,gBACF,OAAO,cAAc,aAAa,kBAAkB,UAAU;CAElE,MAAM,gBAAgB,IAAmB;EACvC,UAAU;EACV,KAAK;EACL,MAAM;CACR,CAAC;CACD,SAAS,4BAAuC;EAC9C,OAAO,OAAO,QAAQ,cAAc,aAAa,QAAQ,UAAU,IAAI,QAAQ,aAAa;CAC9F;CAEA,SAAS,oBAAqC;EAC5C,OAAO,OAAO,QAAQ,eAAe,aAAa,QAAQ,WAAW,IAAI,QAAQ,cAAc;CACjG;CAEA,MAAM,oBAAoB,IAAe,0BAA0B,CAAC;CAEpE,IAAI,iBAAsC;CAE1C,eAAe,SAAwB;EACrC,MAAM,YAAY,cAAc;EAChC,MAAM,WAAW,WAAW;EAC5B,IAAI,CAAC,aAAa,CAAC,UACjB;EAEF,MAAM,aAA2B;GAC/B,OAAiB,QAAQ,YAAY,CAAC;GACtC,KAAK,EAAE,SAAS,QAAQ,mBAAmB,EAAE,CAAC;GAC9C,MAAM,EAAE,SAAS,QAAQ,mBAAmB,EAAE,CAAC;EACjD;EAEA,MAAM,aAAa,kBAAkB;EACrC,IAAI,YACF,WAAW,KACT,KAAe,EACb,MAAM,EAAE,OAAO,YAAY;GACzB,IAAI,eAAe,OAAO;IACxB,OAAO,OAAO,SAAS,SAAS,OAAO;KACrC,OAAO;KACP,UAAU,GAAG,MAAM,UAAU,MAAM;IACrC,CAAC;IACD;GACF;GAEA,OAAO,OAAO,SAAS,SAAS,OAAO;IACrC,OAAO,GAAG,MAAM,UAAU,MAAM;IAChC,UAAU;GACZ,CAAC;EACH,EACF,CAAC,CACH;EAGF,MAAM,EAAE,GAAG,GAAG,cAAc,MAAM,gBAAgB,WAAW,UAAU;GACrE,WAAW,0BAA0B;GACrC,UAAU;GACV;EACF,CAAC;EAED,kBAAkB,QAAQ;EAC1B,cAAc,QAAQ;GACpB,UAAU;GACV,MAAM,GAAG,WAAW,UAAU,CAAC,EAAE;GACjC,KAAK,GAAG,WAAW,UAAU,CAAC,EAAE;GAChC,QAAQ,oBAAoB,QAAQ,aAAa,iBAAiB;EACpE;CACF;CAEA,SAAS,OAAa;EACpB,iBAAiB;EACjB,iBAAiB;CACnB;CAEA,SAAS,QAAc;EACrB,KAAK;EACL,MAAM,YAAY,cAAc;EAChC,MAAM,WAAW,WAAW;EAC5B,IAAI,CAAC,aAAa,CAAC,UACjB;EAMF,OAAY;EACZ,iBAAiB,WAAW,WAAW,gBAAgB,KAAK,OAAO,CAAC;CACtE;CAKA,IAAI,eAAqC;CAEzC,MACE,CAAC,YAAY,cAAc,CAAC,IAC3B,CAAC,QAAQ,mBAAmB;EAC3B,IAAI,CAAC,UAAU,CAAC,eAAe;GAC7B,KAAK;GACL,eAAe;GACf;EACF;EAEA,IAAI,kBAAkB,gBAAgB,gBACpC;EAOF,eAAoB;GAClB,IAAI,CAAC,KAAK,OACR;GACF,eAAe,cAAc;GAC7B,MAAM;EACR,CAAC;CACH,GACA,EAAE,WAAW,KAAK,CACpB;CAEA,gBAAgB,IAAI;CAEpB,OAAO;EACL;EACA;EACA,cAAc,KAAK,OAAO;CAC5B;AACF"}
@@ -1,5 +1,5 @@
1
- import { i as removeOverlayLayer, r as pushOverlayLayer, t as layerRootsAbove } from "../chunks/overlayStack-C8cfsqXa.js";
2
- import { nextTick, onUnmounted, watch } from "vue";
1
+ import { a as removeOverlayLayer, i as pushOverlayLayer, n as layerRootsAbove } from "../chunks/overlayStack-0CZS99Da.js";
2
+ import { nextTick, onUnmounted, ref, watch } from "vue";
3
3
  //#region src/composables/useOverlayLayer.ts
4
4
  /**
5
5
  * Регистрирует оверлей в общем стеке слоёв, пока `open` истинно.
@@ -24,6 +24,7 @@ import { nextTick, onUnmounted, watch } from "vue";
24
24
  function useOverlayLayer(open, onDismiss, options = {}) {
25
25
  let layerId = null;
26
26
  let previouslyFocused = null;
27
+ const depth = ref(0);
27
28
  function register() {
28
29
  if (layerId !== null) return;
29
30
  if (typeof window === "undefined") return;
@@ -33,6 +34,9 @@ function useOverlayLayer(open, onDismiss, options = {}) {
33
34
  shouldClose: () => options.closeOnEscape?.() ?? true,
34
35
  close: onDismiss,
35
36
  setTopmost: options.onTopmostChange,
37
+ setDepth: (value) => {
38
+ depth.value = value;
39
+ },
36
40
  root: () => options.root?.value ?? null
37
41
  });
38
42
  }
@@ -59,7 +63,10 @@ function useOverlayLayer(open, onDismiss, options = {}) {
59
63
  else unregister();
60
64
  }, { immediate: true });
61
65
  onUnmounted(unregister);
62
- return { rootsAbove: () => layerId === null ? [] : layerRootsAbove(layerId) };
66
+ return {
67
+ rootsAbove: () => layerId === null ? [] : layerRootsAbove(layerId),
68
+ depth
69
+ };
63
70
  }
64
71
  //#endregion
65
72
  export { useOverlayLayer };
@@ -1 +1 @@
1
- {"version":3,"file":"useOverlayLayer.js","names":[],"sources":["../../src/composables/useOverlayLayer.ts"],"sourcesContent":["import type { Ref } from 'vue'\nimport { nextTick, onUnmounted, watch } from 'vue'\n\nimport { layerRootsAbove, pushOverlayLayer, removeOverlayLayer } from './internal/overlayStack'\n\nexport interface UseOverlayLayerOptions {\n /**\n * Модальный слой: блокирует страницу, и модалки под ним получают `inert`.\n * Немодальные (поповеры, меню, подсказки) участвуют только в очереди Esc.\n * По умолчанию `false`.\n *\n * Геттером — там, где модальность включается пропом (`GrDrawer`): значение\n * читается в момент регистрации, то есть при каждом открытии.\n */\n modal?: boolean | (() => boolean)\n /**\n * Закрывать ли слой по Esc. Геттер, а не значение, — чтобы читать реактивный\n * проп (`closeOnEsc`). По умолчанию закрывается всегда.\n *\n * Если геттер вернёт `false`, нажатие всё равно не пройдёт на слой ниже:\n * пользователь видит верхним этот слой, и Esc адресован ему.\n */\n closeOnEscape?: () => boolean\n /**\n * Уведомление «этот модальный слой сейчас верхний». Компонент вешает по нему\n * `inert` на свой корень, когда перестал быть верхним.\n */\n onTopmostChange?: (isTopmost: boolean) => void\n /**\n * Возвращать фокус элементу, который был активен до открытия. По умолчанию —\n * да. Отключать имеет смысл там, где фокус с триггера и не уходил: `GrSelect`\n * ведёт список через `aria-activedescendant`.\n */\n restoreFocus?: boolean\n /**\n * Корень слоя. Нужен дважды: для правила возврата фокуса (если на момент\n * закрытия фокус уже вне слоя, пользователь ушёл сам — забирать нельзя) и\n * для ловушки фокуса слоя под ним, которая обязана считать этот корень\n * своим (панель селекта внутри модалки телепортирована в `body`).\n */\n root?: Ref<HTMLElement | null>\n}\n\nexport interface OverlayLayerHandle {\n /**\n * Корни слоёв, открытых поверх этого. Передаётся в `useFocusTrap` как\n * `containers`: пока панель, открытая изнутри окна, жива, фокус в ней\n * законен.\n */\n rootsAbove: () => HTMLElement[]\n}\n\n/**\n * Регистрирует оверлей в общем стеке слоёв, пока `open` истинно.\n *\n * Один контракт на порядок Esc, `inert` и возврат фокуса — три механизма,\n * которые обязаны видеть один и тот же порядок слоёв. Что берёт на себя:\n *\n * - **Esc** — верхнему слою, вниз не проваливается;\n * - **`inert`** — через `onTopmostChange` у модальных слоёв;\n * - **возврат фокуса** — по единому правилу (см. ниже).\n *\n * Чего **не** делает: фокус-ловушку — это `useFocusTrap`, отдельный примитив.\n * Поповеру ловушка и не нужна: Tab обязан уводить фокус наружу и закрывать его,\n * поэтому связка «слой + ловушка» собирается в компоненте, а не тут.\n *\n * Правило возврата фокуса: восстанавливаем, **только если на момент закрытия\n * фокус всё ещё внутри слоя**. Если пользователь кликнул в другое поле, фокус\n * уже там — забирать его обратно нельзя. Эвристика «возвращать, если открыли с\n * клавиатуры» промахивается в обе стороны: мышью открытый слой фокус не вернёт,\n * а клавиатурный отберёт его у того, куда пользователь ушёл сам.\n */\nexport function useOverlayLayer(\n open: Ref<boolean>,\n onDismiss: () => void,\n options: UseOverlayLayerOptions = {},\n): OverlayLayerHandle {\n let layerId: number | null = null\n let previouslyFocused: HTMLElement | null = null\n\n function register(): void {\n if (layerId !== null)\n return\n\n // Стек — браузерное понятие: Esc, `inert` и возврат фокуса на сервере\n // бессмысленны. Регистрируйся слой и там — он остался бы в модульном массиве\n // навсегда, потому что `onUnmounted` при `renderToString` не вызывается, и\n // каждый серверный рендер открытого оверлея копил бы замыкание со ссылками\n // на свои компоненты.\n if (typeof window === 'undefined')\n return\n\n if (options.restoreFocus !== false && typeof document !== 'undefined')\n previouslyFocused = (document.activeElement as HTMLElement) ?? null\n\n layerId = pushOverlayLayer({\n modal: typeof options.modal === 'function' ? options.modal() : options.modal ?? false,\n shouldClose: () => options.closeOnEscape?.() ?? true,\n close: onDismiss,\n setTopmost: options.onTopmostChange,\n root: () => options.root?.value ?? null,\n })\n }\n\n function unregister(): void {\n if (layerId === null)\n return\n removeOverlayLayer(layerId)\n layerId = null\n\n restoreFocus()\n }\n\n function restoreFocus(): void {\n const target = previouslyFocused\n previouslyFocused = null\n\n if (options.restoreFocus === false || !target)\n return\n if (typeof document === 'undefined')\n return\n\n // Фокус уже вне слоя — пользователь ушёл сам, отбирать нельзя. Решение\n // принимаем сейчас, пока слой ещё в DOM, а применяем следующим тиком.\n const active = document.activeElement\n const root = options.root?.value\n if (root && active && active !== document.body && !root.contains(active))\n return\n\n // Возврат откладывается намеренно: ловушка фокуса того же слоя снимает свои\n // слушатели в этом же флаше, и сфокусируй мы триггер сразу — она затащила бы\n // фокус обратно в закрывающийся слой.\n void nextTick(() => {\n if (target.isConnected)\n target.focus?.()\n })\n }\n\n watch(\n open,\n (isOpen) => {\n if (isOpen)\n register()\n else unregister()\n },\n { immediate: true },\n )\n\n onUnmounted(unregister)\n\n return {\n rootsAbove: () => (layerId === null ? [] : layerRootsAbove(layerId)),\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAwEA,SAAgB,gBACd,MACA,WACA,UAAkC,CAAC,GACf;CACpB,IAAI,UAAyB;CAC7B,IAAI,oBAAwC;CAE5C,SAAS,WAAiB;EACxB,IAAI,YAAY,MACd;EAOF,IAAI,OAAO,WAAW,aACpB;EAEF,IAAI,QAAQ,iBAAiB,SAAS,OAAO,aAAa,aACxD,oBAAqB,SAAS,iBAAiC;EAEjE,UAAU,iBAAiB;GACzB,OAAO,OAAO,QAAQ,UAAU,aAAa,QAAQ,MAAM,IAAI,QAAQ,SAAS;GAChF,mBAAmB,QAAQ,gBAAgB,KAAK;GAChD,OAAO;GACP,YAAY,QAAQ;GACpB,YAAY,QAAQ,MAAM,SAAS;EACrC,CAAC;CACH;CAEA,SAAS,aAAmB;EAC1B,IAAI,YAAY,MACd;EACF,mBAAmB,OAAO;EAC1B,UAAU;EAEV,aAAa;CACf;CAEA,SAAS,eAAqB;EAC5B,MAAM,SAAS;EACf,oBAAoB;EAEpB,IAAI,QAAQ,iBAAiB,SAAS,CAAC,QACrC;EACF,IAAI,OAAO,aAAa,aACtB;EAIF,MAAM,SAAS,SAAS;EACxB,MAAM,OAAO,QAAQ,MAAM;EAC3B,IAAI,QAAQ,UAAU,WAAW,SAAS,QAAQ,CAAC,KAAK,SAAS,MAAM,GACrE;EAKF,eAAoB;GAClB,IAAI,OAAO,aACT,OAAO,QAAQ;EACnB,CAAC;CACH;CAEA,MACE,OACC,WAAW;EACV,IAAI,QACF,SAAS;OACN,WAAW;CAClB,GACA,EAAE,WAAW,KAAK,CACpB;CAEA,YAAY,UAAU;CAEtB,OAAO,EACL,kBAAmB,YAAY,OAAO,CAAC,IAAI,gBAAgB,OAAO,EACpE;AACF"}
1
+ {"version":3,"file":"useOverlayLayer.js","names":[],"sources":["../../src/composables/useOverlayLayer.ts"],"sourcesContent":["import type { Ref } from 'vue'\nimport { nextTick, onUnmounted, ref, watch } from 'vue'\n\nimport { layerRootsAbove, pushOverlayLayer, removeOverlayLayer } from './internal/overlayStack'\n\nexport interface UseOverlayLayerOptions {\n /**\n * Модальный слой: блокирует страницу, и модалки под ним получают `inert`.\n * Немодальные (поповеры, меню, подсказки) участвуют только в очереди Esc.\n * По умолчанию `false`.\n *\n * Геттером — там, где модальность включается пропом (`GrDrawer`): значение\n * читается в момент регистрации, то есть при каждом открытии.\n */\n modal?: boolean | (() => boolean)\n /**\n * Закрывать ли слой по Esc. Геттер, а не значение, — чтобы читать реактивный\n * проп (`closeOnEsc`). По умолчанию закрывается всегда.\n *\n * Если геттер вернёт `false`, нажатие всё равно не пройдёт на слой ниже:\n * пользователь видит верхним этот слой, и Esc адресован ему.\n */\n closeOnEscape?: () => boolean\n /**\n * Уведомление «этот модальный слой сейчас верхний». Компонент вешает по нему\n * `inert` на свой корень, когда перестал быть верхним.\n */\n onTopmostChange?: (isTopmost: boolean) => void\n /**\n * Возвращать фокус элементу, который был активен до открытия. По умолчанию —\n * да. Отключать имеет смысл там, где фокус с триггера и не уходил: `GrSelect`\n * ведёт список через `aria-activedescendant`.\n */\n restoreFocus?: boolean\n /**\n * Корень слоя. Нужен дважды: для правила возврата фокуса (если на момент\n * закрытия фокус уже вне слоя, пользователь ушёл сам — забирать нельзя) и\n * для ловушки фокуса слоя под ним, которая обязана считать этот корень\n * своим (панель селекта внутри модалки телепортирована в `body`).\n */\n root?: Ref<HTMLElement | null>\n}\n\nexport interface OverlayLayerHandle {\n /**\n * Корни слоёв, открытых поверх этого. Передаётся в `useFocusTrap` как\n * `containers`: пока панель, открытая изнутри окна, жива, фокус в ней\n * законен.\n */\n rootsAbove: () => HTMLElement[]\n /**\n * Позиция среди модальных слоёв, от нуля. По ней компонент считает высоту.\n *\n * Значение **не сбрасывается** при закрытии: слой уходит из стека сразу, а\n * его поддерево живёт до конца leave-анимации. Обнули глубину там — уходящее\n * окно нырнуло бы под соседнее прямо во время ухода.\n */\n depth: Ref<number>\n}\n\n/**\n * Регистрирует оверлей в общем стеке слоёв, пока `open` истинно.\n *\n * Один контракт на порядок Esc, `inert` и возврат фокуса — три механизма,\n * которые обязаны видеть один и тот же порядок слоёв. Что берёт на себя:\n *\n * - **Esc** — верхнему слою, вниз не проваливается;\n * - **`inert`** — через `onTopmostChange` у модальных слоёв;\n * - **возврат фокуса** — по единому правилу (см. ниже).\n *\n * Чего **не** делает: фокус-ловушку — это `useFocusTrap`, отдельный примитив.\n * Поповеру ловушка и не нужна: Tab обязан уводить фокус наружу и закрывать его,\n * поэтому связка «слой + ловушка» собирается в компоненте, а не тут.\n *\n * Правило возврата фокуса: восстанавливаем, **только если на момент закрытия\n * фокус всё ещё внутри слоя**. Если пользователь кликнул в другое поле, фокус\n * уже там — забирать его обратно нельзя. Эвристика «возвращать, если открыли с\n * клавиатуры» промахивается в обе стороны: мышью открытый слой фокус не вернёт,\n * а клавиатурный отберёт его у того, куда пользователь ушёл сам.\n */\nexport function useOverlayLayer(\n open: Ref<boolean>,\n onDismiss: () => void,\n options: UseOverlayLayerOptions = {},\n): OverlayLayerHandle {\n let layerId: number | null = null\n let previouslyFocused: HTMLElement | null = null\n const depth = ref(0)\n\n function register(): void {\n if (layerId !== null)\n return\n\n // Стек — браузерное понятие: Esc, `inert` и возврат фокуса на сервере\n // бессмысленны. Регистрируйся слой и там — он остался бы в модульном массиве\n // навсегда, потому что `onUnmounted` при `renderToString` не вызывается, и\n // каждый серверный рендер открытого оверлея копил бы замыкание со ссылками\n // на свои компоненты.\n if (typeof window === 'undefined')\n return\n\n if (options.restoreFocus !== false && typeof document !== 'undefined')\n previouslyFocused = (document.activeElement as HTMLElement) ?? null\n\n layerId = pushOverlayLayer({\n modal: typeof options.modal === 'function' ? options.modal() : options.modal ?? false,\n shouldClose: () => options.closeOnEscape?.() ?? true,\n close: onDismiss,\n setTopmost: options.onTopmostChange,\n setDepth: (value) => { depth.value = value },\n root: () => options.root?.value ?? null,\n })\n }\n\n function unregister(): void {\n if (layerId === null)\n return\n removeOverlayLayer(layerId)\n layerId = null\n\n restoreFocus()\n }\n\n function restoreFocus(): void {\n const target = previouslyFocused\n previouslyFocused = null\n\n if (options.restoreFocus === false || !target)\n return\n if (typeof document === 'undefined')\n return\n\n // Фокус уже вне слоя — пользователь ушёл сам, отбирать нельзя. Решение\n // принимаем сейчас, пока слой ещё в DOM, а применяем следующим тиком.\n const active = document.activeElement\n const root = options.root?.value\n if (root && active && active !== document.body && !root.contains(active))\n return\n\n // Возврат откладывается намеренно: ловушка фокуса того же слоя снимает свои\n // слушатели в этом же флаше, и сфокусируй мы триггер сразу — она затащила бы\n // фокус обратно в закрывающийся слой.\n void nextTick(() => {\n if (target.isConnected)\n target.focus?.()\n })\n }\n\n watch(\n open,\n (isOpen) => {\n if (isOpen)\n register()\n else unregister()\n },\n { immediate: true },\n )\n\n onUnmounted(unregister)\n\n return {\n rootsAbove: () => (layerId === null ? [] : layerRootsAbove(layerId)),\n depth,\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAgFA,SAAgB,gBACd,MACA,WACA,UAAkC,CAAC,GACf;CACpB,IAAI,UAAyB;CAC7B,IAAI,oBAAwC;CAC5C,MAAM,QAAQ,IAAI,CAAC;CAEnB,SAAS,WAAiB;EACxB,IAAI,YAAY,MACd;EAOF,IAAI,OAAO,WAAW,aACpB;EAEF,IAAI,QAAQ,iBAAiB,SAAS,OAAO,aAAa,aACxD,oBAAqB,SAAS,iBAAiC;EAEjE,UAAU,iBAAiB;GACzB,OAAO,OAAO,QAAQ,UAAU,aAAa,QAAQ,MAAM,IAAI,QAAQ,SAAS;GAChF,mBAAmB,QAAQ,gBAAgB,KAAK;GAChD,OAAO;GACP,YAAY,QAAQ;GACpB,WAAW,UAAU;IAAE,MAAM,QAAQ;GAAM;GAC3C,YAAY,QAAQ,MAAM,SAAS;EACrC,CAAC;CACH;CAEA,SAAS,aAAmB;EAC1B,IAAI,YAAY,MACd;EACF,mBAAmB,OAAO;EAC1B,UAAU;EAEV,aAAa;CACf;CAEA,SAAS,eAAqB;EAC5B,MAAM,SAAS;EACf,oBAAoB;EAEpB,IAAI,QAAQ,iBAAiB,SAAS,CAAC,QACrC;EACF,IAAI,OAAO,aAAa,aACtB;EAIF,MAAM,SAAS,SAAS;EACxB,MAAM,OAAO,QAAQ,MAAM;EAC3B,IAAI,QAAQ,UAAU,WAAW,SAAS,QAAQ,CAAC,KAAK,SAAS,MAAM,GACrE;EAKF,eAAoB;GAClB,IAAI,OAAO,aACT,OAAO,QAAQ;EACnB,CAAC;CACH;CAEA,MACE,OACC,WAAW;EACV,IAAI,QACF,SAAS;OACN,WAAW;CAClB,GACA,EAAE,WAAW,KAAK,CACpB;CAEA,YAAY,UAAU;CAEtB,OAAO;EACL,kBAAmB,YAAY,OAAO,CAAC,IAAI,gBAAgB,OAAO;EAClE;CACF;AACF"}
package/dist/index.js CHANGED
@@ -63,24 +63,24 @@ import { n as GrCollapse_default, t as GrCollapseItem_default } from "./componen
63
63
  import { n as grIconSafelist, t as grIconConfig } from "./chunks/config-3bQeQvqd.js";
64
64
  import { t as GrInput_default } from "./components/GrInput/chunks/GrInput-BLywRZUN.js";
65
65
  import { useFocusTrap } from "./composables/useFocusTrap.js";
66
- import { t as GrPopover_default } from "./components/GrPopover/chunks/GrPopover-CofG-Tfe.js";
66
+ import { t as GrPopover_default } from "./components/GrPopover/chunks/GrPopover-faJanmGs.js";
67
67
  import { useDragGesture } from "./composables/useDragGesture.js";
68
68
  import { t as GrSlider_default } from "./components/GrSlider/chunks/GrSlider-DTTrZTHK.js";
69
- import { t as GrColorPicker_default } from "./components/GrColorPicker/chunks/GrColorPicker-B20gQ6AR.js";
69
+ import { t as GrColorPicker_default } from "./components/GrColorPicker/chunks/GrColorPicker-p2XH_8cE.js";
70
70
  import { n as grColorPickerSafelist, t as grColorPickerConfig } from "./chunks/config-DIDz_E5m.js";
71
71
  import { a as GR_KBD_TOKENS, o as findKbdToken, t as GrKbd_default } from "./components/GrKbd/chunks/GrKbd-D2ga1H9i.js";
72
- import { t as GrModal_default } from "./components/GrModal/chunks/GrModal-CCoWZuIl.js";
73
- import { t as GrCommandPalette_default } from "./components/GrCommandPalette/chunks/GrCommandPalette-Cd0Yo3Dw.js";
72
+ import { t as GrModal_default } from "./components/GrModal/chunks/GrModal-D0mf1SAx.js";
73
+ import { t as GrCommandPalette_default } from "./components/GrCommandPalette/chunks/GrCommandPalette-BXbh3q7O.js";
74
74
  import { n as grCommandPaletteSafelist, t as grCommandPaletteConfig } from "./chunks/config-D0NzGYaE.js";
75
75
  import { t as GrConfigProvider_default } from "./components/GrConfigProvider/chunks/GrConfigProvider-BF7R3hgg.js";
76
76
  import { t as grConfigProviderConfig } from "./chunks/config-B7W9VlZ7.js";
77
- import { i as GrDialogFooter_default, n as GrDialogHeader_default, r as GrDialogCloseButton_default, t as GrDialog_default } from "./components/GrDialog/chunks/GrDialog-S1Vl-OhX.js";
77
+ import { i as GrDialogFooter_default, n as GrDialogHeader_default, r as GrDialogCloseButton_default, t as GrDialog_default } from "./components/GrDialog/chunks/GrDialog-BiMJ9_T4.js";
78
78
  import { a as extendDefaultParsers, n as coreResponseErrorParsers, r as createResponseErrorClassifier, s as normalizeError, t as GrResponseErrorBanner_default } from "./components/GrResponseErrorBanner/chunks/GrResponseErrorBanner-DMeOWlzj.js";
79
- import { t as GrConfirmDialog_default } from "./components/GrConfirmDialog/chunks/GrConfirmDialog-DOsbRhoZ.js";
79
+ import { t as GrConfirmDialog_default } from "./components/GrConfirmDialog/chunks/GrConfirmDialog-CUP43q__.js";
80
80
  import { t as grConfirmDialogConfig } from "./chunks/config-A-bBiotF.js";
81
81
  import "./components/GrConfirmDialog/index.js";
82
82
  import { a as GrDropdownMenuItem_default, c as GrDropdownMenuDivider_default, o as GrDropdownMenuGroup_default, s as GrDropdownMenuHeader_default, t as GrDropdownMenuList_default } from "./components/GrDropdownMenu/chunks/GrDropdownMenuList-B33ZzwfB.js";
83
- import { t as GrContextMenu_default } from "./components/GrContextMenu/chunks/GrContextMenu-_FKwQvuQ.js";
83
+ import { t as GrContextMenu_default } from "./components/GrContextMenu/chunks/GrContextMenu-ClkVjdcP.js";
84
84
  import { t as grContextMenuConfig } from "./chunks/config-CPSxM7XE.js";
85
85
  import { t as GrTable_default } from "./components/GrTable/chunks/GrTable-Zcdz2St_.js";
86
86
  import { t as useDragSort } from "./chunks/useDragSort-0l47h4yb.js";
@@ -99,13 +99,13 @@ import { n as useGrFormContext } from "./chunks/context-Bfrgsluh.js";
99
99
  import { t as GrFormField_default } from "./components/GrFormField/chunks/GrFormField-DSylU7IU.js";
100
100
  import { t as GrTextarea_default } from "./components/GrTextarea/chunks/GrTextarea-DinXG12-.js";
101
101
  import { n as getByPath, o as runFieldRules, r as isEmpty, s as setByPath, t as createGrFormMessageResolver } from "./chunks/validation-5HhpanPP.js";
102
- import { t as GrPromptDialog_default } from "./components/GrPromptDialog/chunks/GrPromptDialog-BuJQMZLM.js";
103
- import { a as useDialogService, i as teardownDialogService, n as dialogService, r as granularityDialogServicePlugin, t as GrDialogServiceHost_default } from "./components/GrDialogService/chunks/GrDialogService-Cuhc4YLp.js";
102
+ import { t as GrPromptDialog_default } from "./components/GrPromptDialog/chunks/GrPromptDialog-C6QGC7QJ.js";
103
+ import { a as useDialogService, i as teardownDialogService, n as dialogService, r as granularityDialogServicePlugin, t as GrDialogServiceHost_default } from "./components/GrDialogService/chunks/GrDialogService-BxNK6crD.js";
104
104
  import { t as grDialogServiceConfig } from "./chunks/config-CjXjhphs.js";
105
105
  import { n as grDividerSafelist, t as grDividerConfig } from "./chunks/config-D82r782v.js";
106
106
  import { t as GrDivider_default } from "./components/GrDivider/chunks/GrDivider-BXavGcMX.js";
107
107
  import { n as grDrawerSafelist, t as grDrawerConfig } from "./chunks/config-ChPg-BQv.js";
108
- import { t as GrDrawer_default } from "./components/GrDrawer/chunks/GrDrawer-C0YqVE9e.js";
108
+ import { t as GrDrawer_default } from "./components/GrDrawer/chunks/GrDrawer-Ce2l3zaG.js";
109
109
  import { t as GrDropdown_default } from "./components/GrDropdown/chunks/GrDropdown-13Wt9eB_.js";
110
110
  import { n as grDropdownSafelist, t as grDropdownConfig } from "./chunks/config-uzUuosv7.js";
111
111
  import "./components/GrDropdown/index.js";
@@ -126,7 +126,7 @@ import { t as GrFormFile_default } from "./components/GrFormFile/chunks/GrFormFi
126
126
  import { n as grFormFileSafelist, t as grFormFileConfig } from "./chunks/config-BcXX7S7B.js";
127
127
  import { n as grFormSectionSafelist, t as grFormSectionConfig } from "./chunks/config-BEO7brpO.js";
128
128
  import { t as GrFormSection_default } from "./components/GrFormSection/chunks/GrFormSection-ejieOg9H.js";
129
- import { t as GrImageViewer_default } from "./components/GrImageViewer/chunks/GrImageViewer-CyVN6Zpc.js";
129
+ import { t as GrImageViewer_default } from "./components/GrImageViewer/chunks/GrImageViewer-CG4vUBg_.js";
130
130
  import { n as grImageViewerSafelist, t as grImageViewerConfig } from "./chunks/config-BDSaD-5p.js";
131
131
  import { n as grInputSafelist, t as grInputConfig } from "./chunks/config-CIkdsWUk.js";
132
132
  import "./components/GrInput/index.js";
@@ -36,6 +36,16 @@ export interface OverlayLayer {
36
36
  close: () => void;
37
37
  /** Уведомление «этот модальный слой сейчас верхний». Только для `modal`. */
38
38
  setTopmost?: (isTopmost: boolean) => void;
39
+ /**
40
+ * Позиция слоя среди модальных, от нуля. Только для `modal`.
41
+ *
42
+ * От неё считается высота: у всех модальных слоёв один токен
43
+ * (`--gr-z-modal`), и без этой прибавки порядок отрисовки решал бы порядок
44
+ * узлов в портале, а он задаётся **созданием** телепорта, а не открытием
45
+ * слоя. Диалог, объявленный в шаблоне статически, вставал бы под окном,
46
+ * открытым позже, — и получал бы `inert` от стека, оставаясь видимым.
47
+ */
48
+ setDepth?: (depth: number) => void;
39
49
  /**
40
50
  * Корень слоя. Ловушка фокуса нижнего слоя обязана считать его своим:
41
51
  * панель селекта, открытого внутри модалки, телепортирована в `body` и лежит
@@ -67,5 +77,25 @@ export declare function overlayStackSize(): number;
67
77
  * стек-контекст её не спасает — высоту приходится брать из порядка слоёв.
68
78
  */
69
79
  export declare function openModalCount(): number;
80
+ /**
81
+ * Высота слоя, лежащего внутри открытого окна.
82
+ *
83
+ * Панель телепортирована в общий портал и становится **соседом** корня окна, а
84
+ * не его потомком: собственный stacking-контекст окна её не накрывает, и
85
+ * статический `--gr-z-dropdown` (1000) оставлял её под окном (1100). Слагаемое —
86
+ * число модальных слоёв, поэтому панель встаёт над самым верхним из них, а
87
+ * полноэкранная загрузка (1150) и тосты (1200) остаются сверху.
88
+ *
89
+ * Обратный случай — «поповер снаружи не должен перекрывать окно» — не страдает:
90
+ * пока окно открыто, страница под ним в `inert`, и открыть там поповер нечем.
91
+ */
92
+ export declare function floatingLayerZIndex(zIndexVar: string): string;
93
+ /**
94
+ * Высота самого модального слоя: базовый токен плюс его позиция в стеке.
95
+ *
96
+ * Нулевая глубина оставляет чистый `var(...)` — у единственного окна на экране
97
+ * в стилях не появляется лишней арифметики.
98
+ */
99
+ export declare function modalLayerZIndex(depth: number, zIndexVar?: string): string;
70
100
  /** Тестовая/служебная очистка стека. */
71
101
  export declare function resetOverlayStack(): void;
@@ -47,6 +47,11 @@ export interface UseModalOverlayOptions {
47
47
  * композабл заводит корень сам, как у центрированных окон.
48
48
  */
49
49
  root?: Ref<HTMLElement | null>;
50
+ /**
51
+ * Имя CSS-переменной слоя. Escape-hatch мимо `--gr-z-modal` — им пользуется
52
+ * просмотрщик. Глубина слоя прибавляется и к подменённой переменной.
53
+ */
54
+ zIndexVar?: () => string | undefined;
50
55
  /**
51
56
  * Точечное переопределение точки монтирования (проп `teleportTo`). По
52
57
  * умолчанию — общий портал оверлеев.
@@ -75,6 +80,15 @@ export interface ModalOverlay {
75
80
  isTopmost: Ref<boolean>;
76
81
  /** Значение атрибута `inert` для корня: слой перестал быть верхним. */
77
82
  inertAttr: ComputedRef<true | undefined>;
83
+ /**
84
+ * Высота слоя: базовый токен плюс позиция в стеке.
85
+ *
86
+ * Один токен на все модальные слои означал бы, что порядок отрисовки решает
87
+ * порядок узлов в портале, а он задан **созданием** телепорта, а не
88
+ * открытием: статически объявленный диалог вставал бы под окном, открытым
89
+ * позже, и оказывался невидимым — при том что стек считает верхним его.
90
+ */
91
+ layerZIndex: ComputedRef<string>;
78
92
  portalTarget: ComputedRef<string | HTMLElement>;
79
93
  teleportEnabled: ComputedRef<boolean>;
80
94
  /** `data-theme` на телепортированный корень: в DOM он вне обёртки провайдера. */
@@ -43,6 +43,14 @@ export interface OverlayLayerHandle {
43
43
  * законен.
44
44
  */
45
45
  rootsAbove: () => HTMLElement[];
46
+ /**
47
+ * Позиция среди модальных слоёв, от нуля. По ней компонент считает высоту.
48
+ *
49
+ * Значение **не сбрасывается** при закрытии: слой уходит из стека сразу, а
50
+ * его поддерево живёт до конца leave-анимации. Обнули глубину там — уходящее
51
+ * окно нырнуло бы под соседнее прямо во время ухода.
52
+ */
53
+ depth: Ref<number>;
46
54
  }
47
55
  /**
48
56
  * Регистрирует оверлей в общем стеке слоёв, пока `open` истинно.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://raw.githubusercontent.com/JetBrains/web-types/master/schema/web-types.json",
3
3
  "name": "@feugene/granularity",
4
- "version": "0.31.1",
4
+ "version": "0.32.0",
5
5
  "framework": "vue",
6
6
  "js-types-syntax": "typescript",
7
7
  "description-markup": "markdown",