@feugene/granularity 0.22.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.
- package/dist/chunks/{config-jyZq8XKL.js → config-DCwQBMmJ.js} +12 -3
- package/dist/chunks/config-DCwQBMmJ.js.map +1 -0
- package/dist/chunks/{trending-up-Bf9AQmoq.js → deltaTone-B5OoS1vo.js} +19 -1
- package/dist/chunks/deltaTone-B5OoS1vo.js.map +1 -0
- package/dist/chunks/{granular-provider-CYroWMjO.js → granular-provider-CQKhl5C_.js} +1 -1
- package/dist/chunks/{granular-provider-CYroWMjO.js.map → granular-provider-CQKhl5C_.js.map} +1 -1
- package/dist/components/GrDelta/chunks/{GrDelta-BMmpYz8K.js → GrDelta-DFvxNpmN.js} +0 -0
- package/dist/components/GrDelta/chunks/GrDelta-DFvxNpmN.js.map +1 -0
- package/dist/components/GrDelta/index.js +3 -2
- package/dist/components/GrDescriptionList/chunks/{GrDescriptionList-pXh1LZEe.js → GrDescriptionList-2zFXzX83.js} +15 -4
- package/dist/components/GrDescriptionList/chunks/GrDescriptionList-2zFXzX83.js.map +1 -0
- package/dist/components/GrDescriptionList/index.js +2 -2
- package/dist/components/GrFilePreview/chunks/{GrFilePreview-rbE3zL9w.js → GrFilePreview-D0Bt-0nn.js} +1 -1
- package/dist/components/GrFilePreview/chunks/{GrFilePreview-rbE3zL9w.js.map → GrFilePreview-D0Bt-0nn.js.map} +1 -1
- package/dist/components/GrFilePreview/index.js +2 -2
- package/dist/components/GrStatistic/chunks/{GrStatistic-DkzWUAw1.js → GrStatistic-C7wQECeS.js} +20 -4
- package/dist/components/GrStatistic/chunks/GrStatistic-C7wQECeS.js.map +1 -0
- package/dist/components/GrStatistic/index.js +1 -1
- package/dist/granular-provider-node.js +1 -1
- package/dist/granular-provider.js +1 -1
- package/dist/index.js +7 -6
- package/dist/types/components/GrDelta/index.d.ts +1 -0
- package/dist/types/components/GrDescriptionList/GrDescriptionList.vue.d.ts +6 -2
- package/dist/types/components/GrDescriptionList/grDescriptionListStyles.d.ts +12 -5
- package/dist/types/components/GrFilePreview/index.d.ts +1 -0
- package/dist/types/components/GrInput/GrInput.vue.d.ts +1 -1
- package/dist/types/components/GrNumberInput/GrNumberInput.vue.d.ts +1 -1
- package/dist/types/components/GrPromptDialog/GrPromptDialog.vue.d.ts +1 -1
- package/dist/types/components/GrStatistic/GrStatistic.vue.d.ts +12 -2
- package/dist/types/components/GrStatistic/defaults.d.ts +6 -0
- package/dist/types/internal/granularityFormat.d.ts +26 -0
- package/dist/web-types.json +14 -4
- package/package.json +1 -1
- package/dist/chunks/config-jyZq8XKL.js.map +0 -1
- package/dist/chunks/trending-up-Bf9AQmoq.js.map +0 -1
- package/dist/components/GrDelta/chunks/GrDelta-BMmpYz8K.js.map +0 -1
- package/dist/components/GrDescriptionList/chunks/GrDescriptionList-pXh1LZEe.js.map +0 -1
- package/dist/components/GrStatistic/chunks/GrStatistic-DkzWUAw1.js.map +0 -1
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
import { t as splitClassTokens } from "./classTokens-BzazaERD.js";
|
|
2
2
|
import { defineGranularComponent } from "@feugene/unocss-preset-granular/contract";
|
|
3
3
|
//#region src/components/GrDescriptionList/grDescriptionListStyles.ts
|
|
4
|
-
var descriptionRootClass = "m-0
|
|
4
|
+
var descriptionRootClass = "m-0 gap-x-4 [row-gap:var(--gr-description-list-row-gap,0.375rem)]";
|
|
5
|
+
/** Сетка колонок — раскладки `inline` и `stacked`. */
|
|
6
|
+
var descriptionRootGridClass = "grid";
|
|
7
|
+
var descriptionRootFlowClass = "flex flex-wrap items-baseline";
|
|
5
8
|
var descriptionColumnsClass = {
|
|
6
9
|
1: "grid-cols-1",
|
|
7
|
-
2: "grid-cols-1 sm:grid-cols-2"
|
|
10
|
+
2: "grid-cols-1 sm:grid-cols-2",
|
|
11
|
+
3: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
|
|
12
|
+
4: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4"
|
|
8
13
|
};
|
|
9
14
|
var descriptionPairInlineClass = "flex min-w-0 items-baseline gap-2";
|
|
10
15
|
var descriptionPairStackedClass = "min-w-0";
|
|
16
|
+
var descriptionPairFlowClass = "flex min-w-0 items-baseline gap-1";
|
|
11
17
|
var descriptionDividedClass = "border-b border-[var(--gr-description-list-divider,var(--gr-brd))] pb-1 last:border-b-0 last:pb-0";
|
|
12
18
|
var descriptionLabelInlineClass = "shrink-0 text-[var(--gr-description-list-label-color,var(--gr-muted-fg))]";
|
|
13
19
|
var descriptionLabelStackedClass = "text-[var(--gr-description-list-label-color,var(--gr-muted-fg))]";
|
|
@@ -37,8 +43,11 @@ var descriptionDensityClass = {
|
|
|
37
43
|
//#region src/components/GrDescriptionList/safelist.ts
|
|
38
44
|
var grDescriptionListSafelist = [.../* @__PURE__ */ new Set([
|
|
39
45
|
...splitClassTokens(descriptionRootClass),
|
|
46
|
+
...splitClassTokens(descriptionRootGridClass),
|
|
47
|
+
...splitClassTokens(descriptionRootFlowClass),
|
|
40
48
|
...splitClassTokens(descriptionPairInlineClass),
|
|
41
49
|
...splitClassTokens(descriptionPairStackedClass),
|
|
50
|
+
...splitClassTokens(descriptionPairFlowClass),
|
|
42
51
|
...splitClassTokens(descriptionDividedClass),
|
|
43
52
|
...splitClassTokens(descriptionLabelInlineClass),
|
|
44
53
|
...splitClassTokens(descriptionLabelStackedClass),
|
|
@@ -55,4 +64,4 @@ var grDescriptionListConfig = defineGranularComponent(import.meta.url, {
|
|
|
55
64
|
safelist: grDescriptionListSafelist
|
|
56
65
|
});
|
|
57
66
|
//#endregion
|
|
58
|
-
export { descriptionDividedClass as a,
|
|
67
|
+
export { descriptionDividedClass as a, descriptionPairFlowClass as c, descriptionRootClass as d, descriptionRootFlowClass as f, descriptionValueToneClass as g, descriptionValueClass as h, descriptionDensityClass as i, descriptionPairInlineClass as l, descriptionSizeClass as m, grDescriptionListSafelist as n, descriptionLabelInlineClass as o, descriptionRootGridClass as p, descriptionColumnsClass as r, descriptionLabelStackedClass as s, grDescriptionListConfig as t, descriptionPairStackedClass as u };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-DCwQBMmJ.js","names":[],"sources":["../../src/components/GrDescriptionList/grDescriptionListStyles.ts","../../src/components/GrDescriptionList/safelist.ts","../../src/components/GrDescriptionList/config.ts"],"sourcesContent":["import type { GrComponentSize } from '../shared/sizes'\nimport type { GrTone } from '../shared/tones'\n\n/**\n * Классы `GrDescriptionList`.\n *\n * Тон перечислен целиком по одному литералу на ветку: UnoCSS сканирует исходный\n * текст файла, и собранное шаблонной строкой имя уехало бы в CSS литералом.\n */\n\n/**\n * `inline` — подпись колонкой слева; `stacked` — над значением; `flow` — пары\n * текут по строке с переносом.\n */\nexport type GrDescriptionLayout = 'inline' | 'stacked' | 'flow'\n\nexport type GrDescriptionDensity = 'regular' | 'compact'\n\n/** Сколько колонок пар. Длинный список читается в несколько. */\nexport type GrDescriptionColumns = 1 | 2 | 3 | 4\n\n// `m-0` — у нативного `<dl>` есть браузерные отступы, и без сброса список\n// разъезжается по вертикали. Ряды разводит `--gr-description-list-row-gap`,\n// потому что зазор здесь — точка кастомизации, а не ступень шкалы.\nexport const descriptionRootClass = 'm-0 gap-x-4 [row-gap:var(--gr-description-list-row-gap,0.375rem)]'\n\n/** Сетка колонок — раскладки `inline` и `stacked`. */\nexport const descriptionRootGridClass = 'grid'\n\n// `flow` строит строку, а не сетку: пары текут по ширине и переносятся сами,\n// поэтому колонок у неё нет.\nexport const descriptionRootFlowClass = 'flex flex-wrap items-baseline'\n\n// Лестница брейкпоинтов зашита в саму ступень: четыре колонки коротких пар на\n// узком экране нечитаемы, и решать это потребителю незачем.\nexport const descriptionColumnsClass: Record<GrDescriptionColumns, string> = {\n 1: 'grid-cols-1',\n // Пара — элемент сетки, поэтому между колонками она не рвётся. `column-count`\n // рвал бы: он раскладывает поток, а не блоки.\n 2: 'grid-cols-1 sm:grid-cols-2',\n 3: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3',\n 4: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-4',\n}\n\nexport const descriptionPairInlineClass = 'flex min-w-0 items-baseline gap-2'\nexport const descriptionPairStackedClass = 'min-w-0'\n\n// Зазор уже: подпись и значение здесь читаются как одна фраза «Сообщений: 3»,\n// а пары друг от друга отбивает `gap-x-4` корня.\nexport const descriptionPairFlowClass = 'flex min-w-0 items-baseline gap-1'\n\nexport const descriptionDividedClass = 'border-b border-[var(--gr-description-list-divider,var(--gr-brd))] pb-1 last:border-b-0 last:pb-0'\n\n// `shrink-0` держит колонку подписей ровной: иначе длинное значение сжимало бы\n// подпись, и значения переставали бы стоять в колонку — ровно то, ради чего\n// раскладка `inline` и существует.\nexport const descriptionLabelInlineClass = 'shrink-0 text-[var(--gr-description-list-label-color,var(--gr-muted-fg))]'\nexport const descriptionLabelStackedClass = 'text-[var(--gr-description-list-label-color,var(--gr-muted-fg))]'\n\n// `break-words` обязателен: в парах живут идентификаторы, хеши и `X-Request-Id`,\n// и без переноса они выталкивают вёрстку.\nexport const descriptionValueClass = 'm-0 min-w-0 break-words'\n\n/** Тон — только у значения: красная подпись читается как «поле сломано». */\nexport const descriptionValueToneClass: Record<GrTone, string> = {\n primary: 'text-[var(--gr-primary-text)]',\n neutral: '',\n success: 'text-[var(--gr-success-text)]',\n warning: 'text-[var(--gr-warning-text)]',\n danger: 'text-[var(--gr-danger-text)]',\n info: 'text-[var(--gr-info-text)]',\n slate: 'text-[var(--gr-slate-text)]',\n azure: 'text-[var(--gr-azure-text)]',\n}\n\nexport const descriptionSizeClass: Record<GrComponentSize, string> = {\n xs: 'text-[length:var(--gr-control-text-2xs)]',\n sm: 'text-[length:var(--gr-control-text-xs)]',\n md: 'text-[length:var(--gr-control-text-sm)]',\n lg: 'text-[length:var(--gr-control-text-md)]',\n}\n\nexport const descriptionDensityClass: Record<GrDescriptionDensity, string> = {\n regular: '',\n compact: '[row-gap:var(--gr-description-list-row-gap,0.125rem)]',\n}\n","import { splitClassTokens } from '../shared/classTokens'\n\nimport {\n descriptionColumnsClass,\n descriptionDensityClass,\n descriptionDividedClass,\n descriptionLabelInlineClass,\n descriptionLabelStackedClass,\n descriptionPairFlowClass,\n descriptionPairInlineClass,\n descriptionPairStackedClass,\n descriptionRootClass,\n descriptionRootFlowClass,\n descriptionRootGridClass,\n descriptionSizeClass,\n descriptionValueClass,\n descriptionValueToneClass,\n} from './grDescriptionListStyles'\n\n// Классы из вычисляемых мап (тон, размер, колонки) UnoCSS сканом не находит —\n// только safelist. Литералы хелпера туда же: на сборке он уезжает в общий чанк.\nexport const grDescriptionListSafelist = [...new Set([\n ...splitClassTokens(descriptionRootClass),\n ...splitClassTokens(descriptionRootGridClass),\n ...splitClassTokens(descriptionRootFlowClass),\n ...splitClassTokens(descriptionPairInlineClass),\n ...splitClassTokens(descriptionPairStackedClass),\n ...splitClassTokens(descriptionPairFlowClass),\n ...splitClassTokens(descriptionDividedClass),\n ...splitClassTokens(descriptionLabelInlineClass),\n ...splitClassTokens(descriptionLabelStackedClass),\n ...splitClassTokens(descriptionValueClass),\n ...Object.values(descriptionColumnsClass).flatMap(splitClassTokens),\n ...Object.values(descriptionValueToneClass).flatMap(splitClassTokens),\n ...Object.values(descriptionSizeClass).flatMap(splitClassTokens),\n ...Object.values(descriptionDensityClass).flatMap(splitClassTokens),\n])]\n","import { defineGranularComponent } from '@feugene/unocss-preset-granular/contract'\n\nimport { grDescriptionListSafelist } from './safelist'\n\nexport const grDescriptionListConfig = defineGranularComponent(import.meta.url, {\n name: 'GrDescriptionList',\n safelist: grDescriptionListSafelist,\n})\n"],"mappings":";;;AAwBA,IAAa,uBAAuB;;AAGpC,IAAa,2BAA2B;AAIxC,IAAa,2BAA2B;AAIxC,IAAa,0BAAgE;CAC3E,GAAG;CAGH,GAAG;CACH,GAAG;CACH,GAAG;AACL;AAEA,IAAa,6BAA6B;AAC1C,IAAa,8BAA8B;AAI3C,IAAa,2BAA2B;AAExC,IAAa,0BAA0B;AAKvC,IAAa,8BAA8B;AAC3C,IAAa,+BAA+B;AAI5C,IAAa,wBAAwB;;AAGrC,IAAa,4BAAoD;CAC/D,SAAS;CACT,SAAS;CACT,SAAS;CACT,SAAS;CACT,QAAQ;CACR,MAAM;CACN,OAAO;CACP,OAAO;AACT;AAEA,IAAa,uBAAwD;CACnE,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;AACN;AAEA,IAAa,0BAAgE;CAC3E,SAAS;CACT,SAAS;AACX;;;AChEA,IAAa,4BAA4B,CAAC,mBAAG,IAAI,IAAI;CACnD,GAAG,iBAAiB,oBAAoB;CACxC,GAAG,iBAAiB,wBAAwB;CAC5C,GAAG,iBAAiB,wBAAwB;CAC5C,GAAG,iBAAiB,0BAA0B;CAC9C,GAAG,iBAAiB,2BAA2B;CAC/C,GAAG,iBAAiB,wBAAwB;CAC5C,GAAG,iBAAiB,uBAAuB;CAC3C,GAAG,iBAAiB,2BAA2B;CAC/C,GAAG,iBAAiB,4BAA4B;CAChD,GAAG,iBAAiB,qBAAqB;CACzC,GAAG,OAAO,OAAO,uBAAuB,CAAC,CAAC,QAAQ,gBAAgB;CAClE,GAAG,OAAO,OAAO,yBAAyB,CAAC,CAAC,QAAQ,gBAAgB;CACpE,GAAG,OAAO,OAAO,oBAAoB,CAAC,CAAC,QAAQ,gBAAgB;CAC/D,GAAG,OAAO,OAAO,uBAAuB,CAAC,CAAC,QAAQ,gBAAgB;AACpE,CAAC,CAAC;;;AChCF,IAAa,0BAA0B,wBAAwB,OAAO,KAAK,KAAK;CAC9E,MAAM;CACN,UAAU;AACZ,CAAC"}
|
|
@@ -39,4 +39,22 @@ var trending_up_default = markRaw({
|
|
|
39
39
|
render: render$1
|
|
40
40
|
});
|
|
41
41
|
//#endregion
|
|
42
|
-
|
|
42
|
+
//#region src/components/GrDelta/deltaTone.ts
|
|
43
|
+
/**
|
|
44
|
+
* Тон величины по её знаку и полярности.
|
|
45
|
+
*
|
|
46
|
+
* Ноль нейтрален при любой полярности: «не изменилось» — третье состояние, и
|
|
47
|
+
* двумя цветами его не выразить. `null` величины нет вовсе — тона у неё тоже
|
|
48
|
+
* нет, это решает вызывающий.
|
|
49
|
+
*/
|
|
50
|
+
function deltaTone(value, polarity = "positive-good") {
|
|
51
|
+
if (polarity === "none" || value === 0 || Number.isNaN(value)) return "neutral";
|
|
52
|
+
return (polarity === "positive-good" ? value > 0 : value < 0) ? "success" : "danger";
|
|
53
|
+
}
|
|
54
|
+
/** Направление стрелки. Ноль стрелки не получает: двигаться было некуда. */
|
|
55
|
+
function deltaDirection(value) {
|
|
56
|
+
if (Number.isNaN(value) || value === 0) return "flat";
|
|
57
|
+
return value > 0 ? "up" : "down";
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
export { trending_down_default as i, deltaTone as n, trending_up_default as r, deltaDirection as t };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deltaTone-B5OoS1vo.js","names":[],"sources":["../../src/components/GrDelta/deltaTone.ts"],"sourcesContent":["/**\n * Выбор тона для величины со знаком.\n *\n * Чистая функция, а не `switch` внутри компонента: ровно этот `switch`\n * расходился по приложениям копиями, и в одной из них нулевое движение\n * красилось зелёным — как поступление.\n */\n\n/** Что считать хорошим ростом. */\nexport type GrDeltaPolarity\n /** Рост — успех: выручка, конверсия, число подписок. */\n = | 'positive-good'\n /** Рост — проблема: себестоимость, время отклика, отток. */\n | 'negative-good'\n /** Знак ничего не говорит о качестве: сальдо, смещение, разница координат. */\n | 'none'\n\nexport type GrDeltaTone = 'success' | 'danger' | 'neutral'\n\n/**\n * Тон величины по её знаку и полярности.\n *\n * Ноль нейтрален при любой полярности: «не изменилось» — третье состояние, и\n * двумя цветами его не выразить. `null` величины нет вовсе — тона у неё тоже\n * нет, это решает вызывающий.\n */\nexport function deltaTone(value: number, polarity: GrDeltaPolarity = 'positive-good'): GrDeltaTone {\n if (polarity === 'none' || value === 0 || Number.isNaN(value)) return 'neutral'\n\n const good = polarity === 'positive-good' ? value > 0 : value < 0\n\n return good ? 'success' : 'danger'\n}\n\n/** Направление стрелки. Ноль стрелки не получает: двигаться было некуда. */\nexport function deltaDirection(value: number): 'up' | 'down' | 'flat' {\n if (Number.isNaN(value) || value === 0) return 'flat'\n return value > 0 ? 'up' : 'down'\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,SAAgB,UAAU,OAAe,WAA4B,iBAA8B;CACjG,IAAI,aAAa,UAAU,UAAU,KAAK,OAAO,MAAM,KAAK,GAAG,OAAO;CAItE,QAFa,aAAa,kBAAkB,QAAQ,IAAI,QAAQ,KAElD,YAAY;AAC5B;;AAGA,SAAgB,eAAe,OAAuC;CACpE,IAAI,OAAO,MAAM,KAAK,KAAK,UAAU,GAAG,OAAO;CAC/C,OAAO,QAAQ,IAAI,OAAO;AAC5B"}
|
|
@@ -19,7 +19,7 @@ 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-
|
|
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";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"granular-provider-CYroWMjO.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"}
|
|
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"}
|
|
Binary file
|
|
@@ -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-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(() =>
|
|
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
|
/**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GrDescriptionList-2zFXzX83.js","names":[],"sources":["../../../../src/components/GrDescriptionList/GrDescriptionList.vue","../../../../src/components/GrDescriptionList/GrDescriptionList.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref, useSlots } from 'vue'\n\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\n\nimport {\n descriptionColumnsClass,\n descriptionDensityClass,\n descriptionDividedClass,\n descriptionLabelInlineClass,\n descriptionLabelStackedClass,\n descriptionPairFlowClass,\n descriptionPairInlineClass,\n descriptionPairStackedClass,\n descriptionRootClass,\n descriptionRootFlowClass,\n descriptionRootGridClass,\n descriptionSizeClass,\n descriptionValueClass,\n descriptionValueToneClass,\n type GrDescriptionColumns,\n type GrDescriptionDensity,\n type GrDescriptionLayout,\n} from './grDescriptionListStyles'\n\nimport type { GrComponentSize } from '../shared/sizes'\nimport type { GrTone } from '../shared/tones'\n\nexport type {\n GrDescriptionColumns,\n GrDescriptionDensity,\n GrDescriptionLayout,\n} from './grDescriptionListStyles'\n\nexport interface GrDescriptionItem {\n label: string\n /** Значение. `null` и `''` печатаются прочерком — строка не исчезает. */\n value?: string | number | null\n /** Ключ слотов `#value-<name>` и `#label-<name>`. */\n name?: string\n /** Тон значения. Подпись остаётся нейтральной всегда. */\n tone?: GrTone\n}\n\n/**\n * Список пар «характеристика → значение»: настоящий `<dl>` с выравниванием\n * значений в колонку.\n *\n * В отличие от `GrList`, где пункт — это «заголовок с подзаголовком», здесь\n * два разных по роли элемента: термин и его значение.\n */\nexport interface GrDescriptionListProps {\n items: readonly GrDescriptionItem[]\n /**\n * `inline` — подпись слева колонкой; `stacked` — над значением; `flow` —\n * пары текут по строке с переносом (метаданные внутри пункта списка).\n */\n layout?: GrDescriptionLayout\n /** Ширина колонки подписей при `inline`. */\n labelWidth?: string\n /**\n * Ниже какой ширины контейнера (px) `inline` переключается на `stacked`.\n * В узкой колонке фиксированная подпись выжимает значение в букву на строку.\n */\n stackBelow?: number\n density?: GrDescriptionDensity\n /** Отбивать пары линиями. В `flow` не применяется. */\n divided?: boolean\n /** Колонки сетки. В `flow` не применяются: строка колонок не имеет. */\n columns?: GrDescriptionColumns\n /** Чем печатать пустое значение. */\n emptyText?: string\n size?: GrComponentSize\n}\n\nconst props = withDefaults(defineProps<GrDescriptionListProps>(), {\n // Дефолты живут в резолверах ниже: Vue подставил бы свой раньше, чем\n // компонент заглянет в `GrConfigProvider`.\n layout: undefined,\n labelWidth: undefined,\n stackBelow: undefined,\n density: undefined,\n divided: undefined,\n columns: undefined,\n emptyText: undefined,\n size: undefined,\n})\n\nconst slots = useSlots()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrDescriptionList' })\nconst resolvedLayout = useGrComponentProp('GrDescriptionList', 'layout', () => props.layout, 'inline')\nconst resolvedDensity = useGrComponentProp('GrDescriptionList', 'density', () => props.density, 'regular')\nconst resolvedDivided = useGrComponentProp('GrDescriptionList', 'divided', () => props.divided, false)\nconst resolvedColumns = useGrComponentProp('GrDescriptionList', 'columns', () => props.columns, 1)\nconst resolvedLabelWidth = useGrComponentProp('GrDescriptionList', 'labelWidth', () => props.labelWidth, '11rem')\nconst resolvedEmptyText = useGrComponentProp('GrDescriptionList', 'emptyText', () => props.emptyText, '—')\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst narrow = ref(false)\n\n/**\n * `stackBelow` меряет контейнер, а не вьюпорт: пары живут и в узкой карточке\n * на широком экране. Container query здесь не подходит — порог приходит числом\n * в пропе, а `var()` в условии запроса не работает.\n */\nlet observer: ResizeObserver | null = null\n\nonMounted(() => {\n if (props.stackBelow === undefined || typeof ResizeObserver === 'undefined') return\n\n observer = new ResizeObserver(([entry]) => {\n narrow.value = entry.contentRect.width < (props.stackBelow as number)\n })\n if (rootEl.value) observer.observe(rootEl.value)\n})\n\nonBeforeUnmount(() => {\n observer?.disconnect()\n observer = null\n})\n\n/**\n * До монтирования действует заданная раскладка — это же и есть серверный рендер.\n *\n * Порог спасает только `inline`: колонка подписей в узком контейнере выжимает\n * значение в букву на строку. `flow` переносится сам, и подменять его нечем.\n */\nconst effectiveLayout = computed<GrDescriptionLayout>(() => (\n narrow.value && resolvedLayout.value === 'inline' ? 'stacked' : resolvedLayout.value\n))\n\nconst isInline = computed(() => effectiveLayout.value === 'inline')\nconst isFlow = computed(() => effectiveLayout.value === 'flow')\n\nconst rootClass = computed(() => [\n descriptionRootClass,\n isFlow.value ? descriptionRootFlowClass : descriptionRootGridClass,\n // Колонки принадлежат сетке: в строчной раскладке их задавать нечему.\n isFlow.value ? '' : descriptionColumnsClass[resolvedColumns.value],\n descriptionSizeClass[resolvedSize.value],\n descriptionDensityClass[resolvedDensity.value],\n].filter(Boolean).join(' '))\n\nconst pairClass = computed(() => {\n if (isFlow.value) return descriptionPairFlowClass\n\n return [\n isInline.value ? descriptionPairInlineClass : descriptionPairStackedClass,\n resolvedDivided.value ? descriptionDividedClass : '',\n ].filter(Boolean).join(' ')\n})\n\nconst labelClass = computed(() => (\n isInline.value ? descriptionLabelInlineClass : descriptionLabelStackedClass\n))\n\nconst labelStyle = computed(() => (isInline.value ? { width: resolvedLabelWidth.value } : undefined))\n\n/**\n * Пустое значение печатается прочерком, а не пропускается: «поле есть, значения\n * нет» и «поля нет» — разные утверждения, и выпавшая строка ломает и\n * выравнивание, и чтение.\n */\nfunction isEmptyValue(item: GrDescriptionItem): boolean {\n return item.value === null || item.value === undefined || item.value === ''\n}\n\nfunction valueClass(item: GrDescriptionItem): string {\n return [\n descriptionValueClass,\n item.tone ? descriptionValueToneClass[item.tone] : '',\n ].filter(Boolean).join(' ')\n}\n\nfunction pairKey(item: GrDescriptionItem, index: number): string {\n return item.name ?? `${item.label}-${index}`\n}\n\nfunction hasSlot(name: string): boolean {\n return Boolean(slots[name])\n}\n</script>\n\n<template>\n <dl ref=\"rootEl\" data-gr-description-list :class=\"rootClass\">\n <!-- Пара оборачивается в `<div>`: `dl > div > dt + dd` валиден по HTML5 и\n нужен для раскладки. Голые `<div>` вместо `dt`/`dd` — то, ради чего\n компонент и заводится. -->\n <div\n v-for=\"(item, index) in items\"\n :key=\"pairKey(item, index)\"\n data-gr-description-pair\n :class=\"pairClass\"\n >\n <dt data-gr-description-label :class=\"labelClass\" :style=\"labelStyle\">\n <slot v-if=\"item.name && hasSlot(`label-${item.name}`)\" :name=\"`label-${item.name}`\" :item=\"item\" />\n <template v-else>\n {{ item.label }}\n </template>\n </dt>\n\n <dd data-gr-description-value :class=\"valueClass(item)\">\n <slot v-if=\"item.name && hasSlot(`value-${item.name}`)\" :name=\"`value-${item.name}`\" :item=\"item\" />\n <template v-else-if=\"isEmptyValue(item)\">\n <slot name=\"empty\" :item=\"item\">\n {{ resolvedEmptyText }}\n </slot>\n </template>\n <template v-else>\n {{ item.value }}\n </template>\n </dd>\n </div>\n </dl>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, onBeforeUnmount, onMounted, ref, useSlots } from 'vue'\n\nimport { useGrComponentProp, useGrComponentSize } from '../GrConfigProvider/context'\n\nimport {\n descriptionColumnsClass,\n descriptionDensityClass,\n descriptionDividedClass,\n descriptionLabelInlineClass,\n descriptionLabelStackedClass,\n descriptionPairFlowClass,\n descriptionPairInlineClass,\n descriptionPairStackedClass,\n descriptionRootClass,\n descriptionRootFlowClass,\n descriptionRootGridClass,\n descriptionSizeClass,\n descriptionValueClass,\n descriptionValueToneClass,\n type GrDescriptionColumns,\n type GrDescriptionDensity,\n type GrDescriptionLayout,\n} from './grDescriptionListStyles'\n\nimport type { GrComponentSize } from '../shared/sizes'\nimport type { GrTone } from '../shared/tones'\n\nexport type {\n GrDescriptionColumns,\n GrDescriptionDensity,\n GrDescriptionLayout,\n} from './grDescriptionListStyles'\n\nexport interface GrDescriptionItem {\n label: string\n /** Значение. `null` и `''` печатаются прочерком — строка не исчезает. */\n value?: string | number | null\n /** Ключ слотов `#value-<name>` и `#label-<name>`. */\n name?: string\n /** Тон значения. Подпись остаётся нейтральной всегда. */\n tone?: GrTone\n}\n\n/**\n * Список пар «характеристика → значение»: настоящий `<dl>` с выравниванием\n * значений в колонку.\n *\n * В отличие от `GrList`, где пункт — это «заголовок с подзаголовком», здесь\n * два разных по роли элемента: термин и его значение.\n */\nexport interface GrDescriptionListProps {\n items: readonly GrDescriptionItem[]\n /**\n * `inline` — подпись слева колонкой; `stacked` — над значением; `flow` —\n * пары текут по строке с переносом (метаданные внутри пункта списка).\n */\n layout?: GrDescriptionLayout\n /** Ширина колонки подписей при `inline`. */\n labelWidth?: string\n /**\n * Ниже какой ширины контейнера (px) `inline` переключается на `stacked`.\n * В узкой колонке фиксированная подпись выжимает значение в букву на строку.\n */\n stackBelow?: number\n density?: GrDescriptionDensity\n /** Отбивать пары линиями. В `flow` не применяется. */\n divided?: boolean\n /** Колонки сетки. В `flow` не применяются: строка колонок не имеет. */\n columns?: GrDescriptionColumns\n /** Чем печатать пустое значение. */\n emptyText?: string\n size?: GrComponentSize\n}\n\nconst props = withDefaults(defineProps<GrDescriptionListProps>(), {\n // Дефолты живут в резолверах ниже: Vue подставил бы свой раньше, чем\n // компонент заглянет в `GrConfigProvider`.\n layout: undefined,\n labelWidth: undefined,\n stackBelow: undefined,\n density: undefined,\n divided: undefined,\n columns: undefined,\n emptyText: undefined,\n size: undefined,\n})\n\nconst slots = useSlots()\n\nconst resolvedSize = useGrComponentSize(() => props.size, { component: 'GrDescriptionList' })\nconst resolvedLayout = useGrComponentProp('GrDescriptionList', 'layout', () => props.layout, 'inline')\nconst resolvedDensity = useGrComponentProp('GrDescriptionList', 'density', () => props.density, 'regular')\nconst resolvedDivided = useGrComponentProp('GrDescriptionList', 'divided', () => props.divided, false)\nconst resolvedColumns = useGrComponentProp('GrDescriptionList', 'columns', () => props.columns, 1)\nconst resolvedLabelWidth = useGrComponentProp('GrDescriptionList', 'labelWidth', () => props.labelWidth, '11rem')\nconst resolvedEmptyText = useGrComponentProp('GrDescriptionList', 'emptyText', () => props.emptyText, '—')\n\nconst rootEl = ref<HTMLElement | null>(null)\nconst narrow = ref(false)\n\n/**\n * `stackBelow` меряет контейнер, а не вьюпорт: пары живут и в узкой карточке\n * на широком экране. Container query здесь не подходит — порог приходит числом\n * в пропе, а `var()` в условии запроса не работает.\n */\nlet observer: ResizeObserver | null = null\n\nonMounted(() => {\n if (props.stackBelow === undefined || typeof ResizeObserver === 'undefined') return\n\n observer = new ResizeObserver(([entry]) => {\n narrow.value = entry.contentRect.width < (props.stackBelow as number)\n })\n if (rootEl.value) observer.observe(rootEl.value)\n})\n\nonBeforeUnmount(() => {\n observer?.disconnect()\n observer = null\n})\n\n/**\n * До монтирования действует заданная раскладка — это же и есть серверный рендер.\n *\n * Порог спасает только `inline`: колонка подписей в узком контейнере выжимает\n * значение в букву на строку. `flow` переносится сам, и подменять его нечем.\n */\nconst effectiveLayout = computed<GrDescriptionLayout>(() => (\n narrow.value && resolvedLayout.value === 'inline' ? 'stacked' : resolvedLayout.value\n))\n\nconst isInline = computed(() => effectiveLayout.value === 'inline')\nconst isFlow = computed(() => effectiveLayout.value === 'flow')\n\nconst rootClass = computed(() => [\n descriptionRootClass,\n isFlow.value ? descriptionRootFlowClass : descriptionRootGridClass,\n // Колонки принадлежат сетке: в строчной раскладке их задавать нечему.\n isFlow.value ? '' : descriptionColumnsClass[resolvedColumns.value],\n descriptionSizeClass[resolvedSize.value],\n descriptionDensityClass[resolvedDensity.value],\n].filter(Boolean).join(' '))\n\nconst pairClass = computed(() => {\n if (isFlow.value) return descriptionPairFlowClass\n\n return [\n isInline.value ? descriptionPairInlineClass : descriptionPairStackedClass,\n resolvedDivided.value ? descriptionDividedClass : '',\n ].filter(Boolean).join(' ')\n})\n\nconst labelClass = computed(() => (\n isInline.value ? descriptionLabelInlineClass : descriptionLabelStackedClass\n))\n\nconst labelStyle = computed(() => (isInline.value ? { width: resolvedLabelWidth.value } : undefined))\n\n/**\n * Пустое значение печатается прочерком, а не пропускается: «поле есть, значения\n * нет» и «поля нет» — разные утверждения, и выпавшая строка ломает и\n * выравнивание, и чтение.\n */\nfunction isEmptyValue(item: GrDescriptionItem): boolean {\n return item.value === null || item.value === undefined || item.value === ''\n}\n\nfunction valueClass(item: GrDescriptionItem): string {\n return [\n descriptionValueClass,\n item.tone ? descriptionValueToneClass[item.tone] : '',\n ].filter(Boolean).join(' ')\n}\n\nfunction pairKey(item: GrDescriptionItem, index: number): string {\n return item.name ?? `${item.label}-${index}`\n}\n\nfunction hasSlot(name: string): boolean {\n return Boolean(slots[name])\n}\n</script>\n\n<template>\n <dl ref=\"rootEl\" data-gr-description-list :class=\"rootClass\">\n <!-- Пара оборачивается в `<div>`: `dl > div > dt + dd` валиден по HTML5 и\n нужен для раскладки. Голые `<div>` вместо `dt`/`dd` — то, ради чего\n компонент и заводится. -->\n <div\n v-for=\"(item, index) in items\"\n :key=\"pairKey(item, index)\"\n data-gr-description-pair\n :class=\"pairClass\"\n >\n <dt data-gr-description-label :class=\"labelClass\" :style=\"labelStyle\">\n <slot v-if=\"item.name && hasSlot(`label-${item.name}`)\" :name=\"`label-${item.name}`\" :item=\"item\" />\n <template v-else>\n {{ item.label }}\n </template>\n </dt>\n\n <dd data-gr-description-value :class=\"valueClass(item)\">\n <slot v-if=\"item.name && hasSlot(`value-${item.name}`)\" :name=\"`value-${item.name}`\" :item=\"item\" />\n <template v-else-if=\"isEmptyValue(item)\">\n <slot name=\"empty\" :item=\"item\">\n {{ resolvedEmptyText }}\n </slot>\n </template>\n <template v-else>\n {{ item.value }}\n </template>\n </dd>\n </div>\n </dl>\n</template>\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { n as grDescriptionListSafelist, t as grDescriptionListConfig } from "../../chunks/config-
|
|
2
|
-
import { t as GrDescriptionList_default } from "./chunks/GrDescriptionList-
|
|
1
|
+
import { n as grDescriptionListSafelist, t as grDescriptionListConfig } from "../../chunks/config-DCwQBMmJ.js";
|
|
2
|
+
import { t as GrDescriptionList_default } from "./chunks/GrDescriptionList-2zFXzX83.js";
|
|
3
3
|
export { GrDescriptionList_default as GrDescriptionList, GrDescriptionList_default as default, grDescriptionListConfig, grDescriptionListSafelist };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GrFilePreview-rbE3zL9w.js","names":[],"sources":["../../../../src/components/GrFilePreview/fileKindOf.ts","../../../../src/components/GrFilePreview/GrFilePreview.vue","../../../../src/components/GrFilePreview/GrFilePreview.vue"],"sourcesContent":["/**\n * Вид файла по MIME-типу.\n *\n * Тип решает **`mime`, а не расширение**: расширение врёт (`.dat` у выгрузки,\n * `.pdf` у переименованного архива), а бэкенд отдаёт настоящий тип — брать надо\n * его. От вида зависит единственное решение компонента: рисовать `<img>` или\n * иконку.\n */\n\nexport type GrFileKind =\n | 'image'\n | 'pdf'\n | 'document'\n | 'spreadsheet'\n | 'archive'\n /** Тип не опознан, пуст или это `application/octet-stream`. */\n | 'unknown'\n\n/** Точные типы. Порядок не важен — совпадение полное. */\nconst EXACT: Record<string, GrFileKind> = {\n 'application/pdf': 'pdf',\n 'application/x-pdf': 'pdf',\n\n 'application/msword': 'document',\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'document',\n 'application/vnd.oasis.opendocument.text': 'document',\n 'application/rtf': 'document',\n\n 'application/vnd.ms-excel': 'spreadsheet',\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'spreadsheet',\n 'application/vnd.oasis.opendocument.spreadsheet': 'spreadsheet',\n 'text/csv': 'spreadsheet',\n 'text/tab-separated-values': 'spreadsheet',\n\n 'application/zip': 'archive',\n 'application/x-zip-compressed': 'archive',\n 'application/x-rar-compressed': 'archive',\n 'application/vnd.rar': 'archive',\n 'application/x-7z-compressed': 'archive',\n 'application/x-tar': 'archive',\n 'application/gzip': 'archive',\n 'application/x-gzip': 'archive',\n}\n\n/**\n * Приведение к сравнимому виду: тип приходит из БД и бывает с параметрами\n * (`text/plain; charset=utf-8`) и в любом регистре.\n */\nfunction normalize(mime: string): string {\n return mime.split(';')[0].trim().toLowerCase()\n}\n\nexport function fileKindOf(mime: string | null | undefined): GrFileKind {\n if (!mime) return 'unknown'\n\n const type = normalize(mime)\n if (!type) return 'unknown'\n\n // Картинка — единственный вид, который компонент рисует, а не подписывает.\n if (type.startsWith('image/')) return 'image'\n\n const exact = EXACT[type]\n if (exact) return exact\n\n // `text/csv` уже разобран выше: таблица важнее общего правила для текста.\n if (type.startsWith('text/')) return 'document'\n\n return 'unknown'\n}\n\n/** Картинку показываем, остальное — подписываем иконкой. */\nexport function isPreviewableKind(kind: GrFileKind): boolean {\n return kind === 'image'\n}\n","<script setup lang=\"ts\">\nimport { computed, markRaw, ref, watch, type Component } from 'vue'\n\nimport { useGrComponentProp } from '../GrConfigProvider/context'\nimport GrSkeleton from '../GrSkeleton/GrSkeleton.vue'\n\nimport IconFile from '~icons/lucide/file'\nimport IconFileArchive from '~icons/lucide/file-archive'\nimport IconFileSpreadsheet from '~icons/lucide/file-spreadsheet'\nimport IconFileText from '~icons/lucide/file-text'\nimport IconFileType from '~icons/lucide/file-type'\nimport IconImageOff from '~icons/lucide/image-off'\n\nimport type { GrSizeWithPx } from '../shared/sizes'\n\nimport { fileKindOf, isPreviewableKind, type GrFileKind } from './fileKindOf'\nimport {\n filePreviewFallbackClass,\n filePreviewIconClass,\n filePreviewInteractiveClass,\n filePreviewLabelClass,\n filePreviewMediaClass,\n filePreviewMediaLoadingClass,\n filePreviewRatioClass,\n filePreviewRootClass,\n filePreviewTileWidths,\n type GrFilePreviewRatio,\n} from './grFilePreviewStyles'\n\nexport type { GrFileKind } from './fileKindOf'\nexport type { GrFilePreviewRatio } from './grFilePreviewStyles'\n\n/**\n * Плитка превью файла с фолбэком по типу.\n *\n * Показывает **сохранённый** файл, а не отправляет новый: отправка — это\n * `GrFormFile` и `GrFileUpload`. Просмотрщик во весь экран открывает\n * потребитель по событию `click`.\n */\nexport interface GrFilePreviewProps {\n /** Адрес превью. Пусто — сразу заглушка по типу. */\n src?: string | null\n /** MIME-тип. Он решает, картинка это или файл. */\n mime?: string | null\n /** Имя файла: доступное имя картинки и подпись заглушки. */\n name?: string | null\n /**\n * Ступень канонической шкалы либо произвольная ширина в пикселях. Число —\n * escape-hatch, как диаметр у GrAvatar: плитка 96px в ленте вложений в\n * четыре ступени не укладывается.\n */\n tileSize?: GrSizeWithPx\n ratio?: GrFilePreviewRatio\n /** Ссылка на оригинал — для не-картинок и для перехода мимо просмотрщика. */\n href?: string\n /** Свой корневой тег (`RouterLink`, `Link` от Inertia). Сильнее `href`. */\n as?: string | Component\n /** Плитка кликабельна и эмитит `click` — обычно чтобы открыть просмотрщик. */\n clickable?: boolean\n loading?: 'lazy' | 'eager'\n /**\n * Доступное имя интерактивной плитки. Не задано — имя приходит из содержимого:\n * `alt` картинки или подписи заглушки.\n */\n ariaLabel?: string\n}\n\nexport interface GrFilePreviewEmits {\n (e: 'click', event: MouseEvent): void\n}\n\nconst props = withDefaults(defineProps<GrFilePreviewProps>(), {\n // Дефолты живут в резолверах ниже: Vue подставил бы свой раньше, чем компонент\n // заглянет в `GrConfigProvider`.\n src: undefined,\n mime: undefined,\n name: undefined,\n tileSize: undefined,\n ratio: undefined,\n href: undefined,\n as: undefined,\n clickable: false,\n loading: undefined,\n ariaLabel: undefined,\n})\n\nconst emit = defineEmits<GrFilePreviewEmits>()\n\nconst resolvedTileSize = useGrComponentProp('GrFilePreview', 'tileSize', () => props.tileSize, 'md')\nconst resolvedRatio = useGrComponentProp('GrFilePreview', 'ratio', () => props.ratio, '1:1')\nconst resolvedLoading = useGrComponentProp('GrFilePreview', 'loading', () => props.loading, 'lazy')\n\nconst kind = computed<GrFileKind>(() => fileKindOf(props.mime))\n\n/**\n * Состояний три, а не два. Сорвавшаяся загрузка деградирует в заглушку: превью\n * может исчезнуть с диска, и «сломанная картинка» здесь не информативнее\n * иконки. Отдельное `loading` нужно ленте вложений: два десятка плиток\n * приезжают вразнобой, и без него сетка стоит дырами непонятной природы —\n * «ещё едет» и «файл без превью» выглядят одинаково.\n */\ntype ImageState = 'loading' | 'loaded' | 'error'\n\nconst imageState = ref<ImageState>('loading')\n\n// Новая ссылка не должна наследовать ни ошибку прошлой, ни её готовность.\nwatch(() => props.src, () => { imageState.value = 'loading' })\n\nconst showImage = computed(() => (\n isPreviewableKind(kind.value) && Boolean(props.src) && imageState.value !== 'error'\n))\n\nconst showSkeleton = computed(() => showImage.value && imageState.value === 'loading')\n\n/**\n * Иконка вида. Картинка попадает сюда только сорвавшейся загрузкой — тогда и\n * значок другой: «изображение не открылось», а не «это файл».\n */\nconst iconByKind: Record<GrFileKind, Component> = {\n image: markRaw(IconImageOff),\n pdf: markRaw(IconFileText),\n document: markRaw(IconFileType),\n spreadsheet: markRaw(IconFileSpreadsheet),\n archive: markRaw(IconFileArchive),\n unknown: markRaw(IconFile),\n}\n\nconst fallbackIcon = computed(() => iconByKind[kind.value])\n\nconst isInteractive = computed(() => Boolean(props.as) || Boolean(props.href) || props.clickable)\n\nconst rootTag = computed<string | Component>(() => {\n if (props.as) return typeof props.as === 'string' ? props.as : markRaw(props.as)\n if (props.href) return 'a'\n return props.clickable ? 'button' : 'div'\n})\n\n/**\n * Компонент-ссылка (`Link` от Inertia, `RouterLink`) рендерит `<a>` сам, и без\n * `href` он ведёт в никуда. Строковый тег, кроме `a`, атрибут не понимает —\n * там он и гасится.\n */\nconst rootHref = computed(() => (\n typeof rootTag.value === 'string' && rootTag.value !== 'a' ? undefined : props.href\n))\n\nconst rootClass = computed(() => [\n filePreviewRootClass,\n filePreviewRatioClass[resolvedRatio.value],\n typeof resolvedTileSize.value === 'number' ? '' : filePreviewTileWidths[resolvedTileSize.value],\n isInteractive.value ? filePreviewInteractiveClass : '',\n].filter(Boolean).join(' '))\n\nconst rootStyle = computed(() => (\n typeof resolvedTileSize.value === 'number' ? { width: `${resolvedTileSize.value}px` } : undefined\n))\n\n/**\n * `alt` не выдумывается. Есть имя — оно и есть `alt`; нет — картинка\n * декоративна, и придуманное компонентом описание было бы хуже пустого.\n */\nconst alt = computed(() => props.name ?? '')\n\nfunction onClick(event: MouseEvent): void {\n emit('click', event)\n}\n</script>\n\n<template>\n <component\n :is=\"rootTag\"\n data-gr-file-preview\n :data-kind=\"kind\"\n :type=\"rootTag === 'button' ? 'button' : undefined\"\n :href=\"rootHref\"\n :class=\"rootClass\"\n :style=\"rootStyle\"\n :aria-label=\"ariaLabel\"\n @click=\"onClick\"\n >\n <GrSkeleton\n v-if=\"showSkeleton\"\n data-gr-file-preview-skeleton\n width=\"100%\"\n height=\"100%\"\n rounded=\"var(--gr-file-preview-radius, var(--gr-radius-md))\"\n />\n\n <!--\n Условие — `showImage`, а не «картинка готова»: убери её из дерева на\n время загрузки, и браузер не начнёт качать, а `load` не придёт никогда.\n Поэтому она ждёт невидимой и вне потока — иначе поделила бы плитку со\n скелетом как второй flex-элемент.\n -->\n <img\n v-if=\"showImage\"\n :key=\"src ?? ''\"\n data-gr-file-preview-image\n :src=\"src ?? undefined\"\n :alt=\"alt\"\n :loading=\"resolvedLoading\"\n :class=\"[filePreviewMediaClass, showSkeleton ? filePreviewMediaLoadingClass : '']\"\n @load=\"imageState = 'loaded'\"\n @error=\"imageState = 'error'\"\n >\n\n <span v-else data-gr-file-preview-fallback :class=\"filePreviewFallbackClass\">\n <component :is=\"fallbackIcon\" :class=\"filePreviewIconClass\" aria-hidden=\"true\" />\n <span v-if=\"name\" data-gr-file-preview-label :class=\"filePreviewLabelClass\">{{ name }}</span>\n </span>\n </component>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, markRaw, ref, watch, type Component } from 'vue'\n\nimport { useGrComponentProp } from '../GrConfigProvider/context'\nimport GrSkeleton from '../GrSkeleton/GrSkeleton.vue'\n\nimport IconFile from '~icons/lucide/file'\nimport IconFileArchive from '~icons/lucide/file-archive'\nimport IconFileSpreadsheet from '~icons/lucide/file-spreadsheet'\nimport IconFileText from '~icons/lucide/file-text'\nimport IconFileType from '~icons/lucide/file-type'\nimport IconImageOff from '~icons/lucide/image-off'\n\nimport type { GrSizeWithPx } from '../shared/sizes'\n\nimport { fileKindOf, isPreviewableKind, type GrFileKind } from './fileKindOf'\nimport {\n filePreviewFallbackClass,\n filePreviewIconClass,\n filePreviewInteractiveClass,\n filePreviewLabelClass,\n filePreviewMediaClass,\n filePreviewMediaLoadingClass,\n filePreviewRatioClass,\n filePreviewRootClass,\n filePreviewTileWidths,\n type GrFilePreviewRatio,\n} from './grFilePreviewStyles'\n\nexport type { GrFileKind } from './fileKindOf'\nexport type { GrFilePreviewRatio } from './grFilePreviewStyles'\n\n/**\n * Плитка превью файла с фолбэком по типу.\n *\n * Показывает **сохранённый** файл, а не отправляет новый: отправка — это\n * `GrFormFile` и `GrFileUpload`. Просмотрщик во весь экран открывает\n * потребитель по событию `click`.\n */\nexport interface GrFilePreviewProps {\n /** Адрес превью. Пусто — сразу заглушка по типу. */\n src?: string | null\n /** MIME-тип. Он решает, картинка это или файл. */\n mime?: string | null\n /** Имя файла: доступное имя картинки и подпись заглушки. */\n name?: string | null\n /**\n * Ступень канонической шкалы либо произвольная ширина в пикселях. Число —\n * escape-hatch, как диаметр у GrAvatar: плитка 96px в ленте вложений в\n * четыре ступени не укладывается.\n */\n tileSize?: GrSizeWithPx\n ratio?: GrFilePreviewRatio\n /** Ссылка на оригинал — для не-картинок и для перехода мимо просмотрщика. */\n href?: string\n /** Свой корневой тег (`RouterLink`, `Link` от Inertia). Сильнее `href`. */\n as?: string | Component\n /** Плитка кликабельна и эмитит `click` — обычно чтобы открыть просмотрщик. */\n clickable?: boolean\n loading?: 'lazy' | 'eager'\n /**\n * Доступное имя интерактивной плитки. Не задано — имя приходит из содержимого:\n * `alt` картинки или подписи заглушки.\n */\n ariaLabel?: string\n}\n\nexport interface GrFilePreviewEmits {\n (e: 'click', event: MouseEvent): void\n}\n\nconst props = withDefaults(defineProps<GrFilePreviewProps>(), {\n // Дефолты живут в резолверах ниже: Vue подставил бы свой раньше, чем компонент\n // заглянет в `GrConfigProvider`.\n src: undefined,\n mime: undefined,\n name: undefined,\n tileSize: undefined,\n ratio: undefined,\n href: undefined,\n as: undefined,\n clickable: false,\n loading: undefined,\n ariaLabel: undefined,\n})\n\nconst emit = defineEmits<GrFilePreviewEmits>()\n\nconst resolvedTileSize = useGrComponentProp('GrFilePreview', 'tileSize', () => props.tileSize, 'md')\nconst resolvedRatio = useGrComponentProp('GrFilePreview', 'ratio', () => props.ratio, '1:1')\nconst resolvedLoading = useGrComponentProp('GrFilePreview', 'loading', () => props.loading, 'lazy')\n\nconst kind = computed<GrFileKind>(() => fileKindOf(props.mime))\n\n/**\n * Состояний три, а не два. Сорвавшаяся загрузка деградирует в заглушку: превью\n * может исчезнуть с диска, и «сломанная картинка» здесь не информативнее\n * иконки. Отдельное `loading` нужно ленте вложений: два десятка плиток\n * приезжают вразнобой, и без него сетка стоит дырами непонятной природы —\n * «ещё едет» и «файл без превью» выглядят одинаково.\n */\ntype ImageState = 'loading' | 'loaded' | 'error'\n\nconst imageState = ref<ImageState>('loading')\n\n// Новая ссылка не должна наследовать ни ошибку прошлой, ни её готовность.\nwatch(() => props.src, () => { imageState.value = 'loading' })\n\nconst showImage = computed(() => (\n isPreviewableKind(kind.value) && Boolean(props.src) && imageState.value !== 'error'\n))\n\nconst showSkeleton = computed(() => showImage.value && imageState.value === 'loading')\n\n/**\n * Иконка вида. Картинка попадает сюда только сорвавшейся загрузкой — тогда и\n * значок другой: «изображение не открылось», а не «это файл».\n */\nconst iconByKind: Record<GrFileKind, Component> = {\n image: markRaw(IconImageOff),\n pdf: markRaw(IconFileText),\n document: markRaw(IconFileType),\n spreadsheet: markRaw(IconFileSpreadsheet),\n archive: markRaw(IconFileArchive),\n unknown: markRaw(IconFile),\n}\n\nconst fallbackIcon = computed(() => iconByKind[kind.value])\n\nconst isInteractive = computed(() => Boolean(props.as) || Boolean(props.href) || props.clickable)\n\nconst rootTag = computed<string | Component>(() => {\n if (props.as) return typeof props.as === 'string' ? props.as : markRaw(props.as)\n if (props.href) return 'a'\n return props.clickable ? 'button' : 'div'\n})\n\n/**\n * Компонент-ссылка (`Link` от Inertia, `RouterLink`) рендерит `<a>` сам, и без\n * `href` он ведёт в никуда. Строковый тег, кроме `a`, атрибут не понимает —\n * там он и гасится.\n */\nconst rootHref = computed(() => (\n typeof rootTag.value === 'string' && rootTag.value !== 'a' ? undefined : props.href\n))\n\nconst rootClass = computed(() => [\n filePreviewRootClass,\n filePreviewRatioClass[resolvedRatio.value],\n typeof resolvedTileSize.value === 'number' ? '' : filePreviewTileWidths[resolvedTileSize.value],\n isInteractive.value ? filePreviewInteractiveClass : '',\n].filter(Boolean).join(' '))\n\nconst rootStyle = computed(() => (\n typeof resolvedTileSize.value === 'number' ? { width: `${resolvedTileSize.value}px` } : undefined\n))\n\n/**\n * `alt` не выдумывается. Есть имя — оно и есть `alt`; нет — картинка\n * декоративна, и придуманное компонентом описание было бы хуже пустого.\n */\nconst alt = computed(() => props.name ?? '')\n\nfunction onClick(event: MouseEvent): void {\n emit('click', event)\n}\n</script>\n\n<template>\n <component\n :is=\"rootTag\"\n data-gr-file-preview\n :data-kind=\"kind\"\n :type=\"rootTag === 'button' ? 'button' : undefined\"\n :href=\"rootHref\"\n :class=\"rootClass\"\n :style=\"rootStyle\"\n :aria-label=\"ariaLabel\"\n @click=\"onClick\"\n >\n <GrSkeleton\n v-if=\"showSkeleton\"\n data-gr-file-preview-skeleton\n width=\"100%\"\n height=\"100%\"\n rounded=\"var(--gr-file-preview-radius, var(--gr-radius-md))\"\n />\n\n <!--\n Условие — `showImage`, а не «картинка готова»: убери её из дерева на\n время загрузки, и браузер не начнёт качать, а `load` не придёт никогда.\n Поэтому она ждёт невидимой и вне потока — иначе поделила бы плитку со\n скелетом как второй flex-элемент.\n -->\n <img\n v-if=\"showImage\"\n :key=\"src ?? ''\"\n data-gr-file-preview-image\n :src=\"src ?? undefined\"\n :alt=\"alt\"\n :loading=\"resolvedLoading\"\n :class=\"[filePreviewMediaClass, showSkeleton ? filePreviewMediaLoadingClass : '']\"\n @load=\"imageState = 'loaded'\"\n @error=\"imageState = 'error'\"\n >\n\n <span v-else data-gr-file-preview-fallback :class=\"filePreviewFallbackClass\">\n <component :is=\"fallbackIcon\" :class=\"filePreviewIconClass\" aria-hidden=\"true\" />\n <span v-if=\"name\" data-gr-file-preview-label :class=\"filePreviewLabelClass\">{{ name }}</span>\n </span>\n </component>\n</template>\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"GrFilePreview-D0Bt-0nn.js","names":[],"sources":["../../../../src/components/GrFilePreview/fileKindOf.ts","../../../../src/components/GrFilePreview/GrFilePreview.vue","../../../../src/components/GrFilePreview/GrFilePreview.vue"],"sourcesContent":["/**\n * Вид файла по MIME-типу.\n *\n * Тип решает **`mime`, а не расширение**: расширение врёт (`.dat` у выгрузки,\n * `.pdf` у переименованного архива), а бэкенд отдаёт настоящий тип — брать надо\n * его. От вида зависит единственное решение компонента: рисовать `<img>` или\n * иконку.\n */\n\nexport type GrFileKind =\n | 'image'\n | 'pdf'\n | 'document'\n | 'spreadsheet'\n | 'archive'\n /** Тип не опознан, пуст или это `application/octet-stream`. */\n | 'unknown'\n\n/** Точные типы. Порядок не важен — совпадение полное. */\nconst EXACT: Record<string, GrFileKind> = {\n 'application/pdf': 'pdf',\n 'application/x-pdf': 'pdf',\n\n 'application/msword': 'document',\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'document',\n 'application/vnd.oasis.opendocument.text': 'document',\n 'application/rtf': 'document',\n\n 'application/vnd.ms-excel': 'spreadsheet',\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'spreadsheet',\n 'application/vnd.oasis.opendocument.spreadsheet': 'spreadsheet',\n 'text/csv': 'spreadsheet',\n 'text/tab-separated-values': 'spreadsheet',\n\n 'application/zip': 'archive',\n 'application/x-zip-compressed': 'archive',\n 'application/x-rar-compressed': 'archive',\n 'application/vnd.rar': 'archive',\n 'application/x-7z-compressed': 'archive',\n 'application/x-tar': 'archive',\n 'application/gzip': 'archive',\n 'application/x-gzip': 'archive',\n}\n\n/**\n * Приведение к сравнимому виду: тип приходит из БД и бывает с параметрами\n * (`text/plain; charset=utf-8`) и в любом регистре.\n */\nfunction normalize(mime: string): string {\n return mime.split(';')[0].trim().toLowerCase()\n}\n\nexport function fileKindOf(mime: string | null | undefined): GrFileKind {\n if (!mime) return 'unknown'\n\n const type = normalize(mime)\n if (!type) return 'unknown'\n\n // Картинка — единственный вид, который компонент рисует, а не подписывает.\n if (type.startsWith('image/')) return 'image'\n\n const exact = EXACT[type]\n if (exact) return exact\n\n // `text/csv` уже разобран выше: таблица важнее общего правила для текста.\n if (type.startsWith('text/')) return 'document'\n\n return 'unknown'\n}\n\n/** Картинку показываем, остальное — подписываем иконкой. */\nexport function isPreviewableKind(kind: GrFileKind): boolean {\n return kind === 'image'\n}\n","<script setup lang=\"ts\">\nimport { computed, markRaw, ref, watch, type Component } from 'vue'\n\nimport { useGrComponentProp } from '../GrConfigProvider/context'\nimport GrSkeleton from '../GrSkeleton/GrSkeleton.vue'\n\nimport IconFile from '~icons/lucide/file'\nimport IconFileArchive from '~icons/lucide/file-archive'\nimport IconFileSpreadsheet from '~icons/lucide/file-spreadsheet'\nimport IconFileText from '~icons/lucide/file-text'\nimport IconFileType from '~icons/lucide/file-type'\nimport IconImageOff from '~icons/lucide/image-off'\n\nimport type { GrSizeWithPx } from '../shared/sizes'\n\nimport { fileKindOf, isPreviewableKind, type GrFileKind } from './fileKindOf'\nimport {\n filePreviewFallbackClass,\n filePreviewIconClass,\n filePreviewInteractiveClass,\n filePreviewLabelClass,\n filePreviewMediaClass,\n filePreviewMediaLoadingClass,\n filePreviewRatioClass,\n filePreviewRootClass,\n filePreviewTileWidths,\n type GrFilePreviewRatio,\n} from './grFilePreviewStyles'\n\nexport type { GrFileKind } from './fileKindOf'\nexport type { GrFilePreviewRatio } from './grFilePreviewStyles'\n\n/**\n * Плитка превью файла с фолбэком по типу.\n *\n * Показывает **сохранённый** файл, а не отправляет новый: отправка — это\n * `GrFormFile` и `GrFileUpload`. Просмотрщик во весь экран открывает\n * потребитель по событию `click`.\n */\nexport interface GrFilePreviewProps {\n /** Адрес превью. Пусто — сразу заглушка по типу. */\n src?: string | null\n /** MIME-тип. Он решает, картинка это или файл. */\n mime?: string | null\n /** Имя файла: доступное имя картинки и подпись заглушки. */\n name?: string | null\n /**\n * Ступень канонической шкалы либо произвольная ширина в пикселях. Число —\n * escape-hatch, как диаметр у GrAvatar: плитка 96px в ленте вложений в\n * четыре ступени не укладывается.\n */\n tileSize?: GrSizeWithPx\n ratio?: GrFilePreviewRatio\n /** Ссылка на оригинал — для не-картинок и для перехода мимо просмотрщика. */\n href?: string\n /** Свой корневой тег (`RouterLink`, `Link` от Inertia). Сильнее `href`. */\n as?: string | Component\n /** Плитка кликабельна и эмитит `click` — обычно чтобы открыть просмотрщик. */\n clickable?: boolean\n loading?: 'lazy' | 'eager'\n /**\n * Доступное имя интерактивной плитки. Не задано — имя приходит из содержимого:\n * `alt` картинки или подписи заглушки.\n */\n ariaLabel?: string\n}\n\nexport interface GrFilePreviewEmits {\n (e: 'click', event: MouseEvent): void\n}\n\nconst props = withDefaults(defineProps<GrFilePreviewProps>(), {\n // Дефолты живут в резолверах ниже: Vue подставил бы свой раньше, чем компонент\n // заглянет в `GrConfigProvider`.\n src: undefined,\n mime: undefined,\n name: undefined,\n tileSize: undefined,\n ratio: undefined,\n href: undefined,\n as: undefined,\n clickable: false,\n loading: undefined,\n ariaLabel: undefined,\n})\n\nconst emit = defineEmits<GrFilePreviewEmits>()\n\nconst resolvedTileSize = useGrComponentProp('GrFilePreview', 'tileSize', () => props.tileSize, 'md')\nconst resolvedRatio = useGrComponentProp('GrFilePreview', 'ratio', () => props.ratio, '1:1')\nconst resolvedLoading = useGrComponentProp('GrFilePreview', 'loading', () => props.loading, 'lazy')\n\nconst kind = computed<GrFileKind>(() => fileKindOf(props.mime))\n\n/**\n * Состояний три, а не два. Сорвавшаяся загрузка деградирует в заглушку: превью\n * может исчезнуть с диска, и «сломанная картинка» здесь не информативнее\n * иконки. Отдельное `loading` нужно ленте вложений: два десятка плиток\n * приезжают вразнобой, и без него сетка стоит дырами непонятной природы —\n * «ещё едет» и «файл без превью» выглядят одинаково.\n */\ntype ImageState = 'loading' | 'loaded' | 'error'\n\nconst imageState = ref<ImageState>('loading')\n\n// Новая ссылка не должна наследовать ни ошибку прошлой, ни её готовность.\nwatch(() => props.src, () => { imageState.value = 'loading' })\n\nconst showImage = computed(() => (\n isPreviewableKind(kind.value) && Boolean(props.src) && imageState.value !== 'error'\n))\n\nconst showSkeleton = computed(() => showImage.value && imageState.value === 'loading')\n\n/**\n * Иконка вида. Картинка попадает сюда только сорвавшейся загрузкой — тогда и\n * значок другой: «изображение не открылось», а не «это файл».\n */\nconst iconByKind: Record<GrFileKind, Component> = {\n image: markRaw(IconImageOff),\n pdf: markRaw(IconFileText),\n document: markRaw(IconFileType),\n spreadsheet: markRaw(IconFileSpreadsheet),\n archive: markRaw(IconFileArchive),\n unknown: markRaw(IconFile),\n}\n\nconst fallbackIcon = computed(() => iconByKind[kind.value])\n\nconst isInteractive = computed(() => Boolean(props.as) || Boolean(props.href) || props.clickable)\n\nconst rootTag = computed<string | Component>(() => {\n if (props.as) return typeof props.as === 'string' ? props.as : markRaw(props.as)\n if (props.href) return 'a'\n return props.clickable ? 'button' : 'div'\n})\n\n/**\n * Компонент-ссылка (`Link` от Inertia, `RouterLink`) рендерит `<a>` сам, и без\n * `href` он ведёт в никуда. Строковый тег, кроме `a`, атрибут не понимает —\n * там он и гасится.\n */\nconst rootHref = computed(() => (\n typeof rootTag.value === 'string' && rootTag.value !== 'a' ? undefined : props.href\n))\n\nconst rootClass = computed(() => [\n filePreviewRootClass,\n filePreviewRatioClass[resolvedRatio.value],\n typeof resolvedTileSize.value === 'number' ? '' : filePreviewTileWidths[resolvedTileSize.value],\n isInteractive.value ? filePreviewInteractiveClass : '',\n].filter(Boolean).join(' '))\n\nconst rootStyle = computed(() => (\n typeof resolvedTileSize.value === 'number' ? { width: `${resolvedTileSize.value}px` } : undefined\n))\n\n/**\n * `alt` не выдумывается. Есть имя — оно и есть `alt`; нет — картинка\n * декоративна, и придуманное компонентом описание было бы хуже пустого.\n */\nconst alt = computed(() => props.name ?? '')\n\nfunction onClick(event: MouseEvent): void {\n emit('click', event)\n}\n</script>\n\n<template>\n <component\n :is=\"rootTag\"\n data-gr-file-preview\n :data-kind=\"kind\"\n :type=\"rootTag === 'button' ? 'button' : undefined\"\n :href=\"rootHref\"\n :class=\"rootClass\"\n :style=\"rootStyle\"\n :aria-label=\"ariaLabel\"\n @click=\"onClick\"\n >\n <GrSkeleton\n v-if=\"showSkeleton\"\n data-gr-file-preview-skeleton\n width=\"100%\"\n height=\"100%\"\n rounded=\"var(--gr-file-preview-radius, var(--gr-radius-md))\"\n />\n\n <!--\n Условие — `showImage`, а не «картинка готова»: убери её из дерева на\n время загрузки, и браузер не начнёт качать, а `load` не придёт никогда.\n Поэтому она ждёт невидимой и вне потока — иначе поделила бы плитку со\n скелетом как второй flex-элемент.\n -->\n <img\n v-if=\"showImage\"\n :key=\"src ?? ''\"\n data-gr-file-preview-image\n :src=\"src ?? undefined\"\n :alt=\"alt\"\n :loading=\"resolvedLoading\"\n :class=\"[filePreviewMediaClass, showSkeleton ? filePreviewMediaLoadingClass : '']\"\n @load=\"imageState = 'loaded'\"\n @error=\"imageState = 'error'\"\n >\n\n <span v-else data-gr-file-preview-fallback :class=\"filePreviewFallbackClass\">\n <component :is=\"fallbackIcon\" :class=\"filePreviewIconClass\" aria-hidden=\"true\" />\n <span v-if=\"name\" data-gr-file-preview-label :class=\"filePreviewLabelClass\">{{ name }}</span>\n </span>\n </component>\n</template>\n","<script setup lang=\"ts\">\nimport { computed, markRaw, ref, watch, type Component } from 'vue'\n\nimport { useGrComponentProp } from '../GrConfigProvider/context'\nimport GrSkeleton from '../GrSkeleton/GrSkeleton.vue'\n\nimport IconFile from '~icons/lucide/file'\nimport IconFileArchive from '~icons/lucide/file-archive'\nimport IconFileSpreadsheet from '~icons/lucide/file-spreadsheet'\nimport IconFileText from '~icons/lucide/file-text'\nimport IconFileType from '~icons/lucide/file-type'\nimport IconImageOff from '~icons/lucide/image-off'\n\nimport type { GrSizeWithPx } from '../shared/sizes'\n\nimport { fileKindOf, isPreviewableKind, type GrFileKind } from './fileKindOf'\nimport {\n filePreviewFallbackClass,\n filePreviewIconClass,\n filePreviewInteractiveClass,\n filePreviewLabelClass,\n filePreviewMediaClass,\n filePreviewMediaLoadingClass,\n filePreviewRatioClass,\n filePreviewRootClass,\n filePreviewTileWidths,\n type GrFilePreviewRatio,\n} from './grFilePreviewStyles'\n\nexport type { GrFileKind } from './fileKindOf'\nexport type { GrFilePreviewRatio } from './grFilePreviewStyles'\n\n/**\n * Плитка превью файла с фолбэком по типу.\n *\n * Показывает **сохранённый** файл, а не отправляет новый: отправка — это\n * `GrFormFile` и `GrFileUpload`. Просмотрщик во весь экран открывает\n * потребитель по событию `click`.\n */\nexport interface GrFilePreviewProps {\n /** Адрес превью. Пусто — сразу заглушка по типу. */\n src?: string | null\n /** MIME-тип. Он решает, картинка это или файл. */\n mime?: string | null\n /** Имя файла: доступное имя картинки и подпись заглушки. */\n name?: string | null\n /**\n * Ступень канонической шкалы либо произвольная ширина в пикселях. Число —\n * escape-hatch, как диаметр у GrAvatar: плитка 96px в ленте вложений в\n * четыре ступени не укладывается.\n */\n tileSize?: GrSizeWithPx\n ratio?: GrFilePreviewRatio\n /** Ссылка на оригинал — для не-картинок и для перехода мимо просмотрщика. */\n href?: string\n /** Свой корневой тег (`RouterLink`, `Link` от Inertia). Сильнее `href`. */\n as?: string | Component\n /** Плитка кликабельна и эмитит `click` — обычно чтобы открыть просмотрщик. */\n clickable?: boolean\n loading?: 'lazy' | 'eager'\n /**\n * Доступное имя интерактивной плитки. Не задано — имя приходит из содержимого:\n * `alt` картинки или подписи заглушки.\n */\n ariaLabel?: string\n}\n\nexport interface GrFilePreviewEmits {\n (e: 'click', event: MouseEvent): void\n}\n\nconst props = withDefaults(defineProps<GrFilePreviewProps>(), {\n // Дефолты живут в резолверах ниже: Vue подставил бы свой раньше, чем компонент\n // заглянет в `GrConfigProvider`.\n src: undefined,\n mime: undefined,\n name: undefined,\n tileSize: undefined,\n ratio: undefined,\n href: undefined,\n as: undefined,\n clickable: false,\n loading: undefined,\n ariaLabel: undefined,\n})\n\nconst emit = defineEmits<GrFilePreviewEmits>()\n\nconst resolvedTileSize = useGrComponentProp('GrFilePreview', 'tileSize', () => props.tileSize, 'md')\nconst resolvedRatio = useGrComponentProp('GrFilePreview', 'ratio', () => props.ratio, '1:1')\nconst resolvedLoading = useGrComponentProp('GrFilePreview', 'loading', () => props.loading, 'lazy')\n\nconst kind = computed<GrFileKind>(() => fileKindOf(props.mime))\n\n/**\n * Состояний три, а не два. Сорвавшаяся загрузка деградирует в заглушку: превью\n * может исчезнуть с диска, и «сломанная картинка» здесь не информативнее\n * иконки. Отдельное `loading` нужно ленте вложений: два десятка плиток\n * приезжают вразнобой, и без него сетка стоит дырами непонятной природы —\n * «ещё едет» и «файл без превью» выглядят одинаково.\n */\ntype ImageState = 'loading' | 'loaded' | 'error'\n\nconst imageState = ref<ImageState>('loading')\n\n// Новая ссылка не должна наследовать ни ошибку прошлой, ни её готовность.\nwatch(() => props.src, () => { imageState.value = 'loading' })\n\nconst showImage = computed(() => (\n isPreviewableKind(kind.value) && Boolean(props.src) && imageState.value !== 'error'\n))\n\nconst showSkeleton = computed(() => showImage.value && imageState.value === 'loading')\n\n/**\n * Иконка вида. Картинка попадает сюда только сорвавшейся загрузкой — тогда и\n * значок другой: «изображение не открылось», а не «это файл».\n */\nconst iconByKind: Record<GrFileKind, Component> = {\n image: markRaw(IconImageOff),\n pdf: markRaw(IconFileText),\n document: markRaw(IconFileType),\n spreadsheet: markRaw(IconFileSpreadsheet),\n archive: markRaw(IconFileArchive),\n unknown: markRaw(IconFile),\n}\n\nconst fallbackIcon = computed(() => iconByKind[kind.value])\n\nconst isInteractive = computed(() => Boolean(props.as) || Boolean(props.href) || props.clickable)\n\nconst rootTag = computed<string | Component>(() => {\n if (props.as) return typeof props.as === 'string' ? props.as : markRaw(props.as)\n if (props.href) return 'a'\n return props.clickable ? 'button' : 'div'\n})\n\n/**\n * Компонент-ссылка (`Link` от Inertia, `RouterLink`) рендерит `<a>` сам, и без\n * `href` он ведёт в никуда. Строковый тег, кроме `a`, атрибут не понимает —\n * там он и гасится.\n */\nconst rootHref = computed(() => (\n typeof rootTag.value === 'string' && rootTag.value !== 'a' ? undefined : props.href\n))\n\nconst rootClass = computed(() => [\n filePreviewRootClass,\n filePreviewRatioClass[resolvedRatio.value],\n typeof resolvedTileSize.value === 'number' ? '' : filePreviewTileWidths[resolvedTileSize.value],\n isInteractive.value ? filePreviewInteractiveClass : '',\n].filter(Boolean).join(' '))\n\nconst rootStyle = computed(() => (\n typeof resolvedTileSize.value === 'number' ? { width: `${resolvedTileSize.value}px` } : undefined\n))\n\n/**\n * `alt` не выдумывается. Есть имя — оно и есть `alt`; нет — картинка\n * декоративна, и придуманное компонентом описание было бы хуже пустого.\n */\nconst alt = computed(() => props.name ?? '')\n\nfunction onClick(event: MouseEvent): void {\n emit('click', event)\n}\n</script>\n\n<template>\n <component\n :is=\"rootTag\"\n data-gr-file-preview\n :data-kind=\"kind\"\n :type=\"rootTag === 'button' ? 'button' : undefined\"\n :href=\"rootHref\"\n :class=\"rootClass\"\n :style=\"rootStyle\"\n :aria-label=\"ariaLabel\"\n @click=\"onClick\"\n >\n <GrSkeleton\n v-if=\"showSkeleton\"\n data-gr-file-preview-skeleton\n width=\"100%\"\n height=\"100%\"\n rounded=\"var(--gr-file-preview-radius, var(--gr-radius-md))\"\n />\n\n <!--\n Условие — `showImage`, а не «картинка готова»: убери её из дерева на\n время загрузки, и браузер не начнёт качать, а `load` не придёт никогда.\n Поэтому она ждёт невидимой и вне потока — иначе поделила бы плитку со\n скелетом как второй flex-элемент.\n -->\n <img\n v-if=\"showImage\"\n :key=\"src ?? ''\"\n data-gr-file-preview-image\n :src=\"src ?? undefined\"\n :alt=\"alt\"\n :loading=\"resolvedLoading\"\n :class=\"[filePreviewMediaClass, showSkeleton ? filePreviewMediaLoadingClass : '']\"\n @load=\"imageState = 'loaded'\"\n @error=\"imageState = 'error'\"\n >\n\n <span v-else data-gr-file-preview-fallback :class=\"filePreviewFallbackClass\">\n <component :is=\"fallbackIcon\" :class=\"filePreviewIconClass\" aria-hidden=\"true\" />\n <span v-if=\"name\" data-gr-file-preview-label :class=\"filePreviewLabelClass\">{{ name }}</span>\n </span>\n </component>\n</template>\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { t as GrFilePreview_default } from "./chunks/GrFilePreview-
|
|
1
|
+
import { n as fileKindOf, r as isPreviewableKind, t as GrFilePreview_default } from "./chunks/GrFilePreview-D0Bt-0nn.js";
|
|
2
2
|
import { n as grFilePreviewSafelist, t as grFilePreviewConfig } from "../../chunks/config-QJ54_fcM.js";
|
|
3
|
-
export { GrFilePreview_default as GrFilePreview, GrFilePreview_default as default, grFilePreviewConfig, grFilePreviewSafelist };
|
|
3
|
+
export { GrFilePreview_default as GrFilePreview, GrFilePreview_default as default, fileKindOf, grFilePreviewConfig, grFilePreviewSafelist, isPreviewableKind };
|
package/dist/components/GrStatistic/chunks/{GrStatistic-DkzWUAw1.js → GrStatistic-C7wQECeS.js}
RENAMED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { a as useGrComponentSize } from "../../../chunks/context-CTbuwcYs.js";
|
|
1
|
+
import { a as useGrComponentSize, i as useGrComponentProp } from "../../../chunks/context-CTbuwcYs.js";
|
|
2
2
|
import { n as useGranularityTranslations } from "../../../chunks/granularityI18n-BC5zsj_d.js";
|
|
3
3
|
import { t as GrSkeleton_default } from "../../GrSkeleton/chunks/GrSkeleton-CgfXxPQ2.js";
|
|
4
4
|
import { n as iconTag, t as iconClass } from "../../../chunks/icon-Dag2tIMs.js";
|
|
5
5
|
import { t as minus_default } from "../../../chunks/minus-oxdbHF33.js";
|
|
6
|
-
import {
|
|
6
|
+
import { i as trending_down_default, n as deltaTone, r as trending_up_default } from "../../../chunks/deltaTone-B5OoS1vo.js";
|
|
7
7
|
import { a as statisticRootClass, c as statisticTrendClassByTrend, i as statisticPlaceholderHeightBySize, l as statisticTrendSizeBySize, o as statisticTitleClass, r as statisticAffixSizeBySize, s as statisticTitleSizeBySize, u as statisticValueClass } from "../../../chunks/config-Be9k9bOf.js";
|
|
8
8
|
import { computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, markRaw, normalizeClass, onBeforeUnmount, onMounted, openBlock, ref, renderSlot, resolveDynamicComponent, toDisplayString, unref, useSlots, watch, withCtx } from "vue";
|
|
9
9
|
//#region src/components/GrStatistic/countUp.ts
|
|
@@ -118,7 +118,8 @@ var GrStatistic_default = /* @__PURE__ */ defineComponent({
|
|
|
118
118
|
suffix: { default: void 0 },
|
|
119
119
|
icon: { default: void 0 },
|
|
120
120
|
size: { default: void 0 },
|
|
121
|
-
tone: { default:
|
|
121
|
+
tone: { default: void 0 },
|
|
122
|
+
polarity: { default: void 0 },
|
|
122
123
|
trend: { default: void 0 },
|
|
123
124
|
trendText: { default: void 0 },
|
|
124
125
|
loading: {
|
|
@@ -237,6 +238,21 @@ var GrStatistic_default = /* @__PURE__ */ defineComponent({
|
|
|
237
238
|
watch(() => props.value, (next, prev) => startIfNumeric(prev, next));
|
|
238
239
|
onBeforeUnmount(stopCounting);
|
|
239
240
|
const resolvedSize = useGrComponentSize(() => props.size, { component: "GrStatistic" });
|
|
241
|
+
const resolvedPolarity = useGrComponentProp("GrStatistic", "polarity", () => props.polarity, "none");
|
|
242
|
+
/**
|
|
243
|
+
* Тон значения: явный проп → выведенный из знака → нейтральный.
|
|
244
|
+
*
|
|
245
|
+
* Отдельной ветки «полярность не задана» нет намеренно: `deltaTone(x, 'none')`
|
|
246
|
+
* нейтрален при любом значении, то есть `'none'` и есть прежнее поведение.
|
|
247
|
+
*
|
|
248
|
+
* Знак берётся у самого пропа, а не у кадра перебора: `frameValue` стартует с
|
|
249
|
+
* нуля, и цвет плитки мигал бы нейтральным на каждой анимации.
|
|
250
|
+
*/
|
|
251
|
+
const resolvedTone = computed(() => {
|
|
252
|
+
if (props.tone !== void 0) return props.tone;
|
|
253
|
+
const numeric = toNumber(props.value);
|
|
254
|
+
return numeric === null ? "neutral" : deltaTone(numeric, resolvedPolarity.value);
|
|
255
|
+
});
|
|
240
256
|
const trendIconByTrend = {
|
|
241
257
|
up: trending_up_default,
|
|
242
258
|
down: trending_down_default,
|
|
@@ -296,7 +312,7 @@ var GrStatistic_default = /* @__PURE__ */ defineComponent({
|
|
|
296
312
|
"data-gr-statistic-number": "",
|
|
297
313
|
class: normalizeClass(unref(statisticValueClass)({
|
|
298
314
|
size: unref(resolvedSize),
|
|
299
|
-
tone:
|
|
315
|
+
tone: resolvedTone.value
|
|
300
316
|
})),
|
|
301
317
|
"aria-hidden": isCounting.value ? "true" : void 0
|
|
302
318
|
}, [renderSlot(_ctx.$slots, "default", {}, () => [createTextVNode(toDisplayString(formatted.value), 1)])], 10, _hoisted_4),
|