@feugene/granularity 0.6.2 → 0.7.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/{en-BMjJZttK.js → en-QNZhjmSJ.js} +18 -1
- package/dist/chunks/en-QNZhjmSJ.js.map +1 -0
- package/dist/chunks/{es-EYCf8G_1.js → es-C1c5XsIK.js} +18 -1
- package/dist/chunks/es-C1c5XsIK.js.map +1 -0
- package/dist/chunks/{es-Cu_RlgIr.js → es-CKLPuE0h.js} +3 -3
- package/dist/chunks/{es-Cu_RlgIr.js.map → es-CKLPuE0h.js.map} +1 -1
- package/dist/chunks/granularityI18n-CXa_uN7f.js +46 -0
- package/dist/chunks/granularityI18n-CXa_uN7f.js.map +1 -0
- package/dist/chunks/{ru-DKswMSdd.js → ru-DcF7ciz0.js} +18 -1
- package/dist/chunks/ru-DcF7ciz0.js.map +1 -0
- package/dist/chunks/{shared-DO3Sr5FJ.js → shared-BkIIEolF.js} +7 -0
- package/dist/chunks/shared-BkIIEolF.js.map +1 -0
- package/dist/components/GrNavbar/chunks/{GrNavbar-BDIKAYfN.js → GrNavbar-BoVuSZ-V.js} +1 -1
- package/dist/components/GrNavbar/chunks/{GrNavbar-BDIKAYfN.js.map → GrNavbar-BoVuSZ-V.js.map} +1 -1
- package/dist/components/GrNavbar/index.js +1 -1
- package/dist/components/GrPagination/chunks/{GrPagination-BZlH-uEd.js → GrPagination-DW44c2-a.js} +1 -1
- package/dist/components/GrPagination/chunks/{GrPagination-BZlH-uEd.js.map → GrPagination-DW44c2-a.js.map} +1 -1
- package/dist/components/GrPagination/index.js +1 -1
- package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-n0CZT4Ic.js → GrTreeSelect-DkAIksRW.js} +1 -1
- package/dist/components/GrTreeSelect/chunks/{GrTreeSelect-n0CZT4Ic.js.map → GrTreeSelect-DkAIksRW.js.map} +1 -1
- package/dist/components/GrTreeSelect/index.js +1 -1
- package/dist/granular-provider-node.js +1 -1
- package/dist/granular-provider.js +1 -1
- package/dist/i18n/all.js +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/index.js +977 -4
- package/dist/index.js.map +1 -1
- package/dist/types/src/components/GrResponseErrorBanner/GrFormErrorBanner.vue.d.ts +70 -0
- package/dist/types/src/components/GrResponseErrorBanner/GrResponseErrorBanner.vue.d.ts +96 -0
- package/dist/types/src/components/GrResponseErrorBanner/GrUploadErrorBanner.vue.d.ts +73 -0
- package/dist/types/src/components/GrResponseErrorBanner/config.d.ts +1 -0
- package/dist/types/src/components/GrResponseErrorBanner/index.d.ts +10 -0
- package/dist/types/src/components/GrResponseErrorBanner/parsers/abortErrorParser.d.ts +15 -0
- package/dist/types/src/components/GrResponseErrorBanner/parsers/createResponseErrorClassifier.d.ts +28 -0
- package/dist/types/src/components/GrResponseErrorBanner/parsers/fileValidationParser.d.ts +12 -0
- package/dist/types/src/components/GrResponseErrorBanner/parsers/httpStatusParser.d.ts +15 -0
- package/dist/types/src/components/GrResponseErrorBanner/parsers/index.d.ts +11 -0
- package/dist/types/src/components/GrResponseErrorBanner/parsers/jsonApiErrorParser.d.ts +17 -0
- package/dist/types/src/components/GrResponseErrorBanner/parsers/laravelValidationParser.d.ts +15 -0
- package/dist/types/src/components/GrResponseErrorBanner/parsers/networkErrorParser.d.ts +14 -0
- package/dist/types/src/components/GrResponseErrorBanner/parsers/normalizeError.d.ts +19 -0
- package/dist/types/src/components/GrResponseErrorBanner/parsers/plainMessageParser.d.ts +15 -0
- package/dist/types/src/components/GrResponseErrorBanner/parsers/problemDetailsParser.d.ts +17 -0
- package/dist/types/src/components/GrResponseErrorBanner/responseError.defaults.d.ts +18 -0
- package/dist/types/src/components/GrResponseErrorBanner/responseError.types.d.ts +115 -0
- package/dist/types/src/components/GrResponseErrorBanner/useResponseError.d.ts +62 -0
- package/dist/types/src/components/GrToaster/GrToaster.vue.d.ts +1 -1
- package/dist/types/src/granular-provider/shared.d.ts +1 -0
- package/dist/types/src/i18n/messages/en.d.ts +32 -0
- package/dist/types/src/i18n/messages/es.d.ts +32 -0
- package/dist/types/src/i18n/messages/ru.d.ts +32 -0
- package/dist/types/src/index.d.ts +1 -0
- package/package.json +1 -1
- package/dist/chunks/en-BMjJZttK.js.map +0 -1
- package/dist/chunks/es-EYCf8G_1.js.map +0 -1
- package/dist/chunks/granularityI18n-Dm4DuCS9.js +0 -26
- package/dist/chunks/granularityI18n-Dm4DuCS9.js.map +0 -1
- package/dist/chunks/ru-DKswMSdd.js.map +0 -1
- package/dist/chunks/shared-DO3Sr5FJ.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -77,12 +77,13 @@ import { n as grLoadingSafelist, t as grLoadingConfig } from "./chunks/config-B4
|
|
|
77
77
|
import "./components/GrLoading/index.js";
|
|
78
78
|
import { n as grModalClassTokens, r as grModalSafelist, t as grModalConfig } from "./chunks/config-C5TN9J2Z.js";
|
|
79
79
|
import "./components/GrModal/index.js";
|
|
80
|
-
import { t as GrNavbar_default } from "./components/GrNavbar/chunks/GrNavbar-
|
|
80
|
+
import { t as GrNavbar_default } from "./components/GrNavbar/chunks/GrNavbar-BoVuSZ-V.js";
|
|
81
|
+
import { t as useGranularityTranslations } from "./chunks/granularityI18n-CXa_uN7f.js";
|
|
81
82
|
import { t as grNavbarConfig } from "./chunks/config-D1DI3roo.js";
|
|
82
83
|
import { n as grNumberInputSafelist, t as grNumberInputConfig } from "./chunks/config-wo-ncgTH.js";
|
|
83
84
|
import { t as GrNumberInput_default } from "./components/GrNumberInput/chunks/GrNumberInput-ECtFCIKV.js";
|
|
84
85
|
import { t as GrSelect_default } from "./components/GrSelect/chunks/GrSelect-C3NAfTqL.js";
|
|
85
|
-
import { t as GrPagination_default } from "./components/GrPagination/chunks/GrPagination-
|
|
86
|
+
import { t as GrPagination_default } from "./components/GrPagination/chunks/GrPagination-DW44c2-a.js";
|
|
86
87
|
import { t as grPaginationConfig } from "./chunks/config-DudL02ag.js";
|
|
87
88
|
import { n as grProgressBarSafelist, t as grProgressBarConfig } from "./chunks/config-ZJVOVgqR.js";
|
|
88
89
|
import "./components/GrProgressBar/index.js";
|
|
@@ -116,8 +117,980 @@ import { t as grTooltipConfig } from "./chunks/config-C0rHS1-l.js";
|
|
|
116
117
|
import { t as GrTree_default } from "./components/GrTree/chunks/GrTree-Dlev9oBd.js";
|
|
117
118
|
import { n as grTreeSafelist, t as grTreeConfig } from "./chunks/config-D32znLtV2.js";
|
|
118
119
|
import { n as grTreeSelectSafelist, t as grTreeSelectConfig } from "./chunks/config-C0b_3LJV.js";
|
|
119
|
-
import { t as GrTreeSelect_default } from "./components/GrTreeSelect/chunks/GrTreeSelect-
|
|
120
|
+
import { t as GrTreeSelect_default } from "./components/GrTreeSelect/chunks/GrTreeSelect-DkAIksRW.js";
|
|
120
121
|
import { initThemeEarly, useTheme } from "./composables/useTheme.js";
|
|
122
|
+
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createTextVNode, createVNode, defineComponent, openBlock, ref, renderList, shallowRef, toDisplayString, withCtx } from "vue";
|
|
123
|
+
//#region src/components/GrResponseErrorBanner/responseError.defaults.ts
|
|
124
|
+
/**
|
|
125
|
+
* Default English texts. Any field can be overridden via the `texts` prop
|
|
126
|
+
* on `GrResponseErrorBanner` or the `texts` option of `useResponseError`.
|
|
127
|
+
*/
|
|
128
|
+
var DEFAULT_RESPONSE_ERROR_TEXTS = {
|
|
129
|
+
networkTitle: "No connection to the server",
|
|
130
|
+
networkMessage: "Check your internet connection and try again.",
|
|
131
|
+
abortedTitle: "Request cancelled",
|
|
132
|
+
abortedMessage: "The request was cancelled.",
|
|
133
|
+
validationTitle: "Check the data",
|
|
134
|
+
validationMessage: "The server returned a validation error. Fix the fields and try again.",
|
|
135
|
+
clientTitle: "Request error",
|
|
136
|
+
clientMessage: "The server rejected the request.",
|
|
137
|
+
serverTitle: "Server error",
|
|
138
|
+
serverMessage: "A server error occurred. Please try again.",
|
|
139
|
+
unknownTitle: "Something went wrong",
|
|
140
|
+
unknownMessage: "The operation could not be completed. Please try again.",
|
|
141
|
+
retryLabel: "Retry",
|
|
142
|
+
dismissLabel: "Dismiss"
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* Маппинг `kind` → `tone` по умолчанию. Применяется внутри баннера, если
|
|
146
|
+
* пользователь не передал `tone` или `toneByKind`. Цель — визуально отделить
|
|
147
|
+
* «исправь ввод» (warning) от «упало по сети/серверу» (danger).
|
|
148
|
+
*/
|
|
149
|
+
var DEFAULT_TONE_BY_KIND = {
|
|
150
|
+
network: "danger",
|
|
151
|
+
aborted: "info",
|
|
152
|
+
validation: "warning",
|
|
153
|
+
client: "warning",
|
|
154
|
+
server: "danger",
|
|
155
|
+
unknown: "danger"
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* `kind`'ы, которые по умолчанию не показываются (баннер скрыт).
|
|
159
|
+
* Семантика: отмена пользователем — не «ошибка», UI обычно тихо проглатывает.
|
|
160
|
+
* Пользователь может переопределить через `autoHideKinds: []`, чтобы показать info-плашку.
|
|
161
|
+
*/
|
|
162
|
+
var DEFAULT_AUTO_HIDE_KINDS = [];
|
|
163
|
+
//#endregion
|
|
164
|
+
//#region src/components/GrResponseErrorBanner/parsers/abortErrorParser.ts
|
|
165
|
+
/**
|
|
166
|
+
* Распознаёт отмену запроса клиентом.
|
|
167
|
+
*
|
|
168
|
+
* Триггеры:
|
|
169
|
+
* - `ctx.isAbort === true` (выставляется `normalizeError` для `AbortError`,
|
|
170
|
+
* axios `isCancel`/`ERR_CANCELED`, xhr `status === 0 && readyState === 4`).
|
|
171
|
+
*
|
|
172
|
+
* Возвращает `kind: 'aborted'` и `stop: true` — другие парсеры пропускаются,
|
|
173
|
+
* чтобы случайный `status === 0` не интерпретировался как сетевая ошибка.
|
|
174
|
+
*
|
|
175
|
+
* По умолчанию `aborted` показывается info-плашкой; чтобы скрыть совсем —
|
|
176
|
+
* добавьте `'aborted'` в `autoHideKinds`.
|
|
177
|
+
*/
|
|
178
|
+
var abortErrorParser = (ctx) => {
|
|
179
|
+
if (!ctx.isAbort) return null;
|
|
180
|
+
return {
|
|
181
|
+
kind: "aborted",
|
|
182
|
+
message: ctx.texts.abortedMessage,
|
|
183
|
+
stop: true
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
//#endregion
|
|
187
|
+
//#region src/components/GrResponseErrorBanner/parsers/fileValidationParser.ts
|
|
188
|
+
/**
|
|
189
|
+
* Распознаёт `FileValidationError` от `GrFileUpload` (или совместимый по форме
|
|
190
|
+
* объект). Не зависит от жёсткого импорта класса — определяет по `name`.
|
|
191
|
+
*
|
|
192
|
+
* Активируется, если `ctx.raw.name === 'FileValidationError'` и есть массив
|
|
193
|
+
* `issues`/`errors` с полями `{ file?, code?, message }`.
|
|
194
|
+
*
|
|
195
|
+
* Возвращает `kind: 'validation'` и `stop: true` — это локальная клиентская
|
|
196
|
+
* валидация, незачем дальше идти по HTTP-парсерам.
|
|
197
|
+
*/
|
|
198
|
+
var fileValidationParser = (ctx) => {
|
|
199
|
+
const raw = ctx.raw;
|
|
200
|
+
if (!raw || typeof raw !== "object") return null;
|
|
201
|
+
if (raw.name !== "FileValidationError") return null;
|
|
202
|
+
const list = raw.issues ?? raw.errors ?? [];
|
|
203
|
+
const details = [];
|
|
204
|
+
for (const it of list) {
|
|
205
|
+
if (!it || typeof it !== "object") continue;
|
|
206
|
+
const msg = typeof it.message === "string" ? it.message : "";
|
|
207
|
+
if (!msg) continue;
|
|
208
|
+
const fileName = it.file?.name;
|
|
209
|
+
details.push(fileName ? `${fileName}: ${msg}` : msg);
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
kind: "validation",
|
|
213
|
+
message: raw.message || ctx.texts.validationMessage,
|
|
214
|
+
details: details.length ? details : void 0,
|
|
215
|
+
stop: true
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
//#endregion
|
|
219
|
+
//#region src/components/GrResponseErrorBanner/parsers/httpStatusParser.ts
|
|
220
|
+
/**
|
|
221
|
+
* Базовая классификация по HTTP-статусу.
|
|
222
|
+
*
|
|
223
|
+
* Правила:
|
|
224
|
+
* - `400` / `422` ⇒ `kind: 'validation'`,
|
|
225
|
+
* - остальные `4xx` ⇒ `kind: 'client'`,
|
|
226
|
+
* - `>= 500` ⇒ `kind: 'server'`.
|
|
227
|
+
*
|
|
228
|
+
* Не использует `stop` — даёт более специализированным парсерам
|
|
229
|
+
* (Laravel/JSON:API/RFC 7807) уточнить сообщение и `fieldErrors`.
|
|
230
|
+
*
|
|
231
|
+
* `message` берёт из `ctx.texts` как осмысленный фолбэк.
|
|
232
|
+
*/
|
|
233
|
+
var httpStatusParser = (ctx) => {
|
|
234
|
+
const status = ctx.status;
|
|
235
|
+
if (typeof status !== "number") return null;
|
|
236
|
+
if (status >= 500) return {
|
|
237
|
+
kind: "server",
|
|
238
|
+
status,
|
|
239
|
+
message: ctx.texts.serverMessage
|
|
240
|
+
};
|
|
241
|
+
if (status === 400 || status === 422) return {
|
|
242
|
+
kind: "validation",
|
|
243
|
+
status,
|
|
244
|
+
message: ctx.texts.validationMessage
|
|
245
|
+
};
|
|
246
|
+
if (status >= 400) return {
|
|
247
|
+
kind: "client",
|
|
248
|
+
status,
|
|
249
|
+
message: ctx.texts.clientMessage
|
|
250
|
+
};
|
|
251
|
+
return null;
|
|
252
|
+
};
|
|
253
|
+
//#endregion
|
|
254
|
+
//#region src/components/GrResponseErrorBanner/parsers/jsonApiErrorParser.ts
|
|
255
|
+
/**
|
|
256
|
+
* Парсер JSON:API-стиля:
|
|
257
|
+
*
|
|
258
|
+
* ```json
|
|
259
|
+
* { "errors": [
|
|
260
|
+
* { "status": "422", "title": "Invalid Attribute",
|
|
261
|
+
* "detail": "Email is required.",
|
|
262
|
+
* "source": { "pointer": "/data/attributes/email" } }
|
|
263
|
+
* ] }
|
|
264
|
+
* ```
|
|
265
|
+
*
|
|
266
|
+
* Активируется, когда `ctx.body.errors` — массив объектов с `title`/`detail`.
|
|
267
|
+
* Поле извлекается из последнего сегмента `source.pointer`. Все `detail`
|
|
268
|
+
* собираются в `details`, первый `title` — в `message`.
|
|
269
|
+
*/
|
|
270
|
+
var jsonApiErrorParser = (ctx) => {
|
|
271
|
+
const body = ctx.body;
|
|
272
|
+
if (!body || typeof body !== "object") return null;
|
|
273
|
+
const errors = body.errors;
|
|
274
|
+
if (!Array.isArray(errors) || errors.length === 0) return null;
|
|
275
|
+
const first = errors[0];
|
|
276
|
+
if (!first || typeof first !== "object") return null;
|
|
277
|
+
const has = (key) => key in first;
|
|
278
|
+
if (!has("title") && !has("detail") && !has("source")) return null;
|
|
279
|
+
const fieldErrorsMap = /* @__PURE__ */ new Map();
|
|
280
|
+
const details = [];
|
|
281
|
+
let message;
|
|
282
|
+
for (const e of errors) {
|
|
283
|
+
if (!e || typeof e !== "object") continue;
|
|
284
|
+
const eo = e;
|
|
285
|
+
const text = typeof eo.detail === "string" && eo.detail || typeof eo.title === "string" && eo.title || "";
|
|
286
|
+
if (!text) continue;
|
|
287
|
+
if (!message && typeof eo.title === "string") message = eo.title;
|
|
288
|
+
const pointer = typeof eo.source?.pointer === "string" ? eo.source.pointer : "";
|
|
289
|
+
const field = pointer ? pointer.split("/").filter(Boolean).pop() : void 0;
|
|
290
|
+
if (field) {
|
|
291
|
+
const list = fieldErrorsMap.get(field) ?? [];
|
|
292
|
+
list.push(text);
|
|
293
|
+
fieldErrorsMap.set(field, list);
|
|
294
|
+
}
|
|
295
|
+
details.push(text);
|
|
296
|
+
}
|
|
297
|
+
if (!details.length) return null;
|
|
298
|
+
const fieldErrors = Array.from(fieldErrorsMap, ([field, messages]) => ({
|
|
299
|
+
field,
|
|
300
|
+
messages
|
|
301
|
+
}));
|
|
302
|
+
const kind = fieldErrors.length ? "validation" : void 0;
|
|
303
|
+
return {
|
|
304
|
+
kind,
|
|
305
|
+
message: message || (kind === "validation" ? ctx.texts.validationMessage : ctx.texts.clientMessage),
|
|
306
|
+
details,
|
|
307
|
+
fieldErrors: fieldErrors.length ? fieldErrors : void 0
|
|
308
|
+
};
|
|
309
|
+
};
|
|
310
|
+
//#endregion
|
|
311
|
+
//#region src/components/GrResponseErrorBanner/parsers/laravelValidationParser.ts
|
|
312
|
+
/**
|
|
313
|
+
* Парсит Laravel-формат ответа об ошибке валидации:
|
|
314
|
+
*
|
|
315
|
+
* ```json
|
|
316
|
+
* { "message": "The given data was invalid.",
|
|
317
|
+
* "errors": { "file": ["The file is required."], "amount": ["..."] } }
|
|
318
|
+
* ```
|
|
319
|
+
*
|
|
320
|
+
* Активируется при наличии `ctx.body.errors` как plain-объекта (значения —
|
|
321
|
+
* массив строк или строка). Если есть `fieldErrors`, повышает `kind` до
|
|
322
|
+
* `validation` независимо от статуса (полезно, когда сервер прислал 200/4xx
|
|
323
|
+
* без 422). `message` берётся из `body.message` / `body.error`.
|
|
324
|
+
*/
|
|
325
|
+
var laravelValidationParser = (ctx) => {
|
|
326
|
+
const body = ctx.body;
|
|
327
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return null;
|
|
328
|
+
const data = body;
|
|
329
|
+
const errors = data.errors;
|
|
330
|
+
if (!errors || typeof errors !== "object" || Array.isArray(errors)) return null;
|
|
331
|
+
const fieldErrors = [];
|
|
332
|
+
for (const [field, raw] of Object.entries(errors)) {
|
|
333
|
+
const messages = Array.isArray(raw) ? raw.filter((m) => typeof m === "string") : typeof raw === "string" ? [raw] : [];
|
|
334
|
+
if (messages.length) fieldErrors.push({
|
|
335
|
+
field,
|
|
336
|
+
messages
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
if (!fieldErrors.length) return null;
|
|
340
|
+
return {
|
|
341
|
+
kind: "validation",
|
|
342
|
+
message: typeof data.message === "string" && data.message || typeof data.error === "string" && data.error || ctx.texts.validationMessage,
|
|
343
|
+
details: fieldErrors.flatMap((fe) => fe.messages),
|
|
344
|
+
fieldErrors
|
|
345
|
+
};
|
|
346
|
+
};
|
|
347
|
+
//#endregion
|
|
348
|
+
//#region src/components/GrResponseErrorBanner/parsers/networkErrorParser.ts
|
|
349
|
+
/**
|
|
350
|
+
* Распознаёт транспортные ошибки **без ответа сервера**: отсутствие интернета,
|
|
351
|
+
* DNS-фейл, CORS-preflight, fetch `TypeError('Failed to fetch')`,
|
|
352
|
+
* axios `ERR_NETWORK`/`ECONNABORTED`.
|
|
353
|
+
*
|
|
354
|
+
* Триггеры:
|
|
355
|
+
* - `ctx.isNetwork === true` (выставлено `normalizeError`),
|
|
356
|
+
* - либо нет ни `status`, ни `body`, ни признака отмены, но есть исходная
|
|
357
|
+
* ошибка с `code === 'ERR_NETWORK'` / `message ~ /network|fetch/i/`.
|
|
358
|
+
*
|
|
359
|
+
* Возвращает `kind: 'network'` и `stop: true`.
|
|
360
|
+
*/
|
|
361
|
+
var networkErrorParser = (ctx) => {
|
|
362
|
+
if (ctx.isAbort) return null;
|
|
363
|
+
if (ctx.isNetwork) return {
|
|
364
|
+
kind: "network",
|
|
365
|
+
message: ctx.texts.networkMessage,
|
|
366
|
+
stop: true
|
|
367
|
+
};
|
|
368
|
+
if (typeof ctx.status !== "number" && ctx.body == null && ctx.raw && typeof ctx.raw === "object") {
|
|
369
|
+
const r = ctx.raw;
|
|
370
|
+
if (r.code === "ERR_NETWORK" || typeof r.message === "string" && /network|failed to fetch/i.test(r.message)) return {
|
|
371
|
+
kind: "network",
|
|
372
|
+
message: ctx.texts.networkMessage,
|
|
373
|
+
stop: true
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
return null;
|
|
377
|
+
};
|
|
378
|
+
//#endregion
|
|
379
|
+
//#region src/components/GrResponseErrorBanner/parsers/normalizeError.ts
|
|
380
|
+
/**
|
|
381
|
+
* Универсально приводит «сырой» вход к {@link NormalizedRawError}.
|
|
382
|
+
*
|
|
383
|
+
* Поддерживаемые источники:
|
|
384
|
+
* - **axios** — `AxiosError` с `response.status`/`response.data`/`response.headers`,
|
|
385
|
+
* либо `request` без `response` + `code === 'ERR_NETWORK'`, либо `code === 'ERR_CANCELED'`.
|
|
386
|
+
* - **fetch `Response`** — объект `Response` с `ok=false`. Тело читается через
|
|
387
|
+
* `clone().json()` (с фолбэком на `.text()`); поэтому функция асинхронная.
|
|
388
|
+
* - **fetch `TypeError`** — `TypeError('Failed to fetch')` ⇒ `isNetwork=true`.
|
|
389
|
+
* - **`{ response: Response, body: unknown }`** — если потребитель уже прочитал тело сам.
|
|
390
|
+
* - **`XMLHttpRequest`** / `{ status, responseText, response }` — пара полей.
|
|
391
|
+
* - **`AbortError` / `DOMException`** ⇒ `isAbort=true`.
|
|
392
|
+
* - **`Error`/строка/произвольный объект** — кладётся в `body`/`raw` как есть.
|
|
393
|
+
*
|
|
394
|
+
* Цель — отделить «откуда пришла ошибка» от «как её классифицировать»: парсеры
|
|
395
|
+
* работают только с нормализованным `ResponseErrorContext`, не зная о транспорте.
|
|
396
|
+
*/
|
|
397
|
+
async function normalizeError(raw) {
|
|
398
|
+
if (isPlainNormalized(raw)) return {
|
|
399
|
+
...raw,
|
|
400
|
+
raw
|
|
401
|
+
};
|
|
402
|
+
if (isResponseEnvelope(raw)) {
|
|
403
|
+
const res = raw.response;
|
|
404
|
+
return {
|
|
405
|
+
raw,
|
|
406
|
+
status: res.status,
|
|
407
|
+
body: raw.body,
|
|
408
|
+
headers: readHeaders(res)
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
if (isFetchResponse(raw)) {
|
|
412
|
+
const body = await readFetchBody(raw);
|
|
413
|
+
return {
|
|
414
|
+
raw,
|
|
415
|
+
status: raw.status,
|
|
416
|
+
body,
|
|
417
|
+
headers: readHeaders(raw)
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
if (isXhr(raw)) {
|
|
421
|
+
const body = parseMaybeJson(raw.responseText) ?? raw.response;
|
|
422
|
+
return {
|
|
423
|
+
raw,
|
|
424
|
+
status: typeof raw.status === "number" ? raw.status : void 0,
|
|
425
|
+
body,
|
|
426
|
+
isAbort: raw.status === 0 && raw.readyState === 4
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
if (isAxiosLike(raw)) {
|
|
430
|
+
const e = raw;
|
|
431
|
+
const status = e.response?.status;
|
|
432
|
+
const code = e.code;
|
|
433
|
+
const isAbort = code === "ERR_CANCELED" || code === "CANCELED" || e.__CANCEL__ === true;
|
|
434
|
+
const isNetwork = !e.response && (code === "ERR_NETWORK" || code === "ECONNABORTED");
|
|
435
|
+
return {
|
|
436
|
+
raw,
|
|
437
|
+
status,
|
|
438
|
+
body: e.response?.data,
|
|
439
|
+
headers: normalizeHeaderRecord(e.response?.headers),
|
|
440
|
+
isAbort,
|
|
441
|
+
isNetwork
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
if (isAbortError(raw)) return {
|
|
445
|
+
raw,
|
|
446
|
+
isAbort: true
|
|
447
|
+
};
|
|
448
|
+
if (raw instanceof TypeError && /fetch|network/i.test(raw.message || "")) return {
|
|
449
|
+
raw,
|
|
450
|
+
isNetwork: true
|
|
451
|
+
};
|
|
452
|
+
if (raw instanceof Error) return {
|
|
453
|
+
raw,
|
|
454
|
+
body: { message: raw.message }
|
|
455
|
+
};
|
|
456
|
+
if (typeof raw === "string") return {
|
|
457
|
+
raw,
|
|
458
|
+
body: { message: raw }
|
|
459
|
+
};
|
|
460
|
+
if (raw && typeof raw === "object") return {
|
|
461
|
+
raw,
|
|
462
|
+
body: raw
|
|
463
|
+
};
|
|
464
|
+
return { raw };
|
|
465
|
+
}
|
|
466
|
+
function isPlainNormalized(v) {
|
|
467
|
+
if (!v || typeof v !== "object") return false;
|
|
468
|
+
const o = v;
|
|
469
|
+
return "raw" in o && ("status" in o || "body" in o || "isAbort" in o || "isNetwork" in o || "headers" in o);
|
|
470
|
+
}
|
|
471
|
+
function isResponseEnvelope(v) {
|
|
472
|
+
return !!v && typeof v === "object" && "response" in v && isFetchResponse(v.response);
|
|
473
|
+
}
|
|
474
|
+
function isFetchResponse(v) {
|
|
475
|
+
return typeof Response !== "undefined" && v instanceof Response;
|
|
476
|
+
}
|
|
477
|
+
function isXhr(v) {
|
|
478
|
+
return typeof XMLHttpRequest !== "undefined" && v instanceof XMLHttpRequest;
|
|
479
|
+
}
|
|
480
|
+
function isAxiosLike(v) {
|
|
481
|
+
if (!v || typeof v !== "object") return false;
|
|
482
|
+
const o = v;
|
|
483
|
+
return o.isAxiosError === true || "response" in o && typeof o.response === "object" && o.response !== null || "code" in o && typeof o.code === "string";
|
|
484
|
+
}
|
|
485
|
+
function isAbortError(v) {
|
|
486
|
+
if (!v || typeof v !== "object") return false;
|
|
487
|
+
const o = v;
|
|
488
|
+
return o.name === "AbortError" || o.name === "CanceledError" || o.code === "ERR_CANCELED";
|
|
489
|
+
}
|
|
490
|
+
async function readFetchBody(res) {
|
|
491
|
+
if (res.bodyUsed) return void 0;
|
|
492
|
+
try {
|
|
493
|
+
const text = await res.clone().text();
|
|
494
|
+
return parseMaybeJson(text) ?? text;
|
|
495
|
+
} catch {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
function readHeaders(res) {
|
|
500
|
+
const out = {};
|
|
501
|
+
res.headers.forEach((value, key) => {
|
|
502
|
+
out[key.toLowerCase()] = value;
|
|
503
|
+
});
|
|
504
|
+
return out;
|
|
505
|
+
}
|
|
506
|
+
function normalizeHeaderRecord(h) {
|
|
507
|
+
if (!h || typeof h !== "object") return void 0;
|
|
508
|
+
const out = {};
|
|
509
|
+
for (const [k, v] of Object.entries(h)) if (typeof v === "string") out[k.toLowerCase()] = v;
|
|
510
|
+
return out;
|
|
511
|
+
}
|
|
512
|
+
function parseMaybeJson(text) {
|
|
513
|
+
if (typeof text !== "string" || !text) return void 0;
|
|
514
|
+
try {
|
|
515
|
+
return JSON.parse(text);
|
|
516
|
+
} catch {
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
//#endregion
|
|
521
|
+
//#region src/components/GrResponseErrorBanner/parsers/plainMessageParser.ts
|
|
522
|
+
/**
|
|
523
|
+
* Финальный фолбэк-парсер. Достаёт хоть какое-нибудь человекочитаемое
|
|
524
|
+
* сообщение, чтобы баннер не был пустым.
|
|
525
|
+
*
|
|
526
|
+
* Источники (по приоритету):
|
|
527
|
+
* 1. `ctx.body` — если строка ⇒ message,
|
|
528
|
+
* 2. `ctx.body[messageKey]` (default `'message'`),
|
|
529
|
+
* 3. `ctx.body.error` (строка),
|
|
530
|
+
* 4. `ctx.raw.message` (для голых `Error`).
|
|
531
|
+
*
|
|
532
|
+
* Не выставляет `kind` — оставляет на усмотрение классификатора.
|
|
533
|
+
* Использует ключ `messageKey` из `ctx.meta._messageKey`, если задан.
|
|
534
|
+
*/
|
|
535
|
+
var plainMessageParser = (ctx) => {
|
|
536
|
+
const messageKey = ctx.meta._messageKey || "message";
|
|
537
|
+
if (typeof ctx.body === "string" && ctx.body.trim()) return { message: ctx.body };
|
|
538
|
+
if (ctx.body && typeof ctx.body === "object") {
|
|
539
|
+
const data = ctx.body;
|
|
540
|
+
const fromKey = data[messageKey];
|
|
541
|
+
if (typeof fromKey === "string" && fromKey) return { message: fromKey };
|
|
542
|
+
if (typeof data.error === "string" && data.error) return { message: data.error };
|
|
543
|
+
}
|
|
544
|
+
if (ctx.raw instanceof Error && ctx.raw.message) return { message: ctx.raw.message };
|
|
545
|
+
return null;
|
|
546
|
+
};
|
|
547
|
+
//#endregion
|
|
548
|
+
//#region src/components/GrResponseErrorBanner/parsers/problemDetailsParser.ts
|
|
549
|
+
/**
|
|
550
|
+
* Парсер RFC 7807 «Problem Details for HTTP APIs»:
|
|
551
|
+
*
|
|
552
|
+
* ```json
|
|
553
|
+
* { "type": "https://example.com/probs/out-of-credit",
|
|
554
|
+
* "title": "You do not have enough credit.",
|
|
555
|
+
* "status": 403,
|
|
556
|
+
* "detail": "Your current balance is 30.",
|
|
557
|
+
* "errors": { "balance": ["..."] } }
|
|
558
|
+
* ```
|
|
559
|
+
*
|
|
560
|
+
* Активируется, если есть `body.type` + `body.title`, либо
|
|
561
|
+
* Content-Type заголовка `application/problem+json`. Берёт `title` как
|
|
562
|
+
* `message`, `detail` — в `details[0]`, status — в `status`.
|
|
563
|
+
*/
|
|
564
|
+
var problemDetailsParser = (ctx) => {
|
|
565
|
+
const body = ctx.body;
|
|
566
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return null;
|
|
567
|
+
const data = body;
|
|
568
|
+
const isProblemContentType = ctx.headers?.["content-type"]?.includes("application/problem+json");
|
|
569
|
+
const hasShape = typeof data.title === "string" && (typeof data.type === "string" || typeof data.status === "number");
|
|
570
|
+
if (!isProblemContentType && !hasShape) return null;
|
|
571
|
+
const message = typeof data.title === "string" ? data.title : ctx.texts.unknownMessage;
|
|
572
|
+
const details = [];
|
|
573
|
+
if (typeof data.detail === "string" && data.detail) details.push(data.detail);
|
|
574
|
+
const status = typeof data.status === "number" ? data.status : ctx.status;
|
|
575
|
+
return {
|
|
576
|
+
message,
|
|
577
|
+
details: details.length ? details : void 0,
|
|
578
|
+
status
|
|
579
|
+
};
|
|
580
|
+
};
|
|
581
|
+
//#endregion
|
|
582
|
+
//#region src/components/GrResponseErrorBanner/parsers/createResponseErrorClassifier.ts
|
|
583
|
+
/**
|
|
584
|
+
* Дефолтная цепочка парсеров. Порядок важен:
|
|
585
|
+
* `abort → network → fileValidation → laravel → problemDetails → jsonApi → httpStatus → plainMessage`.
|
|
586
|
+
*
|
|
587
|
+
* - Ранние специализированные парсеры могут поставить `stop` (abort/file).
|
|
588
|
+
* - HTTP-парсер даёт fallback-kind/message, чтобы остальное могло уточнить.
|
|
589
|
+
* - `plainMessage` всегда последний — добывает хоть какое-то сообщение.
|
|
590
|
+
*/
|
|
591
|
+
var defaultResponseErrorParsers = [
|
|
592
|
+
abortErrorParser,
|
|
593
|
+
networkErrorParser,
|
|
594
|
+
fileValidationParser,
|
|
595
|
+
laravelValidationParser,
|
|
596
|
+
problemDetailsParser,
|
|
597
|
+
jsonApiErrorParser,
|
|
598
|
+
httpStatusParser,
|
|
599
|
+
plainMessageParser
|
|
600
|
+
];
|
|
601
|
+
/** Добавляет пользовательские парсеры в начало дефолтной цепочки. */
|
|
602
|
+
function extendDefaultParsers(extra) {
|
|
603
|
+
return [...extra, ...defaultResponseErrorParsers];
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Фабрика классификатора. Возвращает функцию `(raw, meta?) => Promise<ResponseErrorInfo>`,
|
|
607
|
+
* которая:
|
|
608
|
+
* 1. нормализует вход через `normalizeError`,
|
|
609
|
+
* 2. строит `ResponseErrorContext`,
|
|
610
|
+
* 3. прогоняет цепочку парсеров (с поддержкой `stop`),
|
|
611
|
+
* 4. собирает финальный `ResponseErrorInfo` с фолбэками из `texts`.
|
|
612
|
+
*/
|
|
613
|
+
function createResponseErrorClassifier(options = {}) {
|
|
614
|
+
const parsers = options.parsers ?? defaultResponseErrorParsers;
|
|
615
|
+
const texts = {
|
|
616
|
+
...DEFAULT_RESPONSE_ERROR_TEXTS,
|
|
617
|
+
...options.texts ?? {}
|
|
618
|
+
};
|
|
619
|
+
const messageKey = options.messageKey ?? "message";
|
|
620
|
+
return async function classify(raw, meta = {}) {
|
|
621
|
+
const ctx = {
|
|
622
|
+
...await normalizeError(raw),
|
|
623
|
+
meta: {
|
|
624
|
+
...meta,
|
|
625
|
+
_messageKey: messageKey
|
|
626
|
+
},
|
|
627
|
+
texts
|
|
628
|
+
};
|
|
629
|
+
let kind;
|
|
630
|
+
let status = ctx.status;
|
|
631
|
+
let message;
|
|
632
|
+
let details;
|
|
633
|
+
let fieldErrors;
|
|
634
|
+
for (const parser of parsers) {
|
|
635
|
+
const result = parser(ctx);
|
|
636
|
+
if (!result) continue;
|
|
637
|
+
if (result.kind) kind = result.kind;
|
|
638
|
+
if (typeof result.status === "number") status = result.status;
|
|
639
|
+
if (result.message) message = result.message;
|
|
640
|
+
if (result.details?.length) details = result.details;
|
|
641
|
+
if (result.fieldErrors?.length) fieldErrors = result.fieldErrors;
|
|
642
|
+
if (result.stop) break;
|
|
643
|
+
}
|
|
644
|
+
if (!kind) kind = "unknown";
|
|
645
|
+
if (!message) message = resolveDefaultMessage(kind, texts);
|
|
646
|
+
return {
|
|
647
|
+
kind,
|
|
648
|
+
status,
|
|
649
|
+
message,
|
|
650
|
+
details,
|
|
651
|
+
fieldErrors,
|
|
652
|
+
raw,
|
|
653
|
+
meta
|
|
654
|
+
};
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
function resolveDefaultMessage(kind, texts) {
|
|
658
|
+
switch (kind) {
|
|
659
|
+
case "network": return texts.networkMessage;
|
|
660
|
+
case "aborted": return texts.abortedMessage;
|
|
661
|
+
case "validation": return texts.validationMessage;
|
|
662
|
+
case "client": return texts.clientMessage;
|
|
663
|
+
case "server": return texts.serverMessage;
|
|
664
|
+
default: return texts.unknownMessage;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
function resolveResponseErrorTitle(kind, texts) {
|
|
668
|
+
switch (kind) {
|
|
669
|
+
case "network": return texts.networkTitle;
|
|
670
|
+
case "aborted": return texts.abortedTitle;
|
|
671
|
+
case "validation": return texts.validationTitle;
|
|
672
|
+
case "client": return texts.clientTitle;
|
|
673
|
+
case "server": return texts.serverTitle;
|
|
674
|
+
default: return texts.unknownTitle;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
//#endregion
|
|
678
|
+
//#region src/components/GrResponseErrorBanner/GrResponseErrorBanner.vue?vue&type=script&setup=true&lang.ts
|
|
679
|
+
var _hoisted_1 = { class: "flex flex-col gap-2" };
|
|
680
|
+
var _hoisted_2 = { class: "flex flex-wrap items-center gap-2" };
|
|
681
|
+
var _hoisted_3 = ["data-testid"];
|
|
682
|
+
var _hoisted_4 = ["data-testid"];
|
|
683
|
+
var _hoisted_5 = ["data-testid"];
|
|
684
|
+
var _hoisted_6 = {
|
|
685
|
+
key: 1,
|
|
686
|
+
class: "mt-1 flex flex-wrap items-center gap-2"
|
|
687
|
+
};
|
|
688
|
+
//#endregion
|
|
689
|
+
//#region src/components/GrResponseErrorBanner/GrResponseErrorBanner.vue
|
|
690
|
+
var GrResponseErrorBanner_default = /* @__PURE__ */ defineComponent({
|
|
691
|
+
__name: "GrResponseErrorBanner",
|
|
692
|
+
props: {
|
|
693
|
+
error: {},
|
|
694
|
+
texts: { default: () => ({}) },
|
|
695
|
+
tone: { default: void 0 },
|
|
696
|
+
toneByKind: { default: () => ({}) },
|
|
697
|
+
showDetails: {
|
|
698
|
+
type: Boolean,
|
|
699
|
+
default: true
|
|
700
|
+
},
|
|
701
|
+
showFieldLabels: {
|
|
702
|
+
type: Boolean,
|
|
703
|
+
default: true
|
|
704
|
+
},
|
|
705
|
+
fieldLabels: { default: () => ({}) },
|
|
706
|
+
dedupeDetails: {
|
|
707
|
+
type: Boolean,
|
|
708
|
+
default: true
|
|
709
|
+
},
|
|
710
|
+
canRetry: {
|
|
711
|
+
type: Boolean,
|
|
712
|
+
default: false
|
|
713
|
+
},
|
|
714
|
+
canDismiss: {
|
|
715
|
+
type: Boolean,
|
|
716
|
+
default: true
|
|
717
|
+
},
|
|
718
|
+
autoHideKinds: { default: () => DEFAULT_AUTO_HIDE_KINDS },
|
|
719
|
+
showStatus: {
|
|
720
|
+
type: Boolean,
|
|
721
|
+
default: true
|
|
722
|
+
},
|
|
723
|
+
testIdPrefix: { default: "response-error" }
|
|
724
|
+
},
|
|
725
|
+
emits: ["retry", "dismiss"],
|
|
726
|
+
setup(__props, { emit: __emit }) {
|
|
727
|
+
const props = __props;
|
|
728
|
+
const emit = __emit;
|
|
729
|
+
const { t } = useGranularityTranslations();
|
|
730
|
+
const i18nTexts = computed(() => {
|
|
731
|
+
const out = {};
|
|
732
|
+
for (const key of Object.keys(DEFAULT_RESPONSE_ERROR_TEXTS)) out[key] = t(`gr.responseError.${key}`, DEFAULT_RESPONSE_ERROR_TEXTS[key]);
|
|
733
|
+
return out;
|
|
734
|
+
});
|
|
735
|
+
const mergedTexts = computed(() => ({
|
|
736
|
+
...DEFAULT_RESPONSE_ERROR_TEXTS,
|
|
737
|
+
...i18nTexts.value,
|
|
738
|
+
...props.texts
|
|
739
|
+
}));
|
|
740
|
+
const mergedToneByKind = computed(() => ({
|
|
741
|
+
...DEFAULT_TONE_BY_KIND,
|
|
742
|
+
...props.toneByKind
|
|
743
|
+
}));
|
|
744
|
+
const isHidden = computed(() => {
|
|
745
|
+
if (!props.error) return true;
|
|
746
|
+
return props.autoHideKinds.includes(props.error.kind);
|
|
747
|
+
});
|
|
748
|
+
const tone = computed(() => {
|
|
749
|
+
if (props.tone) return props.tone;
|
|
750
|
+
if (!props.error) return "danger";
|
|
751
|
+
return mergedToneByKind.value[props.error.kind] ?? "danger";
|
|
752
|
+
});
|
|
753
|
+
const title = computed(() => {
|
|
754
|
+
if (!props.error) return "";
|
|
755
|
+
return resolveResponseErrorTitle(props.error.kind, mergedTexts.value);
|
|
756
|
+
});
|
|
757
|
+
/**
|
|
758
|
+
* Маппинг `kind` → ключ поля сообщения в `ResponseErrorTexts`.
|
|
759
|
+
* Используется, чтобы подменить «дефолтное английское» fallback-сообщение
|
|
760
|
+
* классификатора на i18n-перевод (или текущий `mergedTexts.<kind>Message`).
|
|
761
|
+
*/
|
|
762
|
+
const KIND_MESSAGE_KEY = {
|
|
763
|
+
network: "networkMessage",
|
|
764
|
+
aborted: "abortedMessage",
|
|
765
|
+
validation: "validationMessage",
|
|
766
|
+
client: "clientMessage",
|
|
767
|
+
server: "serverMessage",
|
|
768
|
+
unknown: "unknownMessage"
|
|
769
|
+
};
|
|
770
|
+
const DEFAULT_FALLBACK_MESSAGES = new Set([
|
|
771
|
+
DEFAULT_RESPONSE_ERROR_TEXTS.networkMessage,
|
|
772
|
+
DEFAULT_RESPONSE_ERROR_TEXTS.abortedMessage,
|
|
773
|
+
DEFAULT_RESPONSE_ERROR_TEXTS.validationMessage,
|
|
774
|
+
DEFAULT_RESPONSE_ERROR_TEXTS.clientMessage,
|
|
775
|
+
DEFAULT_RESPONSE_ERROR_TEXTS.serverMessage,
|
|
776
|
+
DEFAULT_RESPONSE_ERROR_TEXTS.unknownMessage
|
|
777
|
+
]);
|
|
778
|
+
const message = computed(() => {
|
|
779
|
+
const raw = props.error?.message ?? "";
|
|
780
|
+
if (!props.error) return raw;
|
|
781
|
+
if (!raw || DEFAULT_FALLBACK_MESSAGES.has(raw)) return mergedTexts.value[KIND_MESSAGE_KEY[props.error.kind]];
|
|
782
|
+
return raw;
|
|
783
|
+
});
|
|
784
|
+
/**
|
|
785
|
+
* Сводный список «деталей» под основным `message`.
|
|
786
|
+
* Предпочитаем `fieldErrors` (поле → сообщения), затем — плоский `details`.
|
|
787
|
+
* Дедуплицируем и убираем повтор с `message`.
|
|
788
|
+
*/
|
|
789
|
+
const detailEntries = computed(() => {
|
|
790
|
+
if (!props.error || !props.showDetails) return [];
|
|
791
|
+
const seen = /* @__PURE__ */ new Set();
|
|
792
|
+
if (props.dedupeDetails) {
|
|
793
|
+
const main = message.value.trim();
|
|
794
|
+
if (main) seen.add(main);
|
|
795
|
+
}
|
|
796
|
+
const result = [];
|
|
797
|
+
const fieldErrors = props.error.fieldErrors;
|
|
798
|
+
if (fieldErrors?.length) {
|
|
799
|
+
for (const fe of fieldErrors) for (const m of fe.messages) {
|
|
800
|
+
const text = m.trim();
|
|
801
|
+
if (!text || seen.has(text)) continue;
|
|
802
|
+
seen.add(text);
|
|
803
|
+
result.push({
|
|
804
|
+
field: fe.field,
|
|
805
|
+
text
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
if (result.length) return result;
|
|
809
|
+
}
|
|
810
|
+
const details = props.error.details;
|
|
811
|
+
if (details?.length) for (const d of details) {
|
|
812
|
+
const text = (d ?? "").trim();
|
|
813
|
+
if (!text || seen.has(text)) continue;
|
|
814
|
+
seen.add(text);
|
|
815
|
+
result.push({ text });
|
|
816
|
+
}
|
|
817
|
+
return result;
|
|
818
|
+
});
|
|
819
|
+
function fieldLabel(field) {
|
|
820
|
+
if (!field) return void 0;
|
|
821
|
+
return props.fieldLabels[field] ?? field;
|
|
822
|
+
}
|
|
823
|
+
function onRetry() {
|
|
824
|
+
if (props.error) emit("retry", props.error);
|
|
825
|
+
}
|
|
826
|
+
function onDismiss() {
|
|
827
|
+
emit("dismiss");
|
|
828
|
+
}
|
|
829
|
+
return (_ctx, _cache) => {
|
|
830
|
+
return !isHidden.value && props.error ? (openBlock(), createBlock(GrAlert_default, {
|
|
831
|
+
key: 0,
|
|
832
|
+
tone: tone.value,
|
|
833
|
+
title: title.value,
|
|
834
|
+
closable: props.canDismiss,
|
|
835
|
+
"data-testid": `${props.testIdPrefix}-banner`,
|
|
836
|
+
"data-kind": props.error.kind,
|
|
837
|
+
onClose: onDismiss
|
|
838
|
+
}, {
|
|
839
|
+
default: withCtx(() => [createElementVNode("div", _hoisted_1, [
|
|
840
|
+
createElementVNode("div", _hoisted_2, [props.showStatus && typeof props.error.status === "number" ? (openBlock(), createElementBlock("span", {
|
|
841
|
+
key: 0,
|
|
842
|
+
class: "inline-flex items-center rounded-[var(--ds-radius-sm)] bg-[color-mix(in_srgb,currentColor_12%,transparent)] px-1.5 py-0.5 text-[11px] font-700 leading-none",
|
|
843
|
+
"data-testid": `${props.testIdPrefix}-status`
|
|
844
|
+
}, " HTTP " + toDisplayString(props.error.status), 9, _hoisted_3)) : createCommentVNode("", true), createElementVNode("span", {
|
|
845
|
+
class: "text-[13px]",
|
|
846
|
+
"data-testid": `${props.testIdPrefix}-message`
|
|
847
|
+
}, toDisplayString(message.value), 9, _hoisted_4)]),
|
|
848
|
+
detailEntries.value.length ? (openBlock(), createElementBlock("ul", {
|
|
849
|
+
key: 0,
|
|
850
|
+
class: "m-0 list-disc pl-5 text-[12px]",
|
|
851
|
+
"data-testid": `${props.testIdPrefix}-details`
|
|
852
|
+
}, [(openBlock(true), createElementBlock(Fragment, null, renderList(detailEntries.value, (entry, idx) => {
|
|
853
|
+
return openBlock(), createElementBlock("li", { key: `${entry.field ?? ""}:${idx}` }, [props.showFieldLabels && entry.field ? (openBlock(), createElementBlock(Fragment, { key: 0 }, [createElementVNode("strong", null, toDisplayString(fieldLabel(entry.field)) + ":", 1), _cache[0] || (_cache[0] = createTextVNode("\xA0 ", -1))], 64)) : createCommentVNode("", true), createTextVNode(toDisplayString(entry.text), 1)]);
|
|
854
|
+
}), 128))], 8, _hoisted_5)) : createCommentVNode("", true),
|
|
855
|
+
props.canRetry ? (openBlock(), createElementBlock("div", _hoisted_6, [createVNode(GrButton_default, {
|
|
856
|
+
size: "sm",
|
|
857
|
+
variant: "outline",
|
|
858
|
+
"data-testid": `${props.testIdPrefix}-retry`,
|
|
859
|
+
onClick: onRetry
|
|
860
|
+
}, {
|
|
861
|
+
default: withCtx(() => [createTextVNode(toDisplayString(mergedTexts.value.retryLabel), 1)]),
|
|
862
|
+
_: 1
|
|
863
|
+
}, 8, ["data-testid"])])) : createCommentVNode("", true)
|
|
864
|
+
])]),
|
|
865
|
+
_: 1
|
|
866
|
+
}, 8, [
|
|
867
|
+
"tone",
|
|
868
|
+
"title",
|
|
869
|
+
"closable",
|
|
870
|
+
"data-testid",
|
|
871
|
+
"data-kind"
|
|
872
|
+
])) : createCommentVNode("", true);
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
});
|
|
876
|
+
//#endregion
|
|
877
|
+
//#region src/components/GrResponseErrorBanner/GrUploadErrorBanner.vue
|
|
878
|
+
var GrUploadErrorBanner_default = /* @__PURE__ */ defineComponent({
|
|
879
|
+
__name: "GrUploadErrorBanner",
|
|
880
|
+
props: {
|
|
881
|
+
error: {},
|
|
882
|
+
files: { default: () => [] },
|
|
883
|
+
texts: { default: () => ({}) },
|
|
884
|
+
tone: { default: void 0 },
|
|
885
|
+
toneByKind: { default: () => ({}) },
|
|
886
|
+
canRetry: {
|
|
887
|
+
type: Boolean,
|
|
888
|
+
default: true
|
|
889
|
+
},
|
|
890
|
+
canDismiss: {
|
|
891
|
+
type: Boolean,
|
|
892
|
+
default: true
|
|
893
|
+
},
|
|
894
|
+
autoHideKinds: { default: () => [] },
|
|
895
|
+
testIdPrefix: { default: "upload-error" }
|
|
896
|
+
},
|
|
897
|
+
emits: ["retry", "dismiss"],
|
|
898
|
+
setup(__props, { emit: __emit }) {
|
|
899
|
+
/**
|
|
900
|
+
* Тонкая обёртка над `GrResponseErrorBanner` с предсетами для сценария
|
|
901
|
+
* «загрузка файлов»:
|
|
902
|
+
*
|
|
903
|
+
* - Включена кнопка «Повторить» по умолчанию (`canRetry=true`).
|
|
904
|
+
* - Тексты сдвинуты под загрузку файлов.
|
|
905
|
+
* - Опциональный prop `files` пробрасывает контекст наружу через `retry`.
|
|
906
|
+
*
|
|
907
|
+
* Парсеры/нормализация — те же, что у базового баннера. Если нужны кастомные
|
|
908
|
+
* парсеры — используйте напрямую `GrResponseErrorBanner` + `useResponseError`.
|
|
909
|
+
*/
|
|
910
|
+
const props = __props;
|
|
911
|
+
const emit = __emit;
|
|
912
|
+
const UPLOAD_TEXTS = {
|
|
913
|
+
networkTitle: "Failed to upload files",
|
|
914
|
+
networkMessage: "Check your connection and try again.",
|
|
915
|
+
validationTitle: "Files did not pass validation",
|
|
916
|
+
validationMessage: "Some of the files do not meet the requirements.",
|
|
917
|
+
serverTitle: "Upload error on the server",
|
|
918
|
+
serverMessage: "The server could not process the upload. Please try again."
|
|
919
|
+
};
|
|
920
|
+
const mergedTexts = computed(() => ({
|
|
921
|
+
...UPLOAD_TEXTS,
|
|
922
|
+
...props.texts
|
|
923
|
+
}));
|
|
924
|
+
function onRetry(info) {
|
|
925
|
+
emit("retry", {
|
|
926
|
+
error: info,
|
|
927
|
+
files: props.files
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
return (_ctx, _cache) => {
|
|
931
|
+
return openBlock(), createBlock(GrResponseErrorBanner_default, {
|
|
932
|
+
error: props.error,
|
|
933
|
+
texts: mergedTexts.value,
|
|
934
|
+
tone: props.tone,
|
|
935
|
+
"tone-by-kind": props.toneByKind,
|
|
936
|
+
"can-retry": props.canRetry,
|
|
937
|
+
"can-dismiss": props.canDismiss,
|
|
938
|
+
"auto-hide-kinds": props.autoHideKinds,
|
|
939
|
+
"test-id-prefix": props.testIdPrefix,
|
|
940
|
+
onRetry,
|
|
941
|
+
onDismiss: _cache[0] || (_cache[0] = ($event) => emit("dismiss"))
|
|
942
|
+
}, null, 8, [
|
|
943
|
+
"error",
|
|
944
|
+
"texts",
|
|
945
|
+
"tone",
|
|
946
|
+
"tone-by-kind",
|
|
947
|
+
"can-retry",
|
|
948
|
+
"can-dismiss",
|
|
949
|
+
"auto-hide-kinds",
|
|
950
|
+
"test-id-prefix"
|
|
951
|
+
]);
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
//#endregion
|
|
956
|
+
//#region src/components/GrResponseErrorBanner/GrFormErrorBanner.vue
|
|
957
|
+
var GrFormErrorBanner_default = /* @__PURE__ */ defineComponent({
|
|
958
|
+
__name: "GrFormErrorBanner",
|
|
959
|
+
props: {
|
|
960
|
+
error: {},
|
|
961
|
+
fieldLabels: { default: () => ({}) },
|
|
962
|
+
texts: { default: () => ({}) },
|
|
963
|
+
tone: { default: void 0 },
|
|
964
|
+
toneByKind: { default: () => ({ validation: "warning" }) },
|
|
965
|
+
canRetry: {
|
|
966
|
+
type: Boolean,
|
|
967
|
+
default: false
|
|
968
|
+
},
|
|
969
|
+
canDismiss: {
|
|
970
|
+
type: Boolean,
|
|
971
|
+
default: true
|
|
972
|
+
},
|
|
973
|
+
autoHideKinds: { default: () => [] },
|
|
974
|
+
testIdPrefix: { default: "form-error" }
|
|
975
|
+
},
|
|
976
|
+
emits: ["retry", "dismiss"],
|
|
977
|
+
setup(__props, { emit: __emit }) {
|
|
978
|
+
/**
|
|
979
|
+
* Тонкая обёртка над `GrResponseErrorBanner` с предсетами для сценария
|
|
980
|
+
* «валидация формы»:
|
|
981
|
+
*
|
|
982
|
+
* - `showFieldLabels=true` — рисуются подписи `field:` перед каждой строкой.
|
|
983
|
+
* - `canRetry=false` — обычно пользователь правит поля сам, retry не нужен.
|
|
984
|
+
* - `toneByKind.validation = 'warning'` — визуально «исправь», не «упало».
|
|
985
|
+
* - Тексты сдвинуты под формы.
|
|
986
|
+
*
|
|
987
|
+
* Для кастомных парсеров используйте напрямую `GrResponseErrorBanner` +
|
|
988
|
+
* `useResponseError({ parsers: () => extendDefaultParsers([myParser]) })`.
|
|
989
|
+
*/
|
|
990
|
+
const props = __props;
|
|
991
|
+
const emit = __emit;
|
|
992
|
+
const FORM_TEXTS = {
|
|
993
|
+
validationTitle: "Check the form fields",
|
|
994
|
+
validationMessage: "The server rejected the data. Fix the highlighted fields and try again.",
|
|
995
|
+
clientTitle: "Failed to save",
|
|
996
|
+
serverTitle: "Save error",
|
|
997
|
+
serverMessage: "Could not save the data. Please try again later."
|
|
998
|
+
};
|
|
999
|
+
const mergedTexts = computed(() => ({
|
|
1000
|
+
...FORM_TEXTS,
|
|
1001
|
+
...props.texts
|
|
1002
|
+
}));
|
|
1003
|
+
return (_ctx, _cache) => {
|
|
1004
|
+
return openBlock(), createBlock(GrResponseErrorBanner_default, {
|
|
1005
|
+
error: props.error,
|
|
1006
|
+
texts: mergedTexts.value,
|
|
1007
|
+
tone: props.tone,
|
|
1008
|
+
"tone-by-kind": props.toneByKind,
|
|
1009
|
+
"field-labels": props.fieldLabels,
|
|
1010
|
+
"show-field-labels": "",
|
|
1011
|
+
"can-retry": props.canRetry,
|
|
1012
|
+
"can-dismiss": props.canDismiss,
|
|
1013
|
+
"auto-hide-kinds": props.autoHideKinds,
|
|
1014
|
+
"test-id-prefix": props.testIdPrefix,
|
|
1015
|
+
onRetry: _cache[0] || (_cache[0] = (info) => emit("retry", info)),
|
|
1016
|
+
onDismiss: _cache[1] || (_cache[1] = ($event) => emit("dismiss"))
|
|
1017
|
+
}, null, 8, [
|
|
1018
|
+
"error",
|
|
1019
|
+
"texts",
|
|
1020
|
+
"tone",
|
|
1021
|
+
"tone-by-kind",
|
|
1022
|
+
"field-labels",
|
|
1023
|
+
"can-retry",
|
|
1024
|
+
"can-dismiss",
|
|
1025
|
+
"auto-hide-kinds",
|
|
1026
|
+
"test-id-prefix"
|
|
1027
|
+
]);
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
1030
|
+
});
|
|
1031
|
+
//#endregion
|
|
1032
|
+
//#region src/components/GrResponseErrorBanner/useResponseError.ts
|
|
1033
|
+
/**
|
|
1034
|
+
* Stateful-композабл вокруг `createResponseErrorClassifier`. Хранит
|
|
1035
|
+
* `currentError`, инкапсулирует setRaw/retry/dismiss. Подходит для типичного
|
|
1036
|
+
* сценария «поймал ошибку запроса → показал баннер → ретрай/скрыть».
|
|
1037
|
+
*/
|
|
1038
|
+
function useResponseError(options = {}) {
|
|
1039
|
+
const currentError = shallowRef(null);
|
|
1040
|
+
const lastRaw = ref(null);
|
|
1041
|
+
function getClassifier() {
|
|
1042
|
+
return createResponseErrorClassifier({
|
|
1043
|
+
parsers: options.parsers?.() ?? defaultResponseErrorParsers,
|
|
1044
|
+
texts: options.texts?.() ?? {},
|
|
1045
|
+
messageKey: options.messageKey?.()
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
function isAutoHidden(kind) {
|
|
1049
|
+
return (options.autoHideKinds?.() ?? DEFAULT_AUTO_HIDE_KINDS).includes(kind);
|
|
1050
|
+
}
|
|
1051
|
+
async function classify(raw, extra) {
|
|
1052
|
+
const info = await getClassifier()(raw, {
|
|
1053
|
+
...options.meta?.() ?? {},
|
|
1054
|
+
...extra ?? {}
|
|
1055
|
+
});
|
|
1056
|
+
options.onClassify?.(info);
|
|
1057
|
+
return info;
|
|
1058
|
+
}
|
|
1059
|
+
async function setRaw(raw, extra) {
|
|
1060
|
+
lastRaw.value = raw;
|
|
1061
|
+
const info = await classify(raw, extra);
|
|
1062
|
+
if (isAutoHidden(info.kind)) {
|
|
1063
|
+
currentError.value = null;
|
|
1064
|
+
return null;
|
|
1065
|
+
}
|
|
1066
|
+
currentError.value = info;
|
|
1067
|
+
return info;
|
|
1068
|
+
}
|
|
1069
|
+
function setError(info) {
|
|
1070
|
+
currentError.value = info;
|
|
1071
|
+
}
|
|
1072
|
+
function dismiss() {
|
|
1073
|
+
currentError.value = null;
|
|
1074
|
+
}
|
|
1075
|
+
async function retry(handler) {
|
|
1076
|
+
const info = currentError.value;
|
|
1077
|
+
if (!info) return;
|
|
1078
|
+
await handler(info);
|
|
1079
|
+
currentError.value = null;
|
|
1080
|
+
}
|
|
1081
|
+
return {
|
|
1082
|
+
currentError,
|
|
1083
|
+
isVisible: computed(() => currentError.value !== null),
|
|
1084
|
+
lastRaw,
|
|
1085
|
+
setRaw,
|
|
1086
|
+
setError,
|
|
1087
|
+
classify,
|
|
1088
|
+
dismiss,
|
|
1089
|
+
reset: dismiss,
|
|
1090
|
+
retry
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
//#endregion
|
|
121
1094
|
//#region src/components/shared/tones.ts
|
|
122
1095
|
/**
|
|
123
1096
|
* Единый перечень дизайн-тонов для всех компонентов библиотеки.
|
|
@@ -138,4 +1111,4 @@ var GR_TONES = [
|
|
|
138
1111
|
"azure"
|
|
139
1112
|
];
|
|
140
1113
|
//#endregion
|
|
141
|
-
export { DEFAULT_GR_DIALOG_BODY_CONFIG, DEFAULT_GR_DIALOG_FOOTER_CONFIG, DEFAULT_GR_DIALOG_HEADER_CONFIG, FileValidationError, GR_COLLAPSE_CONTEXT, GR_RADIO_GROUP_CONTEXT, GR_TONES, GR_UPLOAD_STATE_IDLE, GrAlert_default as GrAlert, GrAvatar_default as GrAvatar, GrBadge_default as GrBadge, GrBadgeWrap_default as GrBadgeWrap, GrBottomNav_default as GrBottomNav, GrButton_default as GrButton, GrButtonGroup_default as GrButtonGroup, GrCard_default as GrCard, GrCheckbox_default as GrCheckbox, GrCollapse_default as GrCollapse, GrCollapseItem_default as GrCollapseItem, GrConfirmDialog_default as GrConfirmDialog, GrDataTable_default as GrDataTable, GrDialog_default as GrDialog, GrDialogCloseButton_default as GrDialogCloseButton, GrDialogFooter_default as GrDialogFooter, GrDialogHeader_default as GrDialogHeader, GrDrawer_default as GrDrawer, GrDropdown_default as GrDropdown, GrDropdownMenu_default as GrDropdownMenu, GrDropdownMenuColumn_default as GrDropdownMenuColumn, GrDropdownMenuColumns_default as GrDropdownMenuColumns, GrDropdownMenuDivider_default as GrDropdownMenuDivider, GrDropdownMenuGroup_default as GrDropdownMenuGroup, GrDropdownMenuHeader_default as GrDropdownMenuHeader, GrDropdownMenuItem_default as GrDropdownMenuItem, GrDropdownMenuList_default as GrDropdownMenuList, GrEmptyState_default as GrEmptyState, GrFileUpload_default as GrFileUpload, GrFormField_default as GrFormField, GrFormFile_default as GrFormFile, GrFormSection_default as GrFormSection, GrIcon_default as GrIcon, GrImageViewer_default as GrImageViewer, GrInput_default as GrInput, GrInputTag_default as GrInputTag, GrLink_default as GrLink, GrList_default as GrList, GrListItem_default as GrListItem, GrLoading_default as GrLoading, GrModal_default as GrModal, GrNavbar_default as GrNavbar, GrNumberInput_default as GrNumberInput, GrPagination_default as GrPagination, GrProgressBar_default as GrProgressBar, GrPromptDialog_default as GrPromptDialog, GrRadio_default as GrRadio, GrRadioGroup_default as GrRadioGroup, GrSegmented_default as GrSegmented, GrSelect_default as GrSelect, GrSidebar_default as GrSidebar, GrSkeleton_default as GrSkeleton, GrSwitch_default as GrSwitch, GrTable_default as GrTable, GrTabs_default as GrTabs, GrTextarea_default as GrTextarea, GrToaster_default as GrToaster, GrTooltip_default as GrTooltip, GrTree_default as GrTree, GrTreeSelect_default as GrTreeSelect, GrUploadAbortError, GrUploadHttpError, acceptValidator, allowedExtensionsValidator, allowedMimeTypesValidator, createLoading, getGrModalPanelClass, grAlertConfig, grAvatarConfig, grAvatarSafelist, grBadgeConfig, grBadgeSafelist, grBadgeWrapConfig, grBottomNavConfig, grButtonClass, grButtonClassTokens, grButtonConfig, grButtonGroupConfig, grButtonSafelist, grCardConfig, grCheckboxConfig, grCollapseConfig, grConfirmDialogConfig, grDataTableConfig, grDialogSafelist, grDrawerConfig, grDrawerSafelist, grDropdownConfig, grDropdownMenuConfig, grDropdownSafelist, grEmptyStateConfig, grFileUploadConfig, grFormFieldConfig, grFormFileConfig, grFormSectionConfig, grIconConfig, grImageViewerConfig, grInputConfig, grInputSafelist, grInputTagConfig, grInputTagSafelist, grLinkConfig, grLinkSafelist, grListConfig, grLoadingConfig, grLoadingSafelist, grModalClassTokens, grModalConfig, grModalSafelist, grNavbarConfig, grNumberInputConfig, grNumberInputSafelist, grPaginationConfig, grProgressBarConfig, grProgressBarSafelist, grPromptDialogConfig, grRadioConfig, grRadioGroupConfig, grRadioSafelist, grSegmentedConfig, grSegmentedSafelist, grSelectConfig, grSelectSafelist, grSidebarConfig, grSkeletonConfig, grSwitchConfig, grSwitchSafelist, grTableConfig, grTabsConfig, grTextareaConfig, grTextareaSafelist, grToasterConfig, grToasterSafelist, grTooltipConfig, grTreeConfig, grTreeSafelist, grTreeSelectConfig, grTreeSelectSafelist, initThemeEarly, matchAccept, maxFileSizeBytesValidator, maxSizeMbValidator, maxTotalSizeBytesValidator, normalizeFiles, resolveGrDialogSectionConfig, resolveGrDialogTitle, runFileValidators, useTheme, useToast, vAutofocus, vAutosize, vClickOutside, vDropzone, vHotkey, vLoading };
|
|
1114
|
+
export { DEFAULT_AUTO_HIDE_KINDS, DEFAULT_GR_DIALOG_BODY_CONFIG, DEFAULT_GR_DIALOG_FOOTER_CONFIG, DEFAULT_GR_DIALOG_HEADER_CONFIG, DEFAULT_RESPONSE_ERROR_TEXTS, DEFAULT_TONE_BY_KIND, FileValidationError, GR_COLLAPSE_CONTEXT, GR_RADIO_GROUP_CONTEXT, GR_TONES, GR_UPLOAD_STATE_IDLE, GrAlert_default as GrAlert, GrAvatar_default as GrAvatar, GrBadge_default as GrBadge, GrBadgeWrap_default as GrBadgeWrap, GrBottomNav_default as GrBottomNav, GrButton_default as GrButton, GrButtonGroup_default as GrButtonGroup, GrCard_default as GrCard, GrCheckbox_default as GrCheckbox, GrCollapse_default as GrCollapse, GrCollapseItem_default as GrCollapseItem, GrConfirmDialog_default as GrConfirmDialog, GrDataTable_default as GrDataTable, GrDialog_default as GrDialog, GrDialogCloseButton_default as GrDialogCloseButton, GrDialogFooter_default as GrDialogFooter, GrDialogHeader_default as GrDialogHeader, GrDrawer_default as GrDrawer, GrDropdown_default as GrDropdown, GrDropdownMenu_default as GrDropdownMenu, GrDropdownMenuColumn_default as GrDropdownMenuColumn, GrDropdownMenuColumns_default as GrDropdownMenuColumns, GrDropdownMenuDivider_default as GrDropdownMenuDivider, GrDropdownMenuGroup_default as GrDropdownMenuGroup, GrDropdownMenuHeader_default as GrDropdownMenuHeader, GrDropdownMenuItem_default as GrDropdownMenuItem, GrDropdownMenuList_default as GrDropdownMenuList, GrEmptyState_default as GrEmptyState, GrFileUpload_default as GrFileUpload, GrFormErrorBanner_default as GrFormErrorBanner, GrFormField_default as GrFormField, GrFormFile_default as GrFormFile, GrFormSection_default as GrFormSection, GrIcon_default as GrIcon, GrImageViewer_default as GrImageViewer, GrInput_default as GrInput, GrInputTag_default as GrInputTag, GrLink_default as GrLink, GrList_default as GrList, GrListItem_default as GrListItem, GrLoading_default as GrLoading, GrModal_default as GrModal, GrNavbar_default as GrNavbar, GrNumberInput_default as GrNumberInput, GrPagination_default as GrPagination, GrProgressBar_default as GrProgressBar, GrPromptDialog_default as GrPromptDialog, GrRadio_default as GrRadio, GrRadioGroup_default as GrRadioGroup, GrResponseErrorBanner_default as GrResponseErrorBanner, GrSegmented_default as GrSegmented, GrSelect_default as GrSelect, GrSidebar_default as GrSidebar, GrSkeleton_default as GrSkeleton, GrSwitch_default as GrSwitch, GrTable_default as GrTable, GrTabs_default as GrTabs, GrTextarea_default as GrTextarea, GrToaster_default as GrToaster, GrTooltip_default as GrTooltip, GrTree_default as GrTree, GrTreeSelect_default as GrTreeSelect, GrUploadAbortError, GrUploadErrorBanner_default as GrUploadErrorBanner, GrUploadHttpError, abortErrorParser, acceptValidator, allowedExtensionsValidator, allowedMimeTypesValidator, createLoading, createResponseErrorClassifier, defaultResponseErrorParsers, extendDefaultParsers, fileValidationParser, getGrModalPanelClass, grAlertConfig, grAvatarConfig, grAvatarSafelist, grBadgeConfig, grBadgeSafelist, grBadgeWrapConfig, grBottomNavConfig, grButtonClass, grButtonClassTokens, grButtonConfig, grButtonGroupConfig, grButtonSafelist, grCardConfig, grCheckboxConfig, grCollapseConfig, grConfirmDialogConfig, grDataTableConfig, grDialogSafelist, grDrawerConfig, grDrawerSafelist, grDropdownConfig, grDropdownMenuConfig, grDropdownSafelist, grEmptyStateConfig, grFileUploadConfig, grFormFieldConfig, grFormFileConfig, grFormSectionConfig, grIconConfig, grImageViewerConfig, grInputConfig, grInputSafelist, grInputTagConfig, grInputTagSafelist, grLinkConfig, grLinkSafelist, grListConfig, grLoadingConfig, grLoadingSafelist, grModalClassTokens, grModalConfig, grModalSafelist, grNavbarConfig, grNumberInputConfig, grNumberInputSafelist, grPaginationConfig, grProgressBarConfig, grProgressBarSafelist, grPromptDialogConfig, grRadioConfig, grRadioGroupConfig, grRadioSafelist, grSegmentedConfig, grSegmentedSafelist, grSelectConfig, grSelectSafelist, grSidebarConfig, grSkeletonConfig, grSwitchConfig, grSwitchSafelist, grTableConfig, grTabsConfig, grTextareaConfig, grTextareaSafelist, grToasterConfig, grToasterSafelist, grTooltipConfig, grTreeConfig, grTreeSafelist, grTreeSelectConfig, grTreeSelectSafelist, httpStatusParser, initThemeEarly, jsonApiErrorParser, laravelValidationParser, matchAccept, maxFileSizeBytesValidator, maxSizeMbValidator, maxTotalSizeBytesValidator, networkErrorParser, normalizeError, normalizeFiles, plainMessageParser, problemDetailsParser, resolveGrDialogSectionConfig, resolveGrDialogTitle, resolveResponseErrorTitle, runFileValidators, useResponseError, useTheme, useToast, vAutofocus, vAutosize, vClickOutside, vDropzone, vHotkey, vLoading };
|