@feugene/granularity 0.29.1 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -6
- package/dist/chunks/{config-DO8mjeIo.js → config-CIkdsWUk.js} +6 -2
- package/dist/chunks/config-CIkdsWUk.js.map +1 -0
- package/dist/chunks/{config-Dpk-eVkS.js → config-DUbbGcfw.js} +12 -1
- package/dist/chunks/config-DUbbGcfw.js.map +1 -0
- package/dist/chunks/{grInputStyles-CAk3xKIb.js → grInputStyles-DgQYjJvD.js} +17 -1
- package/dist/chunks/{grInputStyles-CAk3xKIb.js.map → grInputStyles-DgQYjJvD.js.map} +1 -1
- package/dist/chunks/grSelectStyles-Cdtd10eY.js.map +1 -1
- package/dist/chunks/{granular-provider-NtErwzYB.js → granular-provider-yJNX1g1G.js} +2 -2
- package/dist/chunks/{granular-provider-NtErwzYB.js.map → granular-provider-yJNX1g1G.js.map} +1 -1
- package/dist/chunks/renamedProp-BwcN-SJg.js +33 -0
- package/dist/chunks/renamedProp-BwcN-SJg.js.map +1 -0
- package/dist/components/GrAutocomplete/chunks/{GrAutocomplete-CaHys_A_.js → GrAutocomplete-X6RlmiJQ.js} +1 -1
- package/dist/components/GrAutocomplete/chunks/{GrAutocomplete-CaHys_A_.js.map → GrAutocomplete-X6RlmiJQ.js.map} +1 -1
- package/dist/components/GrAutocomplete/index.js +1 -1
- package/dist/components/GrBadge/chunks/{GrBadge-BM3f9PME.js → GrBadge-BsoocpF-.js} +4 -2
- package/dist/components/GrBadge/chunks/GrBadge-BsoocpF-.js.map +1 -0
- package/dist/components/GrBadge/index.js +1 -1
- package/dist/components/GrBadge/styles.css +1 -1
- package/dist/components/GrCard/chunks/{GrCard-DR3ehBt8.js → GrCard-BttXz1pQ.js} +19 -6
- package/dist/components/GrCard/chunks/GrCard-BttXz1pQ.js.map +1 -0
- package/dist/components/GrCard/index.js +2 -2
- package/dist/components/GrChip/chunks/{GrChip-Df4orxRH.js → GrChip-C0vU69tf.js} +3 -1
- package/dist/components/GrChip/chunks/GrChip-C0vU69tf.js.map +1 -0
- package/dist/components/GrChip/index.js +1 -1
- package/dist/components/GrCollapse/chunks/{GrCollapse-Bw9_JatF.js → GrCollapse-BlW6ybdL.js} +1 -1
- package/dist/components/GrCollapse/chunks/{GrCollapse-Bw9_JatF.js.map → GrCollapse-BlW6ybdL.js.map} +1 -1
- package/dist/components/GrCollapse/index.js +1 -1
- package/dist/components/GrColorPicker/chunks/{GrColorPicker-CE-xtNP-.js → GrColorPicker-C7ifyQ0R.js} +1 -1
- package/dist/components/GrColorPicker/chunks/{GrColorPicker-CE-xtNP-.js.map → GrColorPicker-C7ifyQ0R.js.map} +1 -1
- package/dist/components/GrColorPicker/index.js +1 -1
- package/dist/components/GrContextMenu/chunks/{GrContextMenu-BNQDbAsY.js → GrContextMenu-_FKwQvuQ.js} +1 -1
- package/dist/components/GrContextMenu/chunks/{GrContextMenu-BNQDbAsY.js.map → GrContextMenu-_FKwQvuQ.js.map} +1 -1
- package/dist/components/GrContextMenu/index.js +1 -1
- package/dist/components/GrDialogService/chunks/{GrDialogService-k4SYIv64.js → GrDialogService-6s2cQ-_6.js} +1 -1
- package/dist/components/GrDialogService/chunks/{GrDialogService-k4SYIv64.js.map → GrDialogService-6s2cQ-_6.js.map} +1 -1
- package/dist/components/GrDialogService/index.js +1 -1
- package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-Cg7qHV9J.js → GrDropdownMenu-GOzuAXsi.js} +1 -1
- package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-Cg7qHV9J.js.map → GrDropdownMenu-GOzuAXsi.js.map} +1 -1
- package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenuList-RW5IBvmu.js → GrDropdownMenuList-B33ZzwfB.js} +13 -3
- package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenuList-RW5IBvmu.js.map → GrDropdownMenuList-B33ZzwfB.js.map} +1 -1
- package/dist/components/GrDropdownMenu/index.js +2 -2
- package/dist/components/GrInput/chunks/{GrInput-BqqoEl1b.js → GrInput-BLywRZUN.js} +10 -6
- package/dist/components/GrInput/chunks/GrInput-BLywRZUN.js.map +1 -0
- package/dist/components/GrInput/index.js +2 -2
- package/dist/components/GrInputTag/chunks/{GrInputTag-5wVcB0zG.js → GrInputTag-Cj4A9Xgs.js} +1 -1
- package/dist/components/GrInputTag/chunks/{GrInputTag-5wVcB0zG.js.map → GrInputTag-Cj4A9Xgs.js.map} +1 -1
- package/dist/components/GrInputTag/index.js +1 -1
- package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-CgwjoUJt.js → GrJsonViewer-Cy2bknHO.js} +1 -1
- package/dist/components/GrJsonViewer/chunks/{GrJsonViewer-CgwjoUJt.js.map → GrJsonViewer-Cy2bknHO.js.map} +1 -1
- package/dist/components/GrJsonViewer/index.js +1 -1
- package/dist/components/GrList/chunks/{GrList-DrXrKU37.js → GrList-DmT4qzLO.js} +1 -1
- package/dist/components/GrList/chunks/{GrList-DrXrKU37.js.map → GrList-DmT4qzLO.js.map} +1 -1
- package/dist/components/GrList/index.js +1 -1
- package/dist/components/GrPagination/chunks/{GrPagination-CX9ZV8mR.js → GrPagination-Cw3R21dp.js} +1 -1
- package/dist/components/GrPagination/chunks/{GrPagination-CX9ZV8mR.js.map → GrPagination-Cw3R21dp.js.map} +1 -1
- package/dist/components/GrPagination/index.js +1 -1
- package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-D1ADRFxx.js → GrPromptDialog-DLcl1xiq.js} +1 -1
- package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-D1ADRFxx.js.map → GrPromptDialog-DLcl1xiq.js.map} +1 -1
- package/dist/components/GrPromptDialog/index.js +1 -1
- package/dist/components/GrSelect/chunks/{GrSelect-DKoQu3w9.js → GrSelect-BiUrSSxc.js} +5 -5
- package/dist/components/GrSelect/chunks/GrSelect-BiUrSSxc.js.map +1 -0
- package/dist/components/GrSelect/index.js +1 -1
- package/dist/components/GrSelect/styles.css +1 -1
- package/dist/components/GrSortableList/chunks/{GrSortableList-DGs3Pi2J.js → GrSortableList-HyKX--rO.js} +1 -1
- package/dist/components/GrSortableList/chunks/{GrSortableList-DGs3Pi2J.js.map → GrSortableList-HyKX--rO.js.map} +1 -1
- package/dist/components/GrSortableList/index.js +1 -1
- package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-CymO68Cv.js → GrTreeSelect-iJnMl2t9.js} +1 -1
- package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-CymO68Cv.js.map → GrTreeSelect-iJnMl2t9.js.map} +1 -1
- package/dist/components/GrTreeSelect/index.js +1 -1
- package/dist/granular-provider-node.js +1 -1
- package/dist/granular-provider.js +1 -1
- package/dist/index.js +21 -21
- package/dist/types/components/GrCard/GrCard.vue.d.ts +8 -0
- package/dist/types/components/GrCard/grCardStyles.d.ts +9 -0
- package/dist/types/components/GrDropdownMenu/menuModel.d.ts +20 -2
- package/dist/types/components/GrInput/GrInput.vue.d.ts +16 -0
- package/dist/types/components/GrInput/grInputStyles.d.ts +16 -0
- package/dist/types/components/GrSelect/grSelectStyles.d.ts +12 -0
- package/dist/types/components/shared/renamedProp.d.ts +19 -0
- package/dist/web-types.json +14 -1
- package/package.json +2 -2
- package/dist/chunks/config-DO8mjeIo.js.map +0 -1
- package/dist/chunks/config-Dpk-eVkS.js.map +0 -1
- package/dist/components/GrBadge/chunks/GrBadge-BM3f9PME.js.map +0 -1
- package/dist/components/GrCard/chunks/GrCard-DR3ehBt8.js.map +0 -1
- package/dist/components/GrChip/chunks/GrChip-Df4orxRH.js.map +0 -1
- package/dist/components/GrInput/chunks/GrInput-BqqoEl1b.js.map +0 -1
- package/dist/components/GrSelect/chunks/GrSelect-DKoQu3w9.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GrDropdownMenuList-RW5IBvmu.js","names":[],"sources":["../../../../src/components/GrDropdownMenu/GrDropdownMenuDivider.vue","../../../../src/components/GrDropdownMenu/GrDropdownMenuDivider.vue","../../../../src/components/GrDropdownMenu/GrDropdownMenuHeader.vue","../../../../src/components/GrDropdownMenu/GrDropdownMenuHeader.vue","../../../../src/components/GrDropdownMenu/GrDropdownMenuGroup.vue","../../../../src/components/GrDropdownMenu/GrDropdownMenuGroup.vue","../../../../src/components/GrDropdownMenu/GrDropdownMenuItem.vue","../../../../src/components/GrDropdownMenu/GrDropdownMenuItem.vue","../../../../src/components/GrDropdownMenu/menuModel.ts","../../../../src/components/GrDropdownMenu/GrDropdownMenuEntries.vue","../../../../src/components/GrDropdownMenu/GrDropdownMenuEntries.vue","../../../../src/components/GrDropdownMenu/GrDropdownMenuList.vue","../../../../src/components/GrDropdownMenu/GrDropdownMenuList.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { dividerClass, dividerInsetClass } from './grDropdownMenuStyles'\n\nexport interface GrDropdownMenuDividerProps {\n inset?: boolean\n}\n\nwithDefaults(defineProps<GrDropdownMenuDividerProps>(), {\n inset: false,\n})\n</script>\n\n<template>\n <div\n data-gr-dropdown-menu-divider\n role=\"separator\"\n :class=\"[dividerClass, inset ? dividerInsetClass : '']\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { dividerClass, dividerInsetClass } from './grDropdownMenuStyles'\n\nexport interface GrDropdownMenuDividerProps {\n inset?: boolean\n}\n\nwithDefaults(defineProps<GrDropdownMenuDividerProps>(), {\n inset: false,\n})\n</script>\n\n<template>\n <div\n data-gr-dropdown-menu-divider\n role=\"separator\"\n :class=\"[dividerClass, inset ? dividerInsetClass : '']\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { computed, useSlots } from 'vue'\n\nimport { headerClass, textAlignClass, type GrDropdownMenuHeaderAlign } from './grDropdownMenuStyles'\n\nexport type { GrDropdownMenuHeaderAlign } from './grDropdownMenuStyles'\n\nexport interface GrDropdownMenuHeaderProps {\n title?: string\n align?: GrDropdownMenuHeaderAlign\n uppercase?: boolean\n}\n\nconst props = withDefaults(defineProps<GrDropdownMenuHeaderProps>(), {\n title: undefined,\n align: 'left',\n uppercase: true,\n})\n\n/**\n * Заголовок — подпись, а не пункт меню: `role=\"presentation\"` убирает его из\n * числа потомков `role=\"menu\"`. Имя группе он даёт через `aria-labelledby`\n * у `GrDropdownMenuGroup`.\n */\nconst slots = useSlots()\nconst isVisible = computed(() => Boolean(props.title) || Boolean(slots.default))\n\ndefineSlots<{\n /** Заголовок раздела меню вместо пропа `label`. */\n default?: () => any\n}>()\n\n</script>\n\n<template>\n <div\n v-if=\"isVisible\"\n data-gr-dropdown-menu-header\n role=\"presentation\"\n :class=\"[headerClass, textAlignClass[align], { uppercase }]\"\n >\n <slot>{{ title }}</slot>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, useSlots } from 'vue'\n\nimport { headerClass, textAlignClass, type GrDropdownMenuHeaderAlign } from './grDropdownMenuStyles'\n\nexport type { GrDropdownMenuHeaderAlign } from './grDropdownMenuStyles'\n\nexport interface GrDropdownMenuHeaderProps {\n title?: string\n align?: GrDropdownMenuHeaderAlign\n uppercase?: boolean\n}\n\nconst props = withDefaults(defineProps<GrDropdownMenuHeaderProps>(), {\n title: undefined,\n align: 'left',\n uppercase: true,\n})\n\n/**\n * Заголовок — подпись, а не пункт меню: `role=\"presentation\"` убирает его из\n * числа потомков `role=\"menu\"`. Имя группе он даёт через `aria-labelledby`\n * у `GrDropdownMenuGroup`.\n */\nconst slots = useSlots()\nconst isVisible = computed(() => Boolean(props.title) || Boolean(slots.default))\n\ndefineSlots<{\n /** Заголовок раздела меню вместо пропа `label`. */\n default?: () => any\n}>()\n\n</script>\n\n<template>\n <div\n v-if=\"isVisible\"\n data-gr-dropdown-menu-header\n role=\"presentation\"\n :class=\"[headerClass, textAlignClass[align], { uppercase }]\"\n >\n <slot>{{ title }}</slot>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, useId } from 'vue'\n\nimport GrDropdownMenuHeader from './GrDropdownMenuHeader.vue'\nimport { dividersClass, listBaseClass, type GrDropdownMenuHeaderAlign } from './grDropdownMenuStyles'\n\nexport interface GrDropdownMenuGroupProps {\n title?: string\n titleAlign?: GrDropdownMenuHeaderAlign\n dividers?: boolean\n uppercase?: boolean\n}\n\nconst props = withDefaults(defineProps<GrDropdownMenuGroupProps>(), {\n title: undefined,\n titleAlign: 'left',\n dividers: false,\n uppercase: true,\n})\n\nconst titleId = useId()\n// Имя группы читается диктором при входе в неё; без заголовка называть нечем.\nconst labelledBy = computed(() => (props.title ? titleId : undefined))\n\nconst itemsClass = computed(() => [\n listBaseClass,\n props.dividers ? dividersClass : '',\n].filter(Boolean).join(' '))\n\ndefineSlots<{\n /** Пункты группы. */\n default?: () => any\n}>()\n\n</script>\n\n<template>\n <div\n data-gr-dropdown-menu-group\n role=\"group\"\n :aria-labelledby=\"labelledBy\"\n class=\"w-full\"\n >\n <GrDropdownMenuHeader\n v-if=\"title\"\n :id=\"titleId\"\n :title=\"title\"\n :align=\"titleAlign\"\n :uppercase=\"uppercase\"\n />\n <div role=\"none\" :class=\"itemsClass\">\n <slot />\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, useId } from 'vue'\n\nimport GrDropdownMenuHeader from './GrDropdownMenuHeader.vue'\nimport { dividersClass, listBaseClass, type GrDropdownMenuHeaderAlign } from './grDropdownMenuStyles'\n\nexport interface GrDropdownMenuGroupProps {\n title?: string\n titleAlign?: GrDropdownMenuHeaderAlign\n dividers?: boolean\n uppercase?: boolean\n}\n\nconst props = withDefaults(defineProps<GrDropdownMenuGroupProps>(), {\n title: undefined,\n titleAlign: 'left',\n dividers: false,\n uppercase: true,\n})\n\nconst titleId = useId()\n// Имя группы читается диктором при входе в неё; без заголовка называть нечем.\nconst labelledBy = computed(() => (props.title ? titleId : undefined))\n\nconst itemsClass = computed(() => [\n listBaseClass,\n props.dividers ? dividersClass : '',\n].filter(Boolean).join(' '))\n\ndefineSlots<{\n /** Пункты группы. */\n default?: () => any\n}>()\n\n</script>\n\n<template>\n <div\n data-gr-dropdown-menu-group\n role=\"group\"\n :aria-labelledby=\"labelledBy\"\n class=\"w-full\"\n >\n <GrDropdownMenuHeader\n v-if=\"title\"\n :id=\"titleId\"\n :title=\"title\"\n :align=\"titleAlign\"\n :uppercase=\"uppercase\"\n />\n <div role=\"none\" :class=\"itemsClass\">\n <slot />\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, useAttrs } from 'vue'\nimport type { Component } from 'vue'\n\nimport IconCheck from '~icons/lucide/check'\n\nimport {\n grDropdownMenuItemClass,\n itemIndicatorClass,\n itemShortcutClass,\n type GrDropdownMenuItemAlign,\n type GrDropdownMenuItemVariant,\n} from './grDropdownMenuStyles'\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nexport type { GrDropdownMenuItemAlign, GrDropdownMenuItemVariant } from './grDropdownMenuStyles'\n\n/**\n * Роль пункта. `menuitemcheckbox`/`menuitemradio` — пункты-переключатели: они\n * обязаны нести `aria-checked`, иначе AT прочитает их как обычные команды.\n */\nexport type GrDropdownMenuItemRole = 'menuitem' | 'menuitemcheckbox' | 'menuitemradio'\n\nexport interface GrDropdownMenuItemProps {\n as?: string | Component\n disabled?: boolean\n /** Пункт-ссылка. У выключенного пункта не рендерится: ссылка осталась бы рабочей. */\n href?: string\n target?: string\n rel?: string\n /** Шорткат для `target=\"_blank\"` + `rel=\"noopener noreferrer\"`. */\n external?: boolean\n align?: GrDropdownMenuItemAlign\n variant?: GrDropdownMenuItemVariant\n role?: GrDropdownMenuItemRole\n /** Состояние переключателя. Осмысленно с `role=\"menuitemcheckbox|radio\"`. */\n checked?: boolean\n /** Иконка слева. Слот `#icon` сильнее. */\n icon?: Component\n /** Подпись сочетания клавиш справа. Слот `#shortcut` сильнее. */\n shortcut?: string\n}\n\nconst props = withDefaults(defineProps<GrDropdownMenuItemProps>(), {\n as: 'button',\n disabled: false,\n href: undefined,\n target: undefined,\n rel: undefined,\n external: false,\n align: 'left',\n variant: 'default',\n role: 'menuitem',\n checked: undefined,\n icon: undefined,\n shortcut: undefined,\n})\n\nconst attrs = useAttrs()\n\n// Полиморфный корень: явный `as` сильнее всего, иначе `href` сам делает пункт\n// ссылкой — как у `GrButton`, чтобы не приходилось задавать `as=\"a\"` вручную.\nconst renderAs = computed<string | Component>(() => {\n if (props.as && props.as !== 'button')\n return props.as\n return props.href ? 'a' : 'button'\n})\n\nconst isNativeButton = computed(() => renderAs.value === 'button')\n\n// Выключенный пункт остаётся ссылкой, если оставить ему `href`: средняя кнопка и\n// контекстное меню браузера обойдут любой перехват клика (тот же приём в `GrButton`).\nconst resolvedHref = computed(() => (props.disabled ? undefined : props.href))\nconst resolvedTarget = computed(() => props.target ?? (props.external ? '_blank' : undefined))\nconst resolvedRel = computed(() =>\n props.rel ?? (resolvedTarget.value === '_blank' ? 'noopener noreferrer' : undefined),\n)\n// Отметка занимает место всегда, когда пункт переключаемый: иначе строки\n// «включено» и «выключено» разъезжаются по горизонтали.\nconst isCheckable = computed(() => props.role !== 'menuitem')\n\nconst className = computed(() => grDropdownMenuItemClass({\n align: props.align,\n variant: props.variant,\n disabled: props.disabled,\n}))\n\n/**\n * Roving tabindex паттерна menu: табируемым остаётся триггер, а внутри панели\n * фокусом распоряжаются стрелки (`GrDropdown` наводит его программно). Без\n * этого `Tab` ходил бы по пунктам, и меню было бы списком кнопок, а не меню.\n *\n * Выключённый пункт из обхода **не** выпадает: `aria-disabled` вместо нативного\n * `disabled` оставляет его фокусируемым, и пользователь узнаёт, что действие\n * существует, но сейчас недоступно (рекомендация WAI-ARIA APG).\n */\nconst resolvedTabindex = computed(() => (attrs.tabindex as number | string | undefined) ?? -1)\n\nfunction onClickCapture(e: MouseEvent): void {\n if (!props.disabled)\n return\n\n e.preventDefault()\n // `stopImmediatePropagation`, а не `stopPropagation`: обработчик, навешенный\n // на сам пункт через `v-bind=\"attrs\"`, живёт на этом же элементе, и обычная\n // остановка всплытия его не отменяет.\n e.stopImmediatePropagation()\n}\n\ndefineSlots<{\n /** Подпись пункта вместо пропа `label`. */\n default?: () => any\n /** Иконка перед подписью. */\n icon?: () => any\n /** Сочетание клавиш справа — обычно `GrKbd`. */\n shortcut?: () => any\n}>()\n\n</script>\n\n<template>\n <component\n :is=\"renderAs\"\n v-bind=\"attrs\"\n data-gr-dropdown-menu-item\n :role=\"role\"\n :class=\"className\"\n :type=\"isNativeButton ? (attrs.type as any) ?? 'button' : undefined\"\n :href=\"resolvedHref\"\n :target=\"resolvedHref ? resolvedTarget : undefined\"\n :rel=\"resolvedHref ? resolvedRel : undefined\"\n :aria-disabled=\"disabled ? 'true' : undefined\"\n :aria-checked=\"isCheckable ? (checked ? 'true' : 'false') : undefined\"\n :tabindex=\"resolvedTabindex\"\n @click.capture=\"onClickCapture\"\n >\n <span\n v-if=\"isCheckable\"\n data-gr-dropdown-menu-item-indicator\n :class=\"itemIndicatorClass\"\n aria-hidden=\"true\"\n >\n <IconCheck v-if=\"checked\" :class=\"itemIndicatorClass\" />\n </span>\n\n <slot name=\"icon\">\n <component\n :is=\"icon\"\n v-if=\"icon\"\n :class=\"itemIndicatorClass\"\n aria-hidden=\"true\"\n />\n </slot>\n\n <slot />\n\n <slot name=\"shortcut\">\n <span v-if=\"shortcut\" data-gr-dropdown-menu-item-shortcut :class=\"itemShortcutClass\">{{ shortcut }}</span>\n </slot>\n </component>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, useAttrs } from 'vue'\nimport type { Component } from 'vue'\n\nimport IconCheck from '~icons/lucide/check'\n\nimport {\n grDropdownMenuItemClass,\n itemIndicatorClass,\n itemShortcutClass,\n type GrDropdownMenuItemAlign,\n type GrDropdownMenuItemVariant,\n} from './grDropdownMenuStyles'\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nexport type { GrDropdownMenuItemAlign, GrDropdownMenuItemVariant } from './grDropdownMenuStyles'\n\n/**\n * Роль пункта. `menuitemcheckbox`/`menuitemradio` — пункты-переключатели: они\n * обязаны нести `aria-checked`, иначе AT прочитает их как обычные команды.\n */\nexport type GrDropdownMenuItemRole = 'menuitem' | 'menuitemcheckbox' | 'menuitemradio'\n\nexport interface GrDropdownMenuItemProps {\n as?: string | Component\n disabled?: boolean\n /** Пункт-ссылка. У выключенного пункта не рендерится: ссылка осталась бы рабочей. */\n href?: string\n target?: string\n rel?: string\n /** Шорткат для `target=\"_blank\"` + `rel=\"noopener noreferrer\"`. */\n external?: boolean\n align?: GrDropdownMenuItemAlign\n variant?: GrDropdownMenuItemVariant\n role?: GrDropdownMenuItemRole\n /** Состояние переключателя. Осмысленно с `role=\"menuitemcheckbox|radio\"`. */\n checked?: boolean\n /** Иконка слева. Слот `#icon` сильнее. */\n icon?: Component\n /** Подпись сочетания клавиш справа. Слот `#shortcut` сильнее. */\n shortcut?: string\n}\n\nconst props = withDefaults(defineProps<GrDropdownMenuItemProps>(), {\n as: 'button',\n disabled: false,\n href: undefined,\n target: undefined,\n rel: undefined,\n external: false,\n align: 'left',\n variant: 'default',\n role: 'menuitem',\n checked: undefined,\n icon: undefined,\n shortcut: undefined,\n})\n\nconst attrs = useAttrs()\n\n// Полиморфный корень: явный `as` сильнее всего, иначе `href` сам делает пункт\n// ссылкой — как у `GrButton`, чтобы не приходилось задавать `as=\"a\"` вручную.\nconst renderAs = computed<string | Component>(() => {\n if (props.as && props.as !== 'button')\n return props.as\n return props.href ? 'a' : 'button'\n})\n\nconst isNativeButton = computed(() => renderAs.value === 'button')\n\n// Выключенный пункт остаётся ссылкой, если оставить ему `href`: средняя кнопка и\n// контекстное меню браузера обойдут любой перехват клика (тот же приём в `GrButton`).\nconst resolvedHref = computed(() => (props.disabled ? undefined : props.href))\nconst resolvedTarget = computed(() => props.target ?? (props.external ? '_blank' : undefined))\nconst resolvedRel = computed(() =>\n props.rel ?? (resolvedTarget.value === '_blank' ? 'noopener noreferrer' : undefined),\n)\n// Отметка занимает место всегда, когда пункт переключаемый: иначе строки\n// «включено» и «выключено» разъезжаются по горизонтали.\nconst isCheckable = computed(() => props.role !== 'menuitem')\n\nconst className = computed(() => grDropdownMenuItemClass({\n align: props.align,\n variant: props.variant,\n disabled: props.disabled,\n}))\n\n/**\n * Roving tabindex паттерна menu: табируемым остаётся триггер, а внутри панели\n * фокусом распоряжаются стрелки (`GrDropdown` наводит его программно). Без\n * этого `Tab` ходил бы по пунктам, и меню было бы списком кнопок, а не меню.\n *\n * Выключённый пункт из обхода **не** выпадает: `aria-disabled` вместо нативного\n * `disabled` оставляет его фокусируемым, и пользователь узнаёт, что действие\n * существует, но сейчас недоступно (рекомендация WAI-ARIA APG).\n */\nconst resolvedTabindex = computed(() => (attrs.tabindex as number | string | undefined) ?? -1)\n\nfunction onClickCapture(e: MouseEvent): void {\n if (!props.disabled)\n return\n\n e.preventDefault()\n // `stopImmediatePropagation`, а не `stopPropagation`: обработчик, навешенный\n // на сам пункт через `v-bind=\"attrs\"`, живёт на этом же элементе, и обычная\n // остановка всплытия его не отменяет.\n e.stopImmediatePropagation()\n}\n\ndefineSlots<{\n /** Подпись пункта вместо пропа `label`. */\n default?: () => any\n /** Иконка перед подписью. */\n icon?: () => any\n /** Сочетание клавиш справа — обычно `GrKbd`. */\n shortcut?: () => any\n}>()\n\n</script>\n\n<template>\n <component\n :is=\"renderAs\"\n v-bind=\"attrs\"\n data-gr-dropdown-menu-item\n :role=\"role\"\n :class=\"className\"\n :type=\"isNativeButton ? (attrs.type as any) ?? 'button' : undefined\"\n :href=\"resolvedHref\"\n :target=\"resolvedHref ? resolvedTarget : undefined\"\n :rel=\"resolvedHref ? resolvedRel : undefined\"\n :aria-disabled=\"disabled ? 'true' : undefined\"\n :aria-checked=\"isCheckable ? (checked ? 'true' : 'false') : undefined\"\n :tabindex=\"resolvedTabindex\"\n @click.capture=\"onClickCapture\"\n >\n <span\n v-if=\"isCheckable\"\n data-gr-dropdown-menu-item-indicator\n :class=\"itemIndicatorClass\"\n aria-hidden=\"true\"\n >\n <IconCheck v-if=\"checked\" :class=\"itemIndicatorClass\" />\n </span>\n\n <slot name=\"icon\">\n <component\n :is=\"icon\"\n v-if=\"icon\"\n :class=\"itemIndicatorClass\"\n aria-hidden=\"true\"\n />\n </slot>\n\n <slot />\n\n <slot name=\"shortcut\">\n <span v-if=\"shortcut\" data-gr-dropdown-menu-item-shortcut :class=\"itemShortcutClass\">{{ shortcut }}</span>\n </slot>\n </component>\n</template>\n","import type { Component } from 'vue'\n\nimport type { GrDropdownMenuItemRole } from './GrDropdownMenuItem.vue'\nimport type { GrDropdownMenuItemVariant } from './grDropdownMenuStyles'\n\n/**\n * Декларативная модель меню.\n *\n * Композиция из подкомпонентов остаётся основным способом — она нужна там, где\n * пункт нестандартный. Но девять меню из десяти однотипны («иконка, подпись,\n * шорткат, иногда разделитель»), и собирать их из пяти компонентов вручную\n * значит каждый раз заново решать, где `role`, где `disabled`, где ключ.\n */\nexport interface GrDropdownMenuAction {\n /** Ключ пункта — он же приходит в `select`. */\n key: string\n label: string\n icon?: Component\n shortcut?: string\n disabled?: boolean\n variant?: GrDropdownMenuItemVariant\n role?: GrDropdownMenuItemRole\n /** Состояние переключателя для `menuitemcheckbox`/`menuitemradio`. */\n checked?: boolean\n /** Пункт-ссылка: рендерится как `<a>` с этим `href`. */\n href?: string\n target?: string\n rel?: string\n /** Шорткат для `target=\"_blank\"` + `rel=\"noopener noreferrer\"`. */\n external?: boolean\n}\n\nexport interface GrDropdownMenuSeparator {\n type: 'divider'\n inset?: boolean\n}\n\nexport interface GrDropdownMenuSection {\n type: 'group'\n title?: string\n items: GrDropdownMenuAction[]\n}\n\nexport type GrDropdownMenuEntry = GrDropdownMenuAction | GrDropdownMenuSeparator | GrDropdownMenuSection\n\nexport function isMenuSeparator(entry: GrDropdownMenuEntry): entry is GrDropdownMenuSeparator {\n return 'type' in entry && entry.type === 'divider'\n}\n\nexport function isMenuSection(entry: GrDropdownMenuEntry): entry is GrDropdownMenuSection {\n return 'type' in entry && entry.type === 'group'\n}\n\nexport function isMenuAction(entry: GrDropdownMenuEntry): entry is GrDropdownMenuAction {\n return !isMenuSeparator(entry) && !isMenuSection(entry)\n}\n","<script setup lang=\"ts\">\n/**\n * Рендер декларативной модели меню — без обёртки списка: её ставит потребитель,\n * потому что `dividers`/`borderTop` принадлежат конкретному меню, а не модели.\n *\n * Вынесено из `GrDropdownMenu` ради `GrContextMenu`: модель у них общая, и\n * вторая копия цикла разошлась бы с первой при первом же новом поле пункта.\n */\nimport GrDropdownMenuDivider from './GrDropdownMenuDivider.vue'\nimport GrDropdownMenuGroup from './GrDropdownMenuGroup.vue'\nimport GrDropdownMenuItem from './GrDropdownMenuItem.vue'\nimport type { GrDropdownMenuAction, GrDropdownMenuEntry } from './menuModel'\nimport { isMenuAction, isMenuSection, isMenuSeparator } from './menuModel'\n\nexport interface GrDropdownMenuEntriesProps {\n items?: GrDropdownMenuEntry[]\n}\n\nexport interface GrDropdownMenuEntriesEmits {\n (e: 'select', item: GrDropdownMenuAction): void\n}\n\ndefineProps<GrDropdownMenuEntriesProps>()\n\nconst emit = defineEmits<GrDropdownMenuEntriesEmits>()\n\nfunction itemProps(item: GrDropdownMenuAction): Record<string, unknown> {\n return {\n href: item.href,\n target: item.target,\n rel: item.rel,\n external: item.external,\n disabled: item.disabled,\n variant: item.variant,\n role: item.role,\n checked: item.checked,\n icon: item.icon,\n shortcut: item.shortcut,\n }\n}\n\nfunction onSelect(item: GrDropdownMenuAction): void {\n if (item.disabled)\n return\n\n emit('select', item)\n}\n</script>\n\n<template>\n <template v-for=\"(entry, index) in items ?? []\">\n <GrDropdownMenuDivider\n v-if=\"isMenuSeparator(entry)\"\n :key=\"`divider-${index}`\"\n :inset=\"entry.inset\"\n />\n\n <GrDropdownMenuGroup\n v-else-if=\"isMenuSection(entry)\"\n :key=\"`group-${entry.title ?? index}`\"\n :title=\"entry.title\"\n >\n <GrDropdownMenuItem\n v-for=\"item in entry.items\"\n :key=\"item.key\"\n v-bind=\"itemProps(item)\"\n @click=\"onSelect(item)\"\n >\n {{ item.label }}\n </GrDropdownMenuItem>\n </GrDropdownMenuGroup>\n\n <GrDropdownMenuItem\n v-else-if=\"isMenuAction(entry)\"\n :key=\"entry.key\"\n v-bind=\"itemProps(entry)\"\n @click=\"onSelect(entry)\"\n >\n {{ entry.label }}\n </GrDropdownMenuItem>\n </template>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Рендер декларативной модели меню — без обёртки списка: её ставит потребитель,\n * потому что `dividers`/`borderTop` принадлежат конкретному меню, а не модели.\n *\n * Вынесено из `GrDropdownMenu` ради `GrContextMenu`: модель у них общая, и\n * вторая копия цикла разошлась бы с первой при первом же новом поле пункта.\n */\nimport GrDropdownMenuDivider from './GrDropdownMenuDivider.vue'\nimport GrDropdownMenuGroup from './GrDropdownMenuGroup.vue'\nimport GrDropdownMenuItem from './GrDropdownMenuItem.vue'\nimport type { GrDropdownMenuAction, GrDropdownMenuEntry } from './menuModel'\nimport { isMenuAction, isMenuSection, isMenuSeparator } from './menuModel'\n\nexport interface GrDropdownMenuEntriesProps {\n items?: GrDropdownMenuEntry[]\n}\n\nexport interface GrDropdownMenuEntriesEmits {\n (e: 'select', item: GrDropdownMenuAction): void\n}\n\ndefineProps<GrDropdownMenuEntriesProps>()\n\nconst emit = defineEmits<GrDropdownMenuEntriesEmits>()\n\nfunction itemProps(item: GrDropdownMenuAction): Record<string, unknown> {\n return {\n href: item.href,\n target: item.target,\n rel: item.rel,\n external: item.external,\n disabled: item.disabled,\n variant: item.variant,\n role: item.role,\n checked: item.checked,\n icon: item.icon,\n shortcut: item.shortcut,\n }\n}\n\nfunction onSelect(item: GrDropdownMenuAction): void {\n if (item.disabled)\n return\n\n emit('select', item)\n}\n</script>\n\n<template>\n <template v-for=\"(entry, index) in items ?? []\">\n <GrDropdownMenuDivider\n v-if=\"isMenuSeparator(entry)\"\n :key=\"`divider-${index}`\"\n :inset=\"entry.inset\"\n />\n\n <GrDropdownMenuGroup\n v-else-if=\"isMenuSection(entry)\"\n :key=\"`group-${entry.title ?? index}`\"\n :title=\"entry.title\"\n >\n <GrDropdownMenuItem\n v-for=\"item in entry.items\"\n :key=\"item.key\"\n v-bind=\"itemProps(item)\"\n @click=\"onSelect(item)\"\n >\n {{ item.label }}\n </GrDropdownMenuItem>\n </GrDropdownMenuGroup>\n\n <GrDropdownMenuItem\n v-else-if=\"isMenuAction(entry)\"\n :key=\"entry.key\"\n v-bind=\"itemProps(entry)\"\n @click=\"onSelect(entry)\"\n >\n {{ entry.label }}\n </GrDropdownMenuItem>\n </template>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport { grDropdownMenuListClass } from './grDropdownMenuStyles'\n\n/**\n * `role=\"none\"` на обёртке обязателен: `role=\"menu\"` у панели объявляет своих\n * потомков презентационными, и любой div между панелью и пунктом ломает\n * `aria-required-children`, а пункты теряют принадлежность к меню.\n */\n\nexport interface GrDropdownMenuListProps {\n dividers?: boolean\n borderTop?: boolean\n borderBottom?: boolean\n}\n\nconst props = withDefaults(defineProps<GrDropdownMenuListProps>(), {\n dividers: false,\n borderTop: false,\n borderBottom: false,\n})\n\nconst className = computed(() => grDropdownMenuListClass({\n dividers: props.dividers,\n borderTop: props.borderTop,\n borderBottom: props.borderBottom,\n}))\n\ndefineSlots<{\n /** Пункты списка меню. */\n default?: () => any\n}>()\n\n</script>\n\n<template>\n <div\n data-gr-dropdown-menu-list\n role=\"none\"\n :class=\"className\"\n >\n <slot />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport { grDropdownMenuListClass } from './grDropdownMenuStyles'\n\n/**\n * `role=\"none\"` на обёртке обязателен: `role=\"menu\"` у панели объявляет своих\n * потомков презентационными, и любой div между панелью и пунктом ломает\n * `aria-required-children`, а пункты теряют принадлежность к меню.\n */\n\nexport interface GrDropdownMenuListProps {\n dividers?: boolean\n borderTop?: boolean\n borderBottom?: boolean\n}\n\nconst props = withDefaults(defineProps<GrDropdownMenuListProps>(), {\n dividers: false,\n borderTop: false,\n borderBottom: false,\n})\n\nconst className = computed(() => grDropdownMenuListClass({\n dividers: props.dividers,\n borderTop: props.borderTop,\n borderBottom: props.borderBottom,\n}))\n\ndefineSlots<{\n /** Пункты списка меню. */\n default?: () => any\n}>()\n\n</script>\n\n<template>\n <div\n data-gr-dropdown-menu-list\n role=\"none\"\n :class=\"className\"\n >\n <slot />\n </div>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GrDropdownMenuList-B33ZzwfB.js","names":[],"sources":["../../../../src/components/GrDropdownMenu/GrDropdownMenuDivider.vue","../../../../src/components/GrDropdownMenu/GrDropdownMenuDivider.vue","../../../../src/components/GrDropdownMenu/GrDropdownMenuHeader.vue","../../../../src/components/GrDropdownMenu/GrDropdownMenuHeader.vue","../../../../src/components/GrDropdownMenu/GrDropdownMenuGroup.vue","../../../../src/components/GrDropdownMenu/GrDropdownMenuGroup.vue","../../../../src/components/GrDropdownMenu/GrDropdownMenuItem.vue","../../../../src/components/GrDropdownMenu/GrDropdownMenuItem.vue","../../../../src/components/GrDropdownMenu/menuModel.ts","../../../../src/components/GrDropdownMenu/GrDropdownMenuEntries.vue","../../../../src/components/GrDropdownMenu/GrDropdownMenuEntries.vue","../../../../src/components/GrDropdownMenu/GrDropdownMenuList.vue","../../../../src/components/GrDropdownMenu/GrDropdownMenuList.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { dividerClass, dividerInsetClass } from './grDropdownMenuStyles'\n\nexport interface GrDropdownMenuDividerProps {\n inset?: boolean\n}\n\nwithDefaults(defineProps<GrDropdownMenuDividerProps>(), {\n inset: false,\n})\n</script>\n\n<template>\n <div\n data-gr-dropdown-menu-divider\n role=\"separator\"\n :class=\"[dividerClass, inset ? dividerInsetClass : '']\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { dividerClass, dividerInsetClass } from './grDropdownMenuStyles'\n\nexport interface GrDropdownMenuDividerProps {\n inset?: boolean\n}\n\nwithDefaults(defineProps<GrDropdownMenuDividerProps>(), {\n inset: false,\n})\n</script>\n\n<template>\n <div\n data-gr-dropdown-menu-divider\n role=\"separator\"\n :class=\"[dividerClass, inset ? dividerInsetClass : '']\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { computed, useSlots } from 'vue'\n\nimport { headerClass, textAlignClass, type GrDropdownMenuHeaderAlign } from './grDropdownMenuStyles'\n\nexport type { GrDropdownMenuHeaderAlign } from './grDropdownMenuStyles'\n\nexport interface GrDropdownMenuHeaderProps {\n title?: string\n align?: GrDropdownMenuHeaderAlign\n uppercase?: boolean\n}\n\nconst props = withDefaults(defineProps<GrDropdownMenuHeaderProps>(), {\n title: undefined,\n align: 'left',\n uppercase: true,\n})\n\n/**\n * Заголовок — подпись, а не пункт меню: `role=\"presentation\"` убирает его из\n * числа потомков `role=\"menu\"`. Имя группе он даёт через `aria-labelledby`\n * у `GrDropdownMenuGroup`.\n */\nconst slots = useSlots()\nconst isVisible = computed(() => Boolean(props.title) || Boolean(slots.default))\n\ndefineSlots<{\n /** Заголовок раздела меню вместо пропа `label`. */\n default?: () => any\n}>()\n\n</script>\n\n<template>\n <div\n v-if=\"isVisible\"\n data-gr-dropdown-menu-header\n role=\"presentation\"\n :class=\"[headerClass, textAlignClass[align], { uppercase }]\"\n >\n <slot>{{ title }}</slot>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, useSlots } from 'vue'\n\nimport { headerClass, textAlignClass, type GrDropdownMenuHeaderAlign } from './grDropdownMenuStyles'\n\nexport type { GrDropdownMenuHeaderAlign } from './grDropdownMenuStyles'\n\nexport interface GrDropdownMenuHeaderProps {\n title?: string\n align?: GrDropdownMenuHeaderAlign\n uppercase?: boolean\n}\n\nconst props = withDefaults(defineProps<GrDropdownMenuHeaderProps>(), {\n title: undefined,\n align: 'left',\n uppercase: true,\n})\n\n/**\n * Заголовок — подпись, а не пункт меню: `role=\"presentation\"` убирает его из\n * числа потомков `role=\"menu\"`. Имя группе он даёт через `aria-labelledby`\n * у `GrDropdownMenuGroup`.\n */\nconst slots = useSlots()\nconst isVisible = computed(() => Boolean(props.title) || Boolean(slots.default))\n\ndefineSlots<{\n /** Заголовок раздела меню вместо пропа `label`. */\n default?: () => any\n}>()\n\n</script>\n\n<template>\n <div\n v-if=\"isVisible\"\n data-gr-dropdown-menu-header\n role=\"presentation\"\n :class=\"[headerClass, textAlignClass[align], { uppercase }]\"\n >\n <slot>{{ title }}</slot>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, useId } from 'vue'\n\nimport GrDropdownMenuHeader from './GrDropdownMenuHeader.vue'\nimport { dividersClass, listBaseClass, type GrDropdownMenuHeaderAlign } from './grDropdownMenuStyles'\n\nexport interface GrDropdownMenuGroupProps {\n title?: string\n titleAlign?: GrDropdownMenuHeaderAlign\n dividers?: boolean\n uppercase?: boolean\n}\n\nconst props = withDefaults(defineProps<GrDropdownMenuGroupProps>(), {\n title: undefined,\n titleAlign: 'left',\n dividers: false,\n uppercase: true,\n})\n\nconst titleId = useId()\n// Имя группы читается диктором при входе в неё; без заголовка называть нечем.\nconst labelledBy = computed(() => (props.title ? titleId : undefined))\n\nconst itemsClass = computed(() => [\n listBaseClass,\n props.dividers ? dividersClass : '',\n].filter(Boolean).join(' '))\n\ndefineSlots<{\n /** Пункты группы. */\n default?: () => any\n}>()\n\n</script>\n\n<template>\n <div\n data-gr-dropdown-menu-group\n role=\"group\"\n :aria-labelledby=\"labelledBy\"\n class=\"w-full\"\n >\n <GrDropdownMenuHeader\n v-if=\"title\"\n :id=\"titleId\"\n :title=\"title\"\n :align=\"titleAlign\"\n :uppercase=\"uppercase\"\n />\n <div role=\"none\" :class=\"itemsClass\">\n <slot />\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, useId } from 'vue'\n\nimport GrDropdownMenuHeader from './GrDropdownMenuHeader.vue'\nimport { dividersClass, listBaseClass, type GrDropdownMenuHeaderAlign } from './grDropdownMenuStyles'\n\nexport interface GrDropdownMenuGroupProps {\n title?: string\n titleAlign?: GrDropdownMenuHeaderAlign\n dividers?: boolean\n uppercase?: boolean\n}\n\nconst props = withDefaults(defineProps<GrDropdownMenuGroupProps>(), {\n title: undefined,\n titleAlign: 'left',\n dividers: false,\n uppercase: true,\n})\n\nconst titleId = useId()\n// Имя группы читается диктором при входе в неё; без заголовка называть нечем.\nconst labelledBy = computed(() => (props.title ? titleId : undefined))\n\nconst itemsClass = computed(() => [\n listBaseClass,\n props.dividers ? dividersClass : '',\n].filter(Boolean).join(' '))\n\ndefineSlots<{\n /** Пункты группы. */\n default?: () => any\n}>()\n\n</script>\n\n<template>\n <div\n data-gr-dropdown-menu-group\n role=\"group\"\n :aria-labelledby=\"labelledBy\"\n class=\"w-full\"\n >\n <GrDropdownMenuHeader\n v-if=\"title\"\n :id=\"titleId\"\n :title=\"title\"\n :align=\"titleAlign\"\n :uppercase=\"uppercase\"\n />\n <div role=\"none\" :class=\"itemsClass\">\n <slot />\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, useAttrs } from 'vue'\nimport type { Component } from 'vue'\n\nimport IconCheck from '~icons/lucide/check'\n\nimport {\n grDropdownMenuItemClass,\n itemIndicatorClass,\n itemShortcutClass,\n type GrDropdownMenuItemAlign,\n type GrDropdownMenuItemVariant,\n} from './grDropdownMenuStyles'\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nexport type { GrDropdownMenuItemAlign, GrDropdownMenuItemVariant } from './grDropdownMenuStyles'\n\n/**\n * Роль пункта. `menuitemcheckbox`/`menuitemradio` — пункты-переключатели: они\n * обязаны нести `aria-checked`, иначе AT прочитает их как обычные команды.\n */\nexport type GrDropdownMenuItemRole = 'menuitem' | 'menuitemcheckbox' | 'menuitemradio'\n\nexport interface GrDropdownMenuItemProps {\n as?: string | Component\n disabled?: boolean\n /** Пункт-ссылка. У выключенного пункта не рендерится: ссылка осталась бы рабочей. */\n href?: string\n target?: string\n rel?: string\n /** Шорткат для `target=\"_blank\"` + `rel=\"noopener noreferrer\"`. */\n external?: boolean\n align?: GrDropdownMenuItemAlign\n variant?: GrDropdownMenuItemVariant\n role?: GrDropdownMenuItemRole\n /** Состояние переключателя. Осмысленно с `role=\"menuitemcheckbox|radio\"`. */\n checked?: boolean\n /** Иконка слева. Слот `#icon` сильнее. */\n icon?: Component\n /** Подпись сочетания клавиш справа. Слот `#shortcut` сильнее. */\n shortcut?: string\n}\n\nconst props = withDefaults(defineProps<GrDropdownMenuItemProps>(), {\n as: 'button',\n disabled: false,\n href: undefined,\n target: undefined,\n rel: undefined,\n external: false,\n align: 'left',\n variant: 'default',\n role: 'menuitem',\n checked: undefined,\n icon: undefined,\n shortcut: undefined,\n})\n\nconst attrs = useAttrs()\n\n// Полиморфный корень: явный `as` сильнее всего, иначе `href` сам делает пункт\n// ссылкой — как у `GrButton`, чтобы не приходилось задавать `as=\"a\"` вручную.\nconst renderAs = computed<string | Component>(() => {\n if (props.as && props.as !== 'button')\n return props.as\n return props.href ? 'a' : 'button'\n})\n\nconst isNativeButton = computed(() => renderAs.value === 'button')\n\n// Выключенный пункт остаётся ссылкой, если оставить ему `href`: средняя кнопка и\n// контекстное меню браузера обойдут любой перехват клика (тот же приём в `GrButton`).\nconst resolvedHref = computed(() => (props.disabled ? undefined : props.href))\nconst resolvedTarget = computed(() => props.target ?? (props.external ? '_blank' : undefined))\nconst resolvedRel = computed(() =>\n props.rel ?? (resolvedTarget.value === '_blank' ? 'noopener noreferrer' : undefined),\n)\n// Отметка занимает место всегда, когда пункт переключаемый: иначе строки\n// «включено» и «выключено» разъезжаются по горизонтали.\nconst isCheckable = computed(() => props.role !== 'menuitem')\n\nconst className = computed(() => grDropdownMenuItemClass({\n align: props.align,\n variant: props.variant,\n disabled: props.disabled,\n}))\n\n/**\n * Roving tabindex паттерна menu: табируемым остаётся триггер, а внутри панели\n * фокусом распоряжаются стрелки (`GrDropdown` наводит его программно). Без\n * этого `Tab` ходил бы по пунктам, и меню было бы списком кнопок, а не меню.\n *\n * Выключённый пункт из обхода **не** выпадает: `aria-disabled` вместо нативного\n * `disabled` оставляет его фокусируемым, и пользователь узнаёт, что действие\n * существует, но сейчас недоступно (рекомендация WAI-ARIA APG).\n */\nconst resolvedTabindex = computed(() => (attrs.tabindex as number | string | undefined) ?? -1)\n\nfunction onClickCapture(e: MouseEvent): void {\n if (!props.disabled)\n return\n\n e.preventDefault()\n // `stopImmediatePropagation`, а не `stopPropagation`: обработчик, навешенный\n // на сам пункт через `v-bind=\"attrs\"`, живёт на этом же элементе, и обычная\n // остановка всплытия его не отменяет.\n e.stopImmediatePropagation()\n}\n\ndefineSlots<{\n /** Подпись пункта вместо пропа `label`. */\n default?: () => any\n /** Иконка перед подписью. */\n icon?: () => any\n /** Сочетание клавиш справа — обычно `GrKbd`. */\n shortcut?: () => any\n}>()\n\n</script>\n\n<template>\n <component\n :is=\"renderAs\"\n v-bind=\"attrs\"\n data-gr-dropdown-menu-item\n :role=\"role\"\n :class=\"className\"\n :type=\"isNativeButton ? (attrs.type as any) ?? 'button' : undefined\"\n :href=\"resolvedHref\"\n :target=\"resolvedHref ? resolvedTarget : undefined\"\n :rel=\"resolvedHref ? resolvedRel : undefined\"\n :aria-disabled=\"disabled ? 'true' : undefined\"\n :aria-checked=\"isCheckable ? (checked ? 'true' : 'false') : undefined\"\n :tabindex=\"resolvedTabindex\"\n @click.capture=\"onClickCapture\"\n >\n <span\n v-if=\"isCheckable\"\n data-gr-dropdown-menu-item-indicator\n :class=\"itemIndicatorClass\"\n aria-hidden=\"true\"\n >\n <IconCheck v-if=\"checked\" :class=\"itemIndicatorClass\" />\n </span>\n\n <slot name=\"icon\">\n <component\n :is=\"icon\"\n v-if=\"icon\"\n :class=\"itemIndicatorClass\"\n aria-hidden=\"true\"\n />\n </slot>\n\n <slot />\n\n <slot name=\"shortcut\">\n <span v-if=\"shortcut\" data-gr-dropdown-menu-item-shortcut :class=\"itemShortcutClass\">{{ shortcut }}</span>\n </slot>\n </component>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, useAttrs } from 'vue'\nimport type { Component } from 'vue'\n\nimport IconCheck from '~icons/lucide/check'\n\nimport {\n grDropdownMenuItemClass,\n itemIndicatorClass,\n itemShortcutClass,\n type GrDropdownMenuItemAlign,\n type GrDropdownMenuItemVariant,\n} from './grDropdownMenuStyles'\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nexport type { GrDropdownMenuItemAlign, GrDropdownMenuItemVariant } from './grDropdownMenuStyles'\n\n/**\n * Роль пункта. `menuitemcheckbox`/`menuitemradio` — пункты-переключатели: они\n * обязаны нести `aria-checked`, иначе AT прочитает их как обычные команды.\n */\nexport type GrDropdownMenuItemRole = 'menuitem' | 'menuitemcheckbox' | 'menuitemradio'\n\nexport interface GrDropdownMenuItemProps {\n as?: string | Component\n disabled?: boolean\n /** Пункт-ссылка. У выключенного пункта не рендерится: ссылка осталась бы рабочей. */\n href?: string\n target?: string\n rel?: string\n /** Шорткат для `target=\"_blank\"` + `rel=\"noopener noreferrer\"`. */\n external?: boolean\n align?: GrDropdownMenuItemAlign\n variant?: GrDropdownMenuItemVariant\n role?: GrDropdownMenuItemRole\n /** Состояние переключателя. Осмысленно с `role=\"menuitemcheckbox|radio\"`. */\n checked?: boolean\n /** Иконка слева. Слот `#icon` сильнее. */\n icon?: Component\n /** Подпись сочетания клавиш справа. Слот `#shortcut` сильнее. */\n shortcut?: string\n}\n\nconst props = withDefaults(defineProps<GrDropdownMenuItemProps>(), {\n as: 'button',\n disabled: false,\n href: undefined,\n target: undefined,\n rel: undefined,\n external: false,\n align: 'left',\n variant: 'default',\n role: 'menuitem',\n checked: undefined,\n icon: undefined,\n shortcut: undefined,\n})\n\nconst attrs = useAttrs()\n\n// Полиморфный корень: явный `as` сильнее всего, иначе `href` сам делает пункт\n// ссылкой — как у `GrButton`, чтобы не приходилось задавать `as=\"a\"` вручную.\nconst renderAs = computed<string | Component>(() => {\n if (props.as && props.as !== 'button')\n return props.as\n return props.href ? 'a' : 'button'\n})\n\nconst isNativeButton = computed(() => renderAs.value === 'button')\n\n// Выключенный пункт остаётся ссылкой, если оставить ему `href`: средняя кнопка и\n// контекстное меню браузера обойдут любой перехват клика (тот же приём в `GrButton`).\nconst resolvedHref = computed(() => (props.disabled ? undefined : props.href))\nconst resolvedTarget = computed(() => props.target ?? (props.external ? '_blank' : undefined))\nconst resolvedRel = computed(() =>\n props.rel ?? (resolvedTarget.value === '_blank' ? 'noopener noreferrer' : undefined),\n)\n// Отметка занимает место всегда, когда пункт переключаемый: иначе строки\n// «включено» и «выключено» разъезжаются по горизонтали.\nconst isCheckable = computed(() => props.role !== 'menuitem')\n\nconst className = computed(() => grDropdownMenuItemClass({\n align: props.align,\n variant: props.variant,\n disabled: props.disabled,\n}))\n\n/**\n * Roving tabindex паттерна menu: табируемым остаётся триггер, а внутри панели\n * фокусом распоряжаются стрелки (`GrDropdown` наводит его программно). Без\n * этого `Tab` ходил бы по пунктам, и меню было бы списком кнопок, а не меню.\n *\n * Выключённый пункт из обхода **не** выпадает: `aria-disabled` вместо нативного\n * `disabled` оставляет его фокусируемым, и пользователь узнаёт, что действие\n * существует, но сейчас недоступно (рекомендация WAI-ARIA APG).\n */\nconst resolvedTabindex = computed(() => (attrs.tabindex as number | string | undefined) ?? -1)\n\nfunction onClickCapture(e: MouseEvent): void {\n if (!props.disabled)\n return\n\n e.preventDefault()\n // `stopImmediatePropagation`, а не `stopPropagation`: обработчик, навешенный\n // на сам пункт через `v-bind=\"attrs\"`, живёт на этом же элементе, и обычная\n // остановка всплытия его не отменяет.\n e.stopImmediatePropagation()\n}\n\ndefineSlots<{\n /** Подпись пункта вместо пропа `label`. */\n default?: () => any\n /** Иконка перед подписью. */\n icon?: () => any\n /** Сочетание клавиш справа — обычно `GrKbd`. */\n shortcut?: () => any\n}>()\n\n</script>\n\n<template>\n <component\n :is=\"renderAs\"\n v-bind=\"attrs\"\n data-gr-dropdown-menu-item\n :role=\"role\"\n :class=\"className\"\n :type=\"isNativeButton ? (attrs.type as any) ?? 'button' : undefined\"\n :href=\"resolvedHref\"\n :target=\"resolvedHref ? resolvedTarget : undefined\"\n :rel=\"resolvedHref ? resolvedRel : undefined\"\n :aria-disabled=\"disabled ? 'true' : undefined\"\n :aria-checked=\"isCheckable ? (checked ? 'true' : 'false') : undefined\"\n :tabindex=\"resolvedTabindex\"\n @click.capture=\"onClickCapture\"\n >\n <span\n v-if=\"isCheckable\"\n data-gr-dropdown-menu-item-indicator\n :class=\"itemIndicatorClass\"\n aria-hidden=\"true\"\n >\n <IconCheck v-if=\"checked\" :class=\"itemIndicatorClass\" />\n </span>\n\n <slot name=\"icon\">\n <component\n :is=\"icon\"\n v-if=\"icon\"\n :class=\"itemIndicatorClass\"\n aria-hidden=\"true\"\n />\n </slot>\n\n <slot />\n\n <slot name=\"shortcut\">\n <span v-if=\"shortcut\" data-gr-dropdown-menu-item-shortcut :class=\"itemShortcutClass\">{{ shortcut }}</span>\n </slot>\n </component>\n</template>\n","import type { Component } from 'vue'\n\nimport type { GrDropdownMenuItemAlign, GrDropdownMenuItemRole } from './GrDropdownMenuItem.vue'\nimport type { GrDropdownMenuHeaderAlign, GrDropdownMenuItemVariant } from './grDropdownMenuStyles'\n\n/**\n * Декларативная модель меню.\n *\n * Композиция из подкомпонентов остаётся основным способом — она нужна там, где\n * пункт нестандартный. Но девять меню из десяти однотипны («иконка, подпись,\n * шорткат, иногда разделитель»), и собирать их из пяти компонентов вручную\n * значит каждый раз заново решать, где `role`, где `disabled`, где ключ.\n */\nexport interface GrDropdownMenuAction {\n /** Ключ пункта — он же приходит в `select`. */\n key: string\n label: string\n icon?: Component\n shortcut?: string\n disabled?: boolean\n variant?: GrDropdownMenuItemVariant\n role?: GrDropdownMenuItemRole\n /** Состояние переключателя для `menuitemcheckbox`/`menuitemradio`. */\n checked?: boolean\n /** Пункт-ссылка: рендерится как `<a>` с этим `href`. */\n href?: string\n target?: string\n rel?: string\n /** Шорткат для `target=\"_blank\"` + `rel=\"noopener noreferrer\"`. */\n external?: boolean\n /**\n * Чем рендерить пункт: тег или компонент роутера.\n *\n * Без него пункт-ссылка из `items` остаётся обычным `<a>`, то есть в\n * SPA переход идёт перезагрузкой страницы — а обойти это в модели нечем:\n * `itemProps` внутренняя, слота на пункт нет. Проп есть у самого\n * `GrDropdownMenuItem` и у соседей по кольцу (`GrLink`, `GrBreadcrumbs`,\n * `GrSidebarItem`), из этого ряда выпадала только модель меню.\n */\n as?: string | Component\n /** Выравнивание содержимого пункта. */\n align?: GrDropdownMenuItemAlign\n}\n\nexport interface GrDropdownMenuSeparator {\n type: 'divider'\n inset?: boolean\n}\n\nexport interface GrDropdownMenuSection {\n type: 'group'\n title?: string\n /** Выравнивание заголовка группы. */\n titleAlign?: GrDropdownMenuHeaderAlign\n /** Разделители между пунктами группы. */\n dividers?: boolean\n /** Заголовок капсом. */\n uppercase?: boolean\n items: GrDropdownMenuAction[]\n}\n\nexport type GrDropdownMenuEntry = GrDropdownMenuAction | GrDropdownMenuSeparator | GrDropdownMenuSection\n\nexport function isMenuSeparator(entry: GrDropdownMenuEntry): entry is GrDropdownMenuSeparator {\n return 'type' in entry && entry.type === 'divider'\n}\n\nexport function isMenuSection(entry: GrDropdownMenuEntry): entry is GrDropdownMenuSection {\n return 'type' in entry && entry.type === 'group'\n}\n\nexport function isMenuAction(entry: GrDropdownMenuEntry): entry is GrDropdownMenuAction {\n return !isMenuSeparator(entry) && !isMenuSection(entry)\n}\n","<script setup lang=\"ts\">\n/**\n * Рендер декларативной модели меню — без обёртки списка: её ставит потребитель,\n * потому что `dividers`/`borderTop` принадлежат конкретному меню, а не модели.\n *\n * Вынесено из `GrDropdownMenu` ради `GrContextMenu`: модель у них общая, и\n * вторая копия цикла разошлась бы с первой при первом же новом поле пункта.\n */\nimport GrDropdownMenuDivider from './GrDropdownMenuDivider.vue'\nimport GrDropdownMenuGroup from './GrDropdownMenuGroup.vue'\nimport GrDropdownMenuItem from './GrDropdownMenuItem.vue'\nimport type { GrDropdownMenuAction, GrDropdownMenuEntry } from './menuModel'\nimport { isMenuAction, isMenuSection, isMenuSeparator } from './menuModel'\n\nexport interface GrDropdownMenuEntriesProps {\n items?: GrDropdownMenuEntry[]\n}\n\nexport interface GrDropdownMenuEntriesEmits {\n (e: 'select', item: GrDropdownMenuAction): void\n}\n\ndefineProps<GrDropdownMenuEntriesProps>()\n\nconst emit = defineEmits<GrDropdownMenuEntriesEmits>()\n\nfunction itemProps(item: GrDropdownMenuAction): Record<string, unknown> {\n return {\n href: item.href,\n target: item.target,\n rel: item.rel,\n external: item.external,\n disabled: item.disabled,\n variant: item.variant,\n role: item.role,\n checked: item.checked,\n icon: item.icon,\n shortcut: item.shortcut,\n as: item.as,\n align: item.align,\n }\n}\n\nfunction onSelect(item: GrDropdownMenuAction): void {\n if (item.disabled)\n return\n\n emit('select', item)\n}\n</script>\n\n<template>\n <template v-for=\"(entry, index) in items ?? []\">\n <GrDropdownMenuDivider\n v-if=\"isMenuSeparator(entry)\"\n :key=\"`divider-${index}`\"\n :inset=\"entry.inset\"\n />\n\n <GrDropdownMenuGroup\n v-else-if=\"isMenuSection(entry)\"\n :key=\"`group-${entry.title ?? index}`\"\n :title=\"entry.title\"\n :title-align=\"entry.titleAlign\"\n :dividers=\"entry.dividers\"\n :uppercase=\"entry.uppercase\"\n >\n <GrDropdownMenuItem\n v-for=\"item in entry.items\"\n :key=\"item.key\"\n v-bind=\"itemProps(item)\"\n @click=\"onSelect(item)\"\n >\n {{ item.label }}\n </GrDropdownMenuItem>\n </GrDropdownMenuGroup>\n\n <GrDropdownMenuItem\n v-else-if=\"isMenuAction(entry)\"\n :key=\"entry.key\"\n v-bind=\"itemProps(entry)\"\n @click=\"onSelect(entry)\"\n >\n {{ entry.label }}\n </GrDropdownMenuItem>\n </template>\n</template>\n","<script setup lang=\"ts\">\n/**\n * Рендер декларативной модели меню — без обёртки списка: её ставит потребитель,\n * потому что `dividers`/`borderTop` принадлежат конкретному меню, а не модели.\n *\n * Вынесено из `GrDropdownMenu` ради `GrContextMenu`: модель у них общая, и\n * вторая копия цикла разошлась бы с первой при первом же новом поле пункта.\n */\nimport GrDropdownMenuDivider from './GrDropdownMenuDivider.vue'\nimport GrDropdownMenuGroup from './GrDropdownMenuGroup.vue'\nimport GrDropdownMenuItem from './GrDropdownMenuItem.vue'\nimport type { GrDropdownMenuAction, GrDropdownMenuEntry } from './menuModel'\nimport { isMenuAction, isMenuSection, isMenuSeparator } from './menuModel'\n\nexport interface GrDropdownMenuEntriesProps {\n items?: GrDropdownMenuEntry[]\n}\n\nexport interface GrDropdownMenuEntriesEmits {\n (e: 'select', item: GrDropdownMenuAction): void\n}\n\ndefineProps<GrDropdownMenuEntriesProps>()\n\nconst emit = defineEmits<GrDropdownMenuEntriesEmits>()\n\nfunction itemProps(item: GrDropdownMenuAction): Record<string, unknown> {\n return {\n href: item.href,\n target: item.target,\n rel: item.rel,\n external: item.external,\n disabled: item.disabled,\n variant: item.variant,\n role: item.role,\n checked: item.checked,\n icon: item.icon,\n shortcut: item.shortcut,\n as: item.as,\n align: item.align,\n }\n}\n\nfunction onSelect(item: GrDropdownMenuAction): void {\n if (item.disabled)\n return\n\n emit('select', item)\n}\n</script>\n\n<template>\n <template v-for=\"(entry, index) in items ?? []\">\n <GrDropdownMenuDivider\n v-if=\"isMenuSeparator(entry)\"\n :key=\"`divider-${index}`\"\n :inset=\"entry.inset\"\n />\n\n <GrDropdownMenuGroup\n v-else-if=\"isMenuSection(entry)\"\n :key=\"`group-${entry.title ?? index}`\"\n :title=\"entry.title\"\n :title-align=\"entry.titleAlign\"\n :dividers=\"entry.dividers\"\n :uppercase=\"entry.uppercase\"\n >\n <GrDropdownMenuItem\n v-for=\"item in entry.items\"\n :key=\"item.key\"\n v-bind=\"itemProps(item)\"\n @click=\"onSelect(item)\"\n >\n {{ item.label }}\n </GrDropdownMenuItem>\n </GrDropdownMenuGroup>\n\n <GrDropdownMenuItem\n v-else-if=\"isMenuAction(entry)\"\n :key=\"entry.key\"\n v-bind=\"itemProps(entry)\"\n @click=\"onSelect(entry)\"\n >\n {{ entry.label }}\n </GrDropdownMenuItem>\n </template>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport { grDropdownMenuListClass } from './grDropdownMenuStyles'\n\n/**\n * `role=\"none\"` на обёртке обязателен: `role=\"menu\"` у панели объявляет своих\n * потомков презентационными, и любой div между панелью и пунктом ломает\n * `aria-required-children`, а пункты теряют принадлежность к меню.\n */\n\nexport interface GrDropdownMenuListProps {\n dividers?: boolean\n borderTop?: boolean\n borderBottom?: boolean\n}\n\nconst props = withDefaults(defineProps<GrDropdownMenuListProps>(), {\n dividers: false,\n borderTop: false,\n borderBottom: false,\n})\n\nconst className = computed(() => grDropdownMenuListClass({\n dividers: props.dividers,\n borderTop: props.borderTop,\n borderBottom: props.borderBottom,\n}))\n\ndefineSlots<{\n /** Пункты списка меню. */\n default?: () => any\n}>()\n\n</script>\n\n<template>\n <div\n data-gr-dropdown-menu-list\n role=\"none\"\n :class=\"className\"\n >\n <slot />\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport { grDropdownMenuListClass } from './grDropdownMenuStyles'\n\n/**\n * `role=\"none\"` на обёртке обязателен: `role=\"menu\"` у панели объявляет своих\n * потомков презентационными, и любой div между панелью и пунктом ломает\n * `aria-required-children`, а пункты теряют принадлежность к меню.\n */\n\nexport interface GrDropdownMenuListProps {\n dividers?: boolean\n borderTop?: boolean\n borderBottom?: boolean\n}\n\nconst props = withDefaults(defineProps<GrDropdownMenuListProps>(), {\n dividers: false,\n borderTop: false,\n borderBottom: false,\n})\n\nconst className = computed(() => grDropdownMenuListClass({\n dividers: props.dividers,\n borderTop: props.borderTop,\n borderBottom: props.borderBottom,\n}))\n\ndefineSlots<{\n /** Пункты списка меню. */\n default?: () => any\n}>()\n\n</script>\n\n<template>\n <div\n data-gr-dropdown-menu-list\n role=\"none\"\n :class=\"className\"\n >\n <slot />\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as GrDropdownMenuItem_default, c as GrDropdownMenuDivider_default, o as GrDropdownMenuGroup_default, s as GrDropdownMenuHeader_default, t as GrDropdownMenuList_default } from "./chunks/GrDropdownMenuList-
|
|
2
|
-
import { n as GrDropdownMenuColumn_default, r as GrDropdownMenu_default, t as GrDropdownMenuColumns_default } from "./chunks/GrDropdownMenu-
|
|
1
|
+
import { a as GrDropdownMenuItem_default, c as GrDropdownMenuDivider_default, o as GrDropdownMenuGroup_default, s as GrDropdownMenuHeader_default, t as GrDropdownMenuList_default } from "./chunks/GrDropdownMenuList-B33ZzwfB.js";
|
|
2
|
+
import { n as GrDropdownMenuColumn_default, r as GrDropdownMenu_default, t as GrDropdownMenuColumns_default } from "./chunks/GrDropdownMenu-GOzuAXsi.js";
|
|
3
3
|
import { n as grDropdownMenuSafelist, t as grDropdownMenuConfig } from "../../chunks/config-_jbgsFpw.js";
|
|
4
4
|
export { GrDropdownMenu_default as GrDropdownMenu, GrDropdownMenu_default as default, GrDropdownMenuColumn_default as GrDropdownMenuColumn, GrDropdownMenuColumns_default as GrDropdownMenuColumns, GrDropdownMenuDivider_default as GrDropdownMenuDivider, GrDropdownMenuGroup_default as GrDropdownMenuGroup, GrDropdownMenuHeader_default as GrDropdownMenuHeader, GrDropdownMenuItem_default as GrDropdownMenuItem, GrDropdownMenuList_default as GrDropdownMenuList, grDropdownMenuConfig, grDropdownMenuSafelist };
|
|
@@ -4,7 +4,7 @@ import { n as useGranularityTranslations } from "../../../chunks/granularityI18n
|
|
|
4
4
|
import { n as useGrFormFieldContext } from "../../../chunks/context-DdNIxa1w.js";
|
|
5
5
|
import { useGrFormControl } from "../../../composables/useGrFormControl.js";
|
|
6
6
|
import { t as useControlAddons } from "../../../chunks/useControlAddons-asUr10Pf.js";
|
|
7
|
-
import {
|
|
7
|
+
import { a as grInputFieldClass, c as paddingX, i as addonSegmentSuffixClass, n as addonInlineSuffixClass, o as grInputShellClass, r as addonSegmentPrefixClass, t as addonInlinePrefixClass } from "../../../chunks/grInputStyles-DgQYjJvD.js";
|
|
8
8
|
import { t as loader_2_default } from "../../../chunks/loader-2-B1fdsdWW.js";
|
|
9
9
|
import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, markRaw, mergeProps, normalizeClass, normalizeStyle, openBlock, ref, renderSlot, toDisplayString, unref, useId } from "vue";
|
|
10
10
|
//#region ~icons/lucide/eye
|
|
@@ -159,7 +159,8 @@ var GrInput_default = /* @__PURE__ */ defineComponent({
|
|
|
159
159
|
suffixFixed: {
|
|
160
160
|
type: Boolean,
|
|
161
161
|
default: false
|
|
162
|
-
}
|
|
162
|
+
},
|
|
163
|
+
addon: { default: "segment" }
|
|
163
164
|
},
|
|
164
165
|
emits: [
|
|
165
166
|
"update:modelValue",
|
|
@@ -206,8 +207,11 @@ var GrInput_default = /* @__PURE__ */ defineComponent({
|
|
|
206
207
|
const showClear = computed(() => resolvedClearable.value && props.modelValue.length > 0 && !isDisabled.value && !isReadonly.value);
|
|
207
208
|
const trailingCount = computed(() => (showClear.value ? 1 : 0) + (showPasswordToggle.value ? 1 : 0) + (props.loading ? 1 : 0));
|
|
208
209
|
const trailingReserve = computed(() => trailingCount.value > 0 ? `${trailingCount.value * 28}px` : "0px");
|
|
210
|
+
const isInlineAddon = computed(() => props.addon === "inline");
|
|
211
|
+
const prefixAddonClass = computed(() => isInlineAddon.value ? addonInlinePrefixClass : addonSegmentPrefixClass);
|
|
212
|
+
const suffixAddonClass = computed(() => isInlineAddon.value ? addonInlineSuffixClass : addonSegmentSuffixClass);
|
|
209
213
|
const { prefixEl, suffixEl, prefixStyle, suffixStyle, fieldPadding: inputStyle } = useControlAddons(() => props, {
|
|
210
|
-
defaultMinWidth: () => ADDON_MIN_WIDTH_BY_SIZE[resolvedSize.value],
|
|
214
|
+
defaultMinWidth: () => isInlineAddon.value ? "0px" : ADDON_MIN_WIDTH_BY_SIZE[resolvedSize.value],
|
|
211
215
|
paddingX: () => paddingX[resolvedSize.value],
|
|
212
216
|
trailingReserve: () => trailingReserve.value
|
|
213
217
|
});
|
|
@@ -261,10 +265,10 @@ var GrInput_default = /* @__PURE__ */ defineComponent({
|
|
|
261
265
|
ref_key: "prefixEl",
|
|
262
266
|
ref: prefixEl,
|
|
263
267
|
"data-testid": "gr-input-prefix",
|
|
264
|
-
class: "absolute inset-y-0 left-0 flex items-center justify-center
|
|
268
|
+
class: normalizeClass(["absolute inset-y-0 left-0 flex items-center justify-center text-[var(--gr-muted-fg)] pointer-events-none select-none truncate", prefixAddonClass.value]),
|
|
265
269
|
style: normalizeStyle(unref(prefixStyle)),
|
|
266
270
|
"aria-hidden": "true"
|
|
267
|
-
}, [renderSlot(_ctx.$slots, "prefix")],
|
|
271
|
+
}, [renderSlot(_ctx.$slots, "prefix")], 6)) : createCommentVNode("", true),
|
|
268
272
|
createElementVNode("input", mergeProps({
|
|
269
273
|
id: resolvedId.value,
|
|
270
274
|
ref_key: "inputEl",
|
|
@@ -328,7 +332,7 @@ var GrInput_default = /* @__PURE__ */ defineComponent({
|
|
|
328
332
|
ref_key: "suffixEl",
|
|
329
333
|
ref: suffixEl,
|
|
330
334
|
"data-testid": "gr-input-suffix",
|
|
331
|
-
class: normalizeClass(["absolute inset-y-0 right-0 flex items-center justify-center
|
|
335
|
+
class: normalizeClass(["absolute inset-y-0 right-0 flex items-center justify-center text-[var(--gr-muted-fg)] pointer-events-none select-none truncate", [suffixAddonClass.value, __props.suffixFixed ? "[direction:rtl]" : ""]]),
|
|
332
336
|
style: normalizeStyle(unref(suffixStyle)),
|
|
333
337
|
"aria-hidden": "true"
|
|
334
338
|
}, [renderSlot(_ctx.$slots, "suffix")], 6)) : createCommentVNode("", true)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GrInput-BLywRZUN.js","names":["$slots","$attrs"],"sources":["../../../../src/components/GrInput/GrInput.vue","../../../../src/components/GrInput/GrInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { GrComponentSize } from '../shared/sizes'\nimport { computed, ref, useId } from 'vue'\n\nimport { useControlAddons } from '../../composables/internal/useControlAddons'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport type { InputHTMLAttributes } from 'vue'\n\nimport { addonInlinePrefixClass, addonInlineSuffixClass, addonSegmentPrefixClass, addonSegmentSuffixClass, grInputFieldClass, grInputShellClass, paddingX } from './grInputStyles'\n\nimport IconLoader from '~icons/lucide/loader-2'\nimport IconX from '~icons/lucide/x'\nimport IconEye from '~icons/lucide/eye'\nimport IconEyeOff from '~icons/lucide/eye-off'\n\nexport interface GrInputProps {\n /**\n * Значение поля. Необязательное: без `v-model` поле рисуется пустым.\n * Дефолт — пустая строка, а не `undefined`: длина значения читается\n * напрямую (`showClear`), и `undefined` уронил бы рендер.\n */\n modelValue?: string\n type?: 'text' | 'email' | 'password' | 'number' | 'search' | 'tel' | 'url'\n placeholder?: string\n autocomplete?: string\n inputmode?: InputHTMLAttributes['inputmode']\n disabled?: boolean\n /** Только для чтения: значение видно и выделяемо, но не редактируется. */\n readonly?: boolean\n invalid?: boolean\n /** Обязательное поле (`aria-required`). Складывается с `required` у `GrFormField`. */\n required?: boolean\n /** Доступное имя вне `GrFormField`. */\n ariaLabel?: string\n state?: 'default' | 'success' | 'warning' | 'danger'\n name?: string\n id?: string\n size?: GrInputSize\n\n /** Показывать кнопку очистки, когда есть значение (и не disabled/readonly). */\n clearable?: boolean\n /** i18n aria-label кнопки очистки. */\n clearLabel?: string\n /** Ограничение длины + основа для счётчика символов. */\n maxlength?: number\n /** Показывать счётчик символов (`len` или `len/maxlength`). */\n showCount?: boolean\n /**\n * Фоновая работа по полю (проверка занятости логина, автосохранение):\n * спиннер в trailing-области + `aria-busy`. Ввод не блокируется —\n * для этого есть `disabled`/`readonly`.\n */\n loading?: boolean\n /** Кнопка показать/скрыть пароль (только при `type=\"password\"`). */\n passwordToggle?: boolean\n /** i18n aria-label кнопки показать/скрыть пароль. */\n passwordShowLabel?: string\n passwordHideLabel?: string\n\n textAlign?: GrInputTextAlign\n\n prefixMinWidth?: string\n prefixMaxWidth?: string\n suffixMinWidth?: string\n suffixMaxWidth?: string\n /**\n * Фиксированная ширина у prefix/suffix: аддон получает жёсткую ширину\n * (из `*MaxWidth` → `*MinWidth` → дефолт), а контент обрезается по краю\n * (prefix — справа, suffix — слева). По умолчанию аддоны «растягиваются»\n * под контент (в пределах min/max), а излишек клипается оболочкой.\n */\n prefixFixed?: boolean\n suffixFixed?: boolean\n /**\n * Как выглядят аддоны `#prefix`/`#suffix`.\n *\n * `segment` (по умолчанию) — отдельный отсек, отрезанный рамкой и выровненный\n * по ступени размера: так поле с «₽» и поле с «USD» стоят в колонку.\n * `inline` — украшение внутри рамки: ни разделителя, ни своей ширины.\n *\n * Разница не косметическая. Поисковая строка с лупой в сегменте читается\n * составным элементом — полем с приклеенной кнопкой, — а не одним полем;\n * именно поэтому иконку внутри рамки нельзя было выразить аддоном, и\n * потребители отказывались от неё вовсе.\n */\n addon?: 'segment' | 'inline'\n}\n\nexport interface GrInputEmits {\n (e: 'update:modelValue', value: string): void\n /** Значение зафиксировано нативным `change` — по `blur` или `Enter`. */\n (e: 'change', value: string): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n /**\n * Значение стёрто кнопкой очистки. Отдельное событие потому, что по\n * `update:modelValue` программную очистку от ручного стирания не отличить.\n */\n (e: 'clear'): void\n}\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nexport type GrInputSize = GrComponentSize\nexport type GrInputTextAlign = 'left' | 'center' | 'right'\n\nconst props = withDefaults(\n defineProps<GrInputProps>(),\n {\n modelValue: '',\n type: 'text',\n placeholder: undefined,\n autocomplete: undefined,\n inputmode: undefined,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n ariaLabel: undefined,\n state: 'default',\n name: undefined,\n id: undefined,\n size: undefined,\n\n // Настраивается через `GrConfigProvider`; дефолт — в резолвере ниже.\n clearable: undefined,\n clearLabel: undefined,\n maxlength: undefined,\n showCount: false,\n loading: false,\n passwordToggle: false,\n passwordShowLabel: undefined,\n passwordHideLabel: undefined,\n\n textAlign: 'left',\n\n prefixMinWidth: undefined,\n prefixMaxWidth: undefined,\n suffixMinWidth: undefined,\n suffixMaxWidth: undefined,\n prefixFixed: false,\n suffixFixed: false,\n addon: 'segment',\n },\n)\n\nconst emit = defineEmits<GrInputEmits>()\ndefineSlots<{\n /** Аддон слева от поля: иконка, код валюты, метка. */\n prefix?: () => any\n /** Аддон справа от поля: единица измерения, подсказка. */\n suffix?: () => any\n}>()\n\n\n// Контекст `GrFormField` (если инпут внутри него): даёт id/aria-describedby/\n// invalid/required как fallback, чтобы не прокидывать `forId` вручную.\nconst field = useGrFormFieldContext()\n\n// Эффективные значения: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrInput' })\nconst resolvedClearable = useGrComponentProp('GrInput', 'clearable', () => props.clearable, false)\n\nconst resolvedId = computed(() => props.id ?? field?.id.value)\n\n// Счётчик обязан быть частью описания поля: иначе «12 / 60» видно глазами, но\n// не слышно — при том, что ограничение длины и есть смысл счётчика.\nconst countId = useId()\nconst describedBy = computed(() =>\n [field?.describedById.value, props.showCount ? countId : undefined]\n .filter(Boolean)\n .join(' ') || undefined,\n)\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n} = useGrFormControl(() => props)\n\nconst inputEl = ref<HTMLInputElement | null>(null)\n\nfunction focus(): void {\n inputEl.value?.focus()\n}\n\nfunction blur(): void {\n inputEl.value?.blur()\n}\n\n/** Выделить содержимое — спутник `focus()` для «подставили значение, перезапишите». */\nfunction select(): void {\n inputEl.value?.select()\n}\n\ndefineExpose({\n focus,\n blur,\n select,\n})\n\nconst ADDON_MIN_WIDTH_BY_SIZE: Record<GrInputSize, string> = {\n xs: '2rem', // w-8\n sm: '2.25rem', // w-9\n md: '2.5rem', // w-10\n lg: '3rem', // w-12\n}\n\nconst passwordVisible = ref(false)\n// Тип поля с учётом переключателя пароля.\nconst resolvedType = computed(() => (props.type === 'password' && passwordVisible.value ? 'text' : props.type))\n\nconst showPasswordToggle = computed(() => props.passwordToggle && props.type === 'password' && !isDisabled.value)\nconst showClear = computed(() => resolvedClearable.value && props.modelValue.length > 0 && !isDisabled.value && !isReadonly.value)\n\nconst trailingCount = computed(() => (showClear.value ? 1 : 0) + (showPasswordToggle.value ? 1 : 0) + (props.loading ? 1 : 0))\nconst trailingReserve = computed(() => (trailingCount.value > 0 ? `${trailingCount.value * 28}px` : '0px'))\n\nconst isInlineAddon = computed(() => props.addon === 'inline')\n\nconst prefixAddonClass = computed(() => (isInlineAddon.value ? addonInlinePrefixClass : addonSegmentPrefixClass))\nconst suffixAddonClass = computed(() => (isInlineAddon.value ? addonInlineSuffixClass : addonSegmentSuffixClass))\n\nconst {\n prefixEl,\n suffixEl,\n prefixStyle,\n suffixStyle,\n fieldPadding: inputStyle,\n} = useControlAddons(() => props, {\n // У украшения своей ширины нет — иначе иконка висела бы в пустом отсеке\n // шириной со ступень размера.\n defaultMinWidth: () => (isInlineAddon.value ? '0px' : ADDON_MIN_WIDTH_BY_SIZE[resolvedSize.value]),\n paddingX: () => paddingX[resolvedSize.value],\n trailingReserve: () => trailingReserve.value,\n})\n\n// Border/ring/disabled — на оболочке (`focus-within`), размеры/выравнивание — на инпуте.\nconst shellClass = computed(() => grInputShellClass({\n state: props.state,\n invalid: isInvalid.value,\n disabled: isDisabled.value,\n}))\n\nconst className = computed(() => grInputFieldClass({\n size: resolvedSize.value,\n align: props.textAlign,\n}))\n\nfunction onInput(e: Event): void {\n emit('update:modelValue', (e.target as HTMLInputElement).value)\n}\n\n// Объявленный emit уходит из `$attrs`, поэтому нативные события переизлучаем\n// руками — иначе `@change`/`@focus`/`@blur` у потребителя перестали бы работать.\nfunction onChange(e: Event): void {\n emit('change', (e.target as HTMLInputElement).value)\n}\n\nfunction onFocus(e: FocusEvent): void {\n emit('focus', e)\n}\n\nfunction onBlur(e: FocusEvent): void {\n emit('blur', e)\n}\n\n// ————— Trailing-контролы: очистка, переключатель пароля, счётчик символов.\nconst { t } = useGranularityTranslations()\nconst resolvedClearLabel = computed(() => props.clearLabel ?? t('gr.input.clear', 'Clear'))\nconst resolvedPasswordShowLabel = computed(() => props.passwordShowLabel ?? t('gr.input.showPassword', 'Show password'))\nconst resolvedPasswordHideLabel = computed(() => props.passwordHideLabel ?? t('gr.input.hidePassword', 'Hide password'))\n\n// Счётчик символов: `len` или `len / maxlength`.\nconst countText = computed(() =>\n props.maxlength !== undefined ? `${props.modelValue.length} / ${props.maxlength}` : String(props.modelValue.length),\n)\n\n/**\n * Живой регион объявляет только исчерпание лимита. Читать вслух каждый символ\n * нельзя — диктор захлебнётся, а сам счётчик и так связан с полем через\n * `aria-describedby` и читается при фокусе.\n */\nconst limitReached = computed(() =>\n props.maxlength !== undefined && props.modelValue.length >= props.maxlength,\n)\nconst liveMessage = computed(() =>\n limitReached.value ? t('gr.input.limitReached', 'Character limit reached') : '',\n)\n\nfunction clear(): void {\n emit('update:modelValue', '')\n emit('clear')\n inputEl.value?.focus()\n}\n\nfunction togglePassword(): void {\n passwordVisible.value = !passwordVisible.value\n inputEl.value?.focus()\n}\n</script>\n\n<template>\n <div data-gr-input class=\"w-full\">\n <div :class=\"shellClass\">\n <div\n v-if=\"$slots.prefix\"\n ref=\"prefixEl\"\n data-testid=\"gr-input-prefix\"\n class=\"absolute inset-y-0 left-0 flex items-center justify-center text-[var(--gr-muted-fg)] pointer-events-none select-none truncate\"\n :class=\"prefixAddonClass\"\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=\"props.name\"\n :type=\"resolvedType\"\n :inputmode=\"props.inputmode\"\n :autocomplete=\"props.autocomplete\"\n :placeholder=\"props.placeholder\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :maxlength=\"props.maxlength\"\n :value=\"props.modelValue\"\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 :aria-busy=\"props.loading ? 'true' : undefined\"\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 disabled:text-[var(--gr-muted-fg)]\"\n :class=\"className\"\n :style=\"inputStyle\"\n @input=\"onInput\"\n @change=\"onChange\"\n @focus=\"onFocus\"\n @blur=\"onBlur\"\n >\n\n <div\n v-if=\"trailingCount > 0\"\n data-gr-input-trailing\n class=\"absolute inset-y-0 right-1 flex items-center gap-0.5\"\n >\n <span\n v-if=\"loading\"\n data-gr-input-spinner\n class=\"flex h-6 w-6 items-center justify-center text-[var(--gr-muted-fg)]\"\n aria-hidden=\"true\"\n >\n <IconLoader class=\"h-4 w-4 animate-spin\" />\n </span>\n\n <button\n v-if=\"showClear\"\n type=\"button\"\n data-gr-input-clear\n :aria-label=\"resolvedClearLabel\"\n class=\"flex h-6 w-6 items-center justify-center rounded-[var(--gr-radius-sm)] text-[var(--gr-muted-fg)] transition-colors hover:bg-[var(--gr-muted)] hover:text-[var(--gr-fg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)]\"\n @click=\"clear\"\n >\n <IconX class=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n\n <button\n v-if=\"showPasswordToggle\"\n type=\"button\"\n data-gr-input-password-toggle\n :aria-label=\"passwordVisible ? resolvedPasswordHideLabel : resolvedPasswordShowLabel\"\n :aria-pressed=\"passwordVisible ? 'true' : 'false'\"\n class=\"flex h-6 w-6 items-center justify-center rounded-[var(--gr-radius-sm)] text-[var(--gr-muted-fg)] transition-colors hover:bg-[var(--gr-muted)] hover:text-[var(--gr-fg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)]\"\n @click=\"togglePassword\"\n >\n <IconEyeOff v-if=\"passwordVisible\" class=\"h-4 w-4\" aria-hidden=\"true\" />\n <IconEye v-else class=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n </div>\n\n <div\n v-if=\"$slots.suffix\"\n ref=\"suffixEl\"\n data-testid=\"gr-input-suffix\"\n class=\"absolute inset-y-0 right-0 flex items-center justify-center text-[var(--gr-muted-fg)] pointer-events-none select-none truncate\"\n :class=\"[suffixAddonClass, suffixFixed ? '[direction:rtl]' : '']\"\n :style=\"suffixStyle\"\n aria-hidden=\"true\"\n >\n <slot name=\"suffix\" />\n </div>\n </div>\n\n <div\n v-if=\"showCount\"\n :id=\"countId\"\n data-gr-input-count\n class=\"mt-1 text-right text-[length:var(--gr-control-text-xs)] leading-[var(--gr-leading-xs)] text-[var(--gr-muted-fg)] tabular-nums\"\n >\n {{ countText }}\n </div>\n\n <span\n v-if=\"showCount\"\n data-gr-input-live\n class=\"sr-only\"\n role=\"status\"\n aria-live=\"polite\"\n >{{ liveMessage }}</span>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport type { GrComponentSize } from '../shared/sizes'\nimport { computed, ref, useId } from 'vue'\n\nimport { useControlAddons } from '../../composables/internal/useControlAddons'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport type { InputHTMLAttributes } from 'vue'\n\nimport { addonInlinePrefixClass, addonInlineSuffixClass, addonSegmentPrefixClass, addonSegmentSuffixClass, grInputFieldClass, grInputShellClass, paddingX } from './grInputStyles'\n\nimport IconLoader from '~icons/lucide/loader-2'\nimport IconX from '~icons/lucide/x'\nimport IconEye from '~icons/lucide/eye'\nimport IconEyeOff from '~icons/lucide/eye-off'\n\nexport interface GrInputProps {\n /**\n * Значение поля. Необязательное: без `v-model` поле рисуется пустым.\n * Дефолт — пустая строка, а не `undefined`: длина значения читается\n * напрямую (`showClear`), и `undefined` уронил бы рендер.\n */\n modelValue?: string\n type?: 'text' | 'email' | 'password' | 'number' | 'search' | 'tel' | 'url'\n placeholder?: string\n autocomplete?: string\n inputmode?: InputHTMLAttributes['inputmode']\n disabled?: boolean\n /** Только для чтения: значение видно и выделяемо, но не редактируется. */\n readonly?: boolean\n invalid?: boolean\n /** Обязательное поле (`aria-required`). Складывается с `required` у `GrFormField`. */\n required?: boolean\n /** Доступное имя вне `GrFormField`. */\n ariaLabel?: string\n state?: 'default' | 'success' | 'warning' | 'danger'\n name?: string\n id?: string\n size?: GrInputSize\n\n /** Показывать кнопку очистки, когда есть значение (и не disabled/readonly). */\n clearable?: boolean\n /** i18n aria-label кнопки очистки. */\n clearLabel?: string\n /** Ограничение длины + основа для счётчика символов. */\n maxlength?: number\n /** Показывать счётчик символов (`len` или `len/maxlength`). */\n showCount?: boolean\n /**\n * Фоновая работа по полю (проверка занятости логина, автосохранение):\n * спиннер в trailing-области + `aria-busy`. Ввод не блокируется —\n * для этого есть `disabled`/`readonly`.\n */\n loading?: boolean\n /** Кнопка показать/скрыть пароль (только при `type=\"password\"`). */\n passwordToggle?: boolean\n /** i18n aria-label кнопки показать/скрыть пароль. */\n passwordShowLabel?: string\n passwordHideLabel?: string\n\n textAlign?: GrInputTextAlign\n\n prefixMinWidth?: string\n prefixMaxWidth?: string\n suffixMinWidth?: string\n suffixMaxWidth?: string\n /**\n * Фиксированная ширина у prefix/suffix: аддон получает жёсткую ширину\n * (из `*MaxWidth` → `*MinWidth` → дефолт), а контент обрезается по краю\n * (prefix — справа, suffix — слева). По умолчанию аддоны «растягиваются»\n * под контент (в пределах min/max), а излишек клипается оболочкой.\n */\n prefixFixed?: boolean\n suffixFixed?: boolean\n /**\n * Как выглядят аддоны `#prefix`/`#suffix`.\n *\n * `segment` (по умолчанию) — отдельный отсек, отрезанный рамкой и выровненный\n * по ступени размера: так поле с «₽» и поле с «USD» стоят в колонку.\n * `inline` — украшение внутри рамки: ни разделителя, ни своей ширины.\n *\n * Разница не косметическая. Поисковая строка с лупой в сегменте читается\n * составным элементом — полем с приклеенной кнопкой, — а не одним полем;\n * именно поэтому иконку внутри рамки нельзя было выразить аддоном, и\n * потребители отказывались от неё вовсе.\n */\n addon?: 'segment' | 'inline'\n}\n\nexport interface GrInputEmits {\n (e: 'update:modelValue', value: string): void\n /** Значение зафиксировано нативным `change` — по `blur` или `Enter`. */\n (e: 'change', value: string): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n /**\n * Значение стёрто кнопкой очистки. Отдельное событие потому, что по\n * `update:modelValue` программную очистку от ручного стирания не отличить.\n */\n (e: 'clear'): void\n}\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nexport type GrInputSize = GrComponentSize\nexport type GrInputTextAlign = 'left' | 'center' | 'right'\n\nconst props = withDefaults(\n defineProps<GrInputProps>(),\n {\n modelValue: '',\n type: 'text',\n placeholder: undefined,\n autocomplete: undefined,\n inputmode: undefined,\n disabled: false,\n readonly: false,\n invalid: false,\n required: false,\n ariaLabel: undefined,\n state: 'default',\n name: undefined,\n id: undefined,\n size: undefined,\n\n // Настраивается через `GrConfigProvider`; дефолт — в резолвере ниже.\n clearable: undefined,\n clearLabel: undefined,\n maxlength: undefined,\n showCount: false,\n loading: false,\n passwordToggle: false,\n passwordShowLabel: undefined,\n passwordHideLabel: undefined,\n\n textAlign: 'left',\n\n prefixMinWidth: undefined,\n prefixMaxWidth: undefined,\n suffixMinWidth: undefined,\n suffixMaxWidth: undefined,\n prefixFixed: false,\n suffixFixed: false,\n addon: 'segment',\n },\n)\n\nconst emit = defineEmits<GrInputEmits>()\ndefineSlots<{\n /** Аддон слева от поля: иконка, код валюты, метка. */\n prefix?: () => any\n /** Аддон справа от поля: единица измерения, подсказка. */\n suffix?: () => any\n}>()\n\n\n// Контекст `GrFormField` (если инпут внутри него): даёт id/aria-describedby/\n// invalid/required как fallback, чтобы не прокидывать `forId` вручную.\nconst field = useGrFormFieldContext()\n\n// Эффективные значения: локальный проп → `GrConfigProvider` → дефолт компонента.\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrInput' })\nconst resolvedClearable = useGrComponentProp('GrInput', 'clearable', () => props.clearable, false)\n\nconst resolvedId = computed(() => props.id ?? field?.id.value)\n\n// Счётчик обязан быть частью описания поля: иначе «12 / 60» видно глазами, но\n// не слышно — при том, что ограничение длины и есть смысл счётчика.\nconst countId = useId()\nconst describedBy = computed(() =>\n [field?.describedById.value, props.showCount ? countId : undefined]\n .filter(Boolean)\n .join(' ') || undefined,\n)\nconst {\n disabled: isDisabled,\n invalid: isInvalid,\n required: isRequired,\n readonly: isReadonly,\n} = useGrFormControl(() => props)\n\nconst inputEl = ref<HTMLInputElement | null>(null)\n\nfunction focus(): void {\n inputEl.value?.focus()\n}\n\nfunction blur(): void {\n inputEl.value?.blur()\n}\n\n/** Выделить содержимое — спутник `focus()` для «подставили значение, перезапишите». */\nfunction select(): void {\n inputEl.value?.select()\n}\n\ndefineExpose({\n focus,\n blur,\n select,\n})\n\nconst ADDON_MIN_WIDTH_BY_SIZE: Record<GrInputSize, string> = {\n xs: '2rem', // w-8\n sm: '2.25rem', // w-9\n md: '2.5rem', // w-10\n lg: '3rem', // w-12\n}\n\nconst passwordVisible = ref(false)\n// Тип поля с учётом переключателя пароля.\nconst resolvedType = computed(() => (props.type === 'password' && passwordVisible.value ? 'text' : props.type))\n\nconst showPasswordToggle = computed(() => props.passwordToggle && props.type === 'password' && !isDisabled.value)\nconst showClear = computed(() => resolvedClearable.value && props.modelValue.length > 0 && !isDisabled.value && !isReadonly.value)\n\nconst trailingCount = computed(() => (showClear.value ? 1 : 0) + (showPasswordToggle.value ? 1 : 0) + (props.loading ? 1 : 0))\nconst trailingReserve = computed(() => (trailingCount.value > 0 ? `${trailingCount.value * 28}px` : '0px'))\n\nconst isInlineAddon = computed(() => props.addon === 'inline')\n\nconst prefixAddonClass = computed(() => (isInlineAddon.value ? addonInlinePrefixClass : addonSegmentPrefixClass))\nconst suffixAddonClass = computed(() => (isInlineAddon.value ? addonInlineSuffixClass : addonSegmentSuffixClass))\n\nconst {\n prefixEl,\n suffixEl,\n prefixStyle,\n suffixStyle,\n fieldPadding: inputStyle,\n} = useControlAddons(() => props, {\n // У украшения своей ширины нет — иначе иконка висела бы в пустом отсеке\n // шириной со ступень размера.\n defaultMinWidth: () => (isInlineAddon.value ? '0px' : ADDON_MIN_WIDTH_BY_SIZE[resolvedSize.value]),\n paddingX: () => paddingX[resolvedSize.value],\n trailingReserve: () => trailingReserve.value,\n})\n\n// Border/ring/disabled — на оболочке (`focus-within`), размеры/выравнивание — на инпуте.\nconst shellClass = computed(() => grInputShellClass({\n state: props.state,\n invalid: isInvalid.value,\n disabled: isDisabled.value,\n}))\n\nconst className = computed(() => grInputFieldClass({\n size: resolvedSize.value,\n align: props.textAlign,\n}))\n\nfunction onInput(e: Event): void {\n emit('update:modelValue', (e.target as HTMLInputElement).value)\n}\n\n// Объявленный emit уходит из `$attrs`, поэтому нативные события переизлучаем\n// руками — иначе `@change`/`@focus`/`@blur` у потребителя перестали бы работать.\nfunction onChange(e: Event): void {\n emit('change', (e.target as HTMLInputElement).value)\n}\n\nfunction onFocus(e: FocusEvent): void {\n emit('focus', e)\n}\n\nfunction onBlur(e: FocusEvent): void {\n emit('blur', e)\n}\n\n// ————— Trailing-контролы: очистка, переключатель пароля, счётчик символов.\nconst { t } = useGranularityTranslations()\nconst resolvedClearLabel = computed(() => props.clearLabel ?? t('gr.input.clear', 'Clear'))\nconst resolvedPasswordShowLabel = computed(() => props.passwordShowLabel ?? t('gr.input.showPassword', 'Show password'))\nconst resolvedPasswordHideLabel = computed(() => props.passwordHideLabel ?? t('gr.input.hidePassword', 'Hide password'))\n\n// Счётчик символов: `len` или `len / maxlength`.\nconst countText = computed(() =>\n props.maxlength !== undefined ? `${props.modelValue.length} / ${props.maxlength}` : String(props.modelValue.length),\n)\n\n/**\n * Живой регион объявляет только исчерпание лимита. Читать вслух каждый символ\n * нельзя — диктор захлебнётся, а сам счётчик и так связан с полем через\n * `aria-describedby` и читается при фокусе.\n */\nconst limitReached = computed(() =>\n props.maxlength !== undefined && props.modelValue.length >= props.maxlength,\n)\nconst liveMessage = computed(() =>\n limitReached.value ? t('gr.input.limitReached', 'Character limit reached') : '',\n)\n\nfunction clear(): void {\n emit('update:modelValue', '')\n emit('clear')\n inputEl.value?.focus()\n}\n\nfunction togglePassword(): void {\n passwordVisible.value = !passwordVisible.value\n inputEl.value?.focus()\n}\n</script>\n\n<template>\n <div data-gr-input class=\"w-full\">\n <div :class=\"shellClass\">\n <div\n v-if=\"$slots.prefix\"\n ref=\"prefixEl\"\n data-testid=\"gr-input-prefix\"\n class=\"absolute inset-y-0 left-0 flex items-center justify-center text-[var(--gr-muted-fg)] pointer-events-none select-none truncate\"\n :class=\"prefixAddonClass\"\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=\"props.name\"\n :type=\"resolvedType\"\n :inputmode=\"props.inputmode\"\n :autocomplete=\"props.autocomplete\"\n :placeholder=\"props.placeholder\"\n :disabled=\"isDisabled\"\n :readonly=\"isReadonly\"\n :maxlength=\"props.maxlength\"\n :value=\"props.modelValue\"\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 :aria-busy=\"props.loading ? 'true' : undefined\"\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 disabled:text-[var(--gr-muted-fg)]\"\n :class=\"className\"\n :style=\"inputStyle\"\n @input=\"onInput\"\n @change=\"onChange\"\n @focus=\"onFocus\"\n @blur=\"onBlur\"\n >\n\n <div\n v-if=\"trailingCount > 0\"\n data-gr-input-trailing\n class=\"absolute inset-y-0 right-1 flex items-center gap-0.5\"\n >\n <span\n v-if=\"loading\"\n data-gr-input-spinner\n class=\"flex h-6 w-6 items-center justify-center text-[var(--gr-muted-fg)]\"\n aria-hidden=\"true\"\n >\n <IconLoader class=\"h-4 w-4 animate-spin\" />\n </span>\n\n <button\n v-if=\"showClear\"\n type=\"button\"\n data-gr-input-clear\n :aria-label=\"resolvedClearLabel\"\n class=\"flex h-6 w-6 items-center justify-center rounded-[var(--gr-radius-sm)] text-[var(--gr-muted-fg)] transition-colors hover:bg-[var(--gr-muted)] hover:text-[var(--gr-fg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)]\"\n @click=\"clear\"\n >\n <IconX class=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n\n <button\n v-if=\"showPasswordToggle\"\n type=\"button\"\n data-gr-input-password-toggle\n :aria-label=\"passwordVisible ? resolvedPasswordHideLabel : resolvedPasswordShowLabel\"\n :aria-pressed=\"passwordVisible ? 'true' : 'false'\"\n class=\"flex h-6 w-6 items-center justify-center rounded-[var(--gr-radius-sm)] text-[var(--gr-muted-fg)] transition-colors hover:bg-[var(--gr-muted)] hover:text-[var(--gr-fg)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)]\"\n @click=\"togglePassword\"\n >\n <IconEyeOff v-if=\"passwordVisible\" class=\"h-4 w-4\" aria-hidden=\"true\" />\n <IconEye v-else class=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n </div>\n\n <div\n v-if=\"$slots.suffix\"\n ref=\"suffixEl\"\n data-testid=\"gr-input-suffix\"\n class=\"absolute inset-y-0 right-0 flex items-center justify-center text-[var(--gr-muted-fg)] pointer-events-none select-none truncate\"\n :class=\"[suffixAddonClass, suffixFixed ? '[direction:rtl]' : '']\"\n :style=\"suffixStyle\"\n aria-hidden=\"true\"\n >\n <slot name=\"suffix\" />\n </div>\n </div>\n\n <div\n v-if=\"showCount\"\n :id=\"countId\"\n data-gr-input-count\n class=\"mt-1 text-right text-[length:var(--gr-control-text-xs)] leading-[var(--gr-leading-xs)] text-[var(--gr-muted-fg)] tabular-nums\"\n >\n {{ countText }}\n </div>\n\n <span\n v-if=\"showCount\"\n data-gr-input-live\n class=\"sr-only\"\n role=\"status\"\n aria-live=\"polite\"\n >{{ liveMessage }}</span>\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { t as GrInput_default } from "./chunks/GrInput-
|
|
2
|
-
import { n as grInputSafelist, t as grInputConfig } from "../../chunks/config-
|
|
1
|
+
import { t as GrInput_default } from "./chunks/GrInput-BLywRZUN.js";
|
|
2
|
+
import { n as grInputSafelist, t as grInputConfig } from "../../chunks/config-CIkdsWUk.js";
|
|
3
3
|
export { GrInput_default as GrInput, GrInput_default as default, grInputConfig, grInputSafelist };
|
|
@@ -9,7 +9,7 @@ import { t as useFocusWithin } from "../../../chunks/useFocusWithin-BwHE0rzm.js"
|
|
|
9
9
|
import { useRovingFocus } from "../../../composables/useRovingFocus.js";
|
|
10
10
|
import { t as useControlAddons } from "../../../chunks/useControlAddons-asUr10Pf.js";
|
|
11
11
|
import { d as chipSizeForBadgeScale } from "../../../chunks/grChipStyles-3JZApw03.js";
|
|
12
|
-
import { t as GrChip_default } from "../../GrChip/chunks/GrChip-
|
|
12
|
+
import { t as GrChip_default } from "../../GrChip/chunks/GrChip-C0vU69tf.js";
|
|
13
13
|
import { useAnnouncer } from "../../../composables/useAnnouncer.js";
|
|
14
14
|
import { t as loader_2_default } from "../../../chunks/loader-2-B1fdsdWW.js";
|
|
15
15
|
import { a as grInputTagWrapperClass, i as grInputTagInputClass, o as spinnerClass, r as clearButtonClass } from "../../../chunks/config-BqT6PMzO.js";
|