@feugene/granularity 0.21.0 → 0.23.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.
Files changed (113) hide show
  1. package/dist/chunks/config-BTCOJO5z2.js.map +1 -0
  2. package/dist/chunks/config-B_qi4Fma.js.map +1 -0
  3. package/dist/chunks/config-Baj7Wvx-.js.map +1 -0
  4. package/dist/chunks/{config-BKvX0q10.js → config-C5mO5DYZ.js} +10 -1
  5. package/dist/chunks/config-C5mO5DYZ.js.map +1 -0
  6. package/dist/chunks/config-CIcynHGr.js +96 -0
  7. package/dist/chunks/config-CIcynHGr.js.map +1 -0
  8. package/dist/chunks/{config-BsuDM3Hb.js → config-CKULiuvc.js} +15 -1
  9. package/dist/chunks/config-CKULiuvc.js.map +1 -0
  10. package/dist/chunks/{config-jyZq8XKL.js → config-DCwQBMmJ.js} +12 -3
  11. package/dist/chunks/config-DCwQBMmJ.js.map +1 -0
  12. package/dist/chunks/config-QaIc_gRa.js.map +1 -0
  13. package/dist/chunks/config-eXB87DOi.js.map +1 -0
  14. package/dist/chunks/copy-DChChcq-.js +29 -0
  15. package/dist/chunks/copy-DChChcq-.js.map +1 -0
  16. package/dist/chunks/{trending-up-Bf9AQmoq.js → deltaTone-B5OoS1vo.js} +19 -1
  17. package/dist/chunks/deltaTone-B5OoS1vo.js.map +1 -0
  18. package/dist/chunks/{en-CwAr3C_x.js → en-DCCFP4qu.js} +12 -2
  19. package/dist/chunks/{en-CwAr3C_x.js.map → en-DCCFP4qu.js.map} +1 -1
  20. package/dist/chunks/{es-TR4g2I4A.js → es-CT34HBVM.js} +12 -2
  21. package/dist/chunks/{es-TR4g2I4A.js.map → es-CT34HBVM.js.map} +1 -1
  22. package/dist/chunks/{es-Do1q9_SF.js → es-DdJsxlUr.js} +3 -3
  23. package/dist/chunks/{es-Do1q9_SF.js.map → es-DdJsxlUr.js.map} +1 -1
  24. package/dist/chunks/{generated-Do60Aq2I.js → generated-CiCrGDQO.js} +49 -0
  25. package/dist/chunks/{generated-Do60Aq2I.js.map → generated-CiCrGDQO.js.map} +1 -1
  26. package/dist/chunks/{granular-provider-BW2uopTG.js → granular-provider-CQKhl5C_.js} +10 -8
  27. package/dist/chunks/granular-provider-CQKhl5C_.js.map +1 -0
  28. package/dist/chunks/{ru-Bm-zWJ3F.js → ru-CLiAMwrx.js} +12 -2
  29. package/dist/chunks/{ru-Bm-zWJ3F.js.map → ru-CLiAMwrx.js.map} +1 -1
  30. package/dist/components/GrCodeBlock/chunks/{GrCodeBlock-CW3zY1ee.js → GrCodeBlock-BmXCLgLS.js} +4 -30
  31. package/dist/components/GrCodeBlock/chunks/{GrCodeBlock-CW3zY1ee.js.map → GrCodeBlock-BmXCLgLS.js.map} +1 -1
  32. package/dist/components/GrCodeBlock/index.js +2 -2
  33. package/dist/components/GrDelta/chunks/{GrDelta-BMmpYz8K.js → GrDelta-DFvxNpmN.js} +0 -0
  34. package/dist/components/GrDelta/chunks/GrDelta-DFvxNpmN.js.map +1 -0
  35. package/dist/components/GrDelta/index.js +3 -2
  36. package/dist/components/GrDescriptionList/chunks/{GrDescriptionList-pXh1LZEe.js → GrDescriptionList-2zFXzX83.js} +15 -4
  37. package/dist/components/GrDescriptionList/chunks/GrDescriptionList-2zFXzX83.js.map +1 -0
  38. package/dist/components/GrDescriptionList/index.js +2 -2
  39. package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-LOaFSCyd.js → GrDropdownMenu-DyS6dWDp.js} +2 -2
  40. package/dist/components/GrDropdownMenu/chunks/{GrDropdownMenu-LOaFSCyd.js.map → GrDropdownMenu-DyS6dWDp.js.map} +1 -1
  41. package/dist/components/GrDropdownMenu/index.js +2 -2
  42. package/dist/components/GrFilePreview/chunks/{GrFilePreview-rbE3zL9w.js → GrFilePreview-D0Bt-0nn.js} +1 -1
  43. package/dist/components/GrFilePreview/chunks/{GrFilePreview-rbE3zL9w.js.map → GrFilePreview-D0Bt-0nn.js.map} +1 -1
  44. package/dist/components/GrFilePreview/index.js +2 -2
  45. package/dist/components/GrJsonViewer/chunks/GrJsonViewer-DFUppRNb.js +389 -0
  46. package/dist/components/GrJsonViewer/chunks/GrJsonViewer-DFUppRNb.js.map +1 -0
  47. package/dist/components/GrJsonViewer/index.js +3 -0
  48. package/dist/components/GrList/chunks/{GrList-BUzFRa8-.js → GrList-BRYhpqIe.js} +3 -3
  49. package/dist/components/GrList/chunks/GrList-BRYhpqIe.js.map +1 -0
  50. package/dist/components/GrList/index.js +2 -2
  51. package/dist/components/GrList/styles.css +1 -1
  52. package/dist/components/GrRadio/index.js +1 -1
  53. package/dist/components/GrRadioGroup/index.js +1 -1
  54. package/dist/components/GrRating/chunks/{GrRating-BiAVT9H7.js → GrRating-BSEtaibx.js} +1 -1
  55. package/dist/components/GrRating/chunks/{GrRating-BiAVT9H7.js.map → GrRating-BSEtaibx.js.map} +1 -1
  56. package/dist/components/GrRating/index.js +2 -2
  57. package/dist/components/GrStatistic/chunks/{GrStatistic-DkzWUAw1.js → GrStatistic-C7wQECeS.js} +20 -4
  58. package/dist/components/GrStatistic/chunks/GrStatistic-C7wQECeS.js.map +1 -0
  59. package/dist/components/GrStatistic/index.js +1 -1
  60. package/dist/components/GrTabPanels/index.js +1 -1
  61. package/dist/granular-provider-node.js +1 -1
  62. package/dist/granular-provider.js +1 -1
  63. package/dist/i18n/all.js +1 -1
  64. package/dist/i18n/index.js +1 -1
  65. package/dist/index.js +21 -18
  66. package/dist/theme.js +1 -1
  67. package/dist/tokens.js +1 -1
  68. package/dist/types/components/GrDelta/index.d.ts +1 -0
  69. package/dist/types/components/GrDescriptionList/GrDescriptionList.vue.d.ts +6 -2
  70. package/dist/types/components/GrDescriptionList/grDescriptionListStyles.d.ts +12 -5
  71. package/dist/types/components/GrDropdownMenu/GrDropdownMenu.vue.d.ts +1 -1
  72. package/dist/types/components/GrDropdownMenu/grDropdownMenuStyles.d.ts +10 -1
  73. package/dist/types/components/GrFilePreview/index.d.ts +1 -0
  74. package/dist/types/components/GrInput/GrInput.vue.d.ts +1 -1
  75. package/dist/types/components/GrJsonViewer/GrJsonViewer.vue.d.ts +129 -0
  76. package/dist/types/components/GrJsonViewer/config.d.ts +1 -0
  77. package/dist/types/components/GrJsonViewer/defaults.d.ts +23 -0
  78. package/dist/types/components/GrJsonViewer/grJsonViewerStyles.d.ts +57 -0
  79. package/dist/types/components/GrJsonViewer/index.d.ts +9 -0
  80. package/dist/types/components/GrJsonViewer/jsonToNodes.d.ts +47 -0
  81. package/dist/types/components/GrJsonViewer/safelist.d.ts +1 -0
  82. package/dist/types/components/GrList/grListStyles.d.ts +13 -0
  83. package/dist/types/components/GrNumberInput/GrNumberInput.vue.d.ts +1 -1
  84. package/dist/types/components/GrPromptDialog/GrPromptDialog.vue.d.ts +1 -1
  85. package/dist/types/components/GrStatistic/GrStatistic.vue.d.ts +12 -2
  86. package/dist/types/components/GrStatistic/defaults.d.ts +6 -0
  87. package/dist/types/granular-provider/shared.d.ts +1 -0
  88. package/dist/types/i18n/messages/en.d.ts +18 -0
  89. package/dist/types/i18n/messages/es.d.ts +18 -0
  90. package/dist/types/i18n/messages/ru.d.ts +18 -0
  91. package/dist/types/index.d.ts +1 -0
  92. package/dist/types/internal/granularityFormat.d.ts +26 -0
  93. package/dist/web-types.json +136 -6
  94. package/package.json +5 -1
  95. package/dist/chunks/config-BKvX0q10.js.map +0 -1
  96. package/dist/chunks/config-BTCOJO5z.js.map +0 -1
  97. package/dist/chunks/config-B_qi4Fma2.js.map +0 -1
  98. package/dist/chunks/config-Baj7Wvx-2.js.map +0 -1
  99. package/dist/chunks/config-BsuDM3Hb.js.map +0 -1
  100. package/dist/chunks/config-QaIc_gRa2.js.map +0 -1
  101. package/dist/chunks/config-eXB87DOi2.js.map +0 -1
  102. package/dist/chunks/config-jyZq8XKL.js.map +0 -1
  103. package/dist/chunks/granular-provider-BW2uopTG.js.map +0 -1
  104. package/dist/chunks/trending-up-Bf9AQmoq.js.map +0 -1
  105. package/dist/components/GrDelta/chunks/GrDelta-BMmpYz8K.js.map +0 -1
  106. package/dist/components/GrDescriptionList/chunks/GrDescriptionList-pXh1LZEe.js.map +0 -1
  107. package/dist/components/GrList/chunks/GrList-BUzFRa8-.js.map +0 -1
  108. package/dist/components/GrStatistic/chunks/GrStatistic-DkzWUAw1.js.map +0 -1
  109. /package/dist/chunks/{config-BTCOJO5z.js → config-BTCOJO5z2.js} +0 -0
  110. /package/dist/chunks/{config-B_qi4Fma2.js → config-B_qi4Fma.js} +0 -0
  111. /package/dist/chunks/{config-Baj7Wvx-2.js → config-Baj7Wvx-.js} +0 -0
  112. /package/dist/chunks/{config-QaIc_gRa2.js → config-QaIc_gRa.js} +0 -0
  113. /package/dist/chunks/{config-eXB87DOi2.js → config-eXB87DOi.js} +0 -0
@@ -10,7 +10,7 @@ import { t as grButtonGroupConfig } from "./config-DBmdgoVf.js";
10
10
  import { t as grCardConfig } from "./config-Bm_Sovla.js";
11
11
  import { t as grCheckboxConfig } from "./config-esdviAPL.js";
12
12
  import { t as grCheckboxGroupConfig } from "./config-BuGlNPE9.js";
13
- import { t as grCodeBlockConfig } from "./config-B_qi4Fma2.js";
13
+ import { t as grCodeBlockConfig } from "./config-B_qi4Fma.js";
14
14
  import { t as grCollapseConfig } from "./config-DU2DNSRx.js";
15
15
  import { t as grIconConfig } from "./config-Cp0DE0GH.js";
16
16
  import { t as grColorPickerConfig } from "./config-BsPDQ0dS.js";
@@ -19,13 +19,13 @@ import { t as grConfigProviderConfig } from "./config-B7W9VlZ7.js";
19
19
  import { t as grConfirmDialogConfig } from "./config-A-bBiotF.js";
20
20
  import { t as grDataTableConfig } from "./config-BC8d_smz.js";
21
21
  import { t as grDeltaConfig } from "./config-DBQQT-WP.js";
22
- import { t as grDescriptionListConfig } from "./config-jyZq8XKL.js";
22
+ import { t as grDescriptionListConfig } from "./config-DCwQBMmJ.js";
23
23
  import { t as grDialogSafelist } from "./safelist-CkF4v7DC.js";
24
24
  import { t as grDialogServiceConfig } from "./config-CjXjhphs.js";
25
25
  import { t as grDividerConfig } from "./config-CY0_6cMi.js";
26
26
  import { t as grDrawerConfig } from "./config-D94Z9po5.js";
27
27
  import { t as grDropdownConfig } from "./config-BViooYNv.js";
28
- import { t as grDropdownMenuConfig } from "./config-BKvX0q10.js";
28
+ import { t as grDropdownMenuConfig } from "./config-C5mO5DYZ.js";
29
29
  import { t as grEmptyStateConfig } from "./config-vSji8w90.js";
30
30
  import { t as grFilePreviewConfig } from "./config-QJ54_fcM.js";
31
31
  import { t as grFileUploadConfig } from "./config-CN970ZcJ.js";
@@ -36,9 +36,10 @@ import { t as grFormSectionConfig } from "./config-Dgj1Q6JY.js";
36
36
  import { t as grImageViewerConfig } from "./config-BeZRuonp.js";
37
37
  import { t as grInputConfig } from "./config-DpeQchsV.js";
38
38
  import { t as grInputTagConfig } from "./config-BN4sXYXn.js";
39
+ import { t as grJsonViewerConfig } from "./config-CIcynHGr.js";
39
40
  import { t as grKbdConfig } from "./config-DA6SnEoy.js";
40
41
  import { t as grLinkConfig } from "./config-Cl4ST6Qd.js";
41
- import { t as grListConfig } from "./config-BsuDM3Hb.js";
42
+ import { t as grListConfig } from "./config-CKULiuvc.js";
42
43
  import { t as grLoadingConfig } from "./config-CjnbevbD.js";
43
44
  import { t as grModalConfig } from "./config-BtXRpAtq.js";
44
45
  import { t as grNavbarConfig } from "./config-Bpt9Xbxm.js";
@@ -48,9 +49,9 @@ import { t as grPopoverConfig } from "./config-BSyiFlbi.js";
48
49
  import { t as grProgressBarConfig } from "./config-CQzGyi1C.js";
49
50
  import { t as grProgressCircleConfig } from "./config-MRvhUl-O.js";
50
51
  import { t as grPromptDialogConfig } from "./config-C0Ea9ePP.js";
51
- import { t as grRadioConfig } from "./config-BTCOJO5z.js";
52
- import { t as grRadioGroupConfig } from "./config-QaIc_gRa2.js";
53
- import { t as grRatingConfig } from "./config-Baj7Wvx-2.js";
52
+ import { t as grRadioConfig } from "./config-BTCOJO5z2.js";
53
+ import { t as grRadioGroupConfig } from "./config-QaIc_gRa.js";
54
+ import { t as grRatingConfig } from "./config-Baj7Wvx-.js";
54
55
  import { t as grSegmentedConfig } from "./config-BEVBsvni2.js";
55
56
  import { t as grSelectConfig } from "./config-Cwd3yjRw.js";
56
57
  import { t as grSidebarConfig } from "./config-BfsBepkH2.js";
@@ -61,7 +62,7 @@ import { t as grSplitterConfig } from "./config-BE6n20np.js";
61
62
  import { t as grStatisticConfig } from "./config-Be9k9bOf.js";
62
63
  import { t as grSwitchConfig } from "./config-BoNR7_PD2.js";
63
64
  import { t as grTableConfig } from "./config-WTNhdh6T.js";
64
- import { t as grTabPanelsConfig } from "./config-eXB87DOi2.js";
65
+ import { t as grTabPanelsConfig } from "./config-eXB87DOi.js";
65
66
  import { t as grTabsConfig } from "./config-DWN4x6_e.js";
66
67
  import { t as grTextareaConfig } from "./config-BDfTXHo1.js";
67
68
  import { t as grTimelineConfig } from "./config-COpuWGr_.js";
