@feugene/granularity 0.41.0 → 0.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +13 -162
- package/README.md +4 -4
- package/dist/chunks/{config-BRESvs9g.js → config-Ca8qr8Q3.js} +1 -1
- package/dist/chunks/{config-BRESvs9g.js.map → config-Ca8qr8Q3.js.map} +1 -1
- package/dist/chunks/{en-CuuLfNHX.js → en-B9E3jO6m.js} +5 -1
- package/dist/chunks/{en-CuuLfNHX.js.map → en-B9E3jO6m.js.map} +1 -1
- package/dist/chunks/{es-CsmvfESn.js → es-DCkqHTlU.js} +3 -3
- package/dist/chunks/{es-CsmvfESn.js.map → es-DCkqHTlU.js.map} +1 -1
- package/dist/chunks/{es-CSnmrcMw.js → es-QpzdIEut.js} +5 -1
- package/dist/chunks/{es-CSnmrcMw.js.map → es-QpzdIEut.js.map} +1 -1
- package/dist/chunks/{grKbdStyles-DENu8pUK.js → grKbdStyles-ByMsX-lQ.js} +17 -2
- package/dist/chunks/grKbdStyles-ByMsX-lQ.js.map +1 -0
- package/dist/chunks/{granular-provider-BshNi2la.js → granular-provider-BG_2XqkS.js} +1 -1
- package/dist/chunks/{granular-provider-BshNi2la.js.map → granular-provider-BG_2XqkS.js.map} +1 -1
- package/dist/chunks/{ru-BKWafECI.js → ru-D_1Ya489.js} +5 -1
- package/dist/chunks/{ru-BKWafECI.js.map → ru-D_1Ya489.js.map} +1 -1
- package/dist/components/GrBreadcrumbs/chunks/{GrBreadcrumbs-DOpwY2EF.js → GrBreadcrumbs-D0qPry_R.js} +4 -1
- package/dist/components/GrBreadcrumbs/chunks/GrBreadcrumbs-D0qPry_R.js.map +1 -0
- package/dist/components/GrBreadcrumbs/index.js +1 -1
- package/dist/components/GrCommandPalette/chunks/{GrCommandPalette-CnzPuoIM.js → GrCommandPalette-Ha4s3DMT.js} +1 -1
- package/dist/components/GrCommandPalette/chunks/{GrCommandPalette-CnzPuoIM.js.map → GrCommandPalette-Ha4s3DMT.js.map} +1 -1
- package/dist/components/GrCommandPalette/index.js +1 -1
- package/dist/components/GrDataTable/chunks/{GrDataTable-DpyipOFx.js → GrDataTable-BBe7-uUe.js} +5 -1
- package/dist/components/GrDataTable/chunks/GrDataTable-BBe7-uUe.js.map +1 -0
- package/dist/components/GrDataTable/index.js +1 -1
- package/dist/components/GrDelta/chunks/{GrDelta-BpfqebEV.js → GrDelta-CgTvfoIN.js} +0 -0
- package/dist/components/GrDelta/chunks/GrDelta-CgTvfoIN.js.map +1 -0
- package/dist/components/GrDelta/index.js +1 -1
- package/dist/components/GrDescriptionList/chunks/{GrDescriptionList-D8ANFpn_.js → GrDescriptionList-Dz2Jj1if.js} +26 -11
- package/dist/components/GrDescriptionList/chunks/GrDescriptionList-Dz2Jj1if.js.map +1 -0
- package/dist/components/GrDescriptionList/index.js +1 -1
- package/dist/components/GrFilePreview/chunks/{GrFilePreview-PabkLgYX.js → GrFilePreview-2kVOg1C7.js} +40 -5
- package/dist/components/GrFilePreview/chunks/GrFilePreview-2kVOg1C7.js.map +1 -0
- package/dist/components/GrFilePreview/index.js +1 -1
- package/dist/components/GrInputTag/chunks/{GrInputTag-3Au5HWZL.js → GrInputTag-BKzm1PKw.js} +4 -1
- package/dist/components/GrInputTag/chunks/GrInputTag-BKzm1PKw.js.map +1 -0
- package/dist/components/GrInputTag/index.js +1 -1
- package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-CDGOX3C0.js → GrJsonViewer-BLCEWlwg.js} +32 -6
- package/dist/components/GrJsonViewer/chunks/GrJsonViewer-BLCEWlwg.js.map +1 -0
- package/dist/components/GrJsonViewer/index.js +1 -1
- package/dist/components/GrKbd/chunks/{GrKbd-D_a1mPLe.js → GrKbd-BNMeoaLV.js} +1 -1
- package/dist/components/GrKbd/chunks/{GrKbd-D_a1mPLe.js.map → GrKbd-BNMeoaLV.js.map} +1 -1
- package/dist/components/GrKbd/index.js +2 -2
- package/dist/components/GrNumberInput/chunks/{GrNumberInput-BqPydXpK.js → GrNumberInput-Bm-V59TG.js} +4 -1
- package/dist/components/GrNumberInput/chunks/GrNumberInput-Bm-V59TG.js.map +1 -0
- package/dist/components/GrNumberInput/index.js +1 -1
- package/dist/components/GrSegmented/chunks/{GrSegmented-NKTi9bMG.js → GrSegmented-CuDaKA6S.js} +5 -1
- package/dist/components/GrSegmented/chunks/GrSegmented-CuDaKA6S.js.map +1 -0
- package/dist/components/GrSegmented/index.js +1 -1
- package/dist/components/GrSortableList/chunks/{GrSortableList-CQCaWivn.js → GrSortableList-ce1psLL0.js} +4 -1
- package/dist/components/GrSortableList/chunks/GrSortableList-ce1psLL0.js.map +1 -0
- package/dist/components/GrSortableList/index.js +1 -1
- package/dist/components/GrSteps/chunks/{GrSteps-DXj7m-WO.js → GrSteps-Cng-Wrjk.js} +5 -1
- package/dist/components/GrSteps/chunks/GrSteps-Cng-Wrjk.js.map +1 -0
- package/dist/components/GrSteps/index.js +1 -1
- package/dist/components/GrTabs/chunks/{GrTabs-DHfv67cp.js → GrTabs-BArefUFv.js} +5 -1
- package/dist/components/GrTabs/chunks/GrTabs-BArefUFv.js.map +1 -0
- package/dist/components/GrTabs/index.js +1 -1
- package/dist/granular-provider-node.js +1 -1
- package/dist/granular-provider.js +1 -1
- package/dist/i18n/all.js +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/index.js +15 -15
- package/dist/types/components/GrFilePreview/GrFilePreview.vue.d.ts +14 -0
- package/dist/types/components/GrKbd/grKbdStyles.d.ts +17 -2
- package/dist/types/i18n/messages/en.d.ts +12 -0
- package/dist/types/i18n/messages/es.d.ts +12 -0
- package/dist/types/i18n/messages/ru.d.ts +12 -0
- package/dist/web-types.json +19 -1
- package/package.json +3 -3
- package/dist/chunks/grKbdStyles-DENu8pUK.js.map +0 -1
- package/dist/components/GrBreadcrumbs/chunks/GrBreadcrumbs-DOpwY2EF.js.map +0 -1
- package/dist/components/GrDataTable/chunks/GrDataTable-DpyipOFx.js.map +0 -1
- package/dist/components/GrDelta/chunks/GrDelta-BpfqebEV.js.map +0 -1
- package/dist/components/GrDescriptionList/chunks/GrDescriptionList-D8ANFpn_.js.map +0 -1
- package/dist/components/GrFilePreview/chunks/GrFilePreview-PabkLgYX.js.map +0 -1
- package/dist/components/GrInputTag/chunks/GrInputTag-3Au5HWZL.js.map +0 -1
- package/dist/components/GrJsonViewer/chunks/GrJsonViewer-CDGOX3C0.js.map +0 -1
- package/dist/components/GrNumberInput/chunks/GrNumberInput-BqPydXpK.js.map +0 -1
- package/dist/components/GrSegmented/chunks/GrSegmented-NKTi9bMG.js.map +0 -1
- package/dist/components/GrSortableList/chunks/GrSortableList-CQCaWivn.js.map +0 -1
- package/dist/components/GrSteps/chunks/GrSteps-DXj7m-WO.js.map +0 -1
- package/dist/components/GrTabs/chunks/GrTabs-DHfv67cp.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GrKbd-D_a1mPLe.js","names":[],"sources":["../../../../src/components/shared/hotkeyTokens.ts","../../../../src/components/shared/hotkey.ts","../../../../src/components/GrKbd/GrKbd.vue","../../../../src/components/GrKbd/GrKbd.vue"],"sourcesContent":["/**\n * Каталог клавиш, которые понимает `keys` у `GrKbd`.\n *\n * Таблица, а не цепочка `if`, по двум причинам: во-первых, список — часть\n * публичного контракта (потребителю нужно знать, что можно написать), и\n * витрина показывает именно его, а не свою копию; во-вторых, копия в демо или\n * в доке разошлась бы с форматтером молча.\n */\n\n/** Ключи читаемых имён — `gr.kbd.<name>`. */\nexport type GrKbdKeyName\n = | 'command' | 'option' | 'shift' | 'control' | 'enter' | 'escape' | 'tab'\n | 'backspace' | 'delete' | 'pageUp' | 'pageDown'\n | 'arrowUp' | 'arrowDown' | 'arrowLeft' | 'arrowRight'\n\n/** Одна клавиша в человекочитаемом виде. */\nexport interface HotkeyKeyView {\n /** Что видно на экране: символ (`⌘`) или слово (`Ctrl`). */\n label: string\n /**\n * Ключ читаемого имени (`gr.kbd.<name>`) для символьных клавиш. У `K` или\n * `Ctrl` его нет — они и так читаются вслух.\n */\n name?: GrKbdKeyName\n /** Символ (`⌘`, `↑`) или слово (`Ctrl`, `Esc`): от этого зависит склейка в одной плашке. */\n symbol?: boolean\n}\n\n/** Смысловая группа — ею витрина и дока раскладывают каталог. */\nexport type GrKbdTokenGroup = 'modifier' | 'editing' | 'navigation'\n\nexport interface GrKbdTokenSpec {\n /** Канонический токен: его пишут в `keys`. */\n token: string\n /** Что ещё принимается — синонимы и сам глиф. */\n aliases: readonly string[]\n group: GrKbdTokenGroup\n /** Вид на macOS и на остальных платформах. */\n apple: HotkeyKeyView\n other: HotkeyKeyView\n}\n\nfunction symbol(label: string, name: GrKbdKeyName): HotkeyKeyView {\n return { label, name, symbol: true }\n}\n\nfunction word(label: string, name?: GrKbdKeyName): HotkeyKeyView {\n return name ? { label, name } : { label }\n}\n\nexport const GR_KBD_TOKENS: readonly GrKbdTokenSpec[] = [\n // Модификаторы: на macOS символы, на остальных платформах слова.\n { token: 'mod', aliases: [], group: 'modifier', apple: symbol('⌘', 'command'), other: word('Ctrl') },\n { token: 'meta', aliases: ['cmd', 'command', '⌘'], group: 'modifier', apple: symbol('⌘', 'command'), other: word('Meta') },\n { token: 'ctrl', aliases: ['control', '⌃'], group: 'modifier', apple: symbol('⌃', 'control'), other: word('Ctrl') },\n { token: 'alt', aliases: ['option', '⌥'], group: 'modifier', apple: symbol('⌥', 'option'), other: word('Alt') },\n { token: 'shift', aliases: ['⇧'], group: 'modifier', apple: symbol('⇧', 'shift'), other: word('Shift') },\n\n { token: 'enter', aliases: ['return', '↵', '↩'], group: 'editing', apple: symbol('↩', 'enter'), other: word('Enter') },\n { token: 'esc', aliases: ['escape'], group: 'editing', apple: word('Esc', 'escape'), other: word('Esc', 'escape') },\n { token: 'tab', aliases: ['⇥'], group: 'editing', apple: symbol('⇥', 'tab'), other: word('Tab') },\n { token: 'space', aliases: [], group: 'editing', apple: word('Space'), other: word('Space') },\n { token: 'backspace', aliases: ['⌫'], group: 'editing', apple: symbol('⌫', 'backspace'), other: word('Backspace') },\n { token: 'delete', aliases: ['del', '⌦'], group: 'editing', apple: symbol('⌦', 'delete'), other: word('Del') },\n\n { token: 'up', aliases: ['arrowup', '↑'], group: 'navigation', apple: symbol('↑', 'arrowUp'), other: symbol('↑', 'arrowUp') },\n { token: 'down', aliases: ['arrowdown', '↓'], group: 'navigation', apple: symbol('↓', 'arrowDown'), other: symbol('↓', 'arrowDown') },\n { token: 'left', aliases: ['arrowleft', '←'], group: 'navigation', apple: symbol('←', 'arrowLeft'), other: symbol('←', 'arrowLeft') },\n { token: 'right', aliases: ['arrowright', '→'], group: 'navigation', apple: symbol('→', 'arrowRight'), other: symbol('→', 'arrowRight') },\n { token: 'home', aliases: [], group: 'navigation', apple: word('Home'), other: word('Home') },\n { token: 'end', aliases: [], group: 'navigation', apple: word('End'), other: word('End') },\n { token: 'pageup', aliases: ['pgup', '⇞'], group: 'navigation', apple: symbol('⇞', 'pageUp'), other: word('PgUp') },\n { token: 'pagedown', aliases: ['pgdn', 'pgdown', '⇟'], group: 'navigation', apple: symbol('⇟', 'pageDown'), other: word('PgDn') },\n]\n\nconst BY_ALIAS = new Map<string, GrKbdTokenSpec>()\nfor (const spec of GR_KBD_TOKENS) {\n BY_ALIAS.set(spec.token, spec)\n for (const alias of spec.aliases) BY_ALIAS.set(alias, spec)\n}\n\n/** Токен или его синоним — в описание клавиши. Регистр не важен, глиф тоже принимается. */\nexport function findKbdToken(token: string): GrKbdTokenSpec | undefined {\n const trimmed = token.trim()\n return BY_ALIAS.get(trimmed.toLowerCase()) ?? BY_ALIAS.get(trimmed)\n}\n","/**\n * Разбор, матчинг и человекочитаемое представление сочетаний клавиш.\n *\n * Общий модуль, а не часть `GrCommandPalette`: то же нужно `GrKbd`, а импорт из\n * чужой компонентной директории на сборке дал бы ребро `GrKbd →\n * GrCommandPalette` (хелпер уехал бы в чанк палитры) — `granular doctor` считает\n * такое незадекларированной зависимостью.\n *\n * Директива `v-hotkey` для палитры не годится: ей нужен смонтированный элемент,\n * а палитра в закрытом состоянии не рендерит ничего. Поэтому здесь — свой\n * матчер на `window`, с тем же синтаксисом комбинаций плюс токен `mod`.\n */\n\nimport { eventMatchesKey, isAppleDevice, isComposingEvent, shiftSatisfied } from '../../internal/keyboard'\n\nimport { findKbdToken, type HotkeyKeyView } from './hotkeyTokens'\n\nexport { isAppleDevice } from '../../internal/keyboard'\nexport { findKbdToken, GR_KBD_TOKENS } from './hotkeyTokens'\nexport type { GrKbdKeyName, GrKbdTokenGroup, GrKbdTokenSpec, HotkeyKeyView } from './hotkeyTokens'\n\nexport type ParsedCommandHotkey = {\n key: string\n ctrl: boolean\n meta: boolean\n alt: boolean\n shift: boolean\n /** `mod` — Cmd на macOS, Ctrl на остальных платформах. */\n mod: boolean\n}\n\nexport function parseCommandHotkey(combo: string): ParsedCommandHotkey | null {\n const parts = combo.split('+').map(p => p.trim()).filter(Boolean)\n const keyToken = parts.at(-1)\n if (!keyToken)\n return null\n\n const parsed: ParsedCommandHotkey = {\n key: keyToken.length === 1 ? keyToken.toLowerCase() : keyToken,\n ctrl: false,\n meta: false,\n alt: false,\n shift: false,\n mod: false,\n }\n\n for (const part of parts.slice(0, -1)) {\n const token = part.toLowerCase()\n if (token === 'mod')\n parsed.mod = true\n else if (token === 'ctrl' || token === 'control')\n parsed.ctrl = true\n else if (token === 'meta' || token === 'cmd' || token === 'command' || token === '⌘')\n parsed.meta = true\n else if (token === 'alt' || token === 'option')\n parsed.alt = true\n else if (token === 'shift')\n parsed.shift = true\n }\n\n return parsed\n}\n\nexport function matchesCommandHotkey(\n event: KeyboardEvent,\n hotkey: ParsedCommandHotkey,\n apple = isAppleDevice(),\n): boolean {\n // Клавиша во время IME-композиции принадлежит композиции, а не сочетанию.\n if (isComposingEvent(event))\n return false\n\n const expectMeta = hotkey.meta || (hotkey.mod && apple)\n const expectCtrl = hotkey.ctrl || (hotkey.mod && !apple)\n\n if (event.metaKey !== expectMeta)\n return false\n if (event.ctrlKey !== expectCtrl)\n return false\n if (event.altKey !== hotkey.alt)\n return false\n if (!shiftSatisfied(event, hotkey.key, hotkey.shift))\n return false\n\n // Комбинация с модификаторами матчится и по физическому коду: на нелатинской\n // раскладке `mod+K` приходит как `key: 'л'`, и без кода сочетание мертво.\n return eventMatchesKey(event, hotkey.key, {\n codeFallback: expectMeta || expectCtrl || hotkey.alt,\n })\n}\n\n/** Человекочитаемые клавиши для подсказки в поле ввода (`⌘` / `Ctrl`). */\nexport function formatCommandHotkey(hotkey: ParsedCommandHotkey, apple = isAppleDevice()): string[] {\n const keys: string[] = []\n if (hotkey.meta || (hotkey.mod && apple))\n keys.push('⌘')\n if (hotkey.ctrl || (hotkey.mod && !apple))\n keys.push('Ctrl')\n if (hotkey.alt)\n keys.push(apple ? '⌥' : 'Alt')\n if (hotkey.shift)\n keys.push(apple ? '⇧' : 'Shift')\n keys.push(hotkey.key.length === 1 ? hotkey.key.toUpperCase() : hotkey.key)\n return keys\n}\n\n/**\n * Токен → то, что видит пользователь. Каталог общий с витриной и докой\n * (`hotkeyTokens.ts`): цепочка `if` расходилась бы со списком, который читает\n * потребитель.\n */\nexport function formatHotkeyToken(token: string, apple: boolean): HotkeyKeyView {\n const spec = findKbdToken(token)\n if (spec)\n return apple ? spec.apple : spec.other\n\n // Не из каталога — печатная клавиша: одиночный символ заглавным, потому что\n // `k` и `K` на клавише выглядят одинаково.\n const t = token.trim()\n return { label: t.length === 1 ? t.toUpperCase() : t }\n}\n\nexport function formatHotkeyTokens(tokens: readonly string[], apple: boolean): HotkeyKeyView[] {\n return tokens.map(token => formatHotkeyToken(token, apple))\n}\n\n/** Комбинация строкой (`mod+shift+K`) — в набор токенов в порядке показа. */\nexport function splitHotkeyCombo(combo: string): string[] {\n return combo.split('+').map(part => part.trim()).filter(Boolean)\n}\n","<script setup lang=\"ts\">\nimport { computed, onMounted, ref } from 'vue'\n\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport type { GrComponentSize } from '../shared/sizes'\nimport { formatHotkeyTokens, isAppleDevice, splitHotkeyCombo } from '../shared/hotkey'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport {\n grKbdComboClass,\n grKbdInnerKeyClass,\n grKbdKeyClass,\n separatorClass,\n sequenceSeparatorClass,\n type GrKbdVariant,\n} from './grKbdStyles'\n\nexport interface GrKbdProps {\n size?: GrKbdSize\n /**\n * `merged` (по умолчанию) — сочетание одной плашкой, как его пишут сами\n * системы: `⌘K` на macOS, `Ctrl+K` на прочих. `split` — по плашке на клавишу.\n * `sequence` — аккорд «G затем I»: клавиши нажимают одну за другой.\n */\n variant?: GrKbdVariant\n /**\n * Сочетание: строкой (`\"mod+shift+K\"`) или набором токенов\n * (`['mod', 'K']`). Токен `mod` — Cmd на macOS, Ctrl на остальных.\n */\n keys?: string | string[]\n /**\n * Разделитель между клавишами. Не задан — авто: в общей плашке символы\n * склеиваются (`⌘K`), а слова разделяются плюсом (`Ctrl+K`); у `split` это\n * плюс, у `sequence` — слово из локали. Пустая строка — только зазор.\n */\n separator?: string\n platform?: GrKbdPlatform\n}\n\n/**\n * GrKbd — GR-примитив для отображения клавиш и сочетаний (`<kbd>`).\n *\n * Одна клавиша — слотом: `<GrKbd>Esc</GrKbd>`.\n * Сочетание — пропом: `<GrKbd keys=\"mod+K\" />` (на macOS `⌘ K`, иначе `Ctrl K`).\n */\nexport type GrKbdSize = GrComponentSize\n\nexport type { GrKbdVariant } from './grKbdStyles'\n\n/** Откуда брать платформу для токена `mod`. */\nexport type GrKbdPlatform = 'auto' | 'apple' | 'other'\n\nconst props = withDefaults(\n defineProps<GrKbdProps>(),\n {\n size: undefined,\n variant: 'merged',\n keys: undefined,\n separator: undefined,\n platform: 'auto',\n },\n)\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrKbd' })\nconst { t } = useGranularityTranslations()\n\n/**\n * Платформа уточняется только после монтирования: `navigator` в теле `setup`\n * либо роняет серверный рендер, либо расходится с ним и ломает гидрацию.\n * До этого момента показываем не-Apple вариант — он же приходит с сервера.\n */\nconst detectedApple = ref(false)\n\nonMounted(() => {\n detectedApple.value = isAppleDevice()\n})\n\nconst isApple = computed(() => {\n if (props.platform === 'apple')\n return true\n if (props.platform === 'other')\n return false\n return detectedApple.value\n})\n\nconst tokens = computed(() => {\n if (props.keys === undefined)\n return []\n return Array.isArray(props.keys) ? props.keys : splitHotkeyCombo(props.keys)\n})\n\n// Сочетание рисуется вложенными `<kbd>` — приём из спецификации HTML, а не\n// обёртка-div: сочетание остаётся клавиатурным вводом целиком.\nconst keyViews = computed(() => formatHotkeyTokens(tokens.value, isApple.value))\nconst hasCombo = computed(() => keyViews.value.length > 0)\n\nconst keyClass = computed(() => grKbdKeyClass(resolvedSize.value))\nconst innerKeyClass = computed(() => grKbdInnerKeyClass(resolvedSize.value, props.variant))\nconst comboClass = computed(() => grKbdComboClass(resolvedSize.value, props.variant))\n\n/**\n * Разделитель перед клавишей `index`.\n *\n * Авто-правило одно: в общей плашке символы склеиваются, слова — нет. Внутри\n * одной платформы набор однороден (macOS даёт `⌘⌥⇧`, прочие — `Ctrl`, `Alt`),\n * поэтому «слева символ» и означает «пишем как система».\n */\nfunction separatorAt(index: number): string {\n if (index === 0)\n return ''\n if (props.separator !== undefined)\n return props.separator\n if (props.variant === 'sequence')\n return t('gr.kbd.then', 'then')\n if (props.variant === 'split')\n return '+'\n\n return keyViews.value[index - 1]?.symbol ? '' : '+'\n}\n\nconst NAME_FALLBACKS: Record<string, string> = {\n command: 'Command',\n option: 'Option',\n shift: 'Shift',\n control: 'Control',\n enter: 'Enter',\n escape: 'Escape',\n tab: 'Tab',\n backspace: 'Backspace',\n delete: 'Delete',\n pageUp: 'Page Up',\n pageDown: 'Page Down',\n arrowUp: 'Arrow Up',\n arrowDown: 'Arrow Down',\n arrowLeft: 'Arrow Left',\n arrowRight: 'Arrow Right',\n}\n\n/** Читаемое имя символьной клавиши: `⌘` без него диктор произносит как знак. */\nfunction readableName(name: string): string {\n return t(`gr.kbd.${name}`, NAME_FALLBACKS[name] ?? name)\n}\n\ndefineSlots<{\n /** Клавиша или сочетание вместо пропа `keys`. */\n default?: () => any\n}>()\n</script>\n\n<template>\n <kbd\n v-if=\"hasCombo\"\n data-gr-kbd\n data-gr-kbd-combo\n :data-variant=\"variant\"\n :class=\"comboClass\"\n ><template\n v-for=\"(key, index) in keyViews\"\n :key=\"`${key.label}-${index}`\"\n ><span\n v-if=\"separatorAt(index)\"\n :class=\"variant === 'sequence' ? sequenceSeparatorClass : separatorClass\"\n aria-hidden=\"true\"\n >{{ separatorAt(index) }}</span><kbd\n data-gr-kbd-key\n :class=\"innerKeyClass\"\n ><span :aria-hidden=\"key.name ? 'true' : undefined\">{{ key.label }}</span><span\n v-if=\"key.name\"\n class=\"sr-only\"\n >{{ readableName(key.name) }}</span></kbd></template></kbd>\n\n <kbd\n v-else\n data-gr-kbd\n data-gr-kbd-key\n :class=\"keyClass\"\n ><slot /></kbd>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, onMounted, ref } from 'vue'\n\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport type { GrComponentSize } from '../shared/sizes'\nimport { formatHotkeyTokens, isAppleDevice, splitHotkeyCombo } from '../shared/hotkey'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport {\n grKbdComboClass,\n grKbdInnerKeyClass,\n grKbdKeyClass,\n separatorClass,\n sequenceSeparatorClass,\n type GrKbdVariant,\n} from './grKbdStyles'\n\nexport interface GrKbdProps {\n size?: GrKbdSize\n /**\n * `merged` (по умолчанию) — сочетание одной плашкой, как его пишут сами\n * системы: `⌘K` на macOS, `Ctrl+K` на прочих. `split` — по плашке на клавишу.\n * `sequence` — аккорд «G затем I»: клавиши нажимают одну за другой.\n */\n variant?: GrKbdVariant\n /**\n * Сочетание: строкой (`\"mod+shift+K\"`) или набором токенов\n * (`['mod', 'K']`). Токен `mod` — Cmd на macOS, Ctrl на остальных.\n */\n keys?: string | string[]\n /**\n * Разделитель между клавишами. Не задан — авто: в общей плашке символы\n * склеиваются (`⌘K`), а слова разделяются плюсом (`Ctrl+K`); у `split` это\n * плюс, у `sequence` — слово из локали. Пустая строка — только зазор.\n */\n separator?: string\n platform?: GrKbdPlatform\n}\n\n/**\n * GrKbd — GR-примитив для отображения клавиш и сочетаний (`<kbd>`).\n *\n * Одна клавиша — слотом: `<GrKbd>Esc</GrKbd>`.\n * Сочетание — пропом: `<GrKbd keys=\"mod+K\" />` (на macOS `⌘ K`, иначе `Ctrl K`).\n */\nexport type GrKbdSize = GrComponentSize\n\nexport type { GrKbdVariant } from './grKbdStyles'\n\n/** Откуда брать платформу для токена `mod`. */\nexport type GrKbdPlatform = 'auto' | 'apple' | 'other'\n\nconst props = withDefaults(\n defineProps<GrKbdProps>(),\n {\n size: undefined,\n variant: 'merged',\n keys: undefined,\n separator: undefined,\n platform: 'auto',\n },\n)\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrKbd' })\nconst { t } = useGranularityTranslations()\n\n/**\n * Платформа уточняется только после монтирования: `navigator` в теле `setup`\n * либо роняет серверный рендер, либо расходится с ним и ломает гидрацию.\n * До этого момента показываем не-Apple вариант — он же приходит с сервера.\n */\nconst detectedApple = ref(false)\n\nonMounted(() => {\n detectedApple.value = isAppleDevice()\n})\n\nconst isApple = computed(() => {\n if (props.platform === 'apple')\n return true\n if (props.platform === 'other')\n return false\n return detectedApple.value\n})\n\nconst tokens = computed(() => {\n if (props.keys === undefined)\n return []\n return Array.isArray(props.keys) ? props.keys : splitHotkeyCombo(props.keys)\n})\n\n// Сочетание рисуется вложенными `<kbd>` — приём из спецификации HTML, а не\n// обёртка-div: сочетание остаётся клавиатурным вводом целиком.\nconst keyViews = computed(() => formatHotkeyTokens(tokens.value, isApple.value))\nconst hasCombo = computed(() => keyViews.value.length > 0)\n\nconst keyClass = computed(() => grKbdKeyClass(resolvedSize.value))\nconst innerKeyClass = computed(() => grKbdInnerKeyClass(resolvedSize.value, props.variant))\nconst comboClass = computed(() => grKbdComboClass(resolvedSize.value, props.variant))\n\n/**\n * Разделитель перед клавишей `index`.\n *\n * Авто-правило одно: в общей плашке символы склеиваются, слова — нет. Внутри\n * одной платформы набор однороден (macOS даёт `⌘⌥⇧`, прочие — `Ctrl`, `Alt`),\n * поэтому «слева символ» и означает «пишем как система».\n */\nfunction separatorAt(index: number): string {\n if (index === 0)\n return ''\n if (props.separator !== undefined)\n return props.separator\n if (props.variant === 'sequence')\n return t('gr.kbd.then', 'then')\n if (props.variant === 'split')\n return '+'\n\n return keyViews.value[index - 1]?.symbol ? '' : '+'\n}\n\nconst NAME_FALLBACKS: Record<string, string> = {\n command: 'Command',\n option: 'Option',\n shift: 'Shift',\n control: 'Control',\n enter: 'Enter',\n escape: 'Escape',\n tab: 'Tab',\n backspace: 'Backspace',\n delete: 'Delete',\n pageUp: 'Page Up',\n pageDown: 'Page Down',\n arrowUp: 'Arrow Up',\n arrowDown: 'Arrow Down',\n arrowLeft: 'Arrow Left',\n arrowRight: 'Arrow Right',\n}\n\n/** Читаемое имя символьной клавиши: `⌘` без него диктор произносит как знак. */\nfunction readableName(name: string): string {\n return t(`gr.kbd.${name}`, NAME_FALLBACKS[name] ?? name)\n}\n\ndefineSlots<{\n /** Клавиша или сочетание вместо пропа `keys`. */\n default?: () => any\n}>()\n</script>\n\n<template>\n <kbd\n v-if=\"hasCombo\"\n data-gr-kbd\n data-gr-kbd-combo\n :data-variant=\"variant\"\n :class=\"comboClass\"\n ><template\n v-for=\"(key, index) in keyViews\"\n :key=\"`${key.label}-${index}`\"\n ><span\n v-if=\"separatorAt(index)\"\n :class=\"variant === 'sequence' ? sequenceSeparatorClass : separatorClass\"\n aria-hidden=\"true\"\n >{{ separatorAt(index) }}</span><kbd\n data-gr-kbd-key\n :class=\"innerKeyClass\"\n ><span :aria-hidden=\"key.name ? 'true' : undefined\">{{ key.label }}</span><span\n v-if=\"key.name\"\n class=\"sr-only\"\n >{{ readableName(key.name) }}</span></kbd></template></kbd>\n\n <kbd\n v-else\n data-gr-kbd\n data-gr-kbd-key\n :class=\"keyClass\"\n ><slot /></kbd>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GrKbd-BNMeoaLV.js","names":[],"sources":["../../../../src/components/shared/hotkeyTokens.ts","../../../../src/components/shared/hotkey.ts","../../../../src/components/GrKbd/GrKbd.vue","../../../../src/components/GrKbd/GrKbd.vue"],"sourcesContent":["/**\n * Каталог клавиш, которые понимает `keys` у `GrKbd`.\n *\n * Таблица, а не цепочка `if`, по двум причинам: во-первых, список — часть\n * публичного контракта (потребителю нужно знать, что можно написать), и\n * витрина показывает именно его, а не свою копию; во-вторых, копия в демо или\n * в доке разошлась бы с форматтером молча.\n */\n\n/** Ключи читаемых имён — `gr.kbd.<name>`. */\nexport type GrKbdKeyName\n = | 'command' | 'option' | 'shift' | 'control' | 'enter' | 'escape' | 'tab'\n | 'backspace' | 'delete' | 'pageUp' | 'pageDown'\n | 'arrowUp' | 'arrowDown' | 'arrowLeft' | 'arrowRight'\n\n/** Одна клавиша в человекочитаемом виде. */\nexport interface HotkeyKeyView {\n /** Что видно на экране: символ (`⌘`) или слово (`Ctrl`). */\n label: string\n /**\n * Ключ читаемого имени (`gr.kbd.<name>`) для символьных клавиш. У `K` или\n * `Ctrl` его нет — они и так читаются вслух.\n */\n name?: GrKbdKeyName\n /** Символ (`⌘`, `↑`) или слово (`Ctrl`, `Esc`): от этого зависит склейка в одной плашке. */\n symbol?: boolean\n}\n\n/** Смысловая группа — ею витрина и дока раскладывают каталог. */\nexport type GrKbdTokenGroup = 'modifier' | 'editing' | 'navigation'\n\nexport interface GrKbdTokenSpec {\n /** Канонический токен: его пишут в `keys`. */\n token: string\n /** Что ещё принимается — синонимы и сам глиф. */\n aliases: readonly string[]\n group: GrKbdTokenGroup\n /** Вид на macOS и на остальных платформах. */\n apple: HotkeyKeyView\n other: HotkeyKeyView\n}\n\nfunction symbol(label: string, name: GrKbdKeyName): HotkeyKeyView {\n return { label, name, symbol: true }\n}\n\nfunction word(label: string, name?: GrKbdKeyName): HotkeyKeyView {\n return name ? { label, name } : { label }\n}\n\nexport const GR_KBD_TOKENS: readonly GrKbdTokenSpec[] = [\n // Модификаторы: на macOS символы, на остальных платформах слова.\n { token: 'mod', aliases: [], group: 'modifier', apple: symbol('⌘', 'command'), other: word('Ctrl') },\n { token: 'meta', aliases: ['cmd', 'command', '⌘'], group: 'modifier', apple: symbol('⌘', 'command'), other: word('Meta') },\n { token: 'ctrl', aliases: ['control', '⌃'], group: 'modifier', apple: symbol('⌃', 'control'), other: word('Ctrl') },\n { token: 'alt', aliases: ['option', '⌥'], group: 'modifier', apple: symbol('⌥', 'option'), other: word('Alt') },\n { token: 'shift', aliases: ['⇧'], group: 'modifier', apple: symbol('⇧', 'shift'), other: word('Shift') },\n\n { token: 'enter', aliases: ['return', '↵', '↩'], group: 'editing', apple: symbol('↩', 'enter'), other: word('Enter') },\n { token: 'esc', aliases: ['escape'], group: 'editing', apple: word('Esc', 'escape'), other: word('Esc', 'escape') },\n { token: 'tab', aliases: ['⇥'], group: 'editing', apple: symbol('⇥', 'tab'), other: word('Tab') },\n { token: 'space', aliases: [], group: 'editing', apple: word('Space'), other: word('Space') },\n { token: 'backspace', aliases: ['⌫'], group: 'editing', apple: symbol('⌫', 'backspace'), other: word('Backspace') },\n { token: 'delete', aliases: ['del', '⌦'], group: 'editing', apple: symbol('⌦', 'delete'), other: word('Del') },\n\n { token: 'up', aliases: ['arrowup', '↑'], group: 'navigation', apple: symbol('↑', 'arrowUp'), other: symbol('↑', 'arrowUp') },\n { token: 'down', aliases: ['arrowdown', '↓'], group: 'navigation', apple: symbol('↓', 'arrowDown'), other: symbol('↓', 'arrowDown') },\n { token: 'left', aliases: ['arrowleft', '←'], group: 'navigation', apple: symbol('←', 'arrowLeft'), other: symbol('←', 'arrowLeft') },\n { token: 'right', aliases: ['arrowright', '→'], group: 'navigation', apple: symbol('→', 'arrowRight'), other: symbol('→', 'arrowRight') },\n { token: 'home', aliases: [], group: 'navigation', apple: word('Home'), other: word('Home') },\n { token: 'end', aliases: [], group: 'navigation', apple: word('End'), other: word('End') },\n { token: 'pageup', aliases: ['pgup', '⇞'], group: 'navigation', apple: symbol('⇞', 'pageUp'), other: word('PgUp') },\n { token: 'pagedown', aliases: ['pgdn', 'pgdown', '⇟'], group: 'navigation', apple: symbol('⇟', 'pageDown'), other: word('PgDn') },\n]\n\nconst BY_ALIAS = new Map<string, GrKbdTokenSpec>()\nfor (const spec of GR_KBD_TOKENS) {\n BY_ALIAS.set(spec.token, spec)\n for (const alias of spec.aliases) BY_ALIAS.set(alias, spec)\n}\n\n/** Токен или его синоним — в описание клавиши. Регистр не важен, глиф тоже принимается. */\nexport function findKbdToken(token: string): GrKbdTokenSpec | undefined {\n const trimmed = token.trim()\n return BY_ALIAS.get(trimmed.toLowerCase()) ?? BY_ALIAS.get(trimmed)\n}\n","/**\n * Разбор, матчинг и человекочитаемое представление сочетаний клавиш.\n *\n * Общий модуль, а не часть `GrCommandPalette`: то же нужно `GrKbd`, а импорт из\n * чужой компонентной директории на сборке дал бы ребро `GrKbd →\n * GrCommandPalette` (хелпер уехал бы в чанк палитры) — `granular doctor` считает\n * такое незадекларированной зависимостью.\n *\n * Директива `v-hotkey` для палитры не годится: ей нужен смонтированный элемент,\n * а палитра в закрытом состоянии не рендерит ничего. Поэтому здесь — свой\n * матчер на `window`, с тем же синтаксисом комбинаций плюс токен `mod`.\n */\n\nimport { eventMatchesKey, isAppleDevice, isComposingEvent, shiftSatisfied } from '../../internal/keyboard'\n\nimport { findKbdToken, type HotkeyKeyView } from './hotkeyTokens'\n\nexport { isAppleDevice } from '../../internal/keyboard'\nexport { findKbdToken, GR_KBD_TOKENS } from './hotkeyTokens'\nexport type { GrKbdKeyName, GrKbdTokenGroup, GrKbdTokenSpec, HotkeyKeyView } from './hotkeyTokens'\n\nexport type ParsedCommandHotkey = {\n key: string\n ctrl: boolean\n meta: boolean\n alt: boolean\n shift: boolean\n /** `mod` — Cmd на macOS, Ctrl на остальных платформах. */\n mod: boolean\n}\n\nexport function parseCommandHotkey(combo: string): ParsedCommandHotkey | null {\n const parts = combo.split('+').map(p => p.trim()).filter(Boolean)\n const keyToken = parts.at(-1)\n if (!keyToken)\n return null\n\n const parsed: ParsedCommandHotkey = {\n key: keyToken.length === 1 ? keyToken.toLowerCase() : keyToken,\n ctrl: false,\n meta: false,\n alt: false,\n shift: false,\n mod: false,\n }\n\n for (const part of parts.slice(0, -1)) {\n const token = part.toLowerCase()\n if (token === 'mod')\n parsed.mod = true\n else if (token === 'ctrl' || token === 'control')\n parsed.ctrl = true\n else if (token === 'meta' || token === 'cmd' || token === 'command' || token === '⌘')\n parsed.meta = true\n else if (token === 'alt' || token === 'option')\n parsed.alt = true\n else if (token === 'shift')\n parsed.shift = true\n }\n\n return parsed\n}\n\nexport function matchesCommandHotkey(\n event: KeyboardEvent,\n hotkey: ParsedCommandHotkey,\n apple = isAppleDevice(),\n): boolean {\n // Клавиша во время IME-композиции принадлежит композиции, а не сочетанию.\n if (isComposingEvent(event))\n return false\n\n const expectMeta = hotkey.meta || (hotkey.mod && apple)\n const expectCtrl = hotkey.ctrl || (hotkey.mod && !apple)\n\n if (event.metaKey !== expectMeta)\n return false\n if (event.ctrlKey !== expectCtrl)\n return false\n if (event.altKey !== hotkey.alt)\n return false\n if (!shiftSatisfied(event, hotkey.key, hotkey.shift))\n return false\n\n // Комбинация с модификаторами матчится и по физическому коду: на нелатинской\n // раскладке `mod+K` приходит как `key: 'л'`, и без кода сочетание мертво.\n return eventMatchesKey(event, hotkey.key, {\n codeFallback: expectMeta || expectCtrl || hotkey.alt,\n })\n}\n\n/** Человекочитаемые клавиши для подсказки в поле ввода (`⌘` / `Ctrl`). */\nexport function formatCommandHotkey(hotkey: ParsedCommandHotkey, apple = isAppleDevice()): string[] {\n const keys: string[] = []\n if (hotkey.meta || (hotkey.mod && apple))\n keys.push('⌘')\n if (hotkey.ctrl || (hotkey.mod && !apple))\n keys.push('Ctrl')\n if (hotkey.alt)\n keys.push(apple ? '⌥' : 'Alt')\n if (hotkey.shift)\n keys.push(apple ? '⇧' : 'Shift')\n keys.push(hotkey.key.length === 1 ? hotkey.key.toUpperCase() : hotkey.key)\n return keys\n}\n\n/**\n * Токен → то, что видит пользователь. Каталог общий с витриной и докой\n * (`hotkeyTokens.ts`): цепочка `if` расходилась бы со списком, который читает\n * потребитель.\n */\nexport function formatHotkeyToken(token: string, apple: boolean): HotkeyKeyView {\n const spec = findKbdToken(token)\n if (spec)\n return apple ? spec.apple : spec.other\n\n // Не из каталога — печатная клавиша: одиночный символ заглавным, потому что\n // `k` и `K` на клавише выглядят одинаково.\n const t = token.trim()\n return { label: t.length === 1 ? t.toUpperCase() : t }\n}\n\nexport function formatHotkeyTokens(tokens: readonly string[], apple: boolean): HotkeyKeyView[] {\n return tokens.map(token => formatHotkeyToken(token, apple))\n}\n\n/** Комбинация строкой (`mod+shift+K`) — в набор токенов в порядке показа. */\nexport function splitHotkeyCombo(combo: string): string[] {\n return combo.split('+').map(part => part.trim()).filter(Boolean)\n}\n","<script setup lang=\"ts\">\nimport { computed, onMounted, ref } from 'vue'\n\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport type { GrComponentSize } from '../shared/sizes'\nimport { formatHotkeyTokens, isAppleDevice, splitHotkeyCombo } from '../shared/hotkey'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport {\n grKbdComboClass,\n grKbdInnerKeyClass,\n grKbdKeyClass,\n separatorClass,\n sequenceSeparatorClass,\n type GrKbdVariant,\n} from './grKbdStyles'\n\nexport interface GrKbdProps {\n size?: GrKbdSize\n /**\n * `merged` (по умолчанию) — сочетание одной плашкой, как его пишут сами\n * системы: `⌘K` на macOS, `Ctrl+K` на прочих. `split` — по плашке на клавишу.\n * `sequence` — аккорд «G затем I»: клавиши нажимают одну за другой.\n */\n variant?: GrKbdVariant\n /**\n * Сочетание: строкой (`\"mod+shift+K\"`) или набором токенов\n * (`['mod', 'K']`). Токен `mod` — Cmd на macOS, Ctrl на остальных.\n */\n keys?: string | string[]\n /**\n * Разделитель между клавишами. Не задан — авто: в общей плашке символы\n * склеиваются (`⌘K`), а слова разделяются плюсом (`Ctrl+K`); у `split` это\n * плюс, у `sequence` — слово из локали. Пустая строка — только зазор.\n */\n separator?: string\n platform?: GrKbdPlatform\n}\n\n/**\n * GrKbd — GR-примитив для отображения клавиш и сочетаний (`<kbd>`).\n *\n * Одна клавиша — слотом: `<GrKbd>Esc</GrKbd>`.\n * Сочетание — пропом: `<GrKbd keys=\"mod+K\" />` (на macOS `⌘ K`, иначе `Ctrl K`).\n */\nexport type GrKbdSize = GrComponentSize\n\nexport type { GrKbdVariant } from './grKbdStyles'\n\n/** Откуда брать платформу для токена `mod`. */\nexport type GrKbdPlatform = 'auto' | 'apple' | 'other'\n\nconst props = withDefaults(\n defineProps<GrKbdProps>(),\n {\n size: undefined,\n variant: 'merged',\n keys: undefined,\n separator: undefined,\n platform: 'auto',\n },\n)\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrKbd' })\nconst { t } = useGranularityTranslations()\n\n/**\n * Платформа уточняется только после монтирования: `navigator` в теле `setup`\n * либо роняет серверный рендер, либо расходится с ним и ломает гидрацию.\n * До этого момента показываем не-Apple вариант — он же приходит с сервера.\n */\nconst detectedApple = ref(false)\n\nonMounted(() => {\n detectedApple.value = isAppleDevice()\n})\n\nconst isApple = computed(() => {\n if (props.platform === 'apple')\n return true\n if (props.platform === 'other')\n return false\n return detectedApple.value\n})\n\nconst tokens = computed(() => {\n if (props.keys === undefined)\n return []\n return Array.isArray(props.keys) ? props.keys : splitHotkeyCombo(props.keys)\n})\n\n// Сочетание рисуется вложенными `<kbd>` — приём из спецификации HTML, а не\n// обёртка-div: сочетание остаётся клавиатурным вводом целиком.\nconst keyViews = computed(() => formatHotkeyTokens(tokens.value, isApple.value))\nconst hasCombo = computed(() => keyViews.value.length > 0)\n\nconst keyClass = computed(() => grKbdKeyClass(resolvedSize.value))\nconst innerKeyClass = computed(() => grKbdInnerKeyClass(resolvedSize.value, props.variant))\nconst comboClass = computed(() => grKbdComboClass(resolvedSize.value, props.variant))\n\n/**\n * Разделитель перед клавишей `index`.\n *\n * Авто-правило одно: в общей плашке символы склеиваются, слова — нет. Внутри\n * одной платформы набор однороден (macOS даёт `⌘⌥⇧`, прочие — `Ctrl`, `Alt`),\n * поэтому «слева символ» и означает «пишем как система».\n */\nfunction separatorAt(index: number): string {\n if (index === 0)\n return ''\n if (props.separator !== undefined)\n return props.separator\n if (props.variant === 'sequence')\n return t('gr.kbd.then', 'then')\n if (props.variant === 'split')\n return '+'\n\n return keyViews.value[index - 1]?.symbol ? '' : '+'\n}\n\nconst NAME_FALLBACKS: Record<string, string> = {\n command: 'Command',\n option: 'Option',\n shift: 'Shift',\n control: 'Control',\n enter: 'Enter',\n escape: 'Escape',\n tab: 'Tab',\n backspace: 'Backspace',\n delete: 'Delete',\n pageUp: 'Page Up',\n pageDown: 'Page Down',\n arrowUp: 'Arrow Up',\n arrowDown: 'Arrow Down',\n arrowLeft: 'Arrow Left',\n arrowRight: 'Arrow Right',\n}\n\n/** Читаемое имя символьной клавиши: `⌘` без него диктор произносит как знак. */\nfunction readableName(name: string): string {\n return t(`gr.kbd.${name}`, NAME_FALLBACKS[name] ?? name)\n}\n\ndefineSlots<{\n /** Клавиша или сочетание вместо пропа `keys`. */\n default?: () => any\n}>()\n</script>\n\n<template>\n <kbd\n v-if=\"hasCombo\"\n data-gr-kbd\n data-gr-kbd-combo\n :data-variant=\"variant\"\n :class=\"comboClass\"\n ><template\n v-for=\"(key, index) in keyViews\"\n :key=\"`${key.label}-${index}`\"\n ><span\n v-if=\"separatorAt(index)\"\n :class=\"variant === 'sequence' ? sequenceSeparatorClass : separatorClass\"\n aria-hidden=\"true\"\n >{{ separatorAt(index) }}</span><kbd\n data-gr-kbd-key\n :class=\"innerKeyClass\"\n ><span :aria-hidden=\"key.name ? 'true' : undefined\">{{ key.label }}</span><span\n v-if=\"key.name\"\n class=\"sr-only\"\n >{{ readableName(key.name) }}</span></kbd></template></kbd>\n\n <kbd\n v-else\n data-gr-kbd\n data-gr-kbd-key\n :class=\"keyClass\"\n ><slot /></kbd>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, onMounted, ref } from 'vue'\n\nimport { useGrComponentSize } from '../GrConfigProvider/context'\nimport type { GrComponentSize } from '../shared/sizes'\nimport { formatHotkeyTokens, isAppleDevice, splitHotkeyCombo } from '../shared/hotkey'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\n\nimport {\n grKbdComboClass,\n grKbdInnerKeyClass,\n grKbdKeyClass,\n separatorClass,\n sequenceSeparatorClass,\n type GrKbdVariant,\n} from './grKbdStyles'\n\nexport interface GrKbdProps {\n size?: GrKbdSize\n /**\n * `merged` (по умолчанию) — сочетание одной плашкой, как его пишут сами\n * системы: `⌘K` на macOS, `Ctrl+K` на прочих. `split` — по плашке на клавишу.\n * `sequence` — аккорд «G затем I»: клавиши нажимают одну за другой.\n */\n variant?: GrKbdVariant\n /**\n * Сочетание: строкой (`\"mod+shift+K\"`) или набором токенов\n * (`['mod', 'K']`). Токен `mod` — Cmd на macOS, Ctrl на остальных.\n */\n keys?: string | string[]\n /**\n * Разделитель между клавишами. Не задан — авто: в общей плашке символы\n * склеиваются (`⌘K`), а слова разделяются плюсом (`Ctrl+K`); у `split` это\n * плюс, у `sequence` — слово из локали. Пустая строка — только зазор.\n */\n separator?: string\n platform?: GrKbdPlatform\n}\n\n/**\n * GrKbd — GR-примитив для отображения клавиш и сочетаний (`<kbd>`).\n *\n * Одна клавиша — слотом: `<GrKbd>Esc</GrKbd>`.\n * Сочетание — пропом: `<GrKbd keys=\"mod+K\" />` (на macOS `⌘ K`, иначе `Ctrl K`).\n */\nexport type GrKbdSize = GrComponentSize\n\nexport type { GrKbdVariant } from './grKbdStyles'\n\n/** Откуда брать платформу для токена `mod`. */\nexport type GrKbdPlatform = 'auto' | 'apple' | 'other'\n\nconst props = withDefaults(\n defineProps<GrKbdProps>(),\n {\n size: undefined,\n variant: 'merged',\n keys: undefined,\n separator: undefined,\n platform: 'auto',\n },\n)\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrKbd' })\nconst { t } = useGranularityTranslations()\n\n/**\n * Платформа уточняется только после монтирования: `navigator` в теле `setup`\n * либо роняет серверный рендер, либо расходится с ним и ломает гидрацию.\n * До этого момента показываем не-Apple вариант — он же приходит с сервера.\n */\nconst detectedApple = ref(false)\n\nonMounted(() => {\n detectedApple.value = isAppleDevice()\n})\n\nconst isApple = computed(() => {\n if (props.platform === 'apple')\n return true\n if (props.platform === 'other')\n return false\n return detectedApple.value\n})\n\nconst tokens = computed(() => {\n if (props.keys === undefined)\n return []\n return Array.isArray(props.keys) ? props.keys : splitHotkeyCombo(props.keys)\n})\n\n// Сочетание рисуется вложенными `<kbd>` — приём из спецификации HTML, а не\n// обёртка-div: сочетание остаётся клавиатурным вводом целиком.\nconst keyViews = computed(() => formatHotkeyTokens(tokens.value, isApple.value))\nconst hasCombo = computed(() => keyViews.value.length > 0)\n\nconst keyClass = computed(() => grKbdKeyClass(resolvedSize.value))\nconst innerKeyClass = computed(() => grKbdInnerKeyClass(resolvedSize.value, props.variant))\nconst comboClass = computed(() => grKbdComboClass(resolvedSize.value, props.variant))\n\n/**\n * Разделитель перед клавишей `index`.\n *\n * Авто-правило одно: в общей плашке символы склеиваются, слова — нет. Внутри\n * одной платформы набор однороден (macOS даёт `⌘⌥⇧`, прочие — `Ctrl`, `Alt`),\n * поэтому «слева символ» и означает «пишем как система».\n */\nfunction separatorAt(index: number): string {\n if (index === 0)\n return ''\n if (props.separator !== undefined)\n return props.separator\n if (props.variant === 'sequence')\n return t('gr.kbd.then', 'then')\n if (props.variant === 'split')\n return '+'\n\n return keyViews.value[index - 1]?.symbol ? '' : '+'\n}\n\nconst NAME_FALLBACKS: Record<string, string> = {\n command: 'Command',\n option: 'Option',\n shift: 'Shift',\n control: 'Control',\n enter: 'Enter',\n escape: 'Escape',\n tab: 'Tab',\n backspace: 'Backspace',\n delete: 'Delete',\n pageUp: 'Page Up',\n pageDown: 'Page Down',\n arrowUp: 'Arrow Up',\n arrowDown: 'Arrow Down',\n arrowLeft: 'Arrow Left',\n arrowRight: 'Arrow Right',\n}\n\n/** Читаемое имя символьной клавиши: `⌘` без него диктор произносит как знак. */\nfunction readableName(name: string): string {\n return t(`gr.kbd.${name}`, NAME_FALLBACKS[name] ?? name)\n}\n\ndefineSlots<{\n /** Клавиша или сочетание вместо пропа `keys`. */\n default?: () => any\n}>()\n</script>\n\n<template>\n <kbd\n v-if=\"hasCombo\"\n data-gr-kbd\n data-gr-kbd-combo\n :data-variant=\"variant\"\n :class=\"comboClass\"\n ><template\n v-for=\"(key, index) in keyViews\"\n :key=\"`${key.label}-${index}`\"\n ><span\n v-if=\"separatorAt(index)\"\n :class=\"variant === 'sequence' ? sequenceSeparatorClass : separatorClass\"\n aria-hidden=\"true\"\n >{{ separatorAt(index) }}</span><kbd\n data-gr-kbd-key\n :class=\"innerKeyClass\"\n ><span :aria-hidden=\"key.name ? 'true' : undefined\">{{ key.label }}</span><span\n v-if=\"key.name\"\n class=\"sr-only\"\n >{{ readableName(key.name) }}</span></kbd></template></kbd>\n\n <kbd\n v-else\n data-gr-kbd\n data-gr-kbd-key\n :class=\"keyClass\"\n ><slot /></kbd>\n</template>\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as GR_KBD_TOKENS, o as findKbdToken, t as GrKbd_default } from "./chunks/GrKbd-
|
|
2
|
-
import { n as grKbdSafelist, t as grKbdConfig } from "../../chunks/config-
|
|
1
|
+
import { a as GR_KBD_TOKENS, o as findKbdToken, t as GrKbd_default } from "./chunks/GrKbd-BNMeoaLV.js";
|
|
2
|
+
import { n as grKbdSafelist, t as grKbdConfig } from "../../chunks/config-Ca8qr8Q3.js";
|
|
3
3
|
export { GR_KBD_TOKENS, GrKbd_default as GrKbd, GrKbd_default as default, findKbdToken, grKbdConfig, grKbdSafelist };
|
package/dist/components/GrNumberInput/chunks/{GrNumberInput-BqPydXpK.js → GrNumberInput-Bm-V59TG.js}
RENAMED
|
@@ -10,7 +10,7 @@ import { t as bigStep } from "../../../chunks/numericStep-Bq2lj4ew.js";
|
|
|
10
10
|
import { t as chevron_left_default } from "../../../chunks/chevron-left-D8AUBAM7.js";
|
|
11
11
|
import { t as chevron_right_default } from "../../../chunks/chevron-right-C6Z01h0X.js";
|
|
12
12
|
import { a as grNumberInputShellClass, i as grNumberInputInputClass, o as stepperCompactClass, r as clearButtonClass, s as stepperWideClass } from "../../../chunks/config-MmPym-Z7.js";
|
|
13
|
-
import { computed, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, markRaw, mergeProps, normalizeClass, normalizeStyle, onBeforeUnmount, openBlock, ref, renderSlot, unref, withCtx, withModifiers } from "vue";
|
|
13
|
+
import { computed, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, markRaw, mergeProps, normalizeClass, normalizeStyle, onBeforeUnmount, openBlock, ref, renderSlot, unref, watchEffect, withCtx, withModifiers } from "vue";
|
|
14
14
|
//#region src/components/GrNumberInput/numberInputMath.ts
|
|
15
15
|
/**
|
|
16
16
|
* Экспоненциальная запись разбирается отдельно: `String(1e-7)` — это `"1e-7"`,
|
|
@@ -488,6 +488,9 @@ var GrNumberInput_default = /* @__PURE__ */ defineComponent({
|
|
|
488
488
|
emit("clear");
|
|
489
489
|
focus();
|
|
490
490
|
}
|
|
491
|
+
if (process.env.NODE_ENV !== "production") watchEffect(() => {
|
|
492
|
+
if (props.modelValue === void 0) console.warn(`[granularity] GrNumberInput: обязательный проп \`modelValue\` не передан — получено ${String(props.modelValue)}.`);
|
|
493
|
+
});
|
|
491
494
|
return (_ctx, _cache) => {
|
|
492
495
|
return openBlock(), createElementBlock("div", {
|
|
493
496
|
"data-gr-number-input": "",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GrNumberInput-Bm-V59TG.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))\n 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, watchEffect } 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\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() === '')\n 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))\n return null\n return n\n}\n\nfunction clamp(v: number): number {\n if (props.min !== undefined)\n v = Math.max(props.min, v)\n if (props.max !== undefined)\n v = Math.min(props.max, v)\n return v\n}\n\nfunction normalize(v: number): number {\n if (props.precision === undefined)\n 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 !== '.')\n 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)\n 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)\n 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)\n 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)\n return\n // Readonly-поле ведёт себя как текст: стрелки и Home/End отдаются нативной\n // каретке, значение не меняется.\n if (isReadonly.value)\n 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)\n 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)\n 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)\n 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)\n return draft.value\n if (props.modelValue === null)\n return ''\n if (props.useGrouping && !isFocused.value)\n 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)\n 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\nif (__GR_DEV__) {\n watchEffect(() => {\n if (props.modelValue === undefined) {\n console.warn(\n `[granularity] GrNumberInput: обязательный проп \\`modelValue\\` не передан — получено ${String(props.modelValue)}.`,\n )\n }\n })\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, watchEffect } 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\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() === '')\n 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))\n return null\n return n\n}\n\nfunction clamp(v: number): number {\n if (props.min !== undefined)\n v = Math.max(props.min, v)\n if (props.max !== undefined)\n v = Math.min(props.max, v)\n return v\n}\n\nfunction normalize(v: number): number {\n if (props.precision === undefined)\n 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 !== '.')\n 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)\n 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)\n 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)\n 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)\n return\n // Readonly-поле ведёт себя как текст: стрелки и Home/End отдаются нативной\n // каретке, значение не меняется.\n if (isReadonly.value)\n 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)\n 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)\n 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)\n 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)\n return draft.value\n if (props.modelValue === null)\n return ''\n if (props.useGrouping && !isFocused.value)\n 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)\n 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\nif (__GR_DEV__) {\n watchEffect(() => {\n if (props.modelValue === undefined) {\n console.warn(\n `[granularity] GrNumberInput: обязательный проп \\`modelValue\\` не передан — получено ${String(props.modelValue)}.`,\n )\n }\n })\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-Z7.js";
|
|
2
|
-
import { t as GrNumberInput_default } from "./chunks/GrNumberInput-
|
|
2
|
+
import { t as GrNumberInput_default } from "./chunks/GrNumberInput-Bm-V59TG.js";
|
|
3
3
|
export { GrNumberInput_default as GrNumberInput, GrNumberInput_default as default, grNumberInputConfig, grNumberInputSafelist };
|
package/dist/components/GrSegmented/chunks/{GrSegmented-NKTi9bMG.js → GrSegmented-CuDaKA6S.js}
RENAMED
|
@@ -5,7 +5,7 @@ import { useGrFormControl } from "../../../composables/useGrFormControl.js";
|
|
|
5
5
|
import { t as useFocusWithin } from "../../../chunks/useFocusWithin-BwHE0rzm.js";
|
|
6
6
|
import { useRovingFocus } from "../../../composables/useRovingFocus.js";
|
|
7
7
|
import { a as grSegmentedItemIconClass, c as grSegmentedRootClass, i as grSegmentedItemClass, l as grSegmentedRootStyle, o as grSegmentedItemLabelClass, r as grSegmentedIndicatorClass, s as grSegmentedItemSpinnerClass } from "../../../chunks/config-rLkc9Zn2.js";
|
|
8
|
-
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, defineComponent, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, ref, renderList, renderSlot, resolveDynamicComponent, toDisplayString, unref, useId, watch } from "vue";
|
|
8
|
+
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, defineComponent, nextTick, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, ref, renderList, renderSlot, resolveDynamicComponent, toDisplayString, unref, useId, watch, watchEffect } from "vue";
|
|
9
9
|
//#region src/components/shared/truncationTitle.ts
|
|
10
10
|
/**
|
|
11
11
|
* Полный текст обрезанной строки — нативной подсказкой по наведению.
|
|
@@ -328,6 +328,10 @@ var GrSegmented_default = /* @__PURE__ */ defineComponent({
|
|
|
328
328
|
observedRoot = null;
|
|
329
329
|
observedItem = null;
|
|
330
330
|
});
|
|
331
|
+
if (process.env.NODE_ENV !== "production") watchEffect(() => {
|
|
332
|
+
if (props.modelValue === void 0) console.warn(`[granularity] GrSegmented: обязательный проп \`modelValue\` не передан — получено ${String(props.modelValue)}.`);
|
|
333
|
+
if (!Array.isArray(props.options)) console.warn(`[granularity] GrSegmented: обязательный проп \`options\` должен быть массивом — получено ${String(props.options)}.`);
|
|
334
|
+
});
|
|
331
335
|
return (_ctx, _cache) => {
|
|
332
336
|
return openBlock(), createElementBlock("div", {
|
|
333
337
|
id: fieldId.value,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GrSegmented-CuDaKA6S.js","names":[],"sources":["../../../../src/components/shared/truncationTitle.ts","../../../../src/components/GrSegmented/GrSegmented.vue","../../../../src/components/GrSegmented/GrSegmented.vue"],"sourcesContent":["/**\n * Полный текст обрезанной строки — нативной подсказкой по наведению.\n *\n * `truncate` прячет хвост **визуально**: в DOM и для скринридера подпись\n * остаётся целой, поэтому теряет её ровно один читатель — тот, кто смотрит\n * глазами. Он видит «Включе…» и узнать остальное не может ничем.\n *\n * Подсказка ставится **по факту обрезки**, а не всегда: тултип, дублирующий\n * видимую целиком подпись, — шум, которого никто не просил. Замер делается на\n * самом наведении, поэтому ни наблюдателей, ни подписок заводить не нужно.\n *\n * Ширина сравнивается с запасом в пиксель: браузеры считают `scrollWidth`\n * округлением вверх, и у необрезанной строки он бывает на единицу больше\n * `clientWidth` — без запаса подсказка появлялась бы на ровном месте.\n */\nexport function titleWhenTruncated(event: Event): void {\n const element = event.currentTarget\n\n if (!(element instanceof HTMLElement))\n return\n\n // У поля ввода видимая строка живёт в `value`, а не в содержимом узла.\n const source = element instanceof HTMLInputElement ? element.value : element.textContent\n const text = source?.trim() ?? ''\n\n if (text && element.scrollWidth > element.clientWidth + 1)\n element.setAttribute('title', text)\n else\n element.removeAttribute('title')\n}\n","<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, useId, watch, watchEffect } from 'vue'\n\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { useFocusWithin } from '../../composables/internal/useFocusWithin'\nimport { titleWhenTruncated } from '../shared/truncationTitle'\n\nimport type { ComponentPublicInstance } from 'vue'\nimport IconLoader from '~icons/lucide/loader-circle'\n\nimport {\n grSegmentedIndicatorClass,\n grSegmentedItemClass,\n grSegmentedItemIconClass,\n grSegmentedItemLabelClass,\n grSegmentedItemSpinnerClass,\n grSegmentedRootClass,\n grSegmentedRootStyle,\n type GrSegmentedOption,\n type GrSegmentedOrientation,\n type GrSegmentedSize,\n type GrSegmentedValue,\n type GrSegmentedVariant,\n} from './grSegmentedStyles'\n\ntype IndicatorGeometry = {\n width: number\n height: number\n x: number\n y: number\n}\n\n/**\n * Пропсы публичного GR-примитива «Segmented».\n */\nexport interface GrSegmentedProps {\n modelValue: GrSegmentedValue\n options: GrSegmentedOption[]\n variant?: GrSegmentedVariant\n size?: GrSegmentedSize\n /**\n * Направление ряда. Вертикаль — боковые фильтры; индикатор к ней готов по\n * построению, он двумерный.\n */\n orientation?: GrSegmentedOrientation\n /** Длительность анимации индикатора в мс. */\n indicatorDuration?: number\n /** Растягивать сегмент на всю ширину контейнера. */\n block?: boolean\n disabled?: boolean\n /** Только для чтения: выбор видно, но он не меняется. */\n readonly?: boolean\n /** Визуальное и ARIA-состояние ошибки. */\n invalid?: boolean\n /** Обязательное поле (`aria-required`). */\n required?: boolean\n /** Имя скрытого поля, которым выбранное значение уходит в нативную форму. */\n name?: string\n ariaLabel?: string\n}\n\nexport interface GrSegmentedEmits {\n (e: 'update:modelValue', value: GrSegmentedValue): void\n (e: 'change', value: GrSegmentedValue, option: GrSegmentedOption): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrSegmentedProps>(),\n {\n variant: undefined,\n size: undefined,\n orientation: 'horizontal',\n indicatorDuration: 300,\n block: false,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n name: undefined,\n ariaLabel: undefined,\n },\n)\n\n// Эффективный размер: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrSegmented' })\nconst resolvedVariant = useGrComponentProp('GrSegmented', 'variant', () => props.variant, 'pills')\n\nconst emit = defineEmits<GrSegmentedEmits>()\ndefineSlots<{\n /** Содержимое сегмента вместо подписи из `options`. */\n default?: (props: {\n option: GrSegmentedOption\n selected: boolean\n disabled: boolean\n loading: boolean\n }) => any\n}>()\n\n// `useId()` стабилен между сервером и клиентом, в отличие от `instance.uid`.\nconst fallbackName = `gr-segmented-${useId()}`\n\n// Контекст `GrFormField` + общий контракт форм-контрола.\nconst field = useGrFormFieldContext()\nconst fieldId = computed(() => field?.id.value)\nconst describedBy = computed(() => field?.describedById.value)\nconst labelledBy = computed(() => (props.ariaLabel ? undefined : field?.labelId.value))\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n} = useGrFormControl(() => props)\n\nconst rootRef = ref<HTMLElement | null>(null)\n\n// Фокус ходит между сегментами: без границы каждая стрелка давала бы\n// потребителю пару `blur` + `focus`.\nconst { onFocusIn, onFocusOut } = useFocusWithin(rootRef, {\n enter: event => emit('focus', event),\n leave: event => emit('blur', event),\n})\n\ndefineExpose({ focus, blur })\nconst itemRefs = ref(new Map<string, HTMLElement>())\nconst indicatorGeometry = ref<IndicatorGeometry | null>(null)\nconst indicatorReady = ref(false)\n\nlet resizeObserver: ResizeObserver | null = null\nlet observedRoot: HTMLElement | null = null\nlet observedItem: HTMLElement | null = null\nlet scheduled = false\n\nconst selectedIndex = computed(() => props.options.findIndex(option => option.value === props.modelValue))\nconst selectedOption = computed(() => props.options[selectedIndex.value] ?? null)\nconst indicatorDuration = computed(() => Math.max(0, Math.round(props.indicatorDuration)))\nconst isVertical = computed(() => props.orientation === 'vertical')\nconst rootClassName = computed(() => grSegmentedRootClass({\n variant: resolvedVariant.value,\n orientation: props.orientation,\n block: props.block,\n disabled: isDisabled.value,\n}))\n\n/**\n * Раскладка задаётся треками, а не классами: число сегментов известно только в\n * рантайме. По вертикали колонка одна — сегменты одинаковой ширины по\n * построению, и `block` решает лишь, занимать ли ширину контейнера.\n */\nconst rootStyle = computed<Record<string, string>>(() => {\n const empty = props.options.length === 0\n const track = () => props.block && !isVertical.value ? 'minmax(0,1fr)' : 'minmax(0,max-content)'\n\n return {\n ...grSegmentedRootStyle({\n variant: resolvedVariant.value,\n size: resolvedSize.value,\n orientation: props.orientation,\n }),\n gridTemplateColumns: isVertical.value\n ? 'minmax(0,1fr)'\n : empty ? 'none' : props.options.map(track).join(' '),\n ...(isVertical.value\n ? { gridTemplateRows: empty ? 'none' : props.options.map(track).join(' ') }\n : {}),\n }\n})\nconst indicatorClassName = computed(() => grSegmentedIndicatorClass(resolvedVariant.value))\nconst resolvedName = computed(() => props.name ?? fallbackName)\nconst hasIndicator = computed(() => selectedIndex.value !== -1 && indicatorGeometry.value !== null)\n\nconst indicatorStyle = computed<Record<string, string>>(() => {\n const geometry = indicatorGeometry.value\n if (!geometry) {\n return {\n opacity: '0',\n width: '0px',\n height: '0px',\n transitionDuration: `${indicatorDuration.value}ms`,\n transform: 'translate3d(0px, 0px, 0px)',\n }\n }\n\n return {\n opacity: indicatorReady.value ? '1' : '0',\n width: `${geometry.width}px`,\n height: `${geometry.height}px`,\n transitionDuration: `${indicatorDuration.value}ms`,\n transform: `translate3d(${geometry.x}px, ${geometry.y}px, 0)`,\n }\n})\n\nfunction getOptionKey(value: GrSegmentedValue): string {\n return String(value)\n}\n\nfunction resolveOptionDisabled(option: GrSegmentedOption): boolean {\n return isDisabled.value || Boolean(option.disabled)\n}\n\n/**\n * Занятый сегмент не «недоступен», а работает: нативный `disabled` и\n * `aria-disabled` ему не ставим — состояние несёт `aria-busy`. Но выбор он не\n * принимает, поэтому блокировка считается отдельно от `disabled`.\n */\nfunction isOptionBusy(option: GrSegmentedOption): boolean {\n return Boolean(option.loading)\n}\n\nfunction isOptionBlocked(option: GrSegmentedOption): boolean {\n return resolveOptionDisabled(option) || isOptionBusy(option)\n}\n\nfunction isOptionSelected(option: GrSegmentedOption): boolean {\n return option.value === props.modelValue\n}\n\nfunction isIconOnlyOption(option: GrSegmentedOption): boolean {\n return Boolean(option.icon) && !option.label\n}\n\nfunction setItemRef(value: GrSegmentedValue, element: Element | ComponentPublicInstance | null): void {\n const key = getOptionKey(value)\n\n if (!(element instanceof HTMLElement)) {\n itemRefs.value.delete(key)\n return\n }\n\n itemRefs.value.set(key, element)\n}\n\nfunction isSameGeometry(left: IndicatorGeometry | null, right: IndicatorGeometry | null): boolean {\n if (left === right) {\n return true\n }\n\n if (!left || !right) {\n return false\n }\n\n return left.width === right.width\n && left.height === right.height\n && left.x === right.x\n && left.y === right.y\n}\n\nfunction measureIndicator(): void {\n const root = rootRef.value\n const option = selectedOption.value\n if (!root || !option) {\n if (indicatorGeometry.value !== null)\n indicatorGeometry.value = null\n\n if (indicatorReady.value)\n indicatorReady.value = false\n\n return\n }\n\n const selectedItem = itemRefs.value.get(getOptionKey(option.value))\n if (!selectedItem) {\n return\n }\n\n const rootRect = root.getBoundingClientRect()\n const itemRect = selectedItem.getBoundingClientRect()\n\n const nextGeometry = {\n width: Math.max(Math.round(itemRect.width), 0),\n height: Math.max(Math.round(itemRect.height), 0),\n x: Math.round(itemRect.left - rootRect.left),\n y: Math.max(Math.round(itemRect.top - rootRect.top) - 1, 0),\n } satisfies IndicatorGeometry\n\n if (!isSameGeometry(indicatorGeometry.value, nextGeometry)) {\n indicatorGeometry.value = nextGeometry\n }\n\n const nextReady = nextGeometry.width > 0 && nextGeometry.height > 0\n if (indicatorReady.value !== nextReady) {\n indicatorReady.value = nextReady\n }\n}\n\nfunction refreshObserver(): void {\n if (typeof ResizeObserver === 'undefined') {\n return\n }\n\n const root = rootRef.value\n const option = selectedOption.value\n const selectedItem = option\n ? itemRefs.value.get(getOptionKey(option.value)) ?? null\n : null\n\n if (!resizeObserver) {\n resizeObserver = new ResizeObserver(() => scheduleMeasure())\n }\n\n if (!root) {\n resizeObserver.disconnect()\n observedRoot = null\n observedItem = null\n return\n }\n\n if (observedRoot === root && observedItem === selectedItem) {\n return\n }\n\n resizeObserver.disconnect()\n resizeObserver.observe(root)\n\n if (selectedItem) {\n resizeObserver.observe(selectedItem)\n }\n\n observedRoot = root\n observedItem = selectedItem\n}\n\nfunction scheduleMeasure(): void {\n if (scheduled) {\n return\n }\n\n scheduled = true\n void nextTick(() => {\n scheduled = false\n measureIndicator()\n refreshObserver()\n })\n}\n\nfunction emitValue(option: GrSegmentedOption): void {\n // Один guard на все пути: проверяй `readonly` только клавиатура, и клик по\n // сегменту менял бы значение вопреки заявленному контракту.\n if (isReadonly.value || isOptionBlocked(option) || option.value === props.modelValue) {\n return\n }\n\n emit('update:modelValue', option.value)\n emit('change', option.value, option)\n}\n\nfunction optionOf(value: GrSegmentedValue): GrSegmentedOption | undefined {\n return props.options.find(option => option.value === value)\n}\n\n/**\n * Кольцо roving-фокуса. Обе оси работают всегда: `orientation` у сегмента\n * описывает раскладку и уходит в `aria-orientation`, а не сужает клавиатуру —\n * вертикальный набор листается и стрелками влево/вправо.\n */\nconst roving = useRovingFocus<GrSegmentedValue>({\n items: () => props.options.map(option => option.value),\n elementFor: value => itemRefs.value.get(getOptionKey(value)),\n isDisabled: (value) => {\n const option = optionOf(value)\n return option ? isOptionBlocked(option) : true\n },\n orientation: () => 'both',\n skipDisabled: () => true,\n /**\n * Выбранный сегмент держит остановку `Tab` — кроме выключенного: на нём\n * стоит нативный `disabled`, и фокус на него не встанет вовсе. Загружающийся\n * остановку сохраняет: он не «недоступен», а занят.\n */\n initialKey: () => {\n const selected = selectedOption.value\n return selected && !resolveOptionDisabled(selected) ? selected.value : undefined\n },\n // Стрелка в `radiogroup` переносит и выбор, а не только фокус.\n onMove: (value) => {\n const option = optionOf(value)\n if (option)\n emitValue(option)\n },\n})\n\nfunction onItemClick(option: GrSegmentedOption): void {\n emitValue(option)\n // Клик тоже переносит остановку `Tab`: иначе она осталась бы там, куда её\n // увела последняя стрелка.\n if (!isOptionBlocked(option))\n roving.setActive(option.value)\n}\n\n/** Императивный фокус ведёт на текущую остановку `Tab`, а не на первый сегмент. */\nfunction rovingElement(): HTMLElement | undefined {\n const key = roving.rovingKey.value\n return key === undefined ? undefined : itemRefs.value.get(getOptionKey(key))\n}\n\nfunction focus(): void {\n rovingElement()?.focus()\n}\n\nfunction blur(): void {\n rovingElement()?.blur()\n}\n\nfunction onKeydown(event: KeyboardEvent, index: number): void {\n if (isDisabled.value || isReadonly.value) {\n return\n }\n\n if (roving.handleNavigationKeys(event)) {\n return\n }\n\n if (event.key === ' ' || event.key === 'Enter') {\n event.preventDefault()\n const option = props.options[index]\n if (option) {\n emitValue(option)\n }\n }\n}\n\nwatch(() => props.modelValue, () => scheduleMeasure())\nwatch(() => props.options, () => scheduleMeasure(), { deep: true })\nwatch(resolvedVariant, () => scheduleMeasure())\nwatch(resolvedSize, () => scheduleMeasure())\nwatch(() => props.block, () => scheduleMeasure())\n// Смена ориентации переставляет сегменты: без пересчёта индикатор остался бы в\n// координатах прежней раскладки.\nwatch(() => props.orientation, () => scheduleMeasure())\n\nonMounted(() => scheduleMeasure())\nonBeforeUnmount(() => {\n resizeObserver?.disconnect()\n observedRoot = null\n observedItem = null\n})\n\nif (__GR_DEV__) {\n watchEffect(() => {\n if (props.modelValue === undefined) {\n console.warn(\n `[granularity] GrSegmented: обязательный проп \\`modelValue\\` не передан — получено ${String(props.modelValue)}.`,\n )\n }\n\n if (!Array.isArray(props.options)) {\n console.warn(\n `[granularity] GrSegmented: обязательный проп \\`options\\` должен быть массивом — получено ${String(props.options)}.`,\n )\n }\n })\n}\n</script>\n\n<template>\n <div\n :id=\"fieldId\"\n ref=\"rootRef\"\n data-gr-segmented\n :data-variant=\"resolvedVariant\"\n role=\"radiogroup\"\n :aria-orientation=\"orientation\"\n :aria-label=\"ariaLabel\"\n :aria-labelledby=\"labelledBy\"\n :aria-describedby=\"describedBy\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-readonly=\"isReadonly ? 'true' : undefined\"\n :aria-disabled=\"isDisabled ? 'true' : undefined\"\n :class=\"rootClassName\"\n :style=\"rootStyle\"\n @focusin=\"onFocusIn\"\n @focusout=\"onFocusOut\"\n >\n <!-- Значение для нативной формы уходит одним скрытым полем рядом с\n сегментами, а не вложенным в каждый `role=\"radio\"`: роль объявляет своих\n потомков презентационными, и вложенный интерактивный контрол ломает\n виджет для скринридеров (axe: `nested-interactive`). Скрытое поле не\n фокусируется и интерактивным не считается. -->\n <input\n v-if=\"selectedOption && !resolveOptionDisabled(selectedOption)\"\n type=\"hidden\"\n :name=\"resolvedName\"\n :value=\"String(selectedOption.value)\"\n >\n\n <span\n v-if=\"hasIndicator\"\n data-gr-segmented-indicator\n aria-hidden=\"true\"\n :class=\"indicatorClassName\"\n :style=\"indicatorStyle\"\n />\n\n <button\n v-for=\"(option, index) in options\"\n :key=\"option.value\"\n :ref=\"element => setItemRef(option.value, element)\"\n data-gr-segmented-item\n :data-value=\"String(option.value)\"\n type=\"button\"\n role=\"radio\"\n :aria-label=\"option.ariaLabel\"\n :aria-checked=\"isOptionSelected(option) ? 'true' : 'false'\"\n :aria-disabled=\"resolveOptionDisabled(option) ? 'true' : undefined\"\n :aria-busy=\"isOptionBusy(option) ? 'true' : undefined\"\n :disabled=\"resolveOptionDisabled(option)\"\n :tabindex=\"roving.tabindexFor(option.value)\"\n :class=\"grSegmentedItemClass({\n variant: resolvedVariant,\n selected: isOptionSelected(option),\n disabled: resolveOptionDisabled(option),\n iconOnly: isIconOnlyOption(option),\n })\"\n @click=\"onItemClick(option)\"\n @keydown=\"onKeydown($event, index)\"\n >\n <slot\n :option=\"option\"\n :selected=\"isOptionSelected(option)\"\n :disabled=\"resolveOptionDisabled(option)\"\n :loading=\"isOptionBusy(option)\"\n >\n <!-- Спиннер занимает место иконки: две крутилки рядом читались бы как ошибка. -->\n <IconLoader\n v-if=\"isOptionBusy(option)\"\n data-gr-segmented-spinner\n aria-hidden=\"true\"\n :class=\"grSegmentedItemSpinnerClass\"\n />\n <component\n :is=\"option.icon\"\n v-else-if=\"option.icon\"\n aria-hidden=\"true\"\n :class=\"grSegmentedItemIconClass\"\n />\n <!-- Подпись сегмента обрезается по ширине трека, и «Включе…» без\n подсказки прочитать нечем: полный текст отдаётся по наведению. -->\n <span\n v-if=\"option.label\"\n :class=\"grSegmentedItemLabelClass\"\n @pointerenter=\"titleWhenTruncated\"\n >\n {{ option.label }}\n </span>\n </slot>\n </button>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, useId, watch, watchEffect } from 'vue'\n\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useRovingFocus } from '../../composables/useRovingFocus'\nimport { useFocusWithin } from '../../composables/internal/useFocusWithin'\nimport { titleWhenTruncated } from '../shared/truncationTitle'\n\nimport type { ComponentPublicInstance } from 'vue'\nimport IconLoader from '~icons/lucide/loader-circle'\n\nimport {\n grSegmentedIndicatorClass,\n grSegmentedItemClass,\n grSegmentedItemIconClass,\n grSegmentedItemLabelClass,\n grSegmentedItemSpinnerClass,\n grSegmentedRootClass,\n grSegmentedRootStyle,\n type GrSegmentedOption,\n type GrSegmentedOrientation,\n type GrSegmentedSize,\n type GrSegmentedValue,\n type GrSegmentedVariant,\n} from './grSegmentedStyles'\n\ntype IndicatorGeometry = {\n width: number\n height: number\n x: number\n y: number\n}\n\n/**\n * Пропсы публичного GR-примитива «Segmented».\n */\nexport interface GrSegmentedProps {\n modelValue: GrSegmentedValue\n options: GrSegmentedOption[]\n variant?: GrSegmentedVariant\n size?: GrSegmentedSize\n /**\n * Направление ряда. Вертикаль — боковые фильтры; индикатор к ней готов по\n * построению, он двумерный.\n */\n orientation?: GrSegmentedOrientation\n /** Длительность анимации индикатора в мс. */\n indicatorDuration?: number\n /** Растягивать сегмент на всю ширину контейнера. */\n block?: boolean\n disabled?: boolean\n /** Только для чтения: выбор видно, но он не меняется. */\n readonly?: boolean\n /** Визуальное и ARIA-состояние ошибки. */\n invalid?: boolean\n /** Обязательное поле (`aria-required`). */\n required?: boolean\n /** Имя скрытого поля, которым выбранное значение уходит в нативную форму. */\n name?: string\n ariaLabel?: string\n}\n\nexport interface GrSegmentedEmits {\n (e: 'update:modelValue', value: GrSegmentedValue): void\n (e: 'change', value: GrSegmentedValue, option: GrSegmentedOption): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrSegmentedProps>(),\n {\n variant: undefined,\n size: undefined,\n orientation: 'horizontal',\n indicatorDuration: 300,\n block: false,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n name: undefined,\n ariaLabel: undefined,\n },\n)\n\n// Эффективный размер: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrSegmented' })\nconst resolvedVariant = useGrComponentProp('GrSegmented', 'variant', () => props.variant, 'pills')\n\nconst emit = defineEmits<GrSegmentedEmits>()\ndefineSlots<{\n /** Содержимое сегмента вместо подписи из `options`. */\n default?: (props: {\n option: GrSegmentedOption\n selected: boolean\n disabled: boolean\n loading: boolean\n }) => any\n}>()\n\n// `useId()` стабилен между сервером и клиентом, в отличие от `instance.uid`.\nconst fallbackName = `gr-segmented-${useId()}`\n\n// Контекст `GrFormField` + общий контракт форм-контрола.\nconst field = useGrFormFieldContext()\nconst fieldId = computed(() => field?.id.value)\nconst describedBy = computed(() => field?.describedById.value)\nconst labelledBy = computed(() => (props.ariaLabel ? undefined : field?.labelId.value))\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n} = useGrFormControl(() => props)\n\nconst rootRef = ref<HTMLElement | null>(null)\n\n// Фокус ходит между сегментами: без границы каждая стрелка давала бы\n// потребителю пару `blur` + `focus`.\nconst { onFocusIn, onFocusOut } = useFocusWithin(rootRef, {\n enter: event => emit('focus', event),\n leave: event => emit('blur', event),\n})\n\ndefineExpose({ focus, blur })\nconst itemRefs = ref(new Map<string, HTMLElement>())\nconst indicatorGeometry = ref<IndicatorGeometry | null>(null)\nconst indicatorReady = ref(false)\n\nlet resizeObserver: ResizeObserver | null = null\nlet observedRoot: HTMLElement | null = null\nlet observedItem: HTMLElement | null = null\nlet scheduled = false\n\nconst selectedIndex = computed(() => props.options.findIndex(option => option.value === props.modelValue))\nconst selectedOption = computed(() => props.options[selectedIndex.value] ?? null)\nconst indicatorDuration = computed(() => Math.max(0, Math.round(props.indicatorDuration)))\nconst isVertical = computed(() => props.orientation === 'vertical')\nconst rootClassName = computed(() => grSegmentedRootClass({\n variant: resolvedVariant.value,\n orientation: props.orientation,\n block: props.block,\n disabled: isDisabled.value,\n}))\n\n/**\n * Раскладка задаётся треками, а не классами: число сегментов известно только в\n * рантайме. По вертикали колонка одна — сегменты одинаковой ширины по\n * построению, и `block` решает лишь, занимать ли ширину контейнера.\n */\nconst rootStyle = computed<Record<string, string>>(() => {\n const empty = props.options.length === 0\n const track = () => props.block && !isVertical.value ? 'minmax(0,1fr)' : 'minmax(0,max-content)'\n\n return {\n ...grSegmentedRootStyle({\n variant: resolvedVariant.value,\n size: resolvedSize.value,\n orientation: props.orientation,\n }),\n gridTemplateColumns: isVertical.value\n ? 'minmax(0,1fr)'\n : empty ? 'none' : props.options.map(track).join(' '),\n ...(isVertical.value\n ? { gridTemplateRows: empty ? 'none' : props.options.map(track).join(' ') }\n : {}),\n }\n})\nconst indicatorClassName = computed(() => grSegmentedIndicatorClass(resolvedVariant.value))\nconst resolvedName = computed(() => props.name ?? fallbackName)\nconst hasIndicator = computed(() => selectedIndex.value !== -1 && indicatorGeometry.value !== null)\n\nconst indicatorStyle = computed<Record<string, string>>(() => {\n const geometry = indicatorGeometry.value\n if (!geometry) {\n return {\n opacity: '0',\n width: '0px',\n height: '0px',\n transitionDuration: `${indicatorDuration.value}ms`,\n transform: 'translate3d(0px, 0px, 0px)',\n }\n }\n\n return {\n opacity: indicatorReady.value ? '1' : '0',\n width: `${geometry.width}px`,\n height: `${geometry.height}px`,\n transitionDuration: `${indicatorDuration.value}ms`,\n transform: `translate3d(${geometry.x}px, ${geometry.y}px, 0)`,\n }\n})\n\nfunction getOptionKey(value: GrSegmentedValue): string {\n return String(value)\n}\n\nfunction resolveOptionDisabled(option: GrSegmentedOption): boolean {\n return isDisabled.value || Boolean(option.disabled)\n}\n\n/**\n * Занятый сегмент не «недоступен», а работает: нативный `disabled` и\n * `aria-disabled` ему не ставим — состояние несёт `aria-busy`. Но выбор он не\n * принимает, поэтому блокировка считается отдельно от `disabled`.\n */\nfunction isOptionBusy(option: GrSegmentedOption): boolean {\n return Boolean(option.loading)\n}\n\nfunction isOptionBlocked(option: GrSegmentedOption): boolean {\n return resolveOptionDisabled(option) || isOptionBusy(option)\n}\n\nfunction isOptionSelected(option: GrSegmentedOption): boolean {\n return option.value === props.modelValue\n}\n\nfunction isIconOnlyOption(option: GrSegmentedOption): boolean {\n return Boolean(option.icon) && !option.label\n}\n\nfunction setItemRef(value: GrSegmentedValue, element: Element | ComponentPublicInstance | null): void {\n const key = getOptionKey(value)\n\n if (!(element instanceof HTMLElement)) {\n itemRefs.value.delete(key)\n return\n }\n\n itemRefs.value.set(key, element)\n}\n\nfunction isSameGeometry(left: IndicatorGeometry | null, right: IndicatorGeometry | null): boolean {\n if (left === right) {\n return true\n }\n\n if (!left || !right) {\n return false\n }\n\n return left.width === right.width\n && left.height === right.height\n && left.x === right.x\n && left.y === right.y\n}\n\nfunction measureIndicator(): void {\n const root = rootRef.value\n const option = selectedOption.value\n if (!root || !option) {\n if (indicatorGeometry.value !== null)\n indicatorGeometry.value = null\n\n if (indicatorReady.value)\n indicatorReady.value = false\n\n return\n }\n\n const selectedItem = itemRefs.value.get(getOptionKey(option.value))\n if (!selectedItem) {\n return\n }\n\n const rootRect = root.getBoundingClientRect()\n const itemRect = selectedItem.getBoundingClientRect()\n\n const nextGeometry = {\n width: Math.max(Math.round(itemRect.width), 0),\n height: Math.max(Math.round(itemRect.height), 0),\n x: Math.round(itemRect.left - rootRect.left),\n y: Math.max(Math.round(itemRect.top - rootRect.top) - 1, 0),\n } satisfies IndicatorGeometry\n\n if (!isSameGeometry(indicatorGeometry.value, nextGeometry)) {\n indicatorGeometry.value = nextGeometry\n }\n\n const nextReady = nextGeometry.width > 0 && nextGeometry.height > 0\n if (indicatorReady.value !== nextReady) {\n indicatorReady.value = nextReady\n }\n}\n\nfunction refreshObserver(): void {\n if (typeof ResizeObserver === 'undefined') {\n return\n }\n\n const root = rootRef.value\n const option = selectedOption.value\n const selectedItem = option\n ? itemRefs.value.get(getOptionKey(option.value)) ?? null\n : null\n\n if (!resizeObserver) {\n resizeObserver = new ResizeObserver(() => scheduleMeasure())\n }\n\n if (!root) {\n resizeObserver.disconnect()\n observedRoot = null\n observedItem = null\n return\n }\n\n if (observedRoot === root && observedItem === selectedItem) {\n return\n }\n\n resizeObserver.disconnect()\n resizeObserver.observe(root)\n\n if (selectedItem) {\n resizeObserver.observe(selectedItem)\n }\n\n observedRoot = root\n observedItem = selectedItem\n}\n\nfunction scheduleMeasure(): void {\n if (scheduled) {\n return\n }\n\n scheduled = true\n void nextTick(() => {\n scheduled = false\n measureIndicator()\n refreshObserver()\n })\n}\n\nfunction emitValue(option: GrSegmentedOption): void {\n // Один guard на все пути: проверяй `readonly` только клавиатура, и клик по\n // сегменту менял бы значение вопреки заявленному контракту.\n if (isReadonly.value || isOptionBlocked(option) || option.value === props.modelValue) {\n return\n }\n\n emit('update:modelValue', option.value)\n emit('change', option.value, option)\n}\n\nfunction optionOf(value: GrSegmentedValue): GrSegmentedOption | undefined {\n return props.options.find(option => option.value === value)\n}\n\n/**\n * Кольцо roving-фокуса. Обе оси работают всегда: `orientation` у сегмента\n * описывает раскладку и уходит в `aria-orientation`, а не сужает клавиатуру —\n * вертикальный набор листается и стрелками влево/вправо.\n */\nconst roving = useRovingFocus<GrSegmentedValue>({\n items: () => props.options.map(option => option.value),\n elementFor: value => itemRefs.value.get(getOptionKey(value)),\n isDisabled: (value) => {\n const option = optionOf(value)\n return option ? isOptionBlocked(option) : true\n },\n orientation: () => 'both',\n skipDisabled: () => true,\n /**\n * Выбранный сегмент держит остановку `Tab` — кроме выключенного: на нём\n * стоит нативный `disabled`, и фокус на него не встанет вовсе. Загружающийся\n * остановку сохраняет: он не «недоступен», а занят.\n */\n initialKey: () => {\n const selected = selectedOption.value\n return selected && !resolveOptionDisabled(selected) ? selected.value : undefined\n },\n // Стрелка в `radiogroup` переносит и выбор, а не только фокус.\n onMove: (value) => {\n const option = optionOf(value)\n if (option)\n emitValue(option)\n },\n})\n\nfunction onItemClick(option: GrSegmentedOption): void {\n emitValue(option)\n // Клик тоже переносит остановку `Tab`: иначе она осталась бы там, куда её\n // увела последняя стрелка.\n if (!isOptionBlocked(option))\n roving.setActive(option.value)\n}\n\n/** Императивный фокус ведёт на текущую остановку `Tab`, а не на первый сегмент. */\nfunction rovingElement(): HTMLElement | undefined {\n const key = roving.rovingKey.value\n return key === undefined ? undefined : itemRefs.value.get(getOptionKey(key))\n}\n\nfunction focus(): void {\n rovingElement()?.focus()\n}\n\nfunction blur(): void {\n rovingElement()?.blur()\n}\n\nfunction onKeydown(event: KeyboardEvent, index: number): void {\n if (isDisabled.value || isReadonly.value) {\n return\n }\n\n if (roving.handleNavigationKeys(event)) {\n return\n }\n\n if (event.key === ' ' || event.key === 'Enter') {\n event.preventDefault()\n const option = props.options[index]\n if (option) {\n emitValue(option)\n }\n }\n}\n\nwatch(() => props.modelValue, () => scheduleMeasure())\nwatch(() => props.options, () => scheduleMeasure(), { deep: true })\nwatch(resolvedVariant, () => scheduleMeasure())\nwatch(resolvedSize, () => scheduleMeasure())\nwatch(() => props.block, () => scheduleMeasure())\n// Смена ориентации переставляет сегменты: без пересчёта индикатор остался бы в\n// координатах прежней раскладки.\nwatch(() => props.orientation, () => scheduleMeasure())\n\nonMounted(() => scheduleMeasure())\nonBeforeUnmount(() => {\n resizeObserver?.disconnect()\n observedRoot = null\n observedItem = null\n})\n\nif (__GR_DEV__) {\n watchEffect(() => {\n if (props.modelValue === undefined) {\n console.warn(\n `[granularity] GrSegmented: обязательный проп \\`modelValue\\` не передан — получено ${String(props.modelValue)}.`,\n )\n }\n\n if (!Array.isArray(props.options)) {\n console.warn(\n `[granularity] GrSegmented: обязательный проп \\`options\\` должен быть массивом — получено ${String(props.options)}.`,\n )\n }\n })\n}\n</script>\n\n<template>\n <div\n :id=\"fieldId\"\n ref=\"rootRef\"\n data-gr-segmented\n :data-variant=\"resolvedVariant\"\n role=\"radiogroup\"\n :aria-orientation=\"orientation\"\n :aria-label=\"ariaLabel\"\n :aria-labelledby=\"labelledBy\"\n :aria-describedby=\"describedBy\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-readonly=\"isReadonly ? 'true' : undefined\"\n :aria-disabled=\"isDisabled ? 'true' : undefined\"\n :class=\"rootClassName\"\n :style=\"rootStyle\"\n @focusin=\"onFocusIn\"\n @focusout=\"onFocusOut\"\n >\n <!-- Значение для нативной формы уходит одним скрытым полем рядом с\n сегментами, а не вложенным в каждый `role=\"radio\"`: роль объявляет своих\n потомков презентационными, и вложенный интерактивный контрол ломает\n виджет для скринридеров (axe: `nested-interactive`). Скрытое поле не\n фокусируется и интерактивным не считается. -->\n <input\n v-if=\"selectedOption && !resolveOptionDisabled(selectedOption)\"\n type=\"hidden\"\n :name=\"resolvedName\"\n :value=\"String(selectedOption.value)\"\n >\n\n <span\n v-if=\"hasIndicator\"\n data-gr-segmented-indicator\n aria-hidden=\"true\"\n :class=\"indicatorClassName\"\n :style=\"indicatorStyle\"\n />\n\n <button\n v-for=\"(option, index) in options\"\n :key=\"option.value\"\n :ref=\"element => setItemRef(option.value, element)\"\n data-gr-segmented-item\n :data-value=\"String(option.value)\"\n type=\"button\"\n role=\"radio\"\n :aria-label=\"option.ariaLabel\"\n :aria-checked=\"isOptionSelected(option) ? 'true' : 'false'\"\n :aria-disabled=\"resolveOptionDisabled(option) ? 'true' : undefined\"\n :aria-busy=\"isOptionBusy(option) ? 'true' : undefined\"\n :disabled=\"resolveOptionDisabled(option)\"\n :tabindex=\"roving.tabindexFor(option.value)\"\n :class=\"grSegmentedItemClass({\n variant: resolvedVariant,\n selected: isOptionSelected(option),\n disabled: resolveOptionDisabled(option),\n iconOnly: isIconOnlyOption(option),\n })\"\n @click=\"onItemClick(option)\"\n @keydown=\"onKeydown($event, index)\"\n >\n <slot\n :option=\"option\"\n :selected=\"isOptionSelected(option)\"\n :disabled=\"resolveOptionDisabled(option)\"\n :loading=\"isOptionBusy(option)\"\n >\n <!-- Спиннер занимает место иконки: две крутилки рядом читались бы как ошибка. -->\n <IconLoader\n v-if=\"isOptionBusy(option)\"\n data-gr-segmented-spinner\n aria-hidden=\"true\"\n :class=\"grSegmentedItemSpinnerClass\"\n />\n <component\n :is=\"option.icon\"\n v-else-if=\"option.icon\"\n aria-hidden=\"true\"\n :class=\"grSegmentedItemIconClass\"\n />\n <!-- Подпись сегмента обрезается по ширине трека, и «Включе…» без\n подсказки прочитать нечем: полный текст отдаётся по наведению. -->\n <span\n v-if=\"option.label\"\n :class=\"grSegmentedItemLabelClass\"\n @pointerenter=\"titleWhenTruncated\"\n >\n {{ option.label }}\n </span>\n </slot>\n </button>\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { t as GrSegmented_default } from "./chunks/GrSegmented-
|
|
1
|
+
import { t as GrSegmented_default } from "./chunks/GrSegmented-CuDaKA6S.js";
|
|
2
2
|
import { n as grSegmentedSafelist, t as grSegmentedConfig } from "../../chunks/config-rLkc9Zn2.js";
|
|
3
3
|
export { GrSegmented_default as GrSegmented, GrSegmented_default as default, grSegmentedConfig, grSegmentedSafelist };
|
|
@@ -5,7 +5,7 @@ import { useAnnouncer } from "../../../composables/useAnnouncer.js";
|
|
|
5
5
|
import { n as insertionIndex, r as moveItem, t as useDragSort } from "../../../chunks/useDragSort-0l47h4yb.js";
|
|
6
6
|
import { t as grip_vertical_default } from "../../../chunks/grip-vertical-lRha2S7q.js";
|
|
7
7
|
import { a as emptyClass, c as indicatorAfterClass, d as rowDisabledClass, f as rowDraggingClass, h as rowLayoutClass, i as dividedClass, l as indicatorBeforeClass, m as rowGrabbedClass, o as handleClass, p as rowFocusClass, r as contentClass, s as handleDisabledClass, u as listClass } from "../../../chunks/config-CbEbLyDE.js";
|
|
8
|
-
import { Fragment, computed, createBlock, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, nextTick, normalizeClass, normalizeStyle, openBlock, ref, renderList, renderSlot, toDisplayString, unref, withCtx, withModifiers } from "vue";
|
|
8
|
+
import { Fragment, computed, createBlock, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, nextTick, normalizeClass, normalizeStyle, openBlock, ref, renderList, renderSlot, toDisplayString, unref, watchEffect, withCtx, withModifiers } from "vue";
|
|
9
9
|
//#region src/components/GrSortableList/GrSortableList.vue?vue&type=script&setup=true&lang.ts
|
|
10
10
|
var _hoisted_1 = ["aria-label", "aria-disabled"];
|
|
11
11
|
var _hoisted_2 = [
|
|
@@ -213,6 +213,9 @@ var GrSortableList_default = /* @__PURE__ */ defineComponent({
|
|
|
213
213
|
move: (from, to) => applyMove(from, to),
|
|
214
214
|
focusItem: (index) => roving.focusKey(keys.value[index])
|
|
215
215
|
});
|
|
216
|
+
if (process.env.NODE_ENV !== "production") watchEffect(() => {
|
|
217
|
+
if (!Array.isArray(props.modelValue)) console.warn(`[granularity] GrSortableList: обязательный проп \`modelValue\` должен быть массивом — получено ${String(props.modelValue)}.`);
|
|
218
|
+
});
|
|
216
219
|
return (_ctx, _cache) => {
|
|
217
220
|
return openBlock(), createBlock(unref(GrCard_default), { variant: __props.variant }, {
|
|
218
221
|
default: withCtx(() => [createElementVNode("div", {
|