@feugene/granularity 0.27.0 → 0.27.1

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 (35) hide show
  1. package/dist/components/GrColorPicker/chunks/{GrColorPicker-0n_KGeUy.js → GrColorPicker-CE-xtNP-.js} +1 -1
  2. package/dist/components/GrColorPicker/chunks/{GrColorPicker-0n_KGeUy.js.map → GrColorPicker-CE-xtNP-.js.map} +1 -1
  3. package/dist/components/GrColorPicker/index.js +1 -1
  4. package/dist/components/GrDialogService/chunks/{GrDialogService-B--br5so.js → GrDialogService-k4SYIv64.js} +1 -1
  5. package/dist/components/GrDialogService/chunks/{GrDialogService-B--br5so.js.map → GrDialogService-k4SYIv64.js.map} +1 -1
  6. package/dist/components/GrDialogService/index.js +1 -1
  7. package/dist/components/GrInput/chunks/{GrInput-c53iltz0.js → GrInput-BqqoEl1b.js} +1 -1
  8. package/dist/components/GrInput/chunks/GrInput-BqqoEl1b.js.map +1 -0
  9. package/dist/components/GrInput/index.js +1 -1
  10. package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-ClTvjlFx.js → GrJsonViewer-BoldivFc.js} +1 -1
  11. package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-ClTvjlFx.js.map → GrJsonViewer-BoldivFc.js.map} +1 -1
  12. package/dist/components/GrJsonViewer/index.js +1 -1
  13. package/dist/components/GrPagination/chunks/{GrPagination-CaNTZc-v.js → GrPagination-kQhWlfs-.js} +1 -1
  14. package/dist/components/GrPagination/chunks/{GrPagination-CaNTZc-v.js.map → GrPagination-kQhWlfs-.js.map} +1 -1
  15. package/dist/components/GrPagination/index.js +1 -1
  16. package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-DMhALhSS.js → GrPromptDialog-D1ADRFxx.js} +1 -1
  17. package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-DMhALhSS.js.map → GrPromptDialog-D1ADRFxx.js.map} +1 -1
  18. package/dist/components/GrPromptDialog/index.js +1 -1
  19. package/dist/components/GrSelect/chunks/{GrSelect-BE1VNTiY.js → GrSelect-D-KU9Lx_.js} +1 -1
  20. package/dist/components/GrSelect/chunks/{GrSelect-BE1VNTiY.js.map → GrSelect-D-KU9Lx_.js.map} +1 -1
  21. package/dist/components/GrSelect/index.js +1 -1
  22. package/dist/components/GrSwitch/chunks/{GrSwitch-KRlRMwgp.js → GrSwitch-g_OOSkh7.js} +4 -1
  23. package/dist/components/GrSwitch/chunks/GrSwitch-g_OOSkh7.js.map +1 -0
  24. package/dist/components/GrSwitch/index.js +1 -1
  25. package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-Db_uKdNa.js → GrTreeSelect-CS1WQWsT.js} +1 -1
  26. package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-Db_uKdNa.js.map → GrTreeSelect-CS1WQWsT.js.map} +1 -1
  27. package/dist/components/GrTreeSelect/index.js +1 -1
  28. package/dist/index.js +9 -9
  29. package/dist/types/components/GrInput/GrInput.vue.d.ts +9 -1
  30. package/dist/types/components/GrRadioGroup/index.d.ts +1 -1
  31. package/dist/types/components/GrSwitch/GrSwitch.vue.d.ts +9 -1
  32. package/dist/web-types.json +19 -17
  33. package/package.json +2 -2
  34. package/dist/components/GrInput/chunks/GrInput-c53iltz0.js.map +0 -1
  35. package/dist/components/GrSwitch/chunks/GrSwitch-KRlRMwgp.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"GrJsonViewer-ClTvjlFx.js","names":[],"sources":["../../../../src/components/GrJsonViewer/jsonToNodes.ts","../../../../src/components/GrJsonViewer/GrJsonViewer.vue","../../../../src/components/GrJsonViewer/GrJsonViewer.vue"],"sourcesContent":["/**\n * Разбор произвольного значения в узлы дерева.\n *\n * Компонент показывает данные из БД и от чужих сервисов, поэтому обход обязан\n * пережить всё: цикл, `BigInt`, функцию в поле, массив на десять тысяч записей,\n * строку в миллион символов. Уронить страницу он права не имеет.\n */\n\n/** Что за значение в узле. Решает и цвет, и то, раскрывается ли узел. */\nexport type GrJsonNodeKind =\n | 'object'\n | 'array'\n | 'string'\n | 'number'\n | 'boolean'\n | 'null'\n /** Значения, которых в JSON не бывает: функция, символ, `undefined`. */\n | 'unsupported'\n /** Хвост обрезанного массива — «ещё N элементов». Своего значения не имеет. */\n | 'truncation'\n\nexport type GrJsonNode = {\n /** Читаемый адрес узла (`$.items[3].name`). Он же ключ для `GrTree`. */\n path: string\n /** Имя ключа или индекс. Показывается слева от двоеточия. */\n label: string\n kind: GrJsonNodeKind\n /** Что видно в строке. У ветки — счётчик, у листа — значение, возможно обрезанное. */\n preview: string\n /** Полное значение. Копирование берёт его, а не `preview`. */\n value: unknown\n /** Показ обрезан: копирование всё равно отдаёт целое. */\n truncated: boolean\n isLeaf: boolean\n children?: GrJsonNode[]\n}\n\nexport type GrJsonToNodesOptions = {\n /** Подпись корня. */\n rootLabel?: string\n /** Длина строки, после которой значение обрезается в показе. */\n maxStringLength?: number\n /** Сколько элементов массива разбирать до заглушки «ещё N». */\n maxArrayItems?: number\n}\n\n/** Маркер ветки, которая уже встречалась выше по этой же цепочке предков. */\nexport const CIRCULAR_MARKER = '[Circular]'\n\nconst DEFAULT_ROOT_LABEL = '$'\nconst DEFAULT_MAX_STRING_LENGTH = 200\nconst DEFAULT_MAX_ARRAY_ITEMS = 100\n\n/** Ключ, который можно дописать через точку, не сломав адрес. */\nconst PLAIN_KEY = /^[A-Za-z_$][\\w$]*$/\n\nfunction appendKey(parentPath: string, key: string): string {\n return PLAIN_KEY.test(key) ? `${parentPath}.${key}` : `${parentPath}[${JSON.stringify(key)}]`\n}\n\n/**\n * Счётчик у ветки вместо её содержимого: свёрнутый узел обязан сказать, стоит\n * ли его раскрывать. Слово не склоняется — компонент не знает языка потребителя,\n * а число само по себе читается в любом.\n */\nfunction branchPreview(kind: 'object' | 'array', count: number): string {\n return kind === 'array' ? `[${count}]` : `{${count}}`\n}\n\nfunction scalarPreview(value: unknown, maxStringLength: number): { preview: string, truncated: boolean } {\n if (typeof value === 'string') {\n if (value.length <= maxStringLength) return { preview: JSON.stringify(value), truncated: false }\n\n // Кавычка открывается и не закрывается намеренно: закрытая читалась бы как\n // конец строки, а строка продолжается — её просто не показывают целиком.\n return { preview: `${JSON.stringify(value.slice(0, maxStringLength))}…`, truncated: true }\n }\n\n // `BigInt` представления в JSON не имеет, и `JSON.stringify` на нём бросает.\n if (typeof value === 'bigint') return { preview: `${value}n`, truncated: false }\n\n if (typeof value === 'function' || typeof value === 'symbol' || value === undefined)\n return { preview: String(value), truncated: false }\n\n return { preview: JSON.stringify(value) ?? String(value), truncated: false }\n}\n\nfunction kindOf(value: unknown): GrJsonNodeKind {\n if (value === null) return 'null'\n if (Array.isArray(value)) return 'array'\n\n switch (typeof value) {\n case 'string': return 'string'\n case 'number': return 'number'\n case 'bigint': return 'number'\n case 'boolean': return 'boolean'\n case 'object': return 'object'\n // `undefined`, функция и символ представления в JSON не имеют — узел\n // остаётся, чтобы ключ не исчез из показа молча.\n case 'undefined':\n case 'function':\n case 'symbol': return 'unsupported'\n }\n\n // Недостижимо: ветки выше покрывают `typeof` целиком. Оставлено ради вывода\n // типов — без него TS не считает функцию всегда возвращающей.\n return 'unsupported'\n}\n\ntype BuildContext = {\n maxStringLength: number\n maxArrayItems: number\n /**\n * Цепочка предков текущего узла, а не все встреченные объекты.\n *\n * Разница принципиальна и отличает обход дерева от `replacer` у\n * `JSON.stringify`: тому стек недоступен, поэтому он метит `[Circular]` любую\n * повторную ссылку — в том числе объект, честно положенный в данные дважды.\n * Здесь стек есть, и маркер достаётся только настоящему циклу.\n */\n ancestors: Set<object>\n}\n\nfunction buildNode(label: string, path: string, value: unknown, context: BuildContext): GrJsonNode {\n const kind = kindOf(value)\n\n if (kind !== 'object' && kind !== 'array') {\n const { preview, truncated } = scalarPreview(value, context.maxStringLength)\n\n return { path, label, kind, preview, value, truncated, isLeaf: true }\n }\n\n const container = value as object\n\n if (context.ancestors.has(container)) {\n return { path, label, kind, preview: CIRCULAR_MARKER, value, truncated: false, isLeaf: true }\n }\n\n const entries = Array.isArray(value)\n ? value.map((item, index) => [String(index), item] as const)\n : Object.entries(container)\n\n // Пустой объект и пустой массив — листья: раскрывать нечего, а ветка без\n // детей рисует стрелку, которая ничего не делает.\n if (entries.length === 0) {\n return {\n path,\n label,\n kind,\n preview: Array.isArray(value) ? '[]' : '{}',\n value,\n truncated: false,\n isLeaf: true,\n }\n }\n\n context.ancestors.add(container)\n\n const limit = Math.max(0, context.maxArrayItems)\n const isOverLimit = Array.isArray(value) && entries.length > limit\n const visible = isOverLimit ? entries.slice(0, limit) : entries\n\n const children: GrJsonNode[] = visible.map(([key, item]) => buildNode(\n key,\n Array.isArray(value) ? `${path}[${key}]` : appendKey(path, key),\n item,\n context,\n ))\n\n if (isOverLimit) {\n const rest = entries.length - limit\n\n children.push({\n path: `${path}[…]`,\n label: '…',\n kind: 'truncation',\n preview: String(rest),\n value: (value as unknown[]).slice(limit),\n truncated: true,\n isLeaf: true,\n })\n }\n\n context.ancestors.delete(container)\n\n return {\n path,\n label,\n kind,\n preview: branchPreview(kind, entries.length),\n value,\n truncated: isOverLimit,\n isLeaf: false,\n children,\n }\n}\n\n/**\n * Значение → узлы дерева. Корень всегда один: даже скаляр на входе — это узел,\n * иначе дерево пришлось бы то показывать, то нет.\n */\nexport function jsonToNodes(value: unknown, options: GrJsonToNodesOptions = {}): GrJsonNode[] {\n const context: BuildContext = {\n maxStringLength: options.maxStringLength ?? DEFAULT_MAX_STRING_LENGTH,\n maxArrayItems: options.maxArrayItems ?? DEFAULT_MAX_ARRAY_ITEMS,\n ancestors: new Set(),\n }\n\n return [buildNode(options.rootLabel ?? DEFAULT_ROOT_LABEL, DEFAULT_ROOT_LABEL, value, context)]\n}\n\n/** Пути всех узлов до заданной глубины — ими задаётся начальная свёртка. */\nexport function pathsToDepth(nodes: GrJsonNode[], depth: number, level = 0): string[] {\n if (level >= depth) return []\n\n return nodes.flatMap(node => (\n node.children ? [node.path, ...pathsToDepth(node.children, depth, level + 1)] : []\n ))\n}\n\n/** Пути всех веток — для «раскрыть всё». */\nexport function branchPaths(nodes: GrJsonNode[]): string[] {\n return nodes.flatMap(node => (node.children ? [node.path, ...branchPaths(node.children)] : []))\n}\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\n\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrInput from '../GrInput/GrInput.vue'\nimport GrTree from '../GrTree/GrTree.vue'\nimport { useAnnouncer } from '../../composables/useAnnouncer'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\n\nimport IconCheck from '~icons/lucide/check'\nimport IconCopy from '~icons/lucide/copy'\n\nimport { branchPaths, jsonToNodes, pathsToDepth, type GrJsonNode } from './jsonToNodes'\nimport {\n jsonValueClass,\n jsonViewerContentClass,\n jsonViewerCopyClass,\n jsonViewerGroupClass,\n jsonViewerKeyClass,\n jsonViewerMutedClass,\n jsonViewerPunctuationClass,\n jsonViewerRootClass,\n jsonViewerRowClass,\n jsonViewerSearchClass,\n jsonViewerToolbarClass,\n jsonViewerValueClass,\n} from './grJsonViewerStyles'\n\nimport type { GrComponentSize } from '../shared/sizes'\n\nexport type { GrJsonNode, GrJsonNodeKind } from './jsonToNodes'\n\n/**\n * JSON со свёрткой по узлам, поиском по ключу и значению и копированием узла.\n *\n * Дерево, клавиатуру и виртуализацию несёт `GrTree`; здесь — разбор `unknown` в\n * узлы и разметка строки. Плоский текст целиком, который копируют в тикет, —\n * `GrCodeBlock`.\n */\nexport interface GrJsonViewerProps {\n /** Показываемое значение. `unknown`, потому что приходит из БД или чужого сервиса. */\n value: unknown\n /** Подпись корня. По умолчанию `$` — так адресуют путь JSONPath и dev-tools. */\n rootLabel?: string\n /** Сколько уровней раскрыто изначально. */\n defaultExpandDepth?: number\n /**\n * Длина строкового значения, после которой показ обрезается.\n *\n * Не косметика: запрос к модели с картинкой в base64 — это один лист на\n * сотни тысяч символов, и ни свёртка, ни виртуализация по строкам его не\n * берут. Копирование при этом отдаёт значение целиком.\n */\n maxStringLength?: number\n /** Сколько элементов массива разбирать до заглушки «ещё N». */\n maxArrayItems?: number\n /** Поле поиска над деревом. Выключено — поиск остаётся на `filter()` из `defineExpose`. */\n searchable?: boolean\n /** Кнопка копирования узла в строке. */\n copyable?: boolean\n /** Оставлять в DOM только окно вокруг вьюпорта. Требует `maxHeight`. */\n virtual?: boolean\n /** Высота области просмотра: число — пиксели, строка — как есть. */\n maxHeight?: string | number\n /** Имя области для скринридера. Безымянное дерево объявляется просто «дерево». */\n ariaLabel?: string\n size?: GrComponentSize\n}\n\nexport interface GrJsonViewerEmits {\n /** Узел скопирован: путь для тикета и полное значение. */\n (e: 'copy', payload: { path: string, value: unknown }): void\n}\n\nconst props = withDefaults(defineProps<GrJsonViewerProps>(), {\n // Дефолты живут в резолверах ниже: Vue подставил бы свой раньше, чем\n // компонент заглянет в `GrConfigProvider`.\n rootLabel: undefined,\n defaultExpandDepth: undefined,\n maxStringLength: undefined,\n maxArrayItems: undefined,\n searchable: undefined,\n copyable: undefined,\n virtual: false,\n maxHeight: undefined,\n ariaLabel: undefined,\n size: undefined,\n})\n\nconst emit = defineEmits<GrJsonViewerEmits>()\n\ndefineSlots<{\n /** Значение листа: ссылка, дата, денежная сумма — оформление знает приложение. */\n leaf?: (props: { node: GrJsonNode }) => unknown\n}>()\n\nconst { t } = useGranularityTranslations()\nconst { announce } = useAnnouncer()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrJsonViewer' })\nconst resolvedDepth = useGrComponentProp('GrJsonViewer', 'defaultExpandDepth', () => props.defaultExpandDepth, 1)\nconst resolvedMaxString = useGrComponentProp('GrJsonViewer', 'maxStringLength', () => props.maxStringLength, 200)\nconst resolvedMaxArray = useGrComponentProp('GrJsonViewer', 'maxArrayItems', () => props.maxArrayItems, 100)\nconst resolvedSearchable = useGrComponentProp('GrJsonViewer', 'searchable', () => props.searchable, true)\nconst resolvedCopyable = useGrComponentProp('GrJsonViewer', 'copyable', () => props.copyable, true)\n\nconst nodes = computed(() => jsonToNodes(props.value, {\n rootLabel: props.rootLabel,\n maxStringLength: resolvedMaxString.value,\n maxArrayItems: resolvedMaxArray.value,\n}))\n\n/**\n * Раскрытие ведётся подстановкой набора путей.\n *\n * Контролируемого `expandedKeys` у `GrTree` нет, но `defaultExpandedKeys` —\n * не «начальное значение»: дерево следит за пропом и заменяет набор целиком.\n * Этого хватает и глубине по умолчанию, и кнопкам «раскрыть/свернуть всё»;\n * ручное раскрытие пользователя при этом сбрасывается — от этих кнопок ровно\n * того и ждут.\n */\nconst expandedPaths = ref<string[]>([])\n\nwatch(\n [nodes, resolvedDepth],\n ([list, depth]) => { expandedPaths.value = pathsToDepth(list, depth) },\n { immediate: true },\n)\n\nfunction expandAll(): void {\n expandedPaths.value = branchPaths(nodes.value)\n}\n\nfunction collapseAll(): void {\n expandedPaths.value = []\n}\n\nconst query = ref('')\nconst treeRef = ref<{ filter: (value: string) => void } | null>(null)\n\nwatch(query, (value) => { treeRef.value?.filter(value) })\n\nfunction filter(value: string): void {\n query.value = value\n}\n\n/** Поиск идёт и по ключу, и по значению: в чужом ответе ищут то одно, то другое. */\nfunction matchKeyOrValue(value: string, data: GrJsonNode): boolean {\n if (!value) return true\n\n const needle = value.toLowerCase()\n\n return data.label.toLowerCase().includes(needle) || data.preview.toLowerCase().includes(needle)\n}\n\n/**\n * Наличие буфера уточняется после монтирования: `navigator` в теле `setup` либо\n * роняет серверный рендер, либо расходится с ним. До этого момента кнопки нет —\n * ровно то же, что отдаёт сервер, поэтому гидрация совпадает.\n */\nconst canCopy = ref(false)\n\nonMounted(() => {\n canCopy.value = typeof navigator !== 'undefined' && typeof navigator.clipboard?.writeText === 'function'\n\n if (props.virtual && props.maxHeight === undefined && __GR_DEV__) {\n console.warn('[GrJsonViewer] `virtual` без `maxHeight` не включает виртуализацию: окно считается от высоты области.')\n }\n})\n\nconst showCopy = computed(() => resolvedCopyable.value && canCopy.value)\n\nconst copiedPath = ref<string | null>(null)\nlet copiedTimer: number | null = null\n\nfunction resetCopiedTimer(): void {\n if (copiedTimer !== null) {\n clearTimeout(copiedTimer)\n copiedTimer = null\n }\n}\n\nonBeforeUnmount(resetCopiedTimer)\n\n/**\n * В буфер уходит **полное** значение узла, а не то, что видно: обрезка\n * принадлежит показу, и вставить её обратно нельзя.\n */\nasync function copyNode(node: GrJsonNode): Promise<void> {\n const text = typeof node.value === 'string' ? node.value : JSON.stringify(node.value, null, 2) ?? String(node.value)\n\n try {\n await navigator.clipboard.writeText(text)\n }\n catch {\n announce(t('gr.jsonViewer.copyFailed', 'Could not copy value'))\n return\n }\n\n copiedPath.value = node.path\n resetCopiedTimer()\n copiedTimer = window.setTimeout(() => {\n copiedPath.value = null\n copiedTimer = null\n }, 1200)\n\n announce(t('gr.jsonViewer.copied', 'Value copied'))\n emit('copy', { path: node.path, value: node.value })\n}\n\ndefineExpose({ filter, expandAll, collapseAll })\n</script>\n\n<template>\n <div data-gr-json-viewer :class=\"jsonViewerRootClass\">\n <div v-if=\"resolvedSearchable\" data-gr-json-viewer-toolbar :class=\"jsonViewerToolbarClass\">\n <GrInput\n v-model=\"query\"\n :class=\"jsonViewerSearchClass\"\n :size=\"resolvedSize\"\n clearable\n :placeholder=\"t('gr.jsonViewer.search', 'Search key or value')\"\n :aria-label=\"t('gr.jsonViewer.search', 'Search key or value')\"\n />\n\n <GrButton variant=\"ghost\" :size=\"resolvedSize\" @click=\"expandAll\">\n {{ t('gr.jsonViewer.expandAll', 'Expand all') }}\n </GrButton>\n <GrButton variant=\"ghost\" :size=\"resolvedSize\" @click=\"collapseAll\">\n {{ t('gr.jsonViewer.collapseAll', 'Collapse all') }}\n </GrButton>\n </div>\n\n <GrTree\n ref=\"treeRef\"\n :data=\"nodes\"\n node-key=\"path\"\n :props=\"{ children: 'children', label: 'label', isLeaf: 'isLeaf' }\"\n :default-expanded-keys=\"expandedPaths\"\n :filter-node-method=\"matchKeyOrValue\"\n :content-class=\"jsonViewerContentClass\"\n :virtual=\"virtual\"\n :max-height=\"maxHeight\"\n :size=\"resolvedSize\"\n :aria-label=\"ariaLabel\"\n >\n <template #default=\"{ data }\">\n <span data-gr-json-viewer-row :class=\"[jsonViewerRowClass, jsonViewerGroupClass]\">\n <span data-gr-json-viewer-key :class=\"jsonViewerKeyClass\">{{ data.label }}</span>\n <span :class=\"jsonViewerPunctuationClass\">:</span>\n\n <span\n data-gr-json-viewer-value\n :class=\"[jsonViewerValueClass, jsonValueClass[(data as GrJsonNode).kind]]\"\n >\n <slot name=\"leaf\" :node=\"data as GrJsonNode\">{{ (data as GrJsonNode).preview }}</slot>\n </span>\n\n <span\n v-if=\"(data as GrJsonNode).kind === 'truncation'\"\n :class=\"jsonViewerMutedClass\"\n >{{ t('gr.jsonViewer.moreItems', 'more') }}</span>\n\n <GrButton\n v-if=\"showCopy && (data as GrJsonNode).kind !== 'truncation'\"\n data-gr-json-viewer-copy\n variant=\"ghost\"\n size=\"xs\"\n square\n :class=\"jsonViewerCopyClass\"\n :aria-label=\"t('gr.jsonViewer.copy', 'Copy value')\"\n :title=\"t('gr.jsonViewer.copy', 'Copy value')\"\n @click.stop=\"copyNode(data as GrJsonNode)\"\n >\n <component\n :is=\"copiedPath === (data as GrJsonNode).path ? IconCheck : IconCopy\"\n class=\"h-3.5 w-3.5\"\n aria-hidden=\"true\"\n />\n </GrButton>\n </span>\n </template>\n </GrTree>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\n\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrInput from '../GrInput/GrInput.vue'\nimport GrTree from '../GrTree/GrTree.vue'\nimport { useAnnouncer } from '../../composables/useAnnouncer'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\n\nimport IconCheck from '~icons/lucide/check'\nimport IconCopy from '~icons/lucide/copy'\n\nimport { branchPaths, jsonToNodes, pathsToDepth, type GrJsonNode } from './jsonToNodes'\nimport {\n jsonValueClass,\n jsonViewerContentClass,\n jsonViewerCopyClass,\n jsonViewerGroupClass,\n jsonViewerKeyClass,\n jsonViewerMutedClass,\n jsonViewerPunctuationClass,\n jsonViewerRootClass,\n jsonViewerRowClass,\n jsonViewerSearchClass,\n jsonViewerToolbarClass,\n jsonViewerValueClass,\n} from './grJsonViewerStyles'\n\nimport type { GrComponentSize } from '../shared/sizes'\n\nexport type { GrJsonNode, GrJsonNodeKind } from './jsonToNodes'\n\n/**\n * JSON со свёрткой по узлам, поиском по ключу и значению и копированием узла.\n *\n * Дерево, клавиатуру и виртуализацию несёт `GrTree`; здесь — разбор `unknown` в\n * узлы и разметка строки. Плоский текст целиком, который копируют в тикет, —\n * `GrCodeBlock`.\n */\nexport interface GrJsonViewerProps {\n /** Показываемое значение. `unknown`, потому что приходит из БД или чужого сервиса. */\n value: unknown\n /** Подпись корня. По умолчанию `$` — так адресуют путь JSONPath и dev-tools. */\n rootLabel?: string\n /** Сколько уровней раскрыто изначально. */\n defaultExpandDepth?: number\n /**\n * Длина строкового значения, после которой показ обрезается.\n *\n * Не косметика: запрос к модели с картинкой в base64 — это один лист на\n * сотни тысяч символов, и ни свёртка, ни виртуализация по строкам его не\n * берут. Копирование при этом отдаёт значение целиком.\n */\n maxStringLength?: number\n /** Сколько элементов массива разбирать до заглушки «ещё N». */\n maxArrayItems?: number\n /** Поле поиска над деревом. Выключено — поиск остаётся на `filter()` из `defineExpose`. */\n searchable?: boolean\n /** Кнопка копирования узла в строке. */\n copyable?: boolean\n /** Оставлять в DOM только окно вокруг вьюпорта. Требует `maxHeight`. */\n virtual?: boolean\n /** Высота области просмотра: число — пиксели, строка — как есть. */\n maxHeight?: string | number\n /** Имя области для скринридера. Безымянное дерево объявляется просто «дерево». */\n ariaLabel?: string\n size?: GrComponentSize\n}\n\nexport interface GrJsonViewerEmits {\n /** Узел скопирован: путь для тикета и полное значение. */\n (e: 'copy', payload: { path: string, value: unknown }): void\n}\n\nconst props = withDefaults(defineProps<GrJsonViewerProps>(), {\n // Дефолты живут в резолверах ниже: Vue подставил бы свой раньше, чем\n // компонент заглянет в `GrConfigProvider`.\n rootLabel: undefined,\n defaultExpandDepth: undefined,\n maxStringLength: undefined,\n maxArrayItems: undefined,\n searchable: undefined,\n copyable: undefined,\n virtual: false,\n maxHeight: undefined,\n ariaLabel: undefined,\n size: undefined,\n})\n\nconst emit = defineEmits<GrJsonViewerEmits>()\n\ndefineSlots<{\n /** Значение листа: ссылка, дата, денежная сумма — оформление знает приложение. */\n leaf?: (props: { node: GrJsonNode }) => unknown\n}>()\n\nconst { t } = useGranularityTranslations()\nconst { announce } = useAnnouncer()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrJsonViewer' })\nconst resolvedDepth = useGrComponentProp('GrJsonViewer', 'defaultExpandDepth', () => props.defaultExpandDepth, 1)\nconst resolvedMaxString = useGrComponentProp('GrJsonViewer', 'maxStringLength', () => props.maxStringLength, 200)\nconst resolvedMaxArray = useGrComponentProp('GrJsonViewer', 'maxArrayItems', () => props.maxArrayItems, 100)\nconst resolvedSearchable = useGrComponentProp('GrJsonViewer', 'searchable', () => props.searchable, true)\nconst resolvedCopyable = useGrComponentProp('GrJsonViewer', 'copyable', () => props.copyable, true)\n\nconst nodes = computed(() => jsonToNodes(props.value, {\n rootLabel: props.rootLabel,\n maxStringLength: resolvedMaxString.value,\n maxArrayItems: resolvedMaxArray.value,\n}))\n\n/**\n * Раскрытие ведётся подстановкой набора путей.\n *\n * Контролируемого `expandedKeys` у `GrTree` нет, но `defaultExpandedKeys` —\n * не «начальное значение»: дерево следит за пропом и заменяет набор целиком.\n * Этого хватает и глубине по умолчанию, и кнопкам «раскрыть/свернуть всё»;\n * ручное раскрытие пользователя при этом сбрасывается — от этих кнопок ровно\n * того и ждут.\n */\nconst expandedPaths = ref<string[]>([])\n\nwatch(\n [nodes, resolvedDepth],\n ([list, depth]) => { expandedPaths.value = pathsToDepth(list, depth) },\n { immediate: true },\n)\n\nfunction expandAll(): void {\n expandedPaths.value = branchPaths(nodes.value)\n}\n\nfunction collapseAll(): void {\n expandedPaths.value = []\n}\n\nconst query = ref('')\nconst treeRef = ref<{ filter: (value: string) => void } | null>(null)\n\nwatch(query, (value) => { treeRef.value?.filter(value) })\n\nfunction filter(value: string): void {\n query.value = value\n}\n\n/** Поиск идёт и по ключу, и по значению: в чужом ответе ищут то одно, то другое. */\nfunction matchKeyOrValue(value: string, data: GrJsonNode): boolean {\n if (!value) return true\n\n const needle = value.toLowerCase()\n\n return data.label.toLowerCase().includes(needle) || data.preview.toLowerCase().includes(needle)\n}\n\n/**\n * Наличие буфера уточняется после монтирования: `navigator` в теле `setup` либо\n * роняет серверный рендер, либо расходится с ним. До этого момента кнопки нет —\n * ровно то же, что отдаёт сервер, поэтому гидрация совпадает.\n */\nconst canCopy = ref(false)\n\nonMounted(() => {\n canCopy.value = typeof navigator !== 'undefined' && typeof navigator.clipboard?.writeText === 'function'\n\n if (props.virtual && props.maxHeight === undefined && __GR_DEV__) {\n console.warn('[GrJsonViewer] `virtual` без `maxHeight` не включает виртуализацию: окно считается от высоты области.')\n }\n})\n\nconst showCopy = computed(() => resolvedCopyable.value && canCopy.value)\n\nconst copiedPath = ref<string | null>(null)\nlet copiedTimer: number | null = null\n\nfunction resetCopiedTimer(): void {\n if (copiedTimer !== null) {\n clearTimeout(copiedTimer)\n copiedTimer = null\n }\n}\n\nonBeforeUnmount(resetCopiedTimer)\n\n/**\n * В буфер уходит **полное** значение узла, а не то, что видно: обрезка\n * принадлежит показу, и вставить её обратно нельзя.\n */\nasync function copyNode(node: GrJsonNode): Promise<void> {\n const text = typeof node.value === 'string' ? node.value : JSON.stringify(node.value, null, 2) ?? String(node.value)\n\n try {\n await navigator.clipboard.writeText(text)\n }\n catch {\n announce(t('gr.jsonViewer.copyFailed', 'Could not copy value'))\n return\n }\n\n copiedPath.value = node.path\n resetCopiedTimer()\n copiedTimer = window.setTimeout(() => {\n copiedPath.value = null\n copiedTimer = null\n }, 1200)\n\n announce(t('gr.jsonViewer.copied', 'Value copied'))\n emit('copy', { path: node.path, value: node.value })\n}\n\ndefineExpose({ filter, expandAll, collapseAll })\n</script>\n\n<template>\n <div data-gr-json-viewer :class=\"jsonViewerRootClass\">\n <div v-if=\"resolvedSearchable\" data-gr-json-viewer-toolbar :class=\"jsonViewerToolbarClass\">\n <GrInput\n v-model=\"query\"\n :class=\"jsonViewerSearchClass\"\n :size=\"resolvedSize\"\n clearable\n :placeholder=\"t('gr.jsonViewer.search', 'Search key or value')\"\n :aria-label=\"t('gr.jsonViewer.search', 'Search key or value')\"\n />\n\n <GrButton variant=\"ghost\" :size=\"resolvedSize\" @click=\"expandAll\">\n {{ t('gr.jsonViewer.expandAll', 'Expand all') }}\n </GrButton>\n <GrButton variant=\"ghost\" :size=\"resolvedSize\" @click=\"collapseAll\">\n {{ t('gr.jsonViewer.collapseAll', 'Collapse all') }}\n </GrButton>\n </div>\n\n <GrTree\n ref=\"treeRef\"\n :data=\"nodes\"\n node-key=\"path\"\n :props=\"{ children: 'children', label: 'label', isLeaf: 'isLeaf' }\"\n :default-expanded-keys=\"expandedPaths\"\n :filter-node-method=\"matchKeyOrValue\"\n :content-class=\"jsonViewerContentClass\"\n :virtual=\"virtual\"\n :max-height=\"maxHeight\"\n :size=\"resolvedSize\"\n :aria-label=\"ariaLabel\"\n >\n <template #default=\"{ data }\">\n <span data-gr-json-viewer-row :class=\"[jsonViewerRowClass, jsonViewerGroupClass]\">\n <span data-gr-json-viewer-key :class=\"jsonViewerKeyClass\">{{ data.label }}</span>\n <span :class=\"jsonViewerPunctuationClass\">:</span>\n\n <span\n data-gr-json-viewer-value\n :class=\"[jsonViewerValueClass, jsonValueClass[(data as GrJsonNode).kind]]\"\n >\n <slot name=\"leaf\" :node=\"data as GrJsonNode\">{{ (data as GrJsonNode).preview }}</slot>\n </span>\n\n <span\n v-if=\"(data as GrJsonNode).kind === 'truncation'\"\n :class=\"jsonViewerMutedClass\"\n >{{ t('gr.jsonViewer.moreItems', 'more') }}</span>\n\n <GrButton\n v-if=\"showCopy && (data as GrJsonNode).kind !== 'truncation'\"\n data-gr-json-viewer-copy\n variant=\"ghost\"\n size=\"xs\"\n square\n :class=\"jsonViewerCopyClass\"\n :aria-label=\"t('gr.jsonViewer.copy', 'Copy value')\"\n :title=\"t('gr.jsonViewer.copy', 'Copy value')\"\n @click.stop=\"copyNode(data as GrJsonNode)\"\n >\n <component\n :is=\"copiedPath === (data as GrJsonNode).path ? IconCheck : IconCopy\"\n class=\"h-3.5 w-3.5\"\n aria-hidden=\"true\"\n />\n </GrButton>\n </span>\n </template>\n </GrTree>\n </div>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"GrJsonViewer-BoldivFc.js","names":[],"sources":["../../../../src/components/GrJsonViewer/jsonToNodes.ts","../../../../src/components/GrJsonViewer/GrJsonViewer.vue","../../../../src/components/GrJsonViewer/GrJsonViewer.vue"],"sourcesContent":["/**\n * Разбор произвольного значения в узлы дерева.\n *\n * Компонент показывает данные из БД и от чужих сервисов, поэтому обход обязан\n * пережить всё: цикл, `BigInt`, функцию в поле, массив на десять тысяч записей,\n * строку в миллион символов. Уронить страницу он права не имеет.\n */\n\n/** Что за значение в узле. Решает и цвет, и то, раскрывается ли узел. */\nexport type GrJsonNodeKind =\n | 'object'\n | 'array'\n | 'string'\n | 'number'\n | 'boolean'\n | 'null'\n /** Значения, которых в JSON не бывает: функция, символ, `undefined`. */\n | 'unsupported'\n /** Хвост обрезанного массива — «ещё N элементов». Своего значения не имеет. */\n | 'truncation'\n\nexport type GrJsonNode = {\n /** Читаемый адрес узла (`$.items[3].name`). Он же ключ для `GrTree`. */\n path: string\n /** Имя ключа или индекс. Показывается слева от двоеточия. */\n label: string\n kind: GrJsonNodeKind\n /** Что видно в строке. У ветки — счётчик, у листа — значение, возможно обрезанное. */\n preview: string\n /** Полное значение. Копирование берёт его, а не `preview`. */\n value: unknown\n /** Показ обрезан: копирование всё равно отдаёт целое. */\n truncated: boolean\n isLeaf: boolean\n children?: GrJsonNode[]\n}\n\nexport type GrJsonToNodesOptions = {\n /** Подпись корня. */\n rootLabel?: string\n /** Длина строки, после которой значение обрезается в показе. */\n maxStringLength?: number\n /** Сколько элементов массива разбирать до заглушки «ещё N». */\n maxArrayItems?: number\n}\n\n/** Маркер ветки, которая уже встречалась выше по этой же цепочке предков. */\nexport const CIRCULAR_MARKER = '[Circular]'\n\nconst DEFAULT_ROOT_LABEL = '$'\nconst DEFAULT_MAX_STRING_LENGTH = 200\nconst DEFAULT_MAX_ARRAY_ITEMS = 100\n\n/** Ключ, который можно дописать через точку, не сломав адрес. */\nconst PLAIN_KEY = /^[A-Za-z_$][\\w$]*$/\n\nfunction appendKey(parentPath: string, key: string): string {\n return PLAIN_KEY.test(key) ? `${parentPath}.${key}` : `${parentPath}[${JSON.stringify(key)}]`\n}\n\n/**\n * Счётчик у ветки вместо её содержимого: свёрнутый узел обязан сказать, стоит\n * ли его раскрывать. Слово не склоняется — компонент не знает языка потребителя,\n * а число само по себе читается в любом.\n */\nfunction branchPreview(kind: 'object' | 'array', count: number): string {\n return kind === 'array' ? `[${count}]` : `{${count}}`\n}\n\nfunction scalarPreview(value: unknown, maxStringLength: number): { preview: string, truncated: boolean } {\n if (typeof value === 'string') {\n if (value.length <= maxStringLength) return { preview: JSON.stringify(value), truncated: false }\n\n // Кавычка открывается и не закрывается намеренно: закрытая читалась бы как\n // конец строки, а строка продолжается — её просто не показывают целиком.\n return { preview: `${JSON.stringify(value.slice(0, maxStringLength))}…`, truncated: true }\n }\n\n // `BigInt` представления в JSON не имеет, и `JSON.stringify` на нём бросает.\n if (typeof value === 'bigint') return { preview: `${value}n`, truncated: false }\n\n if (typeof value === 'function' || typeof value === 'symbol' || value === undefined)\n return { preview: String(value), truncated: false }\n\n return { preview: JSON.stringify(value) ?? String(value), truncated: false }\n}\n\nfunction kindOf(value: unknown): GrJsonNodeKind {\n if (value === null) return 'null'\n if (Array.isArray(value)) return 'array'\n\n switch (typeof value) {\n case 'string': return 'string'\n case 'number': return 'number'\n case 'bigint': return 'number'\n case 'boolean': return 'boolean'\n case 'object': return 'object'\n // `undefined`, функция и символ представления в JSON не имеют — узел\n // остаётся, чтобы ключ не исчез из показа молча.\n case 'undefined':\n case 'function':\n case 'symbol': return 'unsupported'\n }\n\n // Недостижимо: ветки выше покрывают `typeof` целиком. Оставлено ради вывода\n // типов — без него TS не считает функцию всегда возвращающей.\n return 'unsupported'\n}\n\ntype BuildContext = {\n maxStringLength: number\n maxArrayItems: number\n /**\n * Цепочка предков текущего узла, а не все встреченные объекты.\n *\n * Разница принципиальна и отличает обход дерева от `replacer` у\n * `JSON.stringify`: тому стек недоступен, поэтому он метит `[Circular]` любую\n * повторную ссылку — в том числе объект, честно положенный в данные дважды.\n * Здесь стек есть, и маркер достаётся только настоящему циклу.\n */\n ancestors: Set<object>\n}\n\nfunction buildNode(label: string, path: string, value: unknown, context: BuildContext): GrJsonNode {\n const kind = kindOf(value)\n\n if (kind !== 'object' && kind !== 'array') {\n const { preview, truncated } = scalarPreview(value, context.maxStringLength)\n\n return { path, label, kind, preview, value, truncated, isLeaf: true }\n }\n\n const container = value as object\n\n if (context.ancestors.has(container)) {\n return { path, label, kind, preview: CIRCULAR_MARKER, value, truncated: false, isLeaf: true }\n }\n\n const entries = Array.isArray(value)\n ? value.map((item, index) => [String(index), item] as const)\n : Object.entries(container)\n\n // Пустой объект и пустой массив — листья: раскрывать нечего, а ветка без\n // детей рисует стрелку, которая ничего не делает.\n if (entries.length === 0) {\n return {\n path,\n label,\n kind,\n preview: Array.isArray(value) ? '[]' : '{}',\n value,\n truncated: false,\n isLeaf: true,\n }\n }\n\n context.ancestors.add(container)\n\n const limit = Math.max(0, context.maxArrayItems)\n const isOverLimit = Array.isArray(value) && entries.length > limit\n const visible = isOverLimit ? entries.slice(0, limit) : entries\n\n const children: GrJsonNode[] = visible.map(([key, item]) => buildNode(\n key,\n Array.isArray(value) ? `${path}[${key}]` : appendKey(path, key),\n item,\n context,\n ))\n\n if (isOverLimit) {\n const rest = entries.length - limit\n\n children.push({\n path: `${path}[…]`,\n label: '…',\n kind: 'truncation',\n preview: String(rest),\n value: (value as unknown[]).slice(limit),\n truncated: true,\n isLeaf: true,\n })\n }\n\n context.ancestors.delete(container)\n\n return {\n path,\n label,\n kind,\n preview: branchPreview(kind, entries.length),\n value,\n truncated: isOverLimit,\n isLeaf: false,\n children,\n }\n}\n\n/**\n * Значение → узлы дерева. Корень всегда один: даже скаляр на входе — это узел,\n * иначе дерево пришлось бы то показывать, то нет.\n */\nexport function jsonToNodes(value: unknown, options: GrJsonToNodesOptions = {}): GrJsonNode[] {\n const context: BuildContext = {\n maxStringLength: options.maxStringLength ?? DEFAULT_MAX_STRING_LENGTH,\n maxArrayItems: options.maxArrayItems ?? DEFAULT_MAX_ARRAY_ITEMS,\n ancestors: new Set(),\n }\n\n return [buildNode(options.rootLabel ?? DEFAULT_ROOT_LABEL, DEFAULT_ROOT_LABEL, value, context)]\n}\n\n/** Пути всех узлов до заданной глубины — ими задаётся начальная свёртка. */\nexport function pathsToDepth(nodes: GrJsonNode[], depth: number, level = 0): string[] {\n if (level >= depth) return []\n\n return nodes.flatMap(node => (\n node.children ? [node.path, ...pathsToDepth(node.children, depth, level + 1)] : []\n ))\n}\n\n/** Пути всех веток — для «раскрыть всё». */\nexport function branchPaths(nodes: GrJsonNode[]): string[] {\n return nodes.flatMap(node => (node.children ? [node.path, ...branchPaths(node.children)] : []))\n}\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\n\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrInput from '../GrInput/GrInput.vue'\nimport GrTree from '../GrTree/GrTree.vue'\nimport { useAnnouncer } from '../../composables/useAnnouncer'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\n\nimport IconCheck from '~icons/lucide/check'\nimport IconCopy from '~icons/lucide/copy'\n\nimport { branchPaths, jsonToNodes, pathsToDepth, type GrJsonNode } from './jsonToNodes'\nimport {\n jsonValueClass,\n jsonViewerContentClass,\n jsonViewerCopyClass,\n jsonViewerGroupClass,\n jsonViewerKeyClass,\n jsonViewerMutedClass,\n jsonViewerPunctuationClass,\n jsonViewerRootClass,\n jsonViewerRowClass,\n jsonViewerSearchClass,\n jsonViewerToolbarClass,\n jsonViewerValueClass,\n} from './grJsonViewerStyles'\n\nimport type { GrComponentSize } from '../shared/sizes'\n\nexport type { GrJsonNode, GrJsonNodeKind } from './jsonToNodes'\n\n/**\n * JSON со свёрткой по узлам, поиском по ключу и значению и копированием узла.\n *\n * Дерево, клавиатуру и виртуализацию несёт `GrTree`; здесь — разбор `unknown` в\n * узлы и разметка строки. Плоский текст целиком, который копируют в тикет, —\n * `GrCodeBlock`.\n */\nexport interface GrJsonViewerProps {\n /** Показываемое значение. `unknown`, потому что приходит из БД или чужого сервиса. */\n value: unknown\n /** Подпись корня. По умолчанию `$` — так адресуют путь JSONPath и dev-tools. */\n rootLabel?: string\n /** Сколько уровней раскрыто изначально. */\n defaultExpandDepth?: number\n /**\n * Длина строкового значения, после которой показ обрезается.\n *\n * Не косметика: запрос к модели с картинкой в base64 — это один лист на\n * сотни тысяч символов, и ни свёртка, ни виртуализация по строкам его не\n * берут. Копирование при этом отдаёт значение целиком.\n */\n maxStringLength?: number\n /** Сколько элементов массива разбирать до заглушки «ещё N». */\n maxArrayItems?: number\n /** Поле поиска над деревом. Выключено — поиск остаётся на `filter()` из `defineExpose`. */\n searchable?: boolean\n /** Кнопка копирования узла в строке. */\n copyable?: boolean\n /** Оставлять в DOM только окно вокруг вьюпорта. Требует `maxHeight`. */\n virtual?: boolean\n /** Высота области просмотра: число — пиксели, строка — как есть. */\n maxHeight?: string | number\n /** Имя области для скринридера. Безымянное дерево объявляется просто «дерево». */\n ariaLabel?: string\n size?: GrComponentSize\n}\n\nexport interface GrJsonViewerEmits {\n /** Узел скопирован: путь для тикета и полное значение. */\n (e: 'copy', payload: { path: string, value: unknown }): void\n}\n\nconst props = withDefaults(defineProps<GrJsonViewerProps>(), {\n // Дефолты живут в резолверах ниже: Vue подставил бы свой раньше, чем\n // компонент заглянет в `GrConfigProvider`.\n rootLabel: undefined,\n defaultExpandDepth: undefined,\n maxStringLength: undefined,\n maxArrayItems: undefined,\n searchable: undefined,\n copyable: undefined,\n virtual: false,\n maxHeight: undefined,\n ariaLabel: undefined,\n size: undefined,\n})\n\nconst emit = defineEmits<GrJsonViewerEmits>()\n\ndefineSlots<{\n /** Значение листа: ссылка, дата, денежная сумма — оформление знает приложение. */\n leaf?: (props: { node: GrJsonNode }) => unknown\n}>()\n\nconst { t } = useGranularityTranslations()\nconst { announce } = useAnnouncer()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrJsonViewer' })\nconst resolvedDepth = useGrComponentProp('GrJsonViewer', 'defaultExpandDepth', () => props.defaultExpandDepth, 1)\nconst resolvedMaxString = useGrComponentProp('GrJsonViewer', 'maxStringLength', () => props.maxStringLength, 200)\nconst resolvedMaxArray = useGrComponentProp('GrJsonViewer', 'maxArrayItems', () => props.maxArrayItems, 100)\nconst resolvedSearchable = useGrComponentProp('GrJsonViewer', 'searchable', () => props.searchable, true)\nconst resolvedCopyable = useGrComponentProp('GrJsonViewer', 'copyable', () => props.copyable, true)\n\nconst nodes = computed(() => jsonToNodes(props.value, {\n rootLabel: props.rootLabel,\n maxStringLength: resolvedMaxString.value,\n maxArrayItems: resolvedMaxArray.value,\n}))\n\n/**\n * Раскрытие ведётся подстановкой набора путей.\n *\n * Контролируемого `expandedKeys` у `GrTree` нет, но `defaultExpandedKeys` —\n * не «начальное значение»: дерево следит за пропом и заменяет набор целиком.\n * Этого хватает и глубине по умолчанию, и кнопкам «раскрыть/свернуть всё»;\n * ручное раскрытие пользователя при этом сбрасывается — от этих кнопок ровно\n * того и ждут.\n */\nconst expandedPaths = ref<string[]>([])\n\nwatch(\n [nodes, resolvedDepth],\n ([list, depth]) => { expandedPaths.value = pathsToDepth(list, depth) },\n { immediate: true },\n)\n\nfunction expandAll(): void {\n expandedPaths.value = branchPaths(nodes.value)\n}\n\nfunction collapseAll(): void {\n expandedPaths.value = []\n}\n\nconst query = ref('')\nconst treeRef = ref<{ filter: (value: string) => void } | null>(null)\n\nwatch(query, (value) => { treeRef.value?.filter(value) })\n\nfunction filter(value: string): void {\n query.value = value\n}\n\n/** Поиск идёт и по ключу, и по значению: в чужом ответе ищут то одно, то другое. */\nfunction matchKeyOrValue(value: string, data: GrJsonNode): boolean {\n if (!value) return true\n\n const needle = value.toLowerCase()\n\n return data.label.toLowerCase().includes(needle) || data.preview.toLowerCase().includes(needle)\n}\n\n/**\n * Наличие буфера уточняется после монтирования: `navigator` в теле `setup` либо\n * роняет серверный рендер, либо расходится с ним. До этого момента кнопки нет —\n * ровно то же, что отдаёт сервер, поэтому гидрация совпадает.\n */\nconst canCopy = ref(false)\n\nonMounted(() => {\n canCopy.value = typeof navigator !== 'undefined' && typeof navigator.clipboard?.writeText === 'function'\n\n if (props.virtual && props.maxHeight === undefined && __GR_DEV__) {\n console.warn('[GrJsonViewer] `virtual` без `maxHeight` не включает виртуализацию: окно считается от высоты области.')\n }\n})\n\nconst showCopy = computed(() => resolvedCopyable.value && canCopy.value)\n\nconst copiedPath = ref<string | null>(null)\nlet copiedTimer: number | null = null\n\nfunction resetCopiedTimer(): void {\n if (copiedTimer !== null) {\n clearTimeout(copiedTimer)\n copiedTimer = null\n }\n}\n\nonBeforeUnmount(resetCopiedTimer)\n\n/**\n * В буфер уходит **полное** значение узла, а не то, что видно: обрезка\n * принадлежит показу, и вставить её обратно нельзя.\n */\nasync function copyNode(node: GrJsonNode): Promise<void> {\n const text = typeof node.value === 'string' ? node.value : JSON.stringify(node.value, null, 2) ?? String(node.value)\n\n try {\n await navigator.clipboard.writeText(text)\n }\n catch {\n announce(t('gr.jsonViewer.copyFailed', 'Could not copy value'))\n return\n }\n\n copiedPath.value = node.path\n resetCopiedTimer()\n copiedTimer = window.setTimeout(() => {\n copiedPath.value = null\n copiedTimer = null\n }, 1200)\n\n announce(t('gr.jsonViewer.copied', 'Value copied'))\n emit('copy', { path: node.path, value: node.value })\n}\n\ndefineExpose({ filter, expandAll, collapseAll })\n</script>\n\n<template>\n <div data-gr-json-viewer :class=\"jsonViewerRootClass\">\n <div v-if=\"resolvedSearchable\" data-gr-json-viewer-toolbar :class=\"jsonViewerToolbarClass\">\n <GrInput\n v-model=\"query\"\n :class=\"jsonViewerSearchClass\"\n :size=\"resolvedSize\"\n clearable\n :placeholder=\"t('gr.jsonViewer.search', 'Search key or value')\"\n :aria-label=\"t('gr.jsonViewer.search', 'Search key or value')\"\n />\n\n <GrButton variant=\"ghost\" :size=\"resolvedSize\" @click=\"expandAll\">\n {{ t('gr.jsonViewer.expandAll', 'Expand all') }}\n </GrButton>\n <GrButton variant=\"ghost\" :size=\"resolvedSize\" @click=\"collapseAll\">\n {{ t('gr.jsonViewer.collapseAll', 'Collapse all') }}\n </GrButton>\n </div>\n\n <GrTree\n ref=\"treeRef\"\n :data=\"nodes\"\n node-key=\"path\"\n :props=\"{ children: 'children', label: 'label', isLeaf: 'isLeaf' }\"\n :default-expanded-keys=\"expandedPaths\"\n :filter-node-method=\"matchKeyOrValue\"\n :content-class=\"jsonViewerContentClass\"\n :virtual=\"virtual\"\n :max-height=\"maxHeight\"\n :size=\"resolvedSize\"\n :aria-label=\"ariaLabel\"\n >\n <template #default=\"{ data }\">\n <span data-gr-json-viewer-row :class=\"[jsonViewerRowClass, jsonViewerGroupClass]\">\n <span data-gr-json-viewer-key :class=\"jsonViewerKeyClass\">{{ data.label }}</span>\n <span :class=\"jsonViewerPunctuationClass\">:</span>\n\n <span\n data-gr-json-viewer-value\n :class=\"[jsonViewerValueClass, jsonValueClass[(data as GrJsonNode).kind]]\"\n >\n <slot name=\"leaf\" :node=\"data as GrJsonNode\">{{ (data as GrJsonNode).preview }}</slot>\n </span>\n\n <span\n v-if=\"(data as GrJsonNode).kind === 'truncation'\"\n :class=\"jsonViewerMutedClass\"\n >{{ t('gr.jsonViewer.moreItems', 'more') }}</span>\n\n <GrButton\n v-if=\"showCopy && (data as GrJsonNode).kind !== 'truncation'\"\n data-gr-json-viewer-copy\n variant=\"ghost\"\n size=\"xs\"\n square\n :class=\"jsonViewerCopyClass\"\n :aria-label=\"t('gr.jsonViewer.copy', 'Copy value')\"\n :title=\"t('gr.jsonViewer.copy', 'Copy value')\"\n @click.stop=\"copyNode(data as GrJsonNode)\"\n >\n <component\n :is=\"copiedPath === (data as GrJsonNode).path ? IconCheck : IconCopy\"\n class=\"h-3.5 w-3.5\"\n aria-hidden=\"true\"\n />\n </GrButton>\n </span>\n </template>\n </GrTree>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'\n\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrInput from '../GrInput/GrInput.vue'\nimport GrTree from '../GrTree/GrTree.vue'\nimport { useAnnouncer } from '../../composables/useAnnouncer'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\n\nimport IconCheck from '~icons/lucide/check'\nimport IconCopy from '~icons/lucide/copy'\n\nimport { branchPaths, jsonToNodes, pathsToDepth, type GrJsonNode } from './jsonToNodes'\nimport {\n jsonValueClass,\n jsonViewerContentClass,\n jsonViewerCopyClass,\n jsonViewerGroupClass,\n jsonViewerKeyClass,\n jsonViewerMutedClass,\n jsonViewerPunctuationClass,\n jsonViewerRootClass,\n jsonViewerRowClass,\n jsonViewerSearchClass,\n jsonViewerToolbarClass,\n jsonViewerValueClass,\n} from './grJsonViewerStyles'\n\nimport type { GrComponentSize } from '../shared/sizes'\n\nexport type { GrJsonNode, GrJsonNodeKind } from './jsonToNodes'\n\n/**\n * JSON со свёрткой по узлам, поиском по ключу и значению и копированием узла.\n *\n * Дерево, клавиатуру и виртуализацию несёт `GrTree`; здесь — разбор `unknown` в\n * узлы и разметка строки. Плоский текст целиком, который копируют в тикет, —\n * `GrCodeBlock`.\n */\nexport interface GrJsonViewerProps {\n /** Показываемое значение. `unknown`, потому что приходит из БД или чужого сервиса. */\n value: unknown\n /** Подпись корня. По умолчанию `$` — так адресуют путь JSONPath и dev-tools. */\n rootLabel?: string\n /** Сколько уровней раскрыто изначально. */\n defaultExpandDepth?: number\n /**\n * Длина строкового значения, после которой показ обрезается.\n *\n * Не косметика: запрос к модели с картинкой в base64 — это один лист на\n * сотни тысяч символов, и ни свёртка, ни виртуализация по строкам его не\n * берут. Копирование при этом отдаёт значение целиком.\n */\n maxStringLength?: number\n /** Сколько элементов массива разбирать до заглушки «ещё N». */\n maxArrayItems?: number\n /** Поле поиска над деревом. Выключено — поиск остаётся на `filter()` из `defineExpose`. */\n searchable?: boolean\n /** Кнопка копирования узла в строке. */\n copyable?: boolean\n /** Оставлять в DOM только окно вокруг вьюпорта. Требует `maxHeight`. */\n virtual?: boolean\n /** Высота области просмотра: число — пиксели, строка — как есть. */\n maxHeight?: string | number\n /** Имя области для скринридера. Безымянное дерево объявляется просто «дерево». */\n ariaLabel?: string\n size?: GrComponentSize\n}\n\nexport interface GrJsonViewerEmits {\n /** Узел скопирован: путь для тикета и полное значение. */\n (e: 'copy', payload: { path: string, value: unknown }): void\n}\n\nconst props = withDefaults(defineProps<GrJsonViewerProps>(), {\n // Дефолты живут в резолверах ниже: Vue подставил бы свой раньше, чем\n // компонент заглянет в `GrConfigProvider`.\n rootLabel: undefined,\n defaultExpandDepth: undefined,\n maxStringLength: undefined,\n maxArrayItems: undefined,\n searchable: undefined,\n copyable: undefined,\n virtual: false,\n maxHeight: undefined,\n ariaLabel: undefined,\n size: undefined,\n})\n\nconst emit = defineEmits<GrJsonViewerEmits>()\n\ndefineSlots<{\n /** Значение листа: ссылка, дата, денежная сумма — оформление знает приложение. */\n leaf?: (props: { node: GrJsonNode }) => unknown\n}>()\n\nconst { t } = useGranularityTranslations()\nconst { announce } = useAnnouncer()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrJsonViewer' })\nconst resolvedDepth = useGrComponentProp('GrJsonViewer', 'defaultExpandDepth', () => props.defaultExpandDepth, 1)\nconst resolvedMaxString = useGrComponentProp('GrJsonViewer', 'maxStringLength', () => props.maxStringLength, 200)\nconst resolvedMaxArray = useGrComponentProp('GrJsonViewer', 'maxArrayItems', () => props.maxArrayItems, 100)\nconst resolvedSearchable = useGrComponentProp('GrJsonViewer', 'searchable', () => props.searchable, true)\nconst resolvedCopyable = useGrComponentProp('GrJsonViewer', 'copyable', () => props.copyable, true)\n\nconst nodes = computed(() => jsonToNodes(props.value, {\n rootLabel: props.rootLabel,\n maxStringLength: resolvedMaxString.value,\n maxArrayItems: resolvedMaxArray.value,\n}))\n\n/**\n * Раскрытие ведётся подстановкой набора путей.\n *\n * Контролируемого `expandedKeys` у `GrTree` нет, но `defaultExpandedKeys` —\n * не «начальное значение»: дерево следит за пропом и заменяет набор целиком.\n * Этого хватает и глубине по умолчанию, и кнопкам «раскрыть/свернуть всё»;\n * ручное раскрытие пользователя при этом сбрасывается — от этих кнопок ровно\n * того и ждут.\n */\nconst expandedPaths = ref<string[]>([])\n\nwatch(\n [nodes, resolvedDepth],\n ([list, depth]) => { expandedPaths.value = pathsToDepth(list, depth) },\n { immediate: true },\n)\n\nfunction expandAll(): void {\n expandedPaths.value = branchPaths(nodes.value)\n}\n\nfunction collapseAll(): void {\n expandedPaths.value = []\n}\n\nconst query = ref('')\nconst treeRef = ref<{ filter: (value: string) => void } | null>(null)\n\nwatch(query, (value) => { treeRef.value?.filter(value) })\n\nfunction filter(value: string): void {\n query.value = value\n}\n\n/** Поиск идёт и по ключу, и по значению: в чужом ответе ищут то одно, то другое. */\nfunction matchKeyOrValue(value: string, data: GrJsonNode): boolean {\n if (!value) return true\n\n const needle = value.toLowerCase()\n\n return data.label.toLowerCase().includes(needle) || data.preview.toLowerCase().includes(needle)\n}\n\n/**\n * Наличие буфера уточняется после монтирования: `navigator` в теле `setup` либо\n * роняет серверный рендер, либо расходится с ним. До этого момента кнопки нет —\n * ровно то же, что отдаёт сервер, поэтому гидрация совпадает.\n */\nconst canCopy = ref(false)\n\nonMounted(() => {\n canCopy.value = typeof navigator !== 'undefined' && typeof navigator.clipboard?.writeText === 'function'\n\n if (props.virtual && props.maxHeight === undefined && __GR_DEV__) {\n console.warn('[GrJsonViewer] `virtual` без `maxHeight` не включает виртуализацию: окно считается от высоты области.')\n }\n})\n\nconst showCopy = computed(() => resolvedCopyable.value && canCopy.value)\n\nconst copiedPath = ref<string | null>(null)\nlet copiedTimer: number | null = null\n\nfunction resetCopiedTimer(): void {\n if (copiedTimer !== null) {\n clearTimeout(copiedTimer)\n copiedTimer = null\n }\n}\n\nonBeforeUnmount(resetCopiedTimer)\n\n/**\n * В буфер уходит **полное** значение узла, а не то, что видно: обрезка\n * принадлежит показу, и вставить её обратно нельзя.\n */\nasync function copyNode(node: GrJsonNode): Promise<void> {\n const text = typeof node.value === 'string' ? node.value : JSON.stringify(node.value, null, 2) ?? String(node.value)\n\n try {\n await navigator.clipboard.writeText(text)\n }\n catch {\n announce(t('gr.jsonViewer.copyFailed', 'Could not copy value'))\n return\n }\n\n copiedPath.value = node.path\n resetCopiedTimer()\n copiedTimer = window.setTimeout(() => {\n copiedPath.value = null\n copiedTimer = null\n }, 1200)\n\n announce(t('gr.jsonViewer.copied', 'Value copied'))\n emit('copy', { path: node.path, value: node.value })\n}\n\ndefineExpose({ filter, expandAll, collapseAll })\n</script>\n\n<template>\n <div data-gr-json-viewer :class=\"jsonViewerRootClass\">\n <div v-if=\"resolvedSearchable\" data-gr-json-viewer-toolbar :class=\"jsonViewerToolbarClass\">\n <GrInput\n v-model=\"query\"\n :class=\"jsonViewerSearchClass\"\n :size=\"resolvedSize\"\n clearable\n :placeholder=\"t('gr.jsonViewer.search', 'Search key or value')\"\n :aria-label=\"t('gr.jsonViewer.search', 'Search key or value')\"\n />\n\n <GrButton variant=\"ghost\" :size=\"resolvedSize\" @click=\"expandAll\">\n {{ t('gr.jsonViewer.expandAll', 'Expand all') }}\n </GrButton>\n <GrButton variant=\"ghost\" :size=\"resolvedSize\" @click=\"collapseAll\">\n {{ t('gr.jsonViewer.collapseAll', 'Collapse all') }}\n </GrButton>\n </div>\n\n <GrTree\n ref=\"treeRef\"\n :data=\"nodes\"\n node-key=\"path\"\n :props=\"{ children: 'children', label: 'label', isLeaf: 'isLeaf' }\"\n :default-expanded-keys=\"expandedPaths\"\n :filter-node-method=\"matchKeyOrValue\"\n :content-class=\"jsonViewerContentClass\"\n :virtual=\"virtual\"\n :max-height=\"maxHeight\"\n :size=\"resolvedSize\"\n :aria-label=\"ariaLabel\"\n >\n <template #default=\"{ data }\">\n <span data-gr-json-viewer-row :class=\"[jsonViewerRowClass, jsonViewerGroupClass]\">\n <span data-gr-json-viewer-key :class=\"jsonViewerKeyClass\">{{ data.label }}</span>\n <span :class=\"jsonViewerPunctuationClass\">:</span>\n\n <span\n data-gr-json-viewer-value\n :class=\"[jsonViewerValueClass, jsonValueClass[(data as GrJsonNode).kind]]\"\n >\n <slot name=\"leaf\" :node=\"data as GrJsonNode\">{{ (data as GrJsonNode).preview }}</slot>\n </span>\n\n <span\n v-if=\"(data as GrJsonNode).kind === 'truncation'\"\n :class=\"jsonViewerMutedClass\"\n >{{ t('gr.jsonViewer.moreItems', 'more') }}</span>\n\n <GrButton\n v-if=\"showCopy && (data as GrJsonNode).kind !== 'truncation'\"\n data-gr-json-viewer-copy\n variant=\"ghost\"\n size=\"xs\"\n square\n :class=\"jsonViewerCopyClass\"\n :aria-label=\"t('gr.jsonViewer.copy', 'Copy value')\"\n :title=\"t('gr.jsonViewer.copy', 'Copy value')\"\n @click.stop=\"copyNode(data as GrJsonNode)\"\n >\n <component\n :is=\"copiedPath === (data as GrJsonNode).path ? IconCheck : IconCopy\"\n class=\"h-3.5 w-3.5\"\n aria-hidden=\"true\"\n />\n </GrButton>\n </span>\n </template>\n </GrTree>\n </div>\n</template>\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- import { t as GrJsonViewer_default } from "./chunks/GrJsonViewer-ClTvjlFx.js";
1
+ import { t as GrJsonViewer_default } from "./chunks/GrJsonViewer-BoldivFc.js";
2
2
  import { n as grJsonViewerSafelist, t as grJsonViewerConfig } from "../../chunks/config-DpAYidyY2.js";
3
3
  export { GrJsonViewer_default as GrJsonViewer, GrJsonViewer_default as default, grJsonViewerConfig, grJsonViewerSafelist };
@@ -1,7 +1,7 @@
1
1
  import { a as useGrComponentSize } from "../../../chunks/context-CTbuwcYs.js";
2
2
  import { n as useGranularityTranslations } from "../../../chunks/granularityI18n-BC5zsj_d.js";
3
3
  import { t as GrButton_default } from "../../GrButton/chunks/GrButton-Bw2AH4uX.js";
4
- import { t as GrSelect_default } from "../../GrSelect/chunks/GrSelect-BE1VNTiY.js";
4
+ import { t as GrSelect_default } from "../../GrSelect/chunks/GrSelect-D-KU9Lx_.js";
5
5
  import { a as labelSizes, c as pageSizeSelectWidths, i as jumperSizes, l as pageSizes, o as navButtonSizes, r as ellipsisSizes, s as pageListGaps, u as rowGaps } from "../../../chunks/config-CjKpUcmy.js";
6
6
  import { Fragment, computed, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, normalizeClass, openBlock, ref, renderList, renderSlot, toDisplayString, unref, vModelText, watch, withCtx, withDirectives, withKeys } from "vue";
7
7
  //#region src/components/GrPagination/GrPagination.vue?vue&type=script&setup=true&lang.ts
@@ -1 +1 @@
1
- {"version":3,"file":"GrPagination-CaNTZc-v.js","names":[],"sources":["../../../../src/components/GrPagination/GrPagination.vue","../../../../src/components/GrPagination/GrPagination.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrSelect from '../GrSelect/GrSelect.vue'\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport {\n type GrPaginationSize,\n ellipsisSizes,\n jumperSizes,\n labelSizes,\n navButtonSizes,\n pageListGaps,\n pageSizes,\n pageSizeSelectWidths,\n rowGaps,\n} from './grPaginationStyles'\n\n/**\n * Пропы пагинации.\n *\n * `total` интерпретируется как общее количество элементов; число страниц\n * вычисляется из `total` и `pageSize`. Номера усекаются многоточием\n * (алгоритм boundary/sibling, как у MUI): всегда видны первая/последняя страница\n * и `siblingCount` соседей вокруг текущей.\n */\nexport interface GrPaginationProps {\n page: number\n pageSize: number\n total: number\n pageSizes?: number[]\n /** Сколько соседних страниц показывать вокруг текущей. По умолчанию `1`. */\n siblingCount?: number\n /** Сколько крайних страниц всегда показывать с каждого края. По умолчанию `1`. */\n boundaryCount?: number\n /**\n * Компактный вариант: вместо нумерованных страниц показывается индикатор\n * «текущая / всего» — удобно для узких мест (мобайл, тулбары таблиц).\n */\n compact?: boolean\n /** Показывать поле «перейти к странице» с быстрым переходом по вводу номера. */\n showJumper?: boolean\n /** Показывать селект размера страницы. */\n showPageSize?: boolean\n /** Показывать диапазон показанных элементов — «1–20 из 137». Слот `#total` сильнее. */\n showTotal?: boolean\n /** i18n-подпись перед полем перехода. По умолчанию — `gr.pagination.jumpTo`. */\n jumperLabel?: string\n /**\n * Имя навигационного лендмарка. По умолчанию — `gr.pagination.label`; задавать\n * стоит там, где пагинаций на странице несколько и их надо различать.\n */\n ariaLabel?: string\n /** Гасит всю пагинацию: номера, кнопки, селект размера и поле перехода. */\n disabled?: boolean\n size?: GrPaginationSize\n}\n\nexport interface GrPaginationEmits {\n (e: 'update:page', value: number): void\n (e: 'update:pageSize', value: number): void\n}\n\nconst { t } = useGranularityTranslations()\n\nconst props = withDefaults(defineProps<GrPaginationProps>(), {\n pageSizes: () => [10, 20, 50],\n siblingCount: 1,\n boundaryCount: 1,\n compact: false,\n showJumper: false,\n showPageSize: false,\n showTotal: false,\n jumperLabel: undefined,\n ariaLabel: undefined,\n disabled: false,\n size: undefined,\n})\n\ndefineSlots<{\n /** Диапазон показанных элементов целиком — вместо строки из локали. */\n total?: (props: { from: number, to: number, total: number }) => unknown\n}>()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrPagination' })\n\nconst rowClass = computed(() => rowGaps[resolvedSize.value])\nconst pageListClass = computed(() => pageListGaps[resolvedSize.value])\nconst pageClass = computed(() => pageSizes[resolvedSize.value])\nconst jumperClass = computed(() => jumperSizes[resolvedSize.value])\nconst ellipsisClass = computed(() => ellipsisSizes[resolvedSize.value])\nconst labelClass = computed(() => labelSizes[resolvedSize.value])\nconst selectWrapClass = computed(() => pageSizeSelectWidths[resolvedSize.value])\nconst navButtonSize = computed(() => navButtonSizes[resolvedSize.value])\n\nconst emit = defineEmits<GrPaginationEmits>()\n\n// Делитель клампится: `pageSize: 0` дал бы `Infinity` страниц и бесконечный\n// цикл в `range()`.\nconst resolvedPageSize = computed(() => Math.max(1, Math.trunc(props.pageSize)))\nconst pageCount = computed(() => Math.max(1, Math.ceil(props.total / resolvedPageSize.value)))\n\n// Рендер идёт от зажатого номера: вотчер ниже просит родителя подтянуть `page`,\n// но на первом рендере он молчит, и без клампа пагинация осталась бы вовсе без\n// активной страницы.\nconst currentPage = computed(() => Math.min(Math.max(Math.trunc(props.page), 1), pageCount.value))\n\n// Контролируемый компонент: если `total`/`pageSize` уменьшились и текущая `page`\n// вышла за диапазон — просим родителя подтянуть её к последней странице.\nwatch(pageCount, (count) => {\n if (props.page > count)\n emit('update:page', count)\n})\n\nconst pageSizeModel = computed({\n get: () => String(props.pageSize),\n set: (value: string) => emit('update:pageSize', Number(value)),\n})\n\nconst pageSizeOptions = computed(() =>\n props.pageSizes.map(pageSize => ({\n value: String(pageSize),\n label: String(pageSize),\n })),\n)\n\nconst rangeFrom = computed(() => (props.total === 0 ? 0 : (currentPage.value - 1) * resolvedPageSize.value + 1))\nconst rangeTo = computed(() => Math.min(currentPage.value * resolvedPageSize.value, props.total))\n\ntype PaginationItem = number | 'ellipsis-start' | 'ellipsis-end'\n\nfunction range(start: number, end: number): number[] {\n const list: number[] = []\n for (let page = start; page <= end; page += 1)\n list.push(page)\n return list\n}\n\n// Алгоритм usePagination как в MUI: крайние `boundaryCount` страниц + `siblingCount`\n// соседей вокруг текущей, разрывы схлопываются в многоточие. Одиночный «пропущенный»\n// номер показываем как номер, а не как «…» (визуально ровнее).\nconst items = computed<PaginationItem[]>(() => {\n const count = pageCount.value\n const current = currentPage.value\n const boundaryCount = Math.max(0, props.boundaryCount)\n const siblingCount = Math.max(0, props.siblingCount)\n\n const startPages = range(1, Math.min(boundaryCount, count))\n const endPages = range(Math.max(count - boundaryCount + 1, boundaryCount + 1), count)\n\n const siblingsStart = Math.max(\n Math.min(current - siblingCount, count - boundaryCount - siblingCount * 2 - 1),\n boundaryCount + 2,\n )\n const siblingsEnd = Math.min(\n Math.max(current + siblingCount, boundaryCount + siblingCount * 2 + 2),\n endPages.length > 0 ? endPages[0] - 2 : count - 1,\n )\n\n return [\n ...startPages,\n ...(siblingsStart > boundaryCount + 2\n ? ['ellipsis-start' as const]\n : boundaryCount + 1 < count - boundaryCount\n ? [boundaryCount + 1]\n : []),\n ...range(siblingsStart, siblingsEnd),\n ...(siblingsEnd < count - boundaryCount - 1\n ? ['ellipsis-end' as const]\n : count - boundaryCount > boundaryCount\n ? [count - boundaryCount]\n : []),\n ...endPages,\n ]\n})\n\nfunction goTo(page: number): void {\n if (props.disabled)\n return\n\n emit('update:page', page)\n}\n\nfunction prev(): void {\n goTo(Math.max(1, currentPage.value - 1))\n}\n\nfunction next(): void {\n goTo(Math.min(pageCount.value, currentPage.value + 1))\n}\n\nfunction first(): void {\n goTo(1)\n}\n\nfunction last(): void {\n goTo(pageCount.value)\n}\n\n// «Перейти к странице»: клампим введённый номер к диапазону [1, pageCount]\n// и сбрасываем поле после перехода.\nconst jumperValue = ref('')\n\nfunction submitJumper(): void {\n const parsed = Number.parseInt(jumperValue.value, 10)\n jumperValue.value = ''\n if (Number.isNaN(parsed))\n return\n\n goTo(Math.min(pageCount.value, Math.max(1, parsed)))\n}\n\nconst resolvedJumperLabel = computed(() => props.jumperLabel ?? t('gr.pagination.jumpTo', 'Go to'))\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.pagination.label', 'Pagination'))\nconst statusText = computed(() =>\n t('gr.pagination.status', 'Page {page} of {count}', { page: currentPage.value, count: pageCount.value }),\n)\nconst totalText = computed(() =>\n t('gr.pagination.total', '{from}–{to} of {total}', { from: rangeFrom.value, to: rangeTo.value, total: props.total }),\n)\n</script>\n\n<template>\n <div\n class=\"flex flex-wrap items-center justify-end\"\n :class=\"rowClass\"\n data-gr-pagination\n role=\"navigation\"\n :aria-label=\"resolvedAriaLabel\"\n >\n <div\n v-if=\"showTotal\"\n data-gr-pagination-total\n class=\"text-[var(--gr-muted-fg)] tabular-nums\"\n :class=\"labelClass\"\n >\n <slot name=\"total\" :from=\"rangeFrom\" :to=\"rangeTo\" :total=\"total\">\n {{ totalText }}\n </slot>\n </div>\n\n <div v-if=\"showPageSize\" :class=\"selectWrapClass\">\n <GrSelect\n v-model=\"pageSizeModel\"\n :options=\"pageSizeOptions\"\n :size=\"resolvedSize\"\n :disabled=\"disabled\"\n :aria-label=\"t('gr.pagination.pageSize', 'Page size')\"\n />\n </div>\n\n <GrButton variant=\"ghost\" :size=\"navButtonSize\" :disabled=\"disabled || currentPage <= 1\" :aria-label=\"t('gr.pagination.first', 'First page')\" data-gr-pagination-first @click=\"first\">\n «\n </GrButton>\n\n <GrButton variant=\"ghost\" :size=\"navButtonSize\" :disabled=\"disabled || currentPage <= 1\" data-gr-pagination-prev @click=\"prev\">\n {{ t('gr.pagination.prev', 'Prev') }}\n </GrButton>\n\n <!-- Компактный вариант: индикатор «текущая / всего» вместо нумерованных страниц. -->\n <div\n v-if=\"compact\"\n data-gr-pagination-compact\n class=\"px-2 text-[var(--gr-fg)] tabular-nums\"\n :class=\"labelClass\"\n role=\"status\"\n >\n {{ currentPage }} / {{ pageCount }}\n </div>\n\n <template v-else>\n <ul class=\"flex items-center [list-style:none]\" :class=\"pageListClass\" data-gr-pagination-pages role=\"list\">\n <li v-for=\"(item, index) in items\" :key=\"index\" :aria-hidden=\"typeof item === 'string' ? 'true' : undefined\">\n <span\n v-if=\"item === 'ellipsis-start' || item === 'ellipsis-end'\"\n data-gr-pagination-ellipsis\n class=\"grid place-items-center text-[var(--gr-muted-fg)]\"\n :class=\"ellipsisClass\"\n >…</span>\n <button\n v-else\n type=\"button\"\n data-gr-pagination-page\n :disabled=\"disabled\"\n :aria-current=\"item === currentPage ? 'page' : undefined\"\n :aria-label=\"t('gr.pagination.page', 'Page {n}', { n: item })\"\n class=\"rounded-[var(--gr-radius-control)] font-600 transition-colors duration-[var(--gr-duration-fast)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)] disabled:cursor-not-allowed\"\n :class=\"[\n pageClass,\n item === currentPage\n ? 'bg-[var(--gr-primary)] text-[var(--gr-primary-fg)]'\n // Подложка тональная, а не `--gr-muted`: серый на белом фоне даёт\n // 1.10:1 и на светлой теме hover был практически не виден, хотя у\n // соседних «назад/вперёд» (ghost-кнопки) он тонирован. Ряд обязан\n // читаться одним контролом.\n : 'text-[var(--gr-muted-fg)] hover:bg-[var(--gr-accent)] hover:text-[var(--gr-accent-fg)]',\n ]\"\n @click=\"goTo(item)\"\n >\n {{ item }}\n </button>\n </li>\n </ul>\n\n <!-- В компактном режиме смену страницы объявляет видимый индикатор. -->\n <div data-gr-pagination-status role=\"status\" class=\"sr-only\">\n {{ statusText }}\n </div>\n </template>\n\n <GrButton variant=\"ghost\" :size=\"navButtonSize\" :disabled=\"disabled || currentPage >= pageCount\" data-gr-pagination-next @click=\"next\">\n {{ t('gr.pagination.next', 'Next') }}\n </GrButton>\n\n <GrButton variant=\"ghost\" :size=\"navButtonSize\" :disabled=\"disabled || currentPage >= pageCount\" :aria-label=\"t('gr.pagination.last', 'Last page')\" data-gr-pagination-last @click=\"last\">\n »\n </GrButton>\n\n <!-- «Перейти к странице»: Enter или blur применяют введённый номер. -->\n <div v-if=\"showJumper\" class=\"flex items-center gap-2 text-[var(--gr-muted-fg)]\" :class=\"labelClass\">\n <span>{{ resolvedJumperLabel }}</span>\n <input\n v-model=\"jumperValue\"\n type=\"number\"\n min=\"1\"\n :max=\"pageCount\"\n inputmode=\"numeric\"\n data-gr-pagination-jumper\n :disabled=\"disabled\"\n :aria-label=\"resolvedJumperLabel\"\n class=\"rounded-[var(--gr-radius-control)] border border-[var(--gr-brd)] bg-[var(--gr-bg)] px-2 text-center text-[var(--gr-fg)] tabular-nums transition-colors duration-[var(--gr-duration-fast)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)] disabled:cursor-not-allowed\"\n :class=\"jumperClass\"\n @keydown.enter=\"submitJumper\"\n @blur=\"submitJumper\"\n >\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrSelect from '../GrSelect/GrSelect.vue'\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport {\n type GrPaginationSize,\n ellipsisSizes,\n jumperSizes,\n labelSizes,\n navButtonSizes,\n pageListGaps,\n pageSizes,\n pageSizeSelectWidths,\n rowGaps,\n} from './grPaginationStyles'\n\n/**\n * Пропы пагинации.\n *\n * `total` интерпретируется как общее количество элементов; число страниц\n * вычисляется из `total` и `pageSize`. Номера усекаются многоточием\n * (алгоритм boundary/sibling, как у MUI): всегда видны первая/последняя страница\n * и `siblingCount` соседей вокруг текущей.\n */\nexport interface GrPaginationProps {\n page: number\n pageSize: number\n total: number\n pageSizes?: number[]\n /** Сколько соседних страниц показывать вокруг текущей. По умолчанию `1`. */\n siblingCount?: number\n /** Сколько крайних страниц всегда показывать с каждого края. По умолчанию `1`. */\n boundaryCount?: number\n /**\n * Компактный вариант: вместо нумерованных страниц показывается индикатор\n * «текущая / всего» — удобно для узких мест (мобайл, тулбары таблиц).\n */\n compact?: boolean\n /** Показывать поле «перейти к странице» с быстрым переходом по вводу номера. */\n showJumper?: boolean\n /** Показывать селект размера страницы. */\n showPageSize?: boolean\n /** Показывать диапазон показанных элементов — «1–20 из 137». Слот `#total` сильнее. */\n showTotal?: boolean\n /** i18n-подпись перед полем перехода. По умолчанию — `gr.pagination.jumpTo`. */\n jumperLabel?: string\n /**\n * Имя навигационного лендмарка. По умолчанию — `gr.pagination.label`; задавать\n * стоит там, где пагинаций на странице несколько и их надо различать.\n */\n ariaLabel?: string\n /** Гасит всю пагинацию: номера, кнопки, селект размера и поле перехода. */\n disabled?: boolean\n size?: GrPaginationSize\n}\n\nexport interface GrPaginationEmits {\n (e: 'update:page', value: number): void\n (e: 'update:pageSize', value: number): void\n}\n\nconst { t } = useGranularityTranslations()\n\nconst props = withDefaults(defineProps<GrPaginationProps>(), {\n pageSizes: () => [10, 20, 50],\n siblingCount: 1,\n boundaryCount: 1,\n compact: false,\n showJumper: false,\n showPageSize: false,\n showTotal: false,\n jumperLabel: undefined,\n ariaLabel: undefined,\n disabled: false,\n size: undefined,\n})\n\ndefineSlots<{\n /** Диапазон показанных элементов целиком — вместо строки из локали. */\n total?: (props: { from: number, to: number, total: number }) => unknown\n}>()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrPagination' })\n\nconst rowClass = computed(() => rowGaps[resolvedSize.value])\nconst pageListClass = computed(() => pageListGaps[resolvedSize.value])\nconst pageClass = computed(() => pageSizes[resolvedSize.value])\nconst jumperClass = computed(() => jumperSizes[resolvedSize.value])\nconst ellipsisClass = computed(() => ellipsisSizes[resolvedSize.value])\nconst labelClass = computed(() => labelSizes[resolvedSize.value])\nconst selectWrapClass = computed(() => pageSizeSelectWidths[resolvedSize.value])\nconst navButtonSize = computed(() => navButtonSizes[resolvedSize.value])\n\nconst emit = defineEmits<GrPaginationEmits>()\n\n// Делитель клампится: `pageSize: 0` дал бы `Infinity` страниц и бесконечный\n// цикл в `range()`.\nconst resolvedPageSize = computed(() => Math.max(1, Math.trunc(props.pageSize)))\nconst pageCount = computed(() => Math.max(1, Math.ceil(props.total / resolvedPageSize.value)))\n\n// Рендер идёт от зажатого номера: вотчер ниже просит родителя подтянуть `page`,\n// но на первом рендере он молчит, и без клампа пагинация осталась бы вовсе без\n// активной страницы.\nconst currentPage = computed(() => Math.min(Math.max(Math.trunc(props.page), 1), pageCount.value))\n\n// Контролируемый компонент: если `total`/`pageSize` уменьшились и текущая `page`\n// вышла за диапазон — просим родителя подтянуть её к последней странице.\nwatch(pageCount, (count) => {\n if (props.page > count)\n emit('update:page', count)\n})\n\nconst pageSizeModel = computed({\n get: () => String(props.pageSize),\n set: (value: string) => emit('update:pageSize', Number(value)),\n})\n\nconst pageSizeOptions = computed(() =>\n props.pageSizes.map(pageSize => ({\n value: String(pageSize),\n label: String(pageSize),\n })),\n)\n\nconst rangeFrom = computed(() => (props.total === 0 ? 0 : (currentPage.value - 1) * resolvedPageSize.value + 1))\nconst rangeTo = computed(() => Math.min(currentPage.value * resolvedPageSize.value, props.total))\n\ntype PaginationItem = number | 'ellipsis-start' | 'ellipsis-end'\n\nfunction range(start: number, end: number): number[] {\n const list: number[] = []\n for (let page = start; page <= end; page += 1)\n list.push(page)\n return list\n}\n\n// Алгоритм usePagination как в MUI: крайние `boundaryCount` страниц + `siblingCount`\n// соседей вокруг текущей, разрывы схлопываются в многоточие. Одиночный «пропущенный»\n// номер показываем как номер, а не как «…» (визуально ровнее).\nconst items = computed<PaginationItem[]>(() => {\n const count = pageCount.value\n const current = currentPage.value\n const boundaryCount = Math.max(0, props.boundaryCount)\n const siblingCount = Math.max(0, props.siblingCount)\n\n const startPages = range(1, Math.min(boundaryCount, count))\n const endPages = range(Math.max(count - boundaryCount + 1, boundaryCount + 1), count)\n\n const siblingsStart = Math.max(\n Math.min(current - siblingCount, count - boundaryCount - siblingCount * 2 - 1),\n boundaryCount + 2,\n )\n const siblingsEnd = Math.min(\n Math.max(current + siblingCount, boundaryCount + siblingCount * 2 + 2),\n endPages.length > 0 ? endPages[0] - 2 : count - 1,\n )\n\n return [\n ...startPages,\n ...(siblingsStart > boundaryCount + 2\n ? ['ellipsis-start' as const]\n : boundaryCount + 1 < count - boundaryCount\n ? [boundaryCount + 1]\n : []),\n ...range(siblingsStart, siblingsEnd),\n ...(siblingsEnd < count - boundaryCount - 1\n ? ['ellipsis-end' as const]\n : count - boundaryCount > boundaryCount\n ? [count - boundaryCount]\n : []),\n ...endPages,\n ]\n})\n\nfunction goTo(page: number): void {\n if (props.disabled)\n return\n\n emit('update:page', page)\n}\n\nfunction prev(): void {\n goTo(Math.max(1, currentPage.value - 1))\n}\n\nfunction next(): void {\n goTo(Math.min(pageCount.value, currentPage.value + 1))\n}\n\nfunction first(): void {\n goTo(1)\n}\n\nfunction last(): void {\n goTo(pageCount.value)\n}\n\n// «Перейти к странице»: клампим введённый номер к диапазону [1, pageCount]\n// и сбрасываем поле после перехода.\nconst jumperValue = ref('')\n\nfunction submitJumper(): void {\n const parsed = Number.parseInt(jumperValue.value, 10)\n jumperValue.value = ''\n if (Number.isNaN(parsed))\n return\n\n goTo(Math.min(pageCount.value, Math.max(1, parsed)))\n}\n\nconst resolvedJumperLabel = computed(() => props.jumperLabel ?? t('gr.pagination.jumpTo', 'Go to'))\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.pagination.label', 'Pagination'))\nconst statusText = computed(() =>\n t('gr.pagination.status', 'Page {page} of {count}', { page: currentPage.value, count: pageCount.value }),\n)\nconst totalText = computed(() =>\n t('gr.pagination.total', '{from}–{to} of {total}', { from: rangeFrom.value, to: rangeTo.value, total: props.total }),\n)\n</script>\n\n<template>\n <div\n class=\"flex flex-wrap items-center justify-end\"\n :class=\"rowClass\"\n data-gr-pagination\n role=\"navigation\"\n :aria-label=\"resolvedAriaLabel\"\n >\n <div\n v-if=\"showTotal\"\n data-gr-pagination-total\n class=\"text-[var(--gr-muted-fg)] tabular-nums\"\n :class=\"labelClass\"\n >\n <slot name=\"total\" :from=\"rangeFrom\" :to=\"rangeTo\" :total=\"total\">\n {{ totalText }}\n </slot>\n </div>\n\n <div v-if=\"showPageSize\" :class=\"selectWrapClass\">\n <GrSelect\n v-model=\"pageSizeModel\"\n :options=\"pageSizeOptions\"\n :size=\"resolvedSize\"\n :disabled=\"disabled\"\n :aria-label=\"t('gr.pagination.pageSize', 'Page size')\"\n />\n </div>\n\n <GrButton variant=\"ghost\" :size=\"navButtonSize\" :disabled=\"disabled || currentPage <= 1\" :aria-label=\"t('gr.pagination.first', 'First page')\" data-gr-pagination-first @click=\"first\">\n «\n </GrButton>\n\n <GrButton variant=\"ghost\" :size=\"navButtonSize\" :disabled=\"disabled || currentPage <= 1\" data-gr-pagination-prev @click=\"prev\">\n {{ t('gr.pagination.prev', 'Prev') }}\n </GrButton>\n\n <!-- Компактный вариант: индикатор «текущая / всего» вместо нумерованных страниц. -->\n <div\n v-if=\"compact\"\n data-gr-pagination-compact\n class=\"px-2 text-[var(--gr-fg)] tabular-nums\"\n :class=\"labelClass\"\n role=\"status\"\n >\n {{ currentPage }} / {{ pageCount }}\n </div>\n\n <template v-else>\n <ul class=\"flex items-center [list-style:none]\" :class=\"pageListClass\" data-gr-pagination-pages role=\"list\">\n <li v-for=\"(item, index) in items\" :key=\"index\" :aria-hidden=\"typeof item === 'string' ? 'true' : undefined\">\n <span\n v-if=\"item === 'ellipsis-start' || item === 'ellipsis-end'\"\n data-gr-pagination-ellipsis\n class=\"grid place-items-center text-[var(--gr-muted-fg)]\"\n :class=\"ellipsisClass\"\n >…</span>\n <button\n v-else\n type=\"button\"\n data-gr-pagination-page\n :disabled=\"disabled\"\n :aria-current=\"item === currentPage ? 'page' : undefined\"\n :aria-label=\"t('gr.pagination.page', 'Page {n}', { n: item })\"\n class=\"rounded-[var(--gr-radius-control)] font-600 transition-colors duration-[var(--gr-duration-fast)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)] disabled:cursor-not-allowed\"\n :class=\"[\n pageClass,\n item === currentPage\n ? 'bg-[var(--gr-primary)] text-[var(--gr-primary-fg)]'\n // Подложка тональная, а не `--gr-muted`: серый на белом фоне даёт\n // 1.10:1 и на светлой теме hover был практически не виден, хотя у\n // соседних «назад/вперёд» (ghost-кнопки) он тонирован. Ряд обязан\n // читаться одним контролом.\n : 'text-[var(--gr-muted-fg)] hover:bg-[var(--gr-accent)] hover:text-[var(--gr-accent-fg)]',\n ]\"\n @click=\"goTo(item)\"\n >\n {{ item }}\n </button>\n </li>\n </ul>\n\n <!-- В компактном режиме смену страницы объявляет видимый индикатор. -->\n <div data-gr-pagination-status role=\"status\" class=\"sr-only\">\n {{ statusText }}\n </div>\n </template>\n\n <GrButton variant=\"ghost\" :size=\"navButtonSize\" :disabled=\"disabled || currentPage >= pageCount\" data-gr-pagination-next @click=\"next\">\n {{ t('gr.pagination.next', 'Next') }}\n </GrButton>\n\n <GrButton variant=\"ghost\" :size=\"navButtonSize\" :disabled=\"disabled || currentPage >= pageCount\" :aria-label=\"t('gr.pagination.last', 'Last page')\" data-gr-pagination-last @click=\"last\">\n »\n </GrButton>\n\n <!-- «Перейти к странице»: Enter или blur применяют введённый номер. -->\n <div v-if=\"showJumper\" class=\"flex items-center gap-2 text-[var(--gr-muted-fg)]\" :class=\"labelClass\">\n <span>{{ resolvedJumperLabel }}</span>\n <input\n v-model=\"jumperValue\"\n type=\"number\"\n min=\"1\"\n :max=\"pageCount\"\n inputmode=\"numeric\"\n data-gr-pagination-jumper\n :disabled=\"disabled\"\n :aria-label=\"resolvedJumperLabel\"\n class=\"rounded-[var(--gr-radius-control)] border border-[var(--gr-brd)] bg-[var(--gr-bg)] px-2 text-center text-[var(--gr-fg)] tabular-nums transition-colors duration-[var(--gr-duration-fast)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)] disabled:cursor-not-allowed\"\n :class=\"jumperClass\"\n @keydown.enter=\"submitJumper\"\n @blur=\"submitJumper\"\n >\n </div>\n </div>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"GrPagination-kQhWlfs-.js","names":[],"sources":["../../../../src/components/GrPagination/GrPagination.vue","../../../../src/components/GrPagination/GrPagination.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrSelect from '../GrSelect/GrSelect.vue'\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport {\n type GrPaginationSize,\n ellipsisSizes,\n jumperSizes,\n labelSizes,\n navButtonSizes,\n pageListGaps,\n pageSizes,\n pageSizeSelectWidths,\n rowGaps,\n} from './grPaginationStyles'\n\n/**\n * Пропы пагинации.\n *\n * `total` интерпретируется как общее количество элементов; число страниц\n * вычисляется из `total` и `pageSize`. Номера усекаются многоточием\n * (алгоритм boundary/sibling, как у MUI): всегда видны первая/последняя страница\n * и `siblingCount` соседей вокруг текущей.\n */\nexport interface GrPaginationProps {\n page: number\n pageSize: number\n total: number\n pageSizes?: number[]\n /** Сколько соседних страниц показывать вокруг текущей. По умолчанию `1`. */\n siblingCount?: number\n /** Сколько крайних страниц всегда показывать с каждого края. По умолчанию `1`. */\n boundaryCount?: number\n /**\n * Компактный вариант: вместо нумерованных страниц показывается индикатор\n * «текущая / всего» — удобно для узких мест (мобайл, тулбары таблиц).\n */\n compact?: boolean\n /** Показывать поле «перейти к странице» с быстрым переходом по вводу номера. */\n showJumper?: boolean\n /** Показывать селект размера страницы. */\n showPageSize?: boolean\n /** Показывать диапазон показанных элементов — «1–20 из 137». Слот `#total` сильнее. */\n showTotal?: boolean\n /** i18n-подпись перед полем перехода. По умолчанию — `gr.pagination.jumpTo`. */\n jumperLabel?: string\n /**\n * Имя навигационного лендмарка. По умолчанию — `gr.pagination.label`; задавать\n * стоит там, где пагинаций на странице несколько и их надо различать.\n */\n ariaLabel?: string\n /** Гасит всю пагинацию: номера, кнопки, селект размера и поле перехода. */\n disabled?: boolean\n size?: GrPaginationSize\n}\n\nexport interface GrPaginationEmits {\n (e: 'update:page', value: number): void\n (e: 'update:pageSize', value: number): void\n}\n\nconst { t } = useGranularityTranslations()\n\nconst props = withDefaults(defineProps<GrPaginationProps>(), {\n pageSizes: () => [10, 20, 50],\n siblingCount: 1,\n boundaryCount: 1,\n compact: false,\n showJumper: false,\n showPageSize: false,\n showTotal: false,\n jumperLabel: undefined,\n ariaLabel: undefined,\n disabled: false,\n size: undefined,\n})\n\ndefineSlots<{\n /** Диапазон показанных элементов целиком — вместо строки из локали. */\n total?: (props: { from: number, to: number, total: number }) => unknown\n}>()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrPagination' })\n\nconst rowClass = computed(() => rowGaps[resolvedSize.value])\nconst pageListClass = computed(() => pageListGaps[resolvedSize.value])\nconst pageClass = computed(() => pageSizes[resolvedSize.value])\nconst jumperClass = computed(() => jumperSizes[resolvedSize.value])\nconst ellipsisClass = computed(() => ellipsisSizes[resolvedSize.value])\nconst labelClass = computed(() => labelSizes[resolvedSize.value])\nconst selectWrapClass = computed(() => pageSizeSelectWidths[resolvedSize.value])\nconst navButtonSize = computed(() => navButtonSizes[resolvedSize.value])\n\nconst emit = defineEmits<GrPaginationEmits>()\n\n// Делитель клампится: `pageSize: 0` дал бы `Infinity` страниц и бесконечный\n// цикл в `range()`.\nconst resolvedPageSize = computed(() => Math.max(1, Math.trunc(props.pageSize)))\nconst pageCount = computed(() => Math.max(1, Math.ceil(props.total / resolvedPageSize.value)))\n\n// Рендер идёт от зажатого номера: вотчер ниже просит родителя подтянуть `page`,\n// но на первом рендере он молчит, и без клампа пагинация осталась бы вовсе без\n// активной страницы.\nconst currentPage = computed(() => Math.min(Math.max(Math.trunc(props.page), 1), pageCount.value))\n\n// Контролируемый компонент: если `total`/`pageSize` уменьшились и текущая `page`\n// вышла за диапазон — просим родителя подтянуть её к последней странице.\nwatch(pageCount, (count) => {\n if (props.page > count)\n emit('update:page', count)\n})\n\nconst pageSizeModel = computed({\n get: () => String(props.pageSize),\n set: (value: string) => emit('update:pageSize', Number(value)),\n})\n\nconst pageSizeOptions = computed(() =>\n props.pageSizes.map(pageSize => ({\n value: String(pageSize),\n label: String(pageSize),\n })),\n)\n\nconst rangeFrom = computed(() => (props.total === 0 ? 0 : (currentPage.value - 1) * resolvedPageSize.value + 1))\nconst rangeTo = computed(() => Math.min(currentPage.value * resolvedPageSize.value, props.total))\n\ntype PaginationItem = number | 'ellipsis-start' | 'ellipsis-end'\n\nfunction range(start: number, end: number): number[] {\n const list: number[] = []\n for (let page = start; page <= end; page += 1)\n list.push(page)\n return list\n}\n\n// Алгоритм usePagination как в MUI: крайние `boundaryCount` страниц + `siblingCount`\n// соседей вокруг текущей, разрывы схлопываются в многоточие. Одиночный «пропущенный»\n// номер показываем как номер, а не как «…» (визуально ровнее).\nconst items = computed<PaginationItem[]>(() => {\n const count = pageCount.value\n const current = currentPage.value\n const boundaryCount = Math.max(0, props.boundaryCount)\n const siblingCount = Math.max(0, props.siblingCount)\n\n const startPages = range(1, Math.min(boundaryCount, count))\n const endPages = range(Math.max(count - boundaryCount + 1, boundaryCount + 1), count)\n\n const siblingsStart = Math.max(\n Math.min(current - siblingCount, count - boundaryCount - siblingCount * 2 - 1),\n boundaryCount + 2,\n )\n const siblingsEnd = Math.min(\n Math.max(current + siblingCount, boundaryCount + siblingCount * 2 + 2),\n endPages.length > 0 ? endPages[0] - 2 : count - 1,\n )\n\n return [\n ...startPages,\n ...(siblingsStart > boundaryCount + 2\n ? ['ellipsis-start' as const]\n : boundaryCount + 1 < count - boundaryCount\n ? [boundaryCount + 1]\n : []),\n ...range(siblingsStart, siblingsEnd),\n ...(siblingsEnd < count - boundaryCount - 1\n ? ['ellipsis-end' as const]\n : count - boundaryCount > boundaryCount\n ? [count - boundaryCount]\n : []),\n ...endPages,\n ]\n})\n\nfunction goTo(page: number): void {\n if (props.disabled)\n return\n\n emit('update:page', page)\n}\n\nfunction prev(): void {\n goTo(Math.max(1, currentPage.value - 1))\n}\n\nfunction next(): void {\n goTo(Math.min(pageCount.value, currentPage.value + 1))\n}\n\nfunction first(): void {\n goTo(1)\n}\n\nfunction last(): void {\n goTo(pageCount.value)\n}\n\n// «Перейти к странице»: клампим введённый номер к диапазону [1, pageCount]\n// и сбрасываем поле после перехода.\nconst jumperValue = ref('')\n\nfunction submitJumper(): void {\n const parsed = Number.parseInt(jumperValue.value, 10)\n jumperValue.value = ''\n if (Number.isNaN(parsed))\n return\n\n goTo(Math.min(pageCount.value, Math.max(1, parsed)))\n}\n\nconst resolvedJumperLabel = computed(() => props.jumperLabel ?? t('gr.pagination.jumpTo', 'Go to'))\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.pagination.label', 'Pagination'))\nconst statusText = computed(() =>\n t('gr.pagination.status', 'Page {page} of {count}', { page: currentPage.value, count: pageCount.value }),\n)\nconst totalText = computed(() =>\n t('gr.pagination.total', '{from}–{to} of {total}', { from: rangeFrom.value, to: rangeTo.value, total: props.total }),\n)\n</script>\n\n<template>\n <div\n class=\"flex flex-wrap items-center justify-end\"\n :class=\"rowClass\"\n data-gr-pagination\n role=\"navigation\"\n :aria-label=\"resolvedAriaLabel\"\n >\n <div\n v-if=\"showTotal\"\n data-gr-pagination-total\n class=\"text-[var(--gr-muted-fg)] tabular-nums\"\n :class=\"labelClass\"\n >\n <slot name=\"total\" :from=\"rangeFrom\" :to=\"rangeTo\" :total=\"total\">\n {{ totalText }}\n </slot>\n </div>\n\n <div v-if=\"showPageSize\" :class=\"selectWrapClass\">\n <GrSelect\n v-model=\"pageSizeModel\"\n :options=\"pageSizeOptions\"\n :size=\"resolvedSize\"\n :disabled=\"disabled\"\n :aria-label=\"t('gr.pagination.pageSize', 'Page size')\"\n />\n </div>\n\n <GrButton variant=\"ghost\" :size=\"navButtonSize\" :disabled=\"disabled || currentPage <= 1\" :aria-label=\"t('gr.pagination.first', 'First page')\" data-gr-pagination-first @click=\"first\">\n «\n </GrButton>\n\n <GrButton variant=\"ghost\" :size=\"navButtonSize\" :disabled=\"disabled || currentPage <= 1\" data-gr-pagination-prev @click=\"prev\">\n {{ t('gr.pagination.prev', 'Prev') }}\n </GrButton>\n\n <!-- Компактный вариант: индикатор «текущая / всего» вместо нумерованных страниц. -->\n <div\n v-if=\"compact\"\n data-gr-pagination-compact\n class=\"px-2 text-[var(--gr-fg)] tabular-nums\"\n :class=\"labelClass\"\n role=\"status\"\n >\n {{ currentPage }} / {{ pageCount }}\n </div>\n\n <template v-else>\n <ul class=\"flex items-center [list-style:none]\" :class=\"pageListClass\" data-gr-pagination-pages role=\"list\">\n <li v-for=\"(item, index) in items\" :key=\"index\" :aria-hidden=\"typeof item === 'string' ? 'true' : undefined\">\n <span\n v-if=\"item === 'ellipsis-start' || item === 'ellipsis-end'\"\n data-gr-pagination-ellipsis\n class=\"grid place-items-center text-[var(--gr-muted-fg)]\"\n :class=\"ellipsisClass\"\n >…</span>\n <button\n v-else\n type=\"button\"\n data-gr-pagination-page\n :disabled=\"disabled\"\n :aria-current=\"item === currentPage ? 'page' : undefined\"\n :aria-label=\"t('gr.pagination.page', 'Page {n}', { n: item })\"\n class=\"rounded-[var(--gr-radius-control)] font-600 transition-colors duration-[var(--gr-duration-fast)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)] disabled:cursor-not-allowed\"\n :class=\"[\n pageClass,\n item === currentPage\n ? 'bg-[var(--gr-primary)] text-[var(--gr-primary-fg)]'\n // Подложка тональная, а не `--gr-muted`: серый на белом фоне даёт\n // 1.10:1 и на светлой теме hover был практически не виден, хотя у\n // соседних «назад/вперёд» (ghost-кнопки) он тонирован. Ряд обязан\n // читаться одним контролом.\n : 'text-[var(--gr-muted-fg)] hover:bg-[var(--gr-accent)] hover:text-[var(--gr-accent-fg)]',\n ]\"\n @click=\"goTo(item)\"\n >\n {{ item }}\n </button>\n </li>\n </ul>\n\n <!-- В компактном режиме смену страницы объявляет видимый индикатор. -->\n <div data-gr-pagination-status role=\"status\" class=\"sr-only\">\n {{ statusText }}\n </div>\n </template>\n\n <GrButton variant=\"ghost\" :size=\"navButtonSize\" :disabled=\"disabled || currentPage >= pageCount\" data-gr-pagination-next @click=\"next\">\n {{ t('gr.pagination.next', 'Next') }}\n </GrButton>\n\n <GrButton variant=\"ghost\" :size=\"navButtonSize\" :disabled=\"disabled || currentPage >= pageCount\" :aria-label=\"t('gr.pagination.last', 'Last page')\" data-gr-pagination-last @click=\"last\">\n »\n </GrButton>\n\n <!-- «Перейти к странице»: Enter или blur применяют введённый номер. -->\n <div v-if=\"showJumper\" class=\"flex items-center gap-2 text-[var(--gr-muted-fg)]\" :class=\"labelClass\">\n <span>{{ resolvedJumperLabel }}</span>\n <input\n v-model=\"jumperValue\"\n type=\"number\"\n min=\"1\"\n :max=\"pageCount\"\n inputmode=\"numeric\"\n data-gr-pagination-jumper\n :disabled=\"disabled\"\n :aria-label=\"resolvedJumperLabel\"\n class=\"rounded-[var(--gr-radius-control)] border border-[var(--gr-brd)] bg-[var(--gr-bg)] px-2 text-center text-[var(--gr-fg)] tabular-nums transition-colors duration-[var(--gr-duration-fast)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)] disabled:cursor-not-allowed\"\n :class=\"jumperClass\"\n @keydown.enter=\"submitJumper\"\n @blur=\"submitJumper\"\n >\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, ref, watch } from 'vue'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrSelect from '../GrSelect/GrSelect.vue'\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport {\n type GrPaginationSize,\n ellipsisSizes,\n jumperSizes,\n labelSizes,\n navButtonSizes,\n pageListGaps,\n pageSizes,\n pageSizeSelectWidths,\n rowGaps,\n} from './grPaginationStyles'\n\n/**\n * Пропы пагинации.\n *\n * `total` интерпретируется как общее количество элементов; число страниц\n * вычисляется из `total` и `pageSize`. Номера усекаются многоточием\n * (алгоритм boundary/sibling, как у MUI): всегда видны первая/последняя страница\n * и `siblingCount` соседей вокруг текущей.\n */\nexport interface GrPaginationProps {\n page: number\n pageSize: number\n total: number\n pageSizes?: number[]\n /** Сколько соседних страниц показывать вокруг текущей. По умолчанию `1`. */\n siblingCount?: number\n /** Сколько крайних страниц всегда показывать с каждого края. По умолчанию `1`. */\n boundaryCount?: number\n /**\n * Компактный вариант: вместо нумерованных страниц показывается индикатор\n * «текущая / всего» — удобно для узких мест (мобайл, тулбары таблиц).\n */\n compact?: boolean\n /** Показывать поле «перейти к странице» с быстрым переходом по вводу номера. */\n showJumper?: boolean\n /** Показывать селект размера страницы. */\n showPageSize?: boolean\n /** Показывать диапазон показанных элементов — «1–20 из 137». Слот `#total` сильнее. */\n showTotal?: boolean\n /** i18n-подпись перед полем перехода. По умолчанию — `gr.pagination.jumpTo`. */\n jumperLabel?: string\n /**\n * Имя навигационного лендмарка. По умолчанию — `gr.pagination.label`; задавать\n * стоит там, где пагинаций на странице несколько и их надо различать.\n */\n ariaLabel?: string\n /** Гасит всю пагинацию: номера, кнопки, селект размера и поле перехода. */\n disabled?: boolean\n size?: GrPaginationSize\n}\n\nexport interface GrPaginationEmits {\n (e: 'update:page', value: number): void\n (e: 'update:pageSize', value: number): void\n}\n\nconst { t } = useGranularityTranslations()\n\nconst props = withDefaults(defineProps<GrPaginationProps>(), {\n pageSizes: () => [10, 20, 50],\n siblingCount: 1,\n boundaryCount: 1,\n compact: false,\n showJumper: false,\n showPageSize: false,\n showTotal: false,\n jumperLabel: undefined,\n ariaLabel: undefined,\n disabled: false,\n size: undefined,\n})\n\ndefineSlots<{\n /** Диапазон показанных элементов целиком — вместо строки из локали. */\n total?: (props: { from: number, to: number, total: number }) => unknown\n}>()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrPagination' })\n\nconst rowClass = computed(() => rowGaps[resolvedSize.value])\nconst pageListClass = computed(() => pageListGaps[resolvedSize.value])\nconst pageClass = computed(() => pageSizes[resolvedSize.value])\nconst jumperClass = computed(() => jumperSizes[resolvedSize.value])\nconst ellipsisClass = computed(() => ellipsisSizes[resolvedSize.value])\nconst labelClass = computed(() => labelSizes[resolvedSize.value])\nconst selectWrapClass = computed(() => pageSizeSelectWidths[resolvedSize.value])\nconst navButtonSize = computed(() => navButtonSizes[resolvedSize.value])\n\nconst emit = defineEmits<GrPaginationEmits>()\n\n// Делитель клампится: `pageSize: 0` дал бы `Infinity` страниц и бесконечный\n// цикл в `range()`.\nconst resolvedPageSize = computed(() => Math.max(1, Math.trunc(props.pageSize)))\nconst pageCount = computed(() => Math.max(1, Math.ceil(props.total / resolvedPageSize.value)))\n\n// Рендер идёт от зажатого номера: вотчер ниже просит родителя подтянуть `page`,\n// но на первом рендере он молчит, и без клампа пагинация осталась бы вовсе без\n// активной страницы.\nconst currentPage = computed(() => Math.min(Math.max(Math.trunc(props.page), 1), pageCount.value))\n\n// Контролируемый компонент: если `total`/`pageSize` уменьшились и текущая `page`\n// вышла за диапазон — просим родителя подтянуть её к последней странице.\nwatch(pageCount, (count) => {\n if (props.page > count)\n emit('update:page', count)\n})\n\nconst pageSizeModel = computed({\n get: () => String(props.pageSize),\n set: (value: string) => emit('update:pageSize', Number(value)),\n})\n\nconst pageSizeOptions = computed(() =>\n props.pageSizes.map(pageSize => ({\n value: String(pageSize),\n label: String(pageSize),\n })),\n)\n\nconst rangeFrom = computed(() => (props.total === 0 ? 0 : (currentPage.value - 1) * resolvedPageSize.value + 1))\nconst rangeTo = computed(() => Math.min(currentPage.value * resolvedPageSize.value, props.total))\n\ntype PaginationItem = number | 'ellipsis-start' | 'ellipsis-end'\n\nfunction range(start: number, end: number): number[] {\n const list: number[] = []\n for (let page = start; page <= end; page += 1)\n list.push(page)\n return list\n}\n\n// Алгоритм usePagination как в MUI: крайние `boundaryCount` страниц + `siblingCount`\n// соседей вокруг текущей, разрывы схлопываются в многоточие. Одиночный «пропущенный»\n// номер показываем как номер, а не как «…» (визуально ровнее).\nconst items = computed<PaginationItem[]>(() => {\n const count = pageCount.value\n const current = currentPage.value\n const boundaryCount = Math.max(0, props.boundaryCount)\n const siblingCount = Math.max(0, props.siblingCount)\n\n const startPages = range(1, Math.min(boundaryCount, count))\n const endPages = range(Math.max(count - boundaryCount + 1, boundaryCount + 1), count)\n\n const siblingsStart = Math.max(\n Math.min(current - siblingCount, count - boundaryCount - siblingCount * 2 - 1),\n boundaryCount + 2,\n )\n const siblingsEnd = Math.min(\n Math.max(current + siblingCount, boundaryCount + siblingCount * 2 + 2),\n endPages.length > 0 ? endPages[0] - 2 : count - 1,\n )\n\n return [\n ...startPages,\n ...(siblingsStart > boundaryCount + 2\n ? ['ellipsis-start' as const]\n : boundaryCount + 1 < count - boundaryCount\n ? [boundaryCount + 1]\n : []),\n ...range(siblingsStart, siblingsEnd),\n ...(siblingsEnd < count - boundaryCount - 1\n ? ['ellipsis-end' as const]\n : count - boundaryCount > boundaryCount\n ? [count - boundaryCount]\n : []),\n ...endPages,\n ]\n})\n\nfunction goTo(page: number): void {\n if (props.disabled)\n return\n\n emit('update:page', page)\n}\n\nfunction prev(): void {\n goTo(Math.max(1, currentPage.value - 1))\n}\n\nfunction next(): void {\n goTo(Math.min(pageCount.value, currentPage.value + 1))\n}\n\nfunction first(): void {\n goTo(1)\n}\n\nfunction last(): void {\n goTo(pageCount.value)\n}\n\n// «Перейти к странице»: клампим введённый номер к диапазону [1, pageCount]\n// и сбрасываем поле после перехода.\nconst jumperValue = ref('')\n\nfunction submitJumper(): void {\n const parsed = Number.parseInt(jumperValue.value, 10)\n jumperValue.value = ''\n if (Number.isNaN(parsed))\n return\n\n goTo(Math.min(pageCount.value, Math.max(1, parsed)))\n}\n\nconst resolvedJumperLabel = computed(() => props.jumperLabel ?? t('gr.pagination.jumpTo', 'Go to'))\nconst resolvedAriaLabel = computed(() => props.ariaLabel ?? t('gr.pagination.label', 'Pagination'))\nconst statusText = computed(() =>\n t('gr.pagination.status', 'Page {page} of {count}', { page: currentPage.value, count: pageCount.value }),\n)\nconst totalText = computed(() =>\n t('gr.pagination.total', '{from}–{to} of {total}', { from: rangeFrom.value, to: rangeTo.value, total: props.total }),\n)\n</script>\n\n<template>\n <div\n class=\"flex flex-wrap items-center justify-end\"\n :class=\"rowClass\"\n data-gr-pagination\n role=\"navigation\"\n :aria-label=\"resolvedAriaLabel\"\n >\n <div\n v-if=\"showTotal\"\n data-gr-pagination-total\n class=\"text-[var(--gr-muted-fg)] tabular-nums\"\n :class=\"labelClass\"\n >\n <slot name=\"total\" :from=\"rangeFrom\" :to=\"rangeTo\" :total=\"total\">\n {{ totalText }}\n </slot>\n </div>\n\n <div v-if=\"showPageSize\" :class=\"selectWrapClass\">\n <GrSelect\n v-model=\"pageSizeModel\"\n :options=\"pageSizeOptions\"\n :size=\"resolvedSize\"\n :disabled=\"disabled\"\n :aria-label=\"t('gr.pagination.pageSize', 'Page size')\"\n />\n </div>\n\n <GrButton variant=\"ghost\" :size=\"navButtonSize\" :disabled=\"disabled || currentPage <= 1\" :aria-label=\"t('gr.pagination.first', 'First page')\" data-gr-pagination-first @click=\"first\">\n «\n </GrButton>\n\n <GrButton variant=\"ghost\" :size=\"navButtonSize\" :disabled=\"disabled || currentPage <= 1\" data-gr-pagination-prev @click=\"prev\">\n {{ t('gr.pagination.prev', 'Prev') }}\n </GrButton>\n\n <!-- Компактный вариант: индикатор «текущая / всего» вместо нумерованных страниц. -->\n <div\n v-if=\"compact\"\n data-gr-pagination-compact\n class=\"px-2 text-[var(--gr-fg)] tabular-nums\"\n :class=\"labelClass\"\n role=\"status\"\n >\n {{ currentPage }} / {{ pageCount }}\n </div>\n\n <template v-else>\n <ul class=\"flex items-center [list-style:none]\" :class=\"pageListClass\" data-gr-pagination-pages role=\"list\">\n <li v-for=\"(item, index) in items\" :key=\"index\" :aria-hidden=\"typeof item === 'string' ? 'true' : undefined\">\n <span\n v-if=\"item === 'ellipsis-start' || item === 'ellipsis-end'\"\n data-gr-pagination-ellipsis\n class=\"grid place-items-center text-[var(--gr-muted-fg)]\"\n :class=\"ellipsisClass\"\n >…</span>\n <button\n v-else\n type=\"button\"\n data-gr-pagination-page\n :disabled=\"disabled\"\n :aria-current=\"item === currentPage ? 'page' : undefined\"\n :aria-label=\"t('gr.pagination.page', 'Page {n}', { n: item })\"\n class=\"rounded-[var(--gr-radius-control)] font-600 transition-colors duration-[var(--gr-duration-fast)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)] disabled:cursor-not-allowed\"\n :class=\"[\n pageClass,\n item === currentPage\n ? 'bg-[var(--gr-primary)] text-[var(--gr-primary-fg)]'\n // Подложка тональная, а не `--gr-muted`: серый на белом фоне даёт\n // 1.10:1 и на светлой теме hover был практически не виден, хотя у\n // соседних «назад/вперёд» (ghost-кнопки) он тонирован. Ряд обязан\n // читаться одним контролом.\n : 'text-[var(--gr-muted-fg)] hover:bg-[var(--gr-accent)] hover:text-[var(--gr-accent-fg)]',\n ]\"\n @click=\"goTo(item)\"\n >\n {{ item }}\n </button>\n </li>\n </ul>\n\n <!-- В компактном режиме смену страницы объявляет видимый индикатор. -->\n <div data-gr-pagination-status role=\"status\" class=\"sr-only\">\n {{ statusText }}\n </div>\n </template>\n\n <GrButton variant=\"ghost\" :size=\"navButtonSize\" :disabled=\"disabled || currentPage >= pageCount\" data-gr-pagination-next @click=\"next\">\n {{ t('gr.pagination.next', 'Next') }}\n </GrButton>\n\n <GrButton variant=\"ghost\" :size=\"navButtonSize\" :disabled=\"disabled || currentPage >= pageCount\" :aria-label=\"t('gr.pagination.last', 'Last page')\" data-gr-pagination-last @click=\"last\">\n »\n </GrButton>\n\n <!-- «Перейти к странице»: Enter или blur применяют введённый номер. -->\n <div v-if=\"showJumper\" class=\"flex items-center gap-2 text-[var(--gr-muted-fg)]\" :class=\"labelClass\">\n <span>{{ resolvedJumperLabel }}</span>\n <input\n v-model=\"jumperValue\"\n type=\"number\"\n min=\"1\"\n :max=\"pageCount\"\n inputmode=\"numeric\"\n data-gr-pagination-jumper\n :disabled=\"disabled\"\n :aria-label=\"resolvedJumperLabel\"\n class=\"rounded-[var(--gr-radius-control)] border border-[var(--gr-brd)] bg-[var(--gr-bg)] px-2 text-center text-[var(--gr-fg)] tabular-nums transition-colors duration-[var(--gr-duration-fast)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)] disabled:cursor-not-allowed\"\n :class=\"jumperClass\"\n @keydown.enter=\"submitJumper\"\n @blur=\"submitJumper\"\n >\n </div>\n </div>\n</template>\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
1
  import { n as grPaginationSafelist, t as grPaginationConfig } from "../../chunks/config-CjKpUcmy.js";
2
- import { t as GrPagination_default } from "./chunks/GrPagination-CaNTZc-v.js";
2
+ import { t as GrPagination_default } from "./chunks/GrPagination-kQhWlfs-.js";
3
3
  export { GrPagination_default as GrPagination, GrPagination_default as default, grPaginationConfig, grPaginationSafelist };
@@ -1,7 +1,7 @@
1
1
  import { i as useGrComponentProp } from "../../../chunks/context-CTbuwcYs.js";
2
2
  import { n as useGranularityTranslations } from "../../../chunks/granularityI18n-BC5zsj_d.js";
3
3
  import { t as GrButton_default } from "../../GrButton/chunks/GrButton-Bw2AH4uX.js";
4
- import { t as GrInput_default } from "../../GrInput/chunks/GrInput-c53iltz0.js";
4
+ import { t as GrInput_default } from "../../GrInput/chunks/GrInput-BqqoEl1b.js";
5
5
  import { t as GrDialog_default } from "../../GrDialog/chunks/GrDialog-bOUDYfmh.js";
6
6
  import { t as GrResponseErrorBanner_default } from "../../GrResponseErrorBanner/chunks/GrResponseErrorBanner-DudGc9LO.js";
7
7
  import { t as GrFormField_default } from "../../GrFormField/chunks/GrFormField-DSylU7IU.js";