@feugene/granularity 0.29.1 → 0.31.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 (89) hide show
  1. package/README.md +9 -6
  2. package/dist/chunks/{config-DO8mjeIo.js → config-CIkdsWUk.js} +6 -2
  3. package/dist/chunks/config-CIkdsWUk.js.map +1 -0
  4. package/dist/chunks/{config-Dpk-eVkS.js → config-DUbbGcfw.js} +12 -1
  5. package/dist/chunks/config-DUbbGcfw.js.map +1 -0
  6. package/dist/chunks/{grInputStyles-CAk3xKIb.js → grInputStyles-DgQYjJvD.js} +17 -1
  7. package/dist/chunks/{grInputStyles-CAk3xKIb.js.map → grInputStyles-DgQYjJvD.js.map} +1 -1
  8. package/dist/chunks/grSelectStyles-Cdtd10eY.js.map +1 -1
  9. package/dist/chunks/{granular-provider-NtErwzYB.js → granular-provider-yJNX1g1G.js} +2 -2
  10. package/dist/chunks/{granular-provider-NtErwzYB.js.map → granular-provider-yJNX1g1G.js.map} +1 -1
  11. package/dist/chunks/renamedProp-BwcN-SJg.js +33 -0
  12. package/dist/chunks/renamedProp-BwcN-SJg.js.map +1 -0
  13. package/dist/components/GrAutocomplete/chunks/{GrAutocomplete-CaHys_A_.js → GrAutocomplete-X6RlmiJQ.js} +1 -1
  14. package/dist/components/GrAutocomplete/chunks/{GrAutocomplete-CaHys_A_.js.map → GrAutocomplete-X6RlmiJQ.js.map} +1 -1
  15. package/dist/components/GrAutocomplete/index.js +1 -1
  16. package/dist/components/GrBadge/chunks/{GrBadge-BM3f9PME.js → GrBadge-BsoocpF-.js} +4 -2
  17. package/dist/components/GrBadge/chunks/GrBadge-BsoocpF-.js.map +1 -0
  18. package/dist/components/GrBadge/index.js +1 -1
  19. package/dist/components/GrBadge/styles.css +1 -1
  20. package/dist/components/GrCard/chunks/{GrCard-DR3ehBt8.js → GrCard-BttXz1pQ.js} +19 -6
  21. package/dist/components/GrCard/chunks/GrCard-BttXz1pQ.js.map +1 -0
  22. package/dist/components/GrCard/index.js +2 -2
  23. package/dist/components/GrChip/chunks/{GrChip-Df4orxRH.js → GrChip-C0vU69tf.js} +3 -1
  24. package/dist/components/GrChip/chunks/GrChip-C0vU69tf.js.map +1 -0
  25. package/dist/components/GrChip/index.js +1 -1
  26. package/dist/components/GrCollapse/chunks/{GrCollapse-Bw9_JatF.js → GrCollapse-BlW6ybdL.js} +1 -1
  27. package/dist/components/GrCollapse/chunks/{GrCollapse-Bw9_JatF.js.map → GrCollapse-BlW6ybdL.js.map} +1 -1
  28. package/dist/components/GrCollapse/index.js +1 -1
  29. package/dist/components/GrColorPicker/chunks/{GrColorPicker-CE-xtNP-.js → GrColorPicker-C7ifyQ0R.js} +1 -1
  30. package/dist/components/GrColorPicker/chunks/{GrColorPicker-CE-xtNP-.js.map → GrColorPicker-C7ifyQ0R.js.map} +1 -1
  31. package/dist/components/GrColorPicker/index.js +1 -1
  32. package/dist/components/GrContextMenu/chunks/{GrContextMenu-BNQDbAsY.js → GrContextMenu-_FKwQvuQ.js} +1 -1
  33. package/dist/components/GrContextMenu/chunks/{GrContextMenu-BNQDbAsY.js.map → GrContextMenu-_FKwQvuQ.js.map} +1 -1
  34. package/dist/components/GrContextMenu/index.js +1 -1
  35. package/dist/components/GrDialogService/chunks/{GrDialogService-k4SYIv64.js → GrDialogService-6s2cQ-_6.js} +1 -1
  36. package/dist/components/GrDialogService/chunks/{GrDialogService-k4SYIv64.js.map → GrDialogService-6s2cQ-_6.js.map} +1 -1
  37. package/dist/components/GrDialogService/index.js +1 -1
  38. package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-Cg7qHV9J.js → GrDropdownMenu-GOzuAXsi.js} +1 -1
  39. package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-Cg7qHV9J.js.map → GrDropdownMenu-GOzuAXsi.js.map} +1 -1
  40. package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenuList-RW5IBvmu.js → GrDropdownMenuList-B33ZzwfB.js} +13 -3
  41. package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenuList-RW5IBvmu.js.map → GrDropdownMenuList-B33ZzwfB.js.map} +1 -1
  42. package/dist/components/GrDropdownMenu/index.js +2 -2
  43. package/dist/components/GrInput/chunks/{GrInput-BqqoEl1b.js → GrInput-BLywRZUN.js} +10 -6
  44. package/dist/components/GrInput/chunks/GrInput-BLywRZUN.js.map +1 -0
  45. package/dist/components/GrInput/index.js +2 -2
  46. package/dist/components/GrInputTag/chunks/{GrInputTag-5wVcB0zG.js → GrInputTag-Cj4A9Xgs.js} +1 -1
  47. package/dist/components/GrInputTag/chunks/{GrInputTag-5wVcB0zG.js.map → GrInputTag-Cj4A9Xgs.js.map} +1 -1
  48. package/dist/components/GrInputTag/index.js +1 -1
  49. package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-CgwjoUJt.js → GrJsonViewer-Cy2bknHO.js} +1 -1
  50. package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-CgwjoUJt.js.map → GrJsonViewer-Cy2bknHO.js.map} +1 -1
  51. package/dist/components/GrJsonViewer/index.js +1 -1
  52. package/dist/components/GrList/chunks/{GrList-DrXrKU37.js → GrList-DmT4qzLO.js} +1 -1
  53. package/dist/components/GrList/chunks/{GrList-DrXrKU37.js.map → GrList-DmT4qzLO.js.map} +1 -1
  54. package/dist/components/GrList/index.js +1 -1
  55. package/dist/components/GrPagination/chunks/{GrPagination-CX9ZV8mR.js → GrPagination-Cw3R21dp.js} +1 -1
  56. package/dist/components/GrPagination/chunks/{GrPagination-CX9ZV8mR.js.map → GrPagination-Cw3R21dp.js.map} +1 -1
  57. package/dist/components/GrPagination/index.js +1 -1
  58. package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-D1ADRFxx.js → GrPromptDialog-DLcl1xiq.js} +1 -1
  59. package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-D1ADRFxx.js.map → GrPromptDialog-DLcl1xiq.js.map} +1 -1
  60. package/dist/components/GrPromptDialog/index.js +1 -1
  61. package/dist/components/GrSelect/chunks/{GrSelect-DKoQu3w9.js → GrSelect-BiUrSSxc.js} +5 -5
  62. package/dist/components/GrSelect/chunks/GrSelect-BiUrSSxc.js.map +1 -0
  63. package/dist/components/GrSelect/index.js +1 -1
  64. package/dist/components/GrSelect/styles.css +1 -1
  65. package/dist/components/GrSortableList/chunks/{GrSortableList-DGs3Pi2J.js → GrSortableList-HyKX--rO.js} +1 -1
  66. package/dist/components/GrSortableList/chunks/{GrSortableList-DGs3Pi2J.js.map → GrSortableList-HyKX--rO.js.map} +1 -1
  67. package/dist/components/GrSortableList/index.js +1 -1
  68. package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-CymO68Cv.js → GrTreeSelect-iJnMl2t9.js} +1 -1
  69. package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-CymO68Cv.js.map → GrTreeSelect-iJnMl2t9.js.map} +1 -1
  70. package/dist/components/GrTreeSelect/index.js +1 -1
  71. package/dist/granular-provider-node.js +1 -1
  72. package/dist/granular-provider.js +1 -1
  73. package/dist/index.js +21 -21
  74. package/dist/types/components/GrCard/GrCard.vue.d.ts +8 -0
  75. package/dist/types/components/GrCard/grCardStyles.d.ts +9 -0
  76. package/dist/types/components/GrDropdownMenu/menuModel.d.ts +20 -2
  77. package/dist/types/components/GrInput/GrInput.vue.d.ts +16 -0
  78. package/dist/types/components/GrInput/grInputStyles.d.ts +16 -0
  79. package/dist/types/components/GrSelect/grSelectStyles.d.ts +12 -0
  80. package/dist/types/components/shared/renamedProp.d.ts +19 -0
  81. package/dist/web-types.json +14 -1
  82. package/package.json +2 -2
  83. package/dist/chunks/config-DO8mjeIo.js.map +0 -1
  84. package/dist/chunks/config-Dpk-eVkS.js.map +0 -1
  85. package/dist/components/GrBadge/chunks/GrBadge-BM3f9PME.js.map +0 -1
  86. package/dist/components/GrCard/chunks/GrCard-DR3ehBt8.js.map +0 -1
  87. package/dist/components/GrChip/chunks/GrChip-Df4orxRH.js.map +0 -1
  88. package/dist/components/GrInput/chunks/GrInput-BqqoEl1b.js.map +0 -1
  89. package/dist/components/GrSelect/chunks/GrSelect-DKoQu3w9.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"GrColorPicker-CE-xtNP-.js","names":[],"sources":["../../../../src/components/GrColorPicker/color.ts","../../../../src/components/GrColorPicker/GrColorPicker.vue","../../../../src/components/GrColorPicker/GrColorPicker.vue"],"sourcesContent":["/**\n * Арифметика цвета для `GrColorPicker` — чистый модуль без Vue.\n *\n * Модель компонента — hex-строка (в такой форме цвет лежит в токенах темы),\n * а панель работает в HSL: у оттенка, насыщенности и светлоты есть шкала, по\n * которой можно вести слайдер, а у каналов RGB её нет.\n *\n * Свой парсер, а не `__tests__/cssContrast.ts`: тот — инфраструктура гейтов, и\n * тянуть тестовый модуль в поставляемый код нельзя.\n */\n\nexport type GrHsla = {\n /** Оттенок, 0…359 (круговой: 360 — это 0). */\n h: number\n /** Насыщенность, 0…100. */\n s: number\n /** Светлота, 0…100. */\n l: number\n /** Непрозрачность, 0…1. */\n a: number\n}\n\ntype Rgb = { r: number, g: number, b: number }\n\nconst HEX_PATTERN = /^#?([0-9a-f]{3,8})$/i\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max)\n}\n\n/**\n * Оттенок цикличен: 360° — тот же цвет, что 0°.\n *\n * Округления здесь нет намеренно: `#3b82f6` даёт 217.22°, и округлив его до\n * целого, обратное преобразование вернуло бы `#3b83f6` — компонент молча\n * переписывал бы чужой цвет при первом же открытии панели. Целые градусы\n * появляются там, где они и нужны, — на шкале слайдера (`normalizeHsla`).\n */\nfunction normalizeHue(hue: number): number {\n if (!Number.isFinite(hue)) return 0\n return ((hue % 360) + 360) % 360\n}\n\n/** Короткая форма `#abc` разворачивается в `#aabbcc` — так её читает и CSS. */\nfunction expandShorthand(hex: string): string | null {\n if (hex.length === 3 || hex.length === 4)\n return [...hex].map(char => char + char).join('')\n\n if (hex.length === 6 || hex.length === 8) return hex\n\n return null\n}\n\nfunction rgbToHsl({ r, g, b }: Rgb): { h: number, s: number, l: number } {\n const red = r / 255\n const green = g / 255\n const blue = b / 255\n\n const max = Math.max(red, green, blue)\n const min = Math.min(red, green, blue)\n const delta = max - min\n const lightness = (max + min) / 2\n\n if (delta === 0) return { h: 0, s: 0, l: lightness * 100 }\n\n const saturation = delta / (1 - Math.abs(2 * lightness - 1))\n\n let hue: number\n if (max === red) hue = ((green - blue) / delta) % 6\n else if (max === green) hue = (blue - red) / delta + 2\n else hue = (red - green) / delta + 4\n\n return { h: normalizeHue(hue * 60), s: saturation * 100, l: lightness * 100 }\n}\n\nfunction hslToRgb({ h, s, l }: Omit<GrHsla, 'a'>): Rgb {\n const saturation = clamp(s, 0, 100) / 100\n const lightness = clamp(l, 0, 100) / 100\n const hue = normalizeHue(h)\n\n const chroma = (1 - Math.abs(2 * lightness - 1)) * saturation\n const second = chroma * (1 - Math.abs(((hue / 60) % 2) - 1))\n const match = lightness - chroma / 2\n\n const [red, green, blue] = ((): [number, number, number] => {\n if (hue < 60) return [chroma, second, 0]\n if (hue < 120) return [second, chroma, 0]\n if (hue < 180) return [0, chroma, second]\n if (hue < 240) return [0, second, chroma]\n if (hue < 300) return [second, 0, chroma]\n return [chroma, 0, second]\n })()\n\n return {\n r: Math.round((red + match) * 255),\n g: Math.round((green + match) * 255),\n b: Math.round((blue + match) * 255),\n }\n}\n\nfunction toHexPair(value: number): string {\n return clamp(Math.round(value), 0, 255).toString(16).padStart(2, '0')\n}\n\n/**\n * Разбирает hex любой из четырёх форм (`#abc`, `#abcd`, `#aabbcc`, `#aabbccdd`),\n * регистр и решётка необязательны.\n *\n * Мусор возвращает `null`, а не бросает: значение приходит из модели снаружи и\n * из поля ввода, и упавший компонент — худший ответ на опечатку.\n */\nexport function parseHexColor(value: unknown): GrHsla | null {\n if (typeof value !== 'string') return null\n\n const match = HEX_PATTERN.exec(value.trim())\n if (!match) return null\n\n const hex = expandShorthand(match[1])\n if (!hex) return null\n\n const rgb: Rgb = {\n r: Number.parseInt(hex.slice(0, 2), 16),\n g: Number.parseInt(hex.slice(2, 4), 16),\n b: Number.parseInt(hex.slice(4, 6), 16),\n }\n\n const alpha = hex.length === 8 ? Number.parseInt(hex.slice(6, 8), 16) / 255 : 1\n\n return { ...rgbToHsl(rgb), a: alpha }\n}\n\n/**\n * Собирает hex обратно. `withAlpha: false` альфу отбрасывает, а не округляет до\n * непрозрачного: компонент без пропа `alpha` не вправе отдавать восьмизначную\n * форму, которую потребитель не ждёт.\n */\nexport function formatHexColor(color: GrHsla, withAlpha = false): string {\n const { r, g, b } = hslToRgb(color)\n const base = `#${toHexPair(r)}${toHexPair(g)}${toHexPair(b)}`\n\n if (!withAlpha) return base\n\n return `${base}${toHexPair(clamp(color.a, 0, 1) * 255)}`\n}\n\n/** Короткая запись числа: целое остаётся целым, дробь обрезается до сотых. */\nfunction trim(value: number): number {\n return Math.round(value * 100) / 100\n}\n\n/** CSS-запись цвета для превью и градиентов. */\nexport function hslaToCss(color: GrHsla): string {\n const h = trim(normalizeHue(color.h))\n const s = trim(clamp(color.s, 0, 100))\n const l = trim(clamp(color.l, 0, 100))\n\n return `hsl(${h} ${s}% ${l}% / ${trim(clamp(color.a, 0, 1))})`\n}\n\n/** Приводит произвольный объект к валидному диапазону каналов. */\nexport function normalizeHsla(color: GrHsla): GrHsla {\n return {\n h: Math.round(normalizeHue(color.h)) % 360,\n s: clamp(Math.round(color.s), 0, 100),\n l: clamp(Math.round(color.l), 0, 100),\n a: clamp(color.a, 0, 1),\n }\n}\n","<script setup lang=\"ts\">\n/**\n * GrColorPicker — поле выбора цвета: образец с текущим значением, панель с\n * оттенком, насыщенностью, светлотой и прозрачностью, поле hex и палитра.\n *\n * Прямой сценарий — настройки темы и брендирования, поэтому модель хранит hex:\n * в такой форме цвет лежит в токенах и его понимает CSS.\n *\n * A11y: каждый канал — отдельный `GrSlider`, то есть настоящий `role=\"slider\"`\n * с полной клавиатурой и `aria-valuetext`. Двумерная область saturation/value\n * не берётся намеренно: это свой виджет со своей клавиатурой по двум осям, и\n * доступность там пришлось бы собирать с нуля.\n */\nimport { computed, nextTick, ref, useId, watch } from 'vue'\n\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport GrInput from '../GrInput/GrInput.vue'\nimport GrPopover from '../GrPopover/GrPopover.vue'\nimport GrSlider from '../GrSlider/GrSlider.vue'\n\nimport { formatHexColor, hslaToCss, normalizeHsla, parseHexColor, type GrHsla } from './color'\nimport {\n checkerClass,\n grColorPickerPanelClass,\n grColorPickerPresetClass,\n grColorPickerTriggerClass,\n presetsGridClass,\n previewClass,\n rowClass,\n rowLabelClass,\n rowValueClass,\n swatchBaseClass,\n swatchFillClass,\n triggerSwatchSizeBySize,\n triggerValueClass,\n type GrColorPickerSize,\n} from './grColorPickerStyles'\n\nexport type { GrColorPickerSize } from './grColorPickerStyles'\n\nexport interface GrColorPickerProps {\n /** Цвет в hex: `#RRGGBB`, а при `alpha` — `#RRGGBBAA`. Мусор не роняет компонент. */\n modelValue: string\n /** Четвёртый слайдер и восьмизначная форма hex. */\n alpha?: boolean\n /** Палитра быстрого выбора. Пусто — блок не рендерится. */\n presets?: string[]\n size?: GrColorPickerSize\n /** Контролируемое состояние панели (`v-model:open`). */\n open?: boolean\n /** Сторона, с которой раскрывается панель. */\n placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end'\n /** Имя для нативной формы: значение уходит скрытым полем. */\n name?: string\n disabled?: boolean\n /** Только для чтения: цвет видно, панель открывается, но значение не меняется. */\n readonly?: boolean\n invalid?: boolean\n required?: boolean\n ariaLabel?: string\n}\n\nexport interface GrColorPickerEmits {\n (e: 'update:modelValue', value: string): void\n (e: 'change', value: string): void\n (e: 'update:open', value: boolean): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(defineProps<GrColorPickerProps>(), {\n alpha: false,\n presets: () => [],\n // Дефолт живёт в резолвере: Vue подставил бы свой раньше, чем компонент\n // заглянет в `GrConfigProvider`.\n size: undefined,\n open: undefined,\n placement: 'bottom-start',\n name: undefined,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n ariaLabel: undefined,\n})\n\nconst emit = defineEmits<GrColorPickerEmits>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrColorPicker' })\n\nconst field = useGrFormFieldContext()\nconst fieldId = computed(() => field?.id.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n // «Ввод не принимается» — `disabled` или `readonly`: значение видно, но не меняется.\n locked: isLocked,\n} = useGrFormControl(() => props)\n\n/**\n * `aria-required` и `aria-readonly` роль `button` не поддерживает — axe роняет\n * это как critical `aria-allowed-attr`. Состояния уходят в описание триггера:\n * иначе они пропали бы для диктора совсем (маркер `*` у `GrFormField`\n * декоративен и скрыт).\n */\nconst stateHintId = useId()\nconst stateHint = computed(() => [\n isRequired.value ? t('gr.form.required', 'This field is required') : undefined,\n isReadonly.value ? t('gr.form.readonly', 'Read only') : undefined,\n].filter(Boolean).join('. ') || undefined)\n\nconst describedBy = computed(() => [\n field?.describedById.value,\n stateHint.value ? stateHintId : undefined,\n].filter(Boolean).join(' ') || undefined)\n\nconst triggerEl = ref<HTMLElement | null>(null)\n\nfunction focus(): void {\n triggerEl.value?.focus()\n}\n\nfunction blur(): void {\n triggerEl.value?.blur()\n}\n\ndefineExpose({ focus, blur })\n\nconst FALLBACK: GrHsla = { h: 0, s: 0, l: 0, a: 1 }\n\n/**\n * Рабочее состояние панели.\n *\n * Держать его отдельно от модели обязательно: hex — проекция с потерями. У\n * серого нет оттенка, и без собственного состояния бегунок оттенка прыгал бы\n * обратно на 0° при каждом движении.\n */\nconst state = ref<GrHsla>(parseHexColor(props.modelValue) ?? FALLBACK)\n\n/** Последнее отданное значение: вернувшись в проп, оно не должно сбрасывать состояние. */\nlet lastEmitted = ''\n\nwatch(() => props.modelValue, (next) => {\n if (next === lastEmitted) return\n\n const parsed = parseHexColor(next)\n if (parsed) state.value = parsed\n})\n\nconst cssColor = computed(() => hslaToCss(state.value))\nconst hexValue = computed(() => formatHexColor(state.value, props.alpha))\n\nfunction commit(next: GrHsla): void {\n if (isLocked.value) return\n\n state.value = normalizeHsla(next)\n\n const hex = formatHexColor(state.value, props.alpha)\n lastEmitted = hex\n emit('update:modelValue', hex)\n emit('change', hex)\n}\n\n/** Канал панели: слайдер читает состояние и коммитит новое значение целиком. */\nfunction channel(key: 'h' | 's' | 'l' | 'a', scale = 1) {\n return computed<number>({\n get: () => Math.round(state.value[key] * scale),\n set: value => commit({ ...state.value, [key]: value / scale }),\n })\n}\n\nconst hue = channel('h')\nconst saturation = channel('s')\nconst lightness = channel('l')\n// Альфа хранится долей, а слайдер ходит по процентам — иначе шаг был бы 0.01.\nconst opacity = channel('a', 100)\n\nconst degrees = (value: number): string => `${value}°`\nconst percent = (value: number): string => `${value}%`\n\n/** Текст поля hex редактируется свободно и коммитится только валидным. */\nconst hexDraft = ref(hexValue.value)\n\nwatch(hexValue, (next) => {\n hexDraft.value = next\n})\n\nfunction commitHex(): void {\n const parsed = parseHexColor(hexDraft.value)\n\n // Мусор откатывается к текущему значению: держать в поле невалидный текст\n // после коммита — значит показывать цвет, которого нет.\n //\n // Откат идёт следующим тиком намеренно: вернув значение синхронно, мы отдали\n // бы Vue то же самое, что он уже отрисовал, — патча не случилось бы, и в поле\n // остался бы набранный мусор.\n if (!parsed) {\n void nextTick(() => { hexDraft.value = hexValue.value })\n return\n }\n\n commit(props.alpha ? parsed : { ...parsed, a: state.value.a })\n}\n\nconst isOpen = ref(false)\nconst panelOpen = computed({\n get: () => props.open ?? isOpen.value,\n set: (value) => {\n isOpen.value = value\n emit('update:open', value)\n },\n})\n\nconst presetList = computed(() => props.presets.filter(preset => parseHexColor(preset) !== null))\n\nfunction isPresetSelected(preset: string): boolean {\n const parsed = parseHexColor(preset)\n return parsed ? formatHexColor(parsed, props.alpha) === hexValue.value : false\n}\n\nfunction selectPreset(preset: string): void {\n const parsed = parseHexColor(preset)\n if (parsed) commit(parsed)\n}\n\nconst triggerClass = computed(() => grColorPickerTriggerClass({\n size: resolvedSize.value,\n disabled: isDisabled.value,\n invalid: isInvalid.value,\n}))\n\nconst panelClass = computed(() => grColorPickerPanelClass(resolvedSize.value))\nconst swatchSizeClass = computed(() => triggerSwatchSizeBySize[resolvedSize.value])\n\n/**\n * Градиенты дорожек. Через `--gr-slider-rail` их не подать: хук уходит в\n * `background-color`, а градиент там не работает — поэтому значения приезжают\n * переменными, а красит дорожки собственный `<style>` компонента.\n *\n * Переменные висят на самой панели, а не на корне: панель уезжает в портал, и\n * наследование от корня до неё не доходит.\n */\nconst trackVars = computed<Record<string, string>>(() => {\n const { h, s, l } = state.value\n const solid = hslaToCss({ h, s, l, a: 1 })\n\n return {\n '--gr-color-picker-track-hue': 'linear-gradient(to right, hsl(0 100% 50%), hsl(60 100% 50%), hsl(120 100% 50%), hsl(180 100% 50%), hsl(240 100% 50%), hsl(300 100% 50%), hsl(360 100% 50%))',\n '--gr-color-picker-track-saturation': `linear-gradient(to right, hsl(${Math.round(h)} 0% ${Math.round(l)}%), hsl(${Math.round(h)} 100% ${Math.round(l)}%))`,\n '--gr-color-picker-track-lightness': `linear-gradient(to right, hsl(${Math.round(h)} ${Math.round(s)}% 0%), hsl(${Math.round(h)} ${Math.round(s)}% 50%), hsl(${Math.round(h)} ${Math.round(s)}% 100%))`,\n '--gr-color-picker-track-alpha': `linear-gradient(to right, transparent, ${solid})`,\n }\n})\n</script>\n\n<template>\n <div data-gr-color-picker>\n <!-- Значение для нативной формы уходит скрытым полем: интерактивного\n контрола внутри виджета быть не должно. -->\n <input v-if=\"name\" type=\"hidden\" :name=\"name\" :value=\"hexValue\">\n\n <span v-if=\"stateHint\" :id=\"stateHintId\" class=\"sr-only\">{{ stateHint }}</span>\n\n <GrPopover\n v-model:open=\"panelOpen\"\n :size=\"resolvedSize\"\n :placement=\"placement\"\n :disabled=\"isDisabled\"\n :aria-label=\"t('gr.colorPicker.panelLabel', 'Color picker')\"\n :close-on-content-click=\"false\"\n >\n <template #trigger=\"{ triggerProps }\">\n <button\n :id=\"fieldId\"\n ref=\"triggerEl\"\n v-bind=\"triggerProps\"\n data-gr-color-picker-trigger\n type=\"button\"\n :class=\"triggerClass\"\n :aria-label=\"ariaLabel\"\n :aria-describedby=\"describedBy\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n @focus=\"emit('focus', $event)\"\n @blur=\"emit('blur', $event)\"\n >\n <span\n data-gr-color-picker-swatch\n aria-hidden=\"true\"\n :class=\"[swatchBaseClass, swatchSizeClass, checkerClass]\"\n >\n <span :class=\"swatchFillClass\" :style=\"{ background: cssColor }\" />\n </span>\n\n <span :class=\"triggerValueClass\">{{ hexValue }}</span>\n </button>\n </template>\n\n <template #content>\n <div data-gr-color-picker-panel :class=\"panelClass\" :style=\"trackVars\">\n <div\n data-gr-color-picker-preview\n aria-hidden=\"true\"\n :class=\"[previewClass, checkerClass]\"\n >\n <span :class=\"swatchFillClass\" :style=\"{ background: cssColor }\" />\n </div>\n\n <div data-gr-color-picker-channel=\"hue\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">H</span>\n <GrSlider\n v-model=\"hue\"\n :min=\"0\"\n :max=\"359\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"degrees\"\n :aria-label=\"t('gr.colorPicker.hue', 'Hue')\"\n />\n <span :class=\"rowValueClass\">{{ degrees(hue) }}</span>\n </div>\n\n <div data-gr-color-picker-channel=\"saturation\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">S</span>\n <GrSlider\n v-model=\"saturation\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"percent\"\n :aria-label=\"t('gr.colorPicker.saturation', 'Saturation')\"\n />\n <span :class=\"rowValueClass\">{{ percent(saturation) }}</span>\n </div>\n\n <div data-gr-color-picker-channel=\"lightness\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">L</span>\n <GrSlider\n v-model=\"lightness\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"percent\"\n :aria-label=\"t('gr.colorPicker.lightness', 'Lightness')\"\n />\n <span :class=\"rowValueClass\">{{ percent(lightness) }}</span>\n </div>\n\n <div v-if=\"alpha\" data-gr-color-picker-channel=\"alpha\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">A</span>\n <GrSlider\n v-model=\"opacity\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"percent\"\n :aria-label=\"t('gr.colorPicker.opacity', 'Opacity')\"\n />\n <span :class=\"rowValueClass\">{{ percent(opacity) }}</span>\n </div>\n\n <GrInput\n v-model=\"hexDraft\"\n data-gr-color-picker-hex\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :aria-label=\"t('gr.colorPicker.hexLabel', 'Hex value')\"\n @change=\"commitHex\"\n @keydown.enter=\"commitHex\"\n />\n\n <div\n v-if=\"presetList.length\"\n data-gr-color-picker-presets\n :class=\"presetsGridClass\"\n role=\"group\"\n :aria-label=\"t('gr.colorPicker.presetsLabel', 'Color presets')\"\n >\n <button\n v-for=\"preset in presetList\"\n :key=\"preset\"\n data-gr-color-picker-preset\n type=\"button\"\n :class=\"[grColorPickerPresetClass(isPresetSelected(preset)), checkerClass]\"\n :aria-label=\"t('gr.colorPicker.swatchLabel', 'Color {color}', { color: preset })\"\n :aria-pressed=\"isPresetSelected(preset) ? 'true' : 'false'\"\n :disabled=\"isLocked\"\n @click=\"selectPreset(preset)\"\n >\n <span :class=\"swatchFillClass\" :style=\"{ background: preset }\" />\n </button>\n </div>\n </div>\n </template>\n </GrPopover>\n </div>\n</template>\n\n<style>\n/*\n * Шахматка под прозрачным цветом. Двумя `conic-gradient` — в утилите такое не\n * выражается, а без подложки прозрачный образец сливается с панелью.\n */\n.gr-color-picker-checker {\n background-image:\n conic-gradient(\n from 90deg,\n var(--gr-color-picker-checker-light, var(--gr-bg)) 0 25%,\n var(--gr-color-picker-checker-dark, var(--gr-muted)) 0 50%,\n var(--gr-color-picker-checker-light, var(--gr-bg)) 0 75%,\n var(--gr-color-picker-checker-dark, var(--gr-muted)) 0\n );\n background-size:\n calc(var(--gr-color-picker-checker-size, 6px) * 2)\n calc(var(--gr-color-picker-checker-size, 6px) * 2);\n /* Рамка образца полупрозрачна, и без обрезки шахматка просвечивает сквозь неё. */\n background-clip: padding-box;\n}\n\n/*\n * Дорожки каналов. Красим `[data-gr-slider-track]` потому, что хук\n * `--gr-slider-rail` уходит в `background-color`, а градиент там не работает.\n * Сами градиенты компонент подаёт переменными — CSS остаётся статичной.\n */\n[data-gr-color-picker-channel] [data-gr-slider-track] {\n border-radius: var(--gr-radius-full);\n}\n\n[data-gr-color-picker-channel='hue'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-hue);\n}\n\n[data-gr-color-picker-channel='saturation'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-saturation);\n}\n\n[data-gr-color-picker-channel='lightness'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-lightness);\n}\n\n[data-gr-color-picker-channel='alpha'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-alpha);\n}\n\n/* Заливка канала прозрачна: шкалу показывает сама дорожка. */\n[data-gr-color-picker-channel] [data-gr-slider-fill] {\n background-color: transparent;\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * GrColorPicker — поле выбора цвета: образец с текущим значением, панель с\n * оттенком, насыщенностью, светлотой и прозрачностью, поле hex и палитра.\n *\n * Прямой сценарий — настройки темы и брендирования, поэтому модель хранит hex:\n * в такой форме цвет лежит в токенах и его понимает CSS.\n *\n * A11y: каждый канал — отдельный `GrSlider`, то есть настоящий `role=\"slider\"`\n * с полной клавиатурой и `aria-valuetext`. Двумерная область saturation/value\n * не берётся намеренно: это свой виджет со своей клавиатурой по двум осям, и\n * доступность там пришлось бы собирать с нуля.\n */\nimport { computed, nextTick, ref, useId, watch } from 'vue'\n\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport GrInput from '../GrInput/GrInput.vue'\nimport GrPopover from '../GrPopover/GrPopover.vue'\nimport GrSlider from '../GrSlider/GrSlider.vue'\n\nimport { formatHexColor, hslaToCss, normalizeHsla, parseHexColor, type GrHsla } from './color'\nimport {\n checkerClass,\n grColorPickerPanelClass,\n grColorPickerPresetClass,\n grColorPickerTriggerClass,\n presetsGridClass,\n previewClass,\n rowClass,\n rowLabelClass,\n rowValueClass,\n swatchBaseClass,\n swatchFillClass,\n triggerSwatchSizeBySize,\n triggerValueClass,\n type GrColorPickerSize,\n} from './grColorPickerStyles'\n\nexport type { GrColorPickerSize } from './grColorPickerStyles'\n\nexport interface GrColorPickerProps {\n /** Цвет в hex: `#RRGGBB`, а при `alpha` — `#RRGGBBAA`. Мусор не роняет компонент. */\n modelValue: string\n /** Четвёртый слайдер и восьмизначная форма hex. */\n alpha?: boolean\n /** Палитра быстрого выбора. Пусто — блок не рендерится. */\n presets?: string[]\n size?: GrColorPickerSize\n /** Контролируемое состояние панели (`v-model:open`). */\n open?: boolean\n /** Сторона, с которой раскрывается панель. */\n placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end'\n /** Имя для нативной формы: значение уходит скрытым полем. */\n name?: string\n disabled?: boolean\n /** Только для чтения: цвет видно, панель открывается, но значение не меняется. */\n readonly?: boolean\n invalid?: boolean\n required?: boolean\n ariaLabel?: string\n}\n\nexport interface GrColorPickerEmits {\n (e: 'update:modelValue', value: string): void\n (e: 'change', value: string): void\n (e: 'update:open', value: boolean): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(defineProps<GrColorPickerProps>(), {\n alpha: false,\n presets: () => [],\n // Дефолт живёт в резолвере: Vue подставил бы свой раньше, чем компонент\n // заглянет в `GrConfigProvider`.\n size: undefined,\n open: undefined,\n placement: 'bottom-start',\n name: undefined,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n ariaLabel: undefined,\n})\n\nconst emit = defineEmits<GrColorPickerEmits>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrColorPicker' })\n\nconst field = useGrFormFieldContext()\nconst fieldId = computed(() => field?.id.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n // «Ввод не принимается» — `disabled` или `readonly`: значение видно, но не меняется.\n locked: isLocked,\n} = useGrFormControl(() => props)\n\n/**\n * `aria-required` и `aria-readonly` роль `button` не поддерживает — axe роняет\n * это как critical `aria-allowed-attr`. Состояния уходят в описание триггера:\n * иначе они пропали бы для диктора совсем (маркер `*` у `GrFormField`\n * декоративен и скрыт).\n */\nconst stateHintId = useId()\nconst stateHint = computed(() => [\n isRequired.value ? t('gr.form.required', 'This field is required') : undefined,\n isReadonly.value ? t('gr.form.readonly', 'Read only') : undefined,\n].filter(Boolean).join('. ') || undefined)\n\nconst describedBy = computed(() => [\n field?.describedById.value,\n stateHint.value ? stateHintId : undefined,\n].filter(Boolean).join(' ') || undefined)\n\nconst triggerEl = ref<HTMLElement | null>(null)\n\nfunction focus(): void {\n triggerEl.value?.focus()\n}\n\nfunction blur(): void {\n triggerEl.value?.blur()\n}\n\ndefineExpose({ focus, blur })\n\nconst FALLBACK: GrHsla = { h: 0, s: 0, l: 0, a: 1 }\n\n/**\n * Рабочее состояние панели.\n *\n * Держать его отдельно от модели обязательно: hex — проекция с потерями. У\n * серого нет оттенка, и без собственного состояния бегунок оттенка прыгал бы\n * обратно на 0° при каждом движении.\n */\nconst state = ref<GrHsla>(parseHexColor(props.modelValue) ?? FALLBACK)\n\n/** Последнее отданное значение: вернувшись в проп, оно не должно сбрасывать состояние. */\nlet lastEmitted = ''\n\nwatch(() => props.modelValue, (next) => {\n if (next === lastEmitted) return\n\n const parsed = parseHexColor(next)\n if (parsed) state.value = parsed\n})\n\nconst cssColor = computed(() => hslaToCss(state.value))\nconst hexValue = computed(() => formatHexColor(state.value, props.alpha))\n\nfunction commit(next: GrHsla): void {\n if (isLocked.value) return\n\n state.value = normalizeHsla(next)\n\n const hex = formatHexColor(state.value, props.alpha)\n lastEmitted = hex\n emit('update:modelValue', hex)\n emit('change', hex)\n}\n\n/** Канал панели: слайдер читает состояние и коммитит новое значение целиком. */\nfunction channel(key: 'h' | 's' | 'l' | 'a', scale = 1) {\n return computed<number>({\n get: () => Math.round(state.value[key] * scale),\n set: value => commit({ ...state.value, [key]: value / scale }),\n })\n}\n\nconst hue = channel('h')\nconst saturation = channel('s')\nconst lightness = channel('l')\n// Альфа хранится долей, а слайдер ходит по процентам — иначе шаг был бы 0.01.\nconst opacity = channel('a', 100)\n\nconst degrees = (value: number): string => `${value}°`\nconst percent = (value: number): string => `${value}%`\n\n/** Текст поля hex редактируется свободно и коммитится только валидным. */\nconst hexDraft = ref(hexValue.value)\n\nwatch(hexValue, (next) => {\n hexDraft.value = next\n})\n\nfunction commitHex(): void {\n const parsed = parseHexColor(hexDraft.value)\n\n // Мусор откатывается к текущему значению: держать в поле невалидный текст\n // после коммита — значит показывать цвет, которого нет.\n //\n // Откат идёт следующим тиком намеренно: вернув значение синхронно, мы отдали\n // бы Vue то же самое, что он уже отрисовал, — патча не случилось бы, и в поле\n // остался бы набранный мусор.\n if (!parsed) {\n void nextTick(() => { hexDraft.value = hexValue.value })\n return\n }\n\n commit(props.alpha ? parsed : { ...parsed, a: state.value.a })\n}\n\nconst isOpen = ref(false)\nconst panelOpen = computed({\n get: () => props.open ?? isOpen.value,\n set: (value) => {\n isOpen.value = value\n emit('update:open', value)\n },\n})\n\nconst presetList = computed(() => props.presets.filter(preset => parseHexColor(preset) !== null))\n\nfunction isPresetSelected(preset: string): boolean {\n const parsed = parseHexColor(preset)\n return parsed ? formatHexColor(parsed, props.alpha) === hexValue.value : false\n}\n\nfunction selectPreset(preset: string): void {\n const parsed = parseHexColor(preset)\n if (parsed) commit(parsed)\n}\n\nconst triggerClass = computed(() => grColorPickerTriggerClass({\n size: resolvedSize.value,\n disabled: isDisabled.value,\n invalid: isInvalid.value,\n}))\n\nconst panelClass = computed(() => grColorPickerPanelClass(resolvedSize.value))\nconst swatchSizeClass = computed(() => triggerSwatchSizeBySize[resolvedSize.value])\n\n/**\n * Градиенты дорожек. Через `--gr-slider-rail` их не подать: хук уходит в\n * `background-color`, а градиент там не работает — поэтому значения приезжают\n * переменными, а красит дорожки собственный `<style>` компонента.\n *\n * Переменные висят на самой панели, а не на корне: панель уезжает в портал, и\n * наследование от корня до неё не доходит.\n */\nconst trackVars = computed<Record<string, string>>(() => {\n const { h, s, l } = state.value\n const solid = hslaToCss({ h, s, l, a: 1 })\n\n return {\n '--gr-color-picker-track-hue': 'linear-gradient(to right, hsl(0 100% 50%), hsl(60 100% 50%), hsl(120 100% 50%), hsl(180 100% 50%), hsl(240 100% 50%), hsl(300 100% 50%), hsl(360 100% 50%))',\n '--gr-color-picker-track-saturation': `linear-gradient(to right, hsl(${Math.round(h)} 0% ${Math.round(l)}%), hsl(${Math.round(h)} 100% ${Math.round(l)}%))`,\n '--gr-color-picker-track-lightness': `linear-gradient(to right, hsl(${Math.round(h)} ${Math.round(s)}% 0%), hsl(${Math.round(h)} ${Math.round(s)}% 50%), hsl(${Math.round(h)} ${Math.round(s)}% 100%))`,\n '--gr-color-picker-track-alpha': `linear-gradient(to right, transparent, ${solid})`,\n }\n})\n</script>\n\n<template>\n <div data-gr-color-picker>\n <!-- Значение для нативной формы уходит скрытым полем: интерактивного\n контрола внутри виджета быть не должно. -->\n <input v-if=\"name\" type=\"hidden\" :name=\"name\" :value=\"hexValue\">\n\n <span v-if=\"stateHint\" :id=\"stateHintId\" class=\"sr-only\">{{ stateHint }}</span>\n\n <GrPopover\n v-model:open=\"panelOpen\"\n :size=\"resolvedSize\"\n :placement=\"placement\"\n :disabled=\"isDisabled\"\n :aria-label=\"t('gr.colorPicker.panelLabel', 'Color picker')\"\n :close-on-content-click=\"false\"\n >\n <template #trigger=\"{ triggerProps }\">\n <button\n :id=\"fieldId\"\n ref=\"triggerEl\"\n v-bind=\"triggerProps\"\n data-gr-color-picker-trigger\n type=\"button\"\n :class=\"triggerClass\"\n :aria-label=\"ariaLabel\"\n :aria-describedby=\"describedBy\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n @focus=\"emit('focus', $event)\"\n @blur=\"emit('blur', $event)\"\n >\n <span\n data-gr-color-picker-swatch\n aria-hidden=\"true\"\n :class=\"[swatchBaseClass, swatchSizeClass, checkerClass]\"\n >\n <span :class=\"swatchFillClass\" :style=\"{ background: cssColor }\" />\n </span>\n\n <span :class=\"triggerValueClass\">{{ hexValue }}</span>\n </button>\n </template>\n\n <template #content>\n <div data-gr-color-picker-panel :class=\"panelClass\" :style=\"trackVars\">\n <div\n data-gr-color-picker-preview\n aria-hidden=\"true\"\n :class=\"[previewClass, checkerClass]\"\n >\n <span :class=\"swatchFillClass\" :style=\"{ background: cssColor }\" />\n </div>\n\n <div data-gr-color-picker-channel=\"hue\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">H</span>\n <GrSlider\n v-model=\"hue\"\n :min=\"0\"\n :max=\"359\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"degrees\"\n :aria-label=\"t('gr.colorPicker.hue', 'Hue')\"\n />\n <span :class=\"rowValueClass\">{{ degrees(hue) }}</span>\n </div>\n\n <div data-gr-color-picker-channel=\"saturation\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">S</span>\n <GrSlider\n v-model=\"saturation\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"percent\"\n :aria-label=\"t('gr.colorPicker.saturation', 'Saturation')\"\n />\n <span :class=\"rowValueClass\">{{ percent(saturation) }}</span>\n </div>\n\n <div data-gr-color-picker-channel=\"lightness\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">L</span>\n <GrSlider\n v-model=\"lightness\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"percent\"\n :aria-label=\"t('gr.colorPicker.lightness', 'Lightness')\"\n />\n <span :class=\"rowValueClass\">{{ percent(lightness) }}</span>\n </div>\n\n <div v-if=\"alpha\" data-gr-color-picker-channel=\"alpha\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">A</span>\n <GrSlider\n v-model=\"opacity\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"percent\"\n :aria-label=\"t('gr.colorPicker.opacity', 'Opacity')\"\n />\n <span :class=\"rowValueClass\">{{ percent(opacity) }}</span>\n </div>\n\n <GrInput\n v-model=\"hexDraft\"\n data-gr-color-picker-hex\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :aria-label=\"t('gr.colorPicker.hexLabel', 'Hex value')\"\n @change=\"commitHex\"\n @keydown.enter=\"commitHex\"\n />\n\n <div\n v-if=\"presetList.length\"\n data-gr-color-picker-presets\n :class=\"presetsGridClass\"\n role=\"group\"\n :aria-label=\"t('gr.colorPicker.presetsLabel', 'Color presets')\"\n >\n <button\n v-for=\"preset in presetList\"\n :key=\"preset\"\n data-gr-color-picker-preset\n type=\"button\"\n :class=\"[grColorPickerPresetClass(isPresetSelected(preset)), checkerClass]\"\n :aria-label=\"t('gr.colorPicker.swatchLabel', 'Color {color}', { color: preset })\"\n :aria-pressed=\"isPresetSelected(preset) ? 'true' : 'false'\"\n :disabled=\"isLocked\"\n @click=\"selectPreset(preset)\"\n >\n <span :class=\"swatchFillClass\" :style=\"{ background: preset }\" />\n </button>\n </div>\n </div>\n </template>\n </GrPopover>\n </div>\n</template>\n\n<style>\n/*\n * Шахматка под прозрачным цветом. Двумя `conic-gradient` — в утилите такое не\n * выражается, а без подложки прозрачный образец сливается с панелью.\n */\n.gr-color-picker-checker {\n background-image:\n conic-gradient(\n from 90deg,\n var(--gr-color-picker-checker-light, var(--gr-bg)) 0 25%,\n var(--gr-color-picker-checker-dark, var(--gr-muted)) 0 50%,\n var(--gr-color-picker-checker-light, var(--gr-bg)) 0 75%,\n var(--gr-color-picker-checker-dark, var(--gr-muted)) 0\n );\n background-size:\n calc(var(--gr-color-picker-checker-size, 6px) * 2)\n calc(var(--gr-color-picker-checker-size, 6px) * 2);\n /* Рамка образца полупрозрачна, и без обрезки шахматка просвечивает сквозь неё. */\n background-clip: padding-box;\n}\n\n/*\n * Дорожки каналов. Красим `[data-gr-slider-track]` потому, что хук\n * `--gr-slider-rail` уходит в `background-color`, а градиент там не работает.\n * Сами градиенты компонент подаёт переменными — CSS остаётся статичной.\n */\n[data-gr-color-picker-channel] [data-gr-slider-track] {\n border-radius: var(--gr-radius-full);\n}\n\n[data-gr-color-picker-channel='hue'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-hue);\n}\n\n[data-gr-color-picker-channel='saturation'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-saturation);\n}\n\n[data-gr-color-picker-channel='lightness'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-lightness);\n}\n\n[data-gr-color-picker-channel='alpha'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-alpha);\n}\n\n/* Заливка канала прозрачна: шкалу показывает сама дорожка. */\n[data-gr-color-picker-channel] [data-gr-slider-fill] {\n background-color: transparent;\n}\n</style>\n"],"mappings":";;;;;;;;;;AAwBA,IAAM,cAAc;AAEpB,SAAS,MAAM,OAAe,KAAa,KAAqB;CAC9D,OAAO,KAAK,IAAI,KAAK,IAAI,OAAO,GAAG,GAAG,GAAG;AAC3C;;;;;;;;;AAUA,SAAS,aAAa,KAAqB;CACzC,IAAI,CAAC,OAAO,SAAS,GAAG,GAAG,OAAO;CAClC,QAAS,MAAM,MAAO,OAAO;AAC/B;;AAGA,SAAS,gBAAgB,KAA4B;CACnD,IAAI,IAAI,WAAW,KAAK,IAAI,WAAW,GACrC,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC,KAAI,SAAQ,OAAO,IAAI,CAAC,CAAC,KAAK,EAAE;CAElD,IAAI,IAAI,WAAW,KAAK,IAAI,WAAW,GAAG,OAAO;CAEjD,OAAO;AACT;AAEA,SAAS,SAAS,EAAE,GAAG,GAAG,KAA+C;CACvE,MAAM,MAAM,IAAI;CAChB,MAAM,QAAQ,IAAI;CAClB,MAAM,OAAO,IAAI;CAEjB,MAAM,MAAM,KAAK,IAAI,KAAK,OAAO,IAAI;CACrC,MAAM,MAAM,KAAK,IAAI,KAAK,OAAO,IAAI;CACrC,MAAM,QAAQ,MAAM;CACpB,MAAM,aAAa,MAAM,OAAO;CAEhC,IAAI,UAAU,GAAG,OAAO;EAAE,GAAG;EAAG,GAAG;EAAG,GAAG,YAAY;CAAI;CAEzD,MAAM,aAAa,SAAS,IAAI,KAAK,IAAI,IAAI,YAAY,CAAC;CAE1D,IAAI;CACJ,IAAI,QAAQ,KAAK,OAAQ,QAAQ,QAAQ,QAAS;MAC7C,IAAI,QAAQ,OAAO,OAAO,OAAO,OAAO,QAAQ;MAChD,OAAO,MAAM,SAAS,QAAQ;CAEnC,OAAO;EAAE,GAAG,aAAa,MAAM,EAAE;EAAG,GAAG,aAAa;EAAK,GAAG,YAAY;CAAI;AAC9E;AAEA,SAAS,SAAS,EAAE,GAAG,GAAG,KAA6B;CACrD,MAAM,aAAa,MAAM,GAAG,GAAG,GAAG,IAAI;CACtC,MAAM,YAAY,MAAM,GAAG,GAAG,GAAG,IAAI;CACrC,MAAM,MAAM,aAAa,CAAC;CAE1B,MAAM,UAAU,IAAI,KAAK,IAAI,IAAI,YAAY,CAAC,KAAK;CACnD,MAAM,SAAS,UAAU,IAAI,KAAK,IAAM,MAAM,KAAM,IAAK,CAAC;CAC1D,MAAM,QAAQ,YAAY,SAAS;CAEnC,MAAM,CAAC,KAAK,OAAO,eAAyC;EAC1D,IAAI,MAAM,IAAI,OAAO;GAAC;GAAQ;GAAQ;EAAC;EACvC,IAAI,MAAM,KAAK,OAAO;GAAC;GAAQ;GAAQ;EAAC;EACxC,IAAI,MAAM,KAAK,OAAO;GAAC;GAAG;GAAQ;EAAM;EACxC,IAAI,MAAM,KAAK,OAAO;GAAC;GAAG;GAAQ;EAAM;EACxC,IAAI,MAAM,KAAK,OAAO;GAAC;GAAQ;GAAG;EAAM;EACxC,OAAO;GAAC;GAAQ;GAAG;EAAM;CAC3B,EAAA,CAAG;CAEH,OAAO;EACL,GAAG,KAAK,OAAO,MAAM,SAAS,GAAG;EACjC,GAAG,KAAK,OAAO,QAAQ,SAAS,GAAG;EACnC,GAAG,KAAK,OAAO,OAAO,SAAS,GAAG;CACpC;AACF;AAEA,SAAS,UAAU,OAAuB;CACxC,OAAO,MAAM,KAAK,MAAM,KAAK,GAAG,GAAG,GAAG,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG;AACtE;;;;;;;;AASA,SAAgB,cAAc,OAA+B;CAC3D,IAAI,OAAO,UAAU,UAAU,OAAO;CAEtC,MAAM,QAAQ,YAAY,KAAK,MAAM,KAAK,CAAC;CAC3C,IAAI,CAAC,OAAO,OAAO;CAEnB,MAAM,MAAM,gBAAgB,MAAM,EAAE;CACpC,IAAI,CAAC,KAAK,OAAO;CAEjB,MAAM,MAAW;EACf,GAAG,OAAO,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE;EACtC,GAAG,OAAO,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE;EACtC,GAAG,OAAO,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE;CACxC;CAEA,MAAM,QAAQ,IAAI,WAAW,IAAI,OAAO,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE,IAAI,MAAM;CAE9E,OAAO;EAAE,GAAG,SAAS,GAAG;EAAG,GAAG;CAAM;AACtC;;;;;;AAOA,SAAgB,eAAe,OAAe,YAAY,OAAe;CACvE,MAAM,EAAE,GAAG,GAAG,MAAM,SAAS,KAAK;CAClC,MAAM,OAAO,IAAI,UAAU,CAAC,IAAI,UAAU,CAAC,IAAI,UAAU,CAAC;CAE1D,IAAI,CAAC,WAAW,OAAO;CAEvB,OAAO,GAAG,OAAO,UAAU,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,GAAG;AACvD;;AAGA,SAAS,KAAK,OAAuB;CACnC,OAAO,KAAK,MAAM,QAAQ,GAAG,IAAI;AACnC;;AAGA,SAAgB,UAAU,OAAuB;CAK/C,OAAO,OAJG,KAAK,aAAa,MAAM,CAAC,CAIrB,EAAE,GAHN,KAAK,MAAM,MAAM,GAAG,GAAG,GAAG,CAGjB,EAAE,IAFX,KAAK,MAAM,MAAM,GAAG,GAAG,GAAG,CAEX,EAAE,MAAM,KAAK,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,EAAE;AAC9D;;AAGA,SAAgB,cAAc,OAAuB;CACnD,OAAO;EACL,GAAG,KAAK,MAAM,aAAa,MAAM,CAAC,CAAC,IAAI;EACvC,GAAG,MAAM,KAAK,MAAM,MAAM,CAAC,GAAG,GAAG,GAAG;EACpC,GAAG,MAAM,KAAK,MAAM,MAAM,CAAC,GAAG,GAAG,GAAG;EACpC,GAAG,MAAM,MAAM,GAAG,GAAG,CAAC;CACxB;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EC9FA,MAAM,QAAQ;EAgBd,MAAM,OAAO;EAEb,MAAM,EAAE,MAAM,2BAA2B;EAEzC,MAAM,eAAe,yBAAyB,MAAM,MAAM,EAAE,WAAW,gBAAgB,CAAC;EAExF,MAAM,QAAQ,sBAAsB;EACpC,MAAM,UAAU,eAAe,OAAO,GAAG,KAAK;EAC9C,MAAM,EACJ,UAAU,YACV,SAAS,WACT,UAAU,YACV,UAAU,YAEV,QAAQ,aACN,uBAAuB,KAAK;;;;;;;EAQhC,MAAM,cAAc,MAAM;EAC1B,MAAM,YAAY,eAAe,CAC/B,WAAW,QAAQ,EAAE,oBAAoB,wBAAwB,IAAI,KAAA,GACrE,WAAW,QAAQ,EAAE,oBAAoB,WAAW,IAAI,KAAA,CAC1D,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,IAAI,KAAK,KAAA,CAAS;EAEzC,MAAM,cAAc,eAAe,CACjC,OAAO,cAAc,OACrB,UAAU,QAAQ,cAAc,KAAA,CAClC,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG,KAAK,KAAA,CAAS;EAExC,MAAM,YAAY,IAAwB,IAAI;EAE9C,SAAS,QAAc;GACrB,UAAU,OAAO,MAAM;EACzB;EAEA,SAAS,OAAa;GACpB,UAAU,OAAO,KAAK;EACxB;EAEA,SAAa;GAAE;GAAO;EAAK,CAAC;;;;;;;;EAW5B,MAAM,QAAQ,IAAY,cAAc,MAAM,UAAU,KAAK;GATlC,GAAG;GAAG,GAAG;GAAG,GAAG;GAAG,GAAG;EASa,CAAQ;;EAGrE,IAAI,cAAc;EAElB,YAAY,MAAM,aAAa,SAAS;GACtC,IAAI,SAAS,aAAa;GAE1B,MAAM,SAAS,cAAc,IAAI;GACjC,IAAI,QAAQ,MAAM,QAAQ;EAC5B,CAAC;EAED,MAAM,WAAW,eAAe,UAAU,MAAM,KAAK,CAAC;EACtD,MAAM,WAAW,eAAe,eAAe,MAAM,OAAO,MAAM,KAAK,CAAC;EAExE,SAAS,OAAO,MAAoB;GAClC,IAAI,SAAS,OAAO;GAEpB,MAAM,QAAQ,cAAc,IAAI;GAEhC,MAAM,MAAM,eAAe,MAAM,OAAO,MAAM,KAAK;GACnD,cAAc;GACd,KAAK,qBAAqB,GAAG;GAC7B,KAAK,UAAU,GAAG;EACpB;;EAGA,SAAS,QAAQ,KAA4B,QAAQ,GAAG;GACtD,OAAO,SAAiB;IACtB,WAAW,KAAK,MAAM,MAAM,MAAM,OAAO,KAAK;IAC9C,MAAK,UAAS,OAAO;KAAE,GAAG,MAAM;MAAQ,MAAM,QAAQ;IAAM,CAAC;GAC/D,CAAC;EACH;EAEA,MAAM,MAAM,QAAQ,GAAG;EACvB,MAAM,aAAa,QAAQ,GAAG;EAC9B,MAAM,YAAY,QAAQ,GAAG;EAE7B,MAAM,UAAU,QAAQ,KAAK,GAAG;EAEhC,MAAM,WAAW,UAA0B,GAAG,MAAM;EACpD,MAAM,WAAW,UAA0B,GAAG,MAAM;;EAGpD,MAAM,WAAW,IAAI,SAAS,KAAK;EAEnC,MAAM,WAAW,SAAS;GACxB,SAAS,QAAQ;EACnB,CAAC;EAED,SAAS,YAAkB;GACzB,MAAM,SAAS,cAAc,SAAS,KAAK;GAQ3C,IAAI,CAAC,QAAQ;IACX,eAAoB;KAAE,SAAS,QAAQ,SAAS;IAAM,CAAC;IACvD;GACF;GAEA,OAAO,MAAM,QAAQ,SAAS;IAAE,GAAG;IAAQ,GAAG,MAAM,MAAM;GAAE,CAAC;EAC/D;EAEA,MAAM,SAAS,IAAI,KAAK;EACxB,MAAM,YAAY,SAAS;GACzB,WAAW,MAAM,QAAQ,OAAO;GAChC,MAAM,UAAU;IACd,OAAO,QAAQ;IACf,KAAK,eAAe,KAAK;GAC3B;EACF,CAAC;EAED,MAAM,aAAa,eAAe,MAAM,QAAQ,QAAO,WAAU,cAAc,MAAM,MAAM,IAAI,CAAC;EAEhG,SAAS,iBAAiB,QAAyB;GACjD,MAAM,SAAS,cAAc,MAAM;GACnC,OAAO,SAAS,eAAe,QAAQ,MAAM,KAAK,MAAM,SAAS,QAAQ;EAC3E;EAEA,SAAS,aAAa,QAAsB;GAC1C,MAAM,SAAS,cAAc,MAAM;GACnC,IAAI,QAAQ,OAAO,MAAM;EAC3B;EAEA,MAAM,eAAe,eAAe,0BAA0B;GAC5D,MAAM,aAAa;GACnB,UAAU,WAAW;GACrB,SAAS,UAAU;EACrB,CAAC,CAAC;EAEF,MAAM,aAAa,eAAe,wBAAwB,aAAa,KAAK,CAAC;EAC7E,MAAM,kBAAkB,eAAe,wBAAwB,aAAa,MAAM;;;;;;;;;EAUlF,MAAM,YAAY,eAAuC;GACvD,MAAM,EAAE,GAAG,GAAG,MAAM,MAAM;GAC1B,MAAM,QAAQ,UAAU;IAAE;IAAG;IAAG;IAAG,GAAG;GAAE,CAAC;GAEzC,OAAO;IACL,+BAA+B;IAC/B,sCAAsC,iCAAiC,KAAK,MAAM,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC,EAAE,UAAU,KAAK,MAAM,CAAC,EAAE,QAAQ,KAAK,MAAM,CAAC,EAAE;IACvJ,qCAAqC,iCAAiC,KAAK,MAAM,CAAC,EAAE,GAAG,KAAK,MAAM,CAAC,EAAE,aAAa,KAAK,MAAM,CAAC,EAAE,GAAG,KAAK,MAAM,CAAC,EAAE,cAAc,KAAK,MAAM,CAAC,EAAE,GAAG,KAAK,MAAM,CAAC,EAAE;IAC9L,iCAAiC,0CAA0C,MAAM;GACnF;EACF,CAAC;;uBAIC,mBA4IM,OA5IN,YA4IM;IAzIS,QAAA,QAAA,UAAA,GAAb,mBAAgE,SAAA;;KAA7C,MAAK;KAAU,MAAM,QAAA;KAAO,OAAO,SAAA;;IAE1C,UAAA,SAAA,UAAA,GAAZ,mBAA+E,QAAA;;KAAvD,IAAI,MAAA,WAAA;KAAa,OAAM;uBAAa,UAAA,KAAS,GAAA,GAAA,UAAA,KAAA,mBAAA,IAAA,IAAA;IAErE,YAoIY,mBAAA;KAnIF,MAAM,UAAA;sEAAS,QAAA;KACtB,MAAM,MAAA,YAAA;KACN,WAAW,QAAA;KACX,UAAU,MAAA,UAAA;KACV,cAAY,MAAA,CAAA,CAAC,CAAA,6BAAA,cAAA;KACb,0BAAwB;;KAEd,SAAO,SAuBP,EAvBW,mBAAY,CAChC,mBAsBS,UAtBT,WAsBS;MArBN,IAAI,QAAA;eACD;MAAJ,KAAI;QACI,cAAY;MACpB,gCAAA;MACA,MAAK;MACJ,OAAO,aAAA;MACP,cAAY,QAAA;MACZ,oBAAkB,YAAA;MAClB,gBAAc,MAAA,SAAA,IAAS,SAAY,KAAA;MACnC,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,SAAU,MAAM;MAC3B,QAAI,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,QAAS,MAAM;UAE1B,mBAMO,QAAA;MALL,+BAAA;MACA,eAAY;MACX,OAAK,eAAA;OAAG,MAAA,eAAA;OAAiB,gBAAA;OAAiB,MAAA,YAAA;MAAY,CAAA;SAEvD,mBAAmE,QAAA;MAA5D,OAAK,eAAE,MAAA,eAAA,CAAe;MAAG,OAAK,eAAA,EAAA,YAAgB,SAAA,MAAQ,CAAA;uBAG/D,mBAAsD,QAAA,EAA/C,OAAK,eAAE,MAAA,iBAAA,CAAiB,EAAA,GAAA,gBAAK,SAAA,KAAQ,GAAA,CAAA,CAAA,GAAA,IAAA,UAAA,CAAA,CAAA;KAIrC,SAAO,cAgGV,CA/FN,mBA+FM,OAAA;MA/FD,8BAAA;MAA4B,OAAK,eAAE,WAAA,KAAU;MAAG,OAAK,eAAE,UAAA,KAAS;;MACnE,mBAMM,OAAA;OALJ,gCAAA;OACA,eAAY;OACX,OAAK,eAAA,CAAG,MAAA,YAAA,GAAc,MAAA,YAAA,CAAY,CAAA;UAEnC,mBAAmE,QAAA;OAA5D,OAAK,eAAE,MAAA,eAAA,CAAe;OAAG,OAAK,eAAA,EAAA,YAAgB,SAAA,MAAQ,CAAA;;MAG/D,mBAaM,OAAA;OAbD,gCAA6B;OAAO,OAAK,eAAE,MAAA,QAAA,CAAQ;;OACtD,mBAAwD,QAAA;QAAjD,OAAK,eAAE,MAAA,aAAA,CAAa;QAAE,eAAY;UAAO,KAAC,CAAA;OACjD,YASE,kBAAA;oBARS,MAAA,GAAA;sFAAG,QAAA,SAAA;QACX,KAAK;QACL,KAAK;QACL,MAAM,MAAA,YAAA;QACN,UAAU,MAAA,UAAA;QACV,UAAU,MAAA,UAAA;QACV,kBAAgB;QAChB,cAAY,MAAA,CAAA,CAAC,CAAA,sBAAA,KAAA;;;;;;;;OAEhB,mBAAsD,QAAA,EAA/C,OAAK,eAAE,MAAA,aAAA,CAAa,EAAA,GAAA,gBAAK,QAAQ,MAAA,GAAA,CAAG,CAAA,GAAA,CAAA;;MAG7C,mBAWM,OAAA;OAXD,gCAA6B;OAAc,OAAK,eAAE,MAAA,QAAA,CAAQ;;OAC7D,mBAAwD,QAAA;QAAjD,OAAK,eAAE,MAAA,aAAA,CAAa;QAAE,eAAY;UAAO,KAAC,CAAA;OACjD,YAOE,kBAAA;oBANS,MAAA,UAAA;oGAAU,QAAA,SAAA;QAClB,MAAM,MAAA,YAAA;QACN,UAAU,MAAA,UAAA;QACV,UAAU,MAAA,UAAA;QACV,kBAAgB;QAChB,cAAY,MAAA,CAAA,CAAC,CAAA,6BAAA,YAAA;;;;;;;;OAEhB,mBAA6D,QAAA,EAAtD,OAAK,eAAE,MAAA,aAAA,CAAa,EAAA,GAAA,gBAAK,QAAQ,MAAA,UAAA,CAAU,CAAA,GAAA,CAAA;;MAGpD,mBAWM,OAAA;OAXD,gCAA6B;OAAa,OAAK,eAAE,MAAA,QAAA,CAAQ;;OAC5D,mBAAwD,QAAA;QAAjD,OAAK,eAAE,MAAA,aAAA,CAAa;QAAE,eAAY;UAAO,KAAC,CAAA;OACjD,YAOE,kBAAA;oBANS,MAAA,SAAA;kGAAS,QAAA,SAAA;QACjB,MAAM,MAAA,YAAA;QACN,UAAU,MAAA,UAAA;QACV,UAAU,MAAA,UAAA;QACV,kBAAgB;QAChB,cAAY,MAAA,CAAA,CAAC,CAAA,4BAAA,WAAA;;;;;;;;OAEhB,mBAA4D,QAAA,EAArD,OAAK,eAAE,MAAA,aAAA,CAAa,EAAA,GAAA,gBAAK,QAAQ,MAAA,SAAA,CAAS,CAAA,GAAA,CAAA;;MAGxC,QAAA,SAAA,UAAA,GAAX,mBAWM,OAAA;;OAXY,gCAA6B;OAAS,OAAK,eAAE,MAAA,QAAA,CAAQ;;OACrE,mBAAwD,QAAA;QAAjD,OAAK,eAAE,MAAA,aAAA,CAAa;QAAE,eAAY;UAAO,KAAC,CAAA;OACjD,YAOE,kBAAA;oBANS,MAAA,OAAA;8FAAO,QAAA,SAAA;QACf,MAAM,MAAA,YAAA;QACN,UAAU,MAAA,UAAA;QACV,UAAU,MAAA,UAAA;QACV,kBAAgB;QAChB,cAAY,MAAA,CAAA,CAAC,CAAA,0BAAA,SAAA;;;;;;;;OAEhB,mBAA0D,QAAA,EAAnD,OAAK,eAAE,MAAA,aAAA,CAAa,EAAA,GAAA,gBAAK,QAAQ,MAAA,OAAA,CAAO,CAAA,GAAA,CAAA;;MAGjD,YASE,iBAAA;mBARS,SAAA;6EAAQ,QAAA;OACjB,4BAAA;OACC,MAAM,MAAA,YAAA;OACN,UAAU,MAAA,UAAA;OACV,UAAU,MAAA,UAAA;OACV,cAAY,MAAA,CAAA,CAAC,CAAA,2BAAA,WAAA;OACb,UAAQ;OACR,WAAO,SAAQ,WAAS,CAAA,OAAA,CAAA;;;;;;;;MAInB,WAAA,MAAW,UAAA,UAAA,GADnB,mBAoBM,OAAA;;OAlBJ,gCAAA;OACC,OAAK,eAAE,MAAA,gBAAA,CAAgB;OACxB,MAAK;OACJ,cAAY,MAAA,CAAA,CAAC,CAAA,+BAAA,eAAA;4BAEd,mBAYS,UAAA,MAAA,WAXU,WAAA,QAAV,WAAM;2BADf,mBAYS,UAAA;QAVN,KAAK;QACN,+BAAA;QACA,MAAK;QACJ,OAAK,eAAA,CAAG,MAAA,wBAAA,CAAwB,CAAC,iBAAiB,MAAM,CAAA,GAAI,MAAA,YAAA,CAAY,CAAA;QACxE,cAAY,MAAA,CAAA,CAAC,CAAA,8BAAA,iBAAA,EAAA,OAAyD,OAAM,CAAA;QAC5E,gBAAc,iBAAiB,MAAM,IAAA,SAAA;QACrC,UAAU,MAAA,QAAA;QACV,UAAK,WAAE,aAAa,MAAM;WAE3B,mBAAiE,QAAA;QAA1D,OAAK,eAAE,MAAA,eAAA,CAAe;QAAG,OAAK,eAAA,EAAA,YAAgB,OAAM,CAAA"}
