@feugene/granularity 0.30.0 → 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/dist/chunks/{config-DO8mjeIo.js → config-CIkdsWUk.js} +6 -2
- package/dist/chunks/config-CIkdsWUk.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/{granular-provider-CHFaw3wU.js → granular-provider-yJNX1g1G.js} +1 -1
- package/dist/chunks/{granular-provider-CHFaw3wU.js.map → granular-provider-yJNX1g1G.js.map} +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/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/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/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/GrPagination/chunks/{GrPagination-kYCx6PAS.js → GrPagination-Cw3R21dp.js} +1 -1
- package/dist/components/GrPagination/chunks/{GrPagination-kYCx6PAS.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-CBp0twbF.js → GrSelect-BiUrSSxc.js} +1 -1
- package/dist/components/GrSelect/chunks/{GrSelect-CBp0twbF.js.map → GrSelect-BiUrSSxc.js.map} +1 -1
- package/dist/components/GrSelect/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 +9 -9
- package/dist/types/components/GrInput/GrInput.vue.d.ts +16 -0
- package/dist/types/components/GrInput/grInputStyles.d.ts +16 -0
- package/dist/web-types.json +10 -1
- package/package.json +1 -1
- package/dist/chunks/config-DO8mjeIo.js.map +0 -1
- package/dist/components/GrInput/chunks/GrInput-BqqoEl1b.js.map +0 -1
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { t as GrSelect_default } from "./chunks/GrSelect-
|
|
1
|
+
import { t as GrSelect_default } from "./chunks/GrSelect-BiUrSSxc.js";
|
|
2
2
|
import { n as grSelectSafelist, t as grSelectConfig } from "../../chunks/config-DnF472Ox.js";
|
|
3
3
|
export { GrSelect_default as GrSelect, GrSelect_default as default, grSelectConfig, grSelectSafelist };
|
package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-CymO68Cv.js → GrTreeSelect-iJnMl2t9.js}
RENAMED
|
@@ -14,7 +14,7 @@ import { t as useControlAddons } from "../../../chunks/useControlAddons-asUr10Pf
|
|
|
14
14
|
import { t as panelPopTransition } from "../../../chunks/overlayTransition-0Fj5tQbq.js";
|
|
15
15
|
import { t as check_default } from "../../../chunks/check-BZI24Qs4.js";
|
|
16
16
|
import { t as chevron_down_default } from "../../../chunks/chevron-down-CQS2OG_s.js";
|
|
17
|
-
import { t as GrInput_default } from "../../GrInput/chunks/GrInput-
|
|
17
|
+
import { t as GrInput_default } from "../../GrInput/chunks/GrInput-BLywRZUN.js";
|
|
18
18
|
import { t as GrTree_default } from "../../GrTree/chunks/GrTree-DC89I4AP.js";
|
|
19
19
|
import { a as grTreeSelectStateClass, i as grTreeSelectPanelClass, o as paddingX, r as grTreeSelectClass, s as trailingZoneWidth } from "../../../chunks/config-T3-DMXYm.js";
|
|
20
20
|
import { Fragment, Teleport, Transition, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, mergeProps, nextTick, normalizeClass, normalizeStyle, openBlock, ref, renderList, renderSlot, toDisplayString, unref, useId, useSlots, vShow, watch, withCtx, withDirectives, withModifiers } from "vue";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GrTreeSelect-CymO68Cv.js","names":["$slots"],"sources":["../../../../src/components/GrTreeSelect/GrTreeSelect.vue","../../../../src/components/GrTreeSelect/GrTreeSelect.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"T extends Record<string, any> = any\">\nimport { computed, nextTick, ref, useId, useSlots, watch } from 'vue'\n\nimport { usePortalTarget } from '../../composables/usePortalTarget'\n\nimport { vClickOutside } from '../../directives'\nimport { useFloating } from '../../composables/useFloating'\nimport { useOverlayLayer } from '../../composables/useOverlayLayer'\nimport { useControlAddons } from '../../composables/internal/useControlAddons'\nimport { useControlledOpen } from '../../composables/internal/useControlledOpen'\nimport { useGrComponentSize, useGrThemeAttrs } from '../GrConfigProvider/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useFocusWithin } from '../../composables/internal/useFocusWithin'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport GrInput from '../GrInput/GrInput.vue'\nimport GrTree, {\n type GrTreeInstance,\n type GrTreeKey,\n type GrTreeNode,\n} from '../GrTree'\nimport type { GrInputSize } from '../GrInput/GrInput.vue'\nimport { panelPopTransition } from '../shared/overlayTransition'\nimport type { GrTreeSelectModelValue, GrTreeSelectProps } from './grTreeSelectTypes'\nimport { grTreeSelectClass, grTreeSelectPanelClass, grTreeSelectStateClass, paddingX, trailingZoneWidth } from './grTreeSelectStyles'\n\nimport IconCheck from '~icons/lucide/check'\nimport IconChevronDown from '~icons/lucide/chevron-down'\nimport IconLoaderCircle from '~icons/lucide/loader-circle'\nimport IconX from '~icons/lucide/x'\n\nexport interface GrTreeSelectEmits<T extends Record<string, any> = any> {\n (e: 'update:modelValue', value: GrTreeSelectModelValue): void\n (e: 'change', value: GrTreeSelectModelValue): void\n /** Панель открылась/закрылась (`v-model:open`). */\n (e: 'update:open', value: boolean): void\n (e: 'clear'): void\n (e: 'nodeClick', data: T, node: GrTreeNode<T>): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrTreeSelectProps<T>>(),\n {\n props: () => ({\n children: 'children',\n label: 'label',\n }),\n nodeKey: 'id' as any,\n defaultExpandedKeys: () => [],\n disabled: false,\n placeholder: undefined,\n size: undefined,\n loading: false,\n invalid: false,\n readonly: false,\n required: false,\n ariaLabel: undefined,\n state: 'default',\n multiple: false,\n showCheckbox: false,\n checkStrictly: false,\n clearable: false,\n open: undefined,\n name: undefined,\n valueDisplay: 'label',\n filterable: false,\n filterPlaceholder: undefined,\n filterInputmode: undefined,\n filterNodeMethod: undefined,\n closeOnSelect: undefined,\n dropdownMaxHeight: 320,\n virtual: false,\n prefixMinWidth: undefined,\n prefixMaxWidth: undefined,\n suffixMinWidth: undefined,\n suffixMaxWidth: undefined,\n prefixFixed: false,\n suffixFixed: false,\n },\n)\n\nconst { t } = useGranularityTranslations()\n\nconst emit = defineEmits<GrTreeSelectEmits<T>>()\n\ndefineSlots<{\n /** Рендер значения внутри триггера (вместо дефолтного текста). */\n value?: (props: { value: GrTreeSelectModelValue; labels: string[]; displayValue: string; pathLabels?: string[] }) => any\n /** Рендер строки дерева. */\n node?: (props: { node: GrTreeNode<T>; data: T; selected: boolean }) => any\n /** Содержимое пустого состояния (когда нет данных). */\n empty?: () => any\n /** Содержимое панели, пока данные едут. */\n loading?: () => any\n /** Аддон слева от значения: иконка, код валюты, метка. */\n prefix?: () => any\n /** Аддон справа от значения, перед крестиком и шевроном. */\n suffix?: () => any\n}>()\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst triggerEl = ref<HTMLInputElement | null>(null)\nconst treeId = `gr-tree-select-tree-${useId()}`\n\n// Контекст `GrFormField` + общий контракт форм-контрола.\nconst field = useGrFormFieldContext()\nconst fieldId = computed(() => field?.id.value)\nconst describedBy = computed(() => field?.describedById.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid, required: isRequired, readonly: isReadonly } = useGrFormControl(() => props)\n\nfunction focus(): void {\n triggerEl.value?.focus()\n}\n\nfunction blur(): void {\n triggerEl.value?.blur()\n}\n\ndefineExpose({ focus, blur })\nconst filterInputRef = ref<InstanceType<typeof GrInput> | null>(null)\nconst treeRef = ref<GrTreeInstance | null>(null)\nconst panelEl = ref<HTMLElement | null>(null)\n\n// Панель телепортирована в `body`, то есть лежит вне корня, но для пользователя\n// она часть контрола: без неё уход фокуса в панель читался бы как `blur`.\nconst { onFocusIn, onFocusOut } = useFocusWithin(rootEl, {\n enter: event => emit('focus', event),\n leave: event => emit('blur', event),\n containers: () => [panelEl.value],\n})\n\nconst clickOutsideExclude = [() => panelEl.value]\n\nlet hadPointerDownOnTrigger = false\n\n// Имя `open` сохранено: читатели работают с computed-Ref.\nconst { open, setOpen: setOpenState } = useControlledOpen(\n () => props.open,\n next => emit('update:open', next),\n)\nconst filterValue = ref('')\n\nconst { floatingStyle } = useFloating(rootEl, panelEl, open, {\n placement: 'bottom-start',\n matchWidth: true,\n zIndexVar: '--gr-z-dropdown',\n})\n\nconst closeOnSelectResolved = computed(() => {\n return props.closeOnSelect ?? !props.multiple\n})\n\nfunction getChildrenKey() {\n return props.props.children ?? 'children'\n}\n\nfunction getLabelKey() {\n return props.props.label ?? 'label'\n}\n\nfunction nodeLabel(data: T): string {\n const key = getLabelKey() as keyof T\n const v = data[key]\n return v == null ? '' : String(v)\n}\n\nfunction nodeChildren(data: T): T[] {\n const key = getChildrenKey() as keyof T\n const v = data[key]\n return Array.isArray(v) ? (v as any) : []\n}\n\nfunction nodeKeyOf(data: T, index: number, parentKey: GrTreeKey | undefined): GrTreeKey {\n const keyName = props.nodeKey as keyof T\n const v = data[keyName]\n if (v != null)\n return v as GrTreeKey\n\n return parentKey == null ? index : `${String(parentKey)}:${index}`\n}\n\nconst labelByKey = computed(() => {\n const map = new Map<GrTreeKey, string>()\n\n const walk = (items: T[], parentKey: GrTreeKey | undefined) => {\n for (let i = 0; i < items.length; i++) {\n const item = items[i]\n const key = nodeKeyOf(item, i, parentKey)\n map.set(key, nodeLabel(item))\n walk(nodeChildren(item), key)\n }\n }\n\n walk(props.data ?? [], undefined)\n return map\n})\n\nconst pathLabelsByKey = computed(() => {\n const map = new Map<GrTreeKey, string[]>()\n\n const walk = (items: T[], parentKey: GrTreeKey | undefined, parentPath: string[]) => {\n for (let i = 0; i < items.length; i++) {\n const item = items[i]\n const key = nodeKeyOf(item, i, parentKey)\n const label = nodeLabel(item)\n const path = [...parentPath, label]\n map.set(key, path)\n walk(nodeChildren(item), key, path)\n }\n }\n\n walk(props.data ?? [], undefined, [])\n return map\n})\n\nconst selectedKeys = computed<GrTreeKey[]>(() => {\n if (props.multiple) {\n return Array.isArray(props.modelValue) ? props.modelValue : []\n }\n\n const v = props.modelValue\n if (v == null || Array.isArray(v))\n return []\n return [v]\n})\n\nconst selectedKeySet = computed(() => new Set(selectedKeys.value))\n\nconst selectedLabels = computed(() => {\n const map = labelByKey.value\n return selectedKeys.value.map((k) => map.get(k) ?? String(k))\n})\n\nconst selectedPathLabels = computed<string[] | undefined>(() => {\n if (props.multiple)\n return undefined\n\n const key = selectedKeys.value[0]\n if (key == null)\n return undefined\n\n const path = pathLabelsByKey.value.get(key)\n if (path?.length)\n return path\n\n const labels = selectedLabels.value\n return labels.length ? [labels[0]] : undefined\n})\n\nconst displayValue = computed(() => {\n const labels = selectedLabels.value\n if (labels.length === 0)\n return ''\n\n if (!props.multiple) {\n if (props.valueDisplay === 'path') {\n const path = selectedPathLabels.value\n if (path?.length)\n return path.join(' / ')\n }\n return labels[0]\n }\n\n if (labels.length === 1)\n return labels[0]\n return `${labels[0]} +${labels.length - 1}`\n})\n\nconst hasSelection = computed(() => selectedKeys.value.length > 0)\n\n/** Дерево рендерится только когда есть что показать — ссылаться иначе не на что. */\nconst hasTree = computed(() => !props.loading && (props.data?.length ?? 0) > 0)\n\nconst resolvedFilterPlaceholder = computed(() => {\n return props.filterPlaceholder ?? t('gr.treeSelect.filterPlaceholder', 'Search…')\n})\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrTreeSelect' })\n\nconst className = computed(() => {\n return grTreeSelectClass({\n size: resolvedSize.value,\n state: props.state,\n invalid: isInvalid.value,\n disabled: isDisabled.value,\n })\n})\n\nconst panelClasses = computed(() => {\n return grTreeSelectPanelClass\n})\n\nconst ADDON_MIN_WIDTH_BY_SIZE: Record<GrInputSize, string> = {\n xs: '2rem',\n sm: '2.25rem',\n md: '2.5rem',\n lg: '3rem',\n}\n\nconst slots = useSlots()\n\nconst {\n hasPrefix,\n hasSuffix,\n prefixEl,\n suffixEl,\n prefixStyle,\n suffixStyle,\n fieldPadding: triggerStyle,\n} = useControlAddons(() => props, {\n defaultMinWidth: () => ADDON_MIN_WIDTH_BY_SIZE[resolvedSize.value],\n paddingX: () => paddingX[resolvedSize.value],\n // Место под крестик и шеврон уже отдано классом `pr-9`, но инлайн-паддинг\n // перекрывает класс целиком — с суффиксом эту зону приходится вернуть числом.\n trailingReserve: () => (slots.suffix ? trailingZoneWidth : '0px'),\n})\n\n// Слот `value` рисуется поверх поля и обязан жить в тех же границах, что и\n// текст под ним. Без аддонов границы задаёт класс — стиль остаётся пустым.\nconst valueOverlayStyle = computed(() => ({\n left: hasPrefix.value ? triggerStyle.value.paddingLeft : undefined,\n right: hasSuffix.value ? triggerStyle.value.paddingRight : undefined,\n}))\n\nfunction setOpen(next: boolean) {\n if (isDisabled.value || isReadonly.value)\n return\n\n setOpenState(next)\n}\n\nfunction onClickOutside(): void {\n setOpen(false)\n}\n\nfunction openDropdown(): void {\n setOpen(true)\n}\n\n/**\n * Стек слоёв возвращает фокус на триггер сразу после закрытия, а у триггера\n * открытие висит на `focus`. Без флага `Escape` из дерева закрывал бы панель и\n * тем же движением открывал её заново.\n */\nlet suppressFocusOpen = false\n\nfunction closeDropdown(): void {\n suppressFocusOpen = true\n setOpen(false)\n setTimeout(() => {\n suppressFocusOpen = false\n }, 0)\n}\n\nfunction toggleDropdown(): void {\n setOpen(!open.value)\n}\n\n// Клик по триггеру сам переключает панель через `@click`; флаг гасит открытие\n// по приходящему следом `focus`, иначе один клик открыл бы и тут же закрыл.\nfunction onTriggerPointerDown(): void {\n hadPointerDownOnTrigger = true\n setTimeout(() => {\n hadPointerDownOnTrigger = false\n }, 0)\n}\n\nfunction onTriggerFocus(): void {\n if (hadPointerDownOnTrigger || suppressFocusOpen)\n return\n openDropdown()\n}\n\n// Возврат фокуса на триггер — из контракта слоя, а не руками через `nextTick`.\nuseOverlayLayer(open, closeDropdown, { root: panelEl })\n\nwatch(\n open,\n async (isOpen) => {\n if (!isOpen) {\n if (filterValue.value.trim().length > 0)\n filterValue.value = ''\n return\n }\n\n // Подсветка дерева идёт за значением: панель могли открыть после того, как\n // значение сменили снаружи.\n if (!props.multiple) {\n const v = props.modelValue\n if (v != null && !Array.isArray(v))\n treeRef.value?.setCurrentKey(v)\n }\n\n if (props.filterable) {\n await nextTick()\n filterInputRef.value?.focus()\n }\n },\n { immediate: true },\n)\n\nwatch(\n () => props.modelValue,\n (v) => {\n if (props.multiple)\n return\n if (v == null || Array.isArray(v)) {\n treeRef.value?.setCurrentKey(undefined)\n return\n }\n treeRef.value?.setCurrentKey(v)\n },\n)\n\nwatch(\n filterValue,\n (v) => {\n treeRef.value?.filter(v)\n },\n)\n\nfunction emitModel(next: GrTreeSelectModelValue) {\n emit('update:modelValue', next)\n emit('change', next)\n}\n\nfunction clear(): void {\n if (isDisabled.value || isReadonly.value)\n return\n\n const next: GrTreeSelectModelValue = props.multiple ? [] : null\n emitModel(next)\n emit('clear')\n if (!props.multiple)\n treeRef.value?.setCurrentKey(undefined)\n}\n\n/**\n * Отдаёт клавиатуру дереву. Панель под `v-show`, поэтому дерево уже в DOM, но\n * скрытый элемент сфокусировать нельзя — сначала открытие, потом кадр, потом\n * фокус. Дальше работают клавиши `GrTree`, а `Escape` возвращает фокус на\n * триггер силами общего стека слоёв.\n */\nasync function focusTree(): Promise<boolean> {\n if (!open.value)\n return false\n\n await nextTick()\n return treeRef.value?.focus() ?? false\n}\n\nfunction onTriggerKeydown(e: KeyboardEvent): void {\n if (isDisabled.value)\n return\n\n if (e.key === 'Escape') {\n e.preventDefault()\n closeDropdown()\n return\n }\n\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n openDropdown()\n\n // При `filterable` фокус уходит в поле поиска (см. watch на `open`), и\n // дальше в дерево ведёт стрелка уже оттуда.\n if (!props.filterable)\n void focusTree()\n }\n}\n\n/** Стрелка из поля поиска — единственный путь из фильтра в дерево. */\nfunction onFilterKeydown(e: KeyboardEvent): void {\n if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp')\n return\n\n e.preventDefault()\n void focusTree()\n}\n\n/** `Tab` уводит из панели — оставлять её открытой за спиной незачем. */\nfunction onPanelKeydown(e: KeyboardEvent): void {\n if (e.key === 'Tab')\n closeDropdown()\n}\n\n// Чекбоксы осмысленны только в множественном выборе: одиночный уже подсвечен\n// текущим узлом.\nconst checkboxMode = computed(() => props.multiple && props.showCheckbox)\n\n// В режиме чекбоксов каскад по родителям и детям считает `GrTree`, а не копия\n// его логики здесь: значение приходит обратно одним `update:checkedKeys`.\nfunction onCheckedKeys(keys: GrTreeKey[]): void {\n if (isDisabled.value || isReadonly.value)\n return\n\n emitModel([...keys])\n}\n\nfunction onNodeClick(data: T, node: GrTreeNode<T>): void {\n emit('nodeClick', data, node)\n\n if (isDisabled.value)\n return\n\n if (checkboxMode.value) {\n // Клик по строке — тот же переключатель отметки, что и сам чекбокс;\n // значение придёт через `update:checkedKeys`, поэтому здесь не эмитим.\n treeRef.value?.setChecked(node.key, !selectedKeySet.value.has(node.key))\n return\n }\n\n if (props.multiple) {\n const next = new Set(selectedKeySet.value)\n if (next.has(node.key))\n next.delete(node.key)\n else\n next.add(node.key)\n emitModel([...next])\n\n if (closeOnSelectResolved.value) {\n closeDropdown()\n }\n return\n }\n\n emitModel(node.key)\n if (closeOnSelectResolved.value) {\n closeDropdown()\n }\n}\n\n// Телепорт включается только ПОСЛЕ монтирования: иначе первый клиентский\n// рендер не совпадает с серверным и ломается гидрация (см. композабл).\nconst { target: portalTarget, enabled: teleportEnabled } = usePortalTarget()\n\n// Тема поддерева на телепортированную панель: в DOM она уезжает в `body`, то\n// есть вне обёртки провайдера, и `data-theme` с неё не наследуется. В дереве\n// компонентов панель остаётся внутри — `inject` доходит, и тему она ставит себе\n// сама.\nconst themeAttrs = useGrThemeAttrs()\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n v-click-outside=\"{ handler: onClickOutside, enabled: open, exclude: clickOutsideExclude }\"\n data-gr-tree-select\n class=\"relative\"\n @focusin=\"onFocusIn\"\n @focusout=\"onFocusOut\"\n >\n <!-- Нативная форма: hidden на каждый выбранный ключ. -->\n <template v-if=\"name\">\n <input\n v-for=\"key in selectedKeys\"\n :key=\"`hidden-${String(key)}`\"\n type=\"hidden\"\n :name=\"name\"\n :value=\"String(key)\"\n >\n </template>\n <div class=\"relative\">\n <div\n v-if=\"hasPrefix\"\n ref=\"prefixEl\"\n data-gr-tree-select-prefix\n class=\"absolute inset-y-px left-px flex items-center justify-center rounded-l-[var(--gr-radius-control)] border-r border-[var(--gr-brd)] px-2 text-[var(--gr-muted-fg)] pointer-events-none select-none truncate\"\n :style=\"prefixStyle\"\n aria-hidden=\"true\"\n >\n <slot name=\"prefix\" />\n </div>\n\n <input\n :id=\"fieldId\"\n ref=\"triggerEl\"\n data-testid=\"gr-tree-select-trigger\"\n data-gr-tree-select-trigger\n type=\"text\"\n :value=\"displayValue\"\n :placeholder=\"placeholder\"\n :disabled=\"isDisabled\"\n readonly\n role=\"combobox\"\n aria-readonly=\"true\"\n aria-haspopup=\"tree\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n :aria-controls=\"open && hasTree ? treeId : undefined\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-describedby=\"describedBy\"\n :aria-label=\"ariaLabel\"\n class=\"w-full rounded-[var(--gr-radius-control)] border placeholder:text-[var(--gr-muted-fg)] transition-colors duration-[var(--gr-duration-fast)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)]\"\n :class=\"[className, $slots.value ? 'text-transparent placeholder:text-transparent' : '']\"\n :style=\"triggerStyle\"\n @pointerdown=\"onTriggerPointerDown\"\n @click=\"toggleDropdown\"\n @focus=\"onTriggerFocus\"\n @keydown=\"onTriggerKeydown\"\n >\n\n <button\n v-if=\"clearable && hasSelection && !disabled && !isReadonly\"\n data-testid=\"gr-tree-select-clear\"\n data-gr-tree-select-clear\n type=\"button\"\n class=\"absolute top-1/2 -translate-y-1/2 right-3 h-6 w-6 inline-flex items-center justify-center rounded-[var(--gr-radius-control)] text-[var(--gr-muted-fg)] hover:text-[var(--gr-fg)] hover:bg-[color-mix(in_srgb,var(--gr-muted)_25%,transparent)]\"\n :aria-label=\"t('gr.common.clear', 'Clear')\"\n @click.stop=\"clear\"\n >\n <IconX class=\"inline-block h-4 w-4\" aria-hidden=\"true\" />\n </button>\n\n <span\n v-else\n data-testid=\"gr-tree-select-chevron\"\n data-gr-tree-select-chevron\n class=\"absolute top-1/2 -translate-y-1/2 right-3 text-[var(--gr-muted-fg)] pointer-events-none\"\n >\n <IconChevronDown\n class=\"inline-block h-4 w-4 transition-transform duration-[var(--gr-duration-fast)]\"\n :class=\"open ? 'rotate-180' : ''\"\n aria-hidden=\"true\"\n />\n </span>\n\n <div\n v-if=\"hasSuffix\"\n ref=\"suffixEl\"\n data-gr-tree-select-suffix\n class=\"absolute inset-y-px right-9 flex items-center justify-center border-l border-[var(--gr-brd)] px-2 text-[var(--gr-muted-fg)] pointer-events-none select-none truncate\"\n :class=\"suffixFixed ? '[direction:rtl]' : ''\"\n :style=\"suffixStyle\"\n aria-hidden=\"true\"\n >\n <slot name=\"suffix\" />\n </div>\n\n <div\n v-if=\"$slots.value\"\n class=\"absolute inset-y-0 left-3 right-9 flex items-center pointer-events-none\"\n :style=\"valueOverlayStyle\"\n >\n <slot\n name=\"value\"\n :value=\"modelValue\"\n :labels=\"selectedLabels\"\n :display-value=\"displayValue\"\n :path-labels=\"selectedPathLabels\"\n />\n </div>\n </div>\n\n <teleport :to=\"portalTarget\" :disabled=\"!teleportEnabled\">\n <transition\n :enter-active-class=\"panelPopTransition.enter\"\n :enter-from-class=\"panelPopTransition.enterFrom\"\n :enter-to-class=\"panelPopTransition.enterTo\"\n :leave-active-class=\"panelPopTransition.leave\"\n :leave-from-class=\"panelPopTransition.leaveFrom\"\n :leave-to-class=\"panelPopTransition.leaveTo\"\n >\n <div\n v-show=\"open\"\n ref=\"panelEl\"\n data-testid=\"gr-tree-select-panel\"\n v-bind=\"themeAttrs\"\n data-gr-tree-select-panel\n data-gr-overlay-root\n :style=\"floatingStyle\"\n @keydown=\"onPanelKeydown\"\n >\n <div :class=\"panelClasses\">\n <div v-if=\"filterable\" class=\"p-2 border-b border-[var(--gr-brd)]\">\n <GrInput\n ref=\"filterInputRef\"\n v-model=\"filterValue\"\n data-testid=\"gr-tree-select-filter\"\n data-gr-tree-select-filter\n type=\"search\"\n :inputmode=\"filterInputmode\"\n :placeholder=\"resolvedFilterPlaceholder\"\n size=\"sm\"\n @keydown=\"onFilterKeydown\"\n />\n </div>\n\n <!--\n Скроллер один. При виртуализации его берёт на себя дерево: своя\n высота нужна ему, чтобы посчитать окно, а вложенный скроллер поверх\n дал бы вторую полосу прокрутки на том же списке.\n -->\n <div\n class=\"p-1\"\n :class=\"virtual ? '' : 'overflow-auto'\"\n :style=\"virtual ? undefined : { maxHeight: `${dropdownMaxHeight}px` }\"\n >\n <div\n v-if=\"loading\"\n data-gr-tree-select-loading\n role=\"status\"\n :class=\"grTreeSelectStateClass\"\n >\n <slot name=\"loading\">\n <IconLoaderCircle class=\"block h-4 w-4 animate-spin\" aria-hidden=\"true\" />\n <span>{{ t('gr.treeSelect.loading', 'Loading…') }}</span>\n </slot>\n </div>\n\n <div v-else-if=\"(data?.length ?? 0) === 0\" :class=\"grTreeSelectStateClass\">\n <slot name=\"empty\">\n {{ t('gr.treeSelect.empty', 'No data') }}\n </slot>\n </div>\n\n <GrTree\n v-else\n :id=\"treeId\"\n ref=\"treeRef\"\n :size=\"resolvedSize\"\n :data=\"data\"\n :props=\"props.props\"\n :node-key=\"nodeKey as any\"\n :default-expanded-keys=\"defaultExpandedKeys\"\n :filter-node-method=\"filterNodeMethod\"\n :highlight-current=\"!multiple\"\n :virtual=\"virtual\"\n :max-height=\"dropdownMaxHeight\"\n :show-checkbox=\"checkboxMode\"\n :check-strictly=\"checkStrictly\"\n :checked-keys=\"selectedKeys\"\n @node-click=\"onNodeClick\"\n @update:checked-keys=\"onCheckedKeys\"\n >\n <template #default=\"{ node, data }\">\n <slot\n name=\"node\"\n :node=\"node\"\n :data=\"data\"\n :selected=\"selectedKeySet.has(node.key)\"\n >\n <div class=\"flex items-center gap-2 min-w-0\">\n <!-- Чекбокс дерева уже показывает отметку: вторая галочка\n рядом читалась бы как ошибка. -->\n <span\n v-if=\"!checkboxMode\"\n data-gr-tree-select-check\n class=\"inline-block h-4 w-4 shrink-0\"\n aria-hidden=\"true\"\n >\n <IconCheck v-if=\"selectedKeySet.has(node.key)\" class=\"block h-4 w-4 text-[var(--gr-primary-text)]\" />\n </span>\n <span class=\"truncate\">{{ node.label }}</span>\n </div>\n </slot>\n </template>\n </GrTree>\n </div>\n </div>\n </div>\n </transition>\n </teleport>\n </div>\n</template>\n","<script setup lang=\"ts\" generic=\"T extends Record<string, any> = any\">\nimport { computed, nextTick, ref, useId, useSlots, watch } from 'vue'\n\nimport { usePortalTarget } from '../../composables/usePortalTarget'\n\nimport { vClickOutside } from '../../directives'\nimport { useFloating } from '../../composables/useFloating'\nimport { useOverlayLayer } from '../../composables/useOverlayLayer'\nimport { useControlAddons } from '../../composables/internal/useControlAddons'\nimport { useControlledOpen } from '../../composables/internal/useControlledOpen'\nimport { useGrComponentSize, useGrThemeAttrs } from '../GrConfigProvider/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useFocusWithin } from '../../composables/internal/useFocusWithin'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport GrInput from '../GrInput/GrInput.vue'\nimport GrTree, {\n type GrTreeInstance,\n type GrTreeKey,\n type GrTreeNode,\n} from '../GrTree'\nimport type { GrInputSize } from '../GrInput/GrInput.vue'\nimport { panelPopTransition } from '../shared/overlayTransition'\nimport type { GrTreeSelectModelValue, GrTreeSelectProps } from './grTreeSelectTypes'\nimport { grTreeSelectClass, grTreeSelectPanelClass, grTreeSelectStateClass, paddingX, trailingZoneWidth } from './grTreeSelectStyles'\n\nimport IconCheck from '~icons/lucide/check'\nimport IconChevronDown from '~icons/lucide/chevron-down'\nimport IconLoaderCircle from '~icons/lucide/loader-circle'\nimport IconX from '~icons/lucide/x'\n\nexport interface GrTreeSelectEmits<T extends Record<string, any> = any> {\n (e: 'update:modelValue', value: GrTreeSelectModelValue): void\n (e: 'change', value: GrTreeSelectModelValue): void\n /** Панель открылась/закрылась (`v-model:open`). */\n (e: 'update:open', value: boolean): void\n (e: 'clear'): void\n (e: 'nodeClick', data: T, node: GrTreeNode<T>): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrTreeSelectProps<T>>(),\n {\n props: () => ({\n children: 'children',\n label: 'label',\n }),\n nodeKey: 'id' as any,\n defaultExpandedKeys: () => [],\n disabled: false,\n placeholder: undefined,\n size: undefined,\n loading: false,\n invalid: false,\n readonly: false,\n required: false,\n ariaLabel: undefined,\n state: 'default',\n multiple: false,\n showCheckbox: false,\n checkStrictly: false,\n clearable: false,\n open: undefined,\n name: undefined,\n valueDisplay: 'label',\n filterable: false,\n filterPlaceholder: undefined,\n filterInputmode: undefined,\n filterNodeMethod: undefined,\n closeOnSelect: undefined,\n dropdownMaxHeight: 320,\n virtual: false,\n prefixMinWidth: undefined,\n prefixMaxWidth: undefined,\n suffixMinWidth: undefined,\n suffixMaxWidth: undefined,\n prefixFixed: false,\n suffixFixed: false,\n },\n)\n\nconst { t } = useGranularityTranslations()\n\nconst emit = defineEmits<GrTreeSelectEmits<T>>()\n\ndefineSlots<{\n /** Рендер значения внутри триггера (вместо дефолтного текста). */\n value?: (props: { value: GrTreeSelectModelValue; labels: string[]; displayValue: string; pathLabels?: string[] }) => any\n /** Рендер строки дерева. */\n node?: (props: { node: GrTreeNode<T>; data: T; selected: boolean }) => any\n /** Содержимое пустого состояния (когда нет данных). */\n empty?: () => any\n /** Содержимое панели, пока данные едут. */\n loading?: () => any\n /** Аддон слева от значения: иконка, код валюты, метка. */\n prefix?: () => any\n /** Аддон справа от значения, перед крестиком и шевроном. */\n suffix?: () => any\n}>()\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst triggerEl = ref<HTMLInputElement | null>(null)\nconst treeId = `gr-tree-select-tree-${useId()}`\n\n// Контекст `GrFormField` + общий контракт форм-контрола.\nconst field = useGrFormFieldContext()\nconst fieldId = computed(() => field?.id.value)\nconst describedBy = computed(() => field?.describedById.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid, required: isRequired, readonly: isReadonly } = useGrFormControl(() => props)\n\nfunction focus(): void {\n triggerEl.value?.focus()\n}\n\nfunction blur(): void {\n triggerEl.value?.blur()\n}\n\ndefineExpose({ focus, blur })\nconst filterInputRef = ref<InstanceType<typeof GrInput> | null>(null)\nconst treeRef = ref<GrTreeInstance | null>(null)\nconst panelEl = ref<HTMLElement | null>(null)\n\n// Панель телепортирована в `body`, то есть лежит вне корня, но для пользователя\n// она часть контрола: без неё уход фокуса в панель читался бы как `blur`.\nconst { onFocusIn, onFocusOut } = useFocusWithin(rootEl, {\n enter: event => emit('focus', event),\n leave: event => emit('blur', event),\n containers: () => [panelEl.value],\n})\n\nconst clickOutsideExclude = [() => panelEl.value]\n\nlet hadPointerDownOnTrigger = false\n\n// Имя `open` сохранено: читатели работают с computed-Ref.\nconst { open, setOpen: setOpenState } = useControlledOpen(\n () => props.open,\n next => emit('update:open', next),\n)\nconst filterValue = ref('')\n\nconst { floatingStyle } = useFloating(rootEl, panelEl, open, {\n placement: 'bottom-start',\n matchWidth: true,\n zIndexVar: '--gr-z-dropdown',\n})\n\nconst closeOnSelectResolved = computed(() => {\n return props.closeOnSelect ?? !props.multiple\n})\n\nfunction getChildrenKey() {\n return props.props.children ?? 'children'\n}\n\nfunction getLabelKey() {\n return props.props.label ?? 'label'\n}\n\nfunction nodeLabel(data: T): string {\n const key = getLabelKey() as keyof T\n const v = data[key]\n return v == null ? '' : String(v)\n}\n\nfunction nodeChildren(data: T): T[] {\n const key = getChildrenKey() as keyof T\n const v = data[key]\n return Array.isArray(v) ? (v as any) : []\n}\n\nfunction nodeKeyOf(data: T, index: number, parentKey: GrTreeKey | undefined): GrTreeKey {\n const keyName = props.nodeKey as keyof T\n const v = data[keyName]\n if (v != null)\n return v as GrTreeKey\n\n return parentKey == null ? index : `${String(parentKey)}:${index}`\n}\n\nconst labelByKey = computed(() => {\n const map = new Map<GrTreeKey, string>()\n\n const walk = (items: T[], parentKey: GrTreeKey | undefined) => {\n for (let i = 0; i < items.length; i++) {\n const item = items[i]\n const key = nodeKeyOf(item, i, parentKey)\n map.set(key, nodeLabel(item))\n walk(nodeChildren(item), key)\n }\n }\n\n walk(props.data ?? [], undefined)\n return map\n})\n\nconst pathLabelsByKey = computed(() => {\n const map = new Map<GrTreeKey, string[]>()\n\n const walk = (items: T[], parentKey: GrTreeKey | undefined, parentPath: string[]) => {\n for (let i = 0; i < items.length; i++) {\n const item = items[i]\n const key = nodeKeyOf(item, i, parentKey)\n const label = nodeLabel(item)\n const path = [...parentPath, label]\n map.set(key, path)\n walk(nodeChildren(item), key, path)\n }\n }\n\n walk(props.data ?? [], undefined, [])\n return map\n})\n\nconst selectedKeys = computed<GrTreeKey[]>(() => {\n if (props.multiple) {\n return Array.isArray(props.modelValue) ? props.modelValue : []\n }\n\n const v = props.modelValue\n if (v == null || Array.isArray(v))\n return []\n return [v]\n})\n\nconst selectedKeySet = computed(() => new Set(selectedKeys.value))\n\nconst selectedLabels = computed(() => {\n const map = labelByKey.value\n return selectedKeys.value.map((k) => map.get(k) ?? String(k))\n})\n\nconst selectedPathLabels = computed<string[] | undefined>(() => {\n if (props.multiple)\n return undefined\n\n const key = selectedKeys.value[0]\n if (key == null)\n return undefined\n\n const path = pathLabelsByKey.value.get(key)\n if (path?.length)\n return path\n\n const labels = selectedLabels.value\n return labels.length ? [labels[0]] : undefined\n})\n\nconst displayValue = computed(() => {\n const labels = selectedLabels.value\n if (labels.length === 0)\n return ''\n\n if (!props.multiple) {\n if (props.valueDisplay === 'path') {\n const path = selectedPathLabels.value\n if (path?.length)\n return path.join(' / ')\n }\n return labels[0]\n }\n\n if (labels.length === 1)\n return labels[0]\n return `${labels[0]} +${labels.length - 1}`\n})\n\nconst hasSelection = computed(() => selectedKeys.value.length > 0)\n\n/** Дерево рендерится только когда есть что показать — ссылаться иначе не на что. */\nconst hasTree = computed(() => !props.loading && (props.data?.length ?? 0) > 0)\n\nconst resolvedFilterPlaceholder = computed(() => {\n return props.filterPlaceholder ?? t('gr.treeSelect.filterPlaceholder', 'Search…')\n})\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrTreeSelect' })\n\nconst className = computed(() => {\n return grTreeSelectClass({\n size: resolvedSize.value,\n state: props.state,\n invalid: isInvalid.value,\n disabled: isDisabled.value,\n })\n})\n\nconst panelClasses = computed(() => {\n return grTreeSelectPanelClass\n})\n\nconst ADDON_MIN_WIDTH_BY_SIZE: Record<GrInputSize, string> = {\n xs: '2rem',\n sm: '2.25rem',\n md: '2.5rem',\n lg: '3rem',\n}\n\nconst slots = useSlots()\n\nconst {\n hasPrefix,\n hasSuffix,\n prefixEl,\n suffixEl,\n prefixStyle,\n suffixStyle,\n fieldPadding: triggerStyle,\n} = useControlAddons(() => props, {\n defaultMinWidth: () => ADDON_MIN_WIDTH_BY_SIZE[resolvedSize.value],\n paddingX: () => paddingX[resolvedSize.value],\n // Место под крестик и шеврон уже отдано классом `pr-9`, но инлайн-паддинг\n // перекрывает класс целиком — с суффиксом эту зону приходится вернуть числом.\n trailingReserve: () => (slots.suffix ? trailingZoneWidth : '0px'),\n})\n\n// Слот `value` рисуется поверх поля и обязан жить в тех же границах, что и\n// текст под ним. Без аддонов границы задаёт класс — стиль остаётся пустым.\nconst valueOverlayStyle = computed(() => ({\n left: hasPrefix.value ? triggerStyle.value.paddingLeft : undefined,\n right: hasSuffix.value ? triggerStyle.value.paddingRight : undefined,\n}))\n\nfunction setOpen(next: boolean) {\n if (isDisabled.value || isReadonly.value)\n return\n\n setOpenState(next)\n}\n\nfunction onClickOutside(): void {\n setOpen(false)\n}\n\nfunction openDropdown(): void {\n setOpen(true)\n}\n\n/**\n * Стек слоёв возвращает фокус на триггер сразу после закрытия, а у триггера\n * открытие висит на `focus`. Без флага `Escape` из дерева закрывал бы панель и\n * тем же движением открывал её заново.\n */\nlet suppressFocusOpen = false\n\nfunction closeDropdown(): void {\n suppressFocusOpen = true\n setOpen(false)\n setTimeout(() => {\n suppressFocusOpen = false\n }, 0)\n}\n\nfunction toggleDropdown(): void {\n setOpen(!open.value)\n}\n\n// Клик по триггеру сам переключает панель через `@click`; флаг гасит открытие\n// по приходящему следом `focus`, иначе один клик открыл бы и тут же закрыл.\nfunction onTriggerPointerDown(): void {\n hadPointerDownOnTrigger = true\n setTimeout(() => {\n hadPointerDownOnTrigger = false\n }, 0)\n}\n\nfunction onTriggerFocus(): void {\n if (hadPointerDownOnTrigger || suppressFocusOpen)\n return\n openDropdown()\n}\n\n// Возврат фокуса на триггер — из контракта слоя, а не руками через `nextTick`.\nuseOverlayLayer(open, closeDropdown, { root: panelEl })\n\nwatch(\n open,\n async (isOpen) => {\n if (!isOpen) {\n if (filterValue.value.trim().length > 0)\n filterValue.value = ''\n return\n }\n\n // Подсветка дерева идёт за значением: панель могли открыть после того, как\n // значение сменили снаружи.\n if (!props.multiple) {\n const v = props.modelValue\n if (v != null && !Array.isArray(v))\n treeRef.value?.setCurrentKey(v)\n }\n\n if (props.filterable) {\n await nextTick()\n filterInputRef.value?.focus()\n }\n },\n { immediate: true },\n)\n\nwatch(\n () => props.modelValue,\n (v) => {\n if (props.multiple)\n return\n if (v == null || Array.isArray(v)) {\n treeRef.value?.setCurrentKey(undefined)\n return\n }\n treeRef.value?.setCurrentKey(v)\n },\n)\n\nwatch(\n filterValue,\n (v) => {\n treeRef.value?.filter(v)\n },\n)\n\nfunction emitModel(next: GrTreeSelectModelValue) {\n emit('update:modelValue', next)\n emit('change', next)\n}\n\nfunction clear(): void {\n if (isDisabled.value || isReadonly.value)\n return\n\n const next: GrTreeSelectModelValue = props.multiple ? [] : null\n emitModel(next)\n emit('clear')\n if (!props.multiple)\n treeRef.value?.setCurrentKey(undefined)\n}\n\n/**\n * Отдаёт клавиатуру дереву. Панель под `v-show`, поэтому дерево уже в DOM, но\n * скрытый элемент сфокусировать нельзя — сначала открытие, потом кадр, потом\n * фокус. Дальше работают клавиши `GrTree`, а `Escape` возвращает фокус на\n * триггер силами общего стека слоёв.\n */\nasync function focusTree(): Promise<boolean> {\n if (!open.value)\n return false\n\n await nextTick()\n return treeRef.value?.focus() ?? false\n}\n\nfunction onTriggerKeydown(e: KeyboardEvent): void {\n if (isDisabled.value)\n return\n\n if (e.key === 'Escape') {\n e.preventDefault()\n closeDropdown()\n return\n }\n\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n openDropdown()\n\n // При `filterable` фокус уходит в поле поиска (см. watch на `open`), и\n // дальше в дерево ведёт стрелка уже оттуда.\n if (!props.filterable)\n void focusTree()\n }\n}\n\n/** Стрелка из поля поиска — единственный путь из фильтра в дерево. */\nfunction onFilterKeydown(e: KeyboardEvent): void {\n if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp')\n return\n\n e.preventDefault()\n void focusTree()\n}\n\n/** `Tab` уводит из панели — оставлять её открытой за спиной незачем. */\nfunction onPanelKeydown(e: KeyboardEvent): void {\n if (e.key === 'Tab')\n closeDropdown()\n}\n\n// Чекбоксы осмысленны только в множественном выборе: одиночный уже подсвечен\n// текущим узлом.\nconst checkboxMode = computed(() => props.multiple && props.showCheckbox)\n\n// В режиме чекбоксов каскад по родителям и детям считает `GrTree`, а не копия\n// его логики здесь: значение приходит обратно одним `update:checkedKeys`.\nfunction onCheckedKeys(keys: GrTreeKey[]): void {\n if (isDisabled.value || isReadonly.value)\n return\n\n emitModel([...keys])\n}\n\nfunction onNodeClick(data: T, node: GrTreeNode<T>): void {\n emit('nodeClick', data, node)\n\n if (isDisabled.value)\n return\n\n if (checkboxMode.value) {\n // Клик по строке — тот же переключатель отметки, что и сам чекбокс;\n // значение придёт через `update:checkedKeys`, поэтому здесь не эмитим.\n treeRef.value?.setChecked(node.key, !selectedKeySet.value.has(node.key))\n return\n }\n\n if (props.multiple) {\n const next = new Set(selectedKeySet.value)\n if (next.has(node.key))\n next.delete(node.key)\n else\n next.add(node.key)\n emitModel([...next])\n\n if (closeOnSelectResolved.value) {\n closeDropdown()\n }\n return\n }\n\n emitModel(node.key)\n if (closeOnSelectResolved.value) {\n closeDropdown()\n }\n}\n\n// Телепорт включается только ПОСЛЕ монтирования: иначе первый клиентский\n// рендер не совпадает с серверным и ломается гидрация (см. композабл).\nconst { target: portalTarget, enabled: teleportEnabled } = usePortalTarget()\n\n// Тема поддерева на телепортированную панель: в DOM она уезжает в `body`, то\n// есть вне обёртки провайдера, и `data-theme` с неё не наследуется. В дереве\n// компонентов панель остаётся внутри — `inject` доходит, и тему она ставит себе\n// сама.\nconst themeAttrs = useGrThemeAttrs()\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n v-click-outside=\"{ handler: onClickOutside, enabled: open, exclude: clickOutsideExclude }\"\n data-gr-tree-select\n class=\"relative\"\n @focusin=\"onFocusIn\"\n @focusout=\"onFocusOut\"\n >\n <!-- Нативная форма: hidden на каждый выбранный ключ. -->\n <template v-if=\"name\">\n <input\n v-for=\"key in selectedKeys\"\n :key=\"`hidden-${String(key)}`\"\n type=\"hidden\"\n :name=\"name\"\n :value=\"String(key)\"\n >\n </template>\n <div class=\"relative\">\n <div\n v-if=\"hasPrefix\"\n ref=\"prefixEl\"\n data-gr-tree-select-prefix\n class=\"absolute inset-y-px left-px flex items-center justify-center rounded-l-[var(--gr-radius-control)] border-r border-[var(--gr-brd)] px-2 text-[var(--gr-muted-fg)] pointer-events-none select-none truncate\"\n :style=\"prefixStyle\"\n aria-hidden=\"true\"\n >\n <slot name=\"prefix\" />\n </div>\n\n <input\n :id=\"fieldId\"\n ref=\"triggerEl\"\n data-testid=\"gr-tree-select-trigger\"\n data-gr-tree-select-trigger\n type=\"text\"\n :value=\"displayValue\"\n :placeholder=\"placeholder\"\n :disabled=\"isDisabled\"\n readonly\n role=\"combobox\"\n aria-readonly=\"true\"\n aria-haspopup=\"tree\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n :aria-controls=\"open && hasTree ? treeId : undefined\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-describedby=\"describedBy\"\n :aria-label=\"ariaLabel\"\n class=\"w-full rounded-[var(--gr-radius-control)] border placeholder:text-[var(--gr-muted-fg)] transition-colors duration-[var(--gr-duration-fast)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)]\"\n :class=\"[className, $slots.value ? 'text-transparent placeholder:text-transparent' : '']\"\n :style=\"triggerStyle\"\n @pointerdown=\"onTriggerPointerDown\"\n @click=\"toggleDropdown\"\n @focus=\"onTriggerFocus\"\n @keydown=\"onTriggerKeydown\"\n >\n\n <button\n v-if=\"clearable && hasSelection && !disabled && !isReadonly\"\n data-testid=\"gr-tree-select-clear\"\n data-gr-tree-select-clear\n type=\"button\"\n class=\"absolute top-1/2 -translate-y-1/2 right-3 h-6 w-6 inline-flex items-center justify-center rounded-[var(--gr-radius-control)] text-[var(--gr-muted-fg)] hover:text-[var(--gr-fg)] hover:bg-[color-mix(in_srgb,var(--gr-muted)_25%,transparent)]\"\n :aria-label=\"t('gr.common.clear', 'Clear')\"\n @click.stop=\"clear\"\n >\n <IconX class=\"inline-block h-4 w-4\" aria-hidden=\"true\" />\n </button>\n\n <span\n v-else\n data-testid=\"gr-tree-select-chevron\"\n data-gr-tree-select-chevron\n class=\"absolute top-1/2 -translate-y-1/2 right-3 text-[var(--gr-muted-fg)] pointer-events-none\"\n >\n <IconChevronDown\n class=\"inline-block h-4 w-4 transition-transform duration-[var(--gr-duration-fast)]\"\n :class=\"open ? 'rotate-180' : ''\"\n aria-hidden=\"true\"\n />\n </span>\n\n <div\n v-if=\"hasSuffix\"\n ref=\"suffixEl\"\n data-gr-tree-select-suffix\n class=\"absolute inset-y-px right-9 flex items-center justify-center border-l border-[var(--gr-brd)] px-2 text-[var(--gr-muted-fg)] pointer-events-none select-none truncate\"\n :class=\"suffixFixed ? '[direction:rtl]' : ''\"\n :style=\"suffixStyle\"\n aria-hidden=\"true\"\n >\n <slot name=\"suffix\" />\n </div>\n\n <div\n v-if=\"$slots.value\"\n class=\"absolute inset-y-0 left-3 right-9 flex items-center pointer-events-none\"\n :style=\"valueOverlayStyle\"\n >\n <slot\n name=\"value\"\n :value=\"modelValue\"\n :labels=\"selectedLabels\"\n :display-value=\"displayValue\"\n :path-labels=\"selectedPathLabels\"\n />\n </div>\n </div>\n\n <teleport :to=\"portalTarget\" :disabled=\"!teleportEnabled\">\n <transition\n :enter-active-class=\"panelPopTransition.enter\"\n :enter-from-class=\"panelPopTransition.enterFrom\"\n :enter-to-class=\"panelPopTransition.enterTo\"\n :leave-active-class=\"panelPopTransition.leave\"\n :leave-from-class=\"panelPopTransition.leaveFrom\"\n :leave-to-class=\"panelPopTransition.leaveTo\"\n >\n <div\n v-show=\"open\"\n ref=\"panelEl\"\n data-testid=\"gr-tree-select-panel\"\n v-bind=\"themeAttrs\"\n data-gr-tree-select-panel\n data-gr-overlay-root\n :style=\"floatingStyle\"\n @keydown=\"onPanelKeydown\"\n >\n <div :class=\"panelClasses\">\n <div v-if=\"filterable\" class=\"p-2 border-b border-[var(--gr-brd)]\">\n <GrInput\n ref=\"filterInputRef\"\n v-model=\"filterValue\"\n data-testid=\"gr-tree-select-filter\"\n data-gr-tree-select-filter\n type=\"search\"\n :inputmode=\"filterInputmode\"\n :placeholder=\"resolvedFilterPlaceholder\"\n size=\"sm\"\n @keydown=\"onFilterKeydown\"\n />\n </div>\n\n <!--\n Скроллер один. При виртуализации его берёт на себя дерево: своя\n высота нужна ему, чтобы посчитать окно, а вложенный скроллер поверх\n дал бы вторую полосу прокрутки на том же списке.\n -->\n <div\n class=\"p-1\"\n :class=\"virtual ? '' : 'overflow-auto'\"\n :style=\"virtual ? undefined : { maxHeight: `${dropdownMaxHeight}px` }\"\n >\n <div\n v-if=\"loading\"\n data-gr-tree-select-loading\n role=\"status\"\n :class=\"grTreeSelectStateClass\"\n >\n <slot name=\"loading\">\n <IconLoaderCircle class=\"block h-4 w-4 animate-spin\" aria-hidden=\"true\" />\n <span>{{ t('gr.treeSelect.loading', 'Loading…') }}</span>\n </slot>\n </div>\n\n <div v-else-if=\"(data?.length ?? 0) === 0\" :class=\"grTreeSelectStateClass\">\n <slot name=\"empty\">\n {{ t('gr.treeSelect.empty', 'No data') }}\n </slot>\n </div>\n\n <GrTree\n v-else\n :id=\"treeId\"\n ref=\"treeRef\"\n :size=\"resolvedSize\"\n :data=\"data\"\n :props=\"props.props\"\n :node-key=\"nodeKey as any\"\n :default-expanded-keys=\"defaultExpandedKeys\"\n :filter-node-method=\"filterNodeMethod\"\n :highlight-current=\"!multiple\"\n :virtual=\"virtual\"\n :max-height=\"dropdownMaxHeight\"\n :show-checkbox=\"checkboxMode\"\n :check-strictly=\"checkStrictly\"\n :checked-keys=\"selectedKeys\"\n @node-click=\"onNodeClick\"\n @update:checked-keys=\"onCheckedKeys\"\n >\n <template #default=\"{ node, data }\">\n <slot\n name=\"node\"\n :node=\"node\"\n :data=\"data\"\n :selected=\"selectedKeySet.has(node.key)\"\n >\n <div class=\"flex items-center gap-2 min-w-0\">\n <!-- Чекбокс дерева уже показывает отметку: вторая галочка\n рядом читалась бы как ошибка. -->\n <span\n v-if=\"!checkboxMode\"\n data-gr-tree-select-check\n class=\"inline-block h-4 w-4 shrink-0\"\n aria-hidden=\"true\"\n >\n <IconCheck v-if=\"selectedKeySet.has(node.key)\" class=\"block h-4 w-4 text-[var(--gr-primary-text)]\" />\n </span>\n <span class=\"truncate\">{{ node.label }}</span>\n </div>\n </slot>\n </template>\n </GrTree>\n </div>\n </div>\n </div>\n </transition>\n </teleport>\n </div>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GrTreeSelect-iJnMl2t9.js","names":["$slots"],"sources":["../../../../src/components/GrTreeSelect/GrTreeSelect.vue","../../../../src/components/GrTreeSelect/GrTreeSelect.vue"],"sourcesContent":["<script setup lang=\"ts\" generic=\"T extends Record<string, any> = any\">\nimport { computed, nextTick, ref, useId, useSlots, watch } from 'vue'\n\nimport { usePortalTarget } from '../../composables/usePortalTarget'\n\nimport { vClickOutside } from '../../directives'\nimport { useFloating } from '../../composables/useFloating'\nimport { useOverlayLayer } from '../../composables/useOverlayLayer'\nimport { useControlAddons } from '../../composables/internal/useControlAddons'\nimport { useControlledOpen } from '../../composables/internal/useControlledOpen'\nimport { useGrComponentSize, useGrThemeAttrs } from '../GrConfigProvider/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useFocusWithin } from '../../composables/internal/useFocusWithin'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport GrInput from '../GrInput/GrInput.vue'\nimport GrTree, {\n type GrTreeInstance,\n type GrTreeKey,\n type GrTreeNode,\n} from '../GrTree'\nimport type { GrInputSize } from '../GrInput/GrInput.vue'\nimport { panelPopTransition } from '../shared/overlayTransition'\nimport type { GrTreeSelectModelValue, GrTreeSelectProps } from './grTreeSelectTypes'\nimport { grTreeSelectClass, grTreeSelectPanelClass, grTreeSelectStateClass, paddingX, trailingZoneWidth } from './grTreeSelectStyles'\n\nimport IconCheck from '~icons/lucide/check'\nimport IconChevronDown from '~icons/lucide/chevron-down'\nimport IconLoaderCircle from '~icons/lucide/loader-circle'\nimport IconX from '~icons/lucide/x'\n\nexport interface GrTreeSelectEmits<T extends Record<string, any> = any> {\n (e: 'update:modelValue', value: GrTreeSelectModelValue): void\n (e: 'change', value: GrTreeSelectModelValue): void\n /** Панель открылась/закрылась (`v-model:open`). */\n (e: 'update:open', value: boolean): void\n (e: 'clear'): void\n (e: 'nodeClick', data: T, node: GrTreeNode<T>): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrTreeSelectProps<T>>(),\n {\n props: () => ({\n children: 'children',\n label: 'label',\n }),\n nodeKey: 'id' as any,\n defaultExpandedKeys: () => [],\n disabled: false,\n placeholder: undefined,\n size: undefined,\n loading: false,\n invalid: false,\n readonly: false,\n required: false,\n ariaLabel: undefined,\n state: 'default',\n multiple: false,\n showCheckbox: false,\n checkStrictly: false,\n clearable: false,\n open: undefined,\n name: undefined,\n valueDisplay: 'label',\n filterable: false,\n filterPlaceholder: undefined,\n filterInputmode: undefined,\n filterNodeMethod: undefined,\n closeOnSelect: undefined,\n dropdownMaxHeight: 320,\n virtual: false,\n prefixMinWidth: undefined,\n prefixMaxWidth: undefined,\n suffixMinWidth: undefined,\n suffixMaxWidth: undefined,\n prefixFixed: false,\n suffixFixed: false,\n },\n)\n\nconst { t } = useGranularityTranslations()\n\nconst emit = defineEmits<GrTreeSelectEmits<T>>()\n\ndefineSlots<{\n /** Рендер значения внутри триггера (вместо дефолтного текста). */\n value?: (props: { value: GrTreeSelectModelValue; labels: string[]; displayValue: string; pathLabels?: string[] }) => any\n /** Рендер строки дерева. */\n node?: (props: { node: GrTreeNode<T>; data: T; selected: boolean }) => any\n /** Содержимое пустого состояния (когда нет данных). */\n empty?: () => any\n /** Содержимое панели, пока данные едут. */\n loading?: () => any\n /** Аддон слева от значения: иконка, код валюты, метка. */\n prefix?: () => any\n /** Аддон справа от значения, перед крестиком и шевроном. */\n suffix?: () => any\n}>()\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst triggerEl = ref<HTMLInputElement | null>(null)\nconst treeId = `gr-tree-select-tree-${useId()}`\n\n// Контекст `GrFormField` + общий контракт форм-контрола.\nconst field = useGrFormFieldContext()\nconst fieldId = computed(() => field?.id.value)\nconst describedBy = computed(() => field?.describedById.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid, required: isRequired, readonly: isReadonly } = useGrFormControl(() => props)\n\nfunction focus(): void {\n triggerEl.value?.focus()\n}\n\nfunction blur(): void {\n triggerEl.value?.blur()\n}\n\ndefineExpose({ focus, blur })\nconst filterInputRef = ref<InstanceType<typeof GrInput> | null>(null)\nconst treeRef = ref<GrTreeInstance | null>(null)\nconst panelEl = ref<HTMLElement | null>(null)\n\n// Панель телепортирована в `body`, то есть лежит вне корня, но для пользователя\n// она часть контрола: без неё уход фокуса в панель читался бы как `blur`.\nconst { onFocusIn, onFocusOut } = useFocusWithin(rootEl, {\n enter: event => emit('focus', event),\n leave: event => emit('blur', event),\n containers: () => [panelEl.value],\n})\n\nconst clickOutsideExclude = [() => panelEl.value]\n\nlet hadPointerDownOnTrigger = false\n\n// Имя `open` сохранено: читатели работают с computed-Ref.\nconst { open, setOpen: setOpenState } = useControlledOpen(\n () => props.open,\n next => emit('update:open', next),\n)\nconst filterValue = ref('')\n\nconst { floatingStyle } = useFloating(rootEl, panelEl, open, {\n placement: 'bottom-start',\n matchWidth: true,\n zIndexVar: '--gr-z-dropdown',\n})\n\nconst closeOnSelectResolved = computed(() => {\n return props.closeOnSelect ?? !props.multiple\n})\n\nfunction getChildrenKey() {\n return props.props.children ?? 'children'\n}\n\nfunction getLabelKey() {\n return props.props.label ?? 'label'\n}\n\nfunction nodeLabel(data: T): string {\n const key = getLabelKey() as keyof T\n const v = data[key]\n return v == null ? '' : String(v)\n}\n\nfunction nodeChildren(data: T): T[] {\n const key = getChildrenKey() as keyof T\n const v = data[key]\n return Array.isArray(v) ? (v as any) : []\n}\n\nfunction nodeKeyOf(data: T, index: number, parentKey: GrTreeKey | undefined): GrTreeKey {\n const keyName = props.nodeKey as keyof T\n const v = data[keyName]\n if (v != null)\n return v as GrTreeKey\n\n return parentKey == null ? index : `${String(parentKey)}:${index}`\n}\n\nconst labelByKey = computed(() => {\n const map = new Map<GrTreeKey, string>()\n\n const walk = (items: T[], parentKey: GrTreeKey | undefined) => {\n for (let i = 0; i < items.length; i++) {\n const item = items[i]\n const key = nodeKeyOf(item, i, parentKey)\n map.set(key, nodeLabel(item))\n walk(nodeChildren(item), key)\n }\n }\n\n walk(props.data ?? [], undefined)\n return map\n})\n\nconst pathLabelsByKey = computed(() => {\n const map = new Map<GrTreeKey, string[]>()\n\n const walk = (items: T[], parentKey: GrTreeKey | undefined, parentPath: string[]) => {\n for (let i = 0; i < items.length; i++) {\n const item = items[i]\n const key = nodeKeyOf(item, i, parentKey)\n const label = nodeLabel(item)\n const path = [...parentPath, label]\n map.set(key, path)\n walk(nodeChildren(item), key, path)\n }\n }\n\n walk(props.data ?? [], undefined, [])\n return map\n})\n\nconst selectedKeys = computed<GrTreeKey[]>(() => {\n if (props.multiple) {\n return Array.isArray(props.modelValue) ? props.modelValue : []\n }\n\n const v = props.modelValue\n if (v == null || Array.isArray(v))\n return []\n return [v]\n})\n\nconst selectedKeySet = computed(() => new Set(selectedKeys.value))\n\nconst selectedLabels = computed(() => {\n const map = labelByKey.value\n return selectedKeys.value.map((k) => map.get(k) ?? String(k))\n})\n\nconst selectedPathLabels = computed<string[] | undefined>(() => {\n if (props.multiple)\n return undefined\n\n const key = selectedKeys.value[0]\n if (key == null)\n return undefined\n\n const path = pathLabelsByKey.value.get(key)\n if (path?.length)\n return path\n\n const labels = selectedLabels.value\n return labels.length ? [labels[0]] : undefined\n})\n\nconst displayValue = computed(() => {\n const labels = selectedLabels.value\n if (labels.length === 0)\n return ''\n\n if (!props.multiple) {\n if (props.valueDisplay === 'path') {\n const path = selectedPathLabels.value\n if (path?.length)\n return path.join(' / ')\n }\n return labels[0]\n }\n\n if (labels.length === 1)\n return labels[0]\n return `${labels[0]} +${labels.length - 1}`\n})\n\nconst hasSelection = computed(() => selectedKeys.value.length > 0)\n\n/** Дерево рендерится только когда есть что показать — ссылаться иначе не на что. */\nconst hasTree = computed(() => !props.loading && (props.data?.length ?? 0) > 0)\n\nconst resolvedFilterPlaceholder = computed(() => {\n return props.filterPlaceholder ?? t('gr.treeSelect.filterPlaceholder', 'Search…')\n})\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrTreeSelect' })\n\nconst className = computed(() => {\n return grTreeSelectClass({\n size: resolvedSize.value,\n state: props.state,\n invalid: isInvalid.value,\n disabled: isDisabled.value,\n })\n})\n\nconst panelClasses = computed(() => {\n return grTreeSelectPanelClass\n})\n\nconst ADDON_MIN_WIDTH_BY_SIZE: Record<GrInputSize, string> = {\n xs: '2rem',\n sm: '2.25rem',\n md: '2.5rem',\n lg: '3rem',\n}\n\nconst slots = useSlots()\n\nconst {\n hasPrefix,\n hasSuffix,\n prefixEl,\n suffixEl,\n prefixStyle,\n suffixStyle,\n fieldPadding: triggerStyle,\n} = useControlAddons(() => props, {\n defaultMinWidth: () => ADDON_MIN_WIDTH_BY_SIZE[resolvedSize.value],\n paddingX: () => paddingX[resolvedSize.value],\n // Место под крестик и шеврон уже отдано классом `pr-9`, но инлайн-паддинг\n // перекрывает класс целиком — с суффиксом эту зону приходится вернуть числом.\n trailingReserve: () => (slots.suffix ? trailingZoneWidth : '0px'),\n})\n\n// Слот `value` рисуется поверх поля и обязан жить в тех же границах, что и\n// текст под ним. Без аддонов границы задаёт класс — стиль остаётся пустым.\nconst valueOverlayStyle = computed(() => ({\n left: hasPrefix.value ? triggerStyle.value.paddingLeft : undefined,\n right: hasSuffix.value ? triggerStyle.value.paddingRight : undefined,\n}))\n\nfunction setOpen(next: boolean) {\n if (isDisabled.value || isReadonly.value)\n return\n\n setOpenState(next)\n}\n\nfunction onClickOutside(): void {\n setOpen(false)\n}\n\nfunction openDropdown(): void {\n setOpen(true)\n}\n\n/**\n * Стек слоёв возвращает фокус на триггер сразу после закрытия, а у триггера\n * открытие висит на `focus`. Без флага `Escape` из дерева закрывал бы панель и\n * тем же движением открывал её заново.\n */\nlet suppressFocusOpen = false\n\nfunction closeDropdown(): void {\n suppressFocusOpen = true\n setOpen(false)\n setTimeout(() => {\n suppressFocusOpen = false\n }, 0)\n}\n\nfunction toggleDropdown(): void {\n setOpen(!open.value)\n}\n\n// Клик по триггеру сам переключает панель через `@click`; флаг гасит открытие\n// по приходящему следом `focus`, иначе один клик открыл бы и тут же закрыл.\nfunction onTriggerPointerDown(): void {\n hadPointerDownOnTrigger = true\n setTimeout(() => {\n hadPointerDownOnTrigger = false\n }, 0)\n}\n\nfunction onTriggerFocus(): void {\n if (hadPointerDownOnTrigger || suppressFocusOpen)\n return\n openDropdown()\n}\n\n// Возврат фокуса на триггер — из контракта слоя, а не руками через `nextTick`.\nuseOverlayLayer(open, closeDropdown, { root: panelEl })\n\nwatch(\n open,\n async (isOpen) => {\n if (!isOpen) {\n if (filterValue.value.trim().length > 0)\n filterValue.value = ''\n return\n }\n\n // Подсветка дерева идёт за значением: панель могли открыть после того, как\n // значение сменили снаружи.\n if (!props.multiple) {\n const v = props.modelValue\n if (v != null && !Array.isArray(v))\n treeRef.value?.setCurrentKey(v)\n }\n\n if (props.filterable) {\n await nextTick()\n filterInputRef.value?.focus()\n }\n },\n { immediate: true },\n)\n\nwatch(\n () => props.modelValue,\n (v) => {\n if (props.multiple)\n return\n if (v == null || Array.isArray(v)) {\n treeRef.value?.setCurrentKey(undefined)\n return\n }\n treeRef.value?.setCurrentKey(v)\n },\n)\n\nwatch(\n filterValue,\n (v) => {\n treeRef.value?.filter(v)\n },\n)\n\nfunction emitModel(next: GrTreeSelectModelValue) {\n emit('update:modelValue', next)\n emit('change', next)\n}\n\nfunction clear(): void {\n if (isDisabled.value || isReadonly.value)\n return\n\n const next: GrTreeSelectModelValue = props.multiple ? [] : null\n emitModel(next)\n emit('clear')\n if (!props.multiple)\n treeRef.value?.setCurrentKey(undefined)\n}\n\n/**\n * Отдаёт клавиатуру дереву. Панель под `v-show`, поэтому дерево уже в DOM, но\n * скрытый элемент сфокусировать нельзя — сначала открытие, потом кадр, потом\n * фокус. Дальше работают клавиши `GrTree`, а `Escape` возвращает фокус на\n * триггер силами общего стека слоёв.\n */\nasync function focusTree(): Promise<boolean> {\n if (!open.value)\n return false\n\n await nextTick()\n return treeRef.value?.focus() ?? false\n}\n\nfunction onTriggerKeydown(e: KeyboardEvent): void {\n if (isDisabled.value)\n return\n\n if (e.key === 'Escape') {\n e.preventDefault()\n closeDropdown()\n return\n }\n\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n openDropdown()\n\n // При `filterable` фокус уходит в поле поиска (см. watch на `open`), и\n // дальше в дерево ведёт стрелка уже оттуда.\n if (!props.filterable)\n void focusTree()\n }\n}\n\n/** Стрелка из поля поиска — единственный путь из фильтра в дерево. */\nfunction onFilterKeydown(e: KeyboardEvent): void {\n if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp')\n return\n\n e.preventDefault()\n void focusTree()\n}\n\n/** `Tab` уводит из панели — оставлять её открытой за спиной незачем. */\nfunction onPanelKeydown(e: KeyboardEvent): void {\n if (e.key === 'Tab')\n closeDropdown()\n}\n\n// Чекбоксы осмысленны только в множественном выборе: одиночный уже подсвечен\n// текущим узлом.\nconst checkboxMode = computed(() => props.multiple && props.showCheckbox)\n\n// В режиме чекбоксов каскад по родителям и детям считает `GrTree`, а не копия\n// его логики здесь: значение приходит обратно одним `update:checkedKeys`.\nfunction onCheckedKeys(keys: GrTreeKey[]): void {\n if (isDisabled.value || isReadonly.value)\n return\n\n emitModel([...keys])\n}\n\nfunction onNodeClick(data: T, node: GrTreeNode<T>): void {\n emit('nodeClick', data, node)\n\n if (isDisabled.value)\n return\n\n if (checkboxMode.value) {\n // Клик по строке — тот же переключатель отметки, что и сам чекбокс;\n // значение придёт через `update:checkedKeys`, поэтому здесь не эмитим.\n treeRef.value?.setChecked(node.key, !selectedKeySet.value.has(node.key))\n return\n }\n\n if (props.multiple) {\n const next = new Set(selectedKeySet.value)\n if (next.has(node.key))\n next.delete(node.key)\n else\n next.add(node.key)\n emitModel([...next])\n\n if (closeOnSelectResolved.value) {\n closeDropdown()\n }\n return\n }\n\n emitModel(node.key)\n if (closeOnSelectResolved.value) {\n closeDropdown()\n }\n}\n\n// Телепорт включается только ПОСЛЕ монтирования: иначе первый клиентский\n// рендер не совпадает с серверным и ломается гидрация (см. композабл).\nconst { target: portalTarget, enabled: teleportEnabled } = usePortalTarget()\n\n// Тема поддерева на телепортированную панель: в DOM она уезжает в `body`, то\n// есть вне обёртки провайдера, и `data-theme` с неё не наследуется. В дереве\n// компонентов панель остаётся внутри — `inject` доходит, и тему она ставит себе\n// сама.\nconst themeAttrs = useGrThemeAttrs()\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n v-click-outside=\"{ handler: onClickOutside, enabled: open, exclude: clickOutsideExclude }\"\n data-gr-tree-select\n class=\"relative\"\n @focusin=\"onFocusIn\"\n @focusout=\"onFocusOut\"\n >\n <!-- Нативная форма: hidden на каждый выбранный ключ. -->\n <template v-if=\"name\">\n <input\n v-for=\"key in selectedKeys\"\n :key=\"`hidden-${String(key)}`\"\n type=\"hidden\"\n :name=\"name\"\n :value=\"String(key)\"\n >\n </template>\n <div class=\"relative\">\n <div\n v-if=\"hasPrefix\"\n ref=\"prefixEl\"\n data-gr-tree-select-prefix\n class=\"absolute inset-y-px left-px flex items-center justify-center rounded-l-[var(--gr-radius-control)] border-r border-[var(--gr-brd)] px-2 text-[var(--gr-muted-fg)] pointer-events-none select-none truncate\"\n :style=\"prefixStyle\"\n aria-hidden=\"true\"\n >\n <slot name=\"prefix\" />\n </div>\n\n <input\n :id=\"fieldId\"\n ref=\"triggerEl\"\n data-testid=\"gr-tree-select-trigger\"\n data-gr-tree-select-trigger\n type=\"text\"\n :value=\"displayValue\"\n :placeholder=\"placeholder\"\n :disabled=\"isDisabled\"\n readonly\n role=\"combobox\"\n aria-readonly=\"true\"\n aria-haspopup=\"tree\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n :aria-controls=\"open && hasTree ? treeId : undefined\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-describedby=\"describedBy\"\n :aria-label=\"ariaLabel\"\n class=\"w-full rounded-[var(--gr-radius-control)] border placeholder:text-[var(--gr-muted-fg)] transition-colors duration-[var(--gr-duration-fast)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)]\"\n :class=\"[className, $slots.value ? 'text-transparent placeholder:text-transparent' : '']\"\n :style=\"triggerStyle\"\n @pointerdown=\"onTriggerPointerDown\"\n @click=\"toggleDropdown\"\n @focus=\"onTriggerFocus\"\n @keydown=\"onTriggerKeydown\"\n >\n\n <button\n v-if=\"clearable && hasSelection && !disabled && !isReadonly\"\n data-testid=\"gr-tree-select-clear\"\n data-gr-tree-select-clear\n type=\"button\"\n class=\"absolute top-1/2 -translate-y-1/2 right-3 h-6 w-6 inline-flex items-center justify-center rounded-[var(--gr-radius-control)] text-[var(--gr-muted-fg)] hover:text-[var(--gr-fg)] hover:bg-[color-mix(in_srgb,var(--gr-muted)_25%,transparent)]\"\n :aria-label=\"t('gr.common.clear', 'Clear')\"\n @click.stop=\"clear\"\n >\n <IconX class=\"inline-block h-4 w-4\" aria-hidden=\"true\" />\n </button>\n\n <span\n v-else\n data-testid=\"gr-tree-select-chevron\"\n data-gr-tree-select-chevron\n class=\"absolute top-1/2 -translate-y-1/2 right-3 text-[var(--gr-muted-fg)] pointer-events-none\"\n >\n <IconChevronDown\n class=\"inline-block h-4 w-4 transition-transform duration-[var(--gr-duration-fast)]\"\n :class=\"open ? 'rotate-180' : ''\"\n aria-hidden=\"true\"\n />\n </span>\n\n <div\n v-if=\"hasSuffix\"\n ref=\"suffixEl\"\n data-gr-tree-select-suffix\n class=\"absolute inset-y-px right-9 flex items-center justify-center border-l border-[var(--gr-brd)] px-2 text-[var(--gr-muted-fg)] pointer-events-none select-none truncate\"\n :class=\"suffixFixed ? '[direction:rtl]' : ''\"\n :style=\"suffixStyle\"\n aria-hidden=\"true\"\n >\n <slot name=\"suffix\" />\n </div>\n\n <div\n v-if=\"$slots.value\"\n class=\"absolute inset-y-0 left-3 right-9 flex items-center pointer-events-none\"\n :style=\"valueOverlayStyle\"\n >\n <slot\n name=\"value\"\n :value=\"modelValue\"\n :labels=\"selectedLabels\"\n :display-value=\"displayValue\"\n :path-labels=\"selectedPathLabels\"\n />\n </div>\n </div>\n\n <teleport :to=\"portalTarget\" :disabled=\"!teleportEnabled\">\n <transition\n :enter-active-class=\"panelPopTransition.enter\"\n :enter-from-class=\"panelPopTransition.enterFrom\"\n :enter-to-class=\"panelPopTransition.enterTo\"\n :leave-active-class=\"panelPopTransition.leave\"\n :leave-from-class=\"panelPopTransition.leaveFrom\"\n :leave-to-class=\"panelPopTransition.leaveTo\"\n >\n <div\n v-show=\"open\"\n ref=\"panelEl\"\n data-testid=\"gr-tree-select-panel\"\n v-bind=\"themeAttrs\"\n data-gr-tree-select-panel\n data-gr-overlay-root\n :style=\"floatingStyle\"\n @keydown=\"onPanelKeydown\"\n >\n <div :class=\"panelClasses\">\n <div v-if=\"filterable\" class=\"p-2 border-b border-[var(--gr-brd)]\">\n <GrInput\n ref=\"filterInputRef\"\n v-model=\"filterValue\"\n data-testid=\"gr-tree-select-filter\"\n data-gr-tree-select-filter\n type=\"search\"\n :inputmode=\"filterInputmode\"\n :placeholder=\"resolvedFilterPlaceholder\"\n size=\"sm\"\n @keydown=\"onFilterKeydown\"\n />\n </div>\n\n <!--\n Скроллер один. При виртуализации его берёт на себя дерево: своя\n высота нужна ему, чтобы посчитать окно, а вложенный скроллер поверх\n дал бы вторую полосу прокрутки на том же списке.\n -->\n <div\n class=\"p-1\"\n :class=\"virtual ? '' : 'overflow-auto'\"\n :style=\"virtual ? undefined : { maxHeight: `${dropdownMaxHeight}px` }\"\n >\n <div\n v-if=\"loading\"\n data-gr-tree-select-loading\n role=\"status\"\n :class=\"grTreeSelectStateClass\"\n >\n <slot name=\"loading\">\n <IconLoaderCircle class=\"block h-4 w-4 animate-spin\" aria-hidden=\"true\" />\n <span>{{ t('gr.treeSelect.loading', 'Loading…') }}</span>\n </slot>\n </div>\n\n <div v-else-if=\"(data?.length ?? 0) === 0\" :class=\"grTreeSelectStateClass\">\n <slot name=\"empty\">\n {{ t('gr.treeSelect.empty', 'No data') }}\n </slot>\n </div>\n\n <GrTree\n v-else\n :id=\"treeId\"\n ref=\"treeRef\"\n :size=\"resolvedSize\"\n :data=\"data\"\n :props=\"props.props\"\n :node-key=\"nodeKey as any\"\n :default-expanded-keys=\"defaultExpandedKeys\"\n :filter-node-method=\"filterNodeMethod\"\n :highlight-current=\"!multiple\"\n :virtual=\"virtual\"\n :max-height=\"dropdownMaxHeight\"\n :show-checkbox=\"checkboxMode\"\n :check-strictly=\"checkStrictly\"\n :checked-keys=\"selectedKeys\"\n @node-click=\"onNodeClick\"\n @update:checked-keys=\"onCheckedKeys\"\n >\n <template #default=\"{ node, data }\">\n <slot\n name=\"node\"\n :node=\"node\"\n :data=\"data\"\n :selected=\"selectedKeySet.has(node.key)\"\n >\n <div class=\"flex items-center gap-2 min-w-0\">\n <!-- Чекбокс дерева уже показывает отметку: вторая галочка\n рядом читалась бы как ошибка. -->\n <span\n v-if=\"!checkboxMode\"\n data-gr-tree-select-check\n class=\"inline-block h-4 w-4 shrink-0\"\n aria-hidden=\"true\"\n >\n <IconCheck v-if=\"selectedKeySet.has(node.key)\" class=\"block h-4 w-4 text-[var(--gr-primary-text)]\" />\n </span>\n <span class=\"truncate\">{{ node.label }}</span>\n </div>\n </slot>\n </template>\n </GrTree>\n </div>\n </div>\n </div>\n </transition>\n </teleport>\n </div>\n</template>\n","<script setup lang=\"ts\" generic=\"T extends Record<string, any> = any\">\nimport { computed, nextTick, ref, useId, useSlots, watch } from 'vue'\n\nimport { usePortalTarget } from '../../composables/usePortalTarget'\n\nimport { vClickOutside } from '../../directives'\nimport { useFloating } from '../../composables/useFloating'\nimport { useOverlayLayer } from '../../composables/useOverlayLayer'\nimport { useControlAddons } from '../../composables/internal/useControlAddons'\nimport { useControlledOpen } from '../../composables/internal/useControlledOpen'\nimport { useGrComponentSize, useGrThemeAttrs } from '../GrConfigProvider/context'\nimport { useGrFormControl } from '../../composables/useGrFormControl'\nimport { useFocusWithin } from '../../composables/internal/useFocusWithin'\nimport { useGrFormFieldContext } from '../GrFormField/context'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport GrInput from '../GrInput/GrInput.vue'\nimport GrTree, {\n type GrTreeInstance,\n type GrTreeKey,\n type GrTreeNode,\n} from '../GrTree'\nimport type { GrInputSize } from '../GrInput/GrInput.vue'\nimport { panelPopTransition } from '../shared/overlayTransition'\nimport type { GrTreeSelectModelValue, GrTreeSelectProps } from './grTreeSelectTypes'\nimport { grTreeSelectClass, grTreeSelectPanelClass, grTreeSelectStateClass, paddingX, trailingZoneWidth } from './grTreeSelectStyles'\n\nimport IconCheck from '~icons/lucide/check'\nimport IconChevronDown from '~icons/lucide/chevron-down'\nimport IconLoaderCircle from '~icons/lucide/loader-circle'\nimport IconX from '~icons/lucide/x'\n\nexport interface GrTreeSelectEmits<T extends Record<string, any> = any> {\n (e: 'update:modelValue', value: GrTreeSelectModelValue): void\n (e: 'change', value: GrTreeSelectModelValue): void\n /** Панель открылась/закрылась (`v-model:open`). */\n (e: 'update:open', value: boolean): void\n (e: 'clear'): void\n (e: 'nodeClick', data: T, node: GrTreeNode<T>): void\n (e: 'focus', event: FocusEvent): void\n (e: 'blur', event: FocusEvent): void\n}\n\nconst props = withDefaults(\n defineProps<GrTreeSelectProps<T>>(),\n {\n props: () => ({\n children: 'children',\n label: 'label',\n }),\n nodeKey: 'id' as any,\n defaultExpandedKeys: () => [],\n disabled: false,\n placeholder: undefined,\n size: undefined,\n loading: false,\n invalid: false,\n readonly: false,\n required: false,\n ariaLabel: undefined,\n state: 'default',\n multiple: false,\n showCheckbox: false,\n checkStrictly: false,\n clearable: false,\n open: undefined,\n name: undefined,\n valueDisplay: 'label',\n filterable: false,\n filterPlaceholder: undefined,\n filterInputmode: undefined,\n filterNodeMethod: undefined,\n closeOnSelect: undefined,\n dropdownMaxHeight: 320,\n virtual: false,\n prefixMinWidth: undefined,\n prefixMaxWidth: undefined,\n suffixMinWidth: undefined,\n suffixMaxWidth: undefined,\n prefixFixed: false,\n suffixFixed: false,\n },\n)\n\nconst { t } = useGranularityTranslations()\n\nconst emit = defineEmits<GrTreeSelectEmits<T>>()\n\ndefineSlots<{\n /** Рендер значения внутри триггера (вместо дефолтного текста). */\n value?: (props: { value: GrTreeSelectModelValue; labels: string[]; displayValue: string; pathLabels?: string[] }) => any\n /** Рендер строки дерева. */\n node?: (props: { node: GrTreeNode<T>; data: T; selected: boolean }) => any\n /** Содержимое пустого состояния (когда нет данных). */\n empty?: () => any\n /** Содержимое панели, пока данные едут. */\n loading?: () => any\n /** Аддон слева от значения: иконка, код валюты, метка. */\n prefix?: () => any\n /** Аддон справа от значения, перед крестиком и шевроном. */\n suffix?: () => any\n}>()\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst triggerEl = ref<HTMLInputElement | null>(null)\nconst treeId = `gr-tree-select-tree-${useId()}`\n\n// Контекст `GrFormField` + общий контракт форм-контрола.\nconst field = useGrFormFieldContext()\nconst fieldId = computed(() => field?.id.value)\nconst describedBy = computed(() => field?.describedById.value)\nconst {\n disabled: isDisabled,\n invalid: isInvalid, required: isRequired, readonly: isReadonly } = useGrFormControl(() => props)\n\nfunction focus(): void {\n triggerEl.value?.focus()\n}\n\nfunction blur(): void {\n triggerEl.value?.blur()\n}\n\ndefineExpose({ focus, blur })\nconst filterInputRef = ref<InstanceType<typeof GrInput> | null>(null)\nconst treeRef = ref<GrTreeInstance | null>(null)\nconst panelEl = ref<HTMLElement | null>(null)\n\n// Панель телепортирована в `body`, то есть лежит вне корня, но для пользователя\n// она часть контрола: без неё уход фокуса в панель читался бы как `blur`.\nconst { onFocusIn, onFocusOut } = useFocusWithin(rootEl, {\n enter: event => emit('focus', event),\n leave: event => emit('blur', event),\n containers: () => [panelEl.value],\n})\n\nconst clickOutsideExclude = [() => panelEl.value]\n\nlet hadPointerDownOnTrigger = false\n\n// Имя `open` сохранено: читатели работают с computed-Ref.\nconst { open, setOpen: setOpenState } = useControlledOpen(\n () => props.open,\n next => emit('update:open', next),\n)\nconst filterValue = ref('')\n\nconst { floatingStyle } = useFloating(rootEl, panelEl, open, {\n placement: 'bottom-start',\n matchWidth: true,\n zIndexVar: '--gr-z-dropdown',\n})\n\nconst closeOnSelectResolved = computed(() => {\n return props.closeOnSelect ?? !props.multiple\n})\n\nfunction getChildrenKey() {\n return props.props.children ?? 'children'\n}\n\nfunction getLabelKey() {\n return props.props.label ?? 'label'\n}\n\nfunction nodeLabel(data: T): string {\n const key = getLabelKey() as keyof T\n const v = data[key]\n return v == null ? '' : String(v)\n}\n\nfunction nodeChildren(data: T): T[] {\n const key = getChildrenKey() as keyof T\n const v = data[key]\n return Array.isArray(v) ? (v as any) : []\n}\n\nfunction nodeKeyOf(data: T, index: number, parentKey: GrTreeKey | undefined): GrTreeKey {\n const keyName = props.nodeKey as keyof T\n const v = data[keyName]\n if (v != null)\n return v as GrTreeKey\n\n return parentKey == null ? index : `${String(parentKey)}:${index}`\n}\n\nconst labelByKey = computed(() => {\n const map = new Map<GrTreeKey, string>()\n\n const walk = (items: T[], parentKey: GrTreeKey | undefined) => {\n for (let i = 0; i < items.length; i++) {\n const item = items[i]\n const key = nodeKeyOf(item, i, parentKey)\n map.set(key, nodeLabel(item))\n walk(nodeChildren(item), key)\n }\n }\n\n walk(props.data ?? [], undefined)\n return map\n})\n\nconst pathLabelsByKey = computed(() => {\n const map = new Map<GrTreeKey, string[]>()\n\n const walk = (items: T[], parentKey: GrTreeKey | undefined, parentPath: string[]) => {\n for (let i = 0; i < items.length; i++) {\n const item = items[i]\n const key = nodeKeyOf(item, i, parentKey)\n const label = nodeLabel(item)\n const path = [...parentPath, label]\n map.set(key, path)\n walk(nodeChildren(item), key, path)\n }\n }\n\n walk(props.data ?? [], undefined, [])\n return map\n})\n\nconst selectedKeys = computed<GrTreeKey[]>(() => {\n if (props.multiple) {\n return Array.isArray(props.modelValue) ? props.modelValue : []\n }\n\n const v = props.modelValue\n if (v == null || Array.isArray(v))\n return []\n return [v]\n})\n\nconst selectedKeySet = computed(() => new Set(selectedKeys.value))\n\nconst selectedLabels = computed(() => {\n const map = labelByKey.value\n return selectedKeys.value.map((k) => map.get(k) ?? String(k))\n})\n\nconst selectedPathLabels = computed<string[] | undefined>(() => {\n if (props.multiple)\n return undefined\n\n const key = selectedKeys.value[0]\n if (key == null)\n return undefined\n\n const path = pathLabelsByKey.value.get(key)\n if (path?.length)\n return path\n\n const labels = selectedLabels.value\n return labels.length ? [labels[0]] : undefined\n})\n\nconst displayValue = computed(() => {\n const labels = selectedLabels.value\n if (labels.length === 0)\n return ''\n\n if (!props.multiple) {\n if (props.valueDisplay === 'path') {\n const path = selectedPathLabels.value\n if (path?.length)\n return path.join(' / ')\n }\n return labels[0]\n }\n\n if (labels.length === 1)\n return labels[0]\n return `${labels[0]} +${labels.length - 1}`\n})\n\nconst hasSelection = computed(() => selectedKeys.value.length > 0)\n\n/** Дерево рендерится только когда есть что показать — ссылаться иначе не на что. */\nconst hasTree = computed(() => !props.loading && (props.data?.length ?? 0) > 0)\n\nconst resolvedFilterPlaceholder = computed(() => {\n return props.filterPlaceholder ?? t('gr.treeSelect.filterPlaceholder', 'Search…')\n})\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrTreeSelect' })\n\nconst className = computed(() => {\n return grTreeSelectClass({\n size: resolvedSize.value,\n state: props.state,\n invalid: isInvalid.value,\n disabled: isDisabled.value,\n })\n})\n\nconst panelClasses = computed(() => {\n return grTreeSelectPanelClass\n})\n\nconst ADDON_MIN_WIDTH_BY_SIZE: Record<GrInputSize, string> = {\n xs: '2rem',\n sm: '2.25rem',\n md: '2.5rem',\n lg: '3rem',\n}\n\nconst slots = useSlots()\n\nconst {\n hasPrefix,\n hasSuffix,\n prefixEl,\n suffixEl,\n prefixStyle,\n suffixStyle,\n fieldPadding: triggerStyle,\n} = useControlAddons(() => props, {\n defaultMinWidth: () => ADDON_MIN_WIDTH_BY_SIZE[resolvedSize.value],\n paddingX: () => paddingX[resolvedSize.value],\n // Место под крестик и шеврон уже отдано классом `pr-9`, но инлайн-паддинг\n // перекрывает класс целиком — с суффиксом эту зону приходится вернуть числом.\n trailingReserve: () => (slots.suffix ? trailingZoneWidth : '0px'),\n})\n\n// Слот `value` рисуется поверх поля и обязан жить в тех же границах, что и\n// текст под ним. Без аддонов границы задаёт класс — стиль остаётся пустым.\nconst valueOverlayStyle = computed(() => ({\n left: hasPrefix.value ? triggerStyle.value.paddingLeft : undefined,\n right: hasSuffix.value ? triggerStyle.value.paddingRight : undefined,\n}))\n\nfunction setOpen(next: boolean) {\n if (isDisabled.value || isReadonly.value)\n return\n\n setOpenState(next)\n}\n\nfunction onClickOutside(): void {\n setOpen(false)\n}\n\nfunction openDropdown(): void {\n setOpen(true)\n}\n\n/**\n * Стек слоёв возвращает фокус на триггер сразу после закрытия, а у триггера\n * открытие висит на `focus`. Без флага `Escape` из дерева закрывал бы панель и\n * тем же движением открывал её заново.\n */\nlet suppressFocusOpen = false\n\nfunction closeDropdown(): void {\n suppressFocusOpen = true\n setOpen(false)\n setTimeout(() => {\n suppressFocusOpen = false\n }, 0)\n}\n\nfunction toggleDropdown(): void {\n setOpen(!open.value)\n}\n\n// Клик по триггеру сам переключает панель через `@click`; флаг гасит открытие\n// по приходящему следом `focus`, иначе один клик открыл бы и тут же закрыл.\nfunction onTriggerPointerDown(): void {\n hadPointerDownOnTrigger = true\n setTimeout(() => {\n hadPointerDownOnTrigger = false\n }, 0)\n}\n\nfunction onTriggerFocus(): void {\n if (hadPointerDownOnTrigger || suppressFocusOpen)\n return\n openDropdown()\n}\n\n// Возврат фокуса на триггер — из контракта слоя, а не руками через `nextTick`.\nuseOverlayLayer(open, closeDropdown, { root: panelEl })\n\nwatch(\n open,\n async (isOpen) => {\n if (!isOpen) {\n if (filterValue.value.trim().length > 0)\n filterValue.value = ''\n return\n }\n\n // Подсветка дерева идёт за значением: панель могли открыть после того, как\n // значение сменили снаружи.\n if (!props.multiple) {\n const v = props.modelValue\n if (v != null && !Array.isArray(v))\n treeRef.value?.setCurrentKey(v)\n }\n\n if (props.filterable) {\n await nextTick()\n filterInputRef.value?.focus()\n }\n },\n { immediate: true },\n)\n\nwatch(\n () => props.modelValue,\n (v) => {\n if (props.multiple)\n return\n if (v == null || Array.isArray(v)) {\n treeRef.value?.setCurrentKey(undefined)\n return\n }\n treeRef.value?.setCurrentKey(v)\n },\n)\n\nwatch(\n filterValue,\n (v) => {\n treeRef.value?.filter(v)\n },\n)\n\nfunction emitModel(next: GrTreeSelectModelValue) {\n emit('update:modelValue', next)\n emit('change', next)\n}\n\nfunction clear(): void {\n if (isDisabled.value || isReadonly.value)\n return\n\n const next: GrTreeSelectModelValue = props.multiple ? [] : null\n emitModel(next)\n emit('clear')\n if (!props.multiple)\n treeRef.value?.setCurrentKey(undefined)\n}\n\n/**\n * Отдаёт клавиатуру дереву. Панель под `v-show`, поэтому дерево уже в DOM, но\n * скрытый элемент сфокусировать нельзя — сначала открытие, потом кадр, потом\n * фокус. Дальше работают клавиши `GrTree`, а `Escape` возвращает фокус на\n * триггер силами общего стека слоёв.\n */\nasync function focusTree(): Promise<boolean> {\n if (!open.value)\n return false\n\n await nextTick()\n return treeRef.value?.focus() ?? false\n}\n\nfunction onTriggerKeydown(e: KeyboardEvent): void {\n if (isDisabled.value)\n return\n\n if (e.key === 'Escape') {\n e.preventDefault()\n closeDropdown()\n return\n }\n\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n openDropdown()\n\n // При `filterable` фокус уходит в поле поиска (см. watch на `open`), и\n // дальше в дерево ведёт стрелка уже оттуда.\n if (!props.filterable)\n void focusTree()\n }\n}\n\n/** Стрелка из поля поиска — единственный путь из фильтра в дерево. */\nfunction onFilterKeydown(e: KeyboardEvent): void {\n if (e.key !== 'ArrowDown' && e.key !== 'ArrowUp')\n return\n\n e.preventDefault()\n void focusTree()\n}\n\n/** `Tab` уводит из панели — оставлять её открытой за спиной незачем. */\nfunction onPanelKeydown(e: KeyboardEvent): void {\n if (e.key === 'Tab')\n closeDropdown()\n}\n\n// Чекбоксы осмысленны только в множественном выборе: одиночный уже подсвечен\n// текущим узлом.\nconst checkboxMode = computed(() => props.multiple && props.showCheckbox)\n\n// В режиме чекбоксов каскад по родителям и детям считает `GrTree`, а не копия\n// его логики здесь: значение приходит обратно одним `update:checkedKeys`.\nfunction onCheckedKeys(keys: GrTreeKey[]): void {\n if (isDisabled.value || isReadonly.value)\n return\n\n emitModel([...keys])\n}\n\nfunction onNodeClick(data: T, node: GrTreeNode<T>): void {\n emit('nodeClick', data, node)\n\n if (isDisabled.value)\n return\n\n if (checkboxMode.value) {\n // Клик по строке — тот же переключатель отметки, что и сам чекбокс;\n // значение придёт через `update:checkedKeys`, поэтому здесь не эмитим.\n treeRef.value?.setChecked(node.key, !selectedKeySet.value.has(node.key))\n return\n }\n\n if (props.multiple) {\n const next = new Set(selectedKeySet.value)\n if (next.has(node.key))\n next.delete(node.key)\n else\n next.add(node.key)\n emitModel([...next])\n\n if (closeOnSelectResolved.value) {\n closeDropdown()\n }\n return\n }\n\n emitModel(node.key)\n if (closeOnSelectResolved.value) {\n closeDropdown()\n }\n}\n\n// Телепорт включается только ПОСЛЕ монтирования: иначе первый клиентский\n// рендер не совпадает с серверным и ломается гидрация (см. композабл).\nconst { target: portalTarget, enabled: teleportEnabled } = usePortalTarget()\n\n// Тема поддерева на телепортированную панель: в DOM она уезжает в `body`, то\n// есть вне обёртки провайдера, и `data-theme` с неё не наследуется. В дереве\n// компонентов панель остаётся внутри — `inject` доходит, и тему она ставит себе\n// сама.\nconst themeAttrs = useGrThemeAttrs()\n</script>\n\n<template>\n <div\n ref=\"rootEl\"\n v-click-outside=\"{ handler: onClickOutside, enabled: open, exclude: clickOutsideExclude }\"\n data-gr-tree-select\n class=\"relative\"\n @focusin=\"onFocusIn\"\n @focusout=\"onFocusOut\"\n >\n <!-- Нативная форма: hidden на каждый выбранный ключ. -->\n <template v-if=\"name\">\n <input\n v-for=\"key in selectedKeys\"\n :key=\"`hidden-${String(key)}`\"\n type=\"hidden\"\n :name=\"name\"\n :value=\"String(key)\"\n >\n </template>\n <div class=\"relative\">\n <div\n v-if=\"hasPrefix\"\n ref=\"prefixEl\"\n data-gr-tree-select-prefix\n class=\"absolute inset-y-px left-px flex items-center justify-center rounded-l-[var(--gr-radius-control)] border-r border-[var(--gr-brd)] px-2 text-[var(--gr-muted-fg)] pointer-events-none select-none truncate\"\n :style=\"prefixStyle\"\n aria-hidden=\"true\"\n >\n <slot name=\"prefix\" />\n </div>\n\n <input\n :id=\"fieldId\"\n ref=\"triggerEl\"\n data-testid=\"gr-tree-select-trigger\"\n data-gr-tree-select-trigger\n type=\"text\"\n :value=\"displayValue\"\n :placeholder=\"placeholder\"\n :disabled=\"isDisabled\"\n readonly\n role=\"combobox\"\n aria-readonly=\"true\"\n aria-haspopup=\"tree\"\n :aria-expanded=\"open ? 'true' : 'false'\"\n :aria-controls=\"open && hasTree ? treeId : undefined\"\n :aria-invalid=\"isInvalid ? 'true' : undefined\"\n :aria-required=\"isRequired ? 'true' : undefined\"\n :aria-describedby=\"describedBy\"\n :aria-label=\"ariaLabel\"\n class=\"w-full rounded-[var(--gr-radius-control)] border placeholder:text-[var(--gr-muted-fg)] transition-colors duration-[var(--gr-duration-fast)] focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--gr-ring)]\"\n :class=\"[className, $slots.value ? 'text-transparent placeholder:text-transparent' : '']\"\n :style=\"triggerStyle\"\n @pointerdown=\"onTriggerPointerDown\"\n @click=\"toggleDropdown\"\n @focus=\"onTriggerFocus\"\n @keydown=\"onTriggerKeydown\"\n >\n\n <button\n v-if=\"clearable && hasSelection && !disabled && !isReadonly\"\n data-testid=\"gr-tree-select-clear\"\n data-gr-tree-select-clear\n type=\"button\"\n class=\"absolute top-1/2 -translate-y-1/2 right-3 h-6 w-6 inline-flex items-center justify-center rounded-[var(--gr-radius-control)] text-[var(--gr-muted-fg)] hover:text-[var(--gr-fg)] hover:bg-[color-mix(in_srgb,var(--gr-muted)_25%,transparent)]\"\n :aria-label=\"t('gr.common.clear', 'Clear')\"\n @click.stop=\"clear\"\n >\n <IconX class=\"inline-block h-4 w-4\" aria-hidden=\"true\" />\n </button>\n\n <span\n v-else\n data-testid=\"gr-tree-select-chevron\"\n data-gr-tree-select-chevron\n class=\"absolute top-1/2 -translate-y-1/2 right-3 text-[var(--gr-muted-fg)] pointer-events-none\"\n >\n <IconChevronDown\n class=\"inline-block h-4 w-4 transition-transform duration-[var(--gr-duration-fast)]\"\n :class=\"open ? 'rotate-180' : ''\"\n aria-hidden=\"true\"\n />\n </span>\n\n <div\n v-if=\"hasSuffix\"\n ref=\"suffixEl\"\n data-gr-tree-select-suffix\n class=\"absolute inset-y-px right-9 flex items-center justify-center border-l border-[var(--gr-brd)] px-2 text-[var(--gr-muted-fg)] pointer-events-none select-none truncate\"\n :class=\"suffixFixed ? '[direction:rtl]' : ''\"\n :style=\"suffixStyle\"\n aria-hidden=\"true\"\n >\n <slot name=\"suffix\" />\n </div>\n\n <div\n v-if=\"$slots.value\"\n class=\"absolute inset-y-0 left-3 right-9 flex items-center pointer-events-none\"\n :style=\"valueOverlayStyle\"\n >\n <slot\n name=\"value\"\n :value=\"modelValue\"\n :labels=\"selectedLabels\"\n :display-value=\"displayValue\"\n :path-labels=\"selectedPathLabels\"\n />\n </div>\n </div>\n\n <teleport :to=\"portalTarget\" :disabled=\"!teleportEnabled\">\n <transition\n :enter-active-class=\"panelPopTransition.enter\"\n :enter-from-class=\"panelPopTransition.enterFrom\"\n :enter-to-class=\"panelPopTransition.enterTo\"\n :leave-active-class=\"panelPopTransition.leave\"\n :leave-from-class=\"panelPopTransition.leaveFrom\"\n :leave-to-class=\"panelPopTransition.leaveTo\"\n >\n <div\n v-show=\"open\"\n ref=\"panelEl\"\n data-testid=\"gr-tree-select-panel\"\n v-bind=\"themeAttrs\"\n data-gr-tree-select-panel\n data-gr-overlay-root\n :style=\"floatingStyle\"\n @keydown=\"onPanelKeydown\"\n >\n <div :class=\"panelClasses\">\n <div v-if=\"filterable\" class=\"p-2 border-b border-[var(--gr-brd)]\">\n <GrInput\n ref=\"filterInputRef\"\n v-model=\"filterValue\"\n data-testid=\"gr-tree-select-filter\"\n data-gr-tree-select-filter\n type=\"search\"\n :inputmode=\"filterInputmode\"\n :placeholder=\"resolvedFilterPlaceholder\"\n size=\"sm\"\n @keydown=\"onFilterKeydown\"\n />\n </div>\n\n <!--\n Скроллер один. При виртуализации его берёт на себя дерево: своя\n высота нужна ему, чтобы посчитать окно, а вложенный скроллер поверх\n дал бы вторую полосу прокрутки на том же списке.\n -->\n <div\n class=\"p-1\"\n :class=\"virtual ? '' : 'overflow-auto'\"\n :style=\"virtual ? undefined : { maxHeight: `${dropdownMaxHeight}px` }\"\n >\n <div\n v-if=\"loading\"\n data-gr-tree-select-loading\n role=\"status\"\n :class=\"grTreeSelectStateClass\"\n >\n <slot name=\"loading\">\n <IconLoaderCircle class=\"block h-4 w-4 animate-spin\" aria-hidden=\"true\" />\n <span>{{ t('gr.treeSelect.loading', 'Loading…') }}</span>\n </slot>\n </div>\n\n <div v-else-if=\"(data?.length ?? 0) === 0\" :class=\"grTreeSelectStateClass\">\n <slot name=\"empty\">\n {{ t('gr.treeSelect.empty', 'No data') }}\n </slot>\n </div>\n\n <GrTree\n v-else\n :id=\"treeId\"\n ref=\"treeRef\"\n :size=\"resolvedSize\"\n :data=\"data\"\n :props=\"props.props\"\n :node-key=\"nodeKey as any\"\n :default-expanded-keys=\"defaultExpandedKeys\"\n :filter-node-method=\"filterNodeMethod\"\n :highlight-current=\"!multiple\"\n :virtual=\"virtual\"\n :max-height=\"dropdownMaxHeight\"\n :show-checkbox=\"checkboxMode\"\n :check-strictly=\"checkStrictly\"\n :checked-keys=\"selectedKeys\"\n @node-click=\"onNodeClick\"\n @update:checked-keys=\"onCheckedKeys\"\n >\n <template #default=\"{ node, data }\">\n <slot\n name=\"node\"\n :node=\"node\"\n :data=\"data\"\n :selected=\"selectedKeySet.has(node.key)\"\n >\n <div class=\"flex items-center gap-2 min-w-0\">\n <!-- Чекбокс дерева уже показывает отметку: вторая галочка\n рядом читалась бы как ошибка. -->\n <span\n v-if=\"!checkboxMode\"\n data-gr-tree-select-check\n class=\"inline-block h-4 w-4 shrink-0\"\n aria-hidden=\"true\"\n >\n <IconCheck v-if=\"selectedKeySet.has(node.key)\" class=\"block h-4 w-4 text-[var(--gr-primary-text)]\" />\n </span>\n <span class=\"truncate\">{{ node.label }}</span>\n </div>\n </slot>\n </template>\n </GrTree>\n </div>\n </div>\n </div>\n </transition>\n </teleport>\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { n as grTreeSelectSafelist, t as grTreeSelectConfig } from "../../chunks/config-T3-DMXYm.js";
|
|
2
|
-
import { t as GrTreeSelect_default } from "./chunks/GrTreeSelect-
|
|
2
|
+
import { t as GrTreeSelect_default } from "./chunks/GrTreeSelect-iJnMl2t9.js";
|
|
3
3
|
export { GrTreeSelect_default as GrTreeSelect, GrTreeSelect_default as default, grTreeSelectConfig, grTreeSelectSafelist };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as granularityDefaultThemes, i as granularityComponentConfigs, n as GRANULARITY_PROVIDER_ID, o as granularityThemeNames, r as createGranularityProvider, t as granularityProvider } from "./chunks/granular-provider-
|
|
1
|
+
import { a as granularityDefaultThemes, i as granularityComponentConfigs, n as GRANULARITY_PROVIDER_ID, o as granularityThemeNames, r as createGranularityProvider, t as granularityProvider } from "./chunks/granular-provider-yJNX1g1G.js";
|
|
2
2
|
export { GRANULARITY_PROVIDER_ID, createGranularityProvider, granularityProvider as default, granularityProvider, granularityComponentConfigs, granularityDefaultThemes, granularityThemeNames };
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as granularityDefaultThemes, i as granularityComponentConfigs, n as GRANULARITY_PROVIDER_ID, o as granularityThemeNames, r as createGranularityProvider, t as granularityProvider } from "./chunks/granular-provider-
|
|
1
|
+
import { a as granularityDefaultThemes, i as granularityComponentConfigs, n as GRANULARITY_PROVIDER_ID, o as granularityThemeNames, r as createGranularityProvider, t as granularityProvider } from "./chunks/granular-provider-yJNX1g1G.js";
|
|
2
2
|
export { GRANULARITY_PROVIDER_ID, createGranularityProvider, granularityProvider as default, granularityProvider, granularityComponentConfigs, granularityDefaultThemes, granularityThemeNames };
|
package/dist/index.js
CHANGED
|
@@ -61,12 +61,12 @@ import { n as grCodeBlockSafelist, t as grCodeBlockConfig } from "./chunks/confi
|
|
|
61
61
|
import { n as grCollapseSafelist, t as grCollapseConfig } from "./chunks/config-BoUI0t-t.js";
|
|
62
62
|
import { n as GrCollapse_default, t as GrCollapseItem_default } from "./components/GrCollapse/chunks/GrCollapse-BlW6ybdL.js";
|
|
63
63
|
import { n as grIconSafelist, t as grIconConfig } from "./chunks/config-3bQeQvqd.js";
|
|
64
|
-
import { t as GrInput_default } from "./components/GrInput/chunks/GrInput-
|
|
64
|
+
import { t as GrInput_default } from "./components/GrInput/chunks/GrInput-BLywRZUN.js";
|
|
65
65
|
import { useFocusTrap } from "./composables/useFocusTrap.js";
|
|
66
66
|
import { t as GrPopover_default } from "./components/GrPopover/chunks/GrPopover-CofG-Tfe.js";
|
|
67
67
|
import { useDragGesture } from "./composables/useDragGesture.js";
|
|
68
68
|
import { t as GrSlider_default } from "./components/GrSlider/chunks/GrSlider-DJfeEl4h.js";
|
|
69
|
-
import { t as GrColorPicker_default } from "./components/GrColorPicker/chunks/GrColorPicker-
|
|
69
|
+
import { t as GrColorPicker_default } from "./components/GrColorPicker/chunks/GrColorPicker-C7ifyQ0R.js";
|
|
70
70
|
import { n as grColorPickerSafelist, t as grColorPickerConfig } from "./chunks/config-DIDz_E5m.js";
|
|
71
71
|
import { a as GR_KBD_TOKENS, o as findKbdToken, t as GrKbd_default } from "./components/GrKbd/chunks/GrKbd-D2ga1H9i.js";
|
|
72
72
|
import { t as GrModal_default } from "./components/GrModal/chunks/GrModal-CCoWZuIl.js";
|
|
@@ -99,8 +99,8 @@ import { n as useGrFormContext } from "./chunks/context-Bfrgsluh.js";
|
|
|
99
99
|
import { t as GrFormField_default } from "./components/GrFormField/chunks/GrFormField-DSylU7IU.js";
|
|
100
100
|
import { t as GrTextarea_default } from "./components/GrTextarea/chunks/GrTextarea-KREK75Bu.js";
|
|
101
101
|
import { n as getByPath, o as runFieldRules, r as isEmpty, s as setByPath, t as createGrFormMessageResolver } from "./chunks/validation-5HhpanPP.js";
|
|
102
|
-
import { t as GrPromptDialog_default } from "./components/GrPromptDialog/chunks/GrPromptDialog-
|
|
103
|
-
import { a as useDialogService, i as teardownDialogService, n as dialogService, r as granularityDialogServicePlugin, t as GrDialogServiceHost_default } from "./components/GrDialogService/chunks/GrDialogService-
|
|
102
|
+
import { t as GrPromptDialog_default } from "./components/GrPromptDialog/chunks/GrPromptDialog-DLcl1xiq.js";
|
|
103
|
+
import { a as useDialogService, i as teardownDialogService, n as dialogService, r as granularityDialogServicePlugin, t as GrDialogServiceHost_default } from "./components/GrDialogService/chunks/GrDialogService-6s2cQ-_6.js";
|
|
104
104
|
import { t as grDialogServiceConfig } from "./chunks/config-CjXjhphs.js";
|
|
105
105
|
import { n as grDividerSafelist, t as grDividerConfig } from "./chunks/config-D82r782v.js";
|
|
106
106
|
import { t as GrDivider_default } from "./components/GrDivider/chunks/GrDivider-BXavGcMX.js";
|
|
@@ -128,12 +128,12 @@ import { n as grFormSectionSafelist, t as grFormSectionConfig } from "./chunks/c
|
|
|
128
128
|
import { t as GrFormSection_default } from "./components/GrFormSection/chunks/GrFormSection-ejieOg9H.js";
|
|
129
129
|
import { t as GrImageViewer_default } from "./components/GrImageViewer/chunks/GrImageViewer-CyVN6Zpc.js";
|
|
130
130
|
import { n as grImageViewerSafelist, t as grImageViewerConfig } from "./chunks/config-BDSaD-5p.js";
|
|
131
|
-
import { n as grInputSafelist, t as grInputConfig } from "./chunks/config-
|
|
131
|
+
import { n as grInputSafelist, t as grInputConfig } from "./chunks/config-CIkdsWUk.js";
|
|
132
132
|
import "./components/GrInput/index.js";
|
|
133
133
|
import { n as grInputTagSafelist, t as grInputTagConfig } from "./chunks/config-BqT6PMzO.js";
|
|
134
134
|
import { t as GrInputTag_default } from "./components/GrInputTag/chunks/GrInputTag-Cj4A9Xgs.js";
|
|
135
135
|
import { t as GrTree_default } from "./components/GrTree/chunks/GrTree-DC89I4AP.js";
|
|
136
|
-
import { t as GrJsonViewer_default } from "./components/GrJsonViewer/chunks/GrJsonViewer-
|
|
136
|
+
import { t as GrJsonViewer_default } from "./components/GrJsonViewer/chunks/GrJsonViewer-Cy2bknHO.js";
|
|
137
137
|
import { n as grJsonViewerSafelist, t as grJsonViewerConfig } from "./chunks/config-DpAYidyY2.js";
|
|
138
138
|
import { n as grKbdSafelist, t as grKbdConfig } from "./chunks/config-BRESvs9g.js";
|
|
139
139
|
import "./components/GrKbd/index.js";
|
|
@@ -149,9 +149,9 @@ import { t as GrNavbar_default } from "./components/GrNavbar/chunks/GrNavbar-C5E
|
|
|
149
149
|
import { n as grNavbarSafelist, t as grNavbarConfig } from "./chunks/config-2ZHRsdOc.js";
|
|
150
150
|
import { n as grNumberInputSafelist, t as grNumberInputConfig } from "./chunks/config-MmPym-Z72.js";
|
|
151
151
|
import { t as GrNumberInput_default } from "./components/GrNumberInput/chunks/GrNumberInput-DQ59YICu.js";
|
|
152
|
-
import { t as GrSelect_default } from "./components/GrSelect/chunks/GrSelect-
|
|
152
|
+
import { t as GrSelect_default } from "./components/GrSelect/chunks/GrSelect-BiUrSSxc.js";
|
|
153
153
|
import { n as grPaginationSafelist, t as grPaginationConfig } from "./chunks/config-CjKpUcmy.js";
|
|
154
|
-
import { t as GrPagination_default } from "./components/GrPagination/chunks/GrPagination-
|
|
154
|
+
import { t as GrPagination_default } from "./components/GrPagination/chunks/GrPagination-Cw3R21dp.js";
|
|
155
155
|
import { n as grPopoverSafelist, t as grPopoverConfig } from "./chunks/config-BTwYMQaz2.js";
|
|
156
156
|
import "./components/GrPopover/index.js";
|
|
157
157
|
import { n as grProgressBarSafelist, r as GR_TONES, t as grProgressBarConfig } from "./chunks/config-DWL81-aM.js";
|
|
@@ -205,7 +205,7 @@ import { n as grTooltipSafelist, t as grTooltipConfig } from "./chunks/config-BT
|
|
|
205
205
|
import { t as GrTooltip_default } from "./components/GrTooltip/chunks/GrTooltip-uYETdbCD.js";
|
|
206
206
|
import { n as grTreeSafelist, t as grTreeConfig } from "./chunks/config-CDat1wXf.js";
|
|
207
207
|
import { n as grTreeSelectSafelist, t as grTreeSelectConfig } from "./chunks/config-T3-DMXYm.js";
|
|
208
|
-
import { t as GrTreeSelect_default } from "./components/GrTreeSelect/chunks/GrTreeSelect-
|
|
208
|
+
import { t as GrTreeSelect_default } from "./components/GrTreeSelect/chunks/GrTreeSelect-iJnMl2t9.js";
|
|
209
209
|
import "./composables/useGrComponentConfig.js";
|
|
210
210
|
import "./composables/useGranularityTranslations.js";
|
|
211
211
|
import { GRANULARITY_THEME_STATE, granularityThemePlugin, initThemeEarly, useTheme } from "./composables/useTheme.js";
|
|
@@ -55,6 +55,19 @@ export interface GrInputProps {
|
|
|
55
55
|
*/
|
|
56
56
|
prefixFixed?: boolean;
|
|
57
57
|
suffixFixed?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Как выглядят аддоны `#prefix`/`#suffix`.
|
|
60
|
+
*
|
|
61
|
+
* `segment` (по умолчанию) — отдельный отсек, отрезанный рамкой и выровненный
|
|
62
|
+
* по ступени размера: так поле с «₽» и поле с «USD» стоят в колонку.
|
|
63
|
+
* `inline` — украшение внутри рамки: ни разделителя, ни своей ширины.
|
|
64
|
+
*
|
|
65
|
+
* Разница не косметическая. Поисковая строка с лупой в сегменте читается
|
|
66
|
+
* составным элементом — полем с приклеенной кнопкой, — а не одним полем;
|
|
67
|
+
* именно поэтому иконку внутри рамки нельзя было выразить аддоном, и
|
|
68
|
+
* потребители отказывались от неё вовсе.
|
|
69
|
+
*/
|
|
70
|
+
addon?: 'segment' | 'inline';
|
|
58
71
|
}
|
|
59
72
|
export interface GrInputEmits {
|
|
60
73
|
(e: 'update:modelValue', value: string): void;
|
|
@@ -110,6 +123,7 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
|
|
|
110
123
|
suffixMaxWidth: undefined;
|
|
111
124
|
prefixFixed: boolean;
|
|
112
125
|
suffixFixed: boolean;
|
|
126
|
+
addon: string;
|
|
113
127
|
}>>, {
|
|
114
128
|
focus: typeof focus;
|
|
115
129
|
blur: typeof blur;
|
|
@@ -150,6 +164,7 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
|
|
|
150
164
|
suffixMaxWidth: undefined;
|
|
151
165
|
prefixFixed: boolean;
|
|
152
166
|
suffixFixed: boolean;
|
|
167
|
+
addon: string;
|
|
153
168
|
}>>> & Readonly<{
|
|
154
169
|
onClear?: (() => any) | undefined;
|
|
155
170
|
onBlur?: ((event: FocusEvent) => any) | undefined;
|
|
@@ -186,6 +201,7 @@ declare const __VLS_base: import("vue").DefineComponent<import("vue").ExtractPro
|
|
|
186
201
|
passwordToggle: boolean;
|
|
187
202
|
passwordShowLabel: string;
|
|
188
203
|
passwordHideLabel: string;
|
|
204
|
+
addon: "segment" | "inline";
|
|
189
205
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
190
206
|
declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
|
|
191
207
|
declare const _default: typeof __VLS_export;
|
|
@@ -61,3 +61,19 @@ export declare function grInputFieldClass(options: {
|
|
|
61
61
|
size: keyof typeof sizes;
|
|
62
62
|
align: keyof typeof textAlign;
|
|
63
63
|
}): string;
|
|
64
|
+
/**
|
|
65
|
+
* Аддон-сегмент: отрезан рамкой и держит ширину не меньше ступени размера — так
|
|
66
|
+
* поле с «₽» и поле с «USD» выстраиваются в колонку, а не пляшут по ширине.
|
|
67
|
+
*/
|
|
68
|
+
export declare const addonSegmentPrefixClass = "border-r border-[var(--gr-brd)] px-2";
|
|
69
|
+
export declare const addonSegmentSuffixClass = "border-l border-[var(--gr-brd)] px-2";
|
|
70
|
+
/**
|
|
71
|
+
* Украшение внутри рамки: ни разделителя, ни своей ширины — лупа в поисковой
|
|
72
|
+
* строке, единица измерения, счётчик.
|
|
73
|
+
*
|
|
74
|
+
* Отдельный режим, а не «сегмент без рамки»: у сегмента ширина выровнена по
|
|
75
|
+
* ступени размера, и иконка в нём висела бы в пустом отсеке. Здесь поле берёт
|
|
76
|
+
* ровно ту ширину, что заняло содержимое, а текст начинается сразу за ним.
|
|
77
|
+
*/
|
|
78
|
+
export declare const addonInlinePrefixClass = "pl-3 pr-2";
|
|
79
|
+
export declare const addonInlineSuffixClass = "pl-2 pr-3";
|
package/dist/web-types.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/JetBrains/web-types/master/schema/web-types.json",
|
|
3
3
|
"name": "@feugene/granularity",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.31.0",
|
|
5
5
|
"framework": "vue",
|
|
6
6
|
"js-types-syntax": "typescript",
|
|
7
7
|
"description-markup": "markdown",
|
|
@@ -5545,6 +5545,15 @@
|
|
|
5545
5545
|
"kind": "expression",
|
|
5546
5546
|
"type": "GrInputTextAlign | undefined"
|
|
5547
5547
|
}
|
|
5548
|
+
},
|
|
5549
|
+
{
|
|
5550
|
+
"name": "addon",
|
|
5551
|
+
"description": "Как выглядят аддоны `#prefix`/`#suffix`. `segment` (по умолчанию) — отдельный отсек, отрезанный рамкой и выровненный по ступени размера: так поле с «₽» и поле с «USD» стоят в колонку. `inline` — украшение внутри рамки: ни разделителя, ни своей ширины. Разница не косметическая. Поисковая строка с лупой в сегменте читается составным элементом — полем с приклеенной кнопкой, — а не одним полем; именно поэтому иконку внутри рамки нельзя было выразить аддоном, и потребители отказывались от неё вовсе.",
|
|
5552
|
+
"default": "\"segment\"",
|
|
5553
|
+
"value": {
|
|
5554
|
+
"kind": "expression",
|
|
5555
|
+
"type": "\"inline\" | \"segment\" | undefined"
|
|
5556
|
+
}
|
|
5548
5557
|
}
|
|
5549
5558
|
],
|
|
5550
5559
|
"slots": [
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@feugene/granularity",
|
|
3
3
|
"description": "Granularity design system package with Vue 3 components, consumed via the `@feugene/unocss-preset-granular` preset.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.31.0",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Evgeniy Fureev",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"config-DO8mjeIo.js","names":[],"sources":["../../src/components/GrInput/safelist.ts","../../src/components/GrInput/config.ts"],"sourcesContent":["import { splitClassTokens } from '../shared/classTokens'\nimport { invalidClass, shellBaseClass, shellDisabledClass, shellEnabledClass, sizes, states, textAlign } from './grInputStyles'\n\nexport const grInputSafelist = [...new Set([\n ...Object.values(sizes).flatMap(splitClassTokens),\n ...Object.values(textAlign).flatMap(splitClassTokens),\n ...Object.values(states).flatMap(splitClassTokens),\n ...splitClassTokens(invalidClass),\n ...splitClassTokens(shellBaseClass),\n ...splitClassTokens(shellEnabledClass),\n ...splitClassTokens(shellDisabledClass),\n])]\n","import { defineGranularComponent } from '@feugene/unocss-preset-granular/contract'\nimport { grInputSafelist } from './safelist'\n\nexport const grInputConfig = defineGranularComponent(import.meta.url, {\n name: 'GrInput',\n safelist: grInputSafelist,\n})\n"],"mappings":";;;;AAGA,IAAa,kBAAkB,CAAC,mBAAG,IAAI,IAAI;CACzC,GAAG,OAAO,OAAO,KAAK,CAAC,CAAC,QAAQ,gBAAgB;CAChD,GAAG,OAAO,OAAO,SAAS,CAAC,CAAC,QAAQ,gBAAgB;CACpD,GAAG,OAAO,OAAO,MAAM,CAAC,CAAC,QAAQ,gBAAgB;CACjD,GAAG,iBAAiB,YAAY;CAChC,GAAG,iBAAiB,cAAc;CAClC,GAAG,iBAAiB,iBAAiB;CACrC,GAAG,iBAAiB,kBAAkB;AACxC,CAAC,CAAC;;;ACRF,IAAa,gBAAgB,wBAAwB,OAAO,KAAK,KAAK;CACpE,MAAM;CACN,UAAU;AACZ,CAAC"}
|