@feugene/granularity 0.28.4 → 0.29.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 (91) hide show
  1. package/dist/chunks/{config-_jO3q1Le.js → config-3bQeQvqd.js} +1 -1
  2. package/dist/chunks/{config-_jO3q1Le.js.map → config-3bQeQvqd.js.map} +1 -1
  3. package/dist/chunks/{grIconStyles-B6BRqUld.js → grIconStyles-BwBrJY1E.js} +8 -5
  4. package/dist/chunks/grIconStyles-BwBrJY1E.js.map +1 -0
  5. package/dist/chunks/{granular-provider-Dds_519x.js → granular-provider-NtErwzYB.js} +1 -1
  6. package/dist/chunks/{granular-provider-Dds_519x.js.map → granular-provider-NtErwzYB.js.map} +1 -1
  7. package/dist/components/GrAutocomplete/chunks/{GrAutocomplete-BwhUTqmG.js → GrAutocomplete-CaHys_A_.js} +2 -2
  8. package/dist/components/GrAutocomplete/chunks/GrAutocomplete-CaHys_A_.js.map +1 -0
  9. package/dist/components/GrAutocomplete/index.js +1 -1
  10. package/dist/components/GrAutocomplete/styles.css +1 -1
  11. package/dist/components/GrBreadcrumbs/chunks/{GrBreadcrumbs-BKOvl8W1.js → GrBreadcrumbs-DRDNan_n.js} +1 -1
  12. package/dist/components/GrBreadcrumbs/chunks/{GrBreadcrumbs-BKOvl8W1.js.map → GrBreadcrumbs-DRDNan_n.js.map} +1 -1
  13. package/dist/components/GrBreadcrumbs/index.js +1 -1
  14. package/dist/components/GrCollapse/chunks/{GrCollapse-CytiA2Sx.js → GrCollapse-Bw9_JatF.js} +1 -1
  15. package/dist/components/GrCollapse/chunks/{GrCollapse-CytiA2Sx.js.map → GrCollapse-Bw9_JatF.js.map} +1 -1
  16. package/dist/components/GrCollapse/index.js +1 -1
  17. package/dist/components/GrDataTable/chunks/{GrDataTable-DU00V44y.js → GrDataTable-C8a9_nCg.js} +1 -1
  18. package/dist/components/GrDataTable/chunks/{GrDataTable-DU00V44y.js.map → GrDataTable-C8a9_nCg.js.map} +1 -1
  19. package/dist/components/GrDataTable/index.js +1 -1
  20. package/dist/components/GrDrawer/chunks/{GrDrawer-BCreL-eq.js → GrDrawer-B7PiAcbU.js} +1 -1
  21. package/dist/components/GrDrawer/chunks/{GrDrawer-BCreL-eq.js.map → GrDrawer-B7PiAcbU.js.map} +1 -1
  22. package/dist/components/GrDrawer/index.js +1 -1
  23. package/dist/components/GrEmptyState/chunks/{GrEmptyState-D4T5pzXA.js → GrEmptyState-RqsWvgI9.js} +1 -1
  24. package/dist/components/GrEmptyState/chunks/{GrEmptyState-D4T5pzXA.js.map → GrEmptyState-RqsWvgI9.js.map} +1 -1
  25. package/dist/components/GrEmptyState/index.js +1 -1
  26. package/dist/components/GrFileUpload/chunks/{GrFileUpload-FjbaYGjL.js → GrFileUpload-NsHkRRIa.js} +1 -1
  27. package/dist/components/GrFileUpload/chunks/{GrFileUpload-FjbaYGjL.js.map → GrFileUpload-NsHkRRIa.js.map} +1 -1
  28. package/dist/components/GrFileUpload/index.js +1 -1
  29. package/dist/components/GrFormFile/chunks/{GrFormFile-ChdKlN83.js → GrFormFile-CYtXU9HV.js} +1 -1
  30. package/dist/components/GrFormFile/chunks/{GrFormFile-ChdKlN83.js.map → GrFormFile-CYtXU9HV.js.map} +1 -1
  31. package/dist/components/GrFormFile/index.js +1 -1
  32. package/dist/components/GrIcon/chunks/{GrIcon-Bn1XEriL.js → GrIcon-Dmf0c9ba.js} +1 -1
  33. package/dist/components/GrIcon/chunks/{GrIcon-Bn1XEriL.js.map → GrIcon-Dmf0c9ba.js.map} +1 -1
  34. package/dist/components/GrIcon/index.js +2 -2
  35. package/dist/components/GrImageViewer/chunks/{GrImageViewer-COUcpOgR.js → GrImageViewer-CyVN6Zpc.js} +1 -1
  36. package/dist/components/GrImageViewer/chunks/{GrImageViewer-COUcpOgR.js.map → GrImageViewer-CyVN6Zpc.js.map} +1 -1
  37. package/dist/components/GrImageViewer/index.js +1 -1
  38. package/dist/components/GrInputTag/chunks/{GrInputTag-LP3smqOv.js → GrInputTag-5wVcB0zG.js} +1 -1
  39. package/dist/components/GrInputTag/chunks/{GrInputTag-LP3smqOv.js.map → GrInputTag-5wVcB0zG.js.map} +1 -1
  40. package/dist/components/GrInputTag/index.js +1 -1
  41. package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-DMGuWrYD.js → GrJsonViewer-CgwjoUJt.js} +1 -1
  42. package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-DMGuWrYD.js.map → GrJsonViewer-CgwjoUJt.js.map} +1 -1
  43. package/dist/components/GrJsonViewer/index.js +1 -1
  44. package/dist/components/GrLink/chunks/{GrLink-DAGCFYO7.js → GrLink-DYXw3W9w.js} +1 -1
  45. package/dist/components/GrLink/chunks/{GrLink-DAGCFYO7.js.map → GrLink-DYXw3W9w.js.map} +1 -1
  46. package/dist/components/GrLink/index.js +1 -1
  47. package/dist/components/GrLoading/chunks/{GrLoading-DDD8WAIH.js → GrLoading-CMD3Ri9P.js} +1 -1
  48. package/dist/components/GrLoading/chunks/{GrLoading-DDD8WAIH.js.map → GrLoading-CMD3Ri9P.js.map} +1 -1
  49. package/dist/components/GrLoading/index.js +1 -1
  50. package/dist/components/GrNavbar/chunks/{GrNavbar-DhswlYyh.js → GrNavbar-C5EjMKaC.js} +1 -1
  51. package/dist/components/GrNavbar/chunks/{GrNavbar-DhswlYyh.js.map → GrNavbar-C5EjMKaC.js.map} +1 -1
  52. package/dist/components/GrNavbar/index.js +1 -1
  53. package/dist/components/GrNumberInput/chunks/{GrNumberInput-Daud9Jdm.js → GrNumberInput-DQ59YICu.js} +1 -1
  54. package/dist/components/GrNumberInput/chunks/{GrNumberInput-Daud9Jdm.js.map → GrNumberInput-DQ59YICu.js.map} +1 -1
  55. package/dist/components/GrNumberInput/index.js +1 -1
  56. package/dist/components/GrPagination/chunks/{GrPagination-kQhWlfs-.js → GrPagination-CX9ZV8mR.js} +1 -1
  57. package/dist/components/GrPagination/chunks/{GrPagination-kQhWlfs-.js.map → GrPagination-CX9ZV8mR.js.map} +1 -1
  58. package/dist/components/GrPagination/index.js +1 -1
  59. package/dist/components/GrSelect/chunks/{GrSelect-D-KU9Lx_.js → GrSelect-DKoQu3w9.js} +3 -3
  60. package/dist/components/GrSelect/chunks/{GrSelect-D-KU9Lx_.js.map → GrSelect-DKoQu3w9.js.map} +1 -1
  61. package/dist/components/GrSelect/index.js +1 -1
  62. package/dist/components/GrSelect/styles.css +1 -1
  63. package/dist/components/GrSidebar/chunks/{GrSidebar-DxPhIiWs.js → GrSidebar-B8EQIlfW.js} +1 -1
  64. package/dist/components/GrSidebar/chunks/{GrSidebar-DxPhIiWs.js.map → GrSidebar-B8EQIlfW.js.map} +1 -1
  65. package/dist/components/GrSidebar/index.js +1 -1
  66. package/dist/components/GrToaster/chunks/{GrToaster-CUi3ldJ0.js → GrToaster-Dp7-4B8k.js} +1 -1
  67. package/dist/components/GrToaster/chunks/{GrToaster-CUi3ldJ0.js.map → GrToaster-Dp7-4B8k.js.map} +1 -1
  68. package/dist/components/GrToaster/index.js +1 -1
  69. package/dist/components/GrTooltip/chunks/{GrTooltip-BKFjqu0E.js → GrTooltip-uYETdbCD.js} +1 -1
  70. package/dist/components/GrTooltip/chunks/{GrTooltip-BKFjqu0E.js.map → GrTooltip-uYETdbCD.js.map} +1 -1
  71. package/dist/components/GrTooltip/index.js +1 -1
  72. package/dist/components/GrTree/chunks/{GrTree-C06N9mPK.js → GrTree-DC89I4AP.js} +57 -6
  73. package/dist/components/GrTree/chunks/GrTree-DC89I4AP.js.map +1 -0
  74. package/dist/components/GrTree/index.js +1 -1
  75. package/dist/components/GrTree/styles.css +1 -1
  76. package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-BZfnt0bk.js → GrTreeSelect-CymO68Cv.js} +2 -2
  77. package/dist/components/GrTreeSelect/chunks/GrTreeSelect-CymO68Cv.js.map +1 -0
  78. package/dist/components/GrTreeSelect/index.js +1 -1
  79. package/dist/directives/loading.js +1 -1
  80. package/dist/granular-provider-node.js +1 -1
  81. package/dist/granular-provider.js +1 -1
  82. package/dist/index.js +24 -24
  83. package/dist/types/components/GrIcon/grIconStyles.d.ts +7 -4
  84. package/dist/types/components/GrTree/GrTree.vue.d.ts +17 -0
  85. package/dist/types/components/GrTree/grTreeProps.d.ts +27 -0
  86. package/dist/web-types.json +35 -1
  87. package/package.json +2 -2
  88. package/dist/chunks/grIconStyles-B6BRqUld.js.map +0 -1
  89. package/dist/components/GrAutocomplete/chunks/GrAutocomplete-BwhUTqmG.js.map +0 -1
  90. package/dist/components/GrTree/chunks/GrTree-C06N9mPK.js.map +0 -1
  91. package/dist/components/GrTreeSelect/chunks/GrTreeSelect-BZfnt0bk.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"GrNumberInput-Daud9Jdm.js","names":["$slots","$attrs"],"sources":["../../../../src/components/GrNumberInput/numberInputMath.ts","../../../../src/components/GrNumberInput/GrNumberInput.vue","../../../../src/components/GrNumberInput/GrNumberInput.vue"],"sourcesContent":["export { bigStep } from '../shared/numericStep'\n\n/**\n * Экспоненциальная запись разбирается отдельно: `String(1e-7)` — это `\"1e-7\"`,\n * и поиск точки насчитал бы ноль знаков, после чего округление съело бы всё\n * значение.\n */\nexport function decimalsOf(value: number): number {\n if (!Number.isFinite(value)) return 0\n\n const text = String(Math.abs(value))\n\n const exponential = text.match(/^\\d+(?:\\.(\\d+))?e-(\\d+)$/)\n if (exponential) {\n const [, fraction = '', exponent] = exponential\n return fraction.length + Number(exponent)\n }\n\n const dot = text.indexOf('.')\n return dot === -1 ? 0 : text.length - dot - 1\n}\n\n/**\n * Двоичная дробь копит ошибку, поэтому сумма округляется. Разрядность — по\n * большему из операндов: шаг `0.5` от значения `1.25` обязан дать `1.75`.\n */\nexport function addStep(current: number, step: number): number {\n const decimals = Math.max(decimalsOf(current), decimalsOf(step))\n const sum = current + step\n\n // `toFixed` за пределами 100 знаков бросает.\n return decimals > 100 ? sum : Number(sum.toFixed(decimals))\n}\n","<script setup lang=\"ts\">\nimport type { InputHTMLAttributes } from 'vue'\n\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { computed, onBeforeUnmount, ref } from 'vue'\n\nimport {\n clearButtonClass,\n stepperCompactClass,\n stepperWideClass,\n grNumberInputInputClass,\n grNumberInputShellClass,\n type GrNumberInputControlsDirection,\n type GrNumberInputSize,\n type GrNumberInputState,\n type GrNumberInputTextAlign,\n} from './grNumberInputStyles'\nimport { addStep, bigStep } from './numberInputMath'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport IconChevronDown from '~icons/lucide/chevron-down'\nimport IconChevronLeft from '~icons/lucide/chevron-left'\nimport IconChevronRight from '~icons/lucide/chevron-right'\nimport IconChevronUp from '~icons/lucide/chevron-up'\nimport IconX from '~icons/lucide/x'\nimport { addLen } from '../../composables/internal/useAddonMeasurement'\nimport { useControlAddons } from '../../composables/internal/useControlAddons'\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst ADDON_PX_BY_SIZE: Record<GrNumberInputSize, number> = {\n xs: 32,\n sm: 36,\n md: 40,\n lg: 48,\n}\n\nconst BASE_PADDING_X_LEN_BY_SIZE: Record<GrNumberInputSize, string> = {\n xs: '10px',\n sm: '12px',\n md: '12px',\n lg: '16px',\n}\n\nfunction px(n: number): string {\n return `${n}px`\n}\n\nexport interface GrNumberInputProps {\n /**\n * Значение поля. `null` — пусто.\n *\n * Незавершённый ввод («-», «1,») числом не является и в модель не попадает:\n * пока он набирается, поле держит его во внутреннем черновике, а модель\n * честно говорит «числа пока нет».\n */\n modelValue: number | null\n placeholder?: string\n autocomplete?: string\n inputmode?: InputHTMLAttributes['inputmode']\n disabled?: boolean\n /** Быстрый флаг невалидности; эквивалент `state='danger'` + `aria-invalid`. */\n invalid?: boolean\n /** Только для чтения: значение видно и уходит в форму, но не редактируется. */\n readonly?: boolean\n /** Обязательное поле (`aria-required`). Складывается с `required` у `GrFormField`. */\n required?: boolean\n /** Доступное имя вне `GrFormField`. */\n ariaLabel?: string\n state?: GrNumberInputState\n name?: string\n id?: string\n size?: GrNumberInputSize\n\n textAlign?: GrNumberInputTextAlign\n\n decimalSeparator?: string\n step?: number\n min?: number\n max?: number\n precision?: number\n\n /** Кнопка очистки значения. */\n clearable?: boolean\n /** A11y-подпись кнопки очистки. */\n clearLabel?: string\n /**\n * BCP-47 локаль для отображения значения (группировка разрядов и разделители\n * через `Intl.NumberFormat`). Работает вместе с `useGrouping`.\n */\n locale?: string\n /**\n * Группировать разряды при отображении (когда поле не в фокусе). При фокусе\n * показывается «сырое» значение для редактирования. По умолчанию выключено.\n */\n useGrouping?: boolean\n\n /** Показывать кнопки +/-. */\n controls?: boolean\n controlsDirection?: GrNumberInputControlsDirection\n\n prefixMinWidth?: string\n prefixMaxWidth?: string\n suffixMinWidth?: string\n suffixMaxWidth?: string\n /**\n * Фиксированная ширина у prefix/suffix: жёсткая ширина (из `*MaxWidth` →\n * `*MinWidth` → дефолт) + обрезка контента по краю (prefix — справа,\n * suffix — слева). По умолчанию аддоны растягиваются под контент.\n */\n prefixFixed?: boolean\n suffixFixed?: boolean\n\n /** i18n-friendly aria-label для кнопки \"увеличить\". */\n increaseLabel?: string\n /** i18n-friendly aria-label для кнопки \"уменьшить\". */\n decreaseLabel?: string\n}\n\nexport interface GrNumberInputEmits {\n (e: 'update:modelValue', value: number | null): void\n (e: 'change', value: number | null): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n /** Значение стёрто кнопкой очистки. */\n (e: 'clear'): void\n}\n\n// Контекст `GrFormField` — fallback для id/описания/невалидности, как в `GrInput`.\nconst field = useGrFormFieldContext()\n\nconst props = withDefaults(defineProps<GrNumberInputProps>(), {\n placeholder: undefined,\n autocomplete: undefined,\n inputmode: 'decimal',\n disabled: false,\n invalid: false,\n readonly: false,\n required: false,\n ariaLabel: undefined,\n state: 'default',\n name: undefined,\n id: undefined,\n size: undefined,\n\n textAlign: 'left',\n\n decimalSeparator: '.',\n step: 1,\n min: undefined,\n max: undefined,\n precision: undefined,\n locale: undefined,\n useGrouping: false,\n clearable: undefined,\n clearLabel: undefined,\n\n controls: false,\n controlsDirection: 'vertical',\n\n prefixMinWidth: undefined,\n prefixMaxWidth: undefined,\n suffixMinWidth: undefined,\n suffixMaxWidth: undefined,\n prefixFixed: false,\n suffixFixed: false,\n\n increaseLabel: undefined,\n decreaseLabel: undefined,\n})\n\n// Эффективный размер: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrNumberInput' })\n\nconst resolvedId = computed(() => props.id ?? field?.id.value)\nconst describedBy = computed(() => field?.describedById.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n} = useGrFormControl(() => props)\n\nconst emit = defineEmits<GrNumberInputEmits>()\ndefineSlots<{\n /** Аддон слева от поля: знак валюты, иконка. */\n prefix?: () => any\n /** Аддон справа от поля: единица измерения. */\n suffix?: () => any\n}>()\n\n\nconst inputEl = ref<HTMLInputElement | null>(null)\n\n// В фокусе поле показывает «сырое» значение: группировка мешала бы правке.\nconst isFocused = ref(false)\n\n/**\n * Строка ровно в том виде, в каком её набирают. Модель к этому моменту может\n * быть `null` («-», «1,» — не числа), и без черновика поле стирало бы под\n * пальцами то, что печатают. Коммит (`change`/`blur`) черновик снимает.\n */\nconst draft = ref<string | null>(null)\n\nfunction focus(): void {\n inputEl.value?.focus()\n}\n\nfunction blur(): void {\n inputEl.value?.blur()\n}\n\ndefineExpose({ focus, blur })\n\nconst { t, locale } = useGranularityTranslations()\n\n/**\n * Локаль форматирования: свой проп → локаль i18n-адаптера → окружение. Без\n * второго шага мультиязычное приложение передавало бы `locale` каждому полю.\n */\nconst resolvedLocale = computed(() => props.locale ?? locale.value)\nconst resolvedIncreaseLabel = computed(() => props.increaseLabel ?? t('gr.numberInput.increase', 'Increase'))\nconst resolvedDecreaseLabel = computed(() => props.decreaseLabel ?? t('gr.numberInput.decrease', 'Decrease'))\n\nconst hasHorizontalControls = computed(() => props.controls && props.controlsDirection === 'horizontal')\nconst hasVerticalControls = computed(() => props.controls && props.controlsDirection === 'vertical')\n\nconst addonPx = computed(() => ADDON_PX_BY_SIZE[resolvedSize.value])\nconst addonLen = computed(() => px(addonPx.value))\nconst basePaddingXLen = computed(() => BASE_PADDING_X_LEN_BY_SIZE[resolvedSize.value])\n\n/** Кнопки ± справа: вертикальный стек — одна колонка, горизонтальный — вторая. */\nconst rightControlsCount = computed(() =>\n (hasHorizontalControls.value ? 1 : 0) + (hasVerticalControls.value ? 1 : 0))\n\nconst {\n prefixEl,\n suffixEl,\n prefixLen,\n suffixLen,\n prefixStyle,\n suffixStyle,\n fieldPadding: inputStyle,\n} = useControlAddons(() => props, {\n defaultMinWidth: () => addonLen.value,\n paddingX: () => basePaddingXLen.value,\n leadingReserve: () => (hasHorizontalControls.value ? addonLen.value : '0px'),\n trailingReserve: () => (rightControlsCount.value > 0 ? px(addonPx.value * rightControlsCount.value) : '0px'),\n // Суффикс позиционируется абсолютно: без якоря он уехал бы от правого края.\n anchorSuffixRight: true,\n})\n\nfunction addonStyle(side: 'left' | 'right', offset: string): Record<string, string> {\n return {\n width: addonLen.value,\n [side]: offset,\n }\n}\n\nconst verticalControlsStyle = computed(() => addonStyle('right', suffixLen.value))\n\n/**\n * Крестик очистки живёт левее правой зоны (суффикс + кнопки ±) — иначе он лёг бы\n * на них. Ширины там же, где их считает поле, поэтому переиспользуем расчёт.\n */\nconst clearButtonStyle = computed(() => {\n const controls = rightControlsCount.value > 0 ? px(addonPx.value * rightControlsCount.value) : '0px'\n\n return { right: addLen(addLen(suffixLen.value, controls), '6px') }\n})\nconst horizontalLeftControlsStyle = computed(() => addonStyle('left', prefixLen.value))\nconst horizontalRightControlsStyle = computed(() => addonStyle('right', suffixLen.value))\n\nconst shellClassName = computed(() => {\n return grNumberInputShellClass({\n disabled: isDisabled.value,\n state: props.state,\n invalid: isInvalid.value,\n })\n})\n\nconst inputClassName = computed(() => {\n return grNumberInputInputClass({\n size: resolvedSize.value,\n textAlign: props.textAlign,\n })\n})\n\nfunction isDigit(ch: string): boolean {\n return ch >= '0' && ch <= '9'\n}\n\nfunction sanitize(raw: string): string {\n const sep = props.decimalSeparator || '.'\n\n let out = ''\n let hasSepLocal = false\n\n for (let i = 0; i < raw.length; i++) {\n const ch = raw[i]\n\n // Ведущий минус: разрешаем один в начале, чтобы можно было ввести\n // отрицательное значение с клавиатуры (границы проверит `clamp` на change).\n if (ch === '-' && out.length === 0) {\n out += '-'\n continue\n }\n\n if (isDigit(ch)) {\n out += ch\n continue\n }\n\n if ((ch === sep || ch === '.' || ch === ',') && !hasSepLocal) {\n out += sep\n hasSepLocal = true\n }\n }\n\n return out\n}\n\nfunction toNumber(value: string): number | null {\n if (value.trim() === '') return null\n const sep = props.decimalSeparator || '.'\n const normalized = sep === '.' ? value : value.replace(sep, '.')\n const n = Number(normalized)\n if (!Number.isFinite(n)) return null\n return n\n}\n\nfunction clamp(v: number): number {\n if (props.min !== undefined) v = Math.max(props.min, v)\n if (props.max !== undefined) v = Math.min(props.max, v)\n return v\n}\n\nfunction normalize(v: number): number {\n if (props.precision === undefined) return v\n const p = Math.max(0, props.precision)\n return Number(v.toFixed(p))\n}\n\nfunction format(n: number): string {\n const sep = props.decimalSeparator || '.'\n const p = props.precision\n\n // Без `precision` избегаем экспоненциальной записи (`String(1e21)` → `\"1e+21\"`),\n // форматируя через `Intl` без группировки разрядов.\n let s = p === undefined\n ? n.toLocaleString('en-US', { useGrouping: false, maximumFractionDigits: 20 })\n : n.toFixed(Math.max(0, p))\n if (sep !== '.') s = s.replace('.', sep)\n return s\n}\n\nfunction onInput(e: Event): void {\n const el = e.target as HTMLInputElement\n const raw = el.value\n const next = sanitize(raw)\n\n if (raw !== next) {\n // Сохраняем позицию каретки: считаем, сколько валидных символов было ДО\n // каретки, и ставим её после стольких же в очищенной строке — иначе при\n // редактировании середины числа каретка прыгала в конец.\n const caret = el.selectionStart ?? raw.length\n const keptBefore = sanitize(raw.slice(0, caret)).length\n el.value = next\n el.setSelectionRange(keptBefore, keptBefore)\n }\n\n draft.value = next\n\n const num = toNumber(next)\n if (num !== props.modelValue) emit('update:modelValue', num)\n}\n\n/**\n * Коммит набранного: границы и точность применяются здесь, а не на каждом\n * нажатии, — иначе `min=10` не дал бы набрать «1» как начало «15».\n */\nfunction commit(): void {\n // Без черновика коммитится сама модель: значение вне границ обязано\n // выправиться и тогда, когда его выставили снаружи, а не набрали руками.\n const num = draft.value !== null ? toNumber(draft.value) : props.modelValue\n const next = num === null ? null : clamp(normalize(num))\n\n draft.value = null\n\n if (next !== props.modelValue) emit('update:modelValue', next)\n emit('change', next)\n}\n\nfunction onChange(): void {\n // Клампинг границ (`min`/`max`) и нормализация — на `change`/`blur`, а не только\n // в кнопках: иначе ручной ввод «999» при `max=10` останется невалидным.\n commit()\n}\n\nfunction setValue(n: number): void {\n const next = clamp(normalize(n))\n // Шаг завершает набор: черновик снимается, показ считается от модели.\n draft.value = null\n emit('update:modelValue', next)\n emit('change', next)\n}\n\n/**\n * Шаг значения. Фокус не трогаем: кнопка ± обязана оставаться под фокусом,\n * иначе клавиатурный пользователь после первого Enter теряет её и повторно\n * нажать не может. Поле фокусирует тот, кто шагает от него самого.\n */\nfunction stepBy(dir: 1 | -1, step: number = props.step ?? 1): void {\n if (isDisabled.value || isReadonly.value) return\n\n const current = props.modelValue ?? 0\n setValue(addStep(current, step * dir))\n}\n\n// Интервал уже шагал: финальный `click` при отпускании — не намерение\n// пользователя сделать ещё один шаг, а хвост того же жеста.\nlet repeatFired = false\n\n/** Клик кнопки «±»: шаг, если это не хвост только что завершённого удержания. */\nfunction onStepClick(dir: 1 | -1, event: MouseEvent): void {\n // Enter/Space на кнопке дают click без указателя (`detail === 0`) — хвостом\n // жеста он быть не может. Иначе удержание, кончившееся без клика (отпускание\n // вне кнопки, `pointercancel` на таче), оставляло бы флаг взведённым, и\n // спинбаттон молча терял бы нажатие у клавиатуры и вспомогательных технологий.\n if (event.detail === 0) {\n repeatFired = false\n stepBy(dir)\n return\n }\n\n if (repeatFired) {\n repeatFired = false\n return\n }\n stepBy(dir)\n}\n\nfunction onKeydown(e: KeyboardEvent): void {\n if (isDisabled.value) return\n // Readonly-поле ведёт себя как текст: стрелки и Home/End отдаются нативной\n // каретке, значение не меняется.\n if (isReadonly.value) return\n\n switch (e.key) {\n case 'ArrowUp':\n e.preventDefault()\n stepBy(1)\n break\n case 'ArrowDown':\n e.preventDefault()\n stepBy(-1)\n break\n case 'PageUp':\n e.preventDefault()\n stepBy(1, bigStep(props.step ?? 1, props.min, props.max))\n break\n case 'PageDown':\n e.preventDefault()\n stepBy(-1, bigStep(props.step ?? 1, props.min, props.max))\n break\n case 'Home':\n if (props.min !== undefined) {\n e.preventDefault()\n setValue(props.min)\n }\n break\n case 'End':\n if (props.max !== undefined) {\n e.preventDefault()\n setValue(props.max)\n }\n break\n }\n}\n\n// `aria-valuenow` — числовое значение для скринридеров; отсутствует, если поле пусто.\nconst ariaValueNow = computed(() => props.modelValue ?? undefined)\n\n// На пределе кнопка ± гаснет: активная кнопка, у которой `clamp` съедает\n// результат, читается как сломанная.\nconst numericValue = computed(() => props.modelValue)\n\nconst canIncrease = computed(() => {\n if (props.max === undefined) return true\n const current = numericValue.value\n return current === null || normalize(current) < props.max\n})\n\nconst canDecrease = computed(() => {\n if (props.min === undefined) return true\n const current = numericValue.value\n return current === null || normalize(current) > props.min\n})\n\n// Локале-зависимое отображение: при фокусе показываем «сырое» значение для\n// редактирования, иначе — сгруппированное через `Intl.NumberFormat`.\nfunction formatGrouped(num: number): string {\n const sep = props.decimalSeparator || '.'\n // formatToParts позволяет оставить локале-зависимый групповой разделитель,\n // но принудительно подставить наш десятичный разделитель (`decimalSeparator`),\n // корректно работая для любой локали (в т.ч. de-DE, где группа — '.').\n return new Intl.NumberFormat(resolvedLocale.value, {\n useGrouping: true,\n minimumFractionDigits: props.precision ?? 0,\n maximumFractionDigits: props.precision ?? 20,\n })\n .formatToParts(num)\n .map(part => (part.type === 'decimal' ? sep : part.value))\n .join('')\n}\n\n/**\n * `aria-valuetext` нужен только при группировке: без неё `aria-valuenow` уже\n * несёт то же самое число, и дублировать его текстом незачем.\n */\nconst ariaValueText = computed(() => {\n if (!props.useGrouping) return undefined\n return props.modelValue === null ? undefined : formatGrouped(props.modelValue)\n})\n\n/**\n * Что показывает поле: набираемый черновик, пока он есть, иначе — модель.\n * Группировка только вне фокуса: при правке разделители разрядов мешают.\n */\nconst displayValue = computed(() => {\n if (draft.value !== null) return draft.value\n if (props.modelValue === null) return ''\n if (props.useGrouping && !isFocused.value) return formatGrouped(props.modelValue)\n return format(props.modelValue)\n})\n\nfunction onFocus(event: FocusEvent): void {\n isFocused.value = true\n emit('focus', event)\n}\n\nfunction onBlur(event: FocusEvent): void {\n isFocused.value = false\n // `change` до `blur` доходит не всегда (значение могло вернуться к исходному),\n // а черновик обязан сняться в любом случае — иначе поле застынет на нём.\n draft.value = null\n emit('blur', event)\n stopRepeat()\n}\n\n// ————— Удержание кнопки ±: шаг повторяется, пока кнопку держат.\nconst REPEAT_DELAY_MS = 400\nconst REPEAT_INTERVAL_MS = 60\n\nlet repeatDelay: ReturnType<typeof setTimeout> | null = null\nlet repeatTimer: ReturnType<typeof setInterval> | null = null\n\nfunction stopRepeat(): void {\n if (repeatDelay !== null) {\n clearTimeout(repeatDelay)\n repeatDelay = null\n }\n if (repeatTimer !== null) {\n clearInterval(repeatTimer)\n repeatTimer = null\n }\n}\n\nfunction canStep(dir: 1 | -1): boolean {\n return dir === 1 ? canIncrease.value : canDecrease.value\n}\n\n/**\n * `pointerdown`, а не `mousedown`: на тач-устройствах второго не бывает.\n * Повтор останавливается на границе — иначе таймер крутился бы вхолостую.\n */\nfunction startRepeat(dir: 1 | -1): void {\n if (isDisabled.value || isReadonly.value) return\n\n stopRepeat()\n repeatFired = false\n repeatDelay = setTimeout(() => {\n repeatTimer = setInterval(() => {\n if (!canStep(dir)) {\n stopRepeat()\n return\n }\n repeatFired = true\n stepBy(dir)\n }, REPEAT_INTERVAL_MS)\n }, REPEAT_DELAY_MS)\n}\n\nonBeforeUnmount(stopRepeat)\n\n// ————— Очистка значения.\nconst resolvedClearable = useGrComponentProp('GrNumberInput', 'clearable', () => props.clearable, false)\nconst resolvedClearLabel = computed(() => props.clearLabel ?? t('gr.input.clear', 'Clear'))\n\nconst clearVisible = computed(() =>\n resolvedClearable.value && !isDisabled.value && !isReadonly.value && props.modelValue !== null,\n)\n\nfunction clear(): void {\n draft.value = null\n emit('update:modelValue', null)\n emit('change', null)\n emit('clear')\n focus()\n}\n</script>\n\n<template>\n <div\n data-gr-number-input\n class=\"relative w-full overflow-hidden rounded-[var(--gr-radius-control)] border bg-[var(--gr-bg)] transition-colors duration-[var(--gr-duration-fast)] focus-within:ring-2 focus-within:ring-[var(--gr-ring)]\"\n :class=\"shellClassName\"\n >\n <div\n v-if=\"$slots.prefix\"\n ref=\"prefixEl\"\n data-testid=\"number-input-prefix\"\n data-gr-number-input-prefix\n class=\"absolute inset-y-0 left-0 flex items-center justify-center border-r border-[var(--gr-brd)] px-2 text-[var(--gr-muted-fg)] pointer-events-none select-none truncate\"\n :style=\"prefixStyle\"\n aria-hidden=\"true\"\n >\n <slot name=\"prefix\" />\n </div>\n\n <input\n :id=\"resolvedId\"\n ref=\"inputEl\"\n v-bind=\"$attrs\"\n :name=\"name\"\n type=\"text\"\n :inputmode=\"inputmode\"\n :autocomplete=\"autocomplete\"\n :placeholder=\"placeholder\"\n :disabled=\"isDisabled\"\n :value=\"displayValue\"\n role=\"spinbutton\"\n :aria-valuenow=\"ariaValueNow\"\n :aria-valuemin=\"min\"\n :aria-valuemax=\"max\"\n :aria-valuetext=\"ariaValueText\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-describedby=\"describedBy\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-readonly=\"isReadonly ? 'true' : undefined\"\n :aria-label=\"ariaLabel\"\n :readonly=\"isReadonly\"\n class=\"w-full bg-transparent text-[var(--gr-fg)] placeholder:text-[var(--gr-muted-fg)] focus:placeholder:text-transparent focus:outline-none disabled:cursor-not-allowed\"\n :class=\"inputClassName\"\n :style=\"inputStyle\"\n @input=\"onInput\"\n @change=\"onChange\"\n @keydown=\"onKeydown\"\n @focus=\"onFocus\"\n @blur=\"onBlur\"\n >\n\n <div\n v-if=\"$slots.suffix\"\n ref=\"suffixEl\"\n data-testid=\"number-input-suffix\"\n data-gr-number-input-suffix\n class=\"absolute inset-y-0 flex items-center justify-center border-l border-[var(--gr-brd)] px-2 text-[var(--gr-muted-fg)] pointer-events-none select-none truncate\"\n :class=\"suffixFixed ? '[direction:rtl]' : ''\"\n :style=\"suffixStyle\"\n aria-hidden=\"true\"\n >\n <slot name=\"suffix\" />\n </div>\n\n <button\n v-if=\"clearVisible\"\n type=\"button\"\n data-gr-number-input-clear\n :class=\"clearButtonClass\"\n :style=\"clearButtonStyle\"\n :aria-label=\"resolvedClearLabel\"\n @mousedown.prevent\n @click=\"clear\"\n >\n <GrIcon :size=\"14\">\n <IconX />\n </GrIcon>\n </button>\n\n <div\n v-if=\"hasVerticalControls\"\n data-testid=\"number-input-controls-vertical\"\n data-gr-number-input-controls=\"vertical\"\n class=\"absolute inset-y-0 flex items-center justify-center border-l border-[var(--gr-brd)]\"\n :style=\"verticalControlsStyle\"\n >\n <div class=\"flex flex-col justify-center gap-1\">\n <button\n type=\"button\"\n data-gr-number-input-increase\n :class=\"stepperCompactClass\"\n :disabled=\"isDisabled || isReadonly || !canIncrease\"\n :aria-label=\"resolvedIncreaseLabel\"\n @mousedown.prevent\n @pointerdown=\"startRepeat(1)\"\n @pointerup=\"stopRepeat\"\n @pointerleave=\"stopRepeat\"\n @pointercancel=\"stopRepeat\"\n @click=\"onStepClick(1, $event)\"\n >\n <GrIcon :size=\"12\">\n <IconChevronUp />\n </GrIcon>\n </button>\n <button\n type=\"button\"\n data-gr-number-input-decrease\n :class=\"stepperCompactClass\"\n :disabled=\"isDisabled || isReadonly || !canDecrease\"\n :aria-label=\"resolvedDecreaseLabel\"\n @mousedown.prevent\n @pointerdown=\"startRepeat(-1)\"\n @pointerup=\"stopRepeat\"\n @pointerleave=\"stopRepeat\"\n @pointercancel=\"stopRepeat\"\n @click=\"onStepClick(-1, $event)\"\n >\n <GrIcon :size=\"12\">\n <IconChevronDown />\n </GrIcon>\n </button>\n </div>\n </div>\n\n <div\n v-if=\"hasHorizontalControls\"\n data-testid=\"number-input-controls-horizontal-left\"\n data-gr-number-input-controls=\"horizontal-left\"\n class=\"absolute inset-y-0 flex items-stretch justify-center border-r border-[var(--gr-brd)]\"\n :style=\"horizontalLeftControlsStyle\"\n >\n <button\n type=\"button\"\n data-gr-number-input-decrease\n :class=\"stepperWideClass\"\n :disabled=\"isDisabled || isReadonly || !canDecrease\"\n :aria-label=\"resolvedDecreaseLabel\"\n @mousedown.prevent\n @pointerdown=\"startRepeat(-1)\"\n @pointerup=\"stopRepeat\"\n @pointerleave=\"stopRepeat\"\n @pointercancel=\"stopRepeat\"\n @click=\"onStepClick(-1, $event)\"\n >\n <GrIcon :size=\"12\">\n <IconChevronLeft />\n </GrIcon>\n </button>\n </div>\n\n <div\n v-if=\"hasHorizontalControls\"\n data-testid=\"number-input-controls-horizontal-right\"\n data-gr-number-input-controls=\"horizontal-right\"\n class=\"absolute inset-y-0 flex items-stretch justify-center border-l border-[var(--gr-brd)]\"\n :style=\"horizontalRightControlsStyle\"\n >\n <button\n type=\"button\"\n data-gr-number-input-increase\n :class=\"stepperWideClass\"\n :disabled=\"isDisabled || isReadonly || !canIncrease\"\n :aria-label=\"resolvedIncreaseLabel\"\n @mousedown.prevent\n @pointerdown=\"startRepeat(1)\"\n @pointerup=\"stopRepeat\"\n @pointerleave=\"stopRepeat\"\n @pointercancel=\"stopRepeat\"\n @click=\"onStepClick(1, $event)\"\n >\n <GrIcon :size=\"12\">\n <IconChevronRight />\n </GrIcon>\n </button>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport type { InputHTMLAttributes } from 'vue'\n\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { computed, onBeforeUnmount, ref } from 'vue'\n\nimport {\n clearButtonClass,\n stepperCompactClass,\n stepperWideClass,\n grNumberInputInputClass,\n grNumberInputShellClass,\n type GrNumberInputControlsDirection,\n type GrNumberInputSize,\n type GrNumberInputState,\n type GrNumberInputTextAlign,\n} from './grNumberInputStyles'\nimport { addStep, bigStep } from './numberInputMath'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport IconChevronDown from '~icons/lucide/chevron-down'\nimport IconChevronLeft from '~icons/lucide/chevron-left'\nimport IconChevronRight from '~icons/lucide/chevron-right'\nimport IconChevronUp from '~icons/lucide/chevron-up'\nimport IconX from '~icons/lucide/x'\nimport { addLen } from '../../composables/internal/useAddonMeasurement'\nimport { useControlAddons } from '../../composables/internal/useControlAddons'\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst ADDON_PX_BY_SIZE: Record<GrNumberInputSize, number> = {\n xs: 32,\n sm: 36,\n md: 40,\n lg: 48,\n}\n\nconst BASE_PADDING_X_LEN_BY_SIZE: Record<GrNumberInputSize, string> = {\n xs: '10px',\n sm: '12px',\n md: '12px',\n lg: '16px',\n}\n\nfunction px(n: number): string {\n return `${n}px`\n}\n\nexport interface GrNumberInputProps {\n /**\n * Значение поля. `null` — пусто.\n *\n * Незавершённый ввод («-», «1,») числом не является и в модель не попадает:\n * пока он набирается, поле держит его во внутреннем черновике, а модель\n * честно говорит «числа пока нет».\n */\n modelValue: number | null\n placeholder?: string\n autocomplete?: string\n inputmode?: InputHTMLAttributes['inputmode']\n disabled?: boolean\n /** Быстрый флаг невалидности; эквивалент `state='danger'` + `aria-invalid`. */\n invalid?: boolean\n /** Только для чтения: значение видно и уходит в форму, но не редактируется. */\n readonly?: boolean\n /** Обязательное поле (`aria-required`). Складывается с `required` у `GrFormField`. */\n required?: boolean\n /** Доступное имя вне `GrFormField`. */\n ariaLabel?: string\n state?: GrNumberInputState\n name?: string\n id?: string\n size?: GrNumberInputSize\n\n textAlign?: GrNumberInputTextAlign\n\n decimalSeparator?: string\n step?: number\n min?: number\n max?: number\n precision?: number\n\n /** Кнопка очистки значения. */\n clearable?: boolean\n /** A11y-подпись кнопки очистки. */\n clearLabel?: string\n /**\n * BCP-47 локаль для отображения значения (группировка разрядов и разделители\n * через `Intl.NumberFormat`). Работает вместе с `useGrouping`.\n */\n locale?: string\n /**\n * Группировать разряды при отображении (когда поле не в фокусе). При фокусе\n * показывается «сырое» значение для редактирования. По умолчанию выключено.\n */\n useGrouping?: boolean\n\n /** Показывать кнопки +/-. */\n controls?: boolean\n controlsDirection?: GrNumberInputControlsDirection\n\n prefixMinWidth?: string\n prefixMaxWidth?: string\n suffixMinWidth?: string\n suffixMaxWidth?: string\n /**\n * Фиксированная ширина у prefix/suffix: жёсткая ширина (из `*MaxWidth` →\n * `*MinWidth` → дефолт) + обрезка контента по краю (prefix — справа,\n * suffix — слева). По умолчанию аддоны растягиваются под контент.\n */\n prefixFixed?: boolean\n suffixFixed?: boolean\n\n /** i18n-friendly aria-label для кнопки \"увеличить\". */\n increaseLabel?: string\n /** i18n-friendly aria-label для кнопки \"уменьшить\". */\n decreaseLabel?: string\n}\n\nexport interface GrNumberInputEmits {\n (e: 'update:modelValue', value: number | null): void\n (e: 'change', value: number | null): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n /** Значение стёрто кнопкой очистки. */\n (e: 'clear'): void\n}\n\n// Контекст `GrFormField` — fallback для id/описания/невалидности, как в `GrInput`.\nconst field = useGrFormFieldContext()\n\nconst props = withDefaults(defineProps<GrNumberInputProps>(), {\n placeholder: undefined,\n autocomplete: undefined,\n inputmode: 'decimal',\n disabled: false,\n invalid: false,\n readonly: false,\n required: false,\n ariaLabel: undefined,\n state: 'default',\n name: undefined,\n id: undefined,\n size: undefined,\n\n textAlign: 'left',\n\n decimalSeparator: '.',\n step: 1,\n min: undefined,\n max: undefined,\n precision: undefined,\n locale: undefined,\n useGrouping: false,\n clearable: undefined,\n clearLabel: undefined,\n\n controls: false,\n controlsDirection: 'vertical',\n\n prefixMinWidth: undefined,\n prefixMaxWidth: undefined,\n suffixMinWidth: undefined,\n suffixMaxWidth: undefined,\n prefixFixed: false,\n suffixFixed: false,\n\n increaseLabel: undefined,\n decreaseLabel: undefined,\n})\n\n// Эффективный размер: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrNumberInput' })\n\nconst resolvedId = computed(() => props.id ?? field?.id.value)\nconst describedBy = computed(() => field?.describedById.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n} = useGrFormControl(() => props)\n\nconst emit = defineEmits<GrNumberInputEmits>()\ndefineSlots<{\n /** Аддон слева от поля: знак валюты, иконка. */\n prefix?: () => any\n /** Аддон справа от поля: единица измерения. */\n suffix?: () => any\n}>()\n\n\nconst inputEl = ref<HTMLInputElement | null>(null)\n\n// В фокусе поле показывает «сырое» значение: группировка мешала бы правке.\nconst isFocused = ref(false)\n\n/**\n * Строка ровно в том виде, в каком её набирают. Модель к этому моменту может\n * быть `null` («-», «1,» — не числа), и без черновика поле стирало бы под\n * пальцами то, что печатают. Коммит (`change`/`blur`) черновик снимает.\n */\nconst draft = ref<string | null>(null)\n\nfunction focus(): void {\n inputEl.value?.focus()\n}\n\nfunction blur(): void {\n inputEl.value?.blur()\n}\n\ndefineExpose({ focus, blur })\n\nconst { t, locale } = useGranularityTranslations()\n\n/**\n * Локаль форматирования: свой проп → локаль i18n-адаптера → окружение. Без\n * второго шага мультиязычное приложение передавало бы `locale` каждому полю.\n */\nconst resolvedLocale = computed(() => props.locale ?? locale.value)\nconst resolvedIncreaseLabel = computed(() => props.increaseLabel ?? t('gr.numberInput.increase', 'Increase'))\nconst resolvedDecreaseLabel = computed(() => props.decreaseLabel ?? t('gr.numberInput.decrease', 'Decrease'))\n\nconst hasHorizontalControls = computed(() => props.controls && props.controlsDirection === 'horizontal')\nconst hasVerticalControls = computed(() => props.controls && props.controlsDirection === 'vertical')\n\nconst addonPx = computed(() => ADDON_PX_BY_SIZE[resolvedSize.value])\nconst addonLen = computed(() => px(addonPx.value))\nconst basePaddingXLen = computed(() => BASE_PADDING_X_LEN_BY_SIZE[resolvedSize.value])\n\n/** Кнопки ± справа: вертикальный стек — одна колонка, горизонтальный — вторая. */\nconst rightControlsCount = computed(() =>\n (hasHorizontalControls.value ? 1 : 0) + (hasVerticalControls.value ? 1 : 0))\n\nconst {\n prefixEl,\n suffixEl,\n prefixLen,\n suffixLen,\n prefixStyle,\n suffixStyle,\n fieldPadding: inputStyle,\n} = useControlAddons(() => props, {\n defaultMinWidth: () => addonLen.value,\n paddingX: () => basePaddingXLen.value,\n leadingReserve: () => (hasHorizontalControls.value ? addonLen.value : '0px'),\n trailingReserve: () => (rightControlsCount.value > 0 ? px(addonPx.value * rightControlsCount.value) : '0px'),\n // Суффикс позиционируется абсолютно: без якоря он уехал бы от правого края.\n anchorSuffixRight: true,\n})\n\nfunction addonStyle(side: 'left' | 'right', offset: string): Record<string, string> {\n return {\n width: addonLen.value,\n [side]: offset,\n }\n}\n\nconst verticalControlsStyle = computed(() => addonStyle('right', suffixLen.value))\n\n/**\n * Крестик очистки живёт левее правой зоны (суффикс + кнопки ±) — иначе он лёг бы\n * на них. Ширины там же, где их считает поле, поэтому переиспользуем расчёт.\n */\nconst clearButtonStyle = computed(() => {\n const controls = rightControlsCount.value > 0 ? px(addonPx.value * rightControlsCount.value) : '0px'\n\n return { right: addLen(addLen(suffixLen.value, controls), '6px') }\n})\nconst horizontalLeftControlsStyle = computed(() => addonStyle('left', prefixLen.value))\nconst horizontalRightControlsStyle = computed(() => addonStyle('right', suffixLen.value))\n\nconst shellClassName = computed(() => {\n return grNumberInputShellClass({\n disabled: isDisabled.value,\n state: props.state,\n invalid: isInvalid.value,\n })\n})\n\nconst inputClassName = computed(() => {\n return grNumberInputInputClass({\n size: resolvedSize.value,\n textAlign: props.textAlign,\n })\n})\n\nfunction isDigit(ch: string): boolean {\n return ch >= '0' && ch <= '9'\n}\n\nfunction sanitize(raw: string): string {\n const sep = props.decimalSeparator || '.'\n\n let out = ''\n let hasSepLocal = false\n\n for (let i = 0; i < raw.length; i++) {\n const ch = raw[i]\n\n // Ведущий минус: разрешаем один в начале, чтобы можно было ввести\n // отрицательное значение с клавиатуры (границы проверит `clamp` на change).\n if (ch === '-' && out.length === 0) {\n out += '-'\n continue\n }\n\n if (isDigit(ch)) {\n out += ch\n continue\n }\n\n if ((ch === sep || ch === '.' || ch === ',') && !hasSepLocal) {\n out += sep\n hasSepLocal = true\n }\n }\n\n return out\n}\n\nfunction toNumber(value: string): number | null {\n if (value.trim() === '') return null\n const sep = props.decimalSeparator || '.'\n const normalized = sep === '.' ? value : value.replace(sep, '.')\n const n = Number(normalized)\n if (!Number.isFinite(n)) return null\n return n\n}\n\nfunction clamp(v: number): number {\n if (props.min !== undefined) v = Math.max(props.min, v)\n if (props.max !== undefined) v = Math.min(props.max, v)\n return v\n}\n\nfunction normalize(v: number): number {\n if (props.precision === undefined) return v\n const p = Math.max(0, props.precision)\n return Number(v.toFixed(p))\n}\n\nfunction format(n: number): string {\n const sep = props.decimalSeparator || '.'\n const p = props.precision\n\n // Без `precision` избегаем экспоненциальной записи (`String(1e21)` → `\"1e+21\"`),\n // форматируя через `Intl` без группировки разрядов.\n let s = p === undefined\n ? n.toLocaleString('en-US', { useGrouping: false, maximumFractionDigits: 20 })\n : n.toFixed(Math.max(0, p))\n if (sep !== '.') s = s.replace('.', sep)\n return s\n}\n\nfunction onInput(e: Event): void {\n const el = e.target as HTMLInputElement\n const raw = el.value\n const next = sanitize(raw)\n\n if (raw !== next) {\n // Сохраняем позицию каретки: считаем, сколько валидных символов было ДО\n // каретки, и ставим её после стольких же в очищенной строке — иначе при\n // редактировании середины числа каретка прыгала в конец.\n const caret = el.selectionStart ?? raw.length\n const keptBefore = sanitize(raw.slice(0, caret)).length\n el.value = next\n el.setSelectionRange(keptBefore, keptBefore)\n }\n\n draft.value = next\n\n const num = toNumber(next)\n if (num !== props.modelValue) emit('update:modelValue', num)\n}\n\n/**\n * Коммит набранного: границы и точность применяются здесь, а не на каждом\n * нажатии, — иначе `min=10` не дал бы набрать «1» как начало «15».\n */\nfunction commit(): void {\n // Без черновика коммитится сама модель: значение вне границ обязано\n // выправиться и тогда, когда его выставили снаружи, а не набрали руками.\n const num = draft.value !== null ? toNumber(draft.value) : props.modelValue\n const next = num === null ? null : clamp(normalize(num))\n\n draft.value = null\n\n if (next !== props.modelValue) emit('update:modelValue', next)\n emit('change', next)\n}\n\nfunction onChange(): void {\n // Клампинг границ (`min`/`max`) и нормализация — на `change`/`blur`, а не только\n // в кнопках: иначе ручной ввод «999» при `max=10` останется невалидным.\n commit()\n}\n\nfunction setValue(n: number): void {\n const next = clamp(normalize(n))\n // Шаг завершает набор: черновик снимается, показ считается от модели.\n draft.value = null\n emit('update:modelValue', next)\n emit('change', next)\n}\n\n/**\n * Шаг значения. Фокус не трогаем: кнопка ± обязана оставаться под фокусом,\n * иначе клавиатурный пользователь после первого Enter теряет её и повторно\n * нажать не может. Поле фокусирует тот, кто шагает от него самого.\n */\nfunction stepBy(dir: 1 | -1, step: number = props.step ?? 1): void {\n if (isDisabled.value || isReadonly.value) return\n\n const current = props.modelValue ?? 0\n setValue(addStep(current, step * dir))\n}\n\n// Интервал уже шагал: финальный `click` при отпускании — не намерение\n// пользователя сделать ещё один шаг, а хвост того же жеста.\nlet repeatFired = false\n\n/** Клик кнопки «±»: шаг, если это не хвост только что завершённого удержания. */\nfunction onStepClick(dir: 1 | -1, event: MouseEvent): void {\n // Enter/Space на кнопке дают click без указателя (`detail === 0`) — хвостом\n // жеста он быть не может. Иначе удержание, кончившееся без клика (отпускание\n // вне кнопки, `pointercancel` на таче), оставляло бы флаг взведённым, и\n // спинбаттон молча терял бы нажатие у клавиатуры и вспомогательных технологий.\n if (event.detail === 0) {\n repeatFired = false\n stepBy(dir)\n return\n }\n\n if (repeatFired) {\n repeatFired = false\n return\n }\n stepBy(dir)\n}\n\nfunction onKeydown(e: KeyboardEvent): void {\n if (isDisabled.value) return\n // Readonly-поле ведёт себя как текст: стрелки и Home/End отдаются нативной\n // каретке, значение не меняется.\n if (isReadonly.value) return\n\n switch (e.key) {\n case 'ArrowUp':\n e.preventDefault()\n stepBy(1)\n break\n case 'ArrowDown':\n e.preventDefault()\n stepBy(-1)\n break\n case 'PageUp':\n e.preventDefault()\n stepBy(1, bigStep(props.step ?? 1, props.min, props.max))\n break\n case 'PageDown':\n e.preventDefault()\n stepBy(-1, bigStep(props.step ?? 1, props.min, props.max))\n break\n case 'Home':\n if (props.min !== undefined) {\n e.preventDefault()\n setValue(props.min)\n }\n break\n case 'End':\n if (props.max !== undefined) {\n e.preventDefault()\n setValue(props.max)\n }\n break\n }\n}\n\n// `aria-valuenow` — числовое значение для скринридеров; отсутствует, если поле пусто.\nconst ariaValueNow = computed(() => props.modelValue ?? undefined)\n\n// На пределе кнопка ± гаснет: активная кнопка, у которой `clamp` съедает\n// результат, читается как сломанная.\nconst numericValue = computed(() => props.modelValue)\n\nconst canIncrease = computed(() => {\n if (props.max === undefined) return true\n const current = numericValue.value\n return current === null || normalize(current) < props.max\n})\n\nconst canDecrease = computed(() => {\n if (props.min === undefined) return true\n const current = numericValue.value\n return current === null || normalize(current) > props.min\n})\n\n// Локале-зависимое отображение: при фокусе показываем «сырое» значение для\n// редактирования, иначе — сгруппированное через `Intl.NumberFormat`.\nfunction formatGrouped(num: number): string {\n const sep = props.decimalSeparator || '.'\n // formatToParts позволяет оставить локале-зависимый групповой разделитель,\n // но принудительно подставить наш десятичный разделитель (`decimalSeparator`),\n // корректно работая для любой локали (в т.ч. de-DE, где группа — '.').\n return new Intl.NumberFormat(resolvedLocale.value, {\n useGrouping: true,\n minimumFractionDigits: props.precision ?? 0,\n maximumFractionDigits: props.precision ?? 20,\n })\n .formatToParts(num)\n .map(part => (part.type === 'decimal' ? sep : part.value))\n .join('')\n}\n\n/**\n * `aria-valuetext` нужен только при группировке: без неё `aria-valuenow` уже\n * несёт то же самое число, и дублировать его текстом незачем.\n */\nconst ariaValueText = computed(() => {\n if (!props.useGrouping) return undefined\n return props.modelValue === null ? undefined : formatGrouped(props.modelValue)\n})\n\n/**\n * Что показывает поле: набираемый черновик, пока он есть, иначе — модель.\n * Группировка только вне фокуса: при правке разделители разрядов мешают.\n */\nconst displayValue = computed(() => {\n if (draft.value !== null) return draft.value\n if (props.modelValue === null) return ''\n if (props.useGrouping && !isFocused.value) return formatGrouped(props.modelValue)\n return format(props.modelValue)\n})\n\nfunction onFocus(event: FocusEvent): void {\n isFocused.value = true\n emit('focus', event)\n}\n\nfunction onBlur(event: FocusEvent): void {\n isFocused.value = false\n // `change` до `blur` доходит не всегда (значение могло вернуться к исходному),\n // а черновик обязан сняться в любом случае — иначе поле застынет на нём.\n draft.value = null\n emit('blur', event)\n stopRepeat()\n}\n\n// ————— Удержание кнопки ±: шаг повторяется, пока кнопку держат.\nconst REPEAT_DELAY_MS = 400\nconst REPEAT_INTERVAL_MS = 60\n\nlet repeatDelay: ReturnType<typeof setTimeout> | null = null\nlet repeatTimer: ReturnType<typeof setInterval> | null = null\n\nfunction stopRepeat(): void {\n if (repeatDelay !== null) {\n clearTimeout(repeatDelay)\n repeatDelay = null\n }\n if (repeatTimer !== null) {\n clearInterval(repeatTimer)\n repeatTimer = null\n }\n}\n\nfunction canStep(dir: 1 | -1): boolean {\n return dir === 1 ? canIncrease.value : canDecrease.value\n}\n\n/**\n * `pointerdown`, а не `mousedown`: на тач-устройствах второго не бывает.\n * Повтор останавливается на границе — иначе таймер крутился бы вхолостую.\n */\nfunction startRepeat(dir: 1 | -1): void {\n if (isDisabled.value || isReadonly.value) return\n\n stopRepeat()\n repeatFired = false\n repeatDelay = setTimeout(() => {\n repeatTimer = setInterval(() => {\n if (!canStep(dir)) {\n stopRepeat()\n return\n }\n repeatFired = true\n stepBy(dir)\n }, REPEAT_INTERVAL_MS)\n }, REPEAT_DELAY_MS)\n}\n\nonBeforeUnmount(stopRepeat)\n\n// ————— Очистка значения.\nconst resolvedClearable = useGrComponentProp('GrNumberInput', 'clearable', () => props.clearable, false)\nconst resolvedClearLabel = computed(() => props.clearLabel ?? t('gr.input.clear', 'Clear'))\n\nconst clearVisible = computed(() =>\n resolvedClearable.value && !isDisabled.value && !isReadonly.value && props.modelValue !== null,\n)\n\nfunction clear(): void {\n draft.value = null\n emit('update:modelValue', null)\n emit('change', null)\n emit('clear')\n focus()\n}\n</script>\n\n<template>\n <div\n data-gr-number-input\n class=\"relative w-full overflow-hidden rounded-[var(--gr-radius-control)] border bg-[var(--gr-bg)] transition-colors duration-[var(--gr-duration-fast)] focus-within:ring-2 focus-within:ring-[var(--gr-ring)]\"\n :class=\"shellClassName\"\n >\n <div\n v-if=\"$slots.prefix\"\n ref=\"prefixEl\"\n data-testid=\"number-input-prefix\"\n data-gr-number-input-prefix\n class=\"absolute inset-y-0 left-0 flex items-center justify-center border-r border-[var(--gr-brd)] px-2 text-[var(--gr-muted-fg)] pointer-events-none select-none truncate\"\n :style=\"prefixStyle\"\n aria-hidden=\"true\"\n >\n <slot name=\"prefix\" />\n </div>\n\n <input\n :id=\"resolvedId\"\n ref=\"inputEl\"\n v-bind=\"$attrs\"\n :name=\"name\"\n type=\"text\"\n :inputmode=\"inputmode\"\n :autocomplete=\"autocomplete\"\n :placeholder=\"placeholder\"\n :disabled=\"isDisabled\"\n :value=\"displayValue\"\n role=\"spinbutton\"\n :aria-valuenow=\"ariaValueNow\"\n :aria-valuemin=\"min\"\n :aria-valuemax=\"max\"\n :aria-valuetext=\"ariaValueText\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-describedby=\"describedBy\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-readonly=\"isReadonly ? 'true' : undefined\"\n :aria-label=\"ariaLabel\"\n :readonly=\"isReadonly\"\n class=\"w-full bg-transparent text-[var(--gr-fg)] placeholder:text-[var(--gr-muted-fg)] focus:placeholder:text-transparent focus:outline-none disabled:cursor-not-allowed\"\n :class=\"inputClassName\"\n :style=\"inputStyle\"\n @input=\"onInput\"\n @change=\"onChange\"\n @keydown=\"onKeydown\"\n @focus=\"onFocus\"\n @blur=\"onBlur\"\n >\n\n <div\n v-if=\"$slots.suffix\"\n ref=\"suffixEl\"\n data-testid=\"number-input-suffix\"\n data-gr-number-input-suffix\n class=\"absolute inset-y-0 flex items-center justify-center border-l border-[var(--gr-brd)] px-2 text-[var(--gr-muted-fg)] pointer-events-none select-none truncate\"\n :class=\"suffixFixed ? '[direction:rtl]' : ''\"\n :style=\"suffixStyle\"\n aria-hidden=\"true\"\n >\n <slot name=\"suffix\" />\n </div>\n\n <button\n v-if=\"clearVisible\"\n type=\"button\"\n data-gr-number-input-clear\n :class=\"clearButtonClass\"\n :style=\"clearButtonStyle\"\n :aria-label=\"resolvedClearLabel\"\n @mousedown.prevent\n @click=\"clear\"\n >\n <GrIcon :size=\"14\">\n <IconX />\n </GrIcon>\n </button>\n\n <div\n v-if=\"hasVerticalControls\"\n data-testid=\"number-input-controls-vertical\"\n data-gr-number-input-controls=\"vertical\"\n class=\"absolute inset-y-0 flex items-center justify-center border-l border-[var(--gr-brd)]\"\n :style=\"verticalControlsStyle\"\n >\n <div class=\"flex flex-col justify-center gap-1\">\n <button\n type=\"button\"\n data-gr-number-input-increase\n :class=\"stepperCompactClass\"\n :disabled=\"isDisabled || isReadonly || !canIncrease\"\n :aria-label=\"resolvedIncreaseLabel\"\n @mousedown.prevent\n @pointerdown=\"startRepeat(1)\"\n @pointerup=\"stopRepeat\"\n @pointerleave=\"stopRepeat\"\n @pointercancel=\"stopRepeat\"\n @click=\"onStepClick(1, $event)\"\n >\n <GrIcon :size=\"12\">\n <IconChevronUp />\n </GrIcon>\n </button>\n <button\n type=\"button\"\n data-gr-number-input-decrease\n :class=\"stepperCompactClass\"\n :disabled=\"isDisabled || isReadonly || !canDecrease\"\n :aria-label=\"resolvedDecreaseLabel\"\n @mousedown.prevent\n @pointerdown=\"startRepeat(-1)\"\n @pointerup=\"stopRepeat\"\n @pointerleave=\"stopRepeat\"\n @pointercancel=\"stopRepeat\"\n @click=\"onStepClick(-1, $event)\"\n >\n <GrIcon :size=\"12\">\n <IconChevronDown />\n </GrIcon>\n </button>\n </div>\n </div>\n\n <div\n v-if=\"hasHorizontalControls\"\n data-testid=\"number-input-controls-horizontal-left\"\n data-gr-number-input-controls=\"horizontal-left\"\n class=\"absolute inset-y-0 flex items-stretch justify-center border-r border-[var(--gr-brd)]\"\n :style=\"horizontalLeftControlsStyle\"\n >\n <button\n type=\"button\"\n data-gr-number-input-decrease\n :class=\"stepperWideClass\"\n :disabled=\"isDisabled || isReadonly || !canDecrease\"\n :aria-label=\"resolvedDecreaseLabel\"\n @mousedown.prevent\n @pointerdown=\"startRepeat(-1)\"\n @pointerup=\"stopRepeat\"\n @pointerleave=\"stopRepeat\"\n @pointercancel=\"stopRepeat\"\n @click=\"onStepClick(-1, $event)\"\n >\n <GrIcon :size=\"12\">\n <IconChevronLeft />\n </GrIcon>\n </button>\n </div>\n\n <div\n v-if=\"hasHorizontalControls\"\n data-testid=\"number-input-controls-horizontal-right\"\n data-gr-number-input-controls=\"horizontal-right\"\n class=\"absolute inset-y-0 flex items-stretch justify-center border-l border-[var(--gr-brd)]\"\n :style=\"horizontalRightControlsStyle\"\n >\n <button\n type=\"button\"\n data-gr-number-input-increase\n :class=\"stepperWideClass\"\n :disabled=\"isDisabled || isReadonly || !canIncrease\"\n :aria-label=\"resolvedIncreaseLabel\"\n @mousedown.prevent\n @pointerdown=\"startRepeat(1)\"\n @pointerup=\"stopRepeat\"\n @pointerleave=\"stopRepeat\"\n @pointercancel=\"stopRepeat\"\n @click=\"onStepClick(1, $event)\"\n >\n <GrIcon :size=\"12\">\n <IconChevronRight />\n </GrIcon>\n </button>\n </div>\n </div>\n</template>\n"],"mappings":"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"}