1
+ {"version":3,"file":"GrColorPicker-C7ifyQ0R.js","names":[],"sources":["../../../../src/components/GrColorPicker/color.ts","../../../../src/components/GrColorPicker/GrColorPicker.vue","../../../../src/components/GrColorPicker/GrColorPicker.vue"],"sourcesContent":["/**\n * Арифметика цвета для `GrColorPicker` — чистый модуль без Vue.\n *\n * Модель компонента — hex-строка (в такой форме цвет лежит в токенах темы),\n * а панель работает в HSL: у оттенка, насыщенности и светлоты есть шкала, по\n * которой можно вести слайдер, а у каналов RGB её нет.\n *\n * Свой парсер, а не `__tests__/cssContrast.ts`: тот — инфраструктура гейтов, и\n * тянуть тестовый модуль в поставляемый код нельзя.\n */\n\nexport type GrHsla = {\n /** Оттенок, 0…359 (круговой: 360 — это 0). */\n h: number\n /** Насыщенность, 0…100. */\n s: number\n /** Светлота, 0…100. */\n l: number\n /** Непрозрачность, 0…1. */\n a: number\n}\n\ntype Rgb = { r: number, g: number, b: number }\n\nconst HEX_PATTERN = /^#?([0-9a-f]{3,8})$/i\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max)\n}\n\n/**\n * Оттенок цикличен: 360° — тот же цвет, что 0°.\n *\n * Округления здесь нет намеренно: `#3b82f6` даёт 217.22°, и округлив его до\n * целого, обратное преобразование вернуло бы `#3b83f6` — компонент молча\n * переписывал бы чужой цвет при первом же открытии панели. Целые градусы\n * появляются там, где они и нужны, — на шкале слайдера (`normalizeHsla`).\n */\nfunction normalizeHue(hue: number): number {\n if (!Number.isFinite(hue)) return 0\n return ((hue % 360) + 360) % 360\n}\n\n/** Короткая форма `#abc` разворачивается в `#aabbcc` — так её читает и CSS. */\nfunction expandShorthand(hex: string): string | null {\n if (hex.length === 3 || hex.length === 4)\n return [...hex].map(char => char + char).join('')\n\n if (hex.length === 6 || hex.length === 8) return hex\n\n return null\n}\n\nfunction rgbToHsl({ r, g, b }: Rgb): { h: number, s: number, l: number } {\n const red = r / 255\n const green = g / 255\n const blue = b / 255\n\n const max = Math.max(red, green, blue)\n const min = Math.min(red, green, blue)\n const delta = max - min\n const lightness = (max + min) / 2\n\n if (delta === 0) return { h: 0, s: 0, l: lightness * 100 }\n\n const saturation = delta / (1 - Math.abs(2 * lightness - 1))\n\n let hue: number\n if (max === red) hue = ((green - blue) / delta) % 6\n else if (max === green) hue = (blue - red) / delta + 2\n else hue = (red - green) / delta + 4\n\n return { h: normalizeHue(hue * 60), s: saturation * 100, l: lightness * 100 }\n}\n\nfunction hslToRgb({ h, s, l }: Omit<GrHsla, 'a'>): Rgb {\n const saturation = clamp(s, 0, 100) / 100\n const lightness = clamp(l, 0, 100) / 100\n const hue = normalizeHue(h)\n\n const chroma = (1 - Math.abs(2 * lightness - 1)) * saturation\n const second = chroma * (1 - Math.abs(((hue / 60) % 2) - 1))\n const match = lightness - chroma / 2\n\n const [red, green, blue] = ((): [number, number, number] => {\n if (hue < 60) return [chroma, second, 0]\n if (hue < 120) return [second, chroma, 0]\n if (hue < 180) return [0, chroma, second]\n if (hue < 240) return [0, second, chroma]\n if (hue < 300) return [second, 0, chroma]\n return [chroma, 0, second]\n })()\n\n return {\n r: Math.round((red + match) * 255),\n g: Math.round((green + match) * 255),\n b: Math.round((blue + match) * 255),\n }\n}\n\nfunction toHexPair(value: number): string {\n return clamp(Math.round(value), 0, 255).toString(16).padStart(2, '0')\n}\n\n/**\n * Разбирает hex любой из четырёх форм (`#abc`, `#abcd`, `#aabbcc`, `#aabbccdd`),\n * регистр и решётка необязательны.\n *\n * Мусор возвращает `null`, а не бросает: значение приходит из модели снаружи и\n * из поля ввода, и упавший компонент — худший ответ на опечатку.\n */\nexport function parseHexColor(value: unknown): GrHsla | null {\n if (typeof value !== 'string') return null\n\n const match = HEX_PATTERN.exec(value.trim())\n if (!match) return null\n\n const hex = expandShorthand(match[1])\n if (!hex) return null\n\n const rgb: Rgb = {\n r: Number.parseInt(hex.slice(0, 2), 16),\n g: Number.parseInt(hex.slice(2, 4), 16),\n b: Number.parseInt(hex.slice(4, 6), 16),\n }\n\n const alpha = hex.length === 8 ? Number.parseInt(hex.slice(6, 8), 16) / 255 : 1\n\n return { ...rgbToHsl(rgb), a: alpha }\n}\n\n/**\n * Собирает hex обратно. `withAlpha: false` альфу отбрасывает, а не округляет до\n * непрозрачного: компонент без пропа `alpha` не вправе отдавать восьмизначную\n * форму, которую потребитель не ждёт.\n */\nexport function formatHexColor(color: GrHsla, withAlpha = false): string {\n const { r, g, b } = hslToRgb(color)\n const base = `#${toHexPair(r)}${toHexPair(g)}${toHexPair(b)}`\n\n if (!withAlpha) return base\n\n return `${base}${toHexPair(clamp(color.a, 0, 1) * 255)}`\n}\n\n/** Короткая запись числа: целое остаётся целым, дробь обрезается до сотых. */\nfunction trim(value: number): number {\n return Math.round(value * 100) / 100\n}\n\n/** CSS-запись цвета для превью и градиентов. */\nexport function hslaToCss(color: GrHsla): string {\n const h = trim(normalizeHue(color.h))\n const s = trim(clamp(color.s, 0, 100))\n const l = trim(clamp(color.l, 0, 100))\n\n return `hsl(${h} ${s}% ${l}% / ${trim(clamp(color.a, 0, 1))})`\n}\n\n/** Приводит произвольный объект к валидному диапазону каналов. */\nexport function normalizeHsla(color: GrHsla): GrHsla {\n return {\n h: Math.round(normalizeHue(color.h)) % 360,\n s: clamp(Math.round(color.s), 0, 100),\n l: clamp(Math.round(color.l), 0, 100),\n a: clamp(color.a, 0, 1),\n }\n}\n","<script setup lang=\"ts\">\n/**\n * GrColorPicker — поле выбора цвета: образец с текущим значением, панель с\n * оттенком, насыщенностью, светлотой и прозрачностью, поле hex и палитра.\n *\n * Прямой сценарий — настройки темы и брендирования, поэтому модель хранит hex:\n * в такой форме цвет лежит в токенах и его понимает CSS.\n *\n * A11y: каждый канал — отдельный `GrSlider`, то есть настоящий `role=\"slider\"`\n * с полной клавиатурой и `aria-valuetext`. Двумерная область saturation/value\n * не берётся намеренно: это свой виджет со своей клавиатурой по двум осям, и\n * доступность там пришлось бы собирать с нуля.\n */\nimport { computed, nextTick, ref, useId, watch } from 'vue'\n\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport GrInput from '../GrInput/GrInput.vue'\nimport GrPopover from '../GrPopover/GrPopover.vue'\nimport GrSlider from '../GrSlider/GrSlider.vue'\n\nimport { formatHexColor, hslaToCss, normalizeHsla, parseHexColor, type GrHsla } from './color'\nimport {\n checkerClass,\n grColorPickerPanelClass,\n grColorPickerPresetClass,\n grColorPickerTriggerClass,\n presetsGridClass,\n previewClass,\n rowClass,\n rowLabelClass,\n rowValueClass,\n swatchBaseClass,\n swatchFillClass,\n triggerSwatchSizeBySize,\n triggerValueClass,\n type GrColorPickerSize,\n} from './grColorPickerStyles'\n\nexport type { GrColorPickerSize } from './grColorPickerStyles'\n\nexport interface GrColorPickerProps {\n /** Цвет в hex: `#RRGGBB`, а при `alpha` — `#RRGGBBAA`. Мусор не роняет компонент. */\n modelValue: string\n /** Четвёртый слайдер и восьмизначная форма hex. */\n alpha?: boolean\n /** Палитра быстрого выбора. Пусто — блок не рендерится. */\n presets?: string[]\n size?: GrColorPickerSize\n /** Контролируемое состояние панели (`v-model:open`). */\n open?: boolean\n /** Сторона, с которой раскрывается панель. */\n placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end'\n /** Имя для нативной формы: значение уходит скрытым полем. */\n name?: string\n disabled?: boolean\n /** Только для чтения: цвет видно, панель открывается, но значение не меняется. */\n readonly?: boolean\n invalid?: boolean\n required?: boolean\n ariaLabel?: string\n}\n\nexport interface GrColorPickerEmits {\n (e: 'update:modelValue', value: string): void\n (e: 'change', value: string): void\n (e: 'update:open', value: boolean): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(defineProps<GrColorPickerProps>(), {\n alpha: false,\n presets: () => [],\n // Дефолт живёт в резолвере: Vue подставил бы свой раньше, чем компонент\n // заглянет в `GrConfigProvider`.\n size: undefined,\n open: undefined,\n placement: 'bottom-start',\n name: undefined,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n ariaLabel: undefined,\n})\n\nconst emit = defineEmits<GrColorPickerEmits>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrColorPicker' })\n\nconst field = useGrFormFieldContext()\nconst fieldId = computed(() => field?.id.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n // «Ввод не принимается» — `disabled` или `readonly`: значение видно, но не меняется.\n locked: isLocked,\n} = useGrFormControl(() => props)\n\n/**\n * `aria-required` и `aria-readonly` роль `button` не поддерживает — axe роняет\n * это как critical `aria-allowed-attr`. Состояния уходят в описание триггера:\n * иначе они пропали бы для диктора совсем (маркер `*` у `GrFormField`\n * декоративен и скрыт).\n */\nconst stateHintId = useId()\nconst stateHint = computed(() => [\n isRequired.value ? t('gr.form.required', 'This field is required') : undefined,\n isReadonly.value ? t('gr.form.readonly', 'Read only') : undefined,\n].filter(Boolean).join('. ') || undefined)\n\nconst describedBy = computed(() => [\n field?.describedById.value,\n stateHint.value ? stateHintId : undefined,\n].filter(Boolean).join(' ') || undefined)\n\nconst triggerEl = ref<HTMLElement | null>(null)\n\nfunction focus(): void {\n triggerEl.value?.focus()\n}\n\nfunction blur(): void {\n triggerEl.value?.blur()\n}\n\ndefineExpose({ focus, blur })\n\nconst FALLBACK: GrHsla = { h: 0, s: 0, l: 0, a: 1 }\n\n/**\n * Рабочее состояние панели.\n *\n * Держать его отдельно от модели обязательно: hex — проекция с потерями. У\n * серого нет оттенка, и без собственного состояния бегунок оттенка прыгал бы\n * обратно на 0° при каждом движении.\n */\nconst state = ref<GrHsla>(parseHexColor(props.modelValue) ?? FALLBACK)\n\n/** Последнее отданное значение: вернувшись в проп, оно не должно сбрасывать состояние. */\nlet lastEmitted = ''\n\nwatch(() => props.modelValue, (next) => {\n if (next === lastEmitted) return\n\n const parsed = parseHexColor(next)\n if (parsed) state.value = parsed\n})\n\nconst cssColor = computed(() => hslaToCss(state.value))\nconst hexValue = computed(() => formatHexColor(state.value, props.alpha))\n\nfunction commit(next: GrHsla): void {\n if (isLocked.value) return\n\n state.value = normalizeHsla(next)\n\n const hex = formatHexColor(state.value, props.alpha)\n lastEmitted = hex\n emit('update:modelValue', hex)\n emit('change', hex)\n}\n\n/** Канал панели: слайдер читает состояние и коммитит новое значение целиком. */\nfunction channel(key: 'h' | 's' | 'l' | 'a', scale = 1) {\n return computed<number>({\n get: () => Math.round(state.value[key] * scale),\n set: value => commit({ ...state.value, [key]: value / scale }),\n })\n}\n\nconst hue = channel('h')\nconst saturation = channel('s')\nconst lightness = channel('l')\n// Альфа хранится долей, а слайдер ходит по процентам — иначе шаг был бы 0.01.\nconst opacity = channel('a', 100)\n\nconst degrees = (value: number): string => `${value}°`\nconst percent = (value: number): string => `${value}%`\n\n/** Текст поля hex редактируется свободно и коммитится только валидным. */\nconst hexDraft = ref(hexValue.value)\n\nwatch(hexValue, (next) => {\n hexDraft.value = next\n})\n\nfunction commitHex(): void {\n const parsed = parseHexColor(hexDraft.value)\n\n // Мусор откатывается к текущему значению: держать в поле невалидный текст\n // после коммита — значит показывать цвет, которого нет.\n //\n // Откат идёт следующим тиком намеренно: вернув значение синхронно, мы отдали\n // бы Vue то же самое, что он уже отрисовал, — патча не случилось бы, и в поле\n // остался бы набранный мусор.\n if (!parsed) {\n void nextTick(() => { hexDraft.value = hexValue.value })\n return\n }\n\n commit(props.alpha ? parsed : { ...parsed, a: state.value.a })\n}\n\nconst isOpen = ref(false)\nconst panelOpen = computed({\n get: () => props.open ?? isOpen.value,\n set: (value) => {\n isOpen.value = value\n emit('update:open', value)\n },\n})\n\nconst presetList = computed(() => props.presets.filter(preset => parseHexColor(preset) !== null))\n\nfunction isPresetSelected(preset: string): boolean {\n const parsed = parseHexColor(preset)\n return parsed ? formatHexColor(parsed, props.alpha) === hexValue.value : false\n}\n\nfunction selectPreset(preset: string): void {\n const parsed = parseHexColor(preset)\n if (parsed) commit(parsed)\n}\n\nconst triggerClass = computed(() => grColorPickerTriggerClass({\n size: resolvedSize.value,\n disabled: isDisabled.value,\n invalid: isInvalid.value,\n}))\n\nconst panelClass = computed(() => grColorPickerPanelClass(resolvedSize.value))\nconst swatchSizeClass = computed(() => triggerSwatchSizeBySize[resolvedSize.value])\n\n/**\n * Градиенты дорожек. Через `--gr-slider-rail` их не подать: хук уходит в\n * `background-color`, а градиент там не работает — поэтому значения приезжают\n * переменными, а красит дорожки собственный `<style>` компонента.\n *\n * Переменные висят на самой панели, а не на корне: панель уезжает в портал, и\n * наследование от корня до неё не доходит.\n */\nconst trackVars = computed<Record<string, string>>(() => {\n const { h, s, l } = state.value\n const solid = hslaToCss({ h, s, l, a: 1 })\n\n return {\n '--gr-color-picker-track-hue': 'linear-gradient(to right, hsl(0 100% 50%), hsl(60 100% 50%), hsl(120 100% 50%), hsl(180 100% 50%), hsl(240 100% 50%), hsl(300 100% 50%), hsl(360 100% 50%))',\n '--gr-color-picker-track-saturation': `linear-gradient(to right, hsl(${Math.round(h)} 0% ${Math.round(l)}%), hsl(${Math.round(h)} 100% ${Math.round(l)}%))`,\n '--gr-color-picker-track-lightness': `linear-gradient(to right, hsl(${Math.round(h)} ${Math.round(s)}% 0%), hsl(${Math.round(h)} ${Math.round(s)}% 50%), hsl(${Math.round(h)} ${Math.round(s)}% 100%))`,\n '--gr-color-picker-track-alpha': `linear-gradient(to right, transparent, ${solid})`,\n }\n})\n</script>\n\n<template>\n <div data-gr-color-picker>\n <!-- Значение для нативной формы уходит скрытым полем: интерактивного\n контрола внутри виджета быть не должно. -->\n <input v-if=\"name\" type=\"hidden\" :name=\"name\" :value=\"hexValue\">\n\n <span v-if=\"stateHint\" :id=\"stateHintId\" class=\"sr-only\">{{ stateHint }}</span>\n\n <GrPopover\n v-model:open=\"panelOpen\"\n :size=\"resolvedSize\"\n :placement=\"placement\"\n :disabled=\"isDisabled\"\n :aria-label=\"t('gr.colorPicker.panelLabel', 'Color picker')\"\n :close-on-content-click=\"false\"\n >\n <template #trigger=\"{ triggerProps }\">\n <button\n :id=\"fieldId\"\n ref=\"triggerEl\"\n v-bind=\"triggerProps\"\n data-gr-color-picker-trigger\n type=\"button\"\n :class=\"triggerClass\"\n :aria-label=\"ariaLabel\"\n :aria-describedby=\"describedBy\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n @focus=\"emit('focus', $event)\"\n @blur=\"emit('blur', $event)\"\n >\n <span\n data-gr-color-picker-swatch\n aria-hidden=\"true\"\n :class=\"[swatchBaseClass, swatchSizeClass, checkerClass]\"\n >\n <span :class=\"swatchFillClass\" :style=\"{ background: cssColor }\" />\n </span>\n\n <span :class=\"triggerValueClass\">{{ hexValue }}</span>\n </button>\n </template>\n\n <template #content>\n <div data-gr-color-picker-panel :class=\"panelClass\" :style=\"trackVars\">\n <div\n data-gr-color-picker-preview\n aria-hidden=\"true\"\n :class=\"[previewClass, checkerClass]\"\n >\n <span :class=\"swatchFillClass\" :style=\"{ background: cssColor }\" />\n </div>\n\n <div data-gr-color-picker-channel=\"hue\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">H</span>\n <GrSlider\n v-model=\"hue\"\n :min=\"0\"\n :max=\"359\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"degrees\"\n :aria-label=\"t('gr.colorPicker.hue', 'Hue')\"\n />\n <span :class=\"rowValueClass\">{{ degrees(hue) }}</span>\n </div>\n\n <div data-gr-color-picker-channel=\"saturation\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">S</span>\n <GrSlider\n v-model=\"saturation\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"percent\"\n :aria-label=\"t('gr.colorPicker.saturation', 'Saturation')\"\n />\n <span :class=\"rowValueClass\">{{ percent(saturation) }}</span>\n </div>\n\n <div data-gr-color-picker-channel=\"lightness\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">L</span>\n <GrSlider\n v-model=\"lightness\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"percent\"\n :aria-label=\"t('gr.colorPicker.lightness', 'Lightness')\"\n />\n <span :class=\"rowValueClass\">{{ percent(lightness) }}</span>\n </div>\n\n <div v-if=\"alpha\" data-gr-color-picker-channel=\"alpha\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">A</span>\n <GrSlider\n v-model=\"opacity\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"percent\"\n :aria-label=\"t('gr.colorPicker.opacity', 'Opacity')\"\n />\n <span :class=\"rowValueClass\">{{ percent(opacity) }}</span>\n </div>\n\n <GrInput\n v-model=\"hexDraft\"\n data-gr-color-picker-hex\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :aria-label=\"t('gr.colorPicker.hexLabel', 'Hex value')\"\n @change=\"commitHex\"\n @keydown.enter=\"commitHex\"\n />\n\n <div\n v-if=\"presetList.length\"\n data-gr-color-picker-presets\n :class=\"presetsGridClass\"\n role=\"group\"\n :aria-label=\"t('gr.colorPicker.presetsLabel', 'Color presets')\"\n >\n <button\n v-for=\"preset in presetList\"\n :key=\"preset\"\n data-gr-color-picker-preset\n type=\"button\"\n :class=\"[grColorPickerPresetClass(isPresetSelected(preset)), checkerClass]\"\n :aria-label=\"t('gr.colorPicker.swatchLabel', 'Color {color}', { color: preset })\"\n :aria-pressed=\"isPresetSelected(preset) ? 'true' : 'false'\"\n :disabled=\"isLocked\"\n @click=\"selectPreset(preset)\"\n >\n <span :class=\"swatchFillClass\" :style=\"{ background: preset }\" />\n </button>\n </div>\n </div>\n </template>\n </GrPopover>\n </div>\n</template>\n\n<style>\n/*\n * Шахматка под прозрачным цветом. Двумя `conic-gradient` — в утилите такое не\n * выражается, а без подложки прозрачный образец сливается с панелью.\n */\n.gr-color-picker-checker {\n background-image:\n conic-gradient(\n from 90deg,\n var(--gr-color-picker-checker-light, var(--gr-bg)) 0 25%,\n var(--gr-color-picker-checker-dark, var(--gr-muted)) 0 50%,\n var(--gr-color-picker-checker-light, var(--gr-bg)) 0 75%,\n var(--gr-color-picker-checker-dark, var(--gr-muted)) 0\n );\n background-size:\n calc(var(--gr-color-picker-checker-size, 6px) * 2)\n calc(var(--gr-color-picker-checker-size, 6px) * 2);\n /* Рамка образца полупрозрачна, и без обрезки шахматка просвечивает сквозь неё. */\n background-clip: padding-box;\n}\n\n/*\n * Дорожки каналов. Красим `[data-gr-slider-track]` потому, что хук\n * `--gr-slider-rail` уходит в `background-color`, а градиент там не работает.\n * Сами градиенты компонент подаёт переменными — CSS остаётся статичной.\n */\n[data-gr-color-picker-channel] [data-gr-slider-track] {\n border-radius: var(--gr-radius-full);\n}\n\n[data-gr-color-picker-channel='hue'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-hue);\n}\n\n[data-gr-color-picker-channel='saturation'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-saturation);\n}\n\n[data-gr-color-picker-channel='lightness'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-lightness);\n}\n\n[data-gr-color-picker-channel='alpha'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-alpha);\n}\n\n/* Заливка канала прозрачна: шкалу показывает сама дорожка. */\n[data-gr-color-picker-channel] [data-gr-slider-fill] {\n background-color: transparent;\n}\n</style>\n","<script setup lang=\"ts\">\n/**\n * GrColorPicker — поле выбора цвета: образец с текущим значением, панель с\n * оттенком, насыщенностью, светлотой и прозрачностью, поле hex и палитра.\n *\n * Прямой сценарий — настройки темы и брендирования, поэтому модель хранит hex:\n * в такой форме цвет лежит в токенах и его понимает CSS.\n *\n * A11y: каждый канал — отдельный `GrSlider`, то есть настоящий `role=\"slider\"`\n * с полной клавиатурой и `aria-valuetext`. Двумерная область saturation/value\n * не берётся намеренно: это свой виджет со своей клавиатурой по двум осям, и\n * доступность там пришлось бы собирать с нуля.\n */\nimport { computed, nextTick, ref, useId, watch } from 'vue'\n\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport GrInput from '../GrInput/GrInput.vue'\nimport GrPopover from '../GrPopover/GrPopover.vue'\nimport GrSlider from '../GrSlider/GrSlider.vue'\n\nimport { formatHexColor, hslaToCss, normalizeHsla, parseHexColor, type GrHsla } from './color'\nimport {\n checkerClass,\n grColorPickerPanelClass,\n grColorPickerPresetClass,\n grColorPickerTriggerClass,\n presetsGridClass,\n previewClass,\n rowClass,\n rowLabelClass,\n rowValueClass,\n swatchBaseClass,\n swatchFillClass,\n triggerSwatchSizeBySize,\n triggerValueClass,\n type GrColorPickerSize,\n} from './grColorPickerStyles'\n\nexport type { GrColorPickerSize } from './grColorPickerStyles'\n\nexport interface GrColorPickerProps {\n /** Цвет в hex: `#RRGGBB`, а при `alpha` — `#RRGGBBAA`. Мусор не роняет компонент. */\n modelValue: string\n /** Четвёртый слайдер и восьмизначная форма hex. */\n alpha?: boolean\n /** Палитра быстрого выбора. Пусто — блок не рендерится. */\n presets?: string[]\n size?: GrColorPickerSize\n /** Контролируемое состояние панели (`v-model:open`). */\n open?: boolean\n /** Сторона, с которой раскрывается панель. */\n placement?: 'bottom-start' | 'bottom-end' | 'top-start' | 'top-end'\n /** Имя для нативной формы: значение уходит скрытым полем. */\n name?: string\n disabled?: boolean\n /** Только для чтения: цвет видно, панель открывается, но значение не меняется. */\n readonly?: boolean\n invalid?: boolean\n required?: boolean\n ariaLabel?: string\n}\n\nexport interface GrColorPickerEmits {\n (e: 'update:modelValue', value: string): void\n (e: 'change', value: string): void\n (e: 'update:open', value: boolean): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(defineProps<GrColorPickerProps>(), {\n alpha: false,\n presets: () => [],\n // Дефолт живёт в резолвере: Vue подставил бы свой раньше, чем компонент\n // заглянет в `GrConfigProvider`.\n size: undefined,\n open: undefined,\n placement: 'bottom-start',\n name: undefined,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n ariaLabel: undefined,\n})\n\nconst emit = defineEmits<GrColorPickerEmits>()\n\nconst { t } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrColorPicker' })\n\nconst field = useGrFormFieldContext()\nconst fieldId = computed(() => field?.id.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n // «Ввод не принимается» — `disabled` или `readonly`: значение видно, но не меняется.\n locked: isLocked,\n} = useGrFormControl(() => props)\n\n/**\n * `aria-required` и `aria-readonly` роль `button` не поддерживает — axe роняет\n * это как critical `aria-allowed-attr`. Состояния уходят в описание триггера:\n * иначе они пропали бы для диктора совсем (маркер `*` у `GrFormField`\n * декоративен и скрыт).\n */\nconst stateHintId = useId()\nconst stateHint = computed(() => [\n isRequired.value ? t('gr.form.required', 'This field is required') : undefined,\n isReadonly.value ? t('gr.form.readonly', 'Read only') : undefined,\n].filter(Boolean).join('. ') || undefined)\n\nconst describedBy = computed(() => [\n field?.describedById.value,\n stateHint.value ? stateHintId : undefined,\n].filter(Boolean).join(' ') || undefined)\n\nconst triggerEl = ref<HTMLElement | null>(null)\n\nfunction focus(): void {\n triggerEl.value?.focus()\n}\n\nfunction blur(): void {\n triggerEl.value?.blur()\n}\n\ndefineExpose({ focus, blur })\n\nconst FALLBACK: GrHsla = { h: 0, s: 0, l: 0, a: 1 }\n\n/**\n * Рабочее состояние панели.\n *\n * Держать его отдельно от модели обязательно: hex — проекция с потерями. У\n * серого нет оттенка, и без собственного состояния бегунок оттенка прыгал бы\n * обратно на 0° при каждом движении.\n */\nconst state = ref<GrHsla>(parseHexColor(props.modelValue) ?? FALLBACK)\n\n/** Последнее отданное значение: вернувшись в проп, оно не должно сбрасывать состояние. */\nlet lastEmitted = ''\n\nwatch(() => props.modelValue, (next) => {\n if (next === lastEmitted) return\n\n const parsed = parseHexColor(next)\n if (parsed) state.value = parsed\n})\n\nconst cssColor = computed(() => hslaToCss(state.value))\nconst hexValue = computed(() => formatHexColor(state.value, props.alpha))\n\nfunction commit(next: GrHsla): void {\n if (isLocked.value) return\n\n state.value = normalizeHsla(next)\n\n const hex = formatHexColor(state.value, props.alpha)\n lastEmitted = hex\n emit('update:modelValue', hex)\n emit('change', hex)\n}\n\n/** Канал панели: слайдер читает состояние и коммитит новое значение целиком. */\nfunction channel(key: 'h' | 's' | 'l' | 'a', scale = 1) {\n return computed<number>({\n get: () => Math.round(state.value[key] * scale),\n set: value => commit({ ...state.value, [key]: value / scale }),\n })\n}\n\nconst hue = channel('h')\nconst saturation = channel('s')\nconst lightness = channel('l')\n// Альфа хранится долей, а слайдер ходит по процентам — иначе шаг был бы 0.01.\nconst opacity = channel('a', 100)\n\nconst degrees = (value: number): string => `${value}°`\nconst percent = (value: number): string => `${value}%`\n\n/** Текст поля hex редактируется свободно и коммитится только валидным. */\nconst hexDraft = ref(hexValue.value)\n\nwatch(hexValue, (next) => {\n hexDraft.value = next\n})\n\nfunction commitHex(): void {\n const parsed = parseHexColor(hexDraft.value)\n\n // Мусор откатывается к текущему значению: держать в поле невалидный текст\n // после коммита — значит показывать цвет, которого нет.\n //\n // Откат идёт следующим тиком намеренно: вернув значение синхронно, мы отдали\n // бы Vue то же самое, что он уже отрисовал, — патча не случилось бы, и в поле\n // остался бы набранный мусор.\n if (!parsed) {\n void nextTick(() => { hexDraft.value = hexValue.value })\n return\n }\n\n commit(props.alpha ? parsed : { ...parsed, a: state.value.a })\n}\n\nconst isOpen = ref(false)\nconst panelOpen = computed({\n get: () => props.open ?? isOpen.value,\n set: (value) => {\n isOpen.value = value\n emit('update:open', value)\n },\n})\n\nconst presetList = computed(() => props.presets.filter(preset => parseHexColor(preset) !== null))\n\nfunction isPresetSelected(preset: string): boolean {\n const parsed = parseHexColor(preset)\n return parsed ? formatHexColor(parsed, props.alpha) === hexValue.value : false\n}\n\nfunction selectPreset(preset: string): void {\n const parsed = parseHexColor(preset)\n if (parsed) commit(parsed)\n}\n\nconst triggerClass = computed(() => grColorPickerTriggerClass({\n size: resolvedSize.value,\n disabled: isDisabled.value,\n invalid: isInvalid.value,\n}))\n\nconst panelClass = computed(() => grColorPickerPanelClass(resolvedSize.value))\nconst swatchSizeClass = computed(() => triggerSwatchSizeBySize[resolvedSize.value])\n\n/**\n * Градиенты дорожек. Через `--gr-slider-rail` их не подать: хук уходит в\n * `background-color`, а градиент там не работает — поэтому значения приезжают\n * переменными, а красит дорожки собственный `<style>` компонента.\n *\n * Переменные висят на самой панели, а не на корне: панель уезжает в портал, и\n * наследование от корня до неё не доходит.\n */\nconst trackVars = computed<Record<string, string>>(() => {\n const { h, s, l } = state.value\n const solid = hslaToCss({ h, s, l, a: 1 })\n\n return {\n '--gr-color-picker-track-hue': 'linear-gradient(to right, hsl(0 100% 50%), hsl(60 100% 50%), hsl(120 100% 50%), hsl(180 100% 50%), hsl(240 100% 50%), hsl(300 100% 50%), hsl(360 100% 50%))',\n '--gr-color-picker-track-saturation': `linear-gradient(to right, hsl(${Math.round(h)} 0% ${Math.round(l)}%), hsl(${Math.round(h)} 100% ${Math.round(l)}%))`,\n '--gr-color-picker-track-lightness': `linear-gradient(to right, hsl(${Math.round(h)} ${Math.round(s)}% 0%), hsl(${Math.round(h)} ${Math.round(s)}% 50%), hsl(${Math.round(h)} ${Math.round(s)}% 100%))`,\n '--gr-color-picker-track-alpha': `linear-gradient(to right, transparent, ${solid})`,\n }\n})\n</script>\n\n<template>\n <div data-gr-color-picker>\n <!-- Значение для нативной формы уходит скрытым полем: интерактивного\n контрола внутри виджета быть не должно. -->\n <input v-if=\"name\" type=\"hidden\" :name=\"name\" :value=\"hexValue\">\n\n <span v-if=\"stateHint\" :id=\"stateHintId\" class=\"sr-only\">{{ stateHint }}</span>\n\n <GrPopover\n v-model:open=\"panelOpen\"\n :size=\"resolvedSize\"\n :placement=\"placement\"\n :disabled=\"isDisabled\"\n :aria-label=\"t('gr.colorPicker.panelLabel', 'Color picker')\"\n :close-on-content-click=\"false\"\n >\n <template #trigger=\"{ triggerProps }\">\n <button\n :id=\"fieldId\"\n ref=\"triggerEl\"\n v-bind=\"triggerProps\"\n data-gr-color-picker-trigger\n type=\"button\"\n :class=\"triggerClass\"\n :aria-label=\"ariaLabel\"\n :aria-describedby=\"describedBy\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n @focus=\"emit('focus', $event)\"\n @blur=\"emit('blur', $event)\"\n >\n <span\n data-gr-color-picker-swatch\n aria-hidden=\"true\"\n :class=\"[swatchBaseClass, swatchSizeClass, checkerClass]\"\n >\n <span :class=\"swatchFillClass\" :style=\"{ background: cssColor }\" />\n </span>\n\n <span :class=\"triggerValueClass\">{{ hexValue }}</span>\n </button>\n </template>\n\n <template #content>\n <div data-gr-color-picker-panel :class=\"panelClass\" :style=\"trackVars\">\n <div\n data-gr-color-picker-preview\n aria-hidden=\"true\"\n :class=\"[previewClass, checkerClass]\"\n >\n <span :class=\"swatchFillClass\" :style=\"{ background: cssColor }\" />\n </div>\n\n <div data-gr-color-picker-channel=\"hue\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">H</span>\n <GrSlider\n v-model=\"hue\"\n :min=\"0\"\n :max=\"359\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"degrees\"\n :aria-label=\"t('gr.colorPicker.hue', 'Hue')\"\n />\n <span :class=\"rowValueClass\">{{ degrees(hue) }}</span>\n </div>\n\n <div data-gr-color-picker-channel=\"saturation\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">S</span>\n <GrSlider\n v-model=\"saturation\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"percent\"\n :aria-label=\"t('gr.colorPicker.saturation', 'Saturation')\"\n />\n <span :class=\"rowValueClass\">{{ percent(saturation) }}</span>\n </div>\n\n <div data-gr-color-picker-channel=\"lightness\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">L</span>\n <GrSlider\n v-model=\"lightness\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"percent\"\n :aria-label=\"t('gr.colorPicker.lightness', 'Lightness')\"\n />\n <span :class=\"rowValueClass\">{{ percent(lightness) }}</span>\n </div>\n\n <div v-if=\"alpha\" data-gr-color-picker-channel=\"alpha\" :class=\"rowClass\">\n <span :class=\"rowLabelClass\" aria-hidden=\"true\">A</span>\n <GrSlider\n v-model=\"opacity\"\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :format-tooltip=\"percent\"\n :aria-label=\"t('gr.colorPicker.opacity', 'Opacity')\"\n />\n <span :class=\"rowValueClass\">{{ percent(opacity) }}</span>\n </div>\n\n <GrInput\n v-model=\"hexDraft\"\n data-gr-color-picker-hex\n :size=\"resolvedSize\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :aria-label=\"t('gr.colorPicker.hexLabel', 'Hex value')\"\n @change=\"commitHex\"\n @keydown.enter=\"commitHex\"\n />\n\n <div\n v-if=\"presetList.length\"\n data-gr-color-picker-presets\n :class=\"presetsGridClass\"\n role=\"group\"\n :aria-label=\"t('gr.colorPicker.presetsLabel', 'Color presets')\"\n >\n <button\n v-for=\"preset in presetList\"\n :key=\"preset\"\n data-gr-color-picker-preset\n type=\"button\"\n :class=\"[grColorPickerPresetClass(isPresetSelected(preset)), checkerClass]\"\n :aria-label=\"t('gr.colorPicker.swatchLabel', 'Color {color}', { color: preset })\"\n :aria-pressed=\"isPresetSelected(preset) ? 'true' : 'false'\"\n :disabled=\"isLocked\"\n @click=\"selectPreset(preset)\"\n >\n <span :class=\"swatchFillClass\" :style=\"{ background: preset }\" />\n </button>\n </div>\n </div>\n </template>\n </GrPopover>\n </div>\n</template>\n\n<style>\n/*\n * Шахматка под прозрачным цветом. Двумя `conic-gradient` — в утилите такое не\n * выражается, а без подложки прозрачный образец сливается с панелью.\n */\n.gr-color-picker-checker {\n background-image:\n conic-gradient(\n from 90deg,\n var(--gr-color-picker-checker-light, var(--gr-bg)) 0 25%,\n var(--gr-color-picker-checker-dark, var(--gr-muted)) 0 50%,\n var(--gr-color-picker-checker-light, var(--gr-bg)) 0 75%,\n var(--gr-color-picker-checker-dark, var(--gr-muted)) 0\n );\n background-size:\n calc(var(--gr-color-picker-checker-size, 6px) * 2)\n calc(var(--gr-color-picker-checker-size, 6px) * 2);\n /* Рамка образца полупрозрачна, и без обрезки шахматка просвечивает сквозь неё. */\n background-clip: padding-box;\n}\n\n/*\n * Дорожки каналов. Красим `[data-gr-slider-track]` потому, что хук\n * `--gr-slider-rail` уходит в `background-color`, а градиент там не работает.\n * Сами градиенты компонент подаёт переменными — CSS остаётся статичной.\n */\n[data-gr-color-picker-channel] [data-gr-slider-track] {\n border-radius: var(--gr-radius-full);\n}\n\n[data-gr-color-picker-channel='hue'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-hue);\n}\n\n[data-gr-color-picker-channel='saturation'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-saturation);\n}\n\n[data-gr-color-picker-channel='lightness'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-lightness);\n}\n\n[data-gr-color-picker-channel='alpha'] [data-gr-slider-track] {\n background-image: var(--gr-color-picker-track-alpha);\n}\n\n/* Заливка канала прозрачна: шкалу показывает сама дорожка. */\n[data-gr-color-picker-channel] [data-gr-slider-fill] {\n background-color: transparent;\n}\n</style>\n"],"mappings":";;;;;;;;;;AAwBA,IAAM,cAAc;AAEpB,SAAS,MAAM,OAAe,KAAa,KAAqB;CAC9D,OAAO,KAAK,IAAI,KAAK,IAAI,OAAO,GAAG,GAAG,GAAG;AAC3C;;;;;;;;;AAUA,SAAS,aAAa,KAAqB;CACzC,IAAI,CAAC,OAAO,SAAS,GAAG,GAAG,OAAO;CAClC,QAAS,MAAM,MAAO,OAAO;AAC/B;;AAGA,SAAS,gBAAgB,KAA4B;CACnD,IAAI,IAAI,WAAW,KAAK,IAAI,WAAW,GACrC,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC,KAAI,SAAQ,OAAO,IAAI,CAAC,CAAC,KAAK,EAAE;CAElD,IAAI,IAAI,WAAW,KAAK,IAAI,WAAW,GAAG,OAAO;CAEjD,OAAO;AACT;AAEA,SAAS,SAAS,EAAE,GAAG,GAAG,KAA+C;CACvE,MAAM,MAAM,IAAI;CAChB,MAAM,QAAQ,IAAI;CAClB,MAAM,OAAO,IAAI;CAEjB,MAAM,MAAM,KAAK,IAAI,KAAK,OAAO,IAAI;CACrC,MAAM,MAAM,KAAK,IAAI,KAAK,OAAO,IAAI;CACrC,MAAM,QAAQ,MAAM;CACpB,MAAM,aAAa,MAAM,OAAO;CAEhC,IAAI,UAAU,GAAG,OAAO;EAAE,GAAG;EAAG,GAAG;EAAG,GAAG,YAAY;CAAI;CAEzD,MAAM,aAAa,SAAS,IAAI,KAAK,IAAI,IAAI,YAAY,CAAC;CAE1D,IAAI;CACJ,IAAI,QAAQ,KAAK,OAAQ,QAAQ,QAAQ,QAAS;MAC7C,IAAI,QAAQ,OAAO,OAAO,OAAO,OAAO,QAAQ;MAChD,OAAO,MAAM,SAAS,QAAQ;CAEnC,OAAO;EAAE,GAAG,aAAa,MAAM,EAAE;EAAG,GAAG,aAAa;EAAK,GAAG,YAAY;CAAI;AAC9E;AAEA,SAAS,SAAS,EAAE,GAAG,GAAG,KAA6B;CACrD,MAAM,aAAa,MAAM,GAAG,GAAG,GAAG,IAAI;CACtC,MAAM,YAAY,MAAM,GAAG,GAAG,GAAG,IAAI;CACrC,MAAM,MAAM,aAAa,CAAC;CAE1B,MAAM,UAAU,IAAI,KAAK,IAAI,IAAI,YAAY,CAAC,KAAK;CACnD,MAAM,SAAS,UAAU,IAAI,KAAK,IAAM,MAAM,KAAM,IAAK,CAAC;CAC1D,MAAM,QAAQ,YAAY,SAAS;CAEnC,MAAM,CAAC,KAAK,OAAO,eAAyC;EAC1D,IAAI,MAAM,IAAI,OAAO;GAAC;GAAQ;GAAQ;EAAC;EACvC,IAAI,MAAM,KAAK,OAAO;GAAC;GAAQ;GAAQ;EAAC;EACxC,IAAI,MAAM,KAAK,OAAO;GAAC;GAAG;GAAQ;EAAM;EACxC,IAAI,MAAM,KAAK,OAAO;GAAC;GAAG;GAAQ;EAAM;EACxC,IAAI,MAAM,KAAK,OAAO;GAAC;GAAQ;GAAG;EAAM;EACxC,OAAO;GAAC;GAAQ;GAAG;EAAM;CAC3B,EAAA,CAAG;CAEH,OAAO;EACL,GAAG,KAAK,OAAO,MAAM,SAAS,GAAG;EACjC,GAAG,KAAK,OAAO,QAAQ,SAAS,GAAG;EACnC,GAAG,KAAK,OAAO,OAAO,SAAS,GAAG;CACpC;AACF;AAEA,SAAS,UAAU,OAAuB;CACxC,OAAO,MAAM,KAAK,MAAM,KAAK,GAAG,GAAG,GAAG,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG;AACtE;;;;;;;;AASA,SAAgB,cAAc,OAA+B;CAC3D,IAAI,OAAO,UAAU,UAAU,OAAO;CAEtC,MAAM,QAAQ,YAAY,KAAK,MAAM,KAAK,CAAC;CAC3C,IAAI,CAAC,OAAO,OAAO;CAEnB,MAAM,MAAM,gBAAgB,MAAM,EAAE;CACpC,IAAI,CAAC,KAAK,OAAO;CAEjB,MAAM,MAAW;EACf,GAAG,OAAO,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE;EACtC,GAAG,OAAO,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE;EACtC,GAAG,OAAO,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE;CACxC;CAEA,MAAM,QAAQ,IAAI,WAAW,IAAI,OAAO,SAAS,IAAI,MAAM,GAAG,CAAC,GAAG,EAAE,IAAI,MAAM;CAE9E,OAAO;EAAE,GAAG,SAAS,GAAG;EAAG,GAAG;CAAM;AACtC;;;;;;AAOA,SAAgB,eAAe,OAAe,YAAY,OAAe;CACvE,MAAM,EAAE,GAAG,GAAG,MAAM,SAAS,KAAK;CAClC,MAAM,OAAO,IAAI,UAAU,CAAC,IAAI,UAAU,CAAC,IAAI,UAAU,CAAC;CAE1D,IAAI,CAAC,WAAW,OAAO;CAEvB,OAAO,GAAG,OAAO,UAAU,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,GAAG;AACvD;;AAGA,SAAS,KAAK,OAAuB;CACnC,OAAO,KAAK,MAAM,QAAQ,GAAG,IAAI;AACnC;;AAGA,SAAgB,UAAU,OAAuB;CAK/C,OAAO,OAJG,KAAK,aAAa,MAAM,CAAC,CAIrB,EAAE,GAHN,KAAK,MAAM,MAAM,GAAG,GAAG,GAAG,CAGjB,EAAE,IAFX,KAAK,MAAM,MAAM,GAAG,GAAG,GAAG,CAEX,EAAE,MAAM,KAAK,MAAM,MAAM,GAAG,GAAG,CAAC,CAAC,EAAE;AAC9D;;AAGA,SAAgB,cAAc,OAAuB;CACnD,OAAO;EACL,GAAG,KAAK,MAAM,aAAa,MAAM,CAAC,CAAC,IAAI;EACvC,GAAG,MAAM,KAAK,MAAM,MAAM,CAAC,GAAG,GAAG,GAAG;EACpC,GAAG,MAAM,KAAK,MAAM,MAAM,CAAC,GAAG,GAAG,GAAG;EACpC,GAAG,MAAM,MAAM,GAAG,GAAG,CAAC;CACxB;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EC9FA,MAAM,QAAQ;EAgBd,MAAM,OAAO;EAEb,MAAM,EAAE,MAAM,2BAA2B;EAEzC,MAAM,eAAe,yBAAyB,MAAM,MAAM,EAAE,WAAW,gBAAgB,CAAC;EAExF,MAAM,QAAQ,sBAAsB;EACpC,MAAM,UAAU,eAAe,OAAO,GAAG,KAAK;EAC9C,MAAM,EACJ,UAAU,YACV,SAAS,WACT,UAAU,YACV,UAAU,YAEV,QAAQ,aACN,uBAAuB,KAAK;;;;;;;EAQhC,MAAM,cAAc,MAAM;EAC1B,MAAM,YAAY,eAAe,CAC/B,WAAW,QAAQ,EAAE,oBAAoB,wBAAwB,IAAI,KAAA,GACrE,WAAW,QAAQ,EAAE,oBAAoB,WAAW,IAAI,KAAA,CAC1D,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,IAAI,KAAK,KAAA,CAAS;EAEzC,MAAM,cAAc,eAAe,CACjC,OAAO,cAAc,OACrB,UAAU,QAAQ,cAAc,KAAA,CAClC,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG,KAAK,KAAA,CAAS;EAExC,MAAM,YAAY,IAAwB,IAAI;EAE9C,SAAS,QAAc;GACrB,UAAU,OAAO,MAAM;EACzB;EAEA,SAAS,OAAa;GACpB,UAAU,OAAO,KAAK;EACxB;EAEA,SAAa;GAAE;GAAO;EAAK,CAAC;;;;;;;;EAW5B,MAAM,QAAQ,IAAY,cAAc,MAAM,UAAU,KAAK;GATlC,GAAG;GAAG,GAAG;GAAG,GAAG;GAAG,GAAG;EASa,CAAQ;;EAGrE,IAAI,cAAc;EAElB,YAAY,MAAM,aAAa,SAAS;GACtC,IAAI,SAAS,aAAa;GAE1B,MAAM,SAAS,cAAc,IAAI;GACjC,IAAI,QAAQ,MAAM,QAAQ;EAC5B,CAAC;EAED,MAAM,WAAW,eAAe,UAAU,MAAM,KAAK,CAAC;EACtD,MAAM,WAAW,eAAe,eAAe,MAAM,OAAO,MAAM,KAAK,CAAC;EAExE,SAAS,OAAO,MAAoB;GAClC,IAAI,SAAS,OAAO;GAEpB,MAAM,QAAQ,cAAc,IAAI;GAEhC,MAAM,MAAM,eAAe,MAAM,OAAO,MAAM,KAAK;GACnD,cAAc;GACd,KAAK,qBAAqB,GAAG;GAC7B,KAAK,UAAU,GAAG;EACpB;;EAGA,SAAS,QAAQ,KAA4B,QAAQ,GAAG;GACtD,OAAO,SAAiB;IACtB,WAAW,KAAK,MAAM,MAAM,MAAM,OAAO,KAAK;IAC9C,MAAK,UAAS,OAAO;KAAE,GAAG,MAAM;MAAQ,MAAM,QAAQ;IAAM,CAAC;GAC/D,CAAC;EACH;EAEA,MAAM,MAAM,QAAQ,GAAG;EACvB,MAAM,aAAa,QAAQ,GAAG;EAC9B,MAAM,YAAY,QAAQ,GAAG;EAE7B,MAAM,UAAU,QAAQ,KAAK,GAAG;EAEhC,MAAM,WAAW,UAA0B,GAAG,MAAM;EACpD,MAAM,WAAW,UAA0B,GAAG,MAAM;;EAGpD,MAAM,WAAW,IAAI,SAAS,KAAK;EAEnC,MAAM,WAAW,SAAS;GACxB,SAAS,QAAQ;EACnB,CAAC;EAED,SAAS,YAAkB;GACzB,MAAM,SAAS,cAAc,SAAS,KAAK;GAQ3C,IAAI,CAAC,QAAQ;IACX,eAAoB;KAAE,SAAS,QAAQ,SAAS;IAAM,CAAC;IACvD;GACF;GAEA,OAAO,MAAM,QAAQ,SAAS;IAAE,GAAG;IAAQ,GAAG,MAAM,MAAM;GAAE,CAAC;EAC/D;EAEA,MAAM,SAAS,IAAI,KAAK;EACxB,MAAM,YAAY,SAAS;GACzB,WAAW,MAAM,QAAQ,OAAO;GAChC,MAAM,UAAU;IACd,OAAO,QAAQ;IACf,KAAK,eAAe,KAAK;GAC3B;EACF,CAAC;EAED,MAAM,aAAa,eAAe,MAAM,QAAQ,QAAO,WAAU,cAAc,MAAM,MAAM,IAAI,CAAC;EAEhG,SAAS,iBAAiB,QAAyB;GACjD,MAAM,SAAS,cAAc,MAAM;GACnC,OAAO,SAAS,eAAe,QAAQ,MAAM,KAAK,MAAM,SAAS,QAAQ;EAC3E;EAEA,SAAS,aAAa,QAAsB;GAC1C,MAAM,SAAS,cAAc,MAAM;GACnC,IAAI,QAAQ,OAAO,MAAM;EAC3B;EAEA,MAAM,eAAe,eAAe,0BAA0B;GAC5D,MAAM,aAAa;GACnB,UAAU,WAAW;GACrB,SAAS,UAAU;EACrB,CAAC,CAAC;EAEF,MAAM,aAAa,eAAe,wBAAwB,aAAa,KAAK,CAAC;EAC7E,MAAM,kBAAkB,eAAe,wBAAwB,aAAa,MAAM;;;;;;;;;EAUlF,MAAM,YAAY,eAAuC;GACvD,MAAM,EAAE,GAAG,GAAG,MAAM,MAAM;GAC1B,MAAM,QAAQ,UAAU;IAAE;IAAG;IAAG;IAAG,GAAG;GAAE,CAAC;GAEzC,OAAO;IACL,+BAA+B;IAC/B,sCAAsC,iCAAiC,KAAK,MAAM,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC,EAAE,UAAU,KAAK,MAAM,CAAC,EAAE,QAAQ,KAAK,MAAM,CAAC,EAAE;IACvJ,qCAAqC,iCAAiC,KAAK,MAAM,CAAC,EAAE,GAAG,KAAK,MAAM,CAAC,EAAE,aAAa,KAAK,MAAM,CAAC,EAAE,GAAG,KAAK,MAAM,CAAC,EAAE,cAAc,KAAK,MAAM,CAAC,EAAE,GAAG,KAAK,MAAM,CAAC,EAAE;IAC9L,iCAAiC,0CAA0C,MAAM;GACnF;EACF,CAAC;;uBAIC,mBA4IM,OA5IN,YA4IM;IAzIS,QAAA,QAAA,UAAA,GAAb,mBAAgE,SAAA;;KAA7C,MAAK;KAAU,MAAM,QAAA;KAAO,OAAO,SAAA;;IAE1C,UAAA,SAAA,UAAA,GAAZ,mBAA+E,QAAA;;KAAvD,IAAI,MAAA,WAAA;KAAa,OAAM;uBAAa,UAAA,KAAS,GAAA,GAAA,UAAA,KAAA,mBAAA,IAAA,IAAA;IAErE,YAoIY,mBAAA;KAnIF,MAAM,UAAA;sEAAS,QAAA;KACtB,MAAM,MAAA,YAAA;KACN,WAAW,QAAA;KACX,UAAU,MAAA,UAAA;KACV,cAAY,MAAA,CAAA,CAAC,CAAA,6BAAA,cAAA;KACb,0BAAwB;;KAEd,SAAO,SAuBP,EAvBW,mBAAY,CAChC,mBAsBS,UAtBT,WAsBS;MArBN,IAAI,QAAA;eACD;MAAJ,KAAI;QACI,cAAY;MACpB,gCAAA;MACA,MAAK;MACJ,OAAO,aAAA;MACP,cAAY,QAAA;MACZ,oBAAkB,YAAA;MAClB,gBAAc,MAAA,SAAA,IAAS,SAAY,KAAA;MACnC,SAAK,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,SAAU,MAAM;MAC3B,QAAI,OAAA,OAAA,OAAA,MAAA,WAAE,KAAI,QAAS,MAAM;UAE1B,mBAMO,QAAA;MALL,+BAAA;MACA,eAAY;MACX,OAAK,eAAA;OAAG,MAAA,eAAA;OAAiB,gBAAA;OAAiB,MAAA,YAAA;MAAY,CAAA;SAEvD,mBAAmE,QAAA;MAA5D,OAAK,eAAE,MAAA,eAAA,CAAe;MAAG,OAAK,eAAA,EAAA,YAAgB,SAAA,MAAQ,CAAA;uBAG/D,mBAAsD,QAAA,EAA/C,OAAK,eAAE,MAAA,iBAAA,CAAiB,EAAA,GAAA,gBAAK,SAAA,KAAQ,GAAA,CAAA,CAAA,GAAA,IAAA,UAAA,CAAA,CAAA;KAIrC,SAAO,cAgGV,CA/FN,mBA+FM,OAAA;MA/FD,8BAAA;MAA4B,OAAK,eAAE,WAAA,KAAU;MAAG,OAAK,eAAE,UAAA,KAAS;;MACnE,mBAMM,OAAA;OALJ,gCAAA;OACA,eAAY;OACX,OAAK,eAAA,CAAG,MAAA,YAAA,GAAc,MAAA,YAAA,CAAY,CAAA;UAEnC,mBAAmE,QAAA;OAA5D,OAAK,eAAE,MAAA,eAAA,CAAe;OAAG,OAAK,eAAA,EAAA,YAAgB,SAAA,MAAQ,CAAA;;MAG/D,mBAaM,OAAA;OAbD,gCAA6B;OAAO,OAAK,eAAE,MAAA,QAAA,CAAQ;;OACtD,mBAAwD,QAAA;QAAjD,OAAK,eAAE,MAAA,aAAA,CAAa;QAAE,eAAY;UAAO,KAAC,CAAA;OACjD,YASE,kBAAA;oBARS,MAAA,GAAA;sFAAG,QAAA,SAAA;QACX,KAAK;QACL,KAAK;QACL,MAAM,MAAA,YAAA;QACN,UAAU,MAAA,UAAA;QACV,UAAU,MAAA,UAAA;QACV,kBAAgB;QAChB,cAAY,MAAA,CAAA,CAAC,CAAA,sBAAA,KAAA;;;;;;;;OAEhB,mBAAsD,QAAA,EAA/C,OAAK,eAAE,MAAA,aAAA,CAAa,EAAA,GAAA,gBAAK,QAAQ,MAAA,GAAA,CAAG,CAAA,GAAA,CAAA;;MAG7C,mBAWM,OAAA;OAXD,gCAA6B;OAAc,OAAK,eAAE,MAAA,QAAA,CAAQ;;OAC7D,mBAAwD,QAAA;QAAjD,OAAK,eAAE,MAAA,aAAA,CAAa;QAAE,eAAY;UAAO,KAAC,CAAA;OACjD,YAOE,kBAAA;oBANS,MAAA,UAAA;oGAAU,QAAA,SAAA;QAClB,MAAM,MAAA,YAAA;QACN,UAAU,MAAA,UAAA;QACV,UAAU,MAAA,UAAA;QACV,kBAAgB;QAChB,cAAY,MAAA,CAAA,CAAC,CAAA,6BAAA,YAAA;;;;;;;;OAEhB,mBAA6D,QAAA,EAAtD,OAAK,eAAE,MAAA,aAAA,CAAa,EAAA,GAAA,gBAAK,QAAQ,MAAA,UAAA,CAAU,CAAA,GAAA,CAAA;;MAGpD,mBAWM,OAAA;OAXD,gCAA6B;OAAa,OAAK,eAAE,MAAA,QAAA,CAAQ;;OAC5D,mBAAwD,QAAA;QAAjD,OAAK,eAAE,MAAA,aAAA,CAAa;QAAE,eAAY;UAAO,KAAC,CAAA;OACjD,YAOE,kBAAA;oBANS,MAAA,SAAA;kGAAS,QAAA,SAAA;QACjB,MAAM,MAAA,YAAA;QACN,UAAU,MAAA,UAAA;QACV,UAAU,MAAA,UAAA;QACV,kBAAgB;QAChB,cAAY,MAAA,CAAA,CAAC,CAAA,4BAAA,WAAA;;;;;;;;OAEhB,mBAA4D,QAAA,EAArD,OAAK,eAAE,MAAA,aAAA,CAAa,EAAA,GAAA,gBAAK,QAAQ,MAAA,SAAA,CAAS,CAAA,GAAA,CAAA;;MAGxC,QAAA,SAAA,UAAA,GAAX,mBAWM,OAAA;;OAXY,gCAA6B;OAAS,OAAK,eAAE,MAAA,QAAA,CAAQ;;OACrE,mBAAwD,QAAA;QAAjD,OAAK,eAAE,MAAA,aAAA,CAAa;QAAE,eAAY;UAAO,KAAC,CAAA;OACjD,YAOE,kBAAA;oBANS,MAAA,OAAA;8FAAO,QAAA,SAAA;QACf,MAAM,MAAA,YAAA;QACN,UAAU,MAAA,UAAA;QACV,UAAU,MAAA,UAAA;QACV,kBAAgB;QAChB,cAAY,MAAA,CAAA,CAAC,CAAA,0BAAA,SAAA;;;;;;;;OAEhB,mBAA0D,QAAA,EAAnD,OAAK,eAAE,MAAA,aAAA,CAAa,EAAA,GAAA,gBAAK,QAAQ,MAAA,OAAA,CAAO,CAAA,GAAA,CAAA;;MAGjD,YASE,iBAAA;mBARS,SAAA;6EAAQ,QAAA;OACjB,4BAAA;OACC,MAAM,MAAA,YAAA;OACN,UAAU,MAAA,UAAA;OACV,UAAU,MAAA,UAAA;OACV,cAAY,MAAA,CAAA,CAAC,CAAA,2BAAA,WAAA;OACb,UAAQ;OACR,WAAO,SAAQ,WAAS,CAAA,OAAA,CAAA;;;;;;;;MAInB,WAAA,MAAW,UAAA,UAAA,GADnB,mBAoBM,OAAA;;OAlBJ,gCAAA;OACC,OAAK,eAAE,MAAA,gBAAA,CAAgB;OACxB,MAAK;OACJ,cAAY,MAAA,CAAA,CAAC,CAAA,+BAAA,eAAA;4BAEd,mBAYS,UAAA,MAAA,WAXU,WAAA,QAAV,WAAM;2BADf,mBAYS,UAAA;QAVN,KAAK;QACN,+BAAA;QACA,MAAK;QACJ,OAAK,eAAA,CAAG,MAAA,wBAAA,CAAwB,CAAC,iBAAiB,MAAM,CAAA,GAAI,MAAA,YAAA,CAAY,CAAA;QACxE,cAAY,MAAA,CAAA,CAAC,CAAA,8BAAA,iBAAA,EAAA,OAAyD,OAAM,CAAA;QAC5E,gBAAc,iBAAiB,MAAM,IAAA,SAAA;QACrC,UAAU,MAAA,QAAA;QACV,UAAK,WAAE,aAAa,MAAM;WAE3B,mBAAiE,QAAA;QAA1D,OAAK,eAAE,MAAA,eAAA,CAAe;QAAG,OAAK,eAAA,EAAA,YAAgB,OAAM,CAAA"}
@@ -1,3 +1,3 @@
1
- import { t as GrColorPicker_default } from "./chunks/GrColorPicker-CE-xtNP-.js";
1
+ import { t as GrColorPicker_default } from "./chunks/GrColorPicker-C7ifyQ0R.js";
2
2
  import { n as grColorPickerSafelist, t as grColorPickerConfig } from "../../chunks/config-DIDz_E5m.js";
