@seedgrid/fe-components 2026.11.1 → 2026.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/ai/seedgrid-components.manifest.json +1180 -0
  2. package/dist/generated/compiledStyles.d.ts +1 -1
  3. package/dist/generated/compiledStyles.d.ts.map +1 -1
  4. package/dist/generated/compiledStyles.js +1 -1
  5. package/dist/i18n/en-US.d.ts.map +1 -1
  6. package/dist/i18n/en-US.js +29 -0
  7. package/dist/i18n/es.d.ts.map +1 -1
  8. package/dist/i18n/es.js +29 -0
  9. package/dist/i18n/fr.d.ts.map +1 -1
  10. package/dist/i18n/fr.js +29 -0
  11. package/dist/i18n/pt-BR.d.ts.map +1 -1
  12. package/dist/i18n/pt-BR.js +29 -0
  13. package/dist/i18n/pt-PT.d.ts.map +1 -1
  14. package/dist/i18n/pt-PT.js +29 -0
  15. package/dist/index.d.ts +9 -0
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +4 -0
  18. package/dist/inputs/SgCameraCapture.d.ts +25 -0
  19. package/dist/inputs/SgCameraCapture.d.ts.map +1 -0
  20. package/dist/inputs/SgCameraCapture.js +114 -0
  21. package/dist/inputs/SgCameraCapture.meta.d.ts +4 -0
  22. package/dist/inputs/SgCameraCapture.meta.d.ts.map +1 -0
  23. package/dist/inputs/SgCameraCapture.meta.js +63 -0
  24. package/dist/inputs/SgFileUpload.d.ts +69 -0
  25. package/dist/inputs/SgFileUpload.d.ts.map +1 -0
  26. package/dist/inputs/SgFileUpload.js +234 -0
  27. package/dist/inputs/SgFileUpload.meta.d.ts +4 -0
  28. package/dist/inputs/SgFileUpload.meta.d.ts.map +1 -0
  29. package/dist/inputs/SgFileUpload.meta.js +71 -0
  30. package/dist/inputs/SgImageUpload.d.ts +20 -0
  31. package/dist/inputs/SgImageUpload.d.ts.map +1 -0
  32. package/dist/inputs/SgImageUpload.js +74 -0
  33. package/dist/inputs/SgImageUpload.meta.d.ts +4 -0
  34. package/dist/inputs/SgImageUpload.meta.d.ts.map +1 -0
  35. package/dist/inputs/SgImageUpload.meta.js +73 -0
  36. package/dist/inputs/SgInputDateTime.d.ts +29 -0
  37. package/dist/inputs/SgInputDateTime.d.ts.map +1 -0
  38. package/dist/inputs/SgInputDateTime.js +130 -0
  39. package/dist/inputs/SgInputDateTime.meta.d.ts +4 -0
  40. package/dist/inputs/SgInputDateTime.meta.d.ts.map +1 -0
  41. package/dist/inputs/SgInputDateTime.meta.js +73 -0
  42. package/dist/inputs/cameraLogic.d.ts +27 -0
  43. package/dist/inputs/cameraLogic.d.ts.map +1 -0
  44. package/dist/inputs/cameraLogic.js +57 -0
  45. package/dist/inputs/dateTimeLogic.d.ts +22 -0
  46. package/dist/inputs/dateTimeLogic.d.ts.map +1 -0
  47. package/dist/inputs/dateTimeLogic.js +140 -0
  48. package/dist/inputs/fileUploadLogic.d.ts +62 -0
  49. package/dist/inputs/fileUploadLogic.d.ts.map +1 -0
  50. package/dist/inputs/fileUploadLogic.js +254 -0
  51. package/dist/inputs/imageUploadLogic.d.ts +31 -0
  52. package/dist/inputs/imageUploadLogic.d.ts.map +1 -0
  53. package/dist/inputs/imageUploadLogic.js +70 -0
  54. package/dist/menus/SgDockMenu.d.ts.map +1 -1
  55. package/dist/menus/SgDockMenu.js +2 -0
  56. package/dist/sandbox.cjs +62 -51
  57. package/dist/style.css +1 -1
  58. package/package.json +3 -3