1
+ {"version":3,"file":"GrNumberInput-DQ59YICu.js","names":["$slots","$attrs"],"sources":["../../../../src/components/GrNumberInput/numberInputMath.ts","../../../../src/components/GrNumberInput/GrNumberInput.vue","../../../../src/components/GrNumberInput/GrNumberInput.vue"],"sourcesContent":["export { bigStep } from '../shared/numericStep'\n\n/**\n * Экспоненциальная запись разбирается отдельно: `String(1e-7)` — это `\"1e-7\"`,\n * и поиск точки насчитал бы ноль знаков, после чего округление съело бы всё\n * значение.\n */\nexport function decimalsOf(value: number): number {\n if (!Number.isFinite(value)) return 0\n\n const text = String(Math.abs(value))\n\n const exponential = text.match(/^\\d+(?:\\.(\\d+))?e-(\\d+)$/)\n if (exponential) {\n const [, fraction = '', exponent] = exponential\n return fraction.length + Number(exponent)\n }\n\n const dot = text.indexOf('.')\n return dot === -1 ? 0 : text.length - dot - 1\n}\n\n/**\n * Двоичная дробь копит ошибку, поэтому сумма округляется. Разрядность — по\n * большему из операндов: шаг `0.5` от значения `1.25` обязан дать `1.75`.\n */\nexport function addStep(current: number, step: number): number {\n const decimals = Math.max(decimalsOf(current), decimalsOf(step))\n const sum = current + step\n\n // `toFixed` за пределами 100 знаков бросает.\n return decimals > 100 ? sum : Number(sum.toFixed(decimals))\n}\n","<script setup lang=\"ts\">\nimport type { InputHTMLAttributes } from 'vue'\n\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { computed, onBeforeUnmount, ref } from 'vue'\n\nimport {\n clearButtonClass,\n stepperCompactClass,\n stepperWideClass,\n grNumberInputInputClass,\n grNumberInputShellClass,\n type GrNumberInputControlsDirection,\n type GrNumberInputSize,\n type GrNumberInputState,\n type GrNumberInputTextAlign,\n} from './grNumberInputStyles'\nimport { addStep, bigStep } from './numberInputMath'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport IconChevronDown from '~icons/lucide/chevron-down'\nimport IconChevronLeft from '~icons/lucide/chevron-left'\nimport IconChevronRight from '~icons/lucide/chevron-right'\nimport IconChevronUp from '~icons/lucide/chevron-up'\nimport IconX from '~icons/lucide/x'\nimport { addLen } from '../../composables/internal/useAddonMeasurement'\nimport { useControlAddons } from '../../composables/internal/useControlAddons'\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst ADDON_PX_BY_SIZE: Record<GrNumberInputSize, number> = {\n xs: 32,\n sm: 36,\n md: 40,\n lg: 48,\n}\n\nconst BASE_PADDING_X_LEN_BY_SIZE: Record<GrNumberInputSize, string> = {\n xs: '10px',\n sm: '12px',\n md: '12px',\n lg: '16px',\n}\n\nfunction px(n: number): string {\n return `${n}px`\n}\n\nexport interface GrNumberInputProps {\n /**\n * Значение поля. `null` — пусто.\n *\n * Незавершённый ввод («-», «1,») числом не является и в модель не попадает:\n * пока он набирается, поле держит его во внутреннем черновике, а модель\n * честно говорит «числа пока нет».\n */\n modelValue: number | null\n placeholder?: string\n autocomplete?: string\n inputmode?: InputHTMLAttributes['inputmode']\n disabled?: boolean\n /** Быстрый флаг невалидности; эквивалент `state='danger'` + `aria-invalid`. */\n invalid?: boolean\n /** Только для чтения: значение видно и уходит в форму, но не редактируется. */\n readonly?: boolean\n /** Обязательное поле (`aria-required`). Складывается с `required` у `GrFormField`. */\n required?: boolean\n /** Доступное имя вне `GrFormField`. */\n ariaLabel?: string\n state?: GrNumberInputState\n name?: string\n id?: string\n size?: GrNumberInputSize\n\n textAlign?: GrNumberInputTextAlign\n\n decimalSeparator?: string\n step?: number\n min?: number\n max?: number\n precision?: number\n\n /** Кнопка очистки значения. */\n clearable?: boolean\n /** A11y-подпись кнопки очистки. */\n clearLabel?: string\n /**\n * BCP-47 локаль для отображения значения (группировка разрядов и разделители\n * через `Intl.NumberFormat`). Работает вместе с `useGrouping`.\n */\n locale?: string\n /**\n * Группировать разряды при отображении (когда поле не в фокусе). При фокусе\n * показывается «сырое» значение для редактирования. По умолчанию выключено.\n */\n useGrouping?: boolean\n\n /** Показывать кнопки +/-. */\n controls?: boolean\n controlsDirection?: GrNumberInputControlsDirection\n\n prefixMinWidth?: string\n prefixMaxWidth?: string\n suffixMinWidth?: string\n suffixMaxWidth?: string\n /**\n * Фиксированная ширина у prefix/suffix: жёсткая ширина (из `*MaxWidth` →\n * `*MinWidth` → дефолт) + обрезка контента по краю (prefix — справа,\n * suffix — слева). По умолчанию аддоны растягиваются под контент.\n */\n prefixFixed?: boolean\n suffixFixed?: boolean\n\n /** i18n-friendly aria-label для кнопки \"увеличить\". */\n increaseLabel?: string\n /** i18n-friendly aria-label для кнопки \"уменьшить\". */\n decreaseLabel?: string\n}\n\nexport interface GrNumberInputEmits {\n (e: 'update:modelValue', value: number | null): void\n (e: 'change', value: number | null): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n /** Значение стёрто кнопкой очистки. */\n (e: 'clear'): void\n}\n\n// Контекст `GrFormField` — fallback для id/описания/невалидности, как в `GrInput`.\nconst field = useGrFormFieldContext()\n\nconst props = withDefaults(defineProps<GrNumberInputProps>(), {\n placeholder: undefined,\n autocomplete: undefined,\n inputmode: 'decimal',\n disabled: false,\n invalid: false,\n readonly: false,\n required: false,\n ariaLabel: undefined,\n state: 'default',\n name: undefined,\n id: undefined,\n size: undefined,\n\n textAlign: 'left',\n\n decimalSeparator: '.',\n step: 1,\n min: undefined,\n max: undefined,\n precision: undefined,\n locale: undefined,\n useGrouping: false,\n clearable: undefined,\n clearLabel: undefined,\n\n controls: false,\n controlsDirection: 'vertical',\n\n prefixMinWidth: undefined,\n prefixMaxWidth: undefined,\n suffixMinWidth: undefined,\n suffixMaxWidth: undefined,\n prefixFixed: false,\n suffixFixed: false,\n\n increaseLabel: undefined,\n decreaseLabel: undefined,\n})\n\n// Эффективный размер: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrNumberInput' })\n\nconst resolvedId = computed(() => props.id ?? field?.id.value)\nconst describedBy = computed(() => field?.describedById.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n} = useGrFormControl(() => props)\n\nconst emit = defineEmits<GrNumberInputEmits>()\ndefineSlots<{\n /** Аддон слева от поля: знак валюты, иконка. */\n prefix?: () => any\n /** Аддон справа от поля: единица измерения. */\n suffix?: () => any\n}>()\n\n\nconst inputEl = ref<HTMLInputElement | null>(null)\n\n// В фокусе поле показывает «сырое» значение: группировка мешала бы правке.\nconst isFocused = ref(false)\n\n/**\n * Строка ровно в том виде, в каком её набирают. Модель к этому моменту может\n * быть `null` («-», «1,» — не числа), и без черновика поле стирало бы под\n * пальцами то, что печатают. Коммит (`change`/`blur`) черновик снимает.\n */\nconst draft = ref<string | null>(null)\n\nfunction focus(): void {\n inputEl.value?.focus()\n}\n\nfunction blur(): void {\n inputEl.value?.blur()\n}\n\ndefineExpose({ focus, blur })\n\nconst { t, locale } = useGranularityTranslations()\n\n/**\n * Локаль форматирования: свой проп → локаль i18n-адаптера → окружение. Без\n * второго шага мультиязычное приложение передавало бы `locale` каждому полю.\n */\nconst resolvedLocale = computed(() => props.locale ?? locale.value)\nconst resolvedIncreaseLabel = computed(() => props.increaseLabel ?? t('gr.numberInput.increase', 'Increase'))\nconst resolvedDecreaseLabel = computed(() => props.decreaseLabel ?? t('gr.numberInput.decrease', 'Decrease'))\n\nconst hasHorizontalControls = computed(() => props.controls && props.controlsDirection === 'horizontal')\nconst hasVerticalControls = computed(() => props.controls && props.controlsDirection === 'vertical')\n\nconst addonPx = computed(() => ADDON_PX_BY_SIZE[resolvedSize.value])\nconst addonLen = computed(() => px(addonPx.value))\nconst basePaddingXLen = computed(() => BASE_PADDING_X_LEN_BY_SIZE[resolvedSize.value])\n\n/** Кнопки ± справа: вертикальный стек — одна колонка, горизонтальный — вторая. */\nconst rightControlsCount = computed(() =>\n (hasHorizontalControls.value ? 1 : 0) + (hasVerticalControls.value ? 1 : 0))\n\nconst {\n prefixEl,\n suffixEl,\n prefixLen,\n suffixLen,\n prefixStyle,\n suffixStyle,\n fieldPadding: inputStyle,\n} = useControlAddons(() => props, {\n defaultMinWidth: () => addonLen.value,\n paddingX: () => basePaddingXLen.value,\n leadingReserve: () => (hasHorizontalControls.value ? addonLen.value : '0px'),\n trailingReserve: () => (rightControlsCount.value > 0 ? px(addonPx.value * rightControlsCount.value) : '0px'),\n // Суффикс позиционируется абсолютно: без якоря он уехал бы от правого края.\n anchorSuffixRight: true,\n})\n\nfunction addonStyle(side: 'left' | 'right', offset: string): Record<string, string> {\n return {\n width: addonLen.value,\n [side]: offset,\n }\n}\n\nconst verticalControlsStyle = computed(() => addonStyle('right', suffixLen.value))\n\n/**\n * Крестик очистки живёт левее правой зоны (суффикс + кнопки ±) — иначе он лёг бы\n * на них. Ширины там же, где их считает поле, поэтому переиспользуем расчёт.\n */\nconst clearButtonStyle = computed(() => {\n const controls = rightControlsCount.value > 0 ? px(addonPx.value * rightControlsCount.value) : '0px'\n\n return { right: addLen(addLen(suffixLen.value, controls), '6px') }\n})\nconst horizontalLeftControlsStyle = computed(() => addonStyle('left', prefixLen.value))\nconst horizontalRightControlsStyle = computed(() => addonStyle('right', suffixLen.value))\n\nconst shellClassName = computed(() => {\n return grNumberInputShellClass({\n disabled: isDisabled.value,\n state: props.state,\n invalid: isInvalid.value,\n })\n})\n\nconst inputClassName = computed(() => {\n return grNumberInputInputClass({\n size: resolvedSize.value,\n textAlign: props.textAlign,\n })\n})\n\nfunction isDigit(ch: string): boolean {\n return ch >= '0' && ch <= '9'\n}\n\nfunction sanitize(raw: string): string {\n const sep = props.decimalSeparator || '.'\n\n let out = ''\n let hasSepLocal = false\n\n for (let i = 0; i < raw.length; i++) {\n const ch = raw[i]\n\n // Ведущий минус: разрешаем один в начале, чтобы можно было ввести\n // отрицательное значение с клавиатуры (границы проверит `clamp` на change).\n if (ch === '-' && out.length === 0) {\n out += '-'\n continue\n }\n\n if (isDigit(ch)) {\n out += ch\n continue\n }\n\n if ((ch === sep || ch === '.' || ch === ',') && !hasSepLocal) {\n out += sep\n hasSepLocal = true\n }\n }\n\n return out\n}\n\nfunction toNumber(value: string): number | null {\n if (value.trim() === '') return null\n const sep = props.decimalSeparator || '.'\n const normalized = sep === '.' ? value : value.replace(sep, '.')\n const n = Number(normalized)\n if (!Number.isFinite(n)) return null\n return n\n}\n\nfunction clamp(v: number): number {\n if (props.min !== undefined) v = Math.max(props.min, v)\n if (props.max !== undefined) v = Math.min(props.max, v)\n return v\n}\n\nfunction normalize(v: number): number {\n if (props.precision === undefined) return v\n const p = Math.max(0, props.precision)\n return Number(v.toFixed(p))\n}\n\nfunction format(n: number): string {\n const sep = props.decimalSeparator || '.'\n const p = props.precision\n\n // Без `precision` избегаем экспоненциальной записи (`String(1e21)` → `\"1e+21\"`),\n // форматируя через `Intl` без группировки разрядов.\n let s = p === undefined\n ? n.toLocaleString('en-US', { useGrouping: false, maximumFractionDigits: 20 })\n : n.toFixed(Math.max(0, p))\n if (sep !== '.') s = s.replace('.', sep)\n return s\n}\n\nfunction onInput(e: Event): void {\n const el = e.target as HTMLInputElement\n const raw = el.value\n const next = sanitize(raw)\n\n if (raw !== next) {\n // Сохраняем позицию каретки: считаем, сколько валидных символов было ДО\n // каретки, и ставим её после стольких же в очищенной строке — иначе при\n // редактировании середины числа каретка прыгала в конец.\n const caret = el.selectionStart ?? raw.length\n const keptBefore = sanitize(raw.slice(0, caret)).length\n el.value = next\n el.setSelectionRange(keptBefore, keptBefore)\n }\n\n draft.value = next\n\n const num = toNumber(next)\n if (num !== props.modelValue) emit('update:modelValue', num)\n}\n\n/**\n * Коммит набранного: границы и точность применяются здесь, а не на каждом\n * нажатии, — иначе `min=10` не дал бы набрать «1» как начало «15».\n */\nfunction commit(): void {\n // Без черновика коммитится сама модель: значение вне границ обязано\n // выправиться и тогда, когда его выставили снаружи, а не набрали руками.\n const num = draft.value !== null ? toNumber(draft.value) : props.modelValue\n const next = num === null ? null : clamp(normalize(num))\n\n draft.value = null\n\n if (next !== props.modelValue) emit('update:modelValue', next)\n emit('change', next)\n}\n\nfunction onChange(): void {\n // Клампинг границ (`min`/`max`) и нормализация — на `change`/`blur`, а не только\n // в кнопках: иначе ручной ввод «999» при `max=10` останется невалидным.\n commit()\n}\n\nfunction setValue(n: number): void {\n const next = clamp(normalize(n))\n // Шаг завершает набор: черновик снимается, показ считается от модели.\n draft.value = null\n emit('update:modelValue', next)\n emit('change', next)\n}\n\n/**\n * Шаг значения. Фокус не трогаем: кнопка ± обязана оставаться под фокусом,\n * иначе клавиатурный пользователь после первого Enter теряет её и повторно\n * нажать не может. Поле фокусирует тот, кто шагает от него самого.\n */\nfunction stepBy(dir: 1 | -1, step: number = props.step ?? 1): void {\n if (isDisabled.value || isReadonly.value) return\n\n const current = props.modelValue ?? 0\n setValue(addStep(current, step * dir))\n}\n\n// Интервал уже шагал: финальный `click` при отпускании — не намерение\n// пользователя сделать ещё один шаг, а хвост того же жеста.\nlet repeatFired = false\n\n/** Клик кнопки «±»: шаг, если это не хвост только что завершённого удержания. */\nfunction onStepClick(dir: 1 | -1, event: MouseEvent): void {\n // Enter/Space на кнопке дают click без указателя (`detail === 0`) — хвостом\n // жеста он быть не может. Иначе удержание, кончившееся без клика (отпускание\n // вне кнопки, `pointercancel` на таче), оставляло бы флаг взведённым, и\n // спинбаттон молча терял бы нажатие у клавиатуры и вспомогательных технологий.\n if (event.detail === 0) {\n repeatFired = false\n stepBy(dir)\n return\n }\n\n if (repeatFired) {\n repeatFired = false\n return\n }\n stepBy(dir)\n}\n\nfunction onKeydown(e: KeyboardEvent): void {\n if (isDisabled.value) return\n // Readonly-поле ведёт себя как текст: стрелки и Home/End отдаются нативной\n // каретке, значение не меняется.\n if (isReadonly.value) return\n\n switch (e.key) {\n case 'ArrowUp':\n e.preventDefault()\n stepBy(1)\n break\n case 'ArrowDown':\n e.preventDefault()\n stepBy(-1)\n break\n case 'PageUp':\n e.preventDefault()\n stepBy(1, bigStep(props.step ?? 1, props.min, props.max))\n break\n case 'PageDown':\n e.preventDefault()\n stepBy(-1, bigStep(props.step ?? 1, props.min, props.max))\n break\n case 'Home':\n if (props.min !== undefined) {\n e.preventDefault()\n setValue(props.min)\n }\n break\n case 'End':\n if (props.max !== undefined) {\n e.preventDefault()\n setValue(props.max)\n }\n break\n }\n}\n\n// `aria-valuenow` — числовое значение для скринридеров; отсутствует, если поле пусто.\nconst ariaValueNow = computed(() => props.modelValue ?? undefined)\n\n// На пределе кнопка ± гаснет: активная кнопка, у которой `clamp` съедает\n// результат, читается как сломанная.\nconst numericValue = computed(() => props.modelValue)\n\nconst canIncrease = computed(() => {\n if (props.max === undefined) return true\n const current = numericValue.value\n return current === null || normalize(current) < props.max\n})\n\nconst canDecrease = computed(() => {\n if (props.min === undefined) return true\n const current = numericValue.value\n return current === null || normalize(current) > props.min\n})\n\n// Локале-зависимое отображение: при фокусе показываем «сырое» значение для\n// редактирования, иначе — сгруппированное через `Intl.NumberFormat`.\nfunction formatGrouped(num: number): string {\n const sep = props.decimalSeparator || '.'\n // formatToParts позволяет оставить локале-зависимый групповой разделитель,\n // но принудительно подставить наш десятичный разделитель (`decimalSeparator`),\n // корректно работая для любой локали (в т.ч. de-DE, где группа — '.').\n return new Intl.NumberFormat(resolvedLocale.value, {\n useGrouping: true,\n minimumFractionDigits: props.precision ?? 0,\n maximumFractionDigits: props.precision ?? 20,\n })\n .formatToParts(num)\n .map(part => (part.type === 'decimal' ? sep : part.value))\n .join('')\n}\n\n/**\n * `aria-valuetext` нужен только при группировке: без неё `aria-valuenow` уже\n * несёт то же самое число, и дублировать его текстом незачем.\n */\nconst ariaValueText = computed(() => {\n if (!props.useGrouping) return undefined\n return props.modelValue === null ? undefined : formatGrouped(props.modelValue)\n})\n\n/**\n * Что показывает поле: набираемый черновик, пока он есть, иначе — модель.\n * Группировка только вне фокуса: при правке разделители разрядов мешают.\n */\nconst displayValue = computed(() => {\n if (draft.value !== null) return draft.value\n if (props.modelValue === null) return ''\n if (props.useGrouping && !isFocused.value) return formatGrouped(props.modelValue)\n return format(props.modelValue)\n})\n\nfunction onFocus(event: FocusEvent): void {\n isFocused.value = true\n emit('focus', event)\n}\n\nfunction onBlur(event: FocusEvent): void {\n isFocused.value = false\n // `change` до `blur` доходит не всегда (значение могло вернуться к исходному),\n // а черновик обязан сняться в любом случае — иначе поле застынет на нём.\n draft.value = null\n emit('blur', event)\n stopRepeat()\n}\n\n// ————— Удержание кнопки ±: шаг повторяется, пока кнопку держат.\nconst REPEAT_DELAY_MS = 400\nconst REPEAT_INTERVAL_MS = 60\n\nlet repeatDelay: ReturnType<typeof setTimeout> | null = null\nlet repeatTimer: ReturnType<typeof setInterval> | null = null\n\nfunction stopRepeat(): void {\n if (repeatDelay !== null) {\n clearTimeout(repeatDelay)\n repeatDelay = null\n }\n if (repeatTimer !== null) {\n clearInterval(repeatTimer)\n repeatTimer = null\n }\n}\n\nfunction canStep(dir: 1 | -1): boolean {\n return dir === 1 ? canIncrease.value : canDecrease.value\n}\n\n/**\n * `pointerdown`, а не `mousedown`: на тач-устройствах второго не бывает.\n * Повтор останавливается на границе — иначе таймер крутился бы вхолостую.\n */\nfunction startRepeat(dir: 1 | -1): void {\n if (isDisabled.value || isReadonly.value) return\n\n stopRepeat()\n repeatFired = false\n repeatDelay = setTimeout(() => {\n repeatTimer = setInterval(() => {\n if (!canStep(dir)) {\n stopRepeat()\n return\n }\n repeatFired = true\n stepBy(dir)\n }, REPEAT_INTERVAL_MS)\n }, REPEAT_DELAY_MS)\n}\n\nonBeforeUnmount(stopRepeat)\n\n// ————— Очистка значения.\nconst resolvedClearable = useGrComponentProp('GrNumberInput', 'clearable', () => props.clearable, false)\nconst resolvedClearLabel = computed(() => props.clearLabel ?? t('gr.input.clear', 'Clear'))\n\nconst clearVisible = computed(() =>\n resolvedClearable.value && !isDisabled.value && !isReadonly.value && props.modelValue !== null,\n)\n\nfunction clear(): void {\n draft.value = null\n emit('update:modelValue', null)\n emit('change', null)\n emit('clear')\n focus()\n}\n</script>\n\n<template>\n <div\n data-gr-number-input\n class=\"relative w-full overflow-hidden rounded-[var(--gr-radius-control)] border bg-[var(--gr-bg)] transition-colors duration-[var(--gr-duration-fast)] focus-within:ring-2 focus-within:ring-[var(--gr-ring)]\"\n :class=\"shellClassName\"\n >\n <div\n v-if=\"$slots.prefix\"\n ref=\"prefixEl\"\n data-testid=\"number-input-prefix\"\n data-gr-number-input-prefix\n class=\"absolute inset-y-0 left-0 flex items-center justify-center border-r border-[var(--gr-brd)] px-2 text-[var(--gr-muted-fg)] pointer-events-none select-none truncate\"\n :style=\"prefixStyle\"\n aria-hidden=\"true\"\n >\n <slot name=\"prefix\" />\n </div>\n\n <input\n :id=\"resolvedId\"\n ref=\"inputEl\"\n v-bind=\"$attrs\"\n :name=\"name\"\n type=\"text\"\n :inputmode=\"inputmode\"\n :autocomplete=\"autocomplete\"\n :placeholder=\"placeholder\"\n :disabled=\"isDisabled\"\n :value=\"displayValue\"\n role=\"spinbutton\"\n :aria-valuenow=\"ariaValueNow\"\n :aria-valuemin=\"min\"\n :aria-valuemax=\"max\"\n :aria-valuetext=\"ariaValueText\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-describedby=\"describedBy\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-readonly=\"isReadonly ? 'true' : undefined\"\n :aria-label=\"ariaLabel\"\n :readonly=\"isReadonly\"\n class=\"w-full bg-transparent text-[var(--gr-fg)] placeholder:text-[var(--gr-muted-fg)] focus:placeholder:text-transparent focus:outline-none disabled:cursor-not-allowed\"\n :class=\"inputClassName\"\n :style=\"inputStyle\"\n @input=\"onInput\"\n @change=\"onChange\"\n @keydown=\"onKeydown\"\n @focus=\"onFocus\"\n @blur=\"onBlur\"\n >\n\n <div\n v-if=\"$slots.suffix\"\n ref=\"suffixEl\"\n data-testid=\"number-input-suffix\"\n data-gr-number-input-suffix\n class=\"absolute inset-y-0 flex items-center justify-center border-l border-[var(--gr-brd)] px-2 text-[var(--gr-muted-fg)] pointer-events-none select-none truncate\"\n :class=\"suffixFixed ? '[direction:rtl]' : ''\"\n :style=\"suffixStyle\"\n aria-hidden=\"true\"\n >\n <slot name=\"suffix\" />\n </div>\n\n <button\n v-if=\"clearVisible\"\n type=\"button\"\n data-gr-number-input-clear\n :class=\"clearButtonClass\"\n :style=\"clearButtonStyle\"\n :aria-label=\"resolvedClearLabel\"\n @mousedown.prevent\n @click=\"clear\"\n >\n <GrIcon :size=\"14\">\n <IconX />\n </GrIcon>\n </button>\n\n <div\n v-if=\"hasVerticalControls\"\n data-testid=\"number-input-controls-vertical\"\n data-gr-number-input-controls=\"vertical\"\n class=\"absolute inset-y-0 flex items-center justify-center border-l border-[var(--gr-brd)]\"\n :style=\"verticalControlsStyle\"\n >\n <div class=\"flex flex-col justify-center gap-1\">\n <button\n type=\"button\"\n data-gr-number-input-increase\n :class=\"stepperCompactClass\"\n :disabled=\"isDisabled || isReadonly || !canIncrease\"\n :aria-label=\"resolvedIncreaseLabel\"\n @mousedown.prevent\n @pointerdown=\"startRepeat(1)\"\n @pointerup=\"stopRepeat\"\n @pointerleave=\"stopRepeat\"\n @pointercancel=\"stopRepeat\"\n @click=\"onStepClick(1, $event)\"\n >\n <GrIcon :size=\"12\">\n <IconChevronUp />\n </GrIcon>\n </button>\n <button\n type=\"button\"\n data-gr-number-input-decrease\n :class=\"stepperCompactClass\"\n :disabled=\"isDisabled || isReadonly || !canDecrease\"\n :aria-label=\"resolvedDecreaseLabel\"\n @mousedown.prevent\n @pointerdown=\"startRepeat(-1)\"\n @pointerup=\"stopRepeat\"\n @pointerleave=\"stopRepeat\"\n @pointercancel=\"stopRepeat\"\n @click=\"onStepClick(-1, $event)\"\n >\n <GrIcon :size=\"12\">\n <IconChevronDown />\n </GrIcon>\n </button>\n </div>\n </div>\n\n <div\n v-if=\"hasHorizontalControls\"\n data-testid=\"number-input-controls-horizontal-left\"\n data-gr-number-input-controls=\"horizontal-left\"\n class=\"absolute inset-y-0 flex items-stretch justify-center border-r border-[var(--gr-brd)]\"\n :style=\"horizontalLeftControlsStyle\"\n >\n <button\n type=\"button\"\n data-gr-number-input-decrease\n :class=\"stepperWideClass\"\n :disabled=\"isDisabled || isReadonly || !canDecrease\"\n :aria-label=\"resolvedDecreaseLabel\"\n @mousedown.prevent\n @pointerdown=\"startRepeat(-1)\"\n @pointerup=\"stopRepeat\"\n @pointerleave=\"stopRepeat\"\n @pointercancel=\"stopRepeat\"\n @click=\"onStepClick(-1, $event)\"\n >\n <GrIcon :size=\"12\">\n <IconChevronLeft />\n </GrIcon>\n </button>\n </div>\n\n <div\n v-if=\"hasHorizontalControls\"\n data-testid=\"number-input-controls-horizontal-right\"\n data-gr-number-input-controls=\"horizontal-right\"\n class=\"absolute inset-y-0 flex items-stretch justify-center border-l border-[var(--gr-brd)]\"\n :style=\"horizontalRightControlsStyle\"\n >\n <button\n type=\"button\"\n data-gr-number-input-increase\n :class=\"stepperWideClass\"\n :disabled=\"isDisabled || isReadonly || !canIncrease\"\n :aria-label=\"resolvedIncreaseLabel\"\n @mousedown.prevent\n @pointerdown=\"startRepeat(1)\"\n @pointerup=\"stopRepeat\"\n @pointerleave=\"stopRepeat\"\n @pointercancel=\"stopRepeat\"\n @click=\"onStepClick(1, $event)\"\n >\n <GrIcon :size=\"12\">\n <IconChevronRight />\n </GrIcon>\n </button>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport type { InputHTMLAttributes } from 'vue'\n\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { computed, onBeforeUnmount, ref } from 'vue'\n\nimport {\n clearButtonClass,\n stepperCompactClass,\n stepperWideClass,\n grNumberInputInputClass,\n grNumberInputShellClass,\n type GrNumberInputControlsDirection,\n type GrNumberInputSize,\n type GrNumberInputState,\n type GrNumberInputTextAlign,\n} from './grNumberInputStyles'\nimport { addStep, bigStep } from './numberInputMath'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport GrIcon from '../GrIcon/GrIcon.vue'\nimport IconChevronDown from '~icons/lucide/chevron-down'\nimport IconChevronLeft from '~icons/lucide/chevron-left'\nimport IconChevronRight from '~icons/lucide/chevron-right'\nimport IconChevronUp from '~icons/lucide/chevron-up'\nimport IconX from '~icons/lucide/x'\nimport { addLen } from '../../composables/internal/useAddonMeasurement'\nimport { useControlAddons } from '../../composables/internal/useControlAddons'\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst ADDON_PX_BY_SIZE: Record<GrNumberInputSize, number> = {\n xs: 32,\n sm: 36,\n md: 40,\n lg: 48,\n}\n\nconst BASE_PADDING_X_LEN_BY_SIZE: Record<GrNumberInputSize, string> = {\n xs: '10px',\n sm: '12px',\n md: '12px',\n lg: '16px',\n}\n\nfunction px(n: number): string {\n return `${n}px`\n}\n\nexport interface GrNumberInputProps {\n /**\n * Значение поля. `null` — пусто.\n *\n * Незавершённый ввод («-», «1,») числом не является и в модель не попадает:\n * пока он набирается, поле держит его во внутреннем черновике, а модель\n * честно говорит «числа пока нет».\n */\n modelValue: number | null\n placeholder?: string\n autocomplete?: string\n inputmode?: InputHTMLAttributes['inputmode']\n disabled?: boolean\n /** Быстрый флаг невалидности; эквивалент `state='danger'` + `aria-invalid`. */\n invalid?: boolean\n /** Только для чтения: значение видно и уходит в форму, но не редактируется. */\n readonly?: boolean\n /** Обязательное поле (`aria-required`). Складывается с `required` у `GrFormField`. */\n required?: boolean\n /** Доступное имя вне `GrFormField`. */\n ariaLabel?: string\n state?: GrNumberInputState\n name?: string\n id?: string\n size?: GrNumberInputSize\n\n textAlign?: GrNumberInputTextAlign\n\n decimalSeparator?: string\n step?: number\n min?: number\n max?: number\n precision?: number\n\n /** Кнопка очистки значения. */\n clearable?: boolean\n /** A11y-подпись кнопки очистки. */\n clearLabel?: string\n /**\n * BCP-47 локаль для отображения значения (группировка разрядов и разделители\n * через `Intl.NumberFormat`). Работает вместе с `useGrouping`.\n */\n locale?: string\n /**\n * Группировать разряды при отображении (когда поле не в фокусе). При фокусе\n * показывается «сырое» значение для редактирования. По умолчанию выключено.\n */\n useGrouping?: boolean\n\n /** Показывать кнопки +/-. */\n controls?: boolean\n controlsDirection?: GrNumberInputControlsDirection\n\n prefixMinWidth?: string\n prefixMaxWidth?: string\n suffixMinWidth?: string\n suffixMaxWidth?: string\n /**\n * Фиксированная ширина у prefix/suffix: жёсткая ширина (из `*MaxWidth` →\n * `*MinWidth` → дефолт) + обрезка контента по краю (prefix — справа,\n * suffix — слева). По умолчанию аддоны растягиваются под контент.\n */\n prefixFixed?: boolean\n suffixFixed?: boolean\n\n /** i18n-friendly aria-label для кнопки \"увеличить\". */\n increaseLabel?: string\n /** i18n-friendly aria-label для кнопки \"уменьшить\". */\n decreaseLabel?: string\n}\n\nexport interface GrNumberInputEmits {\n (e: 'update:modelValue', value: number | null): void\n (e: 'change', value: number | null): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n /** Значение стёрто кнопкой очистки. */\n (e: 'clear'): void\n}\n\n// Контекст `GrFormField` — fallback для id/описания/невалидности, как в `GrInput`.\nconst field = useGrFormFieldContext()\n\nconst props = withDefaults(defineProps<GrNumberInputProps>(), {\n placeholder: undefined,\n autocomplete: undefined,\n inputmode: 'decimal',\n disabled: false,\n invalid: false,\n readonly: false,\n required: false,\n ariaLabel: undefined,\n state: 'default',\n name: undefined,\n id: undefined,\n size: undefined,\n\n textAlign: 'left',\n\n decimalSeparator: '.',\n step: 1,\n min: undefined,\n max: undefined,\n precision: undefined,\n locale: undefined,\n useGrouping: false,\n clearable: undefined,\n clearLabel: undefined,\n\n controls: false,\n controlsDirection: 'vertical',\n\n prefixMinWidth: undefined,\n prefixMaxWidth: undefined,\n suffixMinWidth: undefined,\n suffixMaxWidth: undefined,\n prefixFixed: false,\n suffixFixed: false,\n\n increaseLabel: undefined,\n decreaseLabel: undefined,\n})\n\n// Эффективный размер: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrNumberInput' })\n\nconst resolvedId = computed(() => props.id ?? field?.id.value)\nconst describedBy = computed(() => field?.describedById.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n} = useGrFormControl(() => props)\n\nconst emit = defineEmits<GrNumberInputEmits>()\ndefineSlots<{\n /** Аддон слева от поля: знак валюты, иконка. */\n prefix?: () => any\n /** Аддон справа от поля: единица измерения. */\n suffix?: () => any\n}>()\n\n\nconst inputEl = ref<HTMLInputElement | null>(null)\n\n// В фокусе поле показывает «сырое» значение: группировка мешала бы правке.\nconst isFocused = ref(false)\n\n/**\n * Строка ровно в том виде, в каком её набирают. Модель к этому моменту может\n * быть `null` («-», «1,» — не числа), и без черновика поле стирало бы под\n * пальцами то, что печатают. Коммит (`change`/`blur`) черновик снимает.\n */\nconst draft = ref<string | null>(null)\n\nfunction focus(): void {\n inputEl.value?.focus()\n}\n\nfunction blur(): void {\n inputEl.value?.blur()\n}\n\ndefineExpose({ focus, blur })\n\nconst { t, locale } = useGranularityTranslations()\n\n/**\n * Локаль форматирования: свой проп → локаль i18n-адаптера → окружение. Без\n * второго шага мультиязычное приложение передавало бы `locale` каждому полю.\n */\nconst resolvedLocale = computed(() => props.locale ?? locale.value)\nconst resolvedIncreaseLabel = computed(() => props.increaseLabel ?? t('gr.numberInput.increase', 'Increase'))\nconst resolvedDecreaseLabel = computed(() => props.decreaseLabel ?? t('gr.numberInput.decrease', 'Decrease'))\n\nconst hasHorizontalControls = computed(() => props.controls && props.controlsDirection === 'horizontal')\nconst hasVerticalControls = computed(() => props.controls && props.controlsDirection === 'vertical')\n\nconst addonPx = computed(() => ADDON_PX_BY_SIZE[resolvedSize.value])\nconst addonLen = computed(() => px(addonPx.value))\nconst basePaddingXLen = computed(() => BASE_PADDING_X_LEN_BY_SIZE[resolvedSize.value])\n\n/** Кнопки ± справа: вертикальный стек — одна колонка, горизонтальный — вторая. */\nconst rightControlsCount = computed(() =>\n (hasHorizontalControls.value ? 1 : 0) + (hasVerticalControls.value ? 1 : 0))\n\nconst {\n prefixEl,\n suffixEl,\n prefixLen,\n suffixLen,\n prefixStyle,\n suffixStyle,\n fieldPadding: inputStyle,\n} = useControlAddons(() => props, {\n defaultMinWidth: () => addonLen.value,\n paddingX: () => basePaddingXLen.value,\n leadingReserve: () => (hasHorizontalControls.value ? addonLen.value : '0px'),\n trailingReserve: () => (rightControlsCount.value > 0 ? px(addonPx.value * rightControlsCount.value) : '0px'),\n // Суффикс позиционируется абсолютно: без якоря он уехал бы от правого края.\n anchorSuffixRight: true,\n})\n\nfunction addonStyle(side: 'left' | 'right', offset: string): Record<string, string> {\n return {\n width: addonLen.value,\n [side]: offset,\n }\n}\n\nconst verticalControlsStyle = computed(() => addonStyle('right', suffixLen.value))\n\n/**\n * Крестик очистки живёт левее правой зоны (суффикс + кнопки ±) — иначе он лёг бы\n * на них. Ширины там же, где их считает поле, поэтому переиспользуем расчёт.\n */\nconst clearButtonStyle = computed(() => {\n const controls = rightControlsCount.value > 0 ? px(addonPx.value * rightControlsCount.value) : '0px'\n\n return { right: addLen(addLen(suffixLen.value, controls), '6px') }\n})\nconst horizontalLeftControlsStyle = computed(() => addonStyle('left', prefixLen.value))\nconst horizontalRightControlsStyle = computed(() => addonStyle('right', suffixLen.value))\n\nconst shellClassName = computed(() => {\n return grNumberInputShellClass({\n disabled: isDisabled.value,\n state: props.state,\n invalid: isInvalid.value,\n })\n})\n\nconst inputClassName = computed(() => {\n return grNumberInputInputClass({\n size: resolvedSize.value,\n textAlign: props.textAlign,\n })\n})\n\nfunction isDigit(ch: string): boolean {\n return ch >= '0' && ch <= '9'\n}\n\nfunction sanitize(raw: string): string {\n const sep = props.decimalSeparator || '.'\n\n let out = ''\n let hasSepLocal = false\n\n for (let i = 0; i < raw.length; i++) {\n const ch = raw[i]\n\n // Ведущий минус: разрешаем один в начале, чтобы можно было ввести\n // отрицательное значение с клавиатуры (границы проверит `clamp` на change).\n if (ch === '-' && out.length === 0) {\n out += '-'\n continue\n }\n\n if (isDigit(ch)) {\n out += ch\n continue\n }\n\n if ((ch === sep || ch === '.' || ch === ',') && !hasSepLocal) {\n out += sep\n hasSepLocal = true\n }\n }\n\n return out\n}\n\nfunction toNumber(value: string): number | null {\n if (value.trim() === '') return null\n const sep = props.decimalSeparator || '.'\n const normalized = sep === '.' ? value : value.replace(sep, '.')\n const n = Number(normalized)\n if (!Number.isFinite(n)) return null\n return n\n}\n\nfunction clamp(v: number): number {\n if (props.min !== undefined) v = Math.max(props.min, v)\n if (props.max !== undefined) v = Math.min(props.max, v)\n return v\n}\n\nfunction normalize(v: number): number {\n if (props.precision === undefined) return v\n const p = Math.max(0, props.precision)\n return Number(v.toFixed(p))\n}\n\nfunction format(n: number): string {\n const sep = props.decimalSeparator || '.'\n const p = props.precision\n\n // Без `precision` избегаем экспоненциальной записи (`String(1e21)` → `\"1e+21\"`),\n // форматируя через `Intl` без группировки разрядов.\n let s = p === undefined\n ? n.toLocaleString('en-US', { useGrouping: false, maximumFractionDigits: 20 })\n : n.toFixed(Math.max(0, p))\n if (sep !== '.') s = s.replace('.', sep)\n return s\n}\n\nfunction onInput(e: Event): void {\n const el = e.target as HTMLInputElement\n const raw = el.value\n const next = sanitize(raw)\n\n if (raw !== next) {\n // Сохраняем позицию каретки: считаем, сколько валидных символов было ДО\n // каретки, и ставим её после стольких же в очищенной строке — иначе при\n // редактировании середины числа каретка прыгала в конец.\n const caret = el.selectionStart ?? raw.length\n const keptBefore = sanitize(raw.slice(0, caret)).length\n el.value = next\n el.setSelectionRange(keptBefore, keptBefore)\n }\n\n draft.value = next\n\n const num = toNumber(next)\n if (num !== props.modelValue) emit('update:modelValue', num)\n}\n\n/**\n * Коммит набранного: границы и точность применяются здесь, а не на каждом\n * нажатии, — иначе `min=10` не дал бы набрать «1» как начало «15».\n */\nfunction commit(): void {\n // Без черновика коммитится сама модель: значение вне границ обязано\n // выправиться и тогда, когда его выставили снаружи, а не набрали руками.\n const num = draft.value !== null ? toNumber(draft.value) : props.modelValue\n const next = num === null ? null : clamp(normalize(num))\n\n draft.value = null\n\n if (next !== props.modelValue) emit('update:modelValue', next)\n emit('change', next)\n}\n\nfunction onChange(): void {\n // Клампинг границ (`min`/`max`) и нормализация — на `change`/`blur`, а не только\n // в кнопках: иначе ручной ввод «999» при `max=10` останется невалидным.\n commit()\n}\n\nfunction setValue(n: number): void {\n const next = clamp(normalize(n))\n // Шаг завершает набор: черновик снимается, показ считается от модели.\n draft.value = null\n emit('update:modelValue', next)\n emit('change', next)\n}\n\n/**\n * Шаг значения. Фокус не трогаем: кнопка ± обязана оставаться под фокусом,\n * иначе клавиатурный пользователь после первого Enter теряет её и повторно\n * нажать не может. Поле фокусирует тот, кто шагает от него самого.\n */\nfunction stepBy(dir: 1 | -1, step: number = props.step ?? 1): void {\n if (isDisabled.value || isReadonly.value) return\n\n const current = props.modelValue ?? 0\n setValue(addStep(current, step * dir))\n}\n\n// Интервал уже шагал: финальный `click` при отпускании — не намерение\n// пользователя сделать ещё один шаг, а хвост того же жеста.\nlet repeatFired = false\n\n/** Клик кнопки «±»: шаг, если это не хвост только что завершённого удержания. */\nfunction onStepClick(dir: 1 | -1, event: MouseEvent): void {\n // Enter/Space на кнопке дают click без указателя (`detail === 0`) — хвостом\n // жеста он быть не может. Иначе удержание, кончившееся без клика (отпускание\n // вне кнопки, `pointercancel` на таче), оставляло бы флаг взведённым, и\n // спинбаттон молча терял бы нажатие у клавиатуры и вспомогательных технологий.\n if (event.detail === 0) {\n repeatFired = false\n stepBy(dir)\n return\n }\n\n if (repeatFired) {\n repeatFired = false\n return\n }\n stepBy(dir)\n}\n\nfunction onKeydown(e: KeyboardEvent): void {\n if (isDisabled.value) return\n // Readonly-поле ведёт себя как текст: стрелки и Home/End отдаются нативной\n // каретке, значение не меняется.\n if (isReadonly.value) return\n\n switch (e.key) {\n case 'ArrowUp':\n e.preventDefault()\n stepBy(1)\n break\n case 'ArrowDown':\n e.preventDefault()\n stepBy(-1)\n break\n case 'PageUp':\n e.preventDefault()\n stepBy(1, bigStep(props.step ?? 1, props.min, props.max))\n break\n case 'PageDown':\n e.preventDefault()\n stepBy(-1, bigStep(props.step ?? 1, props.min, props.max))\n break\n case 'Home':\n if (props.min !== undefined) {\n e.preventDefault()\n setValue(props.min)\n }\n break\n case 'End':\n if (props.max !== undefined) {\n e.preventDefault()\n setValue(props.max)\n }\n break\n }\n}\n\n// `aria-valuenow` — числовое значение для скринридеров; отсутствует, если поле пусто.\nconst ariaValueNow = computed(() => props.modelValue ?? undefined)\n\n// На пределе кнопка ± гаснет: активная кнопка, у которой `clamp` съедает\n// результат, читается как сломанная.\nconst numericValue = computed(() => props.modelValue)\n\nconst canIncrease = computed(() => {\n if (props.max === undefined) return true\n const current = numericValue.value\n return current === null || normalize(current) < props.max\n})\n\nconst canDecrease = computed(() => {\n if (props.min === undefined) return true\n const current = numericValue.value\n return current === null || normalize(current) > props.min\n})\n\n// Локале-зависимое отображение: при фокусе показываем «сырое» значение для\n// редактирования, иначе — сгруппированное через `Intl.NumberFormat`.\nfunction formatGrouped(num: number): string {\n const sep = props.decimalSeparator || '.'\n // formatToParts позволяет оставить локале-зависимый групповой разделитель,\n // но принудительно подставить наш десятичный разделитель (`decimalSeparator`),\n // корректно работая для любой локали (в т.ч. de-DE, где группа — '.').\n return new Intl.NumberFormat(resolvedLocale.value, {\n useGrouping: true,\n minimumFractionDigits: props.precision ?? 0,\n maximumFractionDigits: props.precision ?? 20,\n })\n .formatToParts(num)\n .map(part => (part.type === 'decimal' ? sep : part.value))\n .join('')\n}\n\n/**\n * `aria-valuetext` нужен только при группировке: без неё `aria-valuenow` уже\n * несёт то же самое число, и дублировать его текстом незачем.\n */\nconst ariaValueText = computed(() => {\n if (!props.useGrouping) return undefined\n return props.modelValue === null ? undefined : formatGrouped(props.modelValue)\n})\n\n/**\n * Что показывает поле: набираемый черновик, пока он есть, иначе — модель.\n * Группировка только вне фокуса: при правке разделители разрядов мешают.\n */\nconst displayValue = computed(() => {\n if (draft.value !== null) return draft.value\n if (props.modelValue === null) return ''\n if (props.useGrouping && !isFocused.value) return formatGrouped(props.modelValue)\n return format(props.modelValue)\n})\n\nfunction onFocus(event: FocusEvent): void {\n isFocused.value = true\n emit('focus', event)\n}\n\nfunction onBlur(event: FocusEvent): void {\n isFocused.value = false\n // `change` до `blur` доходит не всегда (значение могло вернуться к исходному),\n // а черновик обязан сняться в любом случае — иначе поле застынет на нём.\n draft.value = null\n emit('blur', event)\n stopRepeat()\n}\n\n// ————— Удержание кнопки ±: шаг повторяется, пока кнопку держат.\nconst REPEAT_DELAY_MS = 400\nconst REPEAT_INTERVAL_MS = 60\n\nlet repeatDelay: ReturnType<typeof setTimeout> | null = null\nlet repeatTimer: ReturnType<typeof setInterval> | null = null\n\nfunction stopRepeat(): void {\n if (repeatDelay !== null) {\n clearTimeout(repeatDelay)\n repeatDelay = null\n }\n if (repeatTimer !== null) {\n clearInterval(repeatTimer)\n repeatTimer = null\n }\n}\n\nfunction canStep(dir: 1 | -1): boolean {\n return dir === 1 ? canIncrease.value : canDecrease.value\n}\n\n/**\n * `pointerdown`, а не `mousedown`: на тач-устройствах второго не бывает.\n * Повтор останавливается на границе — иначе таймер крутился бы вхолостую.\n */\nfunction startRepeat(dir: 1 | -1): void {\n if (isDisabled.value || isReadonly.value) return\n\n stopRepeat()\n repeatFired = false\n repeatDelay = setTimeout(() => {\n repeatTimer = setInterval(() => {\n if (!canStep(dir)) {\n stopRepeat()\n return\n }\n repeatFired = true\n stepBy(dir)\n }, REPEAT_INTERVAL_MS)\n }, REPEAT_DELAY_MS)\n}\n\nonBeforeUnmount(stopRepeat)\n\n// ————— Очистка значения.\nconst resolvedClearable = useGrComponentProp('GrNumberInput', 'clearable', () => props.clearable, false)\nconst resolvedClearLabel = computed(() => props.clearLabel ?? t('gr.input.clear', 'Clear'))\n\nconst clearVisible = computed(() =>\n resolvedClearable.value && !isDisabled.value && !isReadonly.value && props.modelValue !== null,\n)\n\nfunction clear(): void {\n draft.value = null\n emit('update:modelValue', null)\n emit('change', null)\n emit('clear')\n focus()\n}\n</script>\n\n<template>\n <div\n data-gr-number-input\n class=\"relative w-full overflow-hidden rounded-[var(--gr-radius-control)] border bg-[var(--gr-bg)] transition-colors duration-[var(--gr-duration-fast)] focus-within:ring-2 focus-within:ring-[var(--gr-ring)]\"\n :class=\"shellClassName\"\n >\n <div\n v-if=\"$slots.prefix\"\n ref=\"prefixEl\"\n data-testid=\"number-input-prefix\"\n data-gr-number-input-prefix\n class=\"absolute inset-y-0 left-0 flex items-center justify-center border-r border-[var(--gr-brd)] px-2 text-[var(--gr-muted-fg)] pointer-events-none select-none truncate\"\n :style=\"prefixStyle\"\n aria-hidden=\"true\"\n >\n <slot name=\"prefix\" />\n </div>\n\n <input\n :id=\"resolvedId\"\n ref=\"inputEl\"\n v-bind=\"$attrs\"\n :name=\"name\"\n type=\"text\"\n :inputmode=\"inputmode\"\n :autocomplete=\"autocomplete\"\n :placeholder=\"placeholder\"\n :disabled=\"isDisabled\"\n :value=\"displayValue\"\n role=\"spinbutton\"\n :aria-valuenow=\"ariaValueNow\"\n :aria-valuemin=\"min\"\n :aria-valuemax=\"max\"\n :aria-valuetext=\"ariaValueText\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-describedby=\"describedBy\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-readonly=\"isReadonly ? 'true' : undefined\"\n :aria-label=\"ariaLabel\"\n :readonly=\"isReadonly\"\n class=\"w-full bg-transparent text-[var(--gr-fg)] placeholder:text-[var(--gr-muted-fg)] focus:placeholder:text-transparent focus:outline-none disabled:cursor-not-allowed\"\n :class=\"inputClassName\"\n :style=\"inputStyle\"\n @input=\"onInput\"\n @change=\"onChange\"\n @keydown=\"onKeydown\"\n @focus=\"onFocus\"\n @blur=\"onBlur\"\n >\n\n <div\n v-if=\"$slots.suffix\"\n ref=\"suffixEl\"\n data-testid=\"number-input-suffix\"\n data-gr-number-input-suffix\n class=\"absolute inset-y-0 flex items-center justify-center border-l border-[var(--gr-brd)] px-2 text-[var(--gr-muted-fg)] pointer-events-none select-none truncate\"\n :class=\"suffixFixed ? '[direction:rtl]' : ''\"\n :style=\"suffixStyle\"\n aria-hidden=\"true\"\n >\n <slot name=\"suffix\" />\n </div>\n\n <button\n v-if=\"clearVisible\"\n type=\"button\"\n data-gr-number-input-clear\n :class=\"clearButtonClass\"\n :style=\"clearButtonStyle\"\n :aria-label=\"resolvedClearLabel\"\n @mousedown.prevent\n @click=\"clear\"\n >\n <GrIcon :size=\"14\">\n <IconX />\n </GrIcon>\n </button>\n\n <div\n v-if=\"hasVerticalControls\"\n data-testid=\"number-input-controls-vertical\"\n data-gr-number-input-controls=\"vertical\"\n class=\"absolute inset-y-0 flex items-center justify-center border-l border-[var(--gr-brd)]\"\n :style=\"verticalControlsStyle\"\n >\n <div class=\"flex flex-col justify-center gap-1\">\n <button\n type=\"button\"\n data-gr-number-input-increase\n :class=\"stepperCompactClass\"\n :disabled=\"isDisabled || isReadonly || !canIncrease\"\n :aria-label=\"resolvedIncreaseLabel\"\n @mousedown.prevent\n @pointerdown=\"startRepeat(1)\"\n @pointerup=\"stopRepeat\"\n @pointerleave=\"stopRepeat\"\n @pointercancel=\"stopRepeat\"\n @click=\"onStepClick(1, $event)\"\n >\n <GrIcon :size=\"12\">\n <IconChevronUp />\n </GrIcon>\n </button>\n <button\n type=\"button\"\n data-gr-number-input-decrease\n :class=\"stepperCompactClass\"\n :disabled=\"isDisabled || isReadonly || !canDecrease\"\n :aria-label=\"resolvedDecreaseLabel\"\n @mousedown.prevent\n @pointerdown=\"startRepeat(-1)\"\n @pointerup=\"stopRepeat\"\n @pointerleave=\"stopRepeat\"\n @pointercancel=\"stopRepeat\"\n @click=\"onStepClick(-1, $event)\"\n >\n <GrIcon :size=\"12\">\n <IconChevronDown />\n </GrIcon>\n </button>\n </div>\n </div>\n\n <div\n v-if=\"hasHorizontalControls\"\n data-testid=\"number-input-controls-horizontal-left\"\n data-gr-number-input-controls=\"horizontal-left\"\n class=\"absolute inset-y-0 flex items-stretch justify-center border-r border-[var(--gr-brd)]\"\n :style=\"horizontalLeftControlsStyle\"\n >\n <button\n type=\"button\"\n data-gr-number-input-decrease\n :class=\"stepperWideClass\"\n :disabled=\"isDisabled || isReadonly || !canDecrease\"\n :aria-label=\"resolvedDecreaseLabel\"\n @mousedown.prevent\n @pointerdown=\"startRepeat(-1)\"\n @pointerup=\"stopRepeat\"\n @pointerleave=\"stopRepeat\"\n @pointercancel=\"stopRepeat\"\n @click=\"onStepClick(-1, $event)\"\n >\n <GrIcon :size=\"12\">\n <IconChevronLeft />\n </GrIcon>\n </button>\n </div>\n\n <div\n v-if=\"hasHorizontalControls\"\n data-testid=\"number-input-controls-horizontal-right\"\n data-gr-number-input-controls=\"horizontal-right\"\n class=\"absolute inset-y-0 flex items-stretch justify-center border-l border-[var(--gr-brd)]\"\n :style=\"horizontalRightControlsStyle\"\n >\n <button\n type=\"button\"\n data-gr-number-input-increase\n :class=\"stepperWideClass\"\n :disabled=\"isDisabled || isReadonly || !canIncrease\"\n :aria-label=\"resolvedIncreaseLabel\"\n @mousedown.prevent\n @pointerdown=\"startRepeat(1)\"\n @pointerup=\"stopRepeat\"\n @pointerleave=\"stopRepeat\"\n @pointercancel=\"stopRepeat\"\n @click=\"onStepClick(1, $event)\"\n >\n <GrIcon :size=\"12\">\n <IconChevronRight />\n </GrIcon>\n </button>\n </div>\n </div>\n</template>\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
1
  import { n as grNumberInputSafelist, t as grNumberInputConfig } from "../../chunks/config-MmPym-Z72.js";
2
- import { t as GrNumberInput_default } from "./chunks/GrNumberInput-Daud9Jdm.js";
2
+ import { t as GrNumberInput_default } from "./chunks/GrNumberInput-DQ59YICu.js";
3
3
  export { GrNumberInput_default as GrNumberInput, GrNumberInput_default as default, grNumberInputConfig, grNumberInputSafelist };
@@ -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-D-KU9Lx_.js";
4
+ import { t as GrSelect_default } from "../../GrSelect/chunks/GrSelect-DKoQu3w9.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-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
+ {"version":3,"file":"GrPagination-CX9ZV8mR.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-kQhWlfs-.js";
2
+ import { t as GrPagination_default } from "./chunks/GrPagination-CX9ZV8mR.js";
3
3
  export { GrPagination_default as GrPagination, GrPagination_default as default, grPaginationConfig, grPaginationSafelist };
@@ -1353,7 +1353,7 @@ var GrSelect_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PUR
1353
1353
  selected: isSelected(child.option.value)
1354
1354
  }, () => [createElementVNode("span", _hoisted_22, [createElementVNode("span", _hoisted_23, [isSelected(child.option.value) ? (openBlock(), createBlock(unref(check_default), {
1355
1355
  key: 0,
1356
- class: "block h-4 w-4 text-[var(--gr-primary)]"
1356
+ class: "block h-4 w-4 text-[var(--gr-primary-text)]"
1357
1357
  })) : createCommentVNode("", true)]), createElementVNode("span", _hoisted_24, toDisplayString(child.option.label), 1)])], true)], 16, _hoisted_21);