3
3
  export { GrColorPicker_default as GrColorPicker, GrColorPicker_default as default, grColorPickerConfig, grColorPickerSafelist };
@@ -3,7 +3,7 @@ import { i as isEditableTarget } from "../../../chunks/keyboard-DE2yhz_b.js";
3
3
  import { t as useControlledOpen } from "../../../chunks/useControlledOpen-DV3SqLTP.js";
4
4
  import { t as GrPopover_default } from "../../GrPopover/chunks/GrPopover-CofG-Tfe.js";
5
5
  import { t as useMenuItemsFocus } from "../../../chunks/useMenuItemsFocus-B6TldmGf.js";
6
- import { i as isMenuSection, n as GrDropdownMenuEntries_default, r as isMenuAction, t as GrDropdownMenuList_default } from "../../GrDropdownMenu/chunks/GrDropdownMenuList-RW5IBvmu.js";
6
+ import { i as isMenuSection, n as GrDropdownMenuEntries_default, r as isMenuAction, t as GrDropdownMenuList_default } from "../../GrDropdownMenu/chunks/GrDropdownMenuList-B33ZzwfB.js";
7
7
  import { computed, createCommentVNode, createElementBlock, createVNode, defineComponent, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, openBlock, ref, renderSlot, unref, useSlots, watch, withCtx } from "vue";
