@feugene/granularity 0.11.0 → 0.12.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/GrTree.css +1 -1
- package/dist/chunks/config-1uSYl98Y2.js.map +1 -0
- package/dist/chunks/config-BIRO0vDJ.js.map +1 -0
- package/dist/chunks/config-BWiPnygb.js +5 -0
- package/dist/chunks/config-BWiPnygb.js.map +1 -0
- package/dist/chunks/config-BXtAmU3s2.js.map +1 -0
- package/dist/chunks/config-CGHLoBlH.js.map +1 -0
- package/dist/chunks/config-CrG1XiE72.js.map +1 -0
- package/dist/chunks/config-DUcu1D682.js.map +1 -0
- package/dist/chunks/config-Dlm49Jp5.js +5 -0
- package/dist/chunks/config-Dlm49Jp5.js.map +1 -0
- package/dist/chunks/config-DnvPqu4g2.js.map +1 -0
- package/dist/chunks/config-DpqOnJiz.js.map +1 -0
- package/dist/chunks/config-JpHA4Xx02.js.map +1 -0
- package/dist/chunks/config-LGrZIdaZ.js.map +1 -0
- package/dist/chunks/config-eXB87DOi2.js +9 -0
- package/dist/chunks/config-eXB87DOi2.js.map +1 -0
- package/dist/chunks/context-DdNIxa1w.js +9 -0
- package/dist/chunks/context-DdNIxa1w.js.map +1 -0
- package/dist/chunks/{en-BdtiS2d3.js → en-CfkDa4GX.js} +8 -2
- package/dist/chunks/{en-BdtiS2d3.js.map → en-CfkDa4GX.js.map} +1 -1
- package/dist/chunks/{es-DBhctWg-.js → es-CgaLnFYx.js} +3 -3
- package/dist/chunks/{es-DBhctWg-.js.map → es-CgaLnFYx.js.map} +1 -1
- package/dist/chunks/{es-BGRMZbe9.js → es-FkgECuCm.js} +8 -2
- package/dist/chunks/{es-BGRMZbe9.js.map → es-FkgECuCm.js.map} +1 -1
- package/dist/chunks/grModalEscStack-BXk-9mv7.js +43 -0
- package/dist/chunks/grModalEscStack-BXk-9mv7.js.map +1 -0
- package/dist/chunks/{ru-BFCnOiML.js → ru-Bl8CxpEa.js} +8 -2
- package/dist/chunks/{ru-BFCnOiML.js.map → ru-Bl8CxpEa.js.map} +1 -1
- package/dist/chunks/{shared-ChvcQgDl.js → shared-B6Ay2081.js} +17 -11
- package/dist/chunks/shared-B6Ay2081.js.map +1 -0
- package/dist/chunks/{transition-TLFw4KBn.js → useScrollLock-K7kaP2Ud.js} +52 -1
- package/dist/chunks/useScrollLock-K7kaP2Ud.js.map +1 -0
- package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-BWYKCsHd.js → GrConfirmDialog-D8CkOtqv.js} +1 -1
- package/dist/components/GrConfirmDialog/chunks/{GrConfirmDialog-BWYKCsHd.js.map → GrConfirmDialog-D8CkOtqv.js.map} +1 -1
- package/dist/components/GrConfirmDialog/index.js +2 -2
- package/dist/components/GrDataTable/chunks/{GrDataTable-ptbARmJ3.js → GrDataTable-Ds5OeGat.js} +42 -16
- package/dist/components/GrDataTable/chunks/GrDataTable-Ds5OeGat.js.map +1 -0
- package/dist/components/GrDataTable/index.js +1 -1
- package/dist/components/GrDialog/chunks/{GrDialog-5DAS-1Lb.js → GrDialog-Ctucxju9.js} +2 -2
- package/dist/components/GrDialog/chunks/{GrDialog-5DAS-1Lb.js.map → GrDialog-Ctucxju9.js.map} +1 -1
- package/dist/components/GrDialog/index.js +1 -1
- package/dist/components/GrDialogService/chunks/{GrDialogService-DY_fmaMu.js → GrDialogService-CU1Fbonc.js} +2 -2
- package/dist/components/GrDialogService/chunks/{GrDialogService-DY_fmaMu.js.map → GrDialogService-CU1Fbonc.js.map} +1 -1
- package/dist/components/GrDialogService/index.js +1 -1
- package/dist/components/GrDivider/chunks/GrDivider-5bcE-BB4.js +54 -0
- package/dist/components/GrDivider/chunks/GrDivider-5bcE-BB4.js.map +1 -0
- package/dist/components/GrDivider/index.js +3 -0
- package/dist/components/GrDrawer/chunks/{GrDrawer-D1kzsjXN.js → GrDrawer-SLGKCY2A.js} +37 -13
- package/dist/components/GrDrawer/chunks/GrDrawer-SLGKCY2A.js.map +1 -0
- package/dist/components/GrDrawer/index.js +2 -2
- package/dist/components/GrDropdown/chunks/{GrDropdown-BXb5Cxxf.js → GrDropdown-BnDMYP8v.js} +90 -4
- package/dist/components/GrDropdown/chunks/GrDropdown-BnDMYP8v.js.map +1 -0
- package/dist/components/GrDropdown/index.js +1 -1
- package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-DFEJLPcV.js → GrDropdownMenu--g9Vofcq.js} +1 -1
- package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-DFEJLPcV.js.map → GrDropdownMenu--g9Vofcq.js.map} +1 -1
- package/dist/components/GrDropdownMenu/index.js +1 -1
- package/dist/components/GrFormField/chunks/GrFormField-DnfMHx_U.js +73 -0
- package/dist/components/GrFormField/chunks/GrFormField-DnfMHx_U.js.map +1 -0
- package/dist/components/GrFormField/index.js +3 -2
- package/dist/components/GrImageViewer/chunks/{GrImageViewer-OCUIrNh2.js → GrImageViewer-Dla9IBkd.js} +311 -222
- package/dist/components/GrImageViewer/chunks/GrImageViewer-Dla9IBkd.js.map +1 -0
- package/dist/components/GrImageViewer/index.js +1 -1
- package/dist/components/GrInput/chunks/GrInput-B8dd3JaA.js +334 -0
- package/dist/components/GrInput/chunks/GrInput-B8dd3JaA.js.map +1 -0
- package/dist/components/GrInput/index.js +1 -1
- package/dist/components/GrKbd/chunks/GrKbd-BEoYxWhE.js +22 -0
- package/dist/components/GrKbd/chunks/GrKbd-BEoYxWhE.js.map +1 -0
- package/dist/components/GrKbd/index.js +3 -0
- package/dist/components/GrModal/chunks/{GrModal-BkjW9jZS.js → GrModal-C0GZrHN3.js} +3 -56
- package/dist/components/GrModal/chunks/GrModal-C0GZrHN3.js.map +1 -0
- package/dist/components/GrModal/index.js +1 -1
- package/dist/components/GrNumberInput/chunks/{GrNumberInput-CldPIWQx.js → GrNumberInput-DxCEvL7V.js} +93 -11
- package/dist/components/GrNumberInput/chunks/GrNumberInput-DxCEvL7V.js.map +1 -0
- package/dist/components/GrNumberInput/index.js +1 -1
- package/dist/components/GrPagination/chunks/{GrPagination-Bu9wv6it.js → GrPagination-mlyfLjQY.js} +1 -1
- package/dist/components/GrPagination/chunks/{GrPagination-Bu9wv6it.js.map → GrPagination-mlyfLjQY.js.map} +1 -1
- package/dist/components/GrPagination/index.js +2 -2
- package/dist/components/GrProgressBar/index.js +1 -1
- package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-TmWXbpEP.js → GrPromptDialog-C-_BMNuY.js} +3 -3
- package/dist/components/GrPromptDialog/chunks/{GrPromptDialog-TmWXbpEP.js.map → GrPromptDialog-C-_BMNuY.js.map} +1 -1
- package/dist/components/GrPromptDialog/index.js +1 -1
- package/dist/components/GrRadio/chunks/{GrRadio-CPTixXaY.js → GrRadio-Dr-pvs_9.js} +1 -1
- package/dist/components/GrRadio/chunks/{GrRadio-CPTixXaY.js.map → GrRadio-Dr-pvs_9.js.map} +1 -1
- package/dist/components/GrRadio/index.js +2 -2
- package/dist/components/GrRadioGroup/chunks/{GrRadioGroup-vKZCbkCp.js → GrRadioGroup-nOdvfV_g.js} +1 -1
- package/dist/components/GrRadioGroup/chunks/{GrRadioGroup-vKZCbkCp.js.map → GrRadioGroup-nOdvfV_g.js.map} +1 -1
- package/dist/components/GrRadioGroup/index.js +1 -1
- package/dist/components/GrSelect/chunks/{GrSelect-RShK21lK.js → GrSelect-BVCmitqa.js} +147 -27
- package/dist/components/GrSelect/chunks/GrSelect-BVCmitqa.js.map +1 -0
- package/dist/components/GrSelect/index.js +1 -1
- package/dist/components/GrSidebar/index.js +1 -1
- package/dist/components/GrTabPanels/chunks/GrTabPanels-DbKybwe-.js +68 -0
- package/dist/components/GrTabPanels/chunks/GrTabPanels-DbKybwe-.js.map +1 -0
- package/dist/components/GrTabPanels/index.js +3 -0
- package/dist/components/GrTabs/chunks/{GrTabs-Z-FDJUr_.js → GrTabs-DsLwWoRz.js} +6 -1
- package/dist/components/GrTabs/chunks/GrTabs-DsLwWoRz.js.map +1 -0
- package/dist/components/GrTabs/index.js +1 -1
- package/dist/components/GrTextarea/chunks/{GrTextarea-D56YxhKq.js → GrTextarea-CKukGlNl.js} +15 -5
- package/dist/components/GrTextarea/chunks/GrTextarea-CKukGlNl.js.map +1 -0
- package/dist/components/GrTextarea/index.js +2 -2
- package/dist/components/GrToaster/chunks/{GrToaster-eI8YvnIo.js → GrToaster-L2cu6cSR.js} +1 -1
- package/dist/components/GrToaster/chunks/{GrToaster-eI8YvnIo.js.map → GrToaster-L2cu6cSR.js.map} +1 -1
- package/dist/components/GrToaster/index.js +2 -2
- package/dist/components/GrTree/chunks/{GrTree-D3ODe2dB.js → GrTree-B2_yW6g1.js} +107 -8
- package/dist/components/GrTree/chunks/GrTree-B2_yW6g1.js.map +1 -0
- package/dist/components/GrTree/index.js +2 -2
- package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-B2xBKs5Q.js → GrTreeSelect-0aM62xyu.js} +3 -3
- package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-B2xBKs5Q.js.map → GrTreeSelect-0aM62xyu.js.map} +1 -1
- package/dist/components/GrTreeSelect/index.js +2 -2
- package/dist/granular-provider-node.js +2 -2
- package/dist/granular-provider.js +1 -1
- package/dist/i18n/all.js +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/index.js +40 -33
- package/dist/index.js.map +1 -1
- package/dist/styles/base.css +30 -8
- package/dist/styles/preflight.css +5 -1
- package/dist/types/src/components/GrDataTable/GrDataTable.vue.d.ts +28 -4
- package/dist/types/src/components/GrDivider/GrDivider.vue.d.ts +58 -0
- package/dist/types/src/components/GrDivider/config.d.ts +1 -0
- package/dist/types/src/components/GrDivider/index.d.ts +4 -0
- package/dist/types/src/components/GrDrawer/GrDrawer.vue.d.ts +4 -4
- package/dist/types/src/components/GrDropdown/GrDropdown.vue.d.ts +7 -0
- package/dist/types/src/components/GrDropdownMenu/GrDropdownMenu.vue.d.ts +6 -0
- package/dist/types/src/components/GrFormField/GrFormField.vue.d.ts +15 -2
- package/dist/types/src/components/GrFormField/context.d.ts +20 -0
- package/dist/types/src/components/GrFormField/index.d.ts +1 -0
- package/dist/types/src/components/GrImageViewer/GrImageViewer.vue.d.ts +4 -0
- package/dist/types/src/components/GrImageViewer/composables/useViewerKeyboard.d.ts +20 -0
- package/dist/types/src/components/GrImageViewer/composables/useWheelGesture.d.ts +16 -0
- package/dist/types/src/components/GrImageViewer/composables/useZoomPan.d.ts +50 -0
- package/dist/types/src/components/GrInput/GrInput.vue.d.ts +41 -2
- package/dist/types/src/components/GrKbd/GrKbd.vue.d.ts +46 -0
- package/dist/types/src/components/GrKbd/config.d.ts +1 -0
- package/dist/types/src/components/GrKbd/index.d.ts +4 -0
- package/dist/types/src/components/GrNumberInput/GrNumberInput.vue.d.ts +16 -0
- package/dist/types/src/components/GrSelect/GrSelect.vue.d.ts +2 -2
- package/dist/types/src/components/GrTabPanels/GrTabPanel.vue.d.ts +46 -0
- package/dist/types/src/components/GrTabPanels/GrTabPanels.vue.d.ts +51 -0
- package/dist/types/src/components/GrTabPanels/config.d.ts +5 -0
- package/dist/types/src/components/GrTabPanels/context.d.ts +9 -0
- package/dist/types/src/components/GrTabPanels/index.d.ts +6 -0
- package/dist/types/src/components/GrTabs/GrTabs.vue.d.ts +6 -0
- package/dist/types/src/components/GrTree/grTreeInteractionContext.d.ts +2 -0
- package/dist/types/src/composables/internal/useScrollLock.d.ts +24 -0
- package/dist/types/src/granular-provider/shared.d.ts +3 -0
- package/dist/types/src/i18n/messages/en.d.ts +10 -0
- package/dist/types/src/i18n/messages/es.d.ts +10 -0
- package/dist/types/src/i18n/messages/ru.d.ts +10 -0
- package/dist/types/src/index.d.ts +3 -0
- package/dist/web-types.json +275 -26
- package/package.json +13 -1
- package/dist/chunks/config-1uSYl98Y.js.map +0 -1
- package/dist/chunks/config-BIRO0vDJ2.js.map +0 -1
- package/dist/chunks/config-BXtAmU3s.js.map +0 -1
- package/dist/chunks/config-CGHLoBlH2.js.map +0 -1
- package/dist/chunks/config-CrG1XiE7.js.map +0 -1
- package/dist/chunks/config-DUcu1D68.js.map +0 -1
- package/dist/chunks/config-DnvPqu4g.js.map +0 -1
- package/dist/chunks/config-DpqOnJiz2.js.map +0 -1
- package/dist/chunks/config-JpHA4Xx0.js.map +0 -1
- package/dist/chunks/config-LGrZIdaZ2.js.map +0 -1
- package/dist/chunks/shared-ChvcQgDl.js.map +0 -1
- package/dist/chunks/transition-TLFw4KBn.js.map +0 -1
- package/dist/components/GrDataTable/chunks/GrDataTable-ptbARmJ3.js.map +0 -1
- package/dist/components/GrDrawer/chunks/GrDrawer-D1kzsjXN.js.map +0 -1
- package/dist/components/GrDropdown/chunks/GrDropdown-BXb5Cxxf.js.map +0 -1
- package/dist/components/GrFormField/chunks/GrFormField-BujNQrw8.js +0 -35
- package/dist/components/GrFormField/chunks/GrFormField-BujNQrw8.js.map +0 -1
- package/dist/components/GrImageViewer/chunks/GrImageViewer-OCUIrNh2.js.map +0 -1
- package/dist/components/GrInput/chunks/GrInput-C8pOJgCi.js +0 -188
- package/dist/components/GrInput/chunks/GrInput-C8pOJgCi.js.map +0 -1
- package/dist/components/GrModal/chunks/GrModal-BkjW9jZS.js.map +0 -1
- package/dist/components/GrNumberInput/chunks/GrNumberInput-CldPIWQx.js.map +0 -1
- package/dist/components/GrSelect/chunks/GrSelect-RShK21lK.js.map +0 -1
- package/dist/components/GrTabs/chunks/GrTabs-Z-FDJUr_.js.map +0 -1
- package/dist/components/GrTextarea/chunks/GrTextarea-D56YxhKq.js.map +0 -1
- package/dist/components/GrTree/chunks/GrTree-D3ODe2dB.js.map +0 -1
- /package/dist/chunks/{config-1uSYl98Y.js → config-1uSYl98Y2.js} +0 -0
- /package/dist/chunks/{config-BIRO0vDJ2.js → config-BIRO0vDJ.js} +0 -0
- /package/dist/chunks/{config-BXtAmU3s.js → config-BXtAmU3s2.js} +0 -0
- /package/dist/chunks/{config-CGHLoBlH2.js → config-CGHLoBlH.js} +0 -0
- /package/dist/chunks/{config-CrG1XiE7.js → config-CrG1XiE72.js} +0 -0
- /package/dist/chunks/{config-DUcu1D68.js → config-DUcu1D682.js} +0 -0
- /package/dist/chunks/{config-DnvPqu4g.js → config-DnvPqu4g2.js} +0 -0
- /package/dist/chunks/{config-DpqOnJiz2.js → config-DpqOnJiz.js} +0 -0
- /package/dist/chunks/{config-JpHA4Xx0.js → config-JpHA4Xx02.js} +0 -0
- /package/dist/chunks/{config-LGrZIdaZ2.js → config-LGrZIdaZ.js} +0 -0
|
@@ -1,48 +1,7 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as Je, i as Ge, n as Se, o as Ve, r as he, s as Ye, t as useScrollLock } from "../../../chunks/useScrollLock-K7kaP2Ud.js";
|
|
2
|
+
import { n as removeGrModalEsc, t as pushGrModalEsc } from "../../../chunks/grModalEscStack-BXk-9mv7.js";
|
|
2
3
|
import { c as panelTransition, d as shell, i as overlayTransition, n as layout, r as overlay, t as getGrModalPanelClass, u as root } from "../../../chunks/grModalStyles-DWd3i05Q.js";
|
|
3
4
|
import { Teleport, computed, createBlock, createCommentVNode, createElementVNode, createVNode, defineComponent, normalizeClass, onBeforeUnmount, openBlock, ref, renderSlot, unref, watch, withCtx } from "vue";
|
|
4
|
-
//#region src/components/GrModal/grModalEscStack.ts
|
|
5
|
-
var stack$1 = [];
|
|
6
|
-
var listening = false;
|
|
7
|
-
var nextId$1 = 1;
|
|
8
|
-
function handleKeydown(event) {
|
|
9
|
-
if (event.key !== "Escape") return;
|
|
10
|
-
if (stack$1.length === 0) return;
|
|
11
|
-
const top = stack$1[stack$1.length - 1];
|
|
12
|
-
event.preventDefault();
|
|
13
|
-
event.stopPropagation();
|
|
14
|
-
if (typeof event.stopImmediatePropagation === "function") event.stopImmediatePropagation();
|
|
15
|
-
if (top.shouldClose()) top.close();
|
|
16
|
-
}
|
|
17
|
-
function startListening() {
|
|
18
|
-
if (listening) return;
|
|
19
|
-
if (typeof window === "undefined") return;
|
|
20
|
-
window.addEventListener("keydown", handleKeydown, true);
|
|
21
|
-
listening = true;
|
|
22
|
-
}
|
|
23
|
-
function stopListening() {
|
|
24
|
-
if (!listening) return;
|
|
25
|
-
if (typeof window === "undefined") return;
|
|
26
|
-
window.removeEventListener("keydown", handleKeydown, true);
|
|
27
|
-
listening = false;
|
|
28
|
-
}
|
|
29
|
-
/** Регистрирует открытую модалку как верхнюю и возвращает её id. */
|
|
30
|
-
function pushGrModalEsc(entry) {
|
|
31
|
-
const id = nextId$1++;
|
|
32
|
-
stack$1.push({
|
|
33
|
-
...entry,
|
|
34
|
-
id
|
|
35
|
-
});
|
|
36
|
-
startListening();
|
|
37
|
-
return id;
|
|
38
|
-
}
|
|
39
|
-
/** Снимает модалку со стека (при закрытии или размонтировании). */
|
|
40
|
-
function removeGrModalEsc(id) {
|
|
41
|
-
const index = stack$1.findIndex((item) => item.id === id);
|
|
42
|
-
if (index >= 0) stack$1.splice(index, 1);
|
|
43
|
-
if (stack$1.length === 0) stopListening();
|
|
44
|
-
}
|
|
45
|
-
//#endregion
|
|
46
5
|
//#region src/components/GrModal/grModalTopStack.ts
|
|
47
6
|
var stack = [];
|
|
48
7
|
var nextId = 1;
|
|
@@ -157,19 +116,7 @@ var GrModal_default = /* @__PURE__ */ defineComponent({
|
|
|
157
116
|
function onOverlayPointerDown() {
|
|
158
117
|
closeReason.value = "backdrop";
|
|
159
118
|
}
|
|
160
|
-
|
|
161
|
-
function lockBodyScroll() {
|
|
162
|
-
if (!isClient) return;
|
|
163
|
-
if (savedBodyOverflow !== null) return;
|
|
164
|
-
savedBodyOverflow = document.body.style.overflow;
|
|
165
|
-
document.body.style.overflow = "hidden";
|
|
166
|
-
}
|
|
167
|
-
function unlockBodyScroll() {
|
|
168
|
-
if (!isClient) return;
|
|
169
|
-
if (savedBodyOverflow === null) return;
|
|
170
|
-
document.body.style.overflow = savedBodyOverflow;
|
|
171
|
-
savedBodyOverflow = null;
|
|
172
|
-
}
|
|
119
|
+
const { lock: lockBodyScroll, unlock: unlockBodyScroll } = useScrollLock();
|
|
173
120
|
watch(() => props.modelValue, (value) => {
|
|
174
121
|
if (value) {
|
|
175
122
|
lockBodyScroll();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GrModal-C0GZrHN3.js","names":["$slots"],"sources":["../../../../src/components/GrModal/grModalTopStack.ts","../../../../src/components/GrModal/GrModal.vue","../../../../src/components/GrModal/GrModal.vue"],"sourcesContent":["/**\n * grModalTopStack — общий стек открытых `GrModal` для вычисления «верхнего»\n * (последнего открытого) окна независимо от дерева рендера.\n *\n * Зачем нужен:\n * HeadlessUI ловит фокус (`FocusTrap` с `FocusLock`) у каждого открытого\n * `<Dialog>` и помечает остальную часть страницы как inert. Когда модалки\n * рендерятся в разных деревьях (например, диалоги `useDialogService`\n * монтируются отдельным `render()` в `document.body`), их HeadlessUI-стек не\n * общий: каждый `<Dialog>` считает себя верхним и продолжает удерживать фокус.\n * В итоге нижнее окно «ворует» фокус у верхнего диалога — нельзя, например,\n * сфокусировать input в открытом поверх `prompt`.\n *\n * Решение: единый стек регистраций. Верхней считается последняя\n * зарегистрированная модалка. Все нижние окна уведомляются (`setTopmost(false)`)\n * и помечают свой корень `inert`, освобождая фокус для верхнего окна. Когда\n * верхнее окно закрывается, следующая по стеку модалка снова становится\n * верхней (`setTopmost(true)`) и снимает `inert`.\n */\n\nexport interface GrModalTopEntry {\n /** Уникальный id зарегистрированной модалки. */\n id: number\n /** Колбэк-уведомление: является ли модалка сейчас верхней. */\n setTopmost: (isTopmost: boolean) => void\n}\n\nconst stack: GrModalTopEntry[] = []\nlet nextId = 1\n\n/** Пересчитывает «верхнее» окно и уведомляет все зарегистрированные модалки. */\nfunction sync(): void {\n const lastIndex = stack.length - 1\n stack.forEach((entry, index) => entry.setTopmost(index === lastIndex))\n}\n\n/** Регистрирует открытую модалку как верхнюю и возвращает её id. */\nexport function pushGrModalTop(entry: Omit<GrModalTopEntry, 'id'>): number {\n const id = nextId++\n stack.push({ ...entry, id })\n sync()\n return id\n}\n\n/** Снимает модалку со стека (при закрытии или размонтировании). */\nexport function removeGrModalTop(id: number): void {\n const index = stack.findIndex(item => item.id === id)\n if (index >= 0) stack.splice(index, 1)\n sync()\n}\n\n/** Тестовая/служебная очистка стека. */\nexport function resetGrModalTopStack(): void {\n stack.splice(0, stack.length)\n}\n","<script setup lang=\"ts\">\n/**\n * GrModal — GR-примитив модального окна на базе `@headlessui/vue`.\n *\n * Публичный API:\n * - `modelValue` (v-model) — открыто/закрыто;\n * - `size` — размер панели (`sm | md | lg | xl | full`);\n * - `closeOnBackdrop` (default: true) — закрывать при клике по оверлею;\n * - `closeOnEsc` (default: true) — закрывать по Esc;\n * - слоты `#title` / `#description` — если переданы, оборачиваются в\n * `DialogTitle` / `DialogDescription` (связь через `aria-labelledby` /\n * `aria-describedby` ставится HeadlessUI автоматически).\n *\n * Esc обрабатывается через общий стек открытых модалок (`grModalEscStack`):\n * единый capture-обработчик на `window` закрывает только верхнюю (последнюю\n * открытую) модалку и опережает window-обработчик Escape HeadlessUI. Это\n * чинит кейс, когда поверх `GrModal` открыт диалог `useDialogService` (другое\n * дерево рендера): Esc закрывает именно верхний диалог, а не нижнее окно.\n * Клик по оверлею (backdrop) по-прежнему идёт через `@close` HeadlessUI;\n * источник закрытия различается через ref `closeReason`.\n */\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { Dialog, DialogDescription, DialogPanel, DialogTitle, TransitionChild, TransitionRoot } from '@headlessui/vue'\n\nimport { pushGrModalEsc, removeGrModalEsc } from './grModalEscStack'\nimport { pushGrModalTop, removeGrModalTop } from './grModalTopStack'\nimport { useScrollLock } from '../../composables/internal/useScrollLock'\n\nimport {\n type GrModalSize,\n getGrModalPanelClass,\n layout,\n overlay as overlayClass,\n overlayTransition,\n panelTransition,\n root as rootClass,\n shell,\n} from './grModalStyles'\n\nexport interface GrModalProps {\n modelValue: boolean\n closeOnBackdrop?: boolean\n closeOnEsc?: boolean\n size?: GrModalSize\n}\n\nconst props = withDefaults(defineProps<GrModalProps>(), {\n closeOnBackdrop: true,\n closeOnEsc: true,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: boolean): void\n}>()\n\nconst panelClass = computed(() => getGrModalPanelClass(props.size))\n\n// Ссылка на панель используется как `initialFocus` для HeadlessUI `<Dialog>`:\n// панель имеет `tabindex=\"-1\"`, поэтому всегда фокусируема программно. Это\n// убирает предупреждение «There are no focusable elements inside the\n// <FocusTrap />», которое HeadlessUI выводит, когда не находит фокусируемого\n// элемента (в т.ч. когда контент окна временно помечен `inert`).\nconst panelRef = ref<HTMLElement | null>(null)\n\n// Является ли это окно верхним (последним открытым) в общем стеке модалок.\n// Когда поверх открыт другой `GrModal`/диалог сервиса, окно перестаёт быть\n// верхним и помечается `inert`, чтобы не «воровать» фокус у верхнего окна.\nconst isTopmost = ref(true)\n\n// `inert` ставится только на нижние (не верхние) открытые окна. Inert-поддерево\n// не интерактивно и не фокусируемо, поэтому FocusLock нижнего `<Dialog>`\n// перестаёт возвращать фокус себе — верхний диалог удерживает фокус сам.\nconst inertAttr = computed(() => (props.modelValue && !isTopmost.value ? '' : undefined))\n\n// SSR-guard: на сервере `document.body` недоступен — отключаем teleport,\n// а в клиенте включаем после маунта.\nconst isClient = typeof window !== 'undefined'\n\n// Источник закрытия от HeadlessUI `<Dialog>` — либо Esc, либо клик по оверлею.\n// Различаем их, чтобы `closeOnBackdrop` и `closeOnEsc` работали независимо.\nconst closeReason = ref<'backdrop' | 'esc' | null>(null)\n\nfunction close(): void {\n emit('update:modelValue', false)\n}\n\nfunction onDialogClose(): void {\n const reason = closeReason.value\n closeReason.value = null\n\n if (reason === 'esc') {\n if (props.closeOnEsc) close()\n return\n }\n\n // По умолчанию (клик по оверлею или программный вызов `@close`)\n // уважаем `closeOnBackdrop`.\n if (props.closeOnBackdrop) close()\n}\n\nfunction onRootKeydown(event: KeyboardEvent): void {\n if (event.key === 'Escape') closeReason.value = 'esc'\n}\n\n// ————— Esc-стек: гарантирует, что Esc закрывает именно верхнюю (последнюю\n// открытую) модалку, даже если окна живут в разных деревьях рендера\n// (например, диалоги `useDialogService` поверх `GrModal`).\nlet escEntryId: number | null = null\nlet topEntryId: number | null = null\n\nfunction registerEsc(): void {\n if (escEntryId !== null) return\n escEntryId = pushGrModalEsc({\n shouldClose: () => props.closeOnEsc,\n close,\n })\n}\n\nfunction unregisterEsc(): void {\n if (escEntryId === null) return\n removeGrModalEsc(escEntryId)\n escEntryId = null\n}\n\nfunction registerTop(): void {\n if (topEntryId !== null) return\n topEntryId = pushGrModalTop({\n setTopmost: (value) => { isTopmost.value = value },\n })\n}\n\nfunction unregisterTop(): void {\n if (topEntryId === null) return\n removeGrModalTop(topEntryId)\n topEntryId = null\n isTopmost.value = true\n}\n\nfunction onOverlayPointerDown(): void {\n closeReason.value = 'backdrop'\n}\n\n// ————— Scroll lock на `<body>` на время открытия.\n// HeadlessUI Vue этого не делает автоматически, а фон скроллится — для\n// GR-примитива это мешающий UX. Общий reference-counted lock корректно\n// работает при нескольких открытых оверлеях (LIFO-независимо) и компенсирует\n// ширину скроллбара, чтобы контент не дёргался.\nconst { lock: lockBodyScroll, unlock: unlockBodyScroll } = useScrollLock()\n\nwatch(\n () => props.modelValue,\n (value) => {\n if (value) {\n lockBodyScroll()\n registerEsc()\n registerTop()\n }\n else {\n unlockBodyScroll()\n unregisterEsc()\n unregisterTop()\n }\n },\n { immediate: true },\n)\n\nonBeforeUnmount(() => {\n unlockBodyScroll()\n unregisterEsc()\n unregisterTop()\n})\n\n</script>\n\n<template>\n <teleport to=\"body\" :disabled=\"!isClient\">\n <TransitionRoot :show=\"modelValue\" as=\"template\">\n <Dialog\n as=\"div\"\n :class=\"rootClass\"\n :initial-focus=\"panelRef\"\n :inert=\"inertAttr\"\n @close=\"onDialogClose\"\n @keydown.capture=\"onRootKeydown\"\n >\n <div :class=\"shell\">\n <div :class=\"layout\">\n <TransitionChild\n as=\"template\"\n :enter=\"overlayTransition.enter\"\n :enter-from=\"overlayTransition.enterFrom\"\n :enter-to=\"overlayTransition.enterTo\"\n :leave=\"overlayTransition.leave\"\n :leave-from=\"overlayTransition.leaveFrom\"\n :leave-to=\"overlayTransition.leaveTo\"\n >\n <div\n data-gr-modal-overlay\n :class=\"overlayClass\"\n aria-hidden=\"true\"\n @pointerdown=\"onOverlayPointerDown\"\n />\n </TransitionChild>\n\n <TransitionChild\n as=\"template\"\n :enter=\"panelTransition.enter\"\n :enter-from=\"panelTransition.enterFrom\"\n :enter-to=\"panelTransition.enterTo\"\n :leave=\"panelTransition.leave\"\n :leave-from=\"panelTransition.leaveFrom\"\n :leave-to=\"panelTransition.leaveTo\"\n >\n <DialogPanel\n ref=\"panelRef\"\n data-gr-modal-panel\n tabindex=\"-1\"\n :class=\"panelClass\"\n >\n <DialogTitle v-if=\"$slots.title\" as=\"div\" data-gr-modal-title>\n <slot name=\"title\" />\n </DialogTitle>\n <DialogDescription v-if=\"$slots.description\" as=\"div\" data-gr-modal-description>\n <slot name=\"description\" />\n </DialogDescription>\n <slot />\n </DialogPanel>\n </TransitionChild>\n </div>\n </div>\n </Dialog>\n </TransitionRoot>\n </teleport>\n</template>\n","<script setup lang=\"ts\">\n/**\n * GrModal — GR-примитив модального окна на базе `@headlessui/vue`.\n *\n * Публичный API:\n * - `modelValue` (v-model) — открыто/закрыто;\n * - `size` — размер панели (`sm | md | lg | xl | full`);\n * - `closeOnBackdrop` (default: true) — закрывать при клике по оверлею;\n * - `closeOnEsc` (default: true) — закрывать по Esc;\n * - слоты `#title` / `#description` — если переданы, оборачиваются в\n * `DialogTitle` / `DialogDescription` (связь через `aria-labelledby` /\n * `aria-describedby` ставится HeadlessUI автоматически).\n *\n * Esc обрабатывается через общий стек открытых модалок (`grModalEscStack`):\n * единый capture-обработчик на `window` закрывает только верхнюю (последнюю\n * открытую) модалку и опережает window-обработчик Escape HeadlessUI. Это\n * чинит кейс, когда поверх `GrModal` открыт диалог `useDialogService` (другое\n * дерево рендера): Esc закрывает именно верхний диалог, а не нижнее окно.\n * Клик по оверлею (backdrop) по-прежнему идёт через `@close` HeadlessUI;\n * источник закрытия различается через ref `closeReason`.\n */\nimport { computed, onBeforeUnmount, ref, watch } from 'vue'\nimport { Dialog, DialogDescription, DialogPanel, DialogTitle, TransitionChild, TransitionRoot } from '@headlessui/vue'\n\nimport { pushGrModalEsc, removeGrModalEsc } from './grModalEscStack'\nimport { pushGrModalTop, removeGrModalTop } from './grModalTopStack'\nimport { useScrollLock } from '../../composables/internal/useScrollLock'\n\nimport {\n type GrModalSize,\n getGrModalPanelClass,\n layout,\n overlay as overlayClass,\n overlayTransition,\n panelTransition,\n root as rootClass,\n shell,\n} from './grModalStyles'\n\nexport interface GrModalProps {\n modelValue: boolean\n closeOnBackdrop?: boolean\n closeOnEsc?: boolean\n size?: GrModalSize\n}\n\nconst props = withDefaults(defineProps<GrModalProps>(), {\n closeOnBackdrop: true,\n closeOnEsc: true,\n size: 'md',\n})\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: boolean): void\n}>()\n\nconst panelClass = computed(() => getGrModalPanelClass(props.size))\n\n// Ссылка на панель используется как `initialFocus` для HeadlessUI `<Dialog>`:\n// панель имеет `tabindex=\"-1\"`, поэтому всегда фокусируема программно. Это\n// убирает предупреждение «There are no focusable elements inside the\n// <FocusTrap />», которое HeadlessUI выводит, когда не находит фокусируемого\n// элемента (в т.ч. когда контент окна временно помечен `inert`).\nconst panelRef = ref<HTMLElement | null>(null)\n\n// Является ли это окно верхним (последним открытым) в общем стеке модалок.\n// Когда поверх открыт другой `GrModal`/диалог сервиса, окно перестаёт быть\n// верхним и помечается `inert`, чтобы не «воровать» фокус у верхнего окна.\nconst isTopmost = ref(true)\n\n// `inert` ставится только на нижние (не верхние) открытые окна. Inert-поддерево\n// не интерактивно и не фокусируемо, поэтому FocusLock нижнего `<Dialog>`\n// перестаёт возвращать фокус себе — верхний диалог удерживает фокус сам.\nconst inertAttr = computed(() => (props.modelValue && !isTopmost.value ? '' : undefined))\n\n// SSR-guard: на сервере `document.body` недоступен — отключаем teleport,\n// а в клиенте включаем после маунта.\nconst isClient = typeof window !== 'undefined'\n\n// Источник закрытия от HeadlessUI `<Dialog>` — либо Esc, либо клик по оверлею.\n// Различаем их, чтобы `closeOnBackdrop` и `closeOnEsc` работали независимо.\nconst closeReason = ref<'backdrop' | 'esc' | null>(null)\n\nfunction close(): void {\n emit('update:modelValue', false)\n}\n\nfunction onDialogClose(): void {\n const reason = closeReason.value\n closeReason.value = null\n\n if (reason === 'esc') {\n if (props.closeOnEsc) close()\n return\n }\n\n // По умолчанию (клик по оверлею или программный вызов `@close`)\n // уважаем `closeOnBackdrop`.\n if (props.closeOnBackdrop) close()\n}\n\nfunction onRootKeydown(event: KeyboardEvent): void {\n if (event.key === 'Escape') closeReason.value = 'esc'\n}\n\n// ————— Esc-стек: гарантирует, что Esc закрывает именно верхнюю (последнюю\n// открытую) модалку, даже если окна живут в разных деревьях рендера\n// (например, диалоги `useDialogService` поверх `GrModal`).\nlet escEntryId: number | null = null\nlet topEntryId: number | null = null\n\nfunction registerEsc(): void {\n if (escEntryId !== null) return\n escEntryId = pushGrModalEsc({\n shouldClose: () => props.closeOnEsc,\n close,\n })\n}\n\nfunction unregisterEsc(): void {\n if (escEntryId === null) return\n removeGrModalEsc(escEntryId)\n escEntryId = null\n}\n\nfunction registerTop(): void {\n if (topEntryId !== null) return\n topEntryId = pushGrModalTop({\n setTopmost: (value) => { isTopmost.value = value },\n })\n}\n\nfunction unregisterTop(): void {\n if (topEntryId === null) return\n removeGrModalTop(topEntryId)\n topEntryId = null\n isTopmost.value = true\n}\n\nfunction onOverlayPointerDown(): void {\n closeReason.value = 'backdrop'\n}\n\n// ————— Scroll lock на `<body>` на время открытия.\n// HeadlessUI Vue этого не делает автоматически, а фон скроллится — для\n// GR-примитива это мешающий UX. Общий reference-counted lock корректно\n// работает при нескольких открытых оверлеях (LIFO-независимо) и компенсирует\n// ширину скроллбара, чтобы контент не дёргался.\nconst { lock: lockBodyScroll, unlock: unlockBodyScroll } = useScrollLock()\n\nwatch(\n () => props.modelValue,\n (value) => {\n if (value) {\n lockBodyScroll()\n registerEsc()\n registerTop()\n }\n else {\n unlockBodyScroll()\n unregisterEsc()\n unregisterTop()\n }\n },\n { immediate: true },\n)\n\nonBeforeUnmount(() => {\n unlockBodyScroll()\n unregisterEsc()\n unregisterTop()\n})\n\n</script>\n\n<template>\n <teleport to=\"body\" :disabled=\"!isClient\">\n <TransitionRoot :show=\"modelValue\" as=\"template\">\n <Dialog\n as=\"div\"\n :class=\"rootClass\"\n :initial-focus=\"panelRef\"\n :inert=\"inertAttr\"\n @close=\"onDialogClose\"\n @keydown.capture=\"onRootKeydown\"\n >\n <div :class=\"shell\">\n <div :class=\"layout\">\n <TransitionChild\n as=\"template\"\n :enter=\"overlayTransition.enter\"\n :enter-from=\"overlayTransition.enterFrom\"\n :enter-to=\"overlayTransition.enterTo\"\n :leave=\"overlayTransition.leave\"\n :leave-from=\"overlayTransition.leaveFrom\"\n :leave-to=\"overlayTransition.leaveTo\"\n >\n <div\n data-gr-modal-overlay\n :class=\"overlayClass\"\n aria-hidden=\"true\"\n @pointerdown=\"onOverlayPointerDown\"\n />\n </TransitionChild>\n\n <TransitionChild\n as=\"template\"\n :enter=\"panelTransition.enter\"\n :enter-from=\"panelTransition.enterFrom\"\n :enter-to=\"panelTransition.enterTo\"\n :leave=\"panelTransition.leave\"\n :leave-from=\"panelTransition.leaveFrom\"\n :leave-to=\"panelTransition.leaveTo\"\n >\n <DialogPanel\n ref=\"panelRef\"\n data-gr-modal-panel\n tabindex=\"-1\"\n :class=\"panelClass\"\n >\n <DialogTitle v-if=\"$slots.title\" as=\"div\" data-gr-modal-title>\n <slot name=\"title\" />\n </DialogTitle>\n <DialogDescription v-if=\"$slots.description\" as=\"div\" data-gr-modal-description>\n <slot name=\"description\" />\n </DialogDescription>\n <slot />\n </DialogPanel>\n </TransitionChild>\n </div>\n </div>\n </Dialog>\n </TransitionRoot>\n </teleport>\n</template>\n"],"mappings":";;;;;AA2BA,IAAM,QAA2B,CAAC;AAClC,IAAI,SAAS;;AAGb,SAAS,OAAa;CACpB,MAAM,YAAY,MAAM,SAAS;CACjC,MAAM,SAAS,OAAO,UAAU,MAAM,WAAW,UAAU,SAAS,CAAC;AACvE;;AAGA,SAAgB,eAAe,OAA4C;CACzE,MAAM,KAAK;CACX,MAAM,KAAK;EAAE,GAAG;EAAO;CAAG,CAAC;CAC3B,KAAK;CACL,OAAO;AACT;;AAGA,SAAgB,iBAAiB,IAAkB;CACjD,MAAM,QAAQ,MAAM,WAAU,SAAQ,KAAK,OAAO,EAAE;CACpD,IAAI,SAAS,GAAG,MAAM,OAAO,OAAO,CAAC;CACrC,KAAK;AACP;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ECHA,MAAM,QAAQ;EAMd,MAAM,OAAO;EAIb,MAAM,aAAa,eAAe,qBAAqB,MAAM,IAAI,CAAC;EAOlE,MAAM,WAAW,IAAwB,IAAI;EAK7C,MAAM,YAAY,IAAI,IAAI;EAK1B,MAAM,YAAY,eAAgB,MAAM,cAAc,CAAC,UAAU,QAAQ,KAAK,KAAA,CAAU;EAIxF,MAAM,WAAW,OAAO,WAAW;EAInC,MAAM,cAAc,IAA+B,IAAI;EAEvD,SAAS,QAAc;GACrB,KAAK,qBAAqB,KAAK;EACjC;EAEA,SAAS,gBAAsB;GAC7B,MAAM,SAAS,YAAY;GAC3B,YAAY,QAAQ;GAEpB,IAAI,WAAW,OAAO;IACpB,IAAI,MAAM,YAAY,MAAM;IAC5B;GACF;GAIA,IAAI,MAAM,iBAAiB,MAAM;EACnC;EAEA,SAAS,cAAc,OAA4B;GACjD,IAAI,MAAM,QAAQ,UAAU,YAAY,QAAQ;EAClD;EAKA,IAAI,aAA4B;EAChC,IAAI,aAA4B;EAEhC,SAAS,cAAoB;GAC3B,IAAI,eAAe,MAAM;GACzB,aAAa,eAAe;IAC1B,mBAAmB,MAAM;IACzB;GACF,CAAC;EACH;EAEA,SAAS,gBAAsB;GAC7B,IAAI,eAAe,MAAM;GACzB,iBAAiB,UAAU;GAC3B,aAAa;EACf;EAEA,SAAS,cAAoB;GAC3B,IAAI,eAAe,MAAM;GACzB,aAAa,eAAe,EAC1B,aAAa,UAAU;IAAE,UAAU,QAAQ;GAAM,EACnD,CAAC;EACH;EAEA,SAAS,gBAAsB;GAC7B,IAAI,eAAe,MAAM;GACzB,iBAAiB,UAAU;GAC3B,aAAa;GACb,UAAU,QAAQ;EACpB;EAEA,SAAS,uBAA6B;GACpC,YAAY,QAAQ;EACtB;EAOA,MAAM,EAAE,MAAM,gBAAgB,QAAQ,qBAAqB,cAAc;EAEzE,YACQ,MAAM,aACX,UAAU;GACT,IAAI,OAAO;IACT,eAAe;IACf,YAAY;IACZ,YAAY;GACd,OACK;IACH,iBAAiB;IACjB,cAAc;IACd,cAAc;GAChB;EACF,GACA,EAAE,WAAW,KAAK,CACpB;EAEA,sBAAsB;GACpB,iBAAiB;GACjB,cAAc;GACd,cAAc;EAChB,CAAC;;uBAKC,YAyDW,UAAA;IAzDD,IAAG;IAAQ,UAAQ,CAAG;OAC9B,YAuDiB,MAAA,EAAA,GAAA;IAvDA,MAAM,QAAA;IAAY,IAAG;;2BAsD3B,CArDT,YAqDS,MAAA,EAAA,GAAA;KApDP,IAAG;KACF,OAAK,eAAE,MAAA,IAAA,CAAS;KAChB,iBAAe,SAAA;KACf,OAAO,UAAA;KACP,SAAO;uBACU;;4BA8CZ,CA5CN,mBA4CM,OAAA,EA5CA,OAAK,eAAE,MAAA,KAAA,CAAK,EAAA,GAAA,CAChB,mBA0CM,OAAA,EA1CA,OAAK,eAAE,MAAA,MAAA,CAAM,EAAA,GAAA,CACjB,YAekB,MAAA,EAAA,GAAA;MAdhB,IAAG;MACF,OAAO,MAAA,iBAAA,CAAiB,CAAC;MACzB,cAAY,MAAA,iBAAA,CAAiB,CAAC;MAC9B,YAAU,MAAA,iBAAA,CAAiB,CAAC;MAC5B,OAAO,MAAA,iBAAA,CAAiB,CAAC;MACzB,cAAY,MAAA,iBAAA,CAAiB,CAAC;MAC9B,YAAU,MAAA,iBAAA,CAAiB,CAAC;;6BAO3B,CALF,mBAKE,OAAA;OAJA,yBAAA;OACC,OAAK,eAAE,MAAA,OAAA,CAAY;OACpB,eAAY;OACX,eAAa;;;;;;;;;;SAIlB,YAuBkB,MAAA,EAAA,GAAA;MAtBhB,IAAG;MACF,OAAO,MAAA,eAAA,CAAe,CAAC;MACvB,cAAY,MAAA,eAAA,CAAe,CAAC;MAC5B,YAAU,MAAA,eAAA,CAAe,CAAC;MAC1B,OAAO,MAAA,eAAA,CAAe,CAAC;MACvB,cAAY,MAAA,eAAA,CAAe,CAAC;MAC5B,YAAU,MAAA,eAAA,CAAe,CAAC;;6BAeb,CAbd,YAac,MAAA,EAAA,GAAA;gBAZR;OAAJ,KAAI;OACJ,uBAAA;OACA,UAAS;OACR,OAAK,eAAE,WAAA,KAAU;;8BAIJ;QAFKA,KAAAA,OAAO,SAAA,UAAA,GAA1B,YAEc,MAAA,EAAA,GAAA;;SAFmB,IAAG;SAAM,uBAAA;;gCACnB,CAArB,WAAqB,KAAA,QAAA,OAAA,CAAA,CAAA;;;QAEEA,KAAAA,OAAO,eAAA,UAAA,GAAhC,YAEoB,MAAA,EAAA,GAAA;;SAFyB,IAAG;SAAM,6BAAA;;gCACzB,CAA3B,WAA2B,KAAA,QAAA,aAAA,CAAA,CAAA;;;QAE7B,WAAQ,KAAA,QAAA,SAAA"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as GrModal_default } from "./chunks/GrModal-
|
|
1
|
+
import { t as GrModal_default } from "./chunks/GrModal-C0GZrHN3.js";
|
|
2
2
|
import { t as getGrModalPanelClass } from "../../chunks/grModalStyles-DWd3i05Q.js";
|
|
3
3
|
import { n as grModalClassTokens, r as grModalSafelist, t as grModalConfig } from "../../chunks/config-CziwTDDL.js";
|
|
4
4
|
export { GrModal_default as GrModal, GrModal_default as default, getGrModalPanelClass, grModalClassTokens, grModalConfig, grModalSafelist };
|
package/dist/components/GrNumberInput/chunks/{GrNumberInput-CldPIWQx.js → GrNumberInput-DxCEvL7V.js}
RENAMED
|
@@ -11,6 +11,9 @@ var _hoisted_1 = [
|
|
|
11
11
|
"placeholder",
|
|
12
12
|
"disabled",
|
|
13
13
|
"value",
|
|
14
|
+
"aria-valuenow",
|
|
15
|
+
"aria-valuemin",
|
|
16
|
+
"aria-valuemax",
|
|
14
17
|
"aria-invalid"
|
|
15
18
|
];
|
|
16
19
|
var _hoisted_2 = { class: "flex flex-col justify-center gap-1" };
|
|
@@ -46,6 +49,11 @@ var GrNumberInput_default = /* @__PURE__ */ defineComponent({
|
|
|
46
49
|
min: { default: void 0 },
|
|
47
50
|
max: { default: void 0 },
|
|
48
51
|
precision: { default: void 0 },
|
|
52
|
+
locale: { default: void 0 },
|
|
53
|
+
useGrouping: {
|
|
54
|
+
type: Boolean,
|
|
55
|
+
default: false
|
|
56
|
+
},
|
|
49
57
|
controls: {
|
|
50
58
|
type: Boolean,
|
|
51
59
|
default: false
|
|
@@ -180,6 +188,10 @@ var GrNumberInput_default = /* @__PURE__ */ defineComponent({
|
|
|
180
188
|
let hasSepLocal = false;
|
|
181
189
|
for (let i = 0; i < raw.length; i++) {
|
|
182
190
|
const ch = raw[i];
|
|
191
|
+
if (ch === "-" && out.length === 0) {
|
|
192
|
+
out += "-";
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
183
195
|
if (isDigit(ch)) {
|
|
184
196
|
out += ch;
|
|
185
197
|
continue;
|
|
@@ -212,29 +224,92 @@ var GrNumberInput_default = /* @__PURE__ */ defineComponent({
|
|
|
212
224
|
function format(n) {
|
|
213
225
|
const sep = props.decimalSeparator || ".";
|
|
214
226
|
const p = props.precision;
|
|
215
|
-
let s = p === void 0 ?
|
|
227
|
+
let s = p === void 0 ? n.toLocaleString("en-US", {
|
|
228
|
+
useGrouping: false,
|
|
229
|
+
maximumFractionDigits: 20
|
|
230
|
+
}) : n.toFixed(Math.max(0, p));
|
|
216
231
|
if (sep !== ".") s = s.replace(".", sep);
|
|
217
232
|
return s;
|
|
218
233
|
}
|
|
219
234
|
function onInput(e) {
|
|
220
235
|
const el = e.target;
|
|
221
|
-
const
|
|
222
|
-
|
|
236
|
+
const raw = el.value;
|
|
237
|
+
const next = sanitize(raw);
|
|
238
|
+
if (raw !== next) {
|
|
239
|
+
const caret = el.selectionStart ?? raw.length;
|
|
240
|
+
const keptBefore = sanitize(raw.slice(0, caret)).length;
|
|
241
|
+
el.value = next;
|
|
242
|
+
el.setSelectionRange(keptBefore, keptBefore);
|
|
243
|
+
}
|
|
223
244
|
emit("update:modelValue", next);
|
|
224
245
|
}
|
|
225
|
-
function
|
|
226
|
-
const
|
|
227
|
-
const next = sanitize(el.value);
|
|
246
|
+
function commit(el) {
|
|
247
|
+
const num = toNumber(sanitize(el.value));
|
|
248
|
+
const next = num === null ? sanitize(el.value) : format(clamp(normalize(num)));
|
|
228
249
|
if (el.value !== next) el.value = next;
|
|
250
|
+
if (next !== props.modelValue) emit("update:modelValue", next);
|
|
229
251
|
emit("change", next);
|
|
230
252
|
}
|
|
231
|
-
function
|
|
232
|
-
|
|
233
|
-
|
|
253
|
+
function onChange(e) {
|
|
254
|
+
commit(e.target);
|
|
255
|
+
}
|
|
256
|
+
function setValue(n) {
|
|
257
|
+
const nextStr = format(clamp(normalize(n)));
|
|
234
258
|
emit("update:modelValue", nextStr);
|
|
235
259
|
emit("change", nextStr);
|
|
260
|
+
}
|
|
261
|
+
function stepBy(dir) {
|
|
262
|
+
if (props.disabled) return;
|
|
263
|
+
setValue((toNumber(props.modelValue) ?? 0) + (props.step ?? 1) * dir);
|
|
236
264
|
focus();
|
|
237
265
|
}
|
|
266
|
+
function onKeydown(e) {
|
|
267
|
+
if (props.disabled) return;
|
|
268
|
+
switch (e.key) {
|
|
269
|
+
case "ArrowUp":
|
|
270
|
+
e.preventDefault();
|
|
271
|
+
stepBy(1);
|
|
272
|
+
break;
|
|
273
|
+
case "ArrowDown":
|
|
274
|
+
e.preventDefault();
|
|
275
|
+
stepBy(-1);
|
|
276
|
+
break;
|
|
277
|
+
case "Home":
|
|
278
|
+
if (props.min !== void 0) {
|
|
279
|
+
e.preventDefault();
|
|
280
|
+
setValue(props.min);
|
|
281
|
+
}
|
|
282
|
+
break;
|
|
283
|
+
case "End":
|
|
284
|
+
if (props.max !== void 0) {
|
|
285
|
+
e.preventDefault();
|
|
286
|
+
setValue(props.max);
|
|
287
|
+
}
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
const ariaValueNow = computed(() => toNumber(props.modelValue) ?? void 0);
|
|
292
|
+
const isFocused = ref(false);
|
|
293
|
+
function formatGrouped(value) {
|
|
294
|
+
const num = toNumber(value);
|
|
295
|
+
if (num === null) return value;
|
|
296
|
+
const sep = props.decimalSeparator || ".";
|
|
297
|
+
return new Intl.NumberFormat(props.locale, {
|
|
298
|
+
useGrouping: true,
|
|
299
|
+
minimumFractionDigits: props.precision ?? 0,
|
|
300
|
+
maximumFractionDigits: props.precision ?? 20
|
|
301
|
+
}).formatToParts(num).map((part) => part.type === "decimal" ? sep : part.value).join("");
|
|
302
|
+
}
|
|
303
|
+
const displayValue = computed(() => {
|
|
304
|
+
if (!props.useGrouping || isFocused.value) return props.modelValue;
|
|
305
|
+
return formatGrouped(props.modelValue);
|
|
306
|
+
});
|
|
307
|
+
function onFocus() {
|
|
308
|
+
isFocused.value = true;
|
|
309
|
+
}
|
|
310
|
+
function onBlur() {
|
|
311
|
+
isFocused.value = false;
|
|
312
|
+
}
|
|
238
313
|
return (_ctx, _cache) => {
|
|
239
314
|
return openBlock(), createElementBlock("div", {
|
|
240
315
|
"data-gr-number-input": "",
|
|
@@ -261,12 +336,19 @@ var GrNumberInput_default = /* @__PURE__ */ defineComponent({
|
|
|
261
336
|
autocomplete: __props.autocomplete,
|
|
262
337
|
placeholder: __props.placeholder,
|
|
263
338
|
disabled: __props.disabled,
|
|
264
|
-
value:
|
|
339
|
+
value: displayValue.value,
|
|
340
|
+
role: "spinbutton",
|
|
341
|
+
"aria-valuenow": ariaValueNow.value,
|
|
342
|
+
"aria-valuemin": __props.min,
|
|
343
|
+
"aria-valuemax": __props.max,
|
|
265
344
|
"aria-invalid": __props.invalid ? "true" : void 0,
|
|
266
345
|
class: ["w-full bg-transparent text-[var(--fg)] placeholder:text-[var(--muted-fg)] focus:placeholder:text-transparent focus:outline-none disabled:cursor-not-allowed", inputClassName.value],
|
|
267
346
|
style: inputStyle.value,
|
|
268
347
|
onInput,
|
|
269
|
-
onChange
|
|
348
|
+
onChange,
|
|
349
|
+
onKeydown,
|
|
350
|
+
onFocus,
|
|
351
|
+
onBlur
|
|
270
352
|
}), null, 16, _hoisted_1),
|
|
271
353
|
_ctx.$slots.suffix ? (openBlock(), createElementBlock("div", {
|
|
272
354
|
key: 1,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GrNumberInput-DxCEvL7V.js","names":["$slots","$attrs"],"sources":["../../../../src/components/GrNumberInput/GrNumberInput.vue","../../../../src/components/GrNumberInput/GrNumberInput.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { InputHTMLAttributes } from 'vue'\nimport { computed, ref, useSlots } from 'vue'\n\nimport {\n grNumberInputInputClass,\n grNumberInputShellClass,\n type GrNumberInputControlsDirection,\n type GrNumberInputSize,\n type GrNumberInputState,\n type GrNumberInputTextAlign,\n} from './grNumberInputStyles'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { addLen, useAddonMeasurement } from '../../composables/internal/useAddonMeasurement'\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst ADDON_PX_BY_SIZE: Record<GrNumberInputSize, number> = {\n xs: 32,\n sm: 36,\n md: 40,\n lg: 48,\n}\n\nconst BASE_PADDING_X_LEN_BY_SIZE: Record<GrNumberInputSize, string> = {\n xs: '10px',\n sm: '12px',\n md: '12px',\n lg: '16px',\n}\n\nfunction px(n: number): string {\n return `${n}px`\n}\n\nexport interface GrNumberInputProps {\n /** Значение (строка — контролируем форматирование внутри). */\n modelValue: string\n placeholder?: string\n autocomplete?: string\n inputmode?: InputHTMLAttributes['inputmode']\n disabled?: boolean\n /** Быстрый флаг невалидности; эквивалент `state='danger'` + `aria-invalid`. */\n invalid?: boolean\n state?: GrNumberInputState\n name?: string\n id?: string\n size?: GrNumberInputSize\n\n textAlign?: GrNumberInputTextAlign\n\n decimalSeparator?: string\n step?: number\n min?: number\n max?: number\n precision?: number\n\n /**\n * BCP-47 локаль для отображения значения (группировка разрядов и разделители\n * через `Intl.NumberFormat`). Работает вместе с `useGrouping`.\n */\n locale?: string\n /**\n * Группировать разряды при отображении (когда поле не в фокусе). При фокусе\n * показывается «сырое» значение для редактирования. По умолчанию выключено.\n */\n useGrouping?: boolean\n\n /** Показывать кнопки +/-. */\n controls?: boolean\n controlsDirection?: GrNumberInputControlsDirection\n\n prefixMinWidth?: string\n prefixMaxWidth?: string\n suffixMinWidth?: string\n suffixMaxWidth?: string\n /**\n * Фиксированная ширина у prefix/suffix: жёсткая ширина (из `*MaxWidth` →\n * `*MinWidth` → дефолт) + обрезка контента по краю (prefix — справа,\n * suffix — слева). По умолчанию аддоны растягиваются под контент.\n */\n prefixFixed?: boolean\n suffixFixed?: boolean\n\n /** i18n-friendly aria-label для кнопки \"увеличить\". */\n increaseLabel?: string\n /** i18n-friendly aria-label для кнопки \"уменьшить\". */\n decreaseLabel?: string\n}\n\nconst props = withDefaults(defineProps<GrNumberInputProps>(), {\n placeholder: undefined,\n autocomplete: undefined,\n inputmode: 'decimal',\n disabled: false,\n invalid: false,\n state: 'default',\n name: undefined,\n id: undefined,\n size: 'md',\n\n textAlign: 'left',\n\n decimalSeparator: '.',\n step: 1,\n min: undefined,\n max: undefined,\n precision: undefined,\n locale: undefined,\n useGrouping: false,\n\n controls: false,\n controlsDirection: 'vertical',\n\n prefixMinWidth: undefined,\n prefixMaxWidth: undefined,\n suffixMinWidth: undefined,\n suffixMaxWidth: undefined,\n prefixFixed: false,\n suffixFixed: false,\n\n increaseLabel: undefined,\n decreaseLabel: undefined,\n})\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: string): void\n (e: 'change', value: string): void\n}>()\n\nconst inputEl = ref<HTMLInputElement | null>(null)\n\nfunction focus(): void {\n inputEl.value?.focus()\n}\n\ndefineExpose({\n focus,\n})\n\nconst slots = useSlots()\n\nconst { t } = useGranularityTranslations()\nconst resolvedIncreaseLabel = computed(() => props.increaseLabel ?? t('gr.numberInput.increase', 'Increase'))\nconst resolvedDecreaseLabel = computed(() => props.decreaseLabel ?? t('gr.numberInput.decrease', 'Decrease'))\n\nconst hasPrefix = computed(() => Boolean(slots.prefix))\nconst hasSuffix = computed(() => Boolean(slots.suffix))\n\nconst hasHorizontalControls = computed(() => props.controls && props.controlsDirection === 'horizontal')\nconst hasVerticalControls = computed(() => props.controls && props.controlsDirection === 'vertical')\n\nconst addonPx = computed(() => ADDON_PX_BY_SIZE[props.size])\nconst addonLen = computed(() => px(addonPx.value))\nconst basePaddingXLen = computed(() => BASE_PADDING_X_LEN_BY_SIZE[props.size])\n\nconst defaultAddonMinWidth = computed(() => addonLen.value)\nconst prefixMinWidth = computed(() => props.prefixMinWidth ?? defaultAddonMinWidth.value)\nconst suffixMinWidth = computed(() => props.suffixMinWidth ?? defaultAddonMinWidth.value)\n\n// Жёсткая ширина для fixed-режима: max → min → дефолт.\nconst prefixFixedWidth = computed(() => props.prefixMaxWidth ?? props.prefixMinWidth ?? defaultAddonMinWidth.value)\nconst suffixFixedWidth = computed(() => props.suffixMaxWidth ?? props.suffixMinWidth ?? defaultAddonMinWidth.value)\n\nconst { prefixEl, suffixEl, measuredPrefixWidth, measuredSuffixWidth } = useAddonMeasurement(hasPrefix, hasSuffix)\n\nconst prefixLen = computed(() => {\n if (!hasPrefix.value) return '0px'\n return props.prefixFixed ? prefixFixedWidth.value : measuredPrefixWidth.value ?? prefixMinWidth.value\n})\nconst suffixLen = computed(() => {\n if (!hasSuffix.value) return '0px'\n return props.suffixFixed ? suffixFixedWidth.value : measuredSuffixWidth.value ?? suffixMinWidth.value\n})\n\nfunction addonStyle(side: 'left' | 'right', offset: string): Record<string, string> {\n return {\n width: addonLen.value,\n [side]: offset,\n }\n}\n\nconst inputStyle = computed(() => {\n const leftControls = hasHorizontalControls.value ? addonLen.value : '0px'\n const rightControlsCount = (hasHorizontalControls.value ? 1 : 0) + (hasVerticalControls.value ? 1 : 0)\n const rightControls = rightControlsCount > 0 ? px(addonPx.value * rightControlsCount) : '0px'\n\n const leftReserved = addLen(prefixLen.value, leftControls)\n const rightReserved = addLen(suffixLen.value, rightControls)\n\n const left = hasPrefix.value || hasHorizontalControls.value ? addLen(leftReserved, basePaddingXLen.value) : undefined\n const right = hasSuffix.value || rightControlsCount > 0 ? addLen(rightReserved, basePaddingXLen.value) : undefined\n\n return {\n paddingLeft: left,\n paddingRight: right,\n } as Record<string, string | undefined>\n})\n\nconst suffixStyle = computed(() => {\n if (props.suffixFixed) {\n return {\n right: '0px',\n width: suffixFixedWidth.value,\n minWidth: suffixFixedWidth.value,\n maxWidth: suffixFixedWidth.value,\n }\n }\n\n return {\n right: '0px',\n minWidth: suffixMinWidth.value,\n maxWidth: props.suffixMaxWidth,\n }\n})\n\nconst prefixStyle = computed(() => {\n if (props.prefixFixed) {\n return {\n width: prefixFixedWidth.value,\n minWidth: prefixFixedWidth.value,\n maxWidth: prefixFixedWidth.value,\n }\n }\n\n return {\n minWidth: prefixMinWidth.value,\n maxWidth: props.prefixMaxWidth,\n }\n})\n\nconst verticalControlsStyle = computed(() => addonStyle('right', suffixLen.value))\nconst horizontalLeftControlsStyle = computed(() => addonStyle('left', prefixLen.value))\nconst horizontalRightControlsStyle = computed(() => addonStyle('right', suffixLen.value))\n\nconst shellClassName = computed(() => {\n return grNumberInputShellClass({\n disabled: props.disabled,\n state: props.invalid ? 'danger' : props.state,\n })\n})\n\nconst inputClassName = computed(() => {\n return grNumberInputInputClass({\n size: props.size,\n textAlign: props.textAlign,\n })\n})\n\nfunction isDigit(ch: string): boolean {\n return ch >= '0' && ch <= '9'\n}\n\nfunction sanitize(raw: string): string {\n const sep = props.decimalSeparator || '.'\n\n let out = ''\n let hasSepLocal = false\n\n for (let i = 0; i < raw.length; i++) {\n const ch = raw[i]\n\n // Ведущий минус: разрешаем один в начале, чтобы можно было ввести\n // отрицательное значение с клавиатуры (границы проверит `clamp` на change).\n if (ch === '-' && out.length === 0) {\n out += '-'\n continue\n }\n\n if (isDigit(ch)) {\n out += ch\n continue\n }\n\n if ((ch === sep || ch === '.' || ch === ',') && !hasSepLocal) {\n out += sep\n hasSepLocal = true\n }\n }\n\n return out\n}\n\nfunction toNumber(value: string): number | null {\n if (value.trim() === '') return null\n const sep = props.decimalSeparator || '.'\n const normalized = sep === '.' ? value : value.replace(sep, '.')\n const n = Number(normalized)\n if (!Number.isFinite(n)) return null\n return n\n}\n\nfunction clamp(v: number): number {\n if (props.min !== undefined) v = Math.max(props.min, v)\n if (props.max !== undefined) v = Math.min(props.max, v)\n return v\n}\n\nfunction normalize(v: number): number {\n if (props.precision === undefined) return v\n const p = Math.max(0, props.precision)\n return Number(v.toFixed(p))\n}\n\nfunction format(n: number): string {\n const sep = props.decimalSeparator || '.'\n const p = props.precision\n\n // Без `precision` избегаем экспоненциальной записи (`String(1e21)` → `\"1e+21\"`),\n // форматируя через `Intl` без группировки разрядов.\n let s = p === undefined\n ? n.toLocaleString('en-US', { useGrouping: false, maximumFractionDigits: 20 })\n : n.toFixed(Math.max(0, p))\n if (sep !== '.') s = s.replace('.', sep)\n return s\n}\n\nfunction onInput(e: Event): void {\n const el = e.target as HTMLInputElement\n const raw = el.value\n const next = sanitize(raw)\n\n if (raw !== next) {\n // Сохраняем позицию каретки: считаем, сколько валидных символов было ДО\n // каретки, и ставим её после стольких же в очищенной строке — иначе при\n // редактировании середины числа каретка прыгала в конец.\n const caret = el.selectionStart ?? raw.length\n const keptBefore = sanitize(raw.slice(0, caret)).length\n el.value = next\n el.setSelectionRange(keptBefore, keptBefore)\n }\n\n emit('update:modelValue', next)\n}\n\nfunction commit(el: HTMLInputElement): void {\n const num = toNumber(sanitize(el.value))\n // Пустое значение (или незавершённый ввод `-`) оставляем как есть.\n const next = num === null ? sanitize(el.value) : format(clamp(normalize(num)))\n\n if (el.value !== next) el.value = next\n if (next !== props.modelValue) emit('update:modelValue', next)\n emit('change', next)\n}\n\nfunction onChange(e: Event): void {\n // Клампинг границ (`min`/`max`) и нормализация — на `change`/`blur`, а не только\n // в кнопках: раньше ручной ввод «999» при `max=10` оставался невалидным.\n commit(e.target as HTMLInputElement)\n}\n\nfunction setValue(n: number): void {\n const nextStr = format(clamp(normalize(n)))\n emit('update:modelValue', nextStr)\n emit('change', nextStr)\n}\n\nfunction stepBy(dir: 1 | -1): void {\n if (props.disabled) return\n\n const current = toNumber(props.modelValue) ?? 0\n setValue(current + (props.step ?? 1) * dir)\n focus()\n}\n\n// Клавиатура спинбаттона (WAI-ARIA spinbutton): стрелки шагают, Home/End —\n// к границам `min`/`max` (если заданы).\nfunction onKeydown(e: KeyboardEvent): void {\n if (props.disabled) return\n\n switch (e.key) {\n case 'ArrowUp':\n e.preventDefault()\n stepBy(1)\n break\n case 'ArrowDown':\n e.preventDefault()\n stepBy(-1)\n break\n case 'Home':\n if (props.min !== undefined) {\n e.preventDefault()\n setValue(props.min)\n }\n break\n case 'End':\n if (props.max !== undefined) {\n e.preventDefault()\n setValue(props.max)\n }\n break\n }\n}\n\n// `aria-valuenow` — числовое значение для скринридеров; отсутствует, если поле пусто.\nconst ariaValueNow = computed(() => toNumber(props.modelValue) ?? undefined)\n\n// Локале-зависимое отображение: при фокусе показываем «сырое» значение для\n// редактирования, иначе — сгруппированное через `Intl.NumberFormat`.\nconst isFocused = ref(false)\n\nfunction formatGrouped(value: string): string {\n const num = toNumber(value)\n if (num === null) return value\n const sep = props.decimalSeparator || '.'\n // formatToParts позволяет оставить локале-зависимый групповой разделитель,\n // но принудительно подставить наш десятичный разделитель (`decimalSeparator`),\n // корректно работая для любой локали (в т.ч. de-DE, где группа — '.').\n return new Intl.NumberFormat(props.locale, {\n useGrouping: true,\n minimumFractionDigits: props.precision ?? 0,\n maximumFractionDigits: props.precision ?? 20,\n })\n .formatToParts(num)\n .map(part => (part.type === 'decimal' ? sep : part.value))\n .join('')\n}\n\nconst displayValue = computed(() => {\n if (!props.useGrouping || isFocused.value) return props.modelValue\n return formatGrouped(props.modelValue)\n})\n\nfunction onFocus(): void {\n isFocused.value = true\n}\n\nfunction onBlur(): void {\n isFocused.value = false\n}\n</script>\n\n<template>\n <div\n data-gr-number-input\n class=\"relative w-full overflow-hidden rounded-md border bg-[var(--bg)] transition-colors duration-150 focus-within:ring-2 focus-within:ring-[var(--ring)]\"\n :class=\"shellClassName\"\n >\n <div\n v-if=\"$slots.prefix\"\n ref=\"prefixEl\"\n data-testid=\"number-input-prefix\"\n data-gr-number-input-prefix\n class=\"absolute inset-y-0 left-0 flex items-center justify-center border-r border-[var(--brd)] px-2 text-[var(--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=\"id\"\n ref=\"inputEl\"\n v-bind=\"$attrs\"\n :name=\"name\"\n type=\"text\"\n :inputmode=\"inputmode\"\n :autocomplete=\"autocomplete\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :value=\"displayValue\"\n role=\"spinbutton\"\n :aria-valuenow=\"ariaValueNow\"\n :aria-valuemin=\"min\"\n :aria-valuemax=\"max\"\n :aria-invalid=\"invalid ? 'true' : undefined\"\n class=\"w-full bg-transparent text-[var(--fg)] placeholder:text-[var(--muted-fg)] focus:placeholder:text-transparent focus:outline-none disabled:cursor-not-allowed\"\n :class=\"inputClassName\"\n :style=\"inputStyle\"\n @input=\"onInput\"\n @change=\"onChange\"\n @keydown=\"onKeydown\"\n @focus=\"onFocus\"\n @blur=\"onBlur\"\n >\n\n <div\n v-if=\"$slots.suffix\"\n ref=\"suffixEl\"\n data-testid=\"number-input-suffix\"\n data-gr-number-input-suffix\n class=\"absolute inset-y-0 flex items-center justify-center border-l border-[var(--brd)] px-2 text-[var(--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=\"hasVerticalControls\"\n data-testid=\"number-input-controls-vertical\"\n data-gr-number-input-controls=\"vertical\"\n class=\"absolute inset-y-0 flex items-center justify-center border-l border-[var(--brd)]\"\n :style=\"verticalControlsStyle\"\n >\n <div class=\"flex flex-col justify-center gap-1\">\n <button\n type=\"button\"\n class=\"h-4 w-7 inline-flex items-center justify-center rounded text-[10px] text-[var(--muted-fg)] hover:bg-[var(--muted)] active:bg-[var(--muted)] disabled:opacity-50 disabled:cursor-not-allowed\"\n :disabled=\"disabled\"\n :aria-label=\"resolvedIncreaseLabel\"\n @mousedown.prevent\n @click=\"stepBy(1)\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path\n d=\"M7 14l5-5 5 5\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n <button\n type=\"button\"\n class=\"h-4 w-7 inline-flex items-center justify-center rounded text-[10px] text-[var(--muted-fg)] hover:bg-[var(--muted)] active:bg-[var(--muted)] disabled:opacity-50 disabled:cursor-not-allowed\"\n :disabled=\"disabled\"\n :aria-label=\"resolvedDecreaseLabel\"\n @mousedown.prevent\n @click=\"stepBy(-1)\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path\n d=\"M7 10l5 5 5-5\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n </div>\n </div>\n\n <div\n v-if=\"hasHorizontalControls\"\n data-testid=\"number-input-controls-horizontal-left\"\n data-gr-number-input-controls=\"horizontal-left\"\n class=\"absolute inset-y-0 flex items-stretch justify-center border-r border-[var(--brd)]\"\n :style=\"horizontalLeftControlsStyle\"\n >\n <button\n type=\"button\"\n class=\"h-full w-full inline-flex items-center justify-center text-[var(--muted-fg)] hover:bg-[var(--muted)] active:bg-[var(--muted)] disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)]\"\n :disabled=\"disabled\"\n :aria-label=\"resolvedDecreaseLabel\"\n @mousedown.prevent\n @click=\"stepBy(-1)\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path\n d=\"M14 7l-5 5 5 5\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n </div>\n\n <div\n v-if=\"hasHorizontalControls\"\n data-testid=\"number-input-controls-horizontal-right\"\n data-gr-number-input-controls=\"horizontal-right\"\n class=\"absolute inset-y-0 flex items-stretch justify-center border-l border-[var(--brd)]\"\n :style=\"horizontalRightControlsStyle\"\n >\n <button\n type=\"button\"\n class=\"h-full w-full inline-flex items-center justify-center text-[var(--muted-fg)] hover:bg-[var(--muted)] active:bg-[var(--muted)] disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)]\"\n :disabled=\"disabled\"\n :aria-label=\"resolvedIncreaseLabel\"\n @mousedown.prevent\n @click=\"stepBy(1)\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path\n d=\"M10 7l5 5-5 5\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n </div>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport type { InputHTMLAttributes } from 'vue'\nimport { computed, ref, useSlots } from 'vue'\n\nimport {\n grNumberInputInputClass,\n grNumberInputShellClass,\n type GrNumberInputControlsDirection,\n type GrNumberInputSize,\n type GrNumberInputState,\n type GrNumberInputTextAlign,\n} from './grNumberInputStyles'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { addLen, useAddonMeasurement } from '../../composables/internal/useAddonMeasurement'\n\ndefineOptions({\n inheritAttrs: false,\n})\n\nconst ADDON_PX_BY_SIZE: Record<GrNumberInputSize, number> = {\n xs: 32,\n sm: 36,\n md: 40,\n lg: 48,\n}\n\nconst BASE_PADDING_X_LEN_BY_SIZE: Record<GrNumberInputSize, string> = {\n xs: '10px',\n sm: '12px',\n md: '12px',\n lg: '16px',\n}\n\nfunction px(n: number): string {\n return `${n}px`\n}\n\nexport interface GrNumberInputProps {\n /** Значение (строка — контролируем форматирование внутри). */\n modelValue: string\n placeholder?: string\n autocomplete?: string\n inputmode?: InputHTMLAttributes['inputmode']\n disabled?: boolean\n /** Быстрый флаг невалидности; эквивалент `state='danger'` + `aria-invalid`. */\n invalid?: boolean\n state?: GrNumberInputState\n name?: string\n id?: string\n size?: GrNumberInputSize\n\n textAlign?: GrNumberInputTextAlign\n\n decimalSeparator?: string\n step?: number\n min?: number\n max?: number\n precision?: number\n\n /**\n * BCP-47 локаль для отображения значения (группировка разрядов и разделители\n * через `Intl.NumberFormat`). Работает вместе с `useGrouping`.\n */\n locale?: string\n /**\n * Группировать разряды при отображении (когда поле не в фокусе). При фокусе\n * показывается «сырое» значение для редактирования. По умолчанию выключено.\n */\n useGrouping?: boolean\n\n /** Показывать кнопки +/-. */\n controls?: boolean\n controlsDirection?: GrNumberInputControlsDirection\n\n prefixMinWidth?: string\n prefixMaxWidth?: string\n suffixMinWidth?: string\n suffixMaxWidth?: string\n /**\n * Фиксированная ширина у prefix/suffix: жёсткая ширина (из `*MaxWidth` →\n * `*MinWidth` → дефолт) + обрезка контента по краю (prefix — справа,\n * suffix — слева). По умолчанию аддоны растягиваются под контент.\n */\n prefixFixed?: boolean\n suffixFixed?: boolean\n\n /** i18n-friendly aria-label для кнопки \"увеличить\". */\n increaseLabel?: string\n /** i18n-friendly aria-label для кнопки \"уменьшить\". */\n decreaseLabel?: string\n}\n\nconst props = withDefaults(defineProps<GrNumberInputProps>(), {\n placeholder: undefined,\n autocomplete: undefined,\n inputmode: 'decimal',\n disabled: false,\n invalid: false,\n state: 'default',\n name: undefined,\n id: undefined,\n size: 'md',\n\n textAlign: 'left',\n\n decimalSeparator: '.',\n step: 1,\n min: undefined,\n max: undefined,\n precision: undefined,\n locale: undefined,\n useGrouping: false,\n\n controls: false,\n controlsDirection: 'vertical',\n\n prefixMinWidth: undefined,\n prefixMaxWidth: undefined,\n suffixMinWidth: undefined,\n suffixMaxWidth: undefined,\n prefixFixed: false,\n suffixFixed: false,\n\n increaseLabel: undefined,\n decreaseLabel: undefined,\n})\n\nconst emit = defineEmits<{\n (e: 'update:modelValue', value: string): void\n (e: 'change', value: string): void\n}>()\n\nconst inputEl = ref<HTMLInputElement | null>(null)\n\nfunction focus(): void {\n inputEl.value?.focus()\n}\n\ndefineExpose({\n focus,\n})\n\nconst slots = useSlots()\n\nconst { t } = useGranularityTranslations()\nconst resolvedIncreaseLabel = computed(() => props.increaseLabel ?? t('gr.numberInput.increase', 'Increase'))\nconst resolvedDecreaseLabel = computed(() => props.decreaseLabel ?? t('gr.numberInput.decrease', 'Decrease'))\n\nconst hasPrefix = computed(() => Boolean(slots.prefix))\nconst hasSuffix = computed(() => Boolean(slots.suffix))\n\nconst hasHorizontalControls = computed(() => props.controls && props.controlsDirection === 'horizontal')\nconst hasVerticalControls = computed(() => props.controls && props.controlsDirection === 'vertical')\n\nconst addonPx = computed(() => ADDON_PX_BY_SIZE[props.size])\nconst addonLen = computed(() => px(addonPx.value))\nconst basePaddingXLen = computed(() => BASE_PADDING_X_LEN_BY_SIZE[props.size])\n\nconst defaultAddonMinWidth = computed(() => addonLen.value)\nconst prefixMinWidth = computed(() => props.prefixMinWidth ?? defaultAddonMinWidth.value)\nconst suffixMinWidth = computed(() => props.suffixMinWidth ?? defaultAddonMinWidth.value)\n\n// Жёсткая ширина для fixed-режима: max → min → дефолт.\nconst prefixFixedWidth = computed(() => props.prefixMaxWidth ?? props.prefixMinWidth ?? defaultAddonMinWidth.value)\nconst suffixFixedWidth = computed(() => props.suffixMaxWidth ?? props.suffixMinWidth ?? defaultAddonMinWidth.value)\n\nconst { prefixEl, suffixEl, measuredPrefixWidth, measuredSuffixWidth } = useAddonMeasurement(hasPrefix, hasSuffix)\n\nconst prefixLen = computed(() => {\n if (!hasPrefix.value) return '0px'\n return props.prefixFixed ? prefixFixedWidth.value : measuredPrefixWidth.value ?? prefixMinWidth.value\n})\nconst suffixLen = computed(() => {\n if (!hasSuffix.value) return '0px'\n return props.suffixFixed ? suffixFixedWidth.value : measuredSuffixWidth.value ?? suffixMinWidth.value\n})\n\nfunction addonStyle(side: 'left' | 'right', offset: string): Record<string, string> {\n return {\n width: addonLen.value,\n [side]: offset,\n }\n}\n\nconst inputStyle = computed(() => {\n const leftControls = hasHorizontalControls.value ? addonLen.value : '0px'\n const rightControlsCount = (hasHorizontalControls.value ? 1 : 0) + (hasVerticalControls.value ? 1 : 0)\n const rightControls = rightControlsCount > 0 ? px(addonPx.value * rightControlsCount) : '0px'\n\n const leftReserved = addLen(prefixLen.value, leftControls)\n const rightReserved = addLen(suffixLen.value, rightControls)\n\n const left = hasPrefix.value || hasHorizontalControls.value ? addLen(leftReserved, basePaddingXLen.value) : undefined\n const right = hasSuffix.value || rightControlsCount > 0 ? addLen(rightReserved, basePaddingXLen.value) : undefined\n\n return {\n paddingLeft: left,\n paddingRight: right,\n } as Record<string, string | undefined>\n})\n\nconst suffixStyle = computed(() => {\n if (props.suffixFixed) {\n return {\n right: '0px',\n width: suffixFixedWidth.value,\n minWidth: suffixFixedWidth.value,\n maxWidth: suffixFixedWidth.value,\n }\n }\n\n return {\n right: '0px',\n minWidth: suffixMinWidth.value,\n maxWidth: props.suffixMaxWidth,\n }\n})\n\nconst prefixStyle = computed(() => {\n if (props.prefixFixed) {\n return {\n width: prefixFixedWidth.value,\n minWidth: prefixFixedWidth.value,\n maxWidth: prefixFixedWidth.value,\n }\n }\n\n return {\n minWidth: prefixMinWidth.value,\n maxWidth: props.prefixMaxWidth,\n }\n})\n\nconst verticalControlsStyle = computed(() => addonStyle('right', suffixLen.value))\nconst horizontalLeftControlsStyle = computed(() => addonStyle('left', prefixLen.value))\nconst horizontalRightControlsStyle = computed(() => addonStyle('right', suffixLen.value))\n\nconst shellClassName = computed(() => {\n return grNumberInputShellClass({\n disabled: props.disabled,\n state: props.invalid ? 'danger' : props.state,\n })\n})\n\nconst inputClassName = computed(() => {\n return grNumberInputInputClass({\n size: props.size,\n textAlign: props.textAlign,\n })\n})\n\nfunction isDigit(ch: string): boolean {\n return ch >= '0' && ch <= '9'\n}\n\nfunction sanitize(raw: string): string {\n const sep = props.decimalSeparator || '.'\n\n let out = ''\n let hasSepLocal = false\n\n for (let i = 0; i < raw.length; i++) {\n const ch = raw[i]\n\n // Ведущий минус: разрешаем один в начале, чтобы можно было ввести\n // отрицательное значение с клавиатуры (границы проверит `clamp` на change).\n if (ch === '-' && out.length === 0) {\n out += '-'\n continue\n }\n\n if (isDigit(ch)) {\n out += ch\n continue\n }\n\n if ((ch === sep || ch === '.' || ch === ',') && !hasSepLocal) {\n out += sep\n hasSepLocal = true\n }\n }\n\n return out\n}\n\nfunction toNumber(value: string): number | null {\n if (value.trim() === '') return null\n const sep = props.decimalSeparator || '.'\n const normalized = sep === '.' ? value : value.replace(sep, '.')\n const n = Number(normalized)\n if (!Number.isFinite(n)) return null\n return n\n}\n\nfunction clamp(v: number): number {\n if (props.min !== undefined) v = Math.max(props.min, v)\n if (props.max !== undefined) v = Math.min(props.max, v)\n return v\n}\n\nfunction normalize(v: number): number {\n if (props.precision === undefined) return v\n const p = Math.max(0, props.precision)\n return Number(v.toFixed(p))\n}\n\nfunction format(n: number): string {\n const sep = props.decimalSeparator || '.'\n const p = props.precision\n\n // Без `precision` избегаем экспоненциальной записи (`String(1e21)` → `\"1e+21\"`),\n // форматируя через `Intl` без группировки разрядов.\n let s = p === undefined\n ? n.toLocaleString('en-US', { useGrouping: false, maximumFractionDigits: 20 })\n : n.toFixed(Math.max(0, p))\n if (sep !== '.') s = s.replace('.', sep)\n return s\n}\n\nfunction onInput(e: Event): void {\n const el = e.target as HTMLInputElement\n const raw = el.value\n const next = sanitize(raw)\n\n if (raw !== next) {\n // Сохраняем позицию каретки: считаем, сколько валидных символов было ДО\n // каретки, и ставим её после стольких же в очищенной строке — иначе при\n // редактировании середины числа каретка прыгала в конец.\n const caret = el.selectionStart ?? raw.length\n const keptBefore = sanitize(raw.slice(0, caret)).length\n el.value = next\n el.setSelectionRange(keptBefore, keptBefore)\n }\n\n emit('update:modelValue', next)\n}\n\nfunction commit(el: HTMLInputElement): void {\n const num = toNumber(sanitize(el.value))\n // Пустое значение (или незавершённый ввод `-`) оставляем как есть.\n const next = num === null ? sanitize(el.value) : format(clamp(normalize(num)))\n\n if (el.value !== next) el.value = next\n if (next !== props.modelValue) emit('update:modelValue', next)\n emit('change', next)\n}\n\nfunction onChange(e: Event): void {\n // Клампинг границ (`min`/`max`) и нормализация — на `change`/`blur`, а не только\n // в кнопках: раньше ручной ввод «999» при `max=10` оставался невалидным.\n commit(e.target as HTMLInputElement)\n}\n\nfunction setValue(n: number): void {\n const nextStr = format(clamp(normalize(n)))\n emit('update:modelValue', nextStr)\n emit('change', nextStr)\n}\n\nfunction stepBy(dir: 1 | -1): void {\n if (props.disabled) return\n\n const current = toNumber(props.modelValue) ?? 0\n setValue(current + (props.step ?? 1) * dir)\n focus()\n}\n\n// Клавиатура спинбаттона (WAI-ARIA spinbutton): стрелки шагают, Home/End —\n// к границам `min`/`max` (если заданы).\nfunction onKeydown(e: KeyboardEvent): void {\n if (props.disabled) return\n\n switch (e.key) {\n case 'ArrowUp':\n e.preventDefault()\n stepBy(1)\n break\n case 'ArrowDown':\n e.preventDefault()\n stepBy(-1)\n break\n case 'Home':\n if (props.min !== undefined) {\n e.preventDefault()\n setValue(props.min)\n }\n break\n case 'End':\n if (props.max !== undefined) {\n e.preventDefault()\n setValue(props.max)\n }\n break\n }\n}\n\n// `aria-valuenow` — числовое значение для скринридеров; отсутствует, если поле пусто.\nconst ariaValueNow = computed(() => toNumber(props.modelValue) ?? undefined)\n\n// Локале-зависимое отображение: при фокусе показываем «сырое» значение для\n// редактирования, иначе — сгруппированное через `Intl.NumberFormat`.\nconst isFocused = ref(false)\n\nfunction formatGrouped(value: string): string {\n const num = toNumber(value)\n if (num === null) return value\n const sep = props.decimalSeparator || '.'\n // formatToParts позволяет оставить локале-зависимый групповой разделитель,\n // но принудительно подставить наш десятичный разделитель (`decimalSeparator`),\n // корректно работая для любой локали (в т.ч. de-DE, где группа — '.').\n return new Intl.NumberFormat(props.locale, {\n useGrouping: true,\n minimumFractionDigits: props.precision ?? 0,\n maximumFractionDigits: props.precision ?? 20,\n })\n .formatToParts(num)\n .map(part => (part.type === 'decimal' ? sep : part.value))\n .join('')\n}\n\nconst displayValue = computed(() => {\n if (!props.useGrouping || isFocused.value) return props.modelValue\n return formatGrouped(props.modelValue)\n})\n\nfunction onFocus(): void {\n isFocused.value = true\n}\n\nfunction onBlur(): void {\n isFocused.value = false\n}\n</script>\n\n<template>\n <div\n data-gr-number-input\n class=\"relative w-full overflow-hidden rounded-md border bg-[var(--bg)] transition-colors duration-150 focus-within:ring-2 focus-within:ring-[var(--ring)]\"\n :class=\"shellClassName\"\n >\n <div\n v-if=\"$slots.prefix\"\n ref=\"prefixEl\"\n data-testid=\"number-input-prefix\"\n data-gr-number-input-prefix\n class=\"absolute inset-y-0 left-0 flex items-center justify-center border-r border-[var(--brd)] px-2 text-[var(--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=\"id\"\n ref=\"inputEl\"\n v-bind=\"$attrs\"\n :name=\"name\"\n type=\"text\"\n :inputmode=\"inputmode\"\n :autocomplete=\"autocomplete\"\n :placeholder=\"placeholder\"\n :disabled=\"disabled\"\n :value=\"displayValue\"\n role=\"spinbutton\"\n :aria-valuenow=\"ariaValueNow\"\n :aria-valuemin=\"min\"\n :aria-valuemax=\"max\"\n :aria-invalid=\"invalid ? 'true' : undefined\"\n class=\"w-full bg-transparent text-[var(--fg)] placeholder:text-[var(--muted-fg)] focus:placeholder:text-transparent focus:outline-none disabled:cursor-not-allowed\"\n :class=\"inputClassName\"\n :style=\"inputStyle\"\n @input=\"onInput\"\n @change=\"onChange\"\n @keydown=\"onKeydown\"\n @focus=\"onFocus\"\n @blur=\"onBlur\"\n >\n\n <div\n v-if=\"$slots.suffix\"\n ref=\"suffixEl\"\n data-testid=\"number-input-suffix\"\n data-gr-number-input-suffix\n class=\"absolute inset-y-0 flex items-center justify-center border-l border-[var(--brd)] px-2 text-[var(--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=\"hasVerticalControls\"\n data-testid=\"number-input-controls-vertical\"\n data-gr-number-input-controls=\"vertical\"\n class=\"absolute inset-y-0 flex items-center justify-center border-l border-[var(--brd)]\"\n :style=\"verticalControlsStyle\"\n >\n <div class=\"flex flex-col justify-center gap-1\">\n <button\n type=\"button\"\n class=\"h-4 w-7 inline-flex items-center justify-center rounded text-[10px] text-[var(--muted-fg)] hover:bg-[var(--muted)] active:bg-[var(--muted)] disabled:opacity-50 disabled:cursor-not-allowed\"\n :disabled=\"disabled\"\n :aria-label=\"resolvedIncreaseLabel\"\n @mousedown.prevent\n @click=\"stepBy(1)\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path\n d=\"M7 14l5-5 5 5\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n <button\n type=\"button\"\n class=\"h-4 w-7 inline-flex items-center justify-center rounded text-[10px] text-[var(--muted-fg)] hover:bg-[var(--muted)] active:bg-[var(--muted)] disabled:opacity-50 disabled:cursor-not-allowed\"\n :disabled=\"disabled\"\n :aria-label=\"resolvedDecreaseLabel\"\n @mousedown.prevent\n @click=\"stepBy(-1)\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path\n d=\"M7 10l5 5 5-5\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n </div>\n </div>\n\n <div\n v-if=\"hasHorizontalControls\"\n data-testid=\"number-input-controls-horizontal-left\"\n data-gr-number-input-controls=\"horizontal-left\"\n class=\"absolute inset-y-0 flex items-stretch justify-center border-r border-[var(--brd)]\"\n :style=\"horizontalLeftControlsStyle\"\n >\n <button\n type=\"button\"\n class=\"h-full w-full inline-flex items-center justify-center text-[var(--muted-fg)] hover:bg-[var(--muted)] active:bg-[var(--muted)] disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)]\"\n :disabled=\"disabled\"\n :aria-label=\"resolvedDecreaseLabel\"\n @mousedown.prevent\n @click=\"stepBy(-1)\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path\n d=\"M14 7l-5 5 5 5\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n </div>\n\n <div\n v-if=\"hasHorizontalControls\"\n data-testid=\"number-input-controls-horizontal-right\"\n data-gr-number-input-controls=\"horizontal-right\"\n class=\"absolute inset-y-0 flex items-stretch justify-center border-l border-[var(--brd)]\"\n :style=\"horizontalRightControlsStyle\"\n >\n <button\n type=\"button\"\n class=\"h-full w-full inline-flex items-center justify-center text-[var(--muted-fg)] hover:bg-[var(--muted)] active:bg-[var(--muted)] disabled:opacity-50 disabled:cursor-not-allowed focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)]\"\n :disabled=\"disabled\"\n :aria-label=\"resolvedIncreaseLabel\"\n @mousedown.prevent\n @click=\"stepBy(1)\"\n >\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http://www.w3.org/2000/svg\" aria-hidden=\"true\">\n <path\n d=\"M10 7l5 5-5 5\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </button>\n </div>\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { n as grNumberInputSafelist, t as grNumberInputConfig } from "../../chunks/config-C7NpDrfO.js";
|
|
2
|
-
import { t as GrNumberInput_default } from "./chunks/GrNumberInput-
|
|
2
|
+
import { t as GrNumberInput_default } from "./chunks/GrNumberInput-DxCEvL7V.js";
|
|
3
3
|
export { GrNumberInput_default as GrNumberInput, GrNumberInput_default as default, grNumberInputConfig, grNumberInputSafelist };
|
package/dist/components/GrPagination/chunks/{GrPagination-Bu9wv6it.js → GrPagination-mlyfLjQY.js}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as useGranularityTranslations } from "../../../chunks/granularityI18n-DoV7Ca2m.js";
|
|
2
2
|
import { t as GrButton_default } from "../../GrButton/chunks/GrButton-BlffdeJr.js";
|
|
3
|
-
import { t as GrSelect_default } from "../../GrSelect/chunks/GrSelect-
|
|
3
|
+
import { t as GrSelect_default } from "../../GrSelect/chunks/GrSelect-BVCmitqa.js";
|
|
4
4
|
import { Fragment, computed, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, normalizeClass, openBlock, renderList, toDisplayString, unref, watch, withCtx } from "vue";
|
|
5
5
|
//#region src/components/GrPagination/GrPagination.vue?vue&type=script&setup=true&lang.ts
|
|
6
6
|
var _hoisted_1 = ["aria-label"];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GrPagination-Bu9wv6it.js","names":[],"sources":["../../../../src/components/GrPagination/GrPagination.vue","../../../../src/components/GrPagination/GrPagination.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, watch } from 'vue'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrSelect from '../GrSelect/GrSelect.vue'\n\n/**\n * Пропы пагинации.\n *\n * `total` интерпретируется как общее количество элементов; число страниц\n * вычисляется из `total` и `pageSize`. Номера усекаются многоточием\n * (алгоритм boundary/sibling, как у MUI): всегда видны первая/последняя страница\n * и `siblingCount` соседей вокруг текущей.\n */\nexport interface GrPaginationProps {\n page: number\n pageSize: number\n total: number\n pageSizes?: number[]\n /** Сколько соседних страниц показывать вокруг текущей. По умолчанию `1`. */\n siblingCount?: number\n /** Сколько крайних страниц всегда показывать с каждого края. По умолчанию `1`. */\n boundaryCount?: number\n}\n\nconst { t } = useGranularityTranslations()\n\nconst props = withDefaults(defineProps<GrPaginationProps>(), {\n pageSizes: () => [10, 20, 50],\n siblingCount: 1,\n boundaryCount: 1,\n})\n\nconst emit = defineEmits<{\n (e: 'update:page', value: number): void\n (e: 'update:pageSize', value: number): void\n}>()\n\nconst pageCount = computed(() => Math.max(1, Math.ceil(props.total / props.pageSize)))\n\n// Контролируемый компонент: если `total`/`pageSize` уменьшились и текущая `page`\n// вышла за диапазон — просим родителя подтянуть её к последней странице.\nwatch(pageCount, (count) => {\n if (props.page > count)\n emit('update:page', count)\n})\n\nconst pageSizeModel = computed({\n get: () => String(props.pageSize),\n set: (value: string) => emit('update:pageSize', Number(value)),\n})\n\nconst pageSizeOptions = computed(() =>\n props.pageSizes.map(pageSize => ({\n value: String(pageSize),\n label: String(pageSize),\n })),\n)\n\ntype PaginationItem = number | 'ellipsis-start' | 'ellipsis-end'\n\nfunction range(start: number, end: number): number[] {\n const list: number[] = []\n for (let page = start; page <= end; page += 1)\n list.push(page)\n return list\n}\n\n// Алгоритм usePagination как в MUI: крайние `boundaryCount` страниц + `siblingCount`\n// соседей вокруг текущей, разрывы схлопываются в многоточие. Одиночный «пропущенный»\n// номер показываем как номер, а не как «…» (визуально ровнее).\nconst items = computed<PaginationItem[]>(() => {\n const count = pageCount.value\n const current = props.page\n const boundaryCount = Math.max(0, props.boundaryCount)\n const siblingCount = Math.max(0, props.siblingCount)\n\n const startPages = range(1, Math.min(boundaryCount, count))\n const endPages = range(Math.max(count - boundaryCount + 1, boundaryCount + 1), count)\n\n const siblingsStart = Math.max(\n Math.min(current - siblingCount, count - boundaryCount - siblingCount * 2 - 1),\n boundaryCount + 2,\n )\n const siblingsEnd = Math.min(\n Math.max(current + siblingCount, boundaryCount + siblingCount * 2 + 2),\n endPages.length > 0 ? endPages[0] - 2 : count - 1,\n )\n\n return [\n ...startPages,\n ...(siblingsStart > boundaryCount + 2\n ? ['ellipsis-start' as const]\n : boundaryCount + 1 < count - boundaryCount\n ? [boundaryCount + 1]\n : []),\n ...range(siblingsStart, siblingsEnd),\n ...(siblingsEnd < count - boundaryCount - 1\n ? ['ellipsis-end' as const]\n : count - boundaryCount > boundaryCount\n ? [count - boundaryCount]\n : []),\n ...endPages,\n ]\n})\n\nfunction goTo(page: number): void {\n emit('update:page', page)\n}\n\nfunction prev(): void {\n goTo(Math.max(1, props.page - 1))\n}\n\nfunction next(): void {\n goTo(Math.min(pageCount.value, props.page + 1))\n}\n\nfunction first(): void {\n goTo(1)\n}\n\nfunction last(): void {\n goTo(pageCount.value)\n}\n</script>\n\n<template>\n <div\n class=\"flex flex-wrap items-center justify-end gap-3\"\n data-gr-pagination\n role=\"navigation\"\n :aria-label=\"t('gr.pagination.label', 'Pagination')\"\n >\n <div class=\"min-w-[100px]\">\n <GrSelect\n v-model=\"pageSizeModel\"\n :options=\"pageSizeOptions\"\n :aria-label=\"t('gr.pagination.pageSize', 'Page size')\"\n />\n </div>\n\n <GrButton variant=\"ghost\" size=\"sm\" :disabled=\"page <= 1\" :aria-label=\"t('gr.pagination.first', 'First page')\" data-gr-pagination-first @click=\"first\">\n «\n </GrButton>\n\n <GrButton variant=\"ghost\" size=\"sm\" :disabled=\"page <= 1\" data-gr-pagination-prev @click=\"prev\">\n {{ t('gr.pagination.prev', 'Prev') }}\n </GrButton>\n\n <div class=\"flex items-center gap-1\">\n <template v-for=\"(item, index) in items\" :key=\"index\">\n <span\n v-if=\"item === 'ellipsis-start' || item === 'ellipsis-end'\"\n data-gr-pagination-ellipsis\n aria-hidden=\"true\"\n class=\"h-8 min-w-8 px-1 grid place-items-center text-sm text-[var(--muted-fg)]\"\n >…</span>\n <button\n v-else\n type=\"button\"\n data-gr-pagination-page\n :aria-current=\"item === page ? 'page' : undefined\"\n :aria-label=\"t('gr.pagination.page', 'Page {n}', { n: item })\"\n class=\"h-8 min-w-8 px-2 rounded-md text-sm font-600 transition-colors duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)]\"\n :class=\"item === page ? 'bg-[var(--primary)] text-[var(--primary-fg)]' : 'text-[var(--muted-fg)] hover:bg-[var(--muted)] hover:text-[var(--fg)]'\"\n @click=\"goTo(item)\"\n >\n {{ item }}\n </button>\n </template>\n </div>\n\n <GrButton variant=\"ghost\" size=\"sm\" :disabled=\"page >= pageCount\" data-gr-pagination-next @click=\"next\">\n {{ t('gr.pagination.next', 'Next') }}\n </GrButton>\n\n <GrButton variant=\"ghost\" size=\"sm\" :disabled=\"page >= pageCount\" :aria-label=\"t('gr.pagination.last', 'Last page')\" data-gr-pagination-last @click=\"last\">\n »\n </GrButton>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, watch } from 'vue'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrSelect from '../GrSelect/GrSelect.vue'\n\n/**\n * Пропы пагинации.\n *\n * `total` интерпретируется как общее количество элементов; число страниц\n * вычисляется из `total` и `pageSize`. Номера усекаются многоточием\n * (алгоритм boundary/sibling, как у MUI): всегда видны первая/последняя страница\n * и `siblingCount` соседей вокруг текущей.\n */\nexport interface GrPaginationProps {\n page: number\n pageSize: number\n total: number\n pageSizes?: number[]\n /** Сколько соседних страниц показывать вокруг текущей. По умолчанию `1`. */\n siblingCount?: number\n /** Сколько крайних страниц всегда показывать с каждого края. По умолчанию `1`. */\n boundaryCount?: number\n}\n\nconst { t } = useGranularityTranslations()\n\nconst props = withDefaults(defineProps<GrPaginationProps>(), {\n pageSizes: () => [10, 20, 50],\n siblingCount: 1,\n boundaryCount: 1,\n})\n\nconst emit = defineEmits<{\n (e: 'update:page', value: number): void\n (e: 'update:pageSize', value: number): void\n}>()\n\nconst pageCount = computed(() => Math.max(1, Math.ceil(props.total / props.pageSize)))\n\n// Контролируемый компонент: если `total`/`pageSize` уменьшились и текущая `page`\n// вышла за диапазон — просим родителя подтянуть её к последней странице.\nwatch(pageCount, (count) => {\n if (props.page > count)\n emit('update:page', count)\n})\n\nconst pageSizeModel = computed({\n get: () => String(props.pageSize),\n set: (value: string) => emit('update:pageSize', Number(value)),\n})\n\nconst pageSizeOptions = computed(() =>\n props.pageSizes.map(pageSize => ({\n value: String(pageSize),\n label: String(pageSize),\n })),\n)\n\ntype PaginationItem = number | 'ellipsis-start' | 'ellipsis-end'\n\nfunction range(start: number, end: number): number[] {\n const list: number[] = []\n for (let page = start; page <= end; page += 1)\n list.push(page)\n return list\n}\n\n// Алгоритм usePagination как в MUI: крайние `boundaryCount` страниц + `siblingCount`\n// соседей вокруг текущей, разрывы схлопываются в многоточие. Одиночный «пропущенный»\n// номер показываем как номер, а не как «…» (визуально ровнее).\nconst items = computed<PaginationItem[]>(() => {\n const count = pageCount.value\n const current = props.page\n const boundaryCount = Math.max(0, props.boundaryCount)\n const siblingCount = Math.max(0, props.siblingCount)\n\n const startPages = range(1, Math.min(boundaryCount, count))\n const endPages = range(Math.max(count - boundaryCount + 1, boundaryCount + 1), count)\n\n const siblingsStart = Math.max(\n Math.min(current - siblingCount, count - boundaryCount - siblingCount * 2 - 1),\n boundaryCount + 2,\n )\n const siblingsEnd = Math.min(\n Math.max(current + siblingCount, boundaryCount + siblingCount * 2 + 2),\n endPages.length > 0 ? endPages[0] - 2 : count - 1,\n )\n\n return [\n ...startPages,\n ...(siblingsStart > boundaryCount + 2\n ? ['ellipsis-start' as const]\n : boundaryCount + 1 < count - boundaryCount\n ? [boundaryCount + 1]\n : []),\n ...range(siblingsStart, siblingsEnd),\n ...(siblingsEnd < count - boundaryCount - 1\n ? ['ellipsis-end' as const]\n : count - boundaryCount > boundaryCount\n ? [count - boundaryCount]\n : []),\n ...endPages,\n ]\n})\n\nfunction goTo(page: number): void {\n emit('update:page', page)\n}\n\nfunction prev(): void {\n goTo(Math.max(1, props.page - 1))\n}\n\nfunction next(): void {\n goTo(Math.min(pageCount.value, props.page + 1))\n}\n\nfunction first(): void {\n goTo(1)\n}\n\nfunction last(): void {\n goTo(pageCount.value)\n}\n</script>\n\n<template>\n <div\n class=\"flex flex-wrap items-center justify-end gap-3\"\n data-gr-pagination\n role=\"navigation\"\n :aria-label=\"t('gr.pagination.label', 'Pagination')\"\n >\n <div class=\"min-w-[100px]\">\n <GrSelect\n v-model=\"pageSizeModel\"\n :options=\"pageSizeOptions\"\n :aria-label=\"t('gr.pagination.pageSize', 'Page size')\"\n />\n </div>\n\n <GrButton variant=\"ghost\" size=\"sm\" :disabled=\"page <= 1\" :aria-label=\"t('gr.pagination.first', 'First page')\" data-gr-pagination-first @click=\"first\">\n «\n </GrButton>\n\n <GrButton variant=\"ghost\" size=\"sm\" :disabled=\"page <= 1\" data-gr-pagination-prev @click=\"prev\">\n {{ t('gr.pagination.prev', 'Prev') }}\n </GrButton>\n\n <div class=\"flex items-center gap-1\">\n <template v-for=\"(item, index) in items\" :key=\"index\">\n <span\n v-if=\"item === 'ellipsis-start' || item === 'ellipsis-end'\"\n data-gr-pagination-ellipsis\n aria-hidden=\"true\"\n class=\"h-8 min-w-8 px-1 grid place-items-center text-sm text-[var(--muted-fg)]\"\n >…</span>\n <button\n v-else\n type=\"button\"\n data-gr-pagination-page\n :aria-current=\"item === page ? 'page' : undefined\"\n :aria-label=\"t('gr.pagination.page', 'Page {n}', { n: item })\"\n class=\"h-8 min-w-8 px-2 rounded-md text-sm font-600 transition-colors duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)]\"\n :class=\"item === page ? 'bg-[var(--primary)] text-[var(--primary-fg)]' : 'text-[var(--muted-fg)] hover:bg-[var(--muted)] hover:text-[var(--fg)]'\"\n @click=\"goTo(item)\"\n >\n {{ item }}\n </button>\n </template>\n </div>\n\n <GrButton variant=\"ghost\" size=\"sm\" :disabled=\"page >= pageCount\" data-gr-pagination-next @click=\"next\">\n {{ t('gr.pagination.next', 'Next') }}\n </GrButton>\n\n <GrButton variant=\"ghost\" size=\"sm\" :disabled=\"page >= pageCount\" :aria-label=\"t('gr.pagination.last', 'Last page')\" data-gr-pagination-last @click=\"last\">\n »\n </GrButton>\n </div>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GrPagination-mlyfLjQY.js","names":[],"sources":["../../../../src/components/GrPagination/GrPagination.vue","../../../../src/components/GrPagination/GrPagination.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, watch } from 'vue'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrSelect from '../GrSelect/GrSelect.vue'\n\n/**\n * Пропы пагинации.\n *\n * `total` интерпретируется как общее количество элементов; число страниц\n * вычисляется из `total` и `pageSize`. Номера усекаются многоточием\n * (алгоритм boundary/sibling, как у MUI): всегда видны первая/последняя страница\n * и `siblingCount` соседей вокруг текущей.\n */\nexport interface GrPaginationProps {\n page: number\n pageSize: number\n total: number\n pageSizes?: number[]\n /** Сколько соседних страниц показывать вокруг текущей. По умолчанию `1`. */\n siblingCount?: number\n /** Сколько крайних страниц всегда показывать с каждого края. По умолчанию `1`. */\n boundaryCount?: number\n}\n\nconst { t } = useGranularityTranslations()\n\nconst props = withDefaults(defineProps<GrPaginationProps>(), {\n pageSizes: () => [10, 20, 50],\n siblingCount: 1,\n boundaryCount: 1,\n})\n\nconst emit = defineEmits<{\n (e: 'update:page', value: number): void\n (e: 'update:pageSize', value: number): void\n}>()\n\nconst pageCount = computed(() => Math.max(1, Math.ceil(props.total / props.pageSize)))\n\n// Контролируемый компонент: если `total`/`pageSize` уменьшились и текущая `page`\n// вышла за диапазон — просим родителя подтянуть её к последней странице.\nwatch(pageCount, (count) => {\n if (props.page > count)\n emit('update:page', count)\n})\n\nconst pageSizeModel = computed({\n get: () => String(props.pageSize),\n set: (value: string) => emit('update:pageSize', Number(value)),\n})\n\nconst pageSizeOptions = computed(() =>\n props.pageSizes.map(pageSize => ({\n value: String(pageSize),\n label: String(pageSize),\n })),\n)\n\ntype PaginationItem = number | 'ellipsis-start' | 'ellipsis-end'\n\nfunction range(start: number, end: number): number[] {\n const list: number[] = []\n for (let page = start; page <= end; page += 1)\n list.push(page)\n return list\n}\n\n// Алгоритм usePagination как в MUI: крайние `boundaryCount` страниц + `siblingCount`\n// соседей вокруг текущей, разрывы схлопываются в многоточие. Одиночный «пропущенный»\n// номер показываем как номер, а не как «…» (визуально ровнее).\nconst items = computed<PaginationItem[]>(() => {\n const count = pageCount.value\n const current = props.page\n const boundaryCount = Math.max(0, props.boundaryCount)\n const siblingCount = Math.max(0, props.siblingCount)\n\n const startPages = range(1, Math.min(boundaryCount, count))\n const endPages = range(Math.max(count - boundaryCount + 1, boundaryCount + 1), count)\n\n const siblingsStart = Math.max(\n Math.min(current - siblingCount, count - boundaryCount - siblingCount * 2 - 1),\n boundaryCount + 2,\n )\n const siblingsEnd = Math.min(\n Math.max(current + siblingCount, boundaryCount + siblingCount * 2 + 2),\n endPages.length > 0 ? endPages[0] - 2 : count - 1,\n )\n\n return [\n ...startPages,\n ...(siblingsStart > boundaryCount + 2\n ? ['ellipsis-start' as const]\n : boundaryCount + 1 < count - boundaryCount\n ? [boundaryCount + 1]\n : []),\n ...range(siblingsStart, siblingsEnd),\n ...(siblingsEnd < count - boundaryCount - 1\n ? ['ellipsis-end' as const]\n : count - boundaryCount > boundaryCount\n ? [count - boundaryCount]\n : []),\n ...endPages,\n ]\n})\n\nfunction goTo(page: number): void {\n emit('update:page', page)\n}\n\nfunction prev(): void {\n goTo(Math.max(1, props.page - 1))\n}\n\nfunction next(): void {\n goTo(Math.min(pageCount.value, props.page + 1))\n}\n\nfunction first(): void {\n goTo(1)\n}\n\nfunction last(): void {\n goTo(pageCount.value)\n}\n</script>\n\n<template>\n <div\n class=\"flex flex-wrap items-center justify-end gap-3\"\n data-gr-pagination\n role=\"navigation\"\n :aria-label=\"t('gr.pagination.label', 'Pagination')\"\n >\n <div class=\"min-w-[100px]\">\n <GrSelect\n v-model=\"pageSizeModel\"\n :options=\"pageSizeOptions\"\n :aria-label=\"t('gr.pagination.pageSize', 'Page size')\"\n />\n </div>\n\n <GrButton variant=\"ghost\" size=\"sm\" :disabled=\"page <= 1\" :aria-label=\"t('gr.pagination.first', 'First page')\" data-gr-pagination-first @click=\"first\">\n «\n </GrButton>\n\n <GrButton variant=\"ghost\" size=\"sm\" :disabled=\"page <= 1\" data-gr-pagination-prev @click=\"prev\">\n {{ t('gr.pagination.prev', 'Prev') }}\n </GrButton>\n\n <div class=\"flex items-center gap-1\">\n <template v-for=\"(item, index) in items\" :key=\"index\">\n <span\n v-if=\"item === 'ellipsis-start' || item === 'ellipsis-end'\"\n data-gr-pagination-ellipsis\n aria-hidden=\"true\"\n class=\"h-8 min-w-8 px-1 grid place-items-center text-sm text-[var(--muted-fg)]\"\n >…</span>\n <button\n v-else\n type=\"button\"\n data-gr-pagination-page\n :aria-current=\"item === page ? 'page' : undefined\"\n :aria-label=\"t('gr.pagination.page', 'Page {n}', { n: item })\"\n class=\"h-8 min-w-8 px-2 rounded-md text-sm font-600 transition-colors duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)]\"\n :class=\"item === page ? 'bg-[var(--primary)] text-[var(--primary-fg)]' : 'text-[var(--muted-fg)] hover:bg-[var(--muted)] hover:text-[var(--fg)]'\"\n @click=\"goTo(item)\"\n >\n {{ item }}\n </button>\n </template>\n </div>\n\n <GrButton variant=\"ghost\" size=\"sm\" :disabled=\"page >= pageCount\" data-gr-pagination-next @click=\"next\">\n {{ t('gr.pagination.next', 'Next') }}\n </GrButton>\n\n <GrButton variant=\"ghost\" size=\"sm\" :disabled=\"page >= pageCount\" :aria-label=\"t('gr.pagination.last', 'Last page')\" data-gr-pagination-last @click=\"last\">\n »\n </GrButton>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, watch } from 'vue'\n\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport GrButton from '../GrButton/GrButton.vue'\nimport GrSelect from '../GrSelect/GrSelect.vue'\n\n/**\n * Пропы пагинации.\n *\n * `total` интерпретируется как общее количество элементов; число страниц\n * вычисляется из `total` и `pageSize`. Номера усекаются многоточием\n * (алгоритм boundary/sibling, как у MUI): всегда видны первая/последняя страница\n * и `siblingCount` соседей вокруг текущей.\n */\nexport interface GrPaginationProps {\n page: number\n pageSize: number\n total: number\n pageSizes?: number[]\n /** Сколько соседних страниц показывать вокруг текущей. По умолчанию `1`. */\n siblingCount?: number\n /** Сколько крайних страниц всегда показывать с каждого края. По умолчанию `1`. */\n boundaryCount?: number\n}\n\nconst { t } = useGranularityTranslations()\n\nconst props = withDefaults(defineProps<GrPaginationProps>(), {\n pageSizes: () => [10, 20, 50],\n siblingCount: 1,\n boundaryCount: 1,\n})\n\nconst emit = defineEmits<{\n (e: 'update:page', value: number): void\n (e: 'update:pageSize', value: number): void\n}>()\n\nconst pageCount = computed(() => Math.max(1, Math.ceil(props.total / props.pageSize)))\n\n// Контролируемый компонент: если `total`/`pageSize` уменьшились и текущая `page`\n// вышла за диапазон — просим родителя подтянуть её к последней странице.\nwatch(pageCount, (count) => {\n if (props.page > count)\n emit('update:page', count)\n})\n\nconst pageSizeModel = computed({\n get: () => String(props.pageSize),\n set: (value: string) => emit('update:pageSize', Number(value)),\n})\n\nconst pageSizeOptions = computed(() =>\n props.pageSizes.map(pageSize => ({\n value: String(pageSize),\n label: String(pageSize),\n })),\n)\n\ntype PaginationItem = number | 'ellipsis-start' | 'ellipsis-end'\n\nfunction range(start: number, end: number): number[] {\n const list: number[] = []\n for (let page = start; page <= end; page += 1)\n list.push(page)\n return list\n}\n\n// Алгоритм usePagination как в MUI: крайние `boundaryCount` страниц + `siblingCount`\n// соседей вокруг текущей, разрывы схлопываются в многоточие. Одиночный «пропущенный»\n// номер показываем как номер, а не как «…» (визуально ровнее).\nconst items = computed<PaginationItem[]>(() => {\n const count = pageCount.value\n const current = props.page\n const boundaryCount = Math.max(0, props.boundaryCount)\n const siblingCount = Math.max(0, props.siblingCount)\n\n const startPages = range(1, Math.min(boundaryCount, count))\n const endPages = range(Math.max(count - boundaryCount + 1, boundaryCount + 1), count)\n\n const siblingsStart = Math.max(\n Math.min(current - siblingCount, count - boundaryCount - siblingCount * 2 - 1),\n boundaryCount + 2,\n )\n const siblingsEnd = Math.min(\n Math.max(current + siblingCount, boundaryCount + siblingCount * 2 + 2),\n endPages.length > 0 ? endPages[0] - 2 : count - 1,\n )\n\n return [\n ...startPages,\n ...(siblingsStart > boundaryCount + 2\n ? ['ellipsis-start' as const]\n : boundaryCount + 1 < count - boundaryCount\n ? [boundaryCount + 1]\n : []),\n ...range(siblingsStart, siblingsEnd),\n ...(siblingsEnd < count - boundaryCount - 1\n ? ['ellipsis-end' as const]\n : count - boundaryCount > boundaryCount\n ? [count - boundaryCount]\n : []),\n ...endPages,\n ]\n})\n\nfunction goTo(page: number): void {\n emit('update:page', page)\n}\n\nfunction prev(): void {\n goTo(Math.max(1, props.page - 1))\n}\n\nfunction next(): void {\n goTo(Math.min(pageCount.value, props.page + 1))\n}\n\nfunction first(): void {\n goTo(1)\n}\n\nfunction last(): void {\n goTo(pageCount.value)\n}\n</script>\n\n<template>\n <div\n class=\"flex flex-wrap items-center justify-end gap-3\"\n data-gr-pagination\n role=\"navigation\"\n :aria-label=\"t('gr.pagination.label', 'Pagination')\"\n >\n <div class=\"min-w-[100px]\">\n <GrSelect\n v-model=\"pageSizeModel\"\n :options=\"pageSizeOptions\"\n :aria-label=\"t('gr.pagination.pageSize', 'Page size')\"\n />\n </div>\n\n <GrButton variant=\"ghost\" size=\"sm\" :disabled=\"page <= 1\" :aria-label=\"t('gr.pagination.first', 'First page')\" data-gr-pagination-first @click=\"first\">\n «\n </GrButton>\n\n <GrButton variant=\"ghost\" size=\"sm\" :disabled=\"page <= 1\" data-gr-pagination-prev @click=\"prev\">\n {{ t('gr.pagination.prev', 'Prev') }}\n </GrButton>\n\n <div class=\"flex items-center gap-1\">\n <template v-for=\"(item, index) in items\" :key=\"index\">\n <span\n v-if=\"item === 'ellipsis-start' || item === 'ellipsis-end'\"\n data-gr-pagination-ellipsis\n aria-hidden=\"true\"\n class=\"h-8 min-w-8 px-1 grid place-items-center text-sm text-[var(--muted-fg)]\"\n >…</span>\n <button\n v-else\n type=\"button\"\n data-gr-pagination-page\n :aria-current=\"item === page ? 'page' : undefined\"\n :aria-label=\"t('gr.pagination.page', 'Page {n}', { n: item })\"\n class=\"h-8 min-w-8 px-2 rounded-md text-sm font-600 transition-colors duration-150 focus:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)]\"\n :class=\"item === page ? 'bg-[var(--primary)] text-[var(--primary-fg)]' : 'text-[var(--muted-fg)] hover:bg-[var(--muted)] hover:text-[var(--fg)]'\"\n @click=\"goTo(item)\"\n >\n {{ item }}\n </button>\n </template>\n </div>\n\n <GrButton variant=\"ghost\" size=\"sm\" :disabled=\"page >= pageCount\" data-gr-pagination-next @click=\"next\">\n {{ t('gr.pagination.next', 'Next') }}\n </GrButton>\n\n <GrButton variant=\"ghost\" size=\"sm\" :disabled=\"page >= pageCount\" :aria-label=\"t('gr.pagination.last', 'Last page')\" data-gr-pagination-last @click=\"last\">\n »\n </GrButton>\n </div>\n</template>\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { t as GrPagination_default } from "./chunks/GrPagination-
|
|
2
|
-
import { t as grPaginationConfig } from "../../chunks/config-
|
|
1
|
+
import { t as GrPagination_default } from "./chunks/GrPagination-mlyfLjQY.js";
|
|
2
|
+
import { t as grPaginationConfig } from "../../chunks/config-JpHA4Xx02.js";
|
|
3
3
|
export { GrPagination_default as GrPagination, GrPagination_default as default, grPaginationConfig };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import { t as GrProgressBar_default } from "./chunks/GrProgressBar-CC3z-Zqp.js";
|
|
2
|
-
import { n as grProgressBarSafelist, t as grProgressBarConfig } from "../../chunks/config-
|
|
2
|
+
import { n as grProgressBarSafelist, t as grProgressBarConfig } from "../../chunks/config-DnvPqu4g2.js";
|
|
3
3
|
export { GrProgressBar_default as GrProgressBar, GrProgressBar_default as default, grProgressBarConfig, grProgressBarSafelist };
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { t as useGranularityTranslations } from "../../../chunks/granularityI18n-DoV7Ca2m.js";
|
|
2
2
|
import { t as GrButton_default } from "../../GrButton/chunks/GrButton-BlffdeJr.js";
|
|
3
|
-
import { t as GrDialog_default } from "../../GrDialog/chunks/GrDialog-
|
|
3
|
+
import { t as GrDialog_default } from "../../GrDialog/chunks/GrDialog-Ctucxju9.js";
|
|
4
4
|
import { t as GrResponseErrorBanner_default } from "../../GrResponseErrorBanner/chunks/GrResponseErrorBanner-Cdu9ekK9.js";
|
|
5
|
-
import { t as GrFormField_default } from "../../GrFormField/chunks/GrFormField-
|
|
6
|
-
import { t as GrInput_default } from "../../GrInput/chunks/GrInput-
|
|
5
|
+
import { t as GrFormField_default } from "../../GrFormField/chunks/GrFormField-DnfMHx_U.js";
|
|
6
|
+
import { t as GrInput_default } from "../../GrInput/chunks/GrInput-B8dd3JaA.js";
|
|
7
7
|
import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, openBlock, ref, renderSlot, toDisplayString, watch, withCtx } from "vue";
|
|
8
8
|
//#region src/components/GrPromptDialog/GrPromptDialog.vue?vue&type=script&setup=true&lang.ts
|
|
9
9
|
var _hoisted_1 = { class: "grid gap-4" };
|