1358
1358
  }), 128))], 10, _hoisted_19)) : (openBlock(), createElementBlock("button", mergeProps({
1359
1359
  key: 1,
@@ -1381,7 +1381,7 @@ var GrSelect_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PUR
1381
1381
  selected: isSelected(row.option.value)
1382
1382
  }, () => [createElementVNode("span", _hoisted_26, [createElementVNode("span", _hoisted_27, [isSelected(row.option.value) ? (openBlock(), createBlock(unref(check_default), {
1383
1383
  key: 0,
1384
- class: "block h-4 w-4 text-[var(--gr-primary)]"
1384
+ class: "block h-4 w-4 text-[var(--gr-primary-text)]"
1385
1385
  })) : createCommentVNode("", true)]), createElementVNode("span", _hoisted_28, toDisplayString(row.option.label), 1)])], true)], 16, _hoisted_25))], 64);
1386
1386
  }), 128))], 14, _hoisted_17)),
1387
1387
  !__props.loading && !unref(panelItems).length && !unref(canAddCustom) ? (openBlock(), createElementBlock("div", _hoisted_29, [renderSlot(_ctx.$slots, "empty", {}, () => [createTextVNode(toDisplayString(resolvedNoResultsText.value), 1)], true)])) : createCommentVNode("", true)
@@ -1402,6 +1402,6 @@ var GrSelect_default = /*#__PURE__*/ _plugin_vue_export_helper_default(/* @__PUR
1402
1402
  }]]);
1403
1403
  };
1404
1404
  }
1405
- }), [["__scopeId", "data-v-c93b2c96"]]);
1405
+ }), [["__scopeId", "data-v-ac4e768b"]]);
1406
1406
  //#endregion
1407
1407
  export { GrSelect_default as t };