@@ -143,6 +144,7 @@ var granularityComponentConfigs = {
143
144
  GrImageViewer: grImageViewerConfig,
144
145
  GrInput: grInputConfig,
145
146
  GrInputTag: grInputTagConfig,
147
+ GrJsonViewer: grJsonViewerConfig,
146
148
  GrKbd: grKbdConfig,
147
149
  GrLink: grLinkConfig,
148
150
  GrList: grListConfig,
@@ -0,0 +1 @@
1
+ {"version":3,"file":"granular-provider-CQKhl5C_.js","names":[],"sources":["../../src/components/GrDialog/config.ts","../../src/components/GrResponseErrorBanner/config.ts","../../src/granular-provider/shared.ts","../../src/granular-provider/index.ts"],"sourcesContent":["import { defineGranularComponent } from '@feugene/unocss-preset-granular/contract'\n\nimport { grDialogSafelist } from './safelist'\n\nexport const grDialogConfig = defineGranularComponent(import.meta.url, {\n name: 'GrDialog',\n dependencies: ['GrButton', 'GrModal'],\n safelist: grDialogSafelist,\n})\n","import { defineGranularComponent } from '@feugene/unocss-preset-granular/contract'\n\nexport const grResponseErrorBannerConfig = defineGranularComponent(import.meta.url, {\n name: 'GrResponseErrorBanner',\n dependencies: ['GrAlert', 'GrButton'],\n})\n","// `id`, `theme.*`, `packageBaseUrl` и реестр компонентов провайдера.\n//\n// Все `new URL('../styles/...', import.meta.url)` обязаны лежать именно здесь:\n// `shared.ts` в одной директории с `index.ts` / `node.ts`, поэтому\n// относительные пути совпадают. Для браузера безопасно — бандлеры\n// транслируют такие литералы в статические asset-URL.\n//\n// `apps/showcase/scripts/generate-component-api.mjs` читает список компонентов\n// из `Object.keys(granularityComponentConfigs)` через vite SSR.\nimport {\n defineGranularProvider,\n type GranularComponentDescriptor,\n type GranularProvider,\n resolvePackageBaseUrl,\n} from '@feugene/unocss-preset-granular/contract'\n// <granularity:components:imports> — блок генерируется `yarn generate:registry`\nimport { grAlertConfig } from '../components/GrAlert/config'\nimport { grAutocompleteConfig } from '../components/GrAutocomplete/config'\nimport { grAvatarConfig } from '../components/GrAvatar/config'\nimport { grBadgeConfig } from '../components/GrBadge/config'\nimport { grBadgeWrapConfig } from '../components/GrBadgeWrap/config'\nimport { grBottomNavConfig } from '../components/GrBottomNav/config'\nimport { grBreadcrumbsConfig } from '../components/GrBreadcrumbs/config'\nimport { grButtonConfig } from '../components/GrButton/config'\nimport { grButtonGroupConfig } from '../components/GrButtonGroup/config'\nimport { grCardConfig } from '../components/GrCard/config'\nimport { grCheckboxConfig } from '../components/GrCheckbox/config'\nimport { grCheckboxGroupConfig } from '../components/GrCheckboxGroup/config'\nimport { grCodeBlockConfig } from '../components/GrCodeBlock/config'\nimport { grCollapseConfig } from '../components/GrCollapse/config'\nimport { grColorPickerConfig } from '../components/GrColorPicker/config'\nimport { grCommandPaletteConfig } from '../components/GrCommandPalette/config'\nimport { grConfigProviderConfig } from '../components/GrConfigProvider/config'\nimport { grConfirmDialogConfig } from '../components/GrConfirmDialog/config'\nimport { grDataTableConfig } from '../components/GrDataTable/config'\nimport { grDeltaConfig } from '../components/GrDelta/config'\nimport { grDescriptionListConfig } from '../components/GrDescriptionList/config'\nimport { grDialogConfig } from '../components/GrDialog/config'\nimport { grDialogServiceConfig } from '../components/GrDialogService/config'\nimport { grDividerConfig } from '../components/GrDivider/config'\nimport { grDrawerConfig } from '../components/GrDrawer/config'\nimport { grDropdownConfig } from '../components/GrDropdown/config'\nimport { grDropdownMenuConfig } from '../components/GrDropdownMenu/config'\nimport { grEmptyStateConfig } from '../components/GrEmptyState/config'\nimport { grFilePreviewConfig } from '../components/GrFilePreview/config'\nimport { grFileUploadConfig } from '../components/GrFileUpload/config'\nimport { grFormConfig } from '../components/GrForm/config'\nimport { grFormFieldConfig } from '../components/GrFormField/config'\nimport { grFormFileConfig } from '../components/GrFormFile/config'\nimport { grFormSectionConfig } from '../components/GrFormSection/config'\nimport { grIconConfig } from '../components/GrIcon/config'\nimport { grImageViewerConfig } from '../components/GrImageViewer/config'\nimport { grInputConfig } from '../components/GrInput/config'\nimport { grInputTagConfig } from '../components/GrInputTag/config'\nimport { grJsonViewerConfig } from '../components/GrJsonViewer/config'\nimport { grKbdConfig } from '../components/GrKbd/config'\nimport { grLinkConfig } from '../components/GrLink/config'\nimport { grListConfig } from '../components/GrList/config'\nimport { grLoadingConfig } from '../components/GrLoading/config'\nimport { grModalConfig } from '../components/GrModal/config'\nimport { grNavbarConfig } from '../components/GrNavbar/config'\nimport { grNumberInputConfig } from '../components/GrNumberInput/config'\nimport { grPaginationConfig } from '../components/GrPagination/config'\nimport { grPopoverConfig } from '../components/GrPopover/config'\nimport { grProgressBarConfig } from '../components/GrProgressBar/config'\nimport { grProgressCircleConfig } from '../components/GrProgressCircle/config'\nimport { grPromptDialogConfig } from '../components/GrPromptDialog/config'\nimport { grRadioConfig } from '../components/GrRadio/config'\nimport { grRadioGroupConfig } from '../components/GrRadioGroup/config'\nimport { grRatingConfig } from '../components/GrRating/config'\nimport { grResponseErrorBannerConfig } from '../components/GrResponseErrorBanner/config'\nimport { grSegmentedConfig } from '../components/GrSegmented/config'\nimport { grSelectConfig } from '../components/GrSelect/config'\nimport { grSidebarConfig } from '../components/GrSidebar/config'\nimport { grSkeletonConfig } from '../components/GrSkeleton/config'\nimport { grSliderConfig } from '../components/GrSlider/config'\nimport { grSortableListConfig } from '../components/GrSortableList/config'\nimport { grSplitterConfig } from '../components/GrSplitter/config'\nimport { grStatisticConfig } from '../components/GrStatistic/config'\nimport { grSwitchConfig } from '../components/GrSwitch/config'\nimport { grTableConfig } from '../components/GrTable/config'\nimport { grTabPanelsConfig } from '../components/GrTabPanels/config'\nimport { grTabsConfig } from '../components/GrTabs/config'\nimport { grTextareaConfig } from '../components/GrTextarea/config'\nimport { grTimelineConfig } from '../components/GrTimeline/config'\nimport { grToasterConfig } from '../components/GrToaster/config'\nimport { grTooltipConfig } from '../components/GrTooltip/config'\nimport { grTreeConfig } from '../components/GrTree/config'\nimport { grTreeSelectConfig } from '../components/GrTreeSelect/config'\n// </granularity:components:imports>\n\n/** Идентификатор провайдера — совпадает с именем пакета. */\nexport const GRANULARITY_PROVIDER_ID = '@feugene/granularity'\n\n// Не заменять на `new URL('..', import.meta.url)`: Vite и rolldown распознают\n// этот литерал и подставляют `data:`-URL, после чего scan-директории пустеют.\nconst packageBaseUrl = resolvePackageBaseUrl(import.meta.url)\n\n/** Встроенные темы пакета. Единственный источник правды о списке тем. */\nexport const granularityThemeNames = ['light', 'dark'] as const\nexport type GranularityThemeName = (typeof granularityThemeNames)[number]\n\n/** Темы, активные по умолчанию, если окружение не переопределяет выбор. */\nexport const granularityDefaultThemes: readonly GranularityThemeName[] = ['light']\n\nconst theme = {\n baseCssUrl: new URL('../styles/base.css', import.meta.url).href,\n tokensCssUrl: new URL('../styles/tokens.css', import.meta.url).href,\n themes: {\n light: new URL('../styles/themes/light.css', import.meta.url).href,\n dark: new URL('../styles/themes/dark.css', import.meta.url).href,\n },\n defaultThemes: granularityDefaultThemes,\n} as const\n\n/**\n * Реестр публичных компонентов. Ключи совпадают с subpath-экспортом\n * `@feugene/granularity/components/<Name>`.\n */\nexport const granularityComponentConfigs = {\n // <granularity:components:registry> — блок генерируется `yarn generate:registry`\n GrAlert: grAlertConfig,\n GrAutocomplete: grAutocompleteConfig,\n GrAvatar: grAvatarConfig,\n GrBadge: grBadgeConfig,\n GrBadgeWrap: grBadgeWrapConfig,\n GrBottomNav: grBottomNavConfig,\n GrBreadcrumbs: grBreadcrumbsConfig,\n GrButton: grButtonConfig,\n GrButtonGroup: grButtonGroupConfig,\n GrCard: grCardConfig,\n GrCheckbox: grCheckboxConfig,\n GrCheckboxGroup: grCheckboxGroupConfig,\n GrCodeBlock: grCodeBlockConfig,\n GrCollapse: grCollapseConfig,\n GrColorPicker: grColorPickerConfig,\n GrCommandPalette: grCommandPaletteConfig,\n GrConfigProvider: grConfigProviderConfig,\n GrConfirmDialog: grConfirmDialogConfig,\n GrDataTable: grDataTableConfig,\n GrDelta: grDeltaConfig,\n GrDescriptionList: grDescriptionListConfig,\n GrDialog: grDialogConfig,\n GrDialogService: grDialogServiceConfig,\n GrDivider: grDividerConfig,\n GrDrawer: grDrawerConfig,\n GrDropdown: grDropdownConfig,\n GrDropdownMenu: grDropdownMenuConfig,\n GrEmptyState: grEmptyStateConfig,\n GrFilePreview: grFilePreviewConfig,\n GrFileUpload: grFileUploadConfig,\n GrForm: grFormConfig,\n GrFormField: grFormFieldConfig,\n GrFormFile: grFormFileConfig,\n GrFormSection: grFormSectionConfig,\n GrIcon: grIconConfig,\n GrImageViewer: grImageViewerConfig,\n GrInput: grInputConfig,\n GrInputTag: grInputTagConfig,\n GrJsonViewer: grJsonViewerConfig,\n GrKbd: grKbdConfig,\n GrLink: grLinkConfig,\n GrList: grListConfig,\n GrLoading: grLoadingConfig,\n GrModal: grModalConfig,\n GrNavbar: grNavbarConfig,\n GrNumberInput: grNumberInputConfig,\n GrPagination: grPaginationConfig,\n GrPopover: grPopoverConfig,\n GrProgressBar: grProgressBarConfig,\n GrProgressCircle: grProgressCircleConfig,\n GrPromptDialog: grPromptDialogConfig,\n GrRadio: grRadioConfig,\n GrRadioGroup: grRadioGroupConfig,\n GrRating: grRatingConfig,\n GrResponseErrorBanner: grResponseErrorBannerConfig,\n GrSegmented: grSegmentedConfig,\n GrSelect: grSelectConfig,\n GrSidebar: grSidebarConfig,\n GrSkeleton: grSkeletonConfig,\n GrSlider: grSliderConfig,\n GrSortableList: grSortableListConfig,\n GrSplitter: grSplitterConfig,\n GrStatistic: grStatisticConfig,\n GrSwitch: grSwitchConfig,\n GrTable: grTableConfig,\n GrTabPanels: grTabPanelsConfig,\n GrTabs: grTabsConfig,\n GrTextarea: grTextareaConfig,\n GrTimeline: grTimelineConfig,\n GrToaster: grToasterConfig,\n GrTooltip: grTooltipConfig,\n GrTree: grTreeConfig,\n GrTreeSelect: grTreeSelectConfig,\n // </granularity:components:registry>\n}\n\nexport type GranularityComponentName = keyof typeof granularityComponentConfigs\n\n/** Базовый набор компонентов в порядке реестра. */\nconst baseComponents: readonly GranularComponentDescriptor[] = Object.values(\n granularityComponentConfigs,\n)\n\n/**\n * Собирает granular-provider пакета.\n *\n * `overrides` — точка расширения для потребителя: дескриптор с именем из\n * базового реестра заменяет его, остальные дописываются в конец.\n */\nexport function createGranularityProvider(\n overrides: readonly GranularComponentDescriptor[] = [],\n): GranularProvider {\n const overrideByName = new Map(\n overrides.map(component => [component.name, component]),\n )\n const components: GranularComponentDescriptor[] = [\n ...baseComponents.map(component => overrideByName.get(component.name) ?? component),\n ...overrides.filter(component => !baseComponents.some(base => base.name === component.name)),\n ]\n\n return defineGranularProvider({\n id: GRANULARITY_PROVIDER_ID,\n contractVersion: 1,\n packageBaseUrl,\n components,\n theme,\n })\n}\n","import { createGranularityProvider } from './shared'\n\nexport * from './shared'\n\nexport const granularityProvider = createGranularityProvider()\n\nexport default granularityProvider\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIA,IAAa,iBAAiB,wBAAwB,OAAO,KAAK,KAAK;CACrE,MAAM;CACN,cAAc,CAAC,YAAY,SAAS;CACpC,UAAU;AACZ,CAAC;;;ACND,IAAa,8BAA8B,wBAAwB,OAAO,KAAK,KAAK;CAClF,MAAM;CACN,cAAc,CAAC,WAAW,UAAU;AACtC,CAAC;;;;ACuFD,IAAa,0BAA0B;AAIvC,IAAM,iBAAiB,sBAAsB,OAAO,KAAK,GAAG;;AAG5D,IAAa,wBAAwB,CAAC,SAAS,MAAM;;AAIrD,IAAa,2BAA4D,CAAC,OAAO;AAEjF,IAAM,QAAQ;CACZ,YAAY,IAAA,IAAA,q5KAAA,KAAA,OAAA,KAAA,GAAA,CAA8C,CAAC;CAC3D,cAAc,IAAA,IAAA,ykXAAA,KAAA,OAAA,KAAA,GAAA,CAAgD,CAAC;CAC/D,QAAQ;EACN,OAAO,IAAA,IAAA,qjYAAA,KAAA,OAAA,KAAA,GAAA,CAAsD,CAAC;EAC9D,MAAM,IAAA,IAAA,yxSAAA,KAAA,OAAA,KAAA,GAAA,CAAqD,CAAC;CAC9D;CACA,eAAe;AACjB;;;;;AAMA,IAAa,8BAA8B;CAEzC,SAAS;CACT,gBAAgB;CAChB,UAAU;CACV,SAAS;CACT,aAAa;CACb,aAAa;CACb,eAAe;CACf,UAAU;CACV,eAAe;CACf,QAAQ;CACR,YAAY;CACZ,iBAAiB;CACjB,aAAa;CACb,YAAY;CACZ,eAAe;CACf,kBAAkB;CAClB,kBAAkB;CAClB,iBAAiB;CACjB,aAAa;CACb,SAAS;CACT,mBAAmB;CACnB,UAAU;CACV,iBAAiB;CACjB,WAAW;CACX,UAAU;CACV,YAAY;CACZ,gBAAgB;CAChB,cAAc;CACd,eAAe;CACf,cAAc;CACd,QAAQ;CACR,aAAa;CACb,YAAY;CACZ,eAAe;CACf,QAAQ;CACR,eAAe;CACf,SAAS;CACT,YAAY;CACZ,cAAc;CACd,OAAO;CACP,QAAQ;CACR,QAAQ;CACR,WAAW;CACX,SAAS;CACT,UAAU;CACV,eAAe;CACf,cAAc;CACd,WAAW;CACX,eAAe;CACf,kBAAkB;CAClB,gBAAgB;CAChB,SAAS;CACT,cAAc;CACd,UAAU;CACV,uBAAuB;CACvB,aAAa;CACb,UAAU;CACV,WAAW;CACX,YAAY;CACZ,UAAU;CACV,gBAAgB;CAChB,YAAY;CACZ,aAAa;CACb,UAAU;CACV,SAAS;CACT,aAAa;CACb,QAAQ;CACR,YAAY;CACZ,YAAY;CACZ,WAAW;CACX,WAAW;CACX,QAAQ;CACR,cAAc;AAEhB;;AAKA,IAAM,iBAAyD,OAAO,OACpE,2BACF;;;;;;;AAQA,SAAgB,0BACd,YAAoD,CAAC,GACnC;CAClB,MAAM,iBAAiB,IAAI,IACzB,UAAU,KAAI,cAAa,CAAC,UAAU,MAAM,SAAS,CAAC,CACxD;CAMA,OAAO,uBAAuB;EAC5B,IAAI;EACJ,iBAAiB;EACjB;EACA,aARA,GAAG,eAAe,KAAI,cAAa,eAAe,IAAI,UAAU,IAAI,KAAK,SAAS,GAClF,GAAG,UAAU,QAAO,cAAa,CAAC,eAAe,MAAK,SAAQ,KAAK,SAAS,UAAU,IAAI,CAAC,CAO3F;EACA;CACF,CAAC;AACH;;;AChOA,IAAa,sBAAsB,0BAA0B"}
@@ -307,6 +307,15 @@ var codeBlock = {
307
307
  "copied": "Код скопирован",
308
308
  "copyFailed": "Не удалось скопировать код"
309
309
  };
310
+ var jsonViewer = {
311
+ "search": "Поиск по ключу или значению",
312
+ "expandAll": "Раскрыть всё",
313
+ "collapseAll": "Свернуть всё",
314
+ "copy": "Скопировать значение",
315
+ "copied": "Значение скопировано",
316
+ "copyFailed": "Не удалось скопировать значение",
317
+ "moreItems": "ещё"
318
+ };
310
319
  var ru_default = {
311
320
  common,
312
321
  loading,
@@ -356,7 +365,8 @@ var ru_default = {
356
365
  bottomNav,
357
366
  emptyState,
358
367
  sortable,
359
- codeBlock
368
+ codeBlock,
369
+ jsonViewer
360
370
  };
361
371
  //#endregion
362
- export { autocomplete, avatar, badgeWrap, bottomNav, breadcrumbs, button, codeBlock, collapse, colorPicker, colors, commandPalette, common, dataTable, ru_default as default, dialog, drawer, emptyState, fileUpload, fileValidation, form, formErrorBanner, formFile, imageViewer, input, inputTag, kbd, link, list, loading, modal, navbar, numberInput, pagination, rating, responseError, select, sidebar, slider, sortable, splitter, statistic, table, tabs, textarea, timeline, toaster, tree, treeSelect, uploadErrorBanner };
372
+ export { autocomplete, avatar, badgeWrap, bottomNav, breadcrumbs, button, codeBlock, collapse, colorPicker, colors, commandPalette, common, dataTable, ru_default as default, dialog, drawer, emptyState, fileUpload, fileValidation, form, formErrorBanner, formFile, imageViewer, input, inputTag, jsonViewer, kbd, link, list, loading, modal, navbar, numberInput, pagination, rating, responseError, select, sidebar, slider, sortable, splitter, statistic, table, tabs, textarea, timeline, toaster, tree, treeSelect, uploadErrorBanner };
@@ -1 +1 @@
1
- {"version":3,"file":"ru-Bm-zWJ3F.js","names":[],"sources":["../../src/i18n/locales/ru.json"],"sourcesContent":["{\n \"common\": {\n \"confirm\": \"Подтвердить\",\n \"cancel\": \"Отмена\",\n \"add\": \"Добавить\",\n \"clear\": \"Очистить\",\n \"close\": \"Закрыть\"\n },\n \"loading\": {\n \"defaultText\": \"Загрузка...\"\n },\n \"treeSelect\": {\n \"filterPlaceholder\": \"Поиск…\",\n \"empty\": \"Нет данных\",\n \"loading\": \"Загрузка…\"\n },\n \"badgeWrap\": {\n \"count\": \"{count} новых\"\n },\n \"avatar\": {\n \"group\": \"Участники\",\n \"groupOverflow\": \"и ещё {count}\",\n \"status\": {\n \"online\": \"в сети\",\n \"offline\": \"не в сети\",\n \"busy\": \"занят\",\n \"away\": \"отошёл\"\n }\n },\n \"tree\": {\n \"drag\": \"Перетащить\",\n \"expand\": \"Развернуть\",\n \"collapse\": \"Свернуть\",\n \"moved\": \"{label} перемещён\"\n },\n \"select\": {\n \"customValuePlaceholder\": \"Добавить значение…\",\n \"addOption\": \"Добавить «{value}»\",\n \"clearOption\": \"Не выбрано\",\n \"searchPlaceholder\": \"Поиск…\",\n \"loading\": \"Загрузка…\",\n \"noResults\": \"Ничего не найдено\",\n \"removeTag\": \"Удалить {label}\"\n },\n \"dataTable\": {\n \"loading\": \"Загрузка…\",\n \"empty\": \"Нет данных\",\n \"selectAll\": \"Выбрать все строки\",\n \"selectRow\": \"Выбрать строку\",\n \"sortAsc\": \"Нажмите для сортировки по возрастанию\",\n \"sortDesc\": \"Нажмите для сортировки по убыванию\",\n \"sortNone\": \"Нажмите, чтобы снять сортировку\",\n \"moveColumn\": \"Переставить колонку {label}\",\n \"columnMoved\": \"Колонка {label} перемещена на позицию {position} из {count}\",\n \"resizeColumn\": \"Ширина колонки {label}\",\n \"columnResized\": \"Ширина колонки {label} — {width} пикселей\"\n },\n \"breadcrumbs\": {\n \"label\": \"Навигационная цепочка\",\n \"expand\": \"Показать скрытые уровни\"\n },\n \"autocomplete\": {\n \"loading\": \"Загрузка…\",\n \"noResults\": \"Ничего не найдено\",\n \"addOption\": \"Добавить «{value}»\",\n \"removeValue\": \"Убрать {label}\",\n \"typeMore\": {\n \"one\": \"Введите минимум {n} символ\",\n \"few\": \"Введите минимум {n} символа\",\n \"many\": \"Введите минимум {n} символов\",\n \"other\": \"Введите минимум {n} символа\"\n }\n },\n \"form\": {\n \"required\": \"Обязательное поле\",\n \"min\": \"Минимум {min}\",\n \"max\": \"Максимум {max}\",\n \"len\": \"Длина должна быть {len}\",\n \"pattern\": \"Неверный формат\",\n \"email\": \"Введите корректный e-mail\",\n \"url\": \"Введите корректный URL\",\n \"file\": \"Некорректный файл\",\n \"invalid\": \"Некорректное значение\",\n \"readonly\": \"Только для чтения\",\n \"validating\": \"Проверяем…\"\n },\n \"dialog\": {\n \"confirm\": {\n \"title\": \"Подтверждение\"\n },\n \"prompt\": {\n \"title\": \"Введите значение\",\n \"label\": \"Значение\",\n \"required\": \"Введите значение.\"\n },\n \"ok\": \"ОК\"\n },\n \"switch\": {\n \"loading\": \"Сохраняем…\"\n },\n \"slider\": {\n \"min\": \"минимум\",\n \"max\": \"максимум\"\n },\n \"sidebar\": {\n \"expand\": \"Развернуть панель\",\n \"collapse\": \"Свернуть панель\"\n },\n \"splitter\": {\n \"label\": \"Изменить размер панелей\"\n },\n \"statistic\": {\n \"trendUp\": \"Рост\",\n \"trendDown\": \"Падение\",\n \"trendFlat\": \"Без изменений\"\n },\n \"pagination\": {\n \"label\": \"Пагинация\",\n \"pageSize\": \"Размер страницы\",\n \"prev\": \"Назад\",\n \"next\": \"Вперёд\",\n \"page\": \"Страница {n}\",\n \"first\": \"Первая страница\",\n \"last\": \"Последняя страница\",\n \"jumpTo\": \"Перейти к\",\n \"total\": \"{from}–{to} из {total}\",\n \"status\": \"Страница {page} из {count}\"\n },\n \"navbar\": {\n \"openMenu\": \"Открыть меню\"\n },\n \"responseError\": {\n \"networkTitle\": \"Нет связи с сервером\",\n \"networkMessage\": \"Проверьте подключение к интернету и попробуйте ещё раз.\",\n \"abortedTitle\": \"Запрос отменён\",\n \"abortedMessage\": \"Запрос был отменён.\",\n \"validationTitle\": \"Проверьте данные\",\n \"validationMessage\": \"Сервер вернул ошибку валидации. Исправьте поля и попробуйте снова.\",\n \"clientTitle\": \"Ошибка запроса\",\n \"clientMessage\": \"Сервер отклонил запрос.\",\n \"serverTitle\": \"Ошибка сервера\",\n \"serverMessage\": \"Произошла ошибка на сервере. Попробуйте ещё раз.\",\n \"unknownTitle\": \"Что-то пошло не так\",\n \"unknownMessage\": \"Не удалось выполнить операцию. Попробуйте ещё раз.\",\n \"statusLabel\": \"HTTP {status}\",\n \"retryLabel\": \"Повторить\",\n \"dismissLabel\": \"Скрыть\"\n },\n \"colors\": {\n \"neutral\": \"Нейтральный\",\n \"success\": \"Успех\",\n \"warning\": \"Предупреждение\",\n \"danger\": \"Опасность\",\n \"info\": \"Инфо\",\n \"primary\": \"Primary\",\n \"slate\": \"Сланцевый\",\n \"azure\": \"Лазурный\"\n },\n \"drawer\": {\n \"title\": \"Панель\"\n },\n \"modal\": {\n \"title\": \"Диалог\"\n },\n \"numberInput\": {\n \"increase\": \"Увеличить\",\n \"decrease\": \"Уменьшить\"\n },\n \"toaster\": {\n \"dismiss\": \"Закрыть\",\n \"region\": \"Уведомления\"\n },\n \"inputTag\": {\n \"removeTag\": \"Удалить тег\",\n \"removeTagNamed\": \"Удалить тег {tag}\",\n \"clearAll\": \"Удалить все теги\",\n \"added\": \"Добавлен тег: {tag}\",\n \"addedMany\": \"Добавлено тегов: {count}\",\n \"removed\": \"Удалён тег: {tag}\",\n \"cleared\": \"Все теги удалены\",\n \"limitReached\": \"Достигнут предел тегов\"\n },\n \"fileUpload\": {\n \"placeholder\": \"Перетащите файлы сюда или нажмите для выбора\",\n \"progress\": \"Прогресс загрузки\",\n \"uploading\": \"Загрузка…\",\n \"success\": \"Загрузка завершена\",\n \"error\": \"Не удалось загрузить\",\n \"remove\": \"Убрать {fileName}\",\n \"retry\": \"Повторить загрузку\",\n \"retryFile\": \"Повторить {fileName}\",\n \"abortFile\": \"Отменить загрузку {fileName}\",\n \"statusPending\": \"В очереди\",\n \"statusUploading\": \"Загружается\",\n \"statusSuccess\": \"Загружен\",\n \"statusError\": \"Ошибка\"\n },\n \"formFile\": {\n \"upload\": \"Загрузить файл\",\n \"change\": \"Заменить файл\",\n \"remove\": \"Удалить\",\n \"clearAll\": \"Очистить всё\",\n \"placeholder\": \"Файлы не выбраны\",\n \"validateError\": \"Не удалось проверить перетащенные файлы\",\n \"removeFile\": \"Убрать {fileName}\"\n },\n \"imageViewer\": {\n \"close\": \"Закрыть просмотрщик\",\n \"prev\": \"Предыдущее изображение\",\n \"next\": \"Следующее изображение\",\n \"zoomIn\": \"Увеличить\",\n \"zoomOut\": \"Уменьшить\",\n \"resetZoom\": \"Сбросить масштаб\",\n \"zoomToNatural\": \"Натуральный размер (1:1)\",\n \"rotateLeft\": \"Повернуть влево\",\n \"rotateRight\": \"Повернуть вправо\",\n \"download\": \"Скачать изображение\",\n \"empty\": \"Нет изображения\",\n \"label\": \"Просмотр изображений\",\n \"position\": \"Изображение {index} из {total}\"\n },\n \"formErrorBanner\": {\n \"validationTitle\": \"Проверьте поля формы\",\n \"validationMessage\": \"Сервер отклонил данные. Исправьте выделенные поля и попробуйте снова.\",\n \"clientTitle\": \"Не удалось сохранить\",\n \"serverTitle\": \"Ошибка сохранения\",\n \"serverMessage\": \"Не удалось сохранить данные. Попробуйте позже.\"\n },\n \"uploadErrorBanner\": {\n \"networkTitle\": \"Не удалось загрузить файлы\",\n \"networkMessage\": \"Проверьте подключение и попробуйте снова.\",\n \"validationTitle\": \"Файлы не прошли проверку\",\n \"validationMessage\": \"Некоторые файлы не соответствуют требованиям.\",\n \"serverTitle\": \"Ошибка загрузки на сервере\",\n \"serverMessage\": \"Сервер не смог обработать загрузку. Попробуйте снова.\"\n },\n \"input\": {\n \"clear\": \"Очистить\",\n \"showPassword\": \"Показать пароль\",\n \"hidePassword\": \"Скрыть пароль\",\n \"limitReached\": \"Достигнут предел символов\"\n },\n \"textarea\": {\n \"lines\": {\n \"one\": \"{n} строка\",\n \"few\": \"{n} строки\",\n \"many\": \"{n} строк\",\n \"other\": \"{n} строки\"\n }\n },\n \"rating\": {\n \"label\": \"Оценка\",\n \"valueText\": \"{value} из {max}\",\n \"valueTextWithLabel\": \"{value} из {max}, {label}\"\n },\n \"commandPalette\": {\n \"label\": \"Палитра команд\",\n \"placeholder\": \"Введите команду или запрос…\",\n \"empty\": \"Ничего не найдено\",\n \"recent\": \"Недавние\",\n \"loading\": \"Загрузка…\"\n },\n \"fileValidation\": {\n \"accept\": \"Файл «{fileName}» не подходит под accept=\\\"{accept}\\\"\",\n \"extension\": \"У файла «{fileName}» недопустимое расширение «.{extension}»\",\n \"mimeType\": \"У файла «{fileName}» недопустимый тип «{mimeType}»\",\n \"mimeTypeFallback\": \"У файла «{fileName}» неопределённый тип «{mimeType}»\",\n \"maxFileSize\": \"Файл «{fileName}» слишком большой ({size} байт), максимум {maxBytes}\",\n \"maxCount\": \"Выбрано слишком много файлов ({count}), максимум — {maxCount}\",\n \"maxTotalSize\": \"Суммарный размер файлов слишком большой ({total} байт), максимум {maxBytes}\"\n },\n \"kbd\": {\n \"command\": \"Command\",\n \"option\": \"Option\",\n \"shift\": \"Shift\",\n \"control\": \"Control\",\n \"enter\": \"Enter\",\n \"escape\": \"Escape\",\n \"tab\": \"Tab\",\n \"backspace\": \"Backspace\",\n \"delete\": \"Delete\",\n \"pageUp\": \"Page Up\",\n \"pageDown\": \"Page Down\",\n \"arrowUp\": \"стрелка вверх\",\n \"arrowDown\": \"стрелка вниз\",\n \"arrowLeft\": \"стрелка влево\",\n \"arrowRight\": \"стрелка вправо\",\n \"then\": \"затем\"\n },\n \"link\": {\n \"opensInNewTab\": \"откроется в новой вкладке\"\n },\n \"colorPicker\": {\n \"panelLabel\": \"Выбор цвета\",\n \"hue\": \"Оттенок\",\n \"saturation\": \"Насыщенность\",\n \"lightness\": \"Светлота\",\n \"opacity\": \"Непрозрачность\",\n \"hexLabel\": \"Значение hex\",\n \"presetsLabel\": \"Палитра\",\n \"swatchLabel\": \"Цвет {color}\"\n },\n \"collapse\": {\n \"empty\": \"Пока пусто\"\n },\n \"list\": {\n \"empty\": \"Пока пусто\"\n },\n \"button\": {\n \"loading\": \"Загрузка…\"\n },\n \"table\": {\n \"empty\": \"Пока пусто\"\n },\n \"timeline\": {\n \"empty\": \"Пока пусто\"\n },\n \"tabs\": {\n \"empty\": \"Пока пусто\",\n \"close\": \"Закрыть вкладку\"\n },\n \"bottomNav\": {\n \"label\": \"Нижняя навигация\",\n \"badge\": \"{count} новых\"\n },\n \"emptyState\": {\n \"title\": \"Пока пусто\"\n },\n \"sortable\": {\n \"label\": \"Сортируемый список\",\n \"handle\": \"Переставить\",\n \"empty\": \"Пока нечего сортировать\",\n \"grabbed\": \"Взято. Позиция {position} из {count}\",\n \"moved\": \"Позиция {position} из {count}\",\n \"dropped\": \"Отпущено на позиции {position} из {count}\",\n \"cancelled\": \"Перенос отменён\"\n },\n \"codeBlock\": {\n \"copy\": \"Скопировать код\",\n \"copied\": \"Код скопирован\",\n \"copyFailed\": \"Не удалось скопировать код\"\n }\n}\n"],"mappings":""}
1
+ {"version":3,"file":"ru-CLiAMwrx.js","names":[],"sources":["../../src/i18n/locales/ru.json"],"sourcesContent":["{\n \"common\": {\n \"confirm\": \"Подтвердить\",\n \"cancel\": \"Отмена\",\n \"add\": \"Добавить\",\n \"clear\": \"Очистить\",\n \"close\": \"Закрыть\"\n },\n \"loading\": {\n \"defaultText\": \"Загрузка...\"\n },\n \"treeSelect\": {\n \"filterPlaceholder\": \"Поиск…\",\n \"empty\": \"Нет данных\",\n \"loading\": \"Загрузка…\"\n },\n \"badgeWrap\": {\n \"count\": \"{count} новых\"\n },\n \"avatar\": {\n \"group\": \"Участники\",\n \"groupOverflow\": \"и ещё {count}\",\n \"status\": {\n \"online\": \"в сети\",\n \"offline\": \"не в сети\",\n \"busy\": \"занят\",\n \"away\": \"отошёл\"\n }\n },\n \"tree\": {\n \"drag\": \"Перетащить\",\n \"expand\": \"Развернуть\",\n \"collapse\": \"Свернуть\",\n \"moved\": \"{label} перемещён\"\n },\n \"select\": {\n \"customValuePlaceholder\": \"Добавить значение…\",\n \"addOption\": \"Добавить «{value}»\",\n \"clearOption\": \"Не выбрано\",\n \"searchPlaceholder\": \"Поиск…\",\n \"loading\": \"Загрузка…\",\n \"noResults\": \"Ничего не найдено\",\n \"removeTag\": \"Удалить {label}\"\n },\n \"dataTable\": {\n \"loading\": \"Загрузка…\",\n \"empty\": \"Нет данных\",\n \"selectAll\": \"Выбрать все строки\",\n \"selectRow\": \"Выбрать строку\",\n \"sortAsc\": \"Нажмите для сортировки по возрастанию\",\n \"sortDesc\": \"Нажмите для сортировки по убыванию\",\n \"sortNone\": \"Нажмите, чтобы снять сортировку\",\n \"moveColumn\": \"Переставить колонку {label}\",\n \"columnMoved\": \"Колонка {label} перемещена на позицию {position} из {count}\",\n \"resizeColumn\": \"Ширина колонки {label}\",\n \"columnResized\": \"Ширина колонки {label} — {width} пикселей\"\n },\n \"breadcrumbs\": {\n \"label\": \"Навигационная цепочка\",\n \"expand\": \"Показать скрытые уровни\"\n },\n \"autocomplete\": {\n \"loading\": \"Загрузка…\",\n \"noResults\": \"Ничего не найдено\",\n \"addOption\": \"Добавить «{value}»\",\n \"removeValue\": \"Убрать {label}\",\n \"typeMore\": {\n \"one\": \"Введите минимум {n} символ\",\n \"few\": \"Введите минимум {n} символа\",\n \"many\": \"Введите минимум {n} символов\",\n \"other\": \"Введите минимум {n} символа\"\n }\n },\n \"form\": {\n \"required\": \"Обязательное поле\",\n \"min\": \"Минимум {min}\",\n \"max\": \"Максимум {max}\",\n \"len\": \"Длина должна быть {len}\",\n \"pattern\": \"Неверный формат\",\n \"email\": \"Введите корректный e-mail\",\n \"url\": \"Введите корректный URL\",\n \"file\": \"Некорректный файл\",\n \"invalid\": \"Некорректное значение\",\n \"readonly\": \"Только для чтения\",\n \"validating\": \"Проверяем…\"\n },\n \"dialog\": {\n \"confirm\": {\n \"title\": \"Подтверждение\"\n },\n \"prompt\": {\n \"title\": \"Введите значение\",\n \"label\": \"Значение\",\n \"required\": \"Введите значение.\"\n },\n \"ok\": \"ОК\"\n },\n \"switch\": {\n \"loading\": \"Сохраняем…\"\n },\n \"slider\": {\n \"min\": \"минимум\",\n \"max\": \"максимум\"\n },\n \"sidebar\": {\n \"expand\": \"Развернуть панель\",\n \"collapse\": \"Свернуть панель\"\n },\n \"splitter\": {\n \"label\": \"Изменить размер панелей\"\n },\n \"statistic\": {\n \"trendUp\": \"Рост\",\n \"trendDown\": \"Падение\",\n \"trendFlat\": \"Без изменений\"\n },\n \"pagination\": {\n \"label\": \"Пагинация\",\n \"pageSize\": \"Размер страницы\",\n \"prev\": \"Назад\",\n \"next\": \"Вперёд\",\n \"page\": \"Страница {n}\",\n \"first\": \"Первая страница\",\n \"last\": \"Последняя страница\",\n \"jumpTo\": \"Перейти к\",\n \"total\": \"{from}–{to} из {total}\",\n \"status\": \"Страница {page} из {count}\"\n },\n \"navbar\": {\n \"openMenu\": \"Открыть меню\"\n },\n \"responseError\": {\n \"networkTitle\": \"Нет связи с сервером\",\n \"networkMessage\": \"Проверьте подключение к интернету и попробуйте ещё раз.\",\n \"abortedTitle\": \"Запрос отменён\",\n \"abortedMessage\": \"Запрос был отменён.\",\n \"validationTitle\": \"Проверьте данные\",\n \"validationMessage\": \"Сервер вернул ошибку валидации. Исправьте поля и попробуйте снова.\",\n \"clientTitle\": \"Ошибка запроса\",\n \"clientMessage\": \"Сервер отклонил запрос.\",\n \"serverTitle\": \"Ошибка сервера\",\n \"serverMessage\": \"Произошла ошибка на сервере. Попробуйте ещё раз.\",\n \"unknownTitle\": \"Что-то пошло не так\",\n \"unknownMessage\": \"Не удалось выполнить операцию. Попробуйте ещё раз.\",\n \"statusLabel\": \"HTTP {status}\",\n \"retryLabel\": \"Повторить\",\n \"dismissLabel\": \"Скрыть\"\n },\n \"colors\": {\n \"neutral\": \"Нейтральный\",\n \"success\": \"Успех\",\n \"warning\": \"Предупреждение\",\n \"danger\": \"Опасность\",\n \"info\": \"Инфо\",\n \"primary\": \"Primary\",\n \"slate\": \"Сланцевый\",\n \"azure\": \"Лазурный\"\n },\n \"drawer\": {\n \"title\": \"Панель\"\n },\n \"modal\": {\n \"title\": \"Диалог\"\n },\n \"numberInput\": {\n \"increase\": \"Увеличить\",\n \"decrease\": \"Уменьшить\"\n },\n \"toaster\": {\n \"dismiss\": \"Закрыть\",\n \"region\": \"Уведомления\"\n },\n \"inputTag\": {\n \"removeTag\": \"Удалить тег\",\n \"removeTagNamed\": \"Удалить тег {tag}\",\n \"clearAll\": \"Удалить все теги\",\n \"added\": \"Добавлен тег: {tag}\",\n \"addedMany\": \"Добавлено тегов: {count}\",\n \"removed\": \"Удалён тег: {tag}\",\n \"cleared\": \"Все теги удалены\",\n \"limitReached\": \"Достигнут предел тегов\"\n },\n \"fileUpload\": {\n \"placeholder\": \"Перетащите файлы сюда или нажмите для выбора\",\n \"progress\": \"Прогресс загрузки\",\n \"uploading\": \"Загрузка…\",\n \"success\": \"Загрузка завершена\",\n \"error\": \"Не удалось загрузить\",\n \"remove\": \"Убрать {fileName}\",\n \"retry\": \"Повторить загрузку\",\n \"retryFile\": \"Повторить {fileName}\",\n \"abortFile\": \"Отменить загрузку {fileName}\",\n \"statusPending\": \"В очереди\",\n \"statusUploading\": \"Загружается\",\n \"statusSuccess\": \"Загружен\",\n \"statusError\": \"Ошибка\"\n },\n \"formFile\": {\n \"upload\": \"Загрузить файл\",\n \"change\": \"Заменить файл\",\n \"remove\": \"Удалить\",\n \"clearAll\": \"Очистить всё\",\n \"placeholder\": \"Файлы не выбраны\",\n \"validateError\": \"Не удалось проверить перетащенные файлы\",\n \"removeFile\": \"Убрать {fileName}\"\n },\n \"imageViewer\": {\n \"close\": \"Закрыть просмотрщик\",\n \"prev\": \"Предыдущее изображение\",\n \"next\": \"Следующее изображение\",\n \"zoomIn\": \"Увеличить\",\n \"zoomOut\": \"Уменьшить\",\n \"resetZoom\": \"Сбросить масштаб\",\n \"zoomToNatural\": \"Натуральный размер (1:1)\",\n \"rotateLeft\": \"Повернуть влево\",\n \"rotateRight\": \"Повернуть вправо\",\n \"download\": \"Скачать изображение\",\n \"empty\": \"Нет изображения\",\n \"label\": \"Просмотр изображений\",\n \"position\": \"Изображение {index} из {total}\"\n },\n \"formErrorBanner\": {\n \"validationTitle\": \"Проверьте поля формы\",\n \"validationMessage\": \"Сервер отклонил данные. Исправьте выделенные поля и попробуйте снова.\",\n \"clientTitle\": \"Не удалось сохранить\",\n \"serverTitle\": \"Ошибка сохранения\",\n \"serverMessage\": \"Не удалось сохранить данные. Попробуйте позже.\"\n },\n \"uploadErrorBanner\": {\n \"networkTitle\": \"Не удалось загрузить файлы\",\n \"networkMessage\": \"Проверьте подключение и попробуйте снова.\",\n \"validationTitle\": \"Файлы не прошли проверку\",\n \"validationMessage\": \"Некоторые файлы не соответствуют требованиям.\",\n \"serverTitle\": \"Ошибка загрузки на сервере\",\n \"serverMessage\": \"Сервер не смог обработать загрузку. Попробуйте снова.\"\n },\n \"input\": {\n \"clear\": \"Очистить\",\n \"showPassword\": \"Показать пароль\",\n \"hidePassword\": \"Скрыть пароль\",\n \"limitReached\": \"Достигнут предел символов\"\n },\n \"textarea\": {\n \"lines\": {\n \"one\": \"{n} строка\",\n \"few\": \"{n} строки\",\n \"many\": \"{n} строк\",\n \"other\": \"{n} строки\"\n }\n },\n \"rating\": {\n \"label\": \"Оценка\",\n \"valueText\": \"{value} из {max}\",\n \"valueTextWithLabel\": \"{value} из {max}, {label}\"\n },\n \"commandPalette\": {\n \"label\": \"Палитра команд\",\n \"placeholder\": \"Введите команду или запрос…\",\n \"empty\": \"Ничего не найдено\",\n \"recent\": \"Недавние\",\n \"loading\": \"Загрузка…\"\n },\n \"fileValidation\": {\n \"accept\": \"Файл «{fileName}» не подходит под accept=\\\"{accept}\\\"\",\n \"extension\": \"У файла «{fileName}» недопустимое расширение «.{extension}»\",\n \"mimeType\": \"У файла «{fileName}» недопустимый тип «{mimeType}»\",\n \"mimeTypeFallback\": \"У файла «{fileName}» неопределённый тип «{mimeType}»\",\n \"maxFileSize\": \"Файл «{fileName}» слишком большой ({size} байт), максимум {maxBytes}\",\n \"maxCount\": \"Выбрано слишком много файлов ({count}), максимум — {maxCount}\",\n \"maxTotalSize\": \"Суммарный размер файлов слишком большой ({total} байт), максимум {maxBytes}\"\n },\n \"kbd\": {\n \"command\": \"Command\",\n \"option\": \"Option\",\n \"shift\": \"Shift\",\n \"control\": \"Control\",\n \"enter\": \"Enter\",\n \"escape\": \"Escape\",\n \"tab\": \"Tab\",\n \"backspace\": \"Backspace\",\n \"delete\": \"Delete\",\n \"pageUp\": \"Page Up\",\n \"pageDown\": \"Page Down\",\n \"arrowUp\": \"стрелка вверх\",\n \"arrowDown\": \"стрелка вниз\",\n \"arrowLeft\": \"стрелка влево\",\n \"arrowRight\": \"стрелка вправо\",\n \"then\": \"затем\"\n },\n \"link\": {\n \"opensInNewTab\": \"откроется в новой вкладке\"\n },\n \"colorPicker\": {\n \"panelLabel\": \"Выбор цвета\",\n \"hue\": \"Оттенок\",\n \"saturation\": \"Насыщенность\",\n \"lightness\": \"Светлота\",\n \"opacity\": \"Непрозрачность\",\n \"hexLabel\": \"Значение hex\",\n \"presetsLabel\": \"Палитра\",\n \"swatchLabel\": \"Цвет {color}\"\n },\n \"collapse\": {\n \"empty\": \"Пока пусто\"\n },\n \"list\": {\n \"empty\": \"Пока пусто\"\n },\n \"button\": {\n \"loading\": \"Загрузка…\"\n },\n \"table\": {\n \"empty\": \"Пока пусто\"\n },\n \"timeline\": {\n \"empty\": \"Пока пусто\"\n },\n \"tabs\": {\n \"empty\": \"Пока пусто\",\n \"close\": \"Закрыть вкладку\"\n },\n \"bottomNav\": {\n \"label\": \"Нижняя навигация\",\n \"badge\": \"{count} новых\"\n },\n \"emptyState\": {\n \"title\": \"Пока пусто\"\n },\n \"sortable\": {\n \"label\": \"Сортируемый список\",\n \"handle\": \"Переставить\",\n \"empty\": \"Пока нечего сортировать\",\n \"grabbed\": \"Взято. Позиция {position} из {count}\",\n \"moved\": \"Позиция {position} из {count}\",\n \"dropped\": \"Отпущено на позиции {position} из {count}\",\n \"cancelled\": \"Перенос отменён\"\n },\n \"codeBlock\": {\n \"copy\": \"Скопировать код\",\n \"copied\": \"Код скопирован\",\n \"copyFailed\": \"Не удалось скопировать код\"\n },\n \"jsonViewer\": {\n \"search\": \"Поиск по ключу или значению\",\n \"expandAll\": \"Раскрыть всё\",\n \"collapseAll\": \"Свернуть всё\",\n \"copy\": \"Скопировать значение\",\n \"copied\": \"Значение скопировано\",\n \"copyFailed\": \"Не удалось скопировать значение\",\n \"moreItems\": \"ещё\"\n }\n}\n"],"mappings":""}
@@ -3,36 +3,10 @@ import { n as useGranularityTranslations } from "../../../chunks/granularityI18n
3
3
  import { t as check_default } from "../../../chunks/check-BZI24Qs4.js";
4
4
  import { t as GrButton_default } from "../../GrButton/chunks/GrButton-Bw2AH4uX.js";
5
5
  import { useAnnouncer } from "../../../composables/useAnnouncer.js";
6
- import { a as codeBlockHookClass, c as codeBlockPaddings, d as codeBlockTextSizes, f as codeBlockWrapClass, i as codeBlockGutterClass, l as codeBlockRootClass, o as codeBlockNowrapClass, p as codeTokenClass, r as codeBlockCopyClass, s as codeBlockNumberedClass, u as codeBlockScrollClass } from "../../../chunks/config-B_qi4Fma2.js";
7
- import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, defineComponent, markRaw, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, ref, renderList, resolveDynamicComponent, toDisplayString, unref, withCtx } from "vue";
8
- import '../styles.css';//#region ~icons/lucide/copy
9
- var _hoisted_1$1 = {
10
- viewBox: "0 0 24 24",
11
- width: "1.2em",
12
- height: "1.2em"
13
- };
14
- function render$1(_ctx, _cache) {
15
- return openBlock(), createElementBlock("svg", _hoisted_1$1, [..._cache[0] || (_cache[0] = [createElementVNode("g", {
16
- fill: "none",
17
- stroke: "currentColor",
18
- "stroke-linecap": "round",
19
- "stroke-linejoin": "round",
20
- "stroke-width": "2"
21
- }, [createElementVNode("rect", {
22
- width: "14",
23
- height: "14",
24
- x: "8",
25
- y: "8",
26
- rx: "2",
27
- ry: "2"
28
- }), createElementVNode("path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" })], -1)])]);
29
- }
30
- var copy_default = markRaw({
31
- name: "lucide-copy",
32
- render: render$1
33
- });
34
- //#endregion
35
- //#region src/components/GrCodeBlock/serializeCode.ts
6
+ import { t as copy_default } from "../../../chunks/copy-DChChcq-.js";
7
+ import { a as codeBlockHookClass, c as codeBlockPaddings, d as codeBlockTextSizes, f as codeBlockWrapClass, i as codeBlockGutterClass, l as codeBlockRootClass, o as codeBlockNowrapClass, p as codeTokenClass, r as codeBlockCopyClass, s as codeBlockNumberedClass, u as codeBlockScrollClass } from "../../../chunks/config-B_qi4Fma.js";
8
+ import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, defineComponent, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, ref, renderList, resolveDynamicComponent, toDisplayString, unref, withCtx } from "vue";
9
+ import '../styles.css';//#region src/components/GrCodeBlock/serializeCode.ts
36
10
  /**
37
11
  * Приведение произвольного значения к тексту для показа.
38
12
  *
@@ -1 +1 @@
1
- {"version":3,"file":"GrCodeBlock-CW3zY1ee.js","names":[],"sources":["../../../../src/components/GrCodeBlock/serializeCode.ts","../../../../src/components/GrCodeBlock/tokenizeJson.ts","../../../../src/components/GrCodeBlock/GrCodeBlock.vue","../../../../src/components/GrCodeBlock/GrCodeBlock.vue"],"sourcesContent":["/**\n * Приведение произвольного значения к тексту для показа.\n *\n * Компонент показывает данные из БД, и они бывают любыми: `unknown`-поле ответа\n * модели, `meta` запуска, тело ошибки. Поэтому сериализация обязана пережить всё\n * — уронить страницу она права не имеет.\n */\n\n/** Маркер повторной ссылки. Показывается вместо ветки, которая уже встречалась выше. */\nexport const CIRCULAR_MARKER = '[Circular]'\n\n/** Что печатается, когда сериализовать не удалось вовсе. */\nexport const UNSERIALIZABLE_MARKER = '[Unserializable]'\n\n/**\n * Замена значений, на которых штатный `JSON.stringify` спотыкается.\n *\n * Маркером помечается **повторная ссылка**, а не только настоящий цикл: объект,\n * положенный в дерево дважды, тоже её получит. Отличить одно от другого можно\n * лишь стеком предков, а `replacer` его не отдаёт — узнать, что обход вышел из\n * ветки, изнутри нечем. Размен осознанный: показать `[Circular]` там, где данные\n * просто переиспользуют объект, — неточность; зациклиться на настоящем цикле —\n * зависшая вкладка.\n */\nfunction createSafeReplacer(): (key: string, value: unknown) => unknown {\n const seen = new WeakSet<object>()\n\n return function safeReplacer(_key: string, value: unknown): unknown {\n // `BigInt` не имеет представления в JSON, и `stringify` на нём бросает.\n if (typeof value === 'bigint') return `${value}n`\n\n if (typeof value !== 'object' || value === null) return value\n\n if (seen.has(value)) return CIRCULAR_MARKER\n\n seen.add(value)\n return value\n }\n}\n\n/**\n * Значение → текст.\n *\n * Строка проходит как есть: это уже готовый текст, и оборачивать его в кавычки\n * значило бы показать не то, что пришло. `undefined` даёт пустую строку —\n * показывать нечего. Всё остальное сериализуется с отступом.\n */\nexport function serializeCode(value: unknown, indent = 2): string {\n if (typeof value === 'string') return value\n if (value === undefined) return ''\n\n try {\n const serialized = JSON.stringify(value, createSafeReplacer(), indent)\n // `stringify` отдаёт `undefined` на функции и символе — печатать «undefined»\n // строкой было бы враньём про содержимое.\n return serialized ?? ''\n }\n catch {\n return UNSERIALIZABLE_MARKER\n }\n}\n","/**\n * Токенизация JSON для подсветки.\n *\n * Своя, а не библиотечная: тянуть highlight.js ради четырёх ролей несоразмерно\n * — компонент показывает JSON и обычный текст, а не произвольные языки.\n *\n * Разбор посимвольный и **ничего не теряет**: пробелы, переносы и запятые тоже\n * становятся токенами. Отсюда инвариант, который держит тест: склейка `text`\n * всех токенов равна исходной строке. Любая потеря символа — сдвиг разметки.\n */\n\nexport type GrCodeTokenKind =\n /** Имя поля объекта: строка, за которой идёт двоеточие. */\n | 'key'\n | 'string'\n | 'number'\n /** `true`, `false`, `null`. */\n | 'literal'\n /** Скобки, запятые, двоеточия. */\n | 'punctuation'\n /** Пробелы, переносы и всё, что не опознано. */\n | 'plain'\n\nexport interface GrCodeToken {\n text: string\n kind: GrCodeTokenKind\n}\n\nconst PUNCTUATION = new Set(['{', '}', '[', ']', ',', ':'])\nconst LITERALS = ['true', 'false', 'null'] as const\n\n/** Строка от открывающей кавычки до закрывающей, с учётом экранирования. */\nfunction readString(source: string, start: number): number {\n let index = start + 1\n\n while (index < source.length) {\n const char = source[index]\n\n // Экранированная кавычка не закрывает строку: `\"a\\\":b\"` — одно значение,\n // а не строка плюс мусор.\n if (char === '\\\\') {\n index += 2\n continue\n }\n\n index += 1\n if (char === '\"') break\n }\n\n return index\n}\n\n/** Число целиком: знак, дробная часть, экспонента. */\nfunction readNumber(source: string, start: number): number {\n let index = start\n\n if (source[index] === '-') index += 1\n while (index < source.length && /[\\d.eE+-]/.test(source[index])) index += 1\n\n return index\n}\n\n/** Первый непробельный символ после позиции — по нему строка отличается от ключа. */\nfunction nextMeaningful(source: string, from: number): string | undefined {\n for (let index = from; index < source.length; index += 1) {\n if (!/\\s/.test(source[index])) return source[index]\n }\n\n return undefined\n}\n\nexport function tokenizeJson(source: string): GrCodeToken[] {\n const tokens: GrCodeToken[] = []\n let index = 0\n let plainStart = 0\n\n const flushPlain = (until: number): void => {\n if (until > plainStart) tokens.push({ text: source.slice(plainStart, until), kind: 'plain' })\n }\n\n while (index < source.length) {\n const char = source[index]\n\n if (char === '\"') {\n flushPlain(index)\n const end = readString(source, index)\n // Ключ от строки отличает только идущее следом двоеточие.\n const kind: GrCodeTokenKind = nextMeaningful(source, end) === ':' ? 'key' : 'string'\n tokens.push({ text: source.slice(index, end), kind })\n index = end\n plainStart = index\n continue\n }\n\n if (PUNCTUATION.has(char)) {\n flushPlain(index)\n tokens.push({ text: char, kind: 'punctuation' })\n index += 1\n plainStart = index\n continue\n }\n\n if (char === '-' || (char >= '0' && char <= '9')) {\n flushPlain(index)\n const end = readNumber(source, index)\n tokens.push({ text: source.slice(index, end), kind: 'number' })\n index = end\n plainStart = index\n continue\n }\n\n const literal = LITERALS.find(candidate => source.startsWith(candidate, index))\n if (literal) {\n flushPlain(index)\n tokens.push({ text: literal, kind: 'literal' })\n index += literal.length\n plainStart = index\n continue\n }\n\n index += 1\n }\n\n flushPlain(source.length)\n\n return tokens\n}\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\n\nimport GrButton from '../GrButton/GrButton.vue'\nimport { useAnnouncer } from '../../composables/useAnnouncer'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\n\nimport IconCheck from '~icons/lucide/check'\nimport IconCopy from '~icons/lucide/copy'\n\nimport { serializeCode } from './serializeCode'\nimport { tokenizeJson, type GrCodeToken } from './tokenizeJson'\nimport {\n codeBlockCopyClass,\n codeBlockGutterClass,\n codeBlockHookClass,\n codeBlockNowrapClass,\n codeBlockNumberedClass as numberedClass,\n codeBlockPaddings,\n codeBlockRootClass,\n codeBlockScrollClass,\n codeBlockSurfaceClass,\n codeBlockTextSizes,\n codeBlockWrapClass,\n codeTokenClass,\n} from './grCodeBlockStyles'\n\nimport type { GrComponentSize } from '../shared/sizes'\n\nexport type { GrCodeToken, GrCodeTokenKind } from './tokenizeJson'\n\n/** Язык подсветки. Кроме JSON компонент ничего не разбирает — и не должен. */\nexport type GrCodeBlockLanguage = 'json' | 'text'\n\n/**\n * Блок сырого текста или JSON: моноширинный, с подсветкой, номерами строк и\n * копированием в буфер.\n *\n * Показывает, а не редактирует и не сворачивает узлы. Свёртка по узлам — задача\n * `GrJsonViewer`, дерево выбора — `GrTree`.\n */\nexport interface GrCodeBlockProps {\n /** Строка или значение: объект сериализуется с отступом. */\n code: unknown\n language?: GrCodeBlockLanguage\n /** Высота, после которой включается вертикальный скролл. */\n maxHeight?: string | number\n /** Кнопка копирования. Без защищённого контекста не рисуется вовсе. */\n copyable?: boolean\n /** Переносить длинные строки вместо горизонтальной прокрутки. */\n wrap?: boolean\n lineNumbers?: boolean\n /** Имя области прокрутки. Безымянную скринридер объявляет просто «регион». */\n ariaLabel?: string\n size?: GrComponentSize\n}\n\nexport interface GrCodeBlockEmits {\n /** Текст скопирован. Тост показывает потребитель: у блока своего места для него нет. */\n (e: 'copy', code: string): void\n}\n\nconst props = withDefaults(defineProps<GrCodeBlockProps>(), {\n // Дефолты живут в резолверах ниже: Vue подставил бы свой раньше, чем компонент\n // заглянет в `GrConfigProvider`.\n language: 'json',\n maxHeight: undefined,\n copyable: undefined,\n wrap: undefined,\n lineNumbers: undefined,\n ariaLabel: undefined,\n size: undefined,\n})\n\nconst emit = defineEmits<GrCodeBlockEmits>()\n\nconst { t } = useGranularityTranslations()\nconst { announce } = useAnnouncer()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrCodeBlock' })\nconst resolvedCopyable = useGrComponentProp('GrCodeBlock', 'copyable', () => props.copyable, true)\nconst resolvedWrap = useGrComponentProp('GrCodeBlock', 'wrap', () => props.wrap, false)\nconst resolvedLineNumbers = useGrComponentProp('GrCodeBlock', 'lineNumbers', () => props.lineNumbers, false)\n\n/** Единственный источник текста: он же рендерится, он же уходит в буфер. */\nconst source = computed(() => serializeCode(props.code))\n\n/**\n * Разбор идёт по строкам, а не по всему тексту: номер строки — CSS-счётчик, а\n * счётчику нужен элемент на строку. JSON-строка перенос не содержит (он в ней\n * экранирован), поэтому построчная токенизация ничего не рвёт.\n */\nconst lines = computed<GrCodeToken[][]>(() => {\n const rows = source.value.split('\\n')\n\n if (props.language !== 'json') return rows.map(row => [{ text: row, kind: 'plain' as const }])\n\n return rows.map(row => tokenizeJson(row))\n})\n\n/**\n * Блок — скроллер по конструкции, а не по замеру. Замер здесь означал бы\n * мигающую остановку `Tab` на каждой смене данных и загрузке шрифта; ни один\n * компонент пакета так не делает.\n */\nconst isScroller = computed(() => props.maxHeight !== undefined || !resolvedWrap.value)\n\nconst scrollStyle = computed(() => {\n if (props.maxHeight === undefined) return undefined\n\n return { maxHeight: typeof props.maxHeight === 'number' ? `${props.maxHeight}px` : props.maxHeight }\n})\n\nconst preClass = computed(() => [\n codeBlockHookClass,\n codeBlockSurfaceClass,\n codeBlockPaddings[resolvedSize.value],\n codeBlockTextSizes[resolvedSize.value],\n resolvedWrap.value ? codeBlockWrapClass : codeBlockNowrapClass,\n isScroller.value ? codeBlockScrollClass : '',\n].filter(Boolean).join(' '))\n\n/**\n * Наличие буфера уточняется после монтирования: `navigator` в теле `setup` либо\n * роняет серверный рендер, либо расходится с ним. До этого момента кнопки нет —\n * ровно то же, что отдаёт сервер, поэтому гидрация совпадает.\n */\nconst canCopy = ref(false)\n\nonMounted(() => {\n canCopy.value = typeof navigator !== 'undefined' && typeof navigator.clipboard?.writeText === 'function'\n})\n\nconst showCopy = computed(() => resolvedCopyable.value && canCopy.value)\n\nconst copied = ref(false)\nlet copiedTimer: number | null = null\n\nfunction resetCopiedTimer(): void {\n if (copiedTimer !== null) {\n clearTimeout(copiedTimer)\n copiedTimer = null\n }\n}\n\nonBeforeUnmount(resetCopiedTimer)\n\nasync function copy(): Promise<void> {\n try {\n // Копируется исходный текст, а не разметка: номера строк — CSS-счётчик,\n // а мягкие переносы принадлежат показу, и вставить их обратно нельзя.\n await navigator.clipboard.writeText(source.value)\n }\n catch {\n announce(t('gr.codeBlock.copyFailed', 'Could not copy code'))\n return\n }\n\n copied.value = true\n resetCopiedTimer()\n copiedTimer = window.setTimeout(() => {\n copied.value = false\n copiedTimer = null\n }, 1200)\n\n announce(t('gr.codeBlock.copied', 'Code copied'))\n emit('copy', source.value)\n}\n\nconst copyLabel = computed(() => (\n copied.value ? t('gr.codeBlock.copied', 'Code copied') : t('gr.codeBlock.copy', 'Copy code')\n))\n</script>\n\n<template>\n <div data-gr-code-block :class=\"[codeBlockRootClass, showCopy ? codeBlockGutterClass : '']\">\n <GrButton\n v-if=\"showCopy\"\n data-gr-code-block-copy\n variant=\"ghost\"\n size=\"xs\"\n square\n :class=\"codeBlockCopyClass\"\n :aria-label=\"copyLabel\"\n :title=\"copyLabel\"\n @click=\"copy\"\n >\n <component :is=\"copied ? IconCheck : IconCopy\" class=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n </GrButton>\n\n <!--\n Внутри `<pre>` пробелы значимы, и компилятор Vue их здесь не схлопывает:\n любой перенос между тегами стал бы пустой строкой в коде. Поэтому\n содержимое блока записано без переносов — строки разделяет `display: block`\n у `.gr-code-block__line`, а не текстовый `\\n`.\n -->\n <pre\n data-gr-code-block-scroll\n :class=\"[preClass, resolvedLineNumbers ? numberedClass : '']\"\n :style=\"scrollStyle\"\n :tabindex=\"isScroller ? 0 : undefined\"\n :role=\"ariaLabel ? 'region' : undefined\"\n :aria-label=\"ariaLabel\"\n ><code data-gr-code-block-code><span v-for=\"(tokens, index) in lines\" :key=\"index\" data-gr-code-block-line class=\"gr-code-block__line\"><span v-for=\"(token, tokenIndex) in tokens\" :key=\"tokenIndex\" :class=\"codeTokenClass[token.kind]\">{{ token.text }}</span></span></code></pre>\n </div>\n</template>\n\n<style>\n/*\n * Номер строки — CSS-счётчик, а не текст в разметке. Разница не косметическая:\n * узел со `<span>2</span>` попадает в выделение мышью, и в буфер уезжает то,\n * что нельзя вставить обратно как код. У `::before` содержимого в DOM нет.\n *\n * Стиль глобальный, а не scoped: правило адресует псевдоэлемент строки, и\n * scoped-атрибут пришлось бы вешать на каждый из них.\n */\n:where(.gr-code-block__line) {\n display: block;\n}\n\n:where(.gr-code-block--numbered) {\n counter-reset: gr-code-line;\n}\n\n/*\n * Висячий отступ: строка сдвинута на ширину колонки номеров, а первая строка\n * возвращена назад отрицательным `text-indent` — туда и встаёт номер. Без этого\n * перенесённая при `wrap` строка начиналась бы под номером, а не под кодом.\n */\n:where(.gr-code-block--numbered .gr-code-block__line) {\n padding-left: calc(var(--gr-code-block-line-number-width, 2.5em) + 1em);\n text-indent: calc(-1 * (var(--gr-code-block-line-number-width, 2.5em) + 1em));\n}\n\n:where(.gr-code-block--numbered .gr-code-block__line)::before {\n counter-increment: gr-code-line;\n content: counter(gr-code-line);\n display: inline-block;\n width: var(--gr-code-block-line-number-width, 2.5em);\n margin-right: 1em;\n text-align: right;\n text-indent: 0;\n color: var(--gr-code-block-line-number, var(--gr-muted-fg));\n user-select: none;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\n\nimport GrButton from '../GrButton/GrButton.vue'\nimport { useAnnouncer } from '../../composables/useAnnouncer'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\n\nimport IconCheck from '~icons/lucide/check'\nimport IconCopy from '~icons/lucide/copy'\n\nimport { serializeCode } from './serializeCode'\nimport { tokenizeJson, type GrCodeToken } from './tokenizeJson'\nimport {\n codeBlockCopyClass,\n codeBlockGutterClass,\n codeBlockHookClass,\n codeBlockNowrapClass,\n codeBlockNumberedClass as numberedClass,\n codeBlockPaddings,\n codeBlockRootClass,\n codeBlockScrollClass,\n codeBlockSurfaceClass,\n codeBlockTextSizes,\n codeBlockWrapClass,\n codeTokenClass,\n} from './grCodeBlockStyles'\n\nimport type { GrComponentSize } from '../shared/sizes'\n\nexport type { GrCodeToken, GrCodeTokenKind } from './tokenizeJson'\n\n/** Язык подсветки. Кроме JSON компонент ничего не разбирает — и не должен. */\nexport type GrCodeBlockLanguage = 'json' | 'text'\n\n/**\n * Блок сырого текста или JSON: моноширинный, с подсветкой, номерами строк и\n * копированием в буфер.\n *\n * Показывает, а не редактирует и не сворачивает узлы. Свёртка по узлам — задача\n * `GrJsonViewer`, дерево выбора — `GrTree`.\n */\nexport interface GrCodeBlockProps {\n /** Строка или значение: объект сериализуется с отступом. */\n code: unknown\n language?: GrCodeBlockLanguage\n /** Высота, после которой включается вертикальный скролл. */\n maxHeight?: string | number\n /** Кнопка копирования. Без защищённого контекста не рисуется вовсе. */\n copyable?: boolean\n /** Переносить длинные строки вместо горизонтальной прокрутки. */\n wrap?: boolean\n lineNumbers?: boolean\n /** Имя области прокрутки. Безымянную скринридер объявляет просто «регион». */\n ariaLabel?: string\n size?: GrComponentSize\n}\n\nexport interface GrCodeBlockEmits {\n /** Текст скопирован. Тост показывает потребитель: у блока своего места для него нет. */\n (e: 'copy', code: string): void\n}\n\nconst props = withDefaults(defineProps<GrCodeBlockProps>(), {\n // Дефолты живут в резолверах ниже: Vue подставил бы свой раньше, чем компонент\n // заглянет в `GrConfigProvider`.\n language: 'json',\n maxHeight: undefined,\n copyable: undefined,\n wrap: undefined,\n lineNumbers: undefined,\n ariaLabel: undefined,\n size: undefined,\n})\n\nconst emit = defineEmits<GrCodeBlockEmits>()\n\nconst { t } = useGranularityTranslations()\nconst { announce } = useAnnouncer()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrCodeBlock' })\nconst resolvedCopyable = useGrComponentProp('GrCodeBlock', 'copyable', () => props.copyable, true)\nconst resolvedWrap = useGrComponentProp('GrCodeBlock', 'wrap', () => props.wrap, false)\nconst resolvedLineNumbers = useGrComponentProp('GrCodeBlock', 'lineNumbers', () => props.lineNumbers, false)\n\n/** Единственный источник текста: он же рендерится, он же уходит в буфер. */\nconst source = computed(() => serializeCode(props.code))\n\n/**\n * Разбор идёт по строкам, а не по всему тексту: номер строки — CSS-счётчик, а\n * счётчику нужен элемент на строку. JSON-строка перенос не содержит (он в ней\n * экранирован), поэтому построчная токенизация ничего не рвёт.\n */\nconst lines = computed<GrCodeToken[][]>(() => {\n const rows = source.value.split('\\n')\n\n if (props.language !== 'json') return rows.map(row => [{ text: row, kind: 'plain' as const }])\n\n return rows.map(row => tokenizeJson(row))\n})\n\n/**\n * Блок — скроллер по конструкции, а не по замеру. Замер здесь означал бы\n * мигающую остановку `Tab` на каждой смене данных и загрузке шрифта; ни один\n * компонент пакета так не делает.\n */\nconst isScroller = computed(() => props.maxHeight !== undefined || !resolvedWrap.value)\n\nconst scrollStyle = computed(() => {\n if (props.maxHeight === undefined) return undefined\n\n return { maxHeight: typeof props.maxHeight === 'number' ? `${props.maxHeight}px` : props.maxHeight }\n})\n\nconst preClass = computed(() => [\n codeBlockHookClass,\n codeBlockSurfaceClass,\n codeBlockPaddings[resolvedSize.value],\n codeBlockTextSizes[resolvedSize.value],\n resolvedWrap.value ? codeBlockWrapClass : codeBlockNowrapClass,\n isScroller.value ? codeBlockScrollClass : '',\n].filter(Boolean).join(' '))\n\n/**\n * Наличие буфера уточняется после монтирования: `navigator` в теле `setup` либо\n * роняет серверный рендер, либо расходится с ним. До этого момента кнопки нет —\n * ровно то же, что отдаёт сервер, поэтому гидрация совпадает.\n */\nconst canCopy = ref(false)\n\nonMounted(() => {\n canCopy.value = typeof navigator !== 'undefined' && typeof navigator.clipboard?.writeText === 'function'\n})\n\nconst showCopy = computed(() => resolvedCopyable.value && canCopy.value)\n\nconst copied = ref(false)\nlet copiedTimer: number | null = null\n\nfunction resetCopiedTimer(): void {\n if (copiedTimer !== null) {\n clearTimeout(copiedTimer)\n copiedTimer = null\n }\n}\n\nonBeforeUnmount(resetCopiedTimer)\n\nasync function copy(): Promise<void> {\n try {\n // Копируется исходный текст, а не разметка: номера строк — CSS-счётчик,\n // а мягкие переносы принадлежат показу, и вставить их обратно нельзя.\n await navigator.clipboard.writeText(source.value)\n }\n catch {\n announce(t('gr.codeBlock.copyFailed', 'Could not copy code'))\n return\n }\n\n copied.value = true\n resetCopiedTimer()\n copiedTimer = window.setTimeout(() => {\n copied.value = false\n copiedTimer = null\n }, 1200)\n\n announce(t('gr.codeBlock.copied', 'Code copied'))\n emit('copy', source.value)\n}\n\nconst copyLabel = computed(() => (\n copied.value ? t('gr.codeBlock.copied', 'Code copied') : t('gr.codeBlock.copy', 'Copy code')\n))\n</script>\n\n<template>\n <div data-gr-code-block :class=\"[codeBlockRootClass, showCopy ? codeBlockGutterClass : '']\">\n <GrButton\n v-if=\"showCopy\"\n data-gr-code-block-copy\n variant=\"ghost\"\n size=\"xs\"\n square\n :class=\"codeBlockCopyClass\"\n :aria-label=\"copyLabel\"\n :title=\"copyLabel\"\n @click=\"copy\"\n >\n <component :is=\"copied ? IconCheck : IconCopy\" class=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n </GrButton>\n\n <!--\n Внутри `<pre>` пробелы значимы, и компилятор Vue их здесь не схлопывает:\n любой перенос между тегами стал бы пустой строкой в коде. Поэтому\n содержимое блока записано без переносов — строки разделяет `display: block`\n у `.gr-code-block__line`, а не текстовый `\\n`.\n -->\n <pre\n data-gr-code-block-scroll\n :class=\"[preClass, resolvedLineNumbers ? numberedClass : '']\"\n :style=\"scrollStyle\"\n :tabindex=\"isScroller ? 0 : undefined\"\n :role=\"ariaLabel ? 'region' : undefined\"\n :aria-label=\"ariaLabel\"\n ><code data-gr-code-block-code><span v-for=\"(tokens, index) in lines\" :key=\"index\" data-gr-code-block-line class=\"gr-code-block__line\"><span v-for=\"(token, tokenIndex) in tokens\" :key=\"tokenIndex\" :class=\"codeTokenClass[token.kind]\">{{ token.text }}</span></span></code></pre>\n </div>\n</template>\n\n<style>\n/*\n * Номер строки — CSS-счётчик, а не текст в разметке. Разница не косметическая:\n * узел со `<span>2</span>` попадает в выделение мышью, и в буфер уезжает то,\n * что нельзя вставить обратно как код. У `::before` содержимого в DOM нет.\n *\n * Стиль глобальный, а не scoped: правило адресует псевдоэлемент строки, и\n * scoped-атрибут пришлось бы вешать на каждый из них.\n */\n:where(.gr-code-block__line) {\n display: block;\n}\n\n:where(.gr-code-block--numbered) {\n counter-reset: gr-code-line;\n}\n\n/*\n * Висячий отступ: строка сдвинута на ширину колонки номеров, а первая строка\n * возвращена назад отрицательным `text-indent` — туда и встаёт номер. Без этого\n * перенесённая при `wrap` строка начиналась бы под номером, а не под кодом.\n */\n:where(.gr-code-block--numbered .gr-code-block__line) {\n padding-left: calc(var(--gr-code-block-line-number-width, 2.5em) + 1em);\n text-indent: calc(-1 * (var(--gr-code-block-line-number-width, 2.5em) + 1em));\n}\n\n:where(.gr-code-block--numbered .gr-code-block__line)::before {\n counter-increment: gr-code-line;\n content: counter(gr-code-line);\n display: inline-block;\n width: var(--gr-code-block-line-number-width, 2.5em);\n margin-right: 1em;\n text-align: right;\n text-indent: 0;\n color: var(--gr-code-block-line-number, var(--gr-muted-fg));\n user-select: none;\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AASA,IAAa,kBAAkB;;AAG/B,IAAa,wBAAwB;;;;;;;;;;;AAYrC,SAAS,qBAA+D;CACtE,MAAM,uBAAO,IAAI,QAAgB;CAEjC,OAAO,SAAS,aAAa,MAAc,OAAyB;EAElE,IAAI,OAAO,UAAU,UAAU,OAAO,GAAG,MAAM;EAE/C,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,OAAO;EAExD,IAAI,KAAK,IAAI,KAAK,GAAG,OAAO;EAE5B,KAAK,IAAI,KAAK;EACd,OAAO;CACT;AACF;;;;;;;;AASA,SAAgB,cAAc,OAAgB,SAAS,GAAW;CAChE,IAAI,OAAO,UAAU,UAAU,OAAO;CACtC,IAAI,UAAU,KAAA,GAAW,OAAO;CAEhC,IAAI;EAIF,OAHmB,KAAK,UAAU,OAAO,mBAAmB,GAAG,MAGxD,KAAc;CACvB,QACM;EACJ,OAAO;CACT;AACF;;;AChCA,IAAM,8BAAc,IAAI,IAAI;CAAC;CAAK;CAAK;CAAK;CAAK;CAAK;AAAG,CAAC;AAC1D,IAAM,WAAW;CAAC;CAAQ;CAAS;AAAM;;AAGzC,SAAS,WAAW,QAAgB,OAAuB;CACzD,IAAI,QAAQ,QAAQ;CAEpB,OAAO,QAAQ,OAAO,QAAQ;EAC5B,MAAM,OAAO,OAAO;EAIpB,IAAI,SAAS,MAAM;GACjB,SAAS;GACT;EACF;EAEA,SAAS;EACT,IAAI,SAAS,MAAK;CACpB;CAEA,OAAO;AACT;;AAGA,SAAS,WAAW,QAAgB,OAAuB;CACzD,IAAI,QAAQ;CAEZ,IAAI,OAAO,WAAW,KAAK,SAAS;CACpC,OAAO,QAAQ,OAAO,UAAU,YAAY,KAAK,OAAO,MAAM,GAAG,SAAS;CAE1E,OAAO;AACT;;AAGA,SAAS,eAAe,QAAgB,MAAkC;CACxE,KAAK,IAAI,QAAQ,MAAM,QAAQ,OAAO,QAAQ,SAAS,GACrD,IAAI,CAAC,KAAK,KAAK,OAAO,MAAM,GAAG,OAAO,OAAO;AAIjD;AAEA,SAAgB,aAAa,QAA+B;CAC1D,MAAM,SAAwB,CAAC;CAC/B,IAAI,QAAQ;CACZ,IAAI,aAAa;CAEjB,MAAM,cAAc,UAAwB;EAC1C,IAAI,QAAQ,YAAY,OAAO,KAAK;GAAE,MAAM,OAAO,MAAM,YAAY,KAAK;GAAG,MAAM;EAAQ,CAAC;CAC9F;CAEA,OAAO,QAAQ,OAAO,QAAQ;EAC5B,MAAM,OAAO,OAAO;EAEpB,IAAI,SAAS,MAAK;GAChB,WAAW,KAAK;GAChB,MAAM,MAAM,WAAW,QAAQ,KAAK;GAEpC,MAAM,OAAwB,eAAe,QAAQ,GAAG,MAAM,MAAM,QAAQ;GAC5E,OAAO,KAAK;IAAE,MAAM,OAAO,MAAM,OAAO,GAAG;IAAG;GAAK,CAAC;GACpD,QAAQ;GACR,aAAa;GACb;EACF;EAEA,IAAI,YAAY,IAAI,IAAI,GAAG;GACzB,WAAW,KAAK;GAChB,OAAO,KAAK;IAAE,MAAM;IAAM,MAAM;GAAc,CAAC;GAC/C,SAAS;GACT,aAAa;GACb;EACF;EAEA,IAAI,SAAS,OAAQ,QAAQ,OAAO,QAAQ,KAAM;GAChD,WAAW,KAAK;GAChB,MAAM,MAAM,WAAW,QAAQ,KAAK;GACpC,OAAO,KAAK;IAAE,MAAM,OAAO,MAAM,OAAO,GAAG;IAAG,MAAM;GAAS,CAAC;GAC9D,QAAQ;GACR,aAAa;GACb;EACF;EAEA,MAAM,UAAU,SAAS,MAAK,cAAa,OAAO,WAAW,WAAW,KAAK,CAAC;EAC9E,IAAI,SAAS;GACX,WAAW,KAAK;GAChB,OAAO,KAAK;IAAE,MAAM;IAAS,MAAM;GAAU,CAAC;GAC9C,SAAS,QAAQ;GACjB,aAAa;GACb;EACF;EAEA,SAAS;CACX;CAEA,WAAW,OAAO,MAAM;CAExB,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EC/DA,MAAM,QAAQ;EAYd,MAAM,OAAO;EAEb,MAAM,EAAE,MAAM,2BAA2B;EACzC,MAAM,EAAE,aAAa,aAAa;EAElC,MAAM,eAAe,yBAAyB,MAAM,MAAM,EAAE,WAAW,cAAc,CAAC;EACtF,MAAM,mBAAmB,mBAAmB,eAAe,kBAAkB,MAAM,UAAU,IAAI;EACjG,MAAM,eAAe,mBAAmB,eAAe,cAAc,MAAM,MAAM,KAAK;EACtF,MAAM,sBAAsB,mBAAmB,eAAe,qBAAqB,MAAM,aAAa,KAAK;;EAG3G,MAAM,SAAS,eAAe,cAAc,MAAM,IAAI,CAAC;;;;;;EAOvD,MAAM,QAAQ,eAAgC;GAC5C,MAAM,OAAO,OAAO,MAAM,MAAM,IAAI;GAEpC,IAAI,MAAM,aAAa,QAAQ,OAAO,KAAK,KAAI,QAAO,CAAC;IAAE,MAAM;IAAK,MAAM;GAAiB,CAAC,CAAC;GAE7F,OAAO,KAAK,KAAI,QAAO,aAAa,GAAG,CAAC;EAC1C,CAAC;;;;;;EAOD,MAAM,aAAa,eAAe,MAAM,cAAc,KAAA,KAAa,CAAC,aAAa,KAAK;EAEtF,MAAM,cAAc,eAAe;GACjC,IAAI,MAAM,cAAc,KAAA,GAAW,OAAO,KAAA;GAE1C,OAAO,EAAE,WAAW,OAAO,MAAM,cAAc,WAAW,GAAG,MAAM,UAAU,MAAM,MAAM,UAAU;EACrG,CAAC;EAED,MAAM,WAAW,eAAe;GAC9B;;GAEA,kBAAkB,aAAa;GAC/B,mBAAmB,aAAa;GAChC,aAAa,QAAQ,qBAAqB;GAC1C,WAAW,QAAQ,uBAAuB;EAC5C,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG,CAAC;;;;;;EAO3B,MAAM,UAAU,IAAI,KAAK;EAEzB,gBAAgB;GACd,QAAQ,QAAQ,OAAO,cAAc,eAAe,OAAO,UAAU,WAAW,cAAc;EAChG,CAAC;EAED,MAAM,WAAW,eAAe,iBAAiB,SAAS,QAAQ,KAAK;EAEvE,MAAM,SAAS,IAAI,KAAK;EACxB,IAAI,cAA6B;EAEjC,SAAS,mBAAyB;GAChC,IAAI,gBAAgB,MAAM;IACxB,aAAa,WAAW;IACxB,cAAc;GAChB;EACF;EAEA,gBAAgB,gBAAgB;EAEhC,eAAe,OAAsB;GACnC,IAAI;IAGF,MAAM,UAAU,UAAU,UAAU,OAAO,KAAK;GAClD,QACM;IACJ,SAAS,EAAE,2BAA2B,qBAAqB,CAAC;IAC5D;GACF;GAEA,OAAO,QAAQ;GACf,iBAAiB;GACjB,cAAc,OAAO,iBAAiB;IACpC,OAAO,QAAQ;IACf,cAAc;GAChB,GAAG,IAAI;GAEP,SAAS,EAAE,uBAAuB,aAAa,CAAC;GAChD,KAAK,QAAQ,OAAO,KAAK;EAC3B;EAEA,MAAM,YAAY,eAChB,OAAO,QAAQ,EAAE,uBAAuB,aAAa,IAAI,EAAE,qBAAqB,WAAW,CAC5F;;uBAIC,mBA6BM,OAAA;IA7BD,sBAAA;IAAoB,OAAK,eAAA,CAAG,MAAA,kBAAA,GAAoB,SAAA,QAAW,MAAA,oBAAA,IAAoB,EAAA,CAAA;OAE1E,SAAA,SAAA,UAAA,GADR,YAYW,kBAAA;;IAVT,2BAAA;IACA,SAAQ;IACR,MAAK;IACL,QAAA;IACC,OAAK,eAAE,MAAA,kBAAA,CAAkB;IACzB,cAAY,UAAA;IACZ,OAAO,UAAA;IACP,SAAO;;2BAEgF,EAAA,UAAA,GAAxF,YAAwF,wBAAxE,OAAA,QAAS,MAAA,aAAA,IAAY,MAAA,YAAA,CAAQ,GAAA;KAAE,OAAM;KAAc,eAAY;;;;;;;uCASjF,mBAOoR,OAAA;IANlR,6BAAA;IACC,OAAK,eAAA,CAAG,SAAA,OAAU,MAAA,mBAAA,IAAsB,MAAA,sBAAA,IAAa,EAAA,CAAA;IACrD,OAAK,eAAE,YAAA,KAAW;IAClB,UAAU,WAAA,QAAU,IAAO,KAAA;IAC3B,MAAM,QAAA,YAAS,WAAc,KAAA;IAC7B,cAAY,QAAA;OACd,mBAA6Q,QAA7Q,YAA6Q,EAAA,UAAA,IAAA,GAA/O,mBAAwO,UAAA,MAAA,WAAxM,MAAA,QAAlB,QAAQ,UAAK;wBAA3B,mBAAwO,QAAA;KAAhM,KAAK;KAAO,2BAAA;KAAwB,OAAM;0BAAsB,mBAAyH,UAAA,MAAA,WAArF,SAAtB,OAAO,eAAU;yBAA/B,mBAAyH,QAAA;MAA5E,KAAK;MAAa,OAAK,eAAE,MAAA,cAAA,CAAc,CAAC,MAAM,KAAI;wBAAM,MAAM,IAAI,GAAA,CAAA"}
1
+ {"version":3,"file":"GrCodeBlock-BmXCLgLS.js","names":[],"sources":["../../../../src/components/GrCodeBlock/serializeCode.ts","../../../../src/components/GrCodeBlock/tokenizeJson.ts","../../../../src/components/GrCodeBlock/GrCodeBlock.vue","../../../../src/components/GrCodeBlock/GrCodeBlock.vue"],"sourcesContent":["/**\n * Приведение произвольного значения к тексту для показа.\n *\n * Компонент показывает данные из БД, и они бывают любыми: `unknown`-поле ответа\n * модели, `meta` запуска, тело ошибки. Поэтому сериализация обязана пережить всё\n * — уронить страницу она права не имеет.\n */\n\n/** Маркер повторной ссылки. Показывается вместо ветки, которая уже встречалась выше. */\nexport const CIRCULAR_MARKER = '[Circular]'\n\n/** Что печатается, когда сериализовать не удалось вовсе. */\nexport const UNSERIALIZABLE_MARKER = '[Unserializable]'\n\n/**\n * Замена значений, на которых штатный `JSON.stringify` спотыкается.\n *\n * Маркером помечается **повторная ссылка**, а не только настоящий цикл: объект,\n * положенный в дерево дважды, тоже её получит. Отличить одно от другого можно\n * лишь стеком предков, а `replacer` его не отдаёт — узнать, что обход вышел из\n * ветки, изнутри нечем. Размен осознанный: показать `[Circular]` там, где данные\n * просто переиспользуют объект, — неточность; зациклиться на настоящем цикле —\n * зависшая вкладка.\n */\nfunction createSafeReplacer(): (key: string, value: unknown) => unknown {\n const seen = new WeakSet<object>()\n\n return function safeReplacer(_key: string, value: unknown): unknown {\n // `BigInt` не имеет представления в JSON, и `stringify` на нём бросает.\n if (typeof value === 'bigint') return `${value}n`\n\n if (typeof value !== 'object' || value === null) return value\n\n if (seen.has(value)) return CIRCULAR_MARKER\n\n seen.add(value)\n return value\n }\n}\n\n/**\n * Значение → текст.\n *\n * Строка проходит как есть: это уже готовый текст, и оборачивать его в кавычки\n * значило бы показать не то, что пришло. `undefined` даёт пустую строку —\n * показывать нечего. Всё остальное сериализуется с отступом.\n */\nexport function serializeCode(value: unknown, indent = 2): string {\n if (typeof value === 'string') return value\n if (value === undefined) return ''\n\n try {\n const serialized = JSON.stringify(value, createSafeReplacer(), indent)\n // `stringify` отдаёт `undefined` на функции и символе — печатать «undefined»\n // строкой было бы враньём про содержимое.\n return serialized ?? ''\n }\n catch {\n return UNSERIALIZABLE_MARKER\n }\n}\n","/**\n * Токенизация JSON для подсветки.\n *\n * Своя, а не библиотечная: тянуть highlight.js ради четырёх ролей несоразмерно\n * — компонент показывает JSON и обычный текст, а не произвольные языки.\n *\n * Разбор посимвольный и **ничего не теряет**: пробелы, переносы и запятые тоже\n * становятся токенами. Отсюда инвариант, который держит тест: склейка `text`\n * всех токенов равна исходной строке. Любая потеря символа — сдвиг разметки.\n */\n\nexport type GrCodeTokenKind =\n /** Имя поля объекта: строка, за которой идёт двоеточие. */\n | 'key'\n | 'string'\n | 'number'\n /** `true`, `false`, `null`. */\n | 'literal'\n /** Скобки, запятые, двоеточия. */\n | 'punctuation'\n /** Пробелы, переносы и всё, что не опознано. */\n | 'plain'\n\nexport interface GrCodeToken {\n text: string\n kind: GrCodeTokenKind\n}\n\nconst PUNCTUATION = new Set(['{', '}', '[', ']', ',', ':'])\nconst LITERALS = ['true', 'false', 'null'] as const\n\n/** Строка от открывающей кавычки до закрывающей, с учётом экранирования. */\nfunction readString(source: string, start: number): number {\n let index = start + 1\n\n while (index < source.length) {\n const char = source[index]\n\n // Экранированная кавычка не закрывает строку: `\"a\\\":b\"` — одно значение,\n // а не строка плюс мусор.\n if (char === '\\\\') {\n index += 2\n continue\n }\n\n index += 1\n if (char === '\"') break\n }\n\n return index\n}\n\n/** Число целиком: знак, дробная часть, экспонента. */\nfunction readNumber(source: string, start: number): number {\n let index = start\n\n if (source[index] === '-') index += 1\n while (index < source.length && /[\\d.eE+-]/.test(source[index])) index += 1\n\n return index\n}\n\n/** Первый непробельный символ после позиции — по нему строка отличается от ключа. */\nfunction nextMeaningful(source: string, from: number): string | undefined {\n for (let index = from; index < source.length; index += 1) {\n if (!/\\s/.test(source[index])) return source[index]\n }\n\n return undefined\n}\n\nexport function tokenizeJson(source: string): GrCodeToken[] {\n const tokens: GrCodeToken[] = []\n let index = 0\n let plainStart = 0\n\n const flushPlain = (until: number): void => {\n if (until > plainStart) tokens.push({ text: source.slice(plainStart, until), kind: 'plain' })\n }\n\n while (index < source.length) {\n const char = source[index]\n\n if (char === '\"') {\n flushPlain(index)\n const end = readString(source, index)\n // Ключ от строки отличает только идущее следом двоеточие.\n const kind: GrCodeTokenKind = nextMeaningful(source, end) === ':' ? 'key' : 'string'\n tokens.push({ text: source.slice(index, end), kind })\n index = end\n plainStart = index\n continue\n }\n\n if (PUNCTUATION.has(char)) {\n flushPlain(index)\n tokens.push({ text: char, kind: 'punctuation' })\n index += 1\n plainStart = index\n continue\n }\n\n if (char === '-' || (char >= '0' && char <= '9')) {\n flushPlain(index)\n const end = readNumber(source, index)\n tokens.push({ text: source.slice(index, end), kind: 'number' })\n index = end\n plainStart = index\n continue\n }\n\n const literal = LITERALS.find(candidate => source.startsWith(candidate, index))\n if (literal) {\n flushPlain(index)\n tokens.push({ text: literal, kind: 'literal' })\n index += literal.length\n plainStart = index\n continue\n }\n\n index += 1\n }\n\n flushPlain(source.length)\n\n return tokens\n}\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\n\nimport GrButton from '../GrButton/GrButton.vue'\nimport { useAnnouncer } from '../../composables/useAnnouncer'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\n\nimport IconCheck from '~icons/lucide/check'\nimport IconCopy from '~icons/lucide/copy'\n\nimport { serializeCode } from './serializeCode'\nimport { tokenizeJson, type GrCodeToken } from './tokenizeJson'\nimport {\n codeBlockCopyClass,\n codeBlockGutterClass,\n codeBlockHookClass,\n codeBlockNowrapClass,\n codeBlockNumberedClass as numberedClass,\n codeBlockPaddings,\n codeBlockRootClass,\n codeBlockScrollClass,\n codeBlockSurfaceClass,\n codeBlockTextSizes,\n codeBlockWrapClass,\n codeTokenClass,\n} from './grCodeBlockStyles'\n\nimport type { GrComponentSize } from '../shared/sizes'\n\nexport type { GrCodeToken, GrCodeTokenKind } from './tokenizeJson'\n\n/** Язык подсветки. Кроме JSON компонент ничего не разбирает — и не должен. */\nexport type GrCodeBlockLanguage = 'json' | 'text'\n\n/**\n * Блок сырого текста или JSON: моноширинный, с подсветкой, номерами строк и\n * копированием в буфер.\n *\n * Показывает, а не редактирует и не сворачивает узлы. Свёртка по узлам — задача\n * `GrJsonViewer`, дерево выбора — `GrTree`.\n */\nexport interface GrCodeBlockProps {\n /** Строка или значение: объект сериализуется с отступом. */\n code: unknown\n language?: GrCodeBlockLanguage\n /** Высота, после которой включается вертикальный скролл. */\n maxHeight?: string | number\n /** Кнопка копирования. Без защищённого контекста не рисуется вовсе. */\n copyable?: boolean\n /** Переносить длинные строки вместо горизонтальной прокрутки. */\n wrap?: boolean\n lineNumbers?: boolean\n /** Имя области прокрутки. Безымянную скринридер объявляет просто «регион». */\n ariaLabel?: string\n size?: GrComponentSize\n}\n\nexport interface GrCodeBlockEmits {\n /** Текст скопирован. Тост показывает потребитель: у блока своего места для него нет. */\n (e: 'copy', code: string): void\n}\n\nconst props = withDefaults(defineProps<GrCodeBlockProps>(), {\n // Дефолты живут в резолверах ниже: Vue подставил бы свой раньше, чем компонент\n // заглянет в `GrConfigProvider`.\n language: 'json',\n maxHeight: undefined,\n copyable: undefined,\n wrap: undefined,\n lineNumbers: undefined,\n ariaLabel: undefined,\n size: undefined,\n})\n\nconst emit = defineEmits<GrCodeBlockEmits>()\n\nconst { t } = useGranularityTranslations()\nconst { announce } = useAnnouncer()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrCodeBlock' })\nconst resolvedCopyable = useGrComponentProp('GrCodeBlock', 'copyable', () => props.copyable, true)\nconst resolvedWrap = useGrComponentProp('GrCodeBlock', 'wrap', () => props.wrap, false)\nconst resolvedLineNumbers = useGrComponentProp('GrCodeBlock', 'lineNumbers', () => props.lineNumbers, false)\n\n/** Единственный источник текста: он же рендерится, он же уходит в буфер. */\nconst source = computed(() => serializeCode(props.code))\n\n/**\n * Разбор идёт по строкам, а не по всему тексту: номер строки — CSS-счётчик, а\n * счётчику нужен элемент на строку. JSON-строка перенос не содержит (он в ней\n * экранирован), поэтому построчная токенизация ничего не рвёт.\n */\nconst lines = computed<GrCodeToken[][]>(() => {\n const rows = source.value.split('\\n')\n\n if (props.language !== 'json') return rows.map(row => [{ text: row, kind: 'plain' as const }])\n\n return rows.map(row => tokenizeJson(row))\n})\n\n/**\n * Блок — скроллер по конструкции, а не по замеру. Замер здесь означал бы\n * мигающую остановку `Tab` на каждой смене данных и загрузке шрифта; ни один\n * компонент пакета так не делает.\n */\nconst isScroller = computed(() => props.maxHeight !== undefined || !resolvedWrap.value)\n\nconst scrollStyle = computed(() => {\n if (props.maxHeight === undefined) return undefined\n\n return { maxHeight: typeof props.maxHeight === 'number' ? `${props.maxHeight}px` : props.maxHeight }\n})\n\nconst preClass = computed(() => [\n codeBlockHookClass,\n codeBlockSurfaceClass,\n codeBlockPaddings[resolvedSize.value],\n codeBlockTextSizes[resolvedSize.value],\n resolvedWrap.value ? codeBlockWrapClass : codeBlockNowrapClass,\n isScroller.value ? codeBlockScrollClass : '',\n].filter(Boolean).join(' '))\n\n/**\n * Наличие буфера уточняется после монтирования: `navigator` в теле `setup` либо\n * роняет серверный рендер, либо расходится с ним. До этого момента кнопки нет —\n * ровно то же, что отдаёт сервер, поэтому гидрация совпадает.\n */\nconst canCopy = ref(false)\n\nonMounted(() => {\n canCopy.value = typeof navigator !== 'undefined' && typeof navigator.clipboard?.writeText === 'function'\n})\n\nconst showCopy = computed(() => resolvedCopyable.value && canCopy.value)\n\nconst copied = ref(false)\nlet copiedTimer: number | null = null\n\nfunction resetCopiedTimer(): void {\n if (copiedTimer !== null) {\n clearTimeout(copiedTimer)\n copiedTimer = null\n }\n}\n\nonBeforeUnmount(resetCopiedTimer)\n\nasync function copy(): Promise<void> {\n try {\n // Копируется исходный текст, а не разметка: номера строк — CSS-счётчик,\n // а мягкие переносы принадлежат показу, и вставить их обратно нельзя.\n await navigator.clipboard.writeText(source.value)\n }\n catch {\n announce(t('gr.codeBlock.copyFailed', 'Could not copy code'))\n return\n }\n\n copied.value = true\n resetCopiedTimer()\n copiedTimer = window.setTimeout(() => {\n copied.value = false\n copiedTimer = null\n }, 1200)\n\n announce(t('gr.codeBlock.copied', 'Code copied'))\n emit('copy', source.value)\n}\n\nconst copyLabel = computed(() => (\n copied.value ? t('gr.codeBlock.copied', 'Code copied') : t('gr.codeBlock.copy', 'Copy code')\n))\n</script>\n\n<template>\n <div data-gr-code-block :class=\"[codeBlockRootClass, showCopy ? codeBlockGutterClass : '']\">\n <GrButton\n v-if=\"showCopy\"\n data-gr-code-block-copy\n variant=\"ghost\"\n size=\"xs\"\n square\n :class=\"codeBlockCopyClass\"\n :aria-label=\"copyLabel\"\n :title=\"copyLabel\"\n @click=\"copy\"\n >\n <component :is=\"copied ? IconCheck : IconCopy\" class=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n </GrButton>\n\n <!--\n Внутри `<pre>` пробелы значимы, и компилятор Vue их здесь не схлопывает:\n любой перенос между тегами стал бы пустой строкой в коде. Поэтому\n содержимое блока записано без переносов — строки разделяет `display: block`\n у `.gr-code-block__line`, а не текстовый `\\n`.\n -->\n <pre\n data-gr-code-block-scroll\n :class=\"[preClass, resolvedLineNumbers ? numberedClass : '']\"\n :style=\"scrollStyle\"\n :tabindex=\"isScroller ? 0 : undefined\"\n :role=\"ariaLabel ? 'region' : undefined\"\n :aria-label=\"ariaLabel\"\n ><code data-gr-code-block-code><span v-for=\"(tokens, index) in lines\" :key=\"index\" data-gr-code-block-line class=\"gr-code-block__line\"><span v-for=\"(token, tokenIndex) in tokens\" :key=\"tokenIndex\" :class=\"codeTokenClass[token.kind]\">{{ token.text }}</span></span></code></pre>\n </div>\n</template>\n\n<style>\n/*\n * Номер строки — CSS-счётчик, а не текст в разметке. Разница не косметическая:\n * узел со `<span>2</span>` попадает в выделение мышью, и в буфер уезжает то,\n * что нельзя вставить обратно как код. У `::before` содержимого в DOM нет.\n *\n * Стиль глобальный, а не scoped: правило адресует псевдоэлемент строки, и\n * scoped-атрибут пришлось бы вешать на каждый из них.\n */\n:where(.gr-code-block__line) {\n display: block;\n}\n\n:where(.gr-code-block--numbered) {\n counter-reset: gr-code-line;\n}\n\n/*\n * Висячий отступ: строка сдвинута на ширину колонки номеров, а первая строка\n * возвращена назад отрицательным `text-indent` — туда и встаёт номер. Без этого\n * перенесённая при `wrap` строка начиналась бы под номером, а не под кодом.\n */\n:where(.gr-code-block--numbered .gr-code-block__line) {\n padding-left: calc(var(--gr-code-block-line-number-width, 2.5em) + 1em);\n text-indent: calc(-1 * (var(--gr-code-block-line-number-width, 2.5em) + 1em));\n}\n\n:where(.gr-code-block--numbered .gr-code-block__line)::before {\n counter-increment: gr-code-line;\n content: counter(gr-code-line);\n display: inline-block;\n width: var(--gr-code-block-line-number-width, 2.5em);\n margin-right: 1em;\n text-align: right;\n text-indent: 0;\n color: var(--gr-code-block-line-number, var(--gr-muted-fg));\n user-select: none;\n}\n</style>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref } from 'vue'\n\nimport GrButton from '../GrButton/GrButton.vue'\nimport { useAnnouncer } from '../../composables/useAnnouncer'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\n\nimport IconCheck from '~icons/lucide/check'\nimport IconCopy from '~icons/lucide/copy'\n\nimport { serializeCode } from './serializeCode'\nimport { tokenizeJson, type GrCodeToken } from './tokenizeJson'\nimport {\n codeBlockCopyClass,\n codeBlockGutterClass,\n codeBlockHookClass,\n codeBlockNowrapClass,\n codeBlockNumberedClass as numberedClass,\n codeBlockPaddings,\n codeBlockRootClass,\n codeBlockScrollClass,\n codeBlockSurfaceClass,\n codeBlockTextSizes,\n codeBlockWrapClass,\n codeTokenClass,\n} from './grCodeBlockStyles'\n\nimport type { GrComponentSize } from '../shared/sizes'\n\nexport type { GrCodeToken, GrCodeTokenKind } from './tokenizeJson'\n\n/** Язык подсветки. Кроме JSON компонент ничего не разбирает — и не должен. */\nexport type GrCodeBlockLanguage = 'json' | 'text'\n\n/**\n * Блок сырого текста или JSON: моноширинный, с подсветкой, номерами строк и\n * копированием в буфер.\n *\n * Показывает, а не редактирует и не сворачивает узлы. Свёртка по узлам — задача\n * `GrJsonViewer`, дерево выбора — `GrTree`.\n */\nexport interface GrCodeBlockProps {\n /** Строка или значение: объект сериализуется с отступом. */\n code: unknown\n language?: GrCodeBlockLanguage\n /** Высота, после которой включается вертикальный скролл. */\n maxHeight?: string | number\n /** Кнопка копирования. Без защищённого контекста не рисуется вовсе. */\n copyable?: boolean\n /** Переносить длинные строки вместо горизонтальной прокрутки. */\n wrap?: boolean\n lineNumbers?: boolean\n /** Имя области прокрутки. Безымянную скринридер объявляет просто «регион». */\n ariaLabel?: string\n size?: GrComponentSize\n}\n\nexport interface GrCodeBlockEmits {\n /** Текст скопирован. Тост показывает потребитель: у блока своего места для него нет. */\n (e: 'copy', code: string): void\n}\n\nconst props = withDefaults(defineProps<GrCodeBlockProps>(), {\n // Дефолты живут в резолверах ниже: Vue подставил бы свой раньше, чем компонент\n // заглянет в `GrConfigProvider`.\n language: 'json',\n maxHeight: undefined,\n copyable: undefined,\n wrap: undefined,\n lineNumbers: undefined,\n ariaLabel: undefined,\n size: undefined,\n})\n\nconst emit = defineEmits<GrCodeBlockEmits>()\n\nconst { t } = useGranularityTranslations()\nconst { announce } = useAnnouncer()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrCodeBlock' })\nconst resolvedCopyable = useGrComponentProp('GrCodeBlock', 'copyable', () => props.copyable, true)\nconst resolvedWrap = useGrComponentProp('GrCodeBlock', 'wrap', () => props.wrap, false)\nconst resolvedLineNumbers = useGrComponentProp('GrCodeBlock', 'lineNumbers', () => props.lineNumbers, false)\n\n/** Единственный источник текста: он же рендерится, он же уходит в буфер. */\nconst source = computed(() => serializeCode(props.code))\n\n/**\n * Разбор идёт по строкам, а не по всему тексту: номер строки — CSS-счётчик, а\n * счётчику нужен элемент на строку. JSON-строка перенос не содержит (он в ней\n * экранирован), поэтому построчная токенизация ничего не рвёт.\n */\nconst lines = computed<GrCodeToken[][]>(() => {\n const rows = source.value.split('\\n')\n\n if (props.language !== 'json') return rows.map(row => [{ text: row, kind: 'plain' as const }])\n\n return rows.map(row => tokenizeJson(row))\n})\n\n/**\n * Блок — скроллер по конструкции, а не по замеру. Замер здесь означал бы\n * мигающую остановку `Tab` на каждой смене данных и загрузке шрифта; ни один\n * компонент пакета так не делает.\n */\nconst isScroller = computed(() => props.maxHeight !== undefined || !resolvedWrap.value)\n\nconst scrollStyle = computed(() => {\n if (props.maxHeight === undefined) return undefined\n\n return { maxHeight: typeof props.maxHeight === 'number' ? `${props.maxHeight}px` : props.maxHeight }\n})\n\nconst preClass = computed(() => [\n codeBlockHookClass,\n codeBlockSurfaceClass,\n codeBlockPaddings[resolvedSize.value],\n codeBlockTextSizes[resolvedSize.value],\n resolvedWrap.value ? codeBlockWrapClass : codeBlockNowrapClass,\n isScroller.value ? codeBlockScrollClass : '',\n].filter(Boolean).join(' '))\n\n/**\n * Наличие буфера уточняется после монтирования: `navigator` в теле `setup` либо\n * роняет серверный рендер, либо расходится с ним. До этого момента кнопки нет —\n * ровно то же, что отдаёт сервер, поэтому гидрация совпадает.\n */\nconst canCopy = ref(false)\n\nonMounted(() => {\n canCopy.value = typeof navigator !== 'undefined' && typeof navigator.clipboard?.writeText === 'function'\n})\n\nconst showCopy = computed(() => resolvedCopyable.value && canCopy.value)\n\nconst copied = ref(false)\nlet copiedTimer: number | null = null\n\nfunction resetCopiedTimer(): void {\n if (copiedTimer !== null) {\n clearTimeout(copiedTimer)\n copiedTimer = null\n }\n}\n\nonBeforeUnmount(resetCopiedTimer)\n\nasync function copy(): Promise<void> {\n try {\n // Копируется исходный текст, а не разметка: номера строк — CSS-счётчик,\n // а мягкие переносы принадлежат показу, и вставить их обратно нельзя.\n await navigator.clipboard.writeText(source.value)\n }\n catch {\n announce(t('gr.codeBlock.copyFailed', 'Could not copy code'))\n return\n }\n\n copied.value = true\n resetCopiedTimer()\n copiedTimer = window.setTimeout(() => {\n copied.value = false\n copiedTimer = null\n }, 1200)\n\n announce(t('gr.codeBlock.copied', 'Code copied'))\n emit('copy', source.value)\n}\n\nconst copyLabel = computed(() => (\n copied.value ? t('gr.codeBlock.copied', 'Code copied') : t('gr.codeBlock.copy', 'Copy code')\n))\n</script>\n\n<template>\n <div data-gr-code-block :class=\"[codeBlockRootClass, showCopy ? codeBlockGutterClass : '']\">\n <GrButton\n v-if=\"showCopy\"\n data-gr-code-block-copy\n variant=\"ghost\"\n size=\"xs\"\n square\n :class=\"codeBlockCopyClass\"\n :aria-label=\"copyLabel\"\n :title=\"copyLabel\"\n @click=\"copy\"\n >\n <component :is=\"copied ? IconCheck : IconCopy\" class=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n </GrButton>\n\n <!--\n Внутри `<pre>` пробелы значимы, и компилятор Vue их здесь не схлопывает:\n любой перенос между тегами стал бы пустой строкой в коде. Поэтому\n содержимое блока записано без переносов — строки разделяет `display: block`\n у `.gr-code-block__line`, а не текстовый `\\n`.\n -->\n <pre\n data-gr-code-block-scroll\n :class=\"[preClass, resolvedLineNumbers ? numberedClass : '']\"\n :style=\"scrollStyle\"\n :tabindex=\"isScroller ? 0 : undefined\"\n :role=\"ariaLabel ? 'region' : undefined\"\n :aria-label=\"ariaLabel\"\n ><code data-gr-code-block-code><span v-for=\"(tokens, index) in lines\" :key=\"index\" data-gr-code-block-line class=\"gr-code-block__line\"><span v-for=\"(token, tokenIndex) in tokens\" :key=\"tokenIndex\" :class=\"codeTokenClass[token.kind]\">{{ token.text }}</span></span></code></pre>\n </div>\n</template>\n\n<style>\n/*\n * Номер строки — CSS-счётчик, а не текст в разметке. Разница не косметическая:\n * узел со `<span>2</span>` попадает в выделение мышью, и в буфер уезжает то,\n * что нельзя вставить обратно как код. У `::before` содержимого в DOM нет.\n *\n * Стиль глобальный, а не scoped: правило адресует псевдоэлемент строки, и\n * scoped-атрибут пришлось бы вешать на каждый из них.\n */\n:where(.gr-code-block__line) {\n display: block;\n}\n\n:where(.gr-code-block--numbered) {\n counter-reset: gr-code-line;\n}\n\n/*\n * Висячий отступ: строка сдвинута на ширину колонки номеров, а первая строка\n * возвращена назад отрицательным `text-indent` — туда и встаёт номер. Без этого\n * перенесённая при `wrap` строка начиналась бы под номером, а не под кодом.\n */\n:where(.gr-code-block--numbered .gr-code-block__line) {\n padding-left: calc(var(--gr-code-block-line-number-width, 2.5em) + 1em);\n text-indent: calc(-1 * (var(--gr-code-block-line-number-width, 2.5em) + 1em));\n}\n\n:where(.gr-code-block--numbered .gr-code-block__line)::before {\n counter-increment: gr-code-line;\n content: counter(gr-code-line);\n display: inline-block;\n width: var(--gr-code-block-line-number-width, 2.5em);\n margin-right: 1em;\n text-align: right;\n text-indent: 0;\n color: var(--gr-code-block-line-number, var(--gr-muted-fg));\n user-select: none;\n}\n</style>\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- import { t as GrCodeBlock_default } from "./chunks/GrCodeBlock-CW3zY1ee.js";
2
- import { n as grCodeBlockSafelist, t as grCodeBlockConfig } from "../../chunks/config-B_qi4Fma2.js";
1
+ import { t as GrCodeBlock_default } from "./chunks/GrCodeBlock-BmXCLgLS.js";
2
+ import { n as grCodeBlockSafelist, t as grCodeBlockConfig } from "../../chunks/config-B_qi4Fma.js";
3
3
  export { GrCodeBlock_default as GrCodeBlock, GrCodeBlock_default as default, grCodeBlockConfig, grCodeBlockSafelist };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GrDelta-DFvxNpmN.js","names":[],"sources":["../../../../src/internal/granularityFormat.ts","../../../../src/components/GrDelta/GrDelta.vue","../../../../src/components/GrDelta/GrDelta.vue"],"sourcesContent":["/**\n * Форматирование чисел и дат по локали.\n *\n * Своё, а не из `@feugene/fint-i18n`: тот объявлен **опциональной** peer-зависимостью,\n * и пакет обязан работать без него. Под капотом всё равно `Intl`, то есть\n * результат совпадает с `useI18nFormat()` библиотеки — расхождения между\n * приложением с i18n и без него не возникает.\n *\n * Инстансы `Intl` кэшируются: их построение примерно на порядок дороже самого\n * форматирования. Кэш ограничен и сбрасывается целиком — иначе сгенерированные\n * на лету опции растили бы его без предела.\n */\n\nconst CACHE_LIMIT = 64\n\nconst numberFormats = new Map<string, Intl.NumberFormat>()\nconst dateFormats = new Map<string, Intl.DateTimeFormat>()\n\nfunction cacheKey(locale: string, options?: object): string {\n return options ? `${locale}\u0000${JSON.stringify(options)}` : locale\n}\n\nfunction take<T>(cache: Map<string, T>, key: string, create: () => T): T {\n const cached = cache.get(key)\n if (cached) return cached\n\n const created = create()\n if (cache.size >= CACHE_LIMIT) cache.clear()\n cache.set(key, created)\n\n return created\n}\n\n/**\n * Локаль приходит из настроек приложения, а `Intl` бросает на некорректном\n * теге. Форматирование не имеет права уронить рендер, поэтому неизвестный тег\n * сводим к `en`.\n */\nexport function formatNumber(\n locale: string,\n value: number | bigint,\n options?: Intl.NumberFormatOptions,\n): string {\n try {\n return take(numberFormats, cacheKey(locale, options), () => new Intl.NumberFormat(locale, options)).format(value)\n }\n catch {\n return take(numberFormats, cacheKey('en', options), () => new Intl.NumberFormat('en', options)).format(value)\n }\n}\n\n/**\n * Те же правила, что у `formatNumber`, но по частям: вызывающему нужно знать,\n * где в записи стоит знак, чтобы вынести его отдельным узлом.\n *\n * Инстанс тот же, что у `formatNumber`: `Intl.NumberFormat` умеет оба метода, а\n * второй кэш под теми же ключами разъехался бы с первым на первом вытеснении.\n */\nexport function formatNumberToParts(\n locale: string,\n value: number | bigint,\n options?: Intl.NumberFormatOptions,\n): Intl.NumberFormatPart[] {\n try {\n return take(numberFormats, cacheKey(locale, options), () => new Intl.NumberFormat(locale, options)).formatToParts(value)\n }\n catch {\n return take(numberFormats, cacheKey('en', options), () => new Intl.NumberFormat('en', options)).formatToParts(value)\n }\n}\n\n/**\n * Ведущий знак и остаток записи.\n *\n * Нужно там, где знаку полагается свой узел разметки: валюта обязана стоять\n * после знака (`+$0,03`, а не `$+0,03`). Резать готовую строку по первому\n * символу нельзя — в RTL-локалях знаку предшествует невидимая метка\n * направления (`he-IL` — U+200E, `ar-EG` — U+061C), которой и разворачивается\n * минус относительно цифр. Метка уходит вместе со знаком: она управляет им.\n *\n * `sign` пуст, когда знака нет вовсе (ноль, положительное при `signDisplay:\n * 'auto'`, `NaN`) либо когда перед ним стоит содержательная часть. Во втором\n * случае запись не делится совсем: вернуть половину по догадке хуже, чем\n * вернуть целое.\n */\nexport function splitLeadingSign(\n parts: readonly Intl.NumberFormatPart[],\n): { sign: string, rest: string } {\n const join = (from: readonly Intl.NumberFormatPart[]): string => from.map(part => part.value).join('')\n const at = parts.findIndex(part => part.type === 'minusSign' || part.type === 'plusSign')\n\n if (at === -1 || parts.slice(0, at).some(part => part.type !== 'literal'))\n return { sign: '', rest: join(parts) }\n\n return { sign: join(parts.slice(0, at + 1)), rest: join(parts.slice(at + 1)) }\n}\n\nexport function formatDate(\n locale: string,\n value: Date | number | string,\n options?: Intl.DateTimeFormatOptions,\n): string {\n const date = value instanceof Date ? value : new Date(value)\n\n // Невалидную дату возвращаем как есть: пустая или «Invalid Date» в интерфейсе\n // хуже, чем исходное значение, но лучше, чем исключение на рендере.\n if (Number.isNaN(date.getTime())) return String(value)\n\n try {\n return take(dateFormats, cacheKey(locale, options), () => new Intl.DateTimeFormat(locale, options)).format(date)\n }\n catch {\n return take(dateFormats, cacheKey('en', options), () => new Intl.DateTimeFormat('en', options)).format(date)\n }\n}\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport { formatNumberToParts, splitLeadingSign } from '../../internal/granularityFormat'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\n\nimport IconMinus from '~icons/lucide/minus'\nimport IconTrendingDown from '~icons/lucide/trending-down'\nimport IconTrendingUp from '~icons/lucide/trending-up'\n\nimport { deltaDirection, deltaTone, type GrDeltaPolarity, type GrDeltaTone } from './deltaTone'\nimport {\n deltaArrowClass,\n deltaEmptyClass,\n deltaRootClass,\n deltaSizeClass,\n deltaSuffixClass,\n deltaToneClass,\n} from './grDeltaStyles'\n\nimport type { GrComponentSize } from '../shared/sizes'\n\nexport type { GrDeltaPolarity, GrDeltaTone } from './deltaTone'\n\n/**\n * Величина со знаком и тоном **внутри строки текста**: «Дельта: +$0.004».\n *\n * Компонент презентационный: дельту не считает и периоды не сравнивает —\n * «к чему сравнивать» знает приложение. Блочная плитка показателя — `GrStatistic`.\n */\nexport interface GrDeltaProps {\n /** Величина. `null` — её нет; это не ноль. */\n value: number | null\n /** Знаков после запятой. Не задано — как отдаст локаль. */\n precision?: number\n /** Приписка перед числом: валюта, единица. */\n prefix?: string\n /** Приписка после числа: `%`, `мс`. */\n suffix?: string\n /**\n * Что считать хорошим. Для выручки рост — успех, для себестоимости и времени\n * отклика — наоборот; без этого компонент врал бы в половине случаев.\n */\n polarity?: GrDeltaPolarity\n /** Ставить `+` у положительных. Минус подставляет `Intl` сам. */\n showSign?: boolean\n /** Стрелка направления. Декоративна: направление уже в знаке. */\n showArrow?: boolean\n /** Чем печатать отсутствующую величину. */\n emptyText?: string\n /** Локаль форматирования. Не задана — локаль i18n-адаптера. */\n locale?: string\n size?: GrComponentSize\n}\n\nconst props = withDefaults(defineProps<GrDeltaProps>(), {\n // Дефолты живут в резолверах ниже: Vue подставил бы свой раньше, чем\n // компонент заглянет в `GrConfigProvider`.\n precision: undefined,\n prefix: undefined,\n suffix: undefined,\n polarity: undefined,\n showSign: undefined,\n showArrow: undefined,\n emptyText: undefined,\n locale: undefined,\n size: undefined,\n})\n\nconst { locale } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrDelta' })\nconst resolvedLocale = computed(() => props.locale ?? locale.value ?? 'en')\nconst resolvedPolarity = useGrComponentProp('GrDelta', 'polarity', () => props.polarity, 'positive-good')\nconst resolvedShowSign = useGrComponentProp('GrDelta', 'showSign', () => props.showSign, true)\nconst resolvedShowArrow = useGrComponentProp('GrDelta', 'showArrow', () => props.showArrow, false)\nconst resolvedEmptyText = useGrComponentProp('GrDelta', 'emptyText', () => props.emptyText, '—')\n\nconst isEmpty = computed(() => props.value === null || props.value === undefined)\n\nconst tone = computed<GrDeltaTone>(() => (\n isEmpty.value ? 'neutral' : deltaTone(props.value as number, resolvedPolarity.value)\n))\n\nconst direction = computed(() => (isEmpty.value ? 'flat' : deltaDirection(props.value as number)))\n\nconst formatOptions = computed<Intl.NumberFormatOptions>(() => ({\n signDisplay: resolvedShowSign.value ? 'exceptZero' : 'auto',\n ...(props.precision === undefined\n ? {}\n : { minimumFractionDigits: props.precision, maximumFractionDigits: props.precision }),\n}))\n\n/**\n * Знак и число по отдельности.\n *\n * Знак — префикс форматирования, а не склейка строки. `'+' + value` уже ломало\n * `GrStatistic`: строка `'+10'` разбирается как число, и разряды теряются.\n * Поэтому знак не дописывают, а **вынимают** из готовых частей `Intl`.\n *\n * Делится величина только вместе с `prefix`: валюта обязана стоять после знака\n * (`+$0,0280`, а не `$+0,0280`). Без приписки делить нечего — знак остаётся\n * внутри величины, и разметка не меняется ни на байт.\n */\nconst valueNodes = computed<{ sign: string, value: string }>(() => {\n if (isEmpty.value) return { sign: '', value: resolvedEmptyText.value }\n\n const parts = formatNumberToParts(resolvedLocale.value, props.value as number, formatOptions.value)\n\n if (!props.prefix) return { sign: '', value: parts.map(part => part.value).join('') }\n\n const { sign, rest } = splitLeadingSign(parts)\n\n return { sign, value: rest }\n})\n\nconst sign = computed(() => valueNodes.value.sign)\nconst formatted = computed(() => valueNodes.value.value)\n\nconst arrowIcon = computed(() => {\n if (direction.value === 'up') return IconTrendingUp\n if (direction.value === 'down') return IconTrendingDown\n return IconMinus\n})\n\nconst rootClass = computed(() => [\n deltaRootClass,\n deltaSizeClass[resolvedSize.value],\n isEmpty.value ? deltaEmptyClass : deltaToneClass[tone.value],\n].filter(Boolean).join(' '))\n</script>\n\n<template>\n <span data-gr-delta :data-tone=\"isEmpty ? undefined : tone\" :class=\"rootClass\">\n <component\n :is=\"arrowIcon\"\n v-if=\"resolvedShowArrow && !isEmpty\"\n data-gr-delta-arrow\n :class=\"deltaArrowClass\"\n aria-hidden=\"true\"\n />\n\n <span v-if=\"sign\" data-gr-delta-sign>{{ sign }}</span>\n <span v-if=\"prefix && !isEmpty\" data-gr-delta-prefix>{{ prefix }}</span>\n <span data-gr-delta-value>{{ formatted }}</span>\n <span v-if=\"suffix && !isEmpty\" data-gr-delta-suffix :class=\"deltaSuffixClass\">{{ suffix }}</span>\n </span>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\n\nimport { formatNumberToParts, splitLeadingSign } from '../../internal/granularityFormat'\nimport { useGranularityTranslations } from '../../internal/granularityI18n'\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\n\nimport IconMinus from '~icons/lucide/minus'\nimport IconTrendingDown from '~icons/lucide/trending-down'\nimport IconTrendingUp from '~icons/lucide/trending-up'\n\nimport { deltaDirection, deltaTone, type GrDeltaPolarity, type GrDeltaTone } from './deltaTone'\nimport {\n deltaArrowClass,\n deltaEmptyClass,\n deltaRootClass,\n deltaSizeClass,\n deltaSuffixClass,\n deltaToneClass,\n} from './grDeltaStyles'\n\nimport type { GrComponentSize } from '../shared/sizes'\n\nexport type { GrDeltaPolarity, GrDeltaTone } from './deltaTone'\n\n/**\n * Величина со знаком и тоном **внутри строки текста**: «Дельта: +$0.004».\n *\n * Компонент презентационный: дельту не считает и периоды не сравнивает —\n * «к чему сравнивать» знает приложение. Блочная плитка показателя — `GrStatistic`.\n */\nexport interface GrDeltaProps {\n /** Величина. `null` — её нет; это не ноль. */\n value: number | null\n /** Знаков после запятой. Не задано — как отдаст локаль. */\n precision?: number\n /** Приписка перед числом: валюта, единица. */\n prefix?: string\n /** Приписка после числа: `%`, `мс`. */\n suffix?: string\n /**\n * Что считать хорошим. Для выручки рост — успех, для себестоимости и времени\n * отклика — наоборот; без этого компонент врал бы в половине случаев.\n */\n polarity?: GrDeltaPolarity\n /** Ставить `+` у положительных. Минус подставляет `Intl` сам. */\n showSign?: boolean\n /** Стрелка направления. Декоративна: направление уже в знаке. */\n showArrow?: boolean\n /** Чем печатать отсутствующую величину. */\n emptyText?: string\n /** Локаль форматирования. Не задана — локаль i18n-адаптера. */\n locale?: string\n size?: GrComponentSize\n}\n\nconst props = withDefaults(defineProps<GrDeltaProps>(), {\n // Дефолты живут в резолверах ниже: Vue подставил бы свой раньше, чем\n // компонент заглянет в `GrConfigProvider`.\n precision: undefined,\n prefix: undefined,\n suffix: undefined,\n polarity: undefined,\n showSign: undefined,\n showArrow: undefined,\n emptyText: undefined,\n locale: undefined,\n size: undefined,\n})\n\nconst { locale } = useGranularityTranslations()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrDelta' })\nconst resolvedLocale = computed(() => props.locale ?? locale.value ?? 'en')\nconst resolvedPolarity = useGrComponentProp('GrDelta', 'polarity', () => props.polarity, 'positive-good')\nconst resolvedShowSign = useGrComponentProp('GrDelta', 'showSign', () => props.showSign, true)\nconst resolvedShowArrow = useGrComponentProp('GrDelta', 'showArrow', () => props.showArrow, false)\nconst resolvedEmptyText = useGrComponentProp('GrDelta', 'emptyText', () => props.emptyText, '—')\n\nconst isEmpty = computed(() => props.value === null || props.value === undefined)\n\nconst tone = computed<GrDeltaTone>(() => (\n isEmpty.value ? 'neutral' : deltaTone(props.value as number, resolvedPolarity.value)\n))\n\nconst direction = computed(() => (isEmpty.value ? 'flat' : deltaDirection(props.value as number)))\n\nconst formatOptions = computed<Intl.NumberFormatOptions>(() => ({\n signDisplay: resolvedShowSign.value ? 'exceptZero' : 'auto',\n ...(props.precision === undefined\n ? {}\n : { minimumFractionDigits: props.precision, maximumFractionDigits: props.precision }),\n}))\n\n/**\n * Знак и число по отдельности.\n *\n * Знак — префикс форматирования, а не склейка строки. `'+' + value` уже ломало\n * `GrStatistic`: строка `'+10'` разбирается как число, и разряды теряются.\n * Поэтому знак не дописывают, а **вынимают** из готовых частей `Intl`.\n *\n * Делится величина только вместе с `prefix`: валюта обязана стоять после знака\n * (`+$0,0280`, а не `$+0,0280`). Без приписки делить нечего — знак остаётся\n * внутри величины, и разметка не меняется ни на байт.\n */\nconst valueNodes = computed<{ sign: string, value: string }>(() => {\n if (isEmpty.value) return { sign: '', value: resolvedEmptyText.value }\n\n const parts = formatNumberToParts(resolvedLocale.value, props.value as number, formatOptions.value)\n\n if (!props.prefix) return { sign: '', value: parts.map(part => part.value).join('') }\n\n const { sign, rest } = splitLeadingSign(parts)\n\n return { sign, value: rest }\n})\n\nconst sign = computed(() => valueNodes.value.sign)\nconst formatted = computed(() => valueNodes.value.value)\n\nconst arrowIcon = computed(() => {\n if (direction.value === 'up') return IconTrendingUp\n if (direction.value === 'down') return IconTrendingDown\n return IconMinus\n})\n\nconst rootClass = computed(() => [\n deltaRootClass,\n deltaSizeClass[resolvedSize.value],\n isEmpty.value ? deltaEmptyClass : deltaToneClass[tone.value],\n].filter(Boolean).join(' '))\n</script>\n\n<template>\n <span data-gr-delta :data-tone=\"isEmpty ? undefined : tone\" :class=\"rootClass\">\n <component\n :is=\"arrowIcon\"\n v-if=\"resolvedShowArrow && !isEmpty\"\n data-gr-delta-arrow\n :class=\"deltaArrowClass\"\n aria-hidden=\"true\"\n />\n\n <span v-if=\"sign\" data-gr-delta-sign>{{ sign }}</span>\n <span v-if=\"prefix && !isEmpty\" data-gr-delta-prefix>{{ prefix }}</span>\n <span data-gr-delta-value>{{ formatted }}</span>\n <span v-if=\"suffix && !isEmpty\" data-gr-delta-suffix :class=\"deltaSuffixClass\">{{ suffix }}</span>\n </span>\n</template>\n"],"mappings":"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"}
@@ -1,3 +1,4 @@
1
- import { t as GrDelta_default } from "./chunks/GrDelta-BMmpYz8K.js";
1
+ import { t as GrDelta_default } from "./chunks/GrDelta-DFvxNpmN.js";
2
+ import { n as deltaTone, t as deltaDirection } from "../../chunks/deltaTone-B5OoS1vo.js";
2
3
  import { n as grDeltaSafelist, t as grDeltaConfig } from "../../chunks/config-DBQQT-WP.js";
3
- export { GrDelta_default as GrDelta, GrDelta_default as default, grDeltaConfig, grDeltaSafelist };
4
+ export { GrDelta_default as GrDelta, GrDelta_default as default, deltaDirection, deltaTone, grDeltaConfig, grDeltaSafelist };
@@ -1,5 +1,5 @@
1
1
  import { a as useGrComponentSize, i as useGrComponentProp } from "../../../chunks/context-CTbuwcYs.js";
2
- import { a as descriptionDividedClass, c as descriptionPairInlineClass, d as descriptionSizeClass, f as descriptionValueClass, i as descriptionDensityClass, l as descriptionPairStackedClass, o as descriptionLabelInlineClass, p as descriptionValueToneClass, r as descriptionColumnsClass, s as descriptionLabelStackedClass, u as descriptionRootClass } from "../../../chunks/config-jyZq8XKL.js";
2
+ import { a as descriptionDividedClass, c as descriptionPairFlowClass, d as descriptionRootClass, f as descriptionRootFlowClass, g as descriptionValueToneClass, h as descriptionValueClass, i as descriptionDensityClass, l as descriptionPairInlineClass, m as descriptionSizeClass, o as descriptionLabelInlineClass, p as descriptionRootGridClass, r as descriptionColumnsClass, s as descriptionLabelStackedClass, u as descriptionPairStackedClass } from "../../../chunks/config-DCwQBMmJ.js";
3
3
  import { Fragment, computed, createElementBlock, createElementVNode, createTextVNode, defineComponent, normalizeClass, normalizeStyle, onBeforeUnmount, onMounted, openBlock, ref, renderList, renderSlot, toDisplayString, unref, useSlots } from "vue";
4
4
  //#endregion
5
5
  //#region src/components/GrDescriptionList/GrDescriptionList.vue
@@ -48,15 +48,26 @@ var GrDescriptionList_default = /* @__PURE__ */ defineComponent({
48
48
  observer?.disconnect();
49
49
  observer = null;
50
50
  });
51
- const effectiveLayout = computed(() => narrow.value ? "stacked" : resolvedLayout.value);
51
+ /**
52
+ * До монтирования действует заданная раскладка — это же и есть серверный рендер.
53
+ *
54
+ * Порог спасает только `inline`: колонка подписей в узком контейнере выжимает
55
+ * значение в букву на строку. `flow` переносится сам, и подменять его нечем.
56
+ */
57
+ const effectiveLayout = computed(() => narrow.value && resolvedLayout.value === "inline" ? "stacked" : resolvedLayout.value);
52
58
  const isInline = computed(() => effectiveLayout.value === "inline");
59
+ const isFlow = computed(() => effectiveLayout.value === "flow");
53
60
  const rootClass = computed(() => [
54
61
  descriptionRootClass,
55
- descriptionColumnsClass[resolvedColumns.value],
62
+ isFlow.value ? descriptionRootFlowClass : descriptionRootGridClass,
63
+ isFlow.value ? "" : descriptionColumnsClass[resolvedColumns.value],
56
64
  descriptionSizeClass[resolvedSize.value],
57
65
  descriptionDensityClass[resolvedDensity.value]
58
66
  ].filter(Boolean).join(" "));
59
- const pairClass = computed(() => [isInline.value ? descriptionPairInlineClass : descriptionPairStackedClass, resolvedDivided.value ? descriptionDividedClass : ""].filter(Boolean).join(" "));
67
+ const pairClass = computed(() => {
68
+ if (isFlow.value) return descriptionPairFlowClass;
69
+ return [isInline.value ? descriptionPairInlineClass : descriptionPairStackedClass, resolvedDivided.value ? descriptionDividedClass : ""].filter(Boolean).join(" ");
70
+ });
60
71
  const labelClass = computed(() => isInline.value ? descriptionLabelInlineClass : descriptionLabelStackedClass);
61
72
  const labelStyle = computed(() => isInline.value ? { width: resolvedLabelWidth.value } : void 0);
62
73
  /**