@@ -0,0 +1,140 @@
1
+ // Logica pura do SgInputDateTime (P132): conversao entre instante (ISO 8601 em UTC) e "relogio de parede" no
2
+ // fuso pedido, snap de minutos, validacao e texto de exibicao. Sem DOM: os vetores de paridade com o Flutter
3
+ // (`seedgrid_components/test/widgets/datetime_vectors`) sao gerados DESTE modulo.
4
+ //
5
+ // Fusos aceitos (`timeZone`): omitido ou "local" (o do navegador/dispositivo), "UTC", deslocamento fixo
6
+ // ("+03:00", "-0300", "+5:30") e, so no React, nome IANA ("America/Sao_Paulo"). O Flutter nao traz banco de
7
+ // fusos (sem dependencia nova): la nome IANA cai em "local" (ver DIVERGENCIAS_DO_REACT.md).
8
+ const LOCAL_RE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})(?::(\d{2}))?$/;
9
+ const OFFSET_RE = /^([+-])(\d{1,2})(?::?(\d{2}))?$/;
10
+ function pad(n, size = 2) {
11
+ return String(n).padStart(size, "0");
12
+ }
13
+ /** Deslocamento fixo em minutos ("+03:00" => 180) ou null se nao for um deslocamento. */
14
+ export function parseFixedOffset(timeZone) {
15
+ const m = OFFSET_RE.exec(timeZone.trim());
16
+ if (!m)
17
+ return null;
18
+ const minutes = Number(m[2]) * 60 + Number(m[3] ?? 0);
19
+ if (Number(m[2]) > 14 || Number(m[3] ?? 0) > 59)
20
+ return null;
21
+ return (m[1] === "-" ? -1 : 1) * minutes;
22
+ }
23
+ function isLocalZone(timeZone) {
24
+ return !timeZone || timeZone.trim() === "" || timeZone.trim().toLowerCase() === "local";
25
+ }
26
+ /** Minutos a somar ao UTC para obter o relogio de parede de `instantMs` no fuso. */
27
+ export function zoneOffsetMinutes(instantMs, timeZone) {
28
+ if (isLocalZone(timeZone))
29
+ return -new Date(instantMs).getTimezoneOffset();
30
+ const tz = timeZone.trim();
31
+ if (tz.toUpperCase() === "UTC" || tz.toUpperCase() === "Z")
32
+ return 0;
33
+ const fixed = parseFixedOffset(tz);
34
+ if (fixed !== null)
35
+ return fixed;
36
+ try {
37
+ const parts = new Intl.DateTimeFormat("en-US", {
38
+ timeZone: tz,
39
+ hourCycle: "h23",
40
+ year: "numeric",
41
+ month: "2-digit",
42
+ day: "2-digit",
43
+ hour: "2-digit",
44
+ minute: "2-digit",
45
+ second: "2-digit"
46
+ }).formatToParts(new Date(instantMs));
47
+ const get = (type) => Number(parts.find((p) => p.type === type)?.value);
48
+ const asUtc = Date.UTC(get("year"), get("month") - 1, get("day"), get("hour"), get("minute"), get("second"));
49
+ return Math.round((asUtc - Math.floor(instantMs / 1000) * 1000) / 60000);
50
+ }
51
+ catch {
52
+ return -new Date(instantMs).getTimezoneOffset();
53
+ }
54
+ }
55
+ function toMs(value) {
56
+ if (value === undefined || value === null || value === "")
57
+ return null;
58
+ const ms = value instanceof Date ? value.getTime() : new Date(value).getTime();
59
+ return Number.isNaN(ms) ? null : ms;
60
+ }
61
+ /** Instante (ISO UTC ou Date) => "YYYY-MM-DDTHH:mm[:ss]" no fuso; "" se invalido/vazio. */
62
+ export function instantToWallClock(value, timeZone, showSeconds = false) {
63
+ const ms = toMs(value);
64
+ if (ms === null)
65
+ return "";
66
+ const shifted = new Date(ms + zoneOffsetMinutes(ms, timeZone) * 60000);
67
+ const base = `${pad(shifted.getUTCFullYear(), 4)}-${pad(shifted.getUTCMonth() + 1)}-${pad(shifted.getUTCDate())}T${pad(shifted.getUTCHours())}:${pad(shifted.getUTCMinutes())}`;
68
+ return showSeconds ? `${base}:${pad(shifted.getUTCSeconds())}` : base;
69
+ }
70
+ /** "YYYY-MM-DDTHH:mm[:ss]" no fuso => ISO 8601 UTC ("...Z", ms = 000); null se invalido. */
71
+ export function wallClockToInstant(local, timeZone) {
72
+ const m = LOCAL_RE.exec(local.trim());
73
+ if (!m)
74
+ return null;
75
+ const [y, mo, d, h, mi] = [m[1], m[2], m[3], m[4], m[5]].map(Number);
76
+ const s = Number(m[6] ?? 0);
77
+ if (mo < 1 || mo > 12 || d < 1 || h > 23 || mi > 59 || s > 59)
78
+ return null;
79
+ const naive = Date.UTC(y, mo - 1, d, h, mi, s);
80
+ if (new Date(naive).getUTCDate() !== d)
81
+ return null; // 31/02 e afins
82
+ // Resolve o deslocamento do fuso nesse relogio de parede (duas passadas cobrem a virada de horario de verao).
83
+ let offset = zoneOffsetMinutes(naive, timeZone);
84
+ let instant = naive - offset * 60000;
85
+ const again = zoneOffsetMinutes(instant, timeZone);
86
+ if (again !== offset) {
87
+ offset = again;
88
+ instant = naive - offset * 60000;
89
+ }
90
+ return new Date(instant).toISOString();
91
+ }
92
+ /** Arredonda os minutos ao multiplo de `step` mais proximo (passo invalido ou <= 1 mantem o valor). */
93
+ export function snapMinutes(local, step) {
94
+ const m = LOCAL_RE.exec(local.trim());
95
+ if (!m || !step || !Number.isFinite(step) || step <= 1 || step > 60)
96
+ return local;
97
+ const minutes = Number(m[5]);
98
+ let snapped = Math.round(minutes / step) * step;
99
+ let hour = Number(m[4]);
100
+ let dayShift = 0;
101
+ if (snapped >= 60) {
102
+ snapped = 0;
103
+ hour += 1;
104
+ }
105
+ if (hour >= 24) {
106
+ hour = 0;
107
+ dayShift = 1;
108
+ }
109
+ const date = new Date(Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3]) + dayShift));
110
+ const day = `${pad(date.getUTCFullYear(), 4)}-${pad(date.getUTCMonth() + 1)}-${pad(date.getUTCDate())}`;
111
+ return `${day}T${pad(hour)}:${pad(snapped)}${m[6] !== undefined ? `:${m[6]}` : ""}`;
112
+ }
113
+ /** Codigo do erro (null = valido). `local` e o texto do campo ("" = vazio). min/max sao instantes. */
114
+ export function validateDateTime(local, input = {}) {
115
+ if (!local)
116
+ return input.required ? "required" : null;
117
+ const instant = wallClockToInstant(local, input.timeZone);
118
+ if (!instant)
119
+ return "invalid";
120
+ const ms = new Date(instant).getTime();
121
+ const min = toMs(input.min);
122
+ const max = toMs(input.max);
123
+ const belowMin = min !== null && ms < min;
124
+ const aboveMax = max !== null && ms > max;
125
+ if (!belowMin && !aboveMax)
126
+ return null;
127
+ if (min !== null && max !== null)
128
+ return "range";
129
+ return belowMin ? "min" : "max";
130
+ }
131
+ /** Texto de exibicao de um relogio de parede: dd/mm/aaaa HH:mm (en-US: aaaa-mm-dd HH:mm). */
132
+ export function formatWallClock(local, locale, showSeconds = false) {
133
+ const m = LOCAL_RE.exec(local.trim());
134
+ if (!m)
135
+ return local;
136
+ const time = `${m[4]}:${m[5]}${showSeconds ? `:${m[6] ?? "00"}` : ""}`;
137
+ if (locale === "en-US")
138
+ return `${m[1]}-${m[2]}-${m[3]} ${time}`;
139
+ return `${m[3]}/${m[2]}/${m[1]} ${time}`;
140
+ }
@@ -0,0 +1,62 @@
1
+ export type MediaType = "image" | "video" | "audio" | "pdf" | "doc" | "docx" | "xls" | "xlsx" | "ppt" | "pptx" | "csv" | "txt" | "zip" | "all";
2
+ export type FileErrorCode = "type" | "size" | "total" | "count" | "dimension" | "aspect";
3
+ /** Tamanho em bytes (numero) ou texto com unidade: `"1KB"`, `"1.5MB"`, `"2gb"`. */
4
+ export type SizeLimit = number | string;
5
+ type MediaInfo = {
6
+ group?: string;
7
+ mime?: string;
8
+ extensions: string[];
9
+ };
10
+ export declare const MEDIA_TYPES: Record<Exclude<MediaType, "all">, MediaInfo>;
11
+ /** Extensao em minusculas (sem ponto), ou "" quando nao ha. */
12
+ export declare function extensionOf(name: string): string;
13
+ /** Tipo MIME so pela extensao do nome; "" se desconhecida. */
14
+ export declare function mimeFromName(name: string): string;
15
+ /** Tipo MIME pela assinatura do arquivo (primeiros bytes; ate 256 bastam); "" se nao reconhecida. */
16
+ export declare function sniffMime(b: Uint8Array): string;
17
+ /**
18
+ * Tipo efetivo do arquivo: a assinatura (conteudo) manda; contentores (zip, OLE) e texto puro desempatam pela
19
+ * extensao; sem nenhuma das duas, vale o tipo que o sistema informou (`declared`).
20
+ */
21
+ export declare function resolveMime(name: string, declared: string, head: Uint8Array): string;
22
+ /** O tipo efetivo satisfaz `mediaType` (lista) ou `accept` (texto livre)? Sem nenhum dos dois, tudo passa. */
23
+ export declare function matchesMedia(name: string, mime: string, mediaType?: MediaType[], accept?: string): boolean;
24
+ /**
25
+ * Tamanho em bytes: numero puro = bytes; texto com unidade (B, KB, MB, GB, TB; base 1024; maiusculas ou minusculas;
26
+ * decimal com ponto; espaco opcional). Invalido, vazio ou negativo => `undefined` (sem limite).
27
+ */
28
+ export declare function parseSize(value: SizeLimit | undefined | null): number | undefined;
29
+ export type PickedFacts = {
30
+ name: string;
31
+ type: string;
32
+ size: number;
33
+ head: Uint8Array;
34
+ };
35
+ export type AddRules = {
36
+ mediaType?: MediaType[];
37
+ accept?: string;
38
+ /** Quantos arquivos o campo guarda (padrao 1). Com 1, escolher outro SUBSTITUI o atual. */
39
+ maxFiles?: number;
40
+ maxSizePerFile?: SizeLimit;
41
+ maxSizeTotal?: SizeLimit;
42
+ };
43
+ export type AddPlan = {
44
+ /** Indices (em `picked`) dos arquivos que entram, na ordem. */
45
+ accepted: number[];
46
+ rejected: Array<{
47
+ index: number;
48
+ code: FileErrorCode;
49
+ }>;
50
+ /** `true` quando `maxFiles` e 1: o aceito substitui o que ja havia. */
51
+ replace: boolean;
52
+ };
53
+ /**
54
+ * Decide quais dos arquivos escolhidos entram (`current` = tamanhos ja no campo). Cada recusa fica no seu arquivo
55
+ * (`type`, `size`, `count`, `total`); os demais seguem validos. Ordem das regras: tipo, tamanho por arquivo,
56
+ * quantidade, tamanho total.
57
+ */
58
+ export declare function planAdd(currentSizes: number[], picked: PickedFacts[], rules?: AddRules): AddPlan;
59
+ /** `1536` => "1,5 KB" (virgula fora do en-US). Base 1024; ate TB. */
60
+ export declare function formatFileSize(bytes: number, locale?: string): string;
61
+ export {};
62
+ //# sourceMappingURL=fileUploadLogic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fileUploadLogic.d.ts","sourceRoot":"","sources":["../../src/inputs/fileUploadLogic.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,SAAS,GACjB,OAAO,GACP,OAAO,GACP,OAAO,GACP,KAAK,GACL,KAAK,GACL,MAAM,GACN,KAAK,GACL,MAAM,GACN,KAAK,GACL,MAAM,GACN,KAAK,GACL,KAAK,GACL,KAAK,GACL,KAAK,CAAC;AAEV,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,WAAW,GAAG,QAAQ,CAAC;AAEzF,mFAAmF;AACnF,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAExC,KAAK,SAAS,GAAG;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAEzE,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,KAAK,CAAC,EAAE,SAAS,CAcpE,CAAC;AAkDF,+DAA+D;AAC/D,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGhD;AAED,8DAA8D;AAC9D,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD;AAcD,qGAAqG;AACrG,wBAAgB,SAAS,CAAC,CAAC,EAAE,UAAU,GAAG,MAAM,CA0B/C;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CAQpF;AAiBD,8GAA8G;AAC9G,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAc1G;AAID;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CASjF;AAED,MAAM,MAAM,WAAW,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAEzF,MAAM,MAAM,QAAQ,GAAG;IACrB,SAAS,CAAC,EAAE,SAAS,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,SAAS,CAAC;IAC3B,YAAY,CAAC,EAAE,SAAS,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,aAAa,CAAA;KAAE,CAAC,CAAC;IACxD,uEAAuE;IACvE,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,KAAK,GAAE,QAAa,GAAG,OAAO,CAyBpG;AAED,qEAAqE;AACrE,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,SAAU,GAAG,MAAM,CAUtE"}
@@ -0,0 +1,254 @@
1
+ // Logica pura do SgFileUpload (P141): tipos de midia (`mediaType`), tamanho com unidade ("1.5MB"), tipo do arquivo
2
+ // pelo CONTEUDO (assinatura) e a regra de quantos arquivos entram (maxFiles, por arquivo, total). Sem DOM: os vetores
3
+ // de paridade com o Flutter (`seedgrid_components/test/widgets/file_upload_vectors`) sao gerados DESTE modulo.
4
+ export const MEDIA_TYPES = {
5
+ image: { group: "image/", extensions: ["png", "jpg", "jpeg", "jfif", "jpe", "gif", "webp", "bmp", "svg", "heic", "heif", "avif", "tif", "tiff", "ico"] },
6
+ video: { group: "video/", extensions: ["mp4", "webm", "mov", "mkv", "avi", "ogv", "m4v"] },
7
+ audio: { group: "audio/", extensions: ["mp3", "wav", "ogg", "oga", "m4a", "aac", "flac", "weba"] },
8
+ pdf: { mime: "application/pdf", extensions: ["pdf"] },
9
+ doc: { mime: "application/msword", extensions: ["doc"] },
10
+ docx: { mime: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", extensions: ["docx"] },
11
+ xls: { mime: "application/vnd.ms-excel", extensions: ["xls"] },
12
+ xlsx: { mime: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", extensions: ["xlsx"] },
13
+ ppt: { mime: "application/vnd.ms-powerpoint", extensions: ["ppt"] },
14
+ pptx: { mime: "application/vnd.openxmlformats-officedocument.presentationml.presentation", extensions: ["pptx"] },
15
+ csv: { mime: "text/csv", extensions: ["csv"] },
16
+ txt: { mime: "text/plain", extensions: ["txt"] },
17
+ zip: { mime: "application/zip", extensions: ["zip"] }
18
+ };
19
+ const EXTENSION_MIME = {
20
+ png: "image/png",
21
+ jpg: "image/jpeg",
22
+ jpeg: "image/jpeg",
23
+ jfif: "image/jpeg",
24
+ jpe: "image/jpeg",
25
+ gif: "image/gif",
26
+ webp: "image/webp",
27
+ bmp: "image/bmp",
28
+ svg: "image/svg+xml",
29
+ heic: "image/heic",
30
+ heif: "image/heif",
31
+ avif: "image/avif",
32
+ tif: "image/tiff",
33
+ tiff: "image/tiff",
34
+ ico: "image/x-icon",
35
+ mp4: "video/mp4",
36
+ m4v: "video/mp4",
37
+ webm: "video/webm",
38
+ mov: "video/quicktime",
39
+ mkv: "video/x-matroska",
40
+ avi: "video/x-msvideo",
41
+ ogv: "video/ogg",
42
+ mp3: "audio/mpeg",
43
+ wav: "audio/wav",
44
+ ogg: "audio/ogg",
45
+ oga: "audio/ogg",
46
+ m4a: "audio/mp4",
47
+ aac: "audio/aac",
48
+ flac: "audio/flac",
49
+ weba: "audio/webm",
50
+ pdf: "application/pdf",
51
+ doc: "application/msword",
52
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
53
+ xls: "application/vnd.ms-excel",
54
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
55
+ ppt: "application/vnd.ms-powerpoint",
56
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
57
+ csv: "text/csv",
58
+ txt: "text/plain",
59
+ zip: "application/zip"
60
+ };
61
+ const ZIP = "application/zip";
62
+ const OLE = "application/x-ole-storage";
63
+ const ZIP_BASED = new Set(["docx", "xlsx", "pptx", "zip"]);
64
+ const OLE_BASED = new Set(["doc", "xls", "ppt"]);
65
+ /** Extensao em minusculas (sem ponto), ou "" quando nao ha. */
66
+ export function extensionOf(name) {
67
+ const i = name.lastIndexOf(".");
68
+ return i < 0 || i === name.length - 1 ? "" : name.slice(i + 1).toLowerCase();
69
+ }
70
+ /** Tipo MIME so pela extensao do nome; "" se desconhecida. */
71
+ export function mimeFromName(name) {
72
+ return EXTENSION_MIME[extensionOf(name)] ?? "";
73
+ }
74
+ function startsWith(b, sig, at = 0) {
75
+ if (b.length < at + sig.length)
76
+ return false;
77
+ for (let i = 0; i < sig.length; i += 1)
78
+ if (b[i + at] !== sig[i])
79
+ return false;
80
+ return true;
81
+ }
82
+ function ascii(b, from, to) {
83
+ let s = "";
84
+ for (let i = from; i < Math.min(to, b.length); i += 1)
85
+ s += String.fromCharCode(b[i] ?? 0);
86
+ return s;
87
+ }
88
+ /** Tipo MIME pela assinatura do arquivo (primeiros bytes; ate 256 bastam); "" se nao reconhecida. */
89
+ export function sniffMime(b) {
90
+ if (startsWith(b, [0x89, 0x50, 0x4e, 0x47]))
91
+ return "image/png";
92
+ if (startsWith(b, [0xff, 0xd8, 0xff]))
93
+ return "image/jpeg";
94
+ if (startsWith(b, [0x47, 0x49, 0x46, 0x38]))
95
+ return "image/gif";
96
+ if (startsWith(b, [0x52, 0x49, 0x46, 0x46]) && startsWith(b, [0x57, 0x45, 0x42, 0x50], 8))
97
+ return "image/webp";
98
+ if (startsWith(b, [0x52, 0x49, 0x46, 0x46]) && startsWith(b, [0x57, 0x41, 0x56, 0x45], 8))
99
+ return "audio/wav";
100
+ if (startsWith(b, [0x42, 0x4d]))
101
+ return "image/bmp";
102
+ if (startsWith(b, [0x49, 0x49, 0x2a, 0x00]) || startsWith(b, [0x4d, 0x4d, 0x00, 0x2a]))
103
+ return "image/tiff";
104
+ if (startsWith(b, [0x00, 0x00, 0x01, 0x00]))
105
+ return "image/x-icon";
106
+ if (startsWith(b, [0x25, 0x50, 0x44, 0x46]))
107
+ return "application/pdf";
108
+ if (startsWith(b, [0x50, 0x4b, 0x03, 0x04]))
109
+ return ZIP;
110
+ if (startsWith(b, [0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1]))
111
+ return OLE;
112
+ if (startsWith(b, [0x1a, 0x45, 0xdf, 0xa3]))
113
+ return "video/webm";
114
+ if (startsWith(b, [0x4f, 0x67, 0x67, 0x53]))
115
+ return "audio/ogg";
116
+ if (startsWith(b, [0x66, 0x4c, 0x61, 0x43]))
117
+ return "audio/flac";
118
+ if (startsWith(b, [0x49, 0x44, 0x33]) || startsWith(b, [0xff, 0xfb]) || startsWith(b, [0xff, 0xf3]) || startsWith(b, [0xff, 0xf2]))
119
+ return "audio/mpeg";
120
+ if (startsWith(b, [0x66, 0x74, 0x79, 0x70], 4)) {
121
+ const brand = ascii(b, 8, 12);
122
+ if (brand.startsWith("avif") || brand.startsWith("avis"))
123
+ return "image/avif";
124
+ if (brand.startsWith("hei") || brand.startsWith("mif"))
125
+ return "image/heic";
126
+ if (brand.startsWith("M4A"))
127
+ return "audio/mp4";
128
+ if (brand.startsWith("qt"))
129
+ return "video/quicktime";
130
+ return "video/mp4";
131
+ }
132
+ if (ascii(b, 0, 256).toLowerCase().includes("<svg"))
133
+ return "image/svg+xml";
134
+ return "";
135
+ }
136
+ /**
137
+ * Tipo efetivo do arquivo: a assinatura (conteudo) manda; contentores (zip, OLE) e texto puro desempatam pela
138
+ * extensao; sem nenhuma das duas, vale o tipo que o sistema informou (`declared`).
139
+ */
140
+ export function resolveMime(name, declared, head) {
141
+ const ext = extensionOf(name);
142
+ const byName = EXTENSION_MIME[ext] ?? "";
143
+ const sig = sniffMime(head);
144
+ if (sig === ZIP)
145
+ return ZIP_BASED.has(ext) ? byName : ZIP;
146
+ if (sig === OLE)
147
+ return OLE_BASED.has(ext) ? byName : OLE;
148
+ if (sig)
149
+ return sig;
150
+ return byName || declared.toLowerCase();
151
+ }
152
+ /** Texto `accept` livre: `.png`, `image/png`, `image/*`, separados por virgula. */
153
+ function matchesAcceptToken(name, mime, accept) {
154
+ const tokens = accept
155
+ .split(",")
156
+ .map((t) => t.trim().toLowerCase())
157
+ .filter(Boolean);
158
+ if (tokens.length === 0)
159
+ return false;
160
+ const lowerName = name.toLowerCase();
161
+ return tokens.some((token) => {
162
+ if (token.startsWith("."))
163
+ return lowerName.endsWith(token);
164
+ if (token.endsWith("/*"))
165
+ return mime.startsWith(token.slice(0, -1));
166
+ return mime === token;
167
+ });
168
+ }
169
+ /** O tipo efetivo satisfaz `mediaType` (lista) ou `accept` (texto livre)? Sem nenhum dos dois, tudo passa. */
170
+ export function matchesMedia(name, mime, mediaType, accept) {
171
+ const hasMedia = Boolean(mediaType && mediaType.length > 0);
172
+ const hasAccept = Boolean(accept && accept.trim());
173
+ if (!hasMedia && !hasAccept)
174
+ return true;
175
+ if (hasMedia) {
176
+ for (const m of mediaType) {
177
+ if (m === "all")
178
+ return true;
179
+ const info = MEDIA_TYPES[m];
180
+ if (!info)
181
+ continue;
182
+ if (info.group && mime.startsWith(info.group))
183
+ return true;
184
+ if (info.mime && mime === info.mime)
185
+ return true;
186
+ }
187
+ }
188
+ return hasAccept ? matchesAcceptToken(name, mime, accept) : false;
189
+ }
190
+ const UNITS = { b: 1, kb: 1024, mb: 1024 ** 2, gb: 1024 ** 3, tb: 1024 ** 4 };
191
+ /**
192
+ * Tamanho em bytes: numero puro = bytes; texto com unidade (B, KB, MB, GB, TB; base 1024; maiusculas ou minusculas;
193
+ * decimal com ponto; espaco opcional). Invalido, vazio ou negativo => `undefined` (sem limite).
194
+ */
195
+ export function parseSize(value) {
196
+ if (value === undefined || value === null)
197
+ return undefined;
198
+ if (typeof value === "number")
199
+ return Number.isFinite(value) && value >= 0 ? Math.floor(value) : undefined;
200
+ const m = /^\s*(\d+(?:\.\d+)?)\s*([a-zA-Z]*)\s*$/.exec(value);
201
+ if (!m)
202
+ return undefined;
203
+ const unitText = m[2] ?? "";
204
+ const unit = unitText === "" ? "b" : unitText.toLowerCase();
205
+ const factor = UNITS[unit];
206
+ return factor === undefined ? undefined : Math.floor(Number(m[1] ?? 0) * factor);
207
+ }
208
+ /**
209
+ * Decide quais dos arquivos escolhidos entram (`current` = tamanhos ja no campo). Cada recusa fica no seu arquivo
210
+ * (`type`, `size`, `count`, `total`); os demais seguem validos. Ordem das regras: tipo, tamanho por arquivo,
211
+ * quantidade, tamanho total.
212
+ */
213
+ export function planAdd(currentSizes, picked, rules = {}) {
214
+ const maxFiles = Math.max(1, Math.floor(rules.maxFiles ?? 1));
215
+ const perFile = parseSize(rules.maxSizePerFile);
216
+ const total = parseSize(rules.maxSizeTotal);
217
+ const replace = maxFiles === 1;
218
+ let count = replace ? 0 : currentSizes.length;
219
+ let sum = replace ? 0 : currentSizes.reduce((a, b) => a + b, 0);
220
+ const accepted = [];
221
+ const rejected = [];
222
+ picked.forEach((file, index) => {
223
+ const mime = resolveMime(file.name, file.type, file.head);
224
+ let code = null;
225
+ if (!matchesMedia(file.name, mime, rules.mediaType, rules.accept))
226
+ code = "type";
227
+ else if (perFile !== undefined && file.size > perFile)
228
+ code = "size";
229
+ else if (count >= maxFiles)
230
+ code = "count";
231
+ else if (total !== undefined && sum + file.size > total)
232
+ code = "total";
233
+ if (code) {
234
+ rejected.push({ index, code });
235
+ return;
236
+ }
237
+ accepted.push(index);
238
+ count += 1;
239
+ sum += file.size;
240
+ });
241
+ return { accepted, rejected, replace };
242
+ }
243
+ /** `1536` => "1,5 KB" (virgula fora do en-US). Base 1024; ate TB. */
244
+ export function formatFileSize(bytes, locale = "en-US") {
245
+ const units = ["B", "KB", "MB", "GB", "TB"];
246
+ let value = Math.max(0, bytes);
247
+ let unit = 0;
248
+ while (value >= 1024 && unit < units.length - 1) {
249
+ value /= 1024;
250
+ unit += 1;
251
+ }
252
+ const text = unit === 0 ? String(Math.round(value)) : value.toFixed(1);
253
+ return `${locale === "en-US" ? text : text.replace(".", ",")} ${units[unit]}`;
254
+ }
@@ -0,0 +1,31 @@
1
+ export type ImageErrorCode = "type" | "size" | "dimension" | "aspect";
2
+ export type ImageFacts = {
3
+ name: string;
4
+ /** Tipo MIME (`image/png`); pode vir vazio. */
5
+ type: string;
6
+ size: number;
7
+ /** Dimensoes em px; ausentes quando nao foi possivel ler (as regras de dimensao/proporcao so rodam se existirem). */
8
+ width?: number;
9
+ height?: number;
10
+ };
11
+ export type ImageRules = {
12
+ /** Lista separada por virgula: `image/*`, `image/png`, `.png`. Padrao: `image/*`. */
13
+ accept?: string;
14
+ maxSizeBytes?: number;
15
+ minWidth?: number;
16
+ minHeight?: number;
17
+ /** Proporcao largura/altura exigida (`1`, `16 / 9`). */
18
+ aspect?: number;
19
+ /** Tolerancia relativa da proporcao (padrao 0.02 = 2%). */
20
+ aspectTolerance?: number;
21
+ };
22
+ export declare const DEFAULT_ACCEPT = "image/*";
23
+ /** O arquivo satisfaz `accept`? Compara extensao (`.png`), tipo exato (`image/png`) e curinga (`image/*`). */
24
+ export declare function matchesAccept(name: string, type: string, accept?: string): boolean;
25
+ /** Primeiro erro encontrado (ordem: tipo, tamanho, dimensao, proporcao) ou `null`. */
26
+ export declare function validateImage(file: ImageFacts, rules?: ImageRules): ImageErrorCode | null;
27
+ /** `1536` => "1,5 KB" (virgula fora do en-US). Usa 1024 como base; ate GB. */
28
+ export declare function formatBytes(bytes: number, locale?: string): string;
29
+ /** Texto da proporcao para a mensagem: inteiros viram "16:9"; senao 2 casas ("1.78"). */
30
+ export declare function formatAspect(aspect: number): string;
31
+ //# sourceMappingURL=imageUploadLogic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imageUploadLogic.d.ts","sourceRoot":"","sources":["../../src/inputs/imageUploadLogic.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;AAEtE,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,qHAAqH;IACrH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,qFAAqF;IACrF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,eAAO,MAAM,cAAc,YAAY,CAAC;AAExC,8GAA8G;AAC9G,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAalF;AAED,sFAAsF;AACtF,wBAAgB,aAAa,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,GAAE,UAAe,GAAG,cAAc,GAAG,IAAI,CAiB7F;AAED,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,SAAU,GAAG,MAAM,CAUnE;AAED,yFAAyF;AACzF,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAYnD"}
@@ -0,0 +1,70 @@
1
+ // Logica pura do SgImageUpload (P133): tipos aceitos, validacao (tipo, tamanho, dimensao minima, proporcao) e
2
+ // texto de tamanho. Sem DOM: os vetores de paridade com o Flutter (`seedgrid_components/test/widgets/
3
+ // image_upload_vectors`) sao gerados DESTE modulo.
4
+ export const DEFAULT_ACCEPT = "image/*";
5
+ /** O arquivo satisfaz `accept`? Compara extensao (`.png`), tipo exato (`image/png`) e curinga (`image/*`). */
6
+ export function matchesAccept(name, type, accept) {
7
+ const tokens = (accept ?? DEFAULT_ACCEPT)
8
+ .split(",")
9
+ .map((t) => t.trim().toLowerCase())
10
+ .filter(Boolean);
11
+ if (tokens.length === 0)
12
+ return true;
13
+ const lowerName = name.toLowerCase();
14
+ const lowerType = type.toLowerCase();
15
+ return tokens.some((token) => {
16
+ if (token.startsWith("."))
17
+ return lowerName.endsWith(token);
18
+ if (token.endsWith("/*"))
19
+ return lowerType.startsWith(token.slice(0, -1));
20
+ return lowerType === token;
21
+ });
22
+ }
23
+ /** Primeiro erro encontrado (ordem: tipo, tamanho, dimensao, proporcao) ou `null`. */
24
+ export function validateImage(file, rules = {}) {
25
+ if (!matchesAccept(file.name, file.type, rules.accept))
26
+ return "type";
27
+ if (rules.maxSizeBytes !== undefined && file.size > rules.maxSizeBytes)
28
+ return "size";
29
+ const { width, height } = file;
30
+ if (width !== undefined && height !== undefined) {
31
+ if ((rules.minWidth !== undefined && width < rules.minWidth) ||
32
+ (rules.minHeight !== undefined && height < rules.minHeight)) {
33
+ return "dimension";
34
+ }
35
+ if (rules.aspect !== undefined && rules.aspect > 0 && height > 0) {
36
+ const tolerance = rules.aspectTolerance ?? 0.02;
37
+ if (Math.abs(width / height - rules.aspect) / rules.aspect > tolerance)
38
+ return "aspect";
39
+ }
40
+ }
41
+ return null;
42
+ }
43
+ /** `1536` => "1,5 KB" (virgula fora do en-US). Usa 1024 como base; ate GB. */
44
+ export function formatBytes(bytes, locale = "en-US") {
45
+ const units = ["B", "KB", "MB", "GB"];
46
+ let value = Math.max(0, bytes);
47
+ let unit = 0;
48
+ while (value >= 1024 && unit < units.length - 1) {
49
+ value /= 1024;
50
+ unit += 1;
51
+ }
52
+ const text = unit === 0 ? String(Math.round(value)) : value.toFixed(1);
53
+ return `${locale === "en-US" ? text : text.replace(".", ",")} ${units[unit]}`;
54
+ }
55
+ /** Texto da proporcao para a mensagem: inteiros viram "16:9"; senao 2 casas ("1.78"). */
56
+ export function formatAspect(aspect) {
57
+ const known = [
58
+ [1, "1:1"],
59
+ [4 / 3, "4:3"],
60
+ [3 / 2, "3:2"],
61
+ [16 / 9, "16:9"],
62
+ [3 / 4, "3:4"],
63
+ [2 / 3, "2:3"],
64
+ [9 / 16, "9:16"]
65
+ ];
66
+ for (const [value, text] of known)
67
+ if (Math.abs(value - aspect) < 1e-9)
68
+ return text;
69
+ return aspect.toFixed(2);
70
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"SgDockMenu.d.ts","sourceRoot":"","sources":["../../src/menus/SgDockMenu.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAQ/B,MAAM,MAAM,kBAAkB,GAC1B,UAAU,GAAG,aAAa,GAAG,aAAa,GAC1C,YAAY,GAAG,eAAe,GAC9B,WAAW,GAAG,cAAc,GAAG,cAAc,CAAC;AAElD,MAAM,MAAM,qBAAqB,GAAG,YAAY,GAAG,UAAU,CAAC;AAE9D,MAAM,MAAM,cAAc,GAAG;IAC3B,wBAAwB;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,sBAAsB;IACtB,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC;IACtB,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,oBAAoB;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oBAAoB;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,wBAAwB;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,WAAW,eAAe;IAC9B,qCAAqC;IACrC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,mCAAmC;IACnC,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,yCAAyC;IACzC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,sCAAsC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2BAA2B;IAC3B,MAAM,CAAC,EAAE;QAAE,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACpC,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0BAA0B;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kCAAkC;IAClC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uBAAuB;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2BAA2B;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,oCAAoC;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0BAA0B;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oBAAoB;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IACxC,mBAAmB;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B;AA0BD,wBAAgB,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,eAAe,CAAC,2CAyf1D"}
1
+ {"version":3,"file":"SgDockMenu.d.ts","sourceRoot":"","sources":["../../src/menus/SgDockMenu.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAQ/B,MAAM,MAAM,kBAAkB,GAC1B,UAAU,GAAG,aAAa,GAAG,aAAa,GAC1C,YAAY,GAAG,eAAe,GAC9B,WAAW,GAAG,cAAc,GAAG,cAAc,CAAC;AAElD,MAAM,MAAM,qBAAqB,GAAG,YAAY,GAAG,UAAU,CAAC;AAE9D,MAAM,MAAM,cAAc,GAAG;IAC3B,wBAAwB;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,sBAAsB;IACtB,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC;IACtB,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,oBAAoB;IACpB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,oBAAoB;IACpB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,wBAAwB;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,WAAW,eAAe;IAC9B,qCAAqC;IACrC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,mCAAmC;IACnC,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,2BAA2B;IAC3B,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,yCAAyC;IACzC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,sCAAsC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2BAA2B;IAC3B,MAAM,CAAC,EAAE;QAAE,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACpC,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kCAAkC;IAClC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0BAA0B;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kCAAkC;IAClC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uBAAuB;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2BAA2B;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,oCAAoC;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0BAA0B;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oBAAoB;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uBAAuB;IACvB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IACxC,mBAAmB;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;CAC7B;AA0BD,wBAAgB,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,eAAe,CAAC,2CA2f1D"}
@@ -282,6 +282,8 @@ export function SgDockMenu(props) {
282
282
  });
283
283
  return (_jsxs(_Fragment, { children: [_jsx("div", { ref: containerRef, id: id, className: `sg-dock ${className} ${ELEV_CLS[elevation]}`, style: {
284
284
  ...containerStyleState.style,
285
+ // A sombra (ELEV_CLS) fica neste wrapper: sem o raio ela desenhava um retangulo quadrado atras do dock (P140).
286
+ borderRadius: `${borderRadius}px`,
285
287
  ...style,
286
288
  transform: containerStyleState.transform,
287
289
  cursor: enableDragDrop ? (isDragging ? "grabbing" : "grab") : "default",