8
8
  //#region src/components/GrContextMenu/contextMenuAnchor.ts
9
9
  /** Точка курсора — якорь нулевого размера. */
@@ -1 +1 @@
1
- {"version":3,"file":"GrContextMenu-BNQDbAsY.js","names":[],"sources":["../../../../src/components/GrContextMenu/contextMenuAnchor.ts","../../../../src/components/GrContextMenu/GrContextMenu.vue","../../../../src/components/GrContextMenu/GrContextMenu.vue"],"sourcesContent":["import type { GrFloatingAnchorRect } from '../../composables/useFloating'\n\n/** Точка курсора — якорь нулевого размера. */\nexport function anchorFromPointer(event: MouseEvent): GrFloatingAnchorRect {\n return { x: event.clientX, y: event.clientY }\n}\n\n/**\n * Прямоугольник элемента — якорь клавиатурного вызова.\n *\n * Не угол и не точка: меню, вызванное с клавиатуры, принадлежит **строке**, и\n * при нехватке места снизу обязано перевернуться относительно неё целиком.\n */\nexport function anchorFromElement(element: Element): GrFloatingAnchorRect {\n const rect = element.getBoundingClientRect()\n\n return { x: rect.left, y: rect.top, width: rect.width, height: rect.height }\n}\n\n/**\n * `contextmenu` пришёл с клавиатуры, а не от мыши.\n *\n * Браузеры порождают это событие и для `Shift+F10`, но координаты в нём\n * бесполезны: то угол элемента, то нули. Признаков два сразу, потому что по\n * отдельности каждый ошибается — правый клик всегда `button === 2`, а\n * клавиатурный вызов всегда без «числа кликов».\n */\nexport function isKeyboardContextMenu(event: MouseEvent): boolean {\n return event.button !== 2 && event.detail === 0\n}\n\n/**\n * Пользователь просит нативное меню браузера.\n *\n * `Shift`+правый клик — де-факто escape-hatch, в Firefox задокументированный.\n * `ctrlKey` сюда не входит намеренно: на macOS Ctrl+клик **и есть** правый\n * клик, и меню перестало бы открываться у части пользователей.\n */\nexport function wantsNativeMenu(event: MouseEvent): boolean {\n return event.shiftKey\n}\n\n/** Клавиатурный запрос контекстного меню: `Shift+F10` или клавиша `ContextMenu`. */\nexport function isContextMenuKey(event: KeyboardEvent): boolean {\n return event.key === 'ContextMenu' || (event.key === 'F10' && event.shiftKey)\n}\n","<script setup lang=\"ts\">\n/**\n * GrContextMenu — меню по правому клику.\n *\n * Отличие от `GrDropdownMenu` не в содержимом, а в том, откуда меню растёт: у\n * дропдауна есть триггер-элемент, здесь — точка курсора, которой в DOM нет.\n * Поэтому слой берётся у `GrPopover` в якорном режиме, а не собирается заново.\n *\n * A11y: панель — `role=\"menu\"` с доступным именем, пункты приезжают из\n * `GrDropdownMenu` вместе со своими ролями. Меню, открываемое только правым\n * кликом, недоступно с клавиатуры, поэтому обёртка ловит `Shift+F10` и клавишу\n * `ContextMenu` — это условие приёмки, а не удобство.\n */\nimport { computed, nextTick, onBeforeUnmount, ref, useSlots, watch } from 'vue'\n\nimport { useControlledOpen } from '../../composables/internal/useControlledOpen'\nimport { useMenuItemsFocus } from '../../composables/internal/useMenuItemsFocus'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { isEditableTarget } from '../../internal/keyboard'\nimport GrDropdownMenuEntries from '../GrDropdownMenu/GrDropdownMenuEntries.vue'\nimport GrDropdownMenuList from '../GrDropdownMenu/GrDropdownMenuList.vue'\nimport type { GrDropdownMenuAction, GrDropdownMenuEntry } from '../GrDropdownMenu/menuModel'\nimport { isMenuAction, isMenuSection } from '../GrDropdownMenu/menuModel'\nimport GrPopover from '../GrPopover/GrPopover.vue'\nimport type { GrFloatingAnchorRect, UseFloatingPlacement } from '../../composables/useFloating'\n\nimport {\n anchorFromElement,\n anchorFromPointer,\n isContextMenuKey,\n isKeyboardContextMenu,\n wantsNativeMenu,\n} from './contextMenuAnchor'\n\n/** Чем открывается меню. `manual` — только императивно, из кода потребителя. */\nexport type GrContextMenuTrigger = 'contextmenu' | 'manual'\n\n/** Откуда пришло открытие: указатель, клавиатура или вызов из кода. */\nexport type GrContextMenuSource = 'pointer' | 'keyboard' | 'api'\n\nexport interface GrContextMenuOpenContext {\n source: GrContextMenuSource\n /** По чему кликнули или что было в фокусе — отсюда потребитель берёт строку. */\n target: HTMLElement | null\n anchor: GrFloatingAnchorRect\n event: MouseEvent | KeyboardEvent | null\n}\n\nexport interface GrContextMenuProps {\n /** Декларативное меню. Слот `#content` сильнее. */\n items?: GrDropdownMenuEntry[]\n /** Открыто ли меню. Без пропа компонент ведёт состояние сам. */\n open?: boolean\n trigger?: GrContextMenuTrigger\n disabled?: boolean\n placement?: UseFloatingPlacement\n /** Зазор от якоря. Ноль по умолчанию: меню липнет к курсору. */\n offsetPx?: number\n /** Меню из коротких слов не должно быть шириной в слово. */\n minWidth?: number | string\n ariaLabel?: string\n labelledBy?: string\n teleportTo?: string | HTMLElement\n contentClass?: string\n listClass?: string\n dividers?: boolean\n /**\n * Закрывать при прокрутке страницы. Якорь — точка вьюпорта, и панель осталась\n * бы висеть на экране, пока контент под ней уезжает. Так же ведут себя\n * нативные меню.\n */\n closeOnScroll?: boolean\n /** Пропускать `Shift`+правый клик к браузеру. */\n allowNativeMenu?: boolean\n}\n\nexport interface GrContextMenuEmits {\n (e: 'update:open', value: boolean): void\n /**\n * Меню собирается открыться у этой цели. Приходит **до** открытия, чтобы\n * потребитель успел пересобрать `items` под неё: пункты для папки и для файла\n * разные, а узнать цель иначе неоткуда. Пустая модель после этого просто не\n * откроется — отдельный способ отменить открытие не нужен.\n */\n (e: 'beforeOpen', context: GrContextMenuOpenContext): void\n (e: 'select', item: GrDropdownMenuAction): void\n}\n\nconst props = withDefaults(defineProps<GrContextMenuProps>(), {\n items: undefined,\n open: undefined,\n trigger: 'contextmenu',\n disabled: false,\n placement: 'bottom-start',\n offsetPx: 0,\n minWidth: '11rem',\n ariaLabel: undefined,\n labelledBy: undefined,\n teleportTo: undefined,\n contentClass: undefined,\n listClass: undefined,\n dividers: false,\n closeOnScroll: true,\n allowNativeMenu: true,\n})\n\nconst emit = defineEmits<GrContextMenuEmits>()\n\nconst slots = useSlots()\nconst { t } = useGranularityTranslations()\n\nconst { open: isOpen, setOpen } = useControlledOpen(\n () => props.open,\n next => emit('update:open', next),\n)\n\nconst areaEl = ref<HTMLElement | null>(null)\nconst listRef = ref<{ $el?: HTMLElement } | null>(null)\nconst anchor = ref<GrFloatingAnchorRect | null>(null)\n\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.contextMenu.label', 'Context menu'))\n\nconst listStyle = computed(() => ({\n minWidth: typeof props.minWidth === 'number' ? `${props.minWidth}px` : props.minWidth,\n}))\n\n/**\n * Меню без единого действия не открывается: фокусировать в нём нечего, и оно\n * становится ловушкой, из которой есть только Esc.\n */\nconst isEmpty = computed(() => {\n if (slots.content)\n return false\n\n return (props.items ?? []).every(entry => !isMenuAction(entry)\n && (!isMenuSection(entry) || entry.items.length === 0))\n})\n\nconst menu = useMenuItemsFocus({\n container: () => listRef.value?.$el ?? null,\n close: () => close(),\n})\n\nfunction panelOf(): HTMLElement | null {\n return listRef.value?.$el?.closest('[data-gr-popover-panel]') ?? null\n}\n\nfunction isInsidePanel(target: EventTarget | null): boolean {\n return target instanceof Node && (panelOf()?.contains(target) ?? false)\n}\n\nasync function openWith(context: GrContextMenuOpenContext): Promise<void> {\n if (props.disabled)\n return\n\n anchor.value = context.anchor\n emit('beforeOpen', context)\n\n // Тик между уведомлением и открытием — не перестраховка: `items` доезжают сюда\n // пропом, то есть следующим рендером родителя. Без ожидания меню под узел\n // дерева открылось бы с пунктами от предыдущей цели.\n await nextTick()\n\n if (isEmpty.value)\n return\n\n setOpen(true)\n\n await nextTick()\n menu.focusAt(0)\n}\n\n/** Открыть у точки курсора или у произвольного прямоугольника вьюпорта. */\nfunction openAt(at: MouseEvent | GrFloatingAnchorRect): void {\n const fromPointer = at instanceof MouseEvent\n\n void openWith({\n source: fromPointer ? 'pointer' : 'api',\n target: fromPointer ? (at.target as HTMLElement | null) : null,\n anchor: fromPointer ? anchorFromPointer(at) : at,\n event: fromPointer ? at : null,\n })\n}\n\n/** Открыть у прямоугольника элемента — клавиатурный путь для внешних композитов. */\nfunction openAtElement(element: Element): void {\n void openWith({\n source: 'api',\n target: element instanceof HTMLElement ? element : null,\n anchor: anchorFromElement(element),\n event: null,\n })\n}\n\nfunction open(): void {\n if (anchor.value)\n void openWith({ source: 'api', target: null, anchor: anchor.value, event: null })\n else if (areaEl.value)\n openAtElement(areaEl.value)\n}\n\nfunction close(): void {\n setOpen(false)\n menu.reset()\n}\n\nfunction toggle(): void {\n isOpen.value ? close() : open()\n}\n\nfunction onSelect(item: GrDropdownMenuAction): void {\n emit('select', item)\n}\n\nfunction onAreaContextMenu(event: MouseEvent): void {\n if (props.trigger !== 'contextmenu' || props.disabled)\n return\n\n if (props.allowNativeMenu && wantsNativeMenu(event))\n return\n\n event.preventDefault()\n // Вложенные области: побеждает ближайшая к цели, как в нативных приложениях.\n event.stopPropagation()\n\n if (isKeyboardContextMenu(event)) {\n const target = (document.activeElement as HTMLElement | null) ?? areaEl.value\n if (target) openAtElement(target)\n return\n }\n\n openAt(event)\n}\n\n/**\n * `trigger` здесь намеренно не проверяется: `manual` отключает открытие\n * указателем, а клавиатурный путь оставить обязан — иначе меню недоступно с\n * клавиатуры в принципе, и потребителю пришлось бы писать этот обработчик\n * заново на каждой странице.\n */\nfunction onAreaKeydown(event: KeyboardEvent): void {\n if (props.disabled)\n return\n\n if (!isContextMenuKey(event))\n return\n\n // В поле ввода нативное меню (орфография, буфер) полезнее нашего.\n if (isEditableTarget(event.target))\n return\n\n // Отмена keydown гасит `contextmenu`, который браузер породил бы следом:\n // без неё меню открылось бы дважды и второй раз — по координатам, а не по\n // прямоугольнику строки.\n event.preventDefault()\n\n const target = (document.activeElement as HTMLElement | null) ?? areaEl.value\n if (!target)\n return\n\n void openWith({\n source: 'keyboard',\n target,\n anchor: anchorFromElement(target),\n event,\n })\n}\n\n/**\n * Правый клик мимо меню его не закрывает: `v-click-outside` отбрасывает всё,\n * что не левая кнопка, а `contextmenu` вообще не порождает `click`. Ослаблять\n * директиву нельзя — гард бережёт дропдауны, — поэтому слушаем сами.\n */\nfunction onDocumentContextMenu(event: MouseEvent): void {\n if (isInsidePanel(event.target)) {\n event.preventDefault()\n return\n }\n\n // Внутри своей области меню не закрываем: обработчик области перенесёт его к\n // новой точке, а закрытие с последующим открытием мигало бы панелью.\n if (event.target instanceof Node && areaEl.value?.contains(event.target))\n return\n\n close()\n}\n\nfunction onDocumentScroll(event: Event): void {\n if (isInsidePanel(event.target))\n return\n\n close()\n}\n\nlet scrollFrame: number | undefined\n\nfunction stopListening(): void {\n if (scrollFrame !== undefined) {\n cancelAnimationFrame(scrollFrame)\n scrollFrame = undefined\n }\n\n window.removeEventListener('contextmenu', onDocumentContextMenu, true)\n window.removeEventListener('scroll', onDocumentScroll, true)\n}\n\nwatch(isOpen, (next) => {\n if (typeof window === 'undefined')\n return\n\n if (!next) {\n stopListening()\n return\n }\n\n window.addEventListener('contextmenu', onDocumentContextMenu, true)\n\n // Слушатель прокрутки — со следующего кадра: само открытие прокручивает\n // страницу (браузер подтягивает в вид то, по чему кликнули и что получило\n // фокус), и меню закрывало бы само себя ровно в момент появления.\n if (props.closeOnScroll) {\n scrollFrame = requestAnimationFrame(() => {\n scrollFrame = undefined\n window.addEventListener('scroll', onDocumentScroll, { capture: true, passive: true })\n })\n }\n})\n\nonBeforeUnmount(() => {\n if (typeof window === 'undefined')\n return\n\n stopListening()\n})\n\ndefineExpose({ openAt, openAtElement, open, close, toggle })\n\ndefineSlots<{\n /** Область, по которой вызывается меню. */\n default?: (props: { open: boolean }) => any\n /** Содержимое меню. */\n content?: (props: { close: () => void }) => any\n}>()\n\n</script>\n\n<template>\n <div data-gr-context-menu>\n <div\n v-if=\"slots.default\"\n ref=\"areaEl\"\n data-gr-context-menu-area\n @contextmenu=\"onAreaContextMenu\"\n @keydown=\"onAreaKeydown\"\n >\n <slot :open=\"isOpen\" />\n </div>\n\n <GrPopover\n :open=\"isOpen\"\n :anchor=\"anchor\"\n :placement=\"placement\"\n :offset-px=\"offsetPx\"\n :teleport-to=\"teleportTo\"\n :content-class=\"contentClass\"\n :aria-label=\"labelledBy ? undefined : resolvedAriaLabel\"\n :labelled-by=\"labelledBy\"\n role=\"menu\"\n trigger=\"manual\"\n padding=\"none\"\n close-on-content-click\n :auto-focus=\"false\"\n @update:open=\"setOpen($event)\"\n >\n <template #content=\"{ close: closePanel }\">\n <GrDropdownMenuList\n ref=\"listRef\"\n class=\"p-1\"\n :style=\"listStyle\"\n :dividers=\"dividers\"\n :class=\"listClass\"\n @keydown=\"menu.onKeydown\"\n >\n <slot name=\"content\" :close=\"closePanel\">\n <GrDropdownMenuEntries :items=\"items\" @select=\"onSelect\" />\n </slot>\n </GrDropdownMenuList>\n </template>\n </GrPopover>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * GrContextMenu — меню по правому клику.\n *\n * Отличие от `GrDropdownMenu` не в содержимом, а в том, откуда меню растёт: у\n * дропдауна есть триггер-элемент, здесь — точка курсора, которой в DOM нет.\n * Поэтому слой берётся у `GrPopover` в якорном режиме, а не собирается заново.\n *\n * A11y: панель — `role=\"menu\"` с доступным именем, пункты приезжают из\n * `GrDropdownMenu` вместе со своими ролями. Меню, открываемое только правым\n * кликом, недоступно с клавиатуры, поэтому обёртка ловит `Shift+F10` и клавишу\n * `ContextMenu` — это условие приёмки, а не удобство.\n */\nimport { computed, nextTick, onBeforeUnmount, ref, useSlots, watch } from 'vue'\n\nimport { useControlledOpen } from '../../composables/internal/useControlledOpen'\nimport { useMenuItemsFocus } from '../../composables/internal/useMenuItemsFocus'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { isEditableTarget } from '../../internal/keyboard'\nimport GrDropdownMenuEntries from '../GrDropdownMenu/GrDropdownMenuEntries.vue'\nimport GrDropdownMenuList from '../GrDropdownMenu/GrDropdownMenuList.vue'\nimport type { GrDropdownMenuAction, GrDropdownMenuEntry } from '../GrDropdownMenu/menuModel'\nimport { isMenuAction, isMenuSection } from '../GrDropdownMenu/menuModel'\nimport GrPopover from '../GrPopover/GrPopover.vue'\nimport type { GrFloatingAnchorRect, UseFloatingPlacement } from '../../composables/useFloating'\n\nimport {\n anchorFromElement,\n anchorFromPointer,\n isContextMenuKey,\n isKeyboardContextMenu,\n wantsNativeMenu,\n} from './contextMenuAnchor'\n\n/** Чем открывается меню. `manual` — только императивно, из кода потребителя. */\nexport type GrContextMenuTrigger = 'contextmenu' | 'manual'\n\n/** Откуда пришло открытие: указатель, клавиатура или вызов из кода. */\nexport type GrContextMenuSource = 'pointer' | 'keyboard' | 'api'\n\nexport interface GrContextMenuOpenContext {\n source: GrContextMenuSource\n /** По чему кликнули или что было в фокусе — отсюда потребитель берёт строку. */\n target: HTMLElement | null\n anchor: GrFloatingAnchorRect\n event: MouseEvent | KeyboardEvent | null\n}\n\nexport interface GrContextMenuProps {\n /** Декларативное меню. Слот `#content` сильнее. */\n items?: GrDropdownMenuEntry[]\n /** Открыто ли меню. Без пропа компонент ведёт состояние сам. */\n open?: boolean\n trigger?: GrContextMenuTrigger\n disabled?: boolean\n placement?: UseFloatingPlacement\n /** Зазор от якоря. Ноль по умолчанию: меню липнет к курсору. */\n offsetPx?: number\n /** Меню из коротких слов не должно быть шириной в слово. */\n minWidth?: number | string\n ariaLabel?: string\n labelledBy?: string\n teleportTo?: string | HTMLElement\n contentClass?: string\n listClass?: string\n dividers?: boolean\n /**\n * Закрывать при прокрутке страницы. Якорь — точка вьюпорта, и панель осталась\n * бы висеть на экране, пока контент под ней уезжает. Так же ведут себя\n * нативные меню.\n */\n closeOnScroll?: boolean\n /** Пропускать `Shift`+правый клик к браузеру. */\n allowNativeMenu?: boolean\n}\n\nexport interface GrContextMenuEmits {\n (e: 'update:open', value: boolean): void\n /**\n * Меню собирается открыться у этой цели. Приходит **до** открытия, чтобы\n * потребитель успел пересобрать `items` под неё: пункты для папки и для файла\n * разные, а узнать цель иначе неоткуда. Пустая модель после этого просто не\n * откроется — отдельный способ отменить открытие не нужен.\n */\n (e: 'beforeOpen', context: GrContextMenuOpenContext): void\n (e: 'select', item: GrDropdownMenuAction): void\n}\n\nconst props = withDefaults(defineProps<GrContextMenuProps>(), {\n items: undefined,\n open: undefined,\n trigger: 'contextmenu',\n disabled: false,\n placement: 'bottom-start',\n offsetPx: 0,\n minWidth: '11rem',\n ariaLabel: undefined,\n labelledBy: undefined,\n teleportTo: undefined,\n contentClass: undefined,\n listClass: undefined,\n dividers: false,\n closeOnScroll: true,\n allowNativeMenu: true,\n})\n\nconst emit = defineEmits<GrContextMenuEmits>()\n\nconst slots = useSlots()\nconst { t } = useGranularityTranslations()\n\nconst { open: isOpen, setOpen } = useControlledOpen(\n () => props.open,\n next => emit('update:open', next),\n)\n\nconst areaEl = ref<HTMLElement | null>(null)\nconst listRef = ref<{ $el?: HTMLElement } | null>(null)\nconst anchor = ref<GrFloatingAnchorRect | null>(null)\n\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.contextMenu.label', 'Context menu'))\n\nconst listStyle = computed(() => ({\n minWidth: typeof props.minWidth === 'number' ? `${props.minWidth}px` : props.minWidth,\n}))\n\n/**\n * Меню без единого действия не открывается: фокусировать в нём нечего, и оно\n * становится ловушкой, из которой есть только Esc.\n */\nconst isEmpty = computed(() => {\n if (slots.content)\n return false\n\n return (props.items ?? []).every(entry => !isMenuAction(entry)\n && (!isMenuSection(entry) || entry.items.length === 0))\n})\n\nconst menu = useMenuItemsFocus({\n container: () => listRef.value?.$el ?? null,\n close: () => close(),\n})\n\nfunction panelOf(): HTMLElement | null {\n return listRef.value?.$el?.closest('[data-gr-popover-panel]') ?? null\n}\n\nfunction isInsidePanel(target: EventTarget | null): boolean {\n return target instanceof Node && (panelOf()?.contains(target) ?? false)\n}\n\nasync function openWith(context: GrContextMenuOpenContext): Promise<void> {\n if (props.disabled)\n return\n\n anchor.value = context.anchor\n emit('beforeOpen', context)\n\n // Тик между уведомлением и открытием — не перестраховка: `items` доезжают сюда\n // пропом, то есть следующим рендером родителя. Без ожидания меню под узел\n // дерева открылось бы с пунктами от предыдущей цели.\n await nextTick()\n\n if (isEmpty.value)\n return\n\n setOpen(true)\n\n await nextTick()\n menu.focusAt(0)\n}\n\n/** Открыть у точки курсора или у произвольного прямоугольника вьюпорта. */\nfunction openAt(at: MouseEvent | GrFloatingAnchorRect): void {\n const fromPointer = at instanceof MouseEvent\n\n void openWith({\n source: fromPointer ? 'pointer' : 'api',\n target: fromPointer ? (at.target as HTMLElement | null) : null,\n anchor: fromPointer ? anchorFromPointer(at) : at,\n event: fromPointer ? at : null,\n })\n}\n\n/** Открыть у прямоугольника элемента — клавиатурный путь для внешних композитов. */\nfunction openAtElement(element: Element): void {\n void openWith({\n source: 'api',\n target: element instanceof HTMLElement ? element : null,\n anchor: anchorFromElement(element),\n event: null,\n })\n}\n\nfunction open(): void {\n if (anchor.value)\n void openWith({ source: 'api', target: null, anchor: anchor.value, event: null })\n else if (areaEl.value)\n openAtElement(areaEl.value)\n}\n\nfunction close(): void {\n setOpen(false)\n menu.reset()\n}\n\nfunction toggle(): void {\n isOpen.value ? close() : open()\n}\n\nfunction onSelect(item: GrDropdownMenuAction): void {\n emit('select', item)\n}\n\nfunction onAreaContextMenu(event: MouseEvent): void {\n if (props.trigger !== 'contextmenu' || props.disabled)\n return\n\n if (props.allowNativeMenu && wantsNativeMenu(event))\n return\n\n event.preventDefault()\n // Вложенные области: побеждает ближайшая к цели, как в нативных приложениях.\n event.stopPropagation()\n\n if (isKeyboardContextMenu(event)) {\n const target = (document.activeElement as HTMLElement | null) ?? areaEl.value\n if (target) openAtElement(target)\n return\n }\n\n openAt(event)\n}\n\n/**\n * `trigger` здесь намеренно не проверяется: `manual` отключает открытие\n * указателем, а клавиатурный путь оставить обязан — иначе меню недоступно с\n * клавиатуры в принципе, и потребителю пришлось бы писать этот обработчик\n * заново на каждой странице.\n */\nfunction onAreaKeydown(event: KeyboardEvent): void {\n if (props.disabled)\n return\n\n if (!isContextMenuKey(event))\n return\n\n // В поле ввода нативное меню (орфография, буфер) полезнее нашего.\n if (isEditableTarget(event.target))\n return\n\n // Отмена keydown гасит `contextmenu`, который браузер породил бы следом:\n // без неё меню открылось бы дважды и второй раз — по координатам, а не по\n // прямоугольнику строки.\n event.preventDefault()\n\n const target = (document.activeElement as HTMLElement | null) ?? areaEl.value\n if (!target)\n return\n\n void openWith({\n source: 'keyboard',\n target,\n anchor: anchorFromElement(target),\n event,\n })\n}\n\n/**\n * Правый клик мимо меню его не закрывает: `v-click-outside` отбрасывает всё,\n * что не левая кнопка, а `contextmenu` вообще не порождает `click`. Ослаблять\n * директиву нельзя — гард бережёт дропдауны, — поэтому слушаем сами.\n */\nfunction onDocumentContextMenu(event: MouseEvent): void {\n if (isInsidePanel(event.target)) {\n event.preventDefault()\n return\n }\n\n // Внутри своей области меню не закрываем: обработчик области перенесёт его к\n // новой точке, а закрытие с последующим открытием мигало бы панелью.\n if (event.target instanceof Node && areaEl.value?.contains(event.target))\n return\n\n close()\n}\n\nfunction onDocumentScroll(event: Event): void {\n if (isInsidePanel(event.target))\n return\n\n close()\n}\n\nlet scrollFrame: number | undefined\n\nfunction stopListening(): void {\n if (scrollFrame !== undefined) {\n cancelAnimationFrame(scrollFrame)\n scrollFrame = undefined\n }\n\n window.removeEventListener('contextmenu', onDocumentContextMenu, true)\n window.removeEventListener('scroll', onDocumentScroll, true)\n}\n\nwatch(isOpen, (next) => {\n if (typeof window === 'undefined')\n return\n\n if (!next) {\n stopListening()\n return\n }\n\n window.addEventListener('contextmenu', onDocumentContextMenu, true)\n\n // Слушатель прокрутки — со следующего кадра: само открытие прокручивает\n // страницу (браузер подтягивает в вид то, по чему кликнули и что получило\n // фокус), и меню закрывало бы само себя ровно в момент появления.\n if (props.closeOnScroll) {\n scrollFrame = requestAnimationFrame(() => {\n scrollFrame = undefined\n window.addEventListener('scroll', onDocumentScroll, { capture: true, passive: true })\n })\n }\n})\n\nonBeforeUnmount(() => {\n if (typeof window === 'undefined')\n return\n\n stopListening()\n})\n\ndefineExpose({ openAt, openAtElement, open, close, toggle })\n\ndefineSlots<{\n /** Область, по которой вызывается меню. */\n default?: (props: { open: boolean }) => any\n /** Содержимое меню. */\n content?: (props: { close: () => void }) => any\n}>()\n\n</script>\n\n<template>\n <div data-gr-context-menu>\n <div\n v-if=\"slots.default\"\n ref=\"areaEl\"\n data-gr-context-menu-area\n @contextmenu=\"onAreaContextMenu\"\n @keydown=\"onAreaKeydown\"\n >\n <slot :open=\"isOpen\" />\n </div>\n\n <GrPopover\n :open=\"isOpen\"\n :anchor=\"anchor\"\n :placement=\"placement\"\n :offset-px=\"offsetPx\"\n :teleport-to=\"teleportTo\"\n :content-class=\"contentClass\"\n :aria-label=\"labelledBy ? undefined : resolvedAriaLabel\"\n :labelled-by=\"labelledBy\"\n role=\"menu\"\n trigger=\"manual\"\n padding=\"none\"\n close-on-content-click\n :auto-focus=\"false\"\n @update:open=\"setOpen($event)\"\n >\n <template #content=\"{ close: closePanel }\">\n <GrDropdownMenuList\n ref=\"listRef\"\n class=\"p-1\"\n :style=\"listStyle\"\n :dividers=\"dividers\"\n :class=\"listClass\"\n @keydown=\"menu.onKeydown\"\n >\n <slot name=\"content\" :close=\"closePanel\">\n <GrDropdownMenuEntries :items=\"items\" @select=\"onSelect\" />\n </slot>\n </GrDropdownMenuList>\n </template>\n </GrPopover>\n </div>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"GrContextMenu-_FKwQvuQ.js","names":[],"sources":["../../../../src/components/GrContextMenu/contextMenuAnchor.ts","../../../../src/components/GrContextMenu/GrContextMenu.vue","../../../../src/components/GrContextMenu/GrContextMenu.vue"],"sourcesContent":["import type { GrFloatingAnchorRect } from '../../composables/useFloating'\n\n/** Точка курсора — якорь нулевого размера. */\nexport function anchorFromPointer(event: MouseEvent): GrFloatingAnchorRect {\n return { x: event.clientX, y: event.clientY }\n}\n\n/**\n * Прямоугольник элемента — якорь клавиатурного вызова.\n *\n * Не угол и не точка: меню, вызванное с клавиатуры, принадлежит **строке**, и\n * при нехватке места снизу обязано перевернуться относительно неё целиком.\n */\nexport function anchorFromElement(element: Element): GrFloatingAnchorRect {\n const rect = element.getBoundingClientRect()\n\n return { x: rect.left, y: rect.top, width: rect.width, height: rect.height }\n}\n\n/**\n * `contextmenu` пришёл с клавиатуры, а не от мыши.\n *\n * Браузеры порождают это событие и для `Shift+F10`, но координаты в нём\n * бесполезны: то угол элемента, то нули. Признаков два сразу, потому что по\n * отдельности каждый ошибается — правый клик всегда `button === 2`, а\n * клавиатурный вызов всегда без «числа кликов».\n */\nexport function isKeyboardContextMenu(event: MouseEvent): boolean {\n return event.button !== 2 && event.detail === 0\n}\n\n/**\n * Пользователь просит нативное меню браузера.\n *\n * `Shift`+правый клик — де-факто escape-hatch, в Firefox задокументированный.\n * `ctrlKey` сюда не входит намеренно: на macOS Ctrl+клик **и есть** правый\n * клик, и меню перестало бы открываться у части пользователей.\n */\nexport function wantsNativeMenu(event: MouseEvent): boolean {\n return event.shiftKey\n}\n\n/** Клавиатурный запрос контекстного меню: `Shift+F10` или клавиша `ContextMenu`. */\nexport function isContextMenuKey(event: KeyboardEvent): boolean {\n return event.key === 'ContextMenu' || (event.key === 'F10' && event.shiftKey)\n}\n","<script setup lang=\"ts\">\n/**\n * GrContextMenu — меню по правому клику.\n *\n * Отличие от `GrDropdownMenu` не в содержимом, а в том, откуда меню растёт: у\n * дропдауна есть триггер-элемент, здесь — точка курсора, которой в DOM нет.\n * Поэтому слой берётся у `GrPopover` в якорном режиме, а не собирается заново.\n *\n * A11y: панель — `role=\"menu\"` с доступным именем, пункты приезжают из\n * `GrDropdownMenu` вместе со своими ролями. Меню, открываемое только правым\n * кликом, недоступно с клавиатуры, поэтому обёртка ловит `Shift+F10` и клавишу\n * `ContextMenu` — это условие приёмки, а не удобство.\n */\nimport { computed, nextTick, onBeforeUnmount, ref, useSlots, watch } from 'vue'\n\nimport { useControlledOpen } from '../../composables/internal/useControlledOpen'\nimport { useMenuItemsFocus } from '../../composables/internal/useMenuItemsFocus'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { isEditableTarget } from '../../internal/keyboard'\nimport GrDropdownMenuEntries from '../GrDropdownMenu/GrDropdownMenuEntries.vue'\nimport GrDropdownMenuList from '../GrDropdownMenu/GrDropdownMenuList.vue'\nimport type { GrDropdownMenuAction, GrDropdownMenuEntry } from '../GrDropdownMenu/menuModel'\nimport { isMenuAction, isMenuSection } from '../GrDropdownMenu/menuModel'\nimport GrPopover from '../GrPopover/GrPopover.vue'\nimport type { GrFloatingAnchorRect, UseFloatingPlacement } from '../../composables/useFloating'\n\nimport {\n anchorFromElement,\n anchorFromPointer,\n isContextMenuKey,\n isKeyboardContextMenu,\n wantsNativeMenu,\n} from './contextMenuAnchor'\n\n/** Чем открывается меню. `manual` — только императивно, из кода потребителя. */\nexport type GrContextMenuTrigger = 'contextmenu' | 'manual'\n\n/** Откуда пришло открытие: указатель, клавиатура или вызов из кода. */\nexport type GrContextMenuSource = 'pointer' | 'keyboard' | 'api'\n\nexport interface GrContextMenuOpenContext {\n source: GrContextMenuSource\n /** По чему кликнули или что было в фокусе — отсюда потребитель берёт строку. */\n target: HTMLElement | null\n anchor: GrFloatingAnchorRect\n event: MouseEvent | KeyboardEvent | null\n}\n\nexport interface GrContextMenuProps {\n /** Декларативное меню. Слот `#content` сильнее. */\n items?: GrDropdownMenuEntry[]\n /** Открыто ли меню. Без пропа компонент ведёт состояние сам. */\n open?: boolean\n trigger?: GrContextMenuTrigger\n disabled?: boolean\n placement?: UseFloatingPlacement\n /** Зазор от якоря. Ноль по умолчанию: меню липнет к курсору. */\n offsetPx?: number\n /** Меню из коротких слов не должно быть шириной в слово. */\n minWidth?: number | string\n ariaLabel?: string\n labelledBy?: string\n teleportTo?: string | HTMLElement\n contentClass?: string\n listClass?: string\n dividers?: boolean\n /**\n * Закрывать при прокрутке страницы. Якорь — точка вьюпорта, и панель осталась\n * бы висеть на экране, пока контент под ней уезжает. Так же ведут себя\n * нативные меню.\n */\n closeOnScroll?: boolean\n /** Пропускать `Shift`+правый клик к браузеру. */\n allowNativeMenu?: boolean\n}\n\nexport interface GrContextMenuEmits {\n (e: 'update:open', value: boolean): void\n /**\n * Меню собирается открыться у этой цели. Приходит **до** открытия, чтобы\n * потребитель успел пересобрать `items` под неё: пункты для папки и для файла\n * разные, а узнать цель иначе неоткуда. Пустая модель после этого просто не\n * откроется — отдельный способ отменить открытие не нужен.\n */\n (e: 'beforeOpen', context: GrContextMenuOpenContext): void\n (e: 'select', item: GrDropdownMenuAction): void\n}\n\nconst props = withDefaults(defineProps<GrContextMenuProps>(), {\n items: undefined,\n open: undefined,\n trigger: 'contextmenu',\n disabled: false,\n placement: 'bottom-start',\n offsetPx: 0,\n minWidth: '11rem',\n ariaLabel: undefined,\n labelledBy: undefined,\n teleportTo: undefined,\n contentClass: undefined,\n listClass: undefined,\n dividers: false,\n closeOnScroll: true,\n allowNativeMenu: true,\n})\n\nconst emit = defineEmits<GrContextMenuEmits>()\n\nconst slots = useSlots()\nconst { t } = useGranularityTranslations()\n\nconst { open: isOpen, setOpen } = useControlledOpen(\n () => props.open,\n next => emit('update:open', next),\n)\n\nconst areaEl = ref<HTMLElement | null>(null)\nconst listRef = ref<{ $el?: HTMLElement } | null>(null)\nconst anchor = ref<GrFloatingAnchorRect | null>(null)\n\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.contextMenu.label', 'Context menu'))\n\nconst listStyle = computed(() => ({\n minWidth: typeof props.minWidth === 'number' ? `${props.minWidth}px` : props.minWidth,\n}))\n\n/**\n * Меню без единого действия не открывается: фокусировать в нём нечего, и оно\n * становится ловушкой, из которой есть только Esc.\n */\nconst isEmpty = computed(() => {\n if (slots.content)\n return false\n\n return (props.items ?? []).every(entry => !isMenuAction(entry)\n && (!isMenuSection(entry) || entry.items.length === 0))\n})\n\nconst menu = useMenuItemsFocus({\n container: () => listRef.value?.$el ?? null,\n close: () => close(),\n})\n\nfunction panelOf(): HTMLElement | null {\n return listRef.value?.$el?.closest('[data-gr-popover-panel]') ?? null\n}\n\nfunction isInsidePanel(target: EventTarget | null): boolean {\n return target instanceof Node && (panelOf()?.contains(target) ?? false)\n}\n\nasync function openWith(context: GrContextMenuOpenContext): Promise<void> {\n if (props.disabled)\n return\n\n anchor.value = context.anchor\n emit('beforeOpen', context)\n\n // Тик между уведомлением и открытием — не перестраховка: `items` доезжают сюда\n // пропом, то есть следующим рендером родителя. Без ожидания меню под узел\n // дерева открылось бы с пунктами от предыдущей цели.\n await nextTick()\n\n if (isEmpty.value)\n return\n\n setOpen(true)\n\n await nextTick()\n menu.focusAt(0)\n}\n\n/** Открыть у точки курсора или у произвольного прямоугольника вьюпорта. */\nfunction openAt(at: MouseEvent | GrFloatingAnchorRect): void {\n const fromPointer = at instanceof MouseEvent\n\n void openWith({\n source: fromPointer ? 'pointer' : 'api',\n target: fromPointer ? (at.target as HTMLElement | null) : null,\n anchor: fromPointer ? anchorFromPointer(at) : at,\n event: fromPointer ? at : null,\n })\n}\n\n/** Открыть у прямоугольника элемента — клавиатурный путь для внешних композитов. */\nfunction openAtElement(element: Element): void {\n void openWith({\n source: 'api',\n target: element instanceof HTMLElement ? element : null,\n anchor: anchorFromElement(element),\n event: null,\n })\n}\n\nfunction open(): void {\n if (anchor.value)\n void openWith({ source: 'api', target: null, anchor: anchor.value, event: null })\n else if (areaEl.value)\n openAtElement(areaEl.value)\n}\n\nfunction close(): void {\n setOpen(false)\n menu.reset()\n}\n\nfunction toggle(): void {\n isOpen.value ? close() : open()\n}\n\nfunction onSelect(item: GrDropdownMenuAction): void {\n emit('select', item)\n}\n\nfunction onAreaContextMenu(event: MouseEvent): void {\n if (props.trigger !== 'contextmenu' || props.disabled)\n return\n\n if (props.allowNativeMenu && wantsNativeMenu(event))\n return\n\n event.preventDefault()\n // Вложенные области: побеждает ближайшая к цели, как в нативных приложениях.\n event.stopPropagation()\n\n if (isKeyboardContextMenu(event)) {\n const target = (document.activeElement as HTMLElement | null) ?? areaEl.value\n if (target) openAtElement(target)\n return\n }\n\n openAt(event)\n}\n\n/**\n * `trigger` здесь намеренно не проверяется: `manual` отключает открытие\n * указателем, а клавиатурный путь оставить обязан — иначе меню недоступно с\n * клавиатуры в принципе, и потребителю пришлось бы писать этот обработчик\n * заново на каждой странице.\n */\nfunction onAreaKeydown(event: KeyboardEvent): void {\n if (props.disabled)\n return\n\n if (!isContextMenuKey(event))\n return\n\n // В поле ввода нативное меню (орфография, буфер) полезнее нашего.\n if (isEditableTarget(event.target))\n return\n\n // Отмена keydown гасит `contextmenu`, который браузер породил бы следом:\n // без неё меню открылось бы дважды и второй раз — по координатам, а не по\n // прямоугольнику строки.\n event.preventDefault()\n\n const target = (document.activeElement as HTMLElement | null) ?? areaEl.value\n if (!target)\n return\n\n void openWith({\n source: 'keyboard',\n target,\n anchor: anchorFromElement(target),\n event,\n })\n}\n\n/**\n * Правый клик мимо меню его не закрывает: `v-click-outside` отбрасывает всё,\n * что не левая кнопка, а `contextmenu` вообще не порождает `click`. Ослаблять\n * директиву нельзя — гард бережёт дропдауны, — поэтому слушаем сами.\n */\nfunction onDocumentContextMenu(event: MouseEvent): void {\n if (isInsidePanel(event.target)) {\n event.preventDefault()\n return\n }\n\n // Внутри своей области меню не закрываем: обработчик области перенесёт его к\n // новой точке, а закрытие с последующим открытием мигало бы панелью.\n if (event.target instanceof Node && areaEl.value?.contains(event.target))\n return\n\n close()\n}\n\nfunction onDocumentScroll(event: Event): void {\n if (isInsidePanel(event.target))\n return\n\n close()\n}\n\nlet scrollFrame: number | undefined\n\nfunction stopListening(): void {\n if (scrollFrame !== undefined) {\n cancelAnimationFrame(scrollFrame)\n scrollFrame = undefined\n }\n\n window.removeEventListener('contextmenu', onDocumentContextMenu, true)\n window.removeEventListener('scroll', onDocumentScroll, true)\n}\n\nwatch(isOpen, (next) => {\n if (typeof window === 'undefined')\n return\n\n if (!next) {\n stopListening()\n return\n }\n\n window.addEventListener('contextmenu', onDocumentContextMenu, true)\n\n // Слушатель прокрутки — со следующего кадра: само открытие прокручивает\n // страницу (браузер подтягивает в вид то, по чему кликнули и что получило\n // фокус), и меню закрывало бы само себя ровно в момент появления.\n if (props.closeOnScroll) {\n scrollFrame = requestAnimationFrame(() => {\n scrollFrame = undefined\n window.addEventListener('scroll', onDocumentScroll, { capture: true, passive: true })\n })\n }\n})\n\nonBeforeUnmount(() => {\n if (typeof window === 'undefined')\n return\n\n stopListening()\n})\n\ndefineExpose({ openAt, openAtElement, open, close, toggle })\n\ndefineSlots<{\n /** Область, по которой вызывается меню. */\n default?: (props: { open: boolean }) => any\n /** Содержимое меню. */\n content?: (props: { close: () => void }) => any\n}>()\n\n</script>\n\n<template>\n <div data-gr-context-menu>\n <div\n v-if=\"slots.default\"\n ref=\"areaEl\"\n data-gr-context-menu-area\n @contextmenu=\"onAreaContextMenu\"\n @keydown=\"onAreaKeydown\"\n >\n <slot :open=\"isOpen\" />\n </div>\n\n <GrPopover\n :open=\"isOpen\"\n :anchor=\"anchor\"\n :placement=\"placement\"\n :offset-px=\"offsetPx\"\n :teleport-to=\"teleportTo\"\n :content-class=\"contentClass\"\n :aria-label=\"labelledBy ? undefined : resolvedAriaLabel\"\n :labelled-by=\"labelledBy\"\n role=\"menu\"\n trigger=\"manual\"\n padding=\"none\"\n close-on-content-click\n :auto-focus=\"false\"\n @update:open=\"setOpen($event)\"\n >\n <template #content=\"{ close: closePanel }\">\n <GrDropdownMenuList\n ref=\"listRef\"\n class=\"p-1\"\n :style=\"listStyle\"\n :dividers=\"dividers\"\n :class=\"listClass\"\n @keydown=\"menu.onKeydown\"\n >\n <slot name=\"content\" :close=\"closePanel\">\n <GrDropdownMenuEntries :items=\"items\" @select=\"onSelect\" />\n </slot>\n </GrDropdownMenuList>\n </template>\n </GrPopover>\n </div>\n</template>\n","<script setup lang=\"ts\">\n/**\n * GrContextMenu — меню по правому клику.\n *\n * Отличие от `GrDropdownMenu` не в содержимом, а в том, откуда меню растёт: у\n * дропдауна есть триггер-элемент, здесь — точка курсора, которой в DOM нет.\n * Поэтому слой берётся у `GrPopover` в якорном режиме, а не собирается заново.\n *\n * A11y: панель — `role=\"menu\"` с доступным именем, пункты приезжают из\n * `GrDropdownMenu` вместе со своими ролями. Меню, открываемое только правым\n * кликом, недоступно с клавиатуры, поэтому обёртка ловит `Shift+F10` и клавишу\n * `ContextMenu` — это условие приёмки, а не удобство.\n */\nimport { computed, nextTick, onBeforeUnmount, ref, useSlots, watch } from 'vue'\n\nimport { useControlledOpen } from '../../composables/internal/useControlledOpen'\nimport { useMenuItemsFocus } from '../../composables/internal/useMenuItemsFocus'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { isEditableTarget } from '../../internal/keyboard'\nimport GrDropdownMenuEntries from '../GrDropdownMenu/GrDropdownMenuEntries.vue'\nimport GrDropdownMenuList from '../GrDropdownMenu/GrDropdownMenuList.vue'\nimport type { GrDropdownMenuAction, GrDropdownMenuEntry } from '../GrDropdownMenu/menuModel'\nimport { isMenuAction, isMenuSection } from '../GrDropdownMenu/menuModel'\nimport GrPopover from '../GrPopover/GrPopover.vue'\nimport type { GrFloatingAnchorRect, UseFloatingPlacement } from '../../composables/useFloating'\n\nimport {\n anchorFromElement,\n anchorFromPointer,\n isContextMenuKey,\n isKeyboardContextMenu,\n wantsNativeMenu,\n} from './contextMenuAnchor'\n\n/** Чем открывается меню. `manual` — только императивно, из кода потребителя. */\nexport type GrContextMenuTrigger = 'contextmenu' | 'manual'\n\n/** Откуда пришло открытие: указатель, клавиатура или вызов из кода. */\nexport type GrContextMenuSource = 'pointer' | 'keyboard' | 'api'\n\nexport interface GrContextMenuOpenContext {\n source: GrContextMenuSource\n /** По чему кликнули или что было в фокусе — отсюда потребитель берёт строку. */\n target: HTMLElement | null\n anchor: GrFloatingAnchorRect\n event: MouseEvent | KeyboardEvent | null\n}\n\nexport interface GrContextMenuProps {\n /** Декларативное меню. Слот `#content` сильнее. */\n items?: GrDropdownMenuEntry[]\n /** Открыто ли меню. Без пропа компонент ведёт состояние сам. */\n open?: boolean\n trigger?: GrContextMenuTrigger\n disabled?: boolean\n placement?: UseFloatingPlacement\n /** Зазор от якоря. Ноль по умолчанию: меню липнет к курсору. */\n offsetPx?: number\n /** Меню из коротких слов не должно быть шириной в слово. */\n minWidth?: number | string\n ariaLabel?: string\n labelledBy?: string\n teleportTo?: string | HTMLElement\n contentClass?: string\n listClass?: string\n dividers?: boolean\n /**\n * Закрывать при прокрутке страницы. Якорь — точка вьюпорта, и панель осталась\n * бы висеть на экране, пока контент под ней уезжает. Так же ведут себя\n * нативные меню.\n */\n closeOnScroll?: boolean\n /** Пропускать `Shift`+правый клик к браузеру. */\n allowNativeMenu?: boolean\n}\n\nexport interface GrContextMenuEmits {\n (e: 'update:open', value: boolean): void\n /**\n * Меню собирается открыться у этой цели. Приходит **до** открытия, чтобы\n * потребитель успел пересобрать `items` под неё: пункты для папки и для файла\n * разные, а узнать цель иначе неоткуда. Пустая модель после этого просто не\n * откроется — отдельный способ отменить открытие не нужен.\n */\n (e: 'beforeOpen', context: GrContextMenuOpenContext): void\n (e: 'select', item: GrDropdownMenuAction): void\n}\n\nconst props = withDefaults(defineProps<GrContextMenuProps>(), {\n items: undefined,\n open: undefined,\n trigger: 'contextmenu',\n disabled: false,\n placement: 'bottom-start',\n offsetPx: 0,\n minWidth: '11rem',\n ariaLabel: undefined,\n labelledBy: undefined,\n teleportTo: undefined,\n contentClass: undefined,\n listClass: undefined,\n dividers: false,\n closeOnScroll: true,\n allowNativeMenu: true,\n})\n\nconst emit = defineEmits<GrContextMenuEmits>()\n\nconst slots = useSlots()\nconst { t } = useGranularityTranslations()\n\nconst { open: isOpen, setOpen } = useControlledOpen(\n () => props.open,\n next => emit('update:open', next),\n)\n\nconst areaEl = ref<HTMLElement | null>(null)\nconst listRef = ref<{ $el?: HTMLElement } | null>(null)\nconst anchor = ref<GrFloatingAnchorRect | null>(null)\n\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.contextMenu.label', 'Context menu'))\n\nconst listStyle = computed(() => ({\n minWidth: typeof props.minWidth === 'number' ? `${props.minWidth}px` : props.minWidth,\n}))\n\n/**\n * Меню без единого действия не открывается: фокусировать в нём нечего, и оно\n * становится ловушкой, из которой есть только Esc.\n */\nconst isEmpty = computed(() => {\n if (slots.content)\n return false\n\n return (props.items ?? []).every(entry => !isMenuAction(entry)\n && (!isMenuSection(entry) || entry.items.length === 0))\n})\n\nconst menu = useMenuItemsFocus({\n container: () => listRef.value?.$el ?? null,\n close: () => close(),\n})\n\nfunction panelOf(): HTMLElement | null {\n return listRef.value?.$el?.closest('[data-gr-popover-panel]') ?? null\n}\n\nfunction isInsidePanel(target: EventTarget | null): boolean {\n return target instanceof Node && (panelOf()?.contains(target) ?? false)\n}\n\nasync function openWith(context: GrContextMenuOpenContext): Promise<void> {\n if (props.disabled)\n return\n\n anchor.value = context.anchor\n emit('beforeOpen', context)\n\n // Тик между уведомлением и открытием — не перестраховка: `items` доезжают сюда\n // пропом, то есть следующим рендером родителя. Без ожидания меню под узел\n // дерева открылось бы с пунктами от предыдущей цели.\n await nextTick()\n\n if (isEmpty.value)\n return\n\n setOpen(true)\n\n await nextTick()\n menu.focusAt(0)\n}\n\n/** Открыть у точки курсора или у произвольного прямоугольника вьюпорта. */\nfunction openAt(at: MouseEvent | GrFloatingAnchorRect): void {\n const fromPointer = at instanceof MouseEvent\n\n void openWith({\n source: fromPointer ? 'pointer' : 'api',\n target: fromPointer ? (at.target as HTMLElement | null) : null,\n anchor: fromPointer ? anchorFromPointer(at) : at,\n event: fromPointer ? at : null,\n })\n}\n\n/** Открыть у прямоугольника элемента — клавиатурный путь для внешних композитов. */\nfunction openAtElement(element: Element): void {\n void openWith({\n source: 'api',\n target: element instanceof HTMLElement ? element : null,\n anchor: anchorFromElement(element),\n event: null,\n })\n}\n\nfunction open(): void {\n if (anchor.value)\n void openWith({ source: 'api', target: null, anchor: anchor.value, event: null })\n else if (areaEl.value)\n openAtElement(areaEl.value)\n}\n\nfunction close(): void {\n setOpen(false)\n menu.reset()\n}\n\nfunction toggle(): void {\n isOpen.value ? close() : open()\n}\n\nfunction onSelect(item: GrDropdownMenuAction): void {\n emit('select', item)\n}\n\nfunction onAreaContextMenu(event: MouseEvent): void {\n if (props.trigger !== 'contextmenu' || props.disabled)\n return\n\n if (props.allowNativeMenu && wantsNativeMenu(event))\n return\n\n event.preventDefault()\n // Вложенные области: побеждает ближайшая к цели, как в нативных приложениях.\n event.stopPropagation()\n\n if (isKeyboardContextMenu(event)) {\n const target = (document.activeElement as HTMLElement | null) ?? areaEl.value\n if (target) openAtElement(target)\n return\n }\n\n openAt(event)\n}\n\n/**\n * `trigger` здесь намеренно не проверяется: `manual` отключает открытие\n * указателем, а клавиатурный путь оставить обязан — иначе меню недоступно с\n * клавиатуры в принципе, и потребителю пришлось бы писать этот обработчик\n * заново на каждой странице.\n */\nfunction onAreaKeydown(event: KeyboardEvent): void {\n if (props.disabled)\n return\n\n if (!isContextMenuKey(event))\n return\n\n // В поле ввода нативное меню (орфография, буфер) полезнее нашего.\n if (isEditableTarget(event.target))\n return\n\n // Отмена keydown гасит `contextmenu`, который браузер породил бы следом:\n // без неё меню открылось бы дважды и второй раз — по координатам, а не по\n // прямоугольнику строки.\n event.preventDefault()\n\n const target = (document.activeElement as HTMLElement | null) ?? areaEl.value\n if (!target)\n return\n\n void openWith({\n source: 'keyboard',\n target,\n anchor: anchorFromElement(target),\n event,\n })\n}\n\n/**\n * Правый клик мимо меню его не закрывает: `v-click-outside` отбрасывает всё,\n * что не левая кнопка, а `contextmenu` вообще не порождает `click`. Ослаблять\n * директиву нельзя — гард бережёт дропдауны, — поэтому слушаем сами.\n */\nfunction onDocumentContextMenu(event: MouseEvent): void {\n if (isInsidePanel(event.target)) {\n event.preventDefault()\n return\n }\n\n // Внутри своей области меню не закрываем: обработчик области перенесёт его к\n // новой точке, а закрытие с последующим открытием мигало бы панелью.\n if (event.target instanceof Node && areaEl.value?.contains(event.target))\n return\n\n close()\n}\n\nfunction onDocumentScroll(event: Event): void {\n if (isInsidePanel(event.target))\n return\n\n close()\n}\n\nlet scrollFrame: number | undefined\n\nfunction stopListening(): void {\n if (scrollFrame !== undefined) {\n cancelAnimationFrame(scrollFrame)\n scrollFrame = undefined\n }\n\n window.removeEventListener('contextmenu', onDocumentContextMenu, true)\n window.removeEventListener('scroll', onDocumentScroll, true)\n}\n\nwatch(isOpen, (next) => {\n if (typeof window === 'undefined')\n return\n\n if (!next) {\n stopListening()\n return\n }\n\n window.addEventListener('contextmenu', onDocumentContextMenu, true)\n\n // Слушатель прокрутки — со следующего кадра: само открытие прокручивает\n // страницу (браузер подтягивает в вид то, по чему кликнули и что получило\n // фокус), и меню закрывало бы само себя ровно в момент появления.\n if (props.closeOnScroll) {\n scrollFrame = requestAnimationFrame(() => {\n scrollFrame = undefined\n window.addEventListener('scroll', onDocumentScroll, { capture: true, passive: true })\n })\n }\n})\n\nonBeforeUnmount(() => {\n if (typeof window === 'undefined')\n return\n\n stopListening()\n})\n\ndefineExpose({ openAt, openAtElement, open, close, toggle })\n\ndefineSlots<{\n /** Область, по которой вызывается меню. */\n default?: (props: { open: boolean }) => any\n /** Содержимое меню. */\n content?: (props: { close: () => void }) => any\n}>()\n\n</script>\n\n<template>\n <div data-gr-context-menu>\n <div\n v-if=\"slots.default\"\n ref=\"areaEl\"\n data-gr-context-menu-area\n @contextmenu=\"onAreaContextMenu\"\n @keydown=\"onAreaKeydown\"\n >\n <slot :open=\"isOpen\" />\n </div>\n\n <GrPopover\n :open=\"isOpen\"\n :anchor=\"anchor\"\n :placement=\"placement\"\n :offset-px=\"offsetPx\"\n :teleport-to=\"teleportTo\"\n :content-class=\"contentClass\"\n :aria-label=\"labelledBy ? undefined : resolvedAriaLabel\"\n :labelled-by=\"labelledBy\"\n role=\"menu\"\n trigger=\"manual\"\n padding=\"none\"\n close-on-content-click\n :auto-focus=\"false\"\n @update:open=\"setOpen($event)\"\n >\n <template #content=\"{ close: closePanel }\">\n <GrDropdownMenuList\n ref=\"listRef\"\n class=\"p-1\"\n :style=\"listStyle\"\n :dividers=\"dividers\"\n :class=\"listClass\"\n @keydown=\"menu.onKeydown\"\n >\n <slot name=\"content\" :close=\"closePanel\">\n <GrDropdownMenuEntries :items=\"items\" @select=\"onSelect\" />\n </slot>\n </GrDropdownMenuList>\n </template>\n </GrPopover>\n </div>\n</template>\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- import { t as GrContextMenu_default } from "./chunks/GrContextMenu-BNQDbAsY.js";
1
+ import { t as GrContextMenu_default } from "./chunks/GrContextMenu-_FKwQvuQ.js";
2
2
  import { t as grContextMenuConfig } from "../../chunks/config-CPSxM7XE.js";
3
3
  export { GrContextMenu_default as GrContextMenu, GrContextMenu_default as default, grContextMenuConfig };
@@ -5,7 +5,7 @@ import { t as ensurePortalRoot } from "../../../chunks/portalRoot-CVhCgdG6.js";
5
5
  import { t as GrButton_default } from "../../GrButton/chunks/GrButton-Bw2AH4uX.js";
6
6
  import { n as coreResponseErrorParsers, o as responseErrorParserPresets, r as createResponseErrorClassifier } from "../../GrResponseErrorBanner/chunks/GrResponseErrorBanner-DudGc9LO.js";
7
7
  import { t as GrConfirmDialog_default } from "../../GrConfirmDialog/chunks/GrConfirmDialog-B9mIU78E.js";
8
- import { t as GrPromptDialog_default } from "../../GrPromptDialog/chunks/GrPromptDialog-D1ADRFxx.js";
8
+ import { t as GrPromptDialog_default } from "../../GrPromptDialog/chunks/GrPromptDialog-DLcl1xiq.js";
9
9
  import { Fragment, computed, createBlock, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, getCurrentInstance, inject, markRaw, mergeProps, nextTick, onBeforeUnmount, onMounted, openBlock, provide, reactive, ref, render, renderList, shallowRef, toDisplayString, withCtx } from "vue";
10
10
  //#region src/components/GrDialogService/store.ts
11
11
  function createDialogServiceState() {