@seedgrid/fe-components 2026.11.0 → 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 (63) hide show
  1. package/dist/ai/seedgrid-components.manifest.json +1201 -4
  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 +31 -0
  7. package/dist/i18n/es.d.ts.map +1 -1
  8. package/dist/i18n/es.js +31 -0
  9. package/dist/i18n/fr.d.ts.map +1 -1
  10. package/dist/i18n/fr.js +31 -0
  11. package/dist/i18n/pt-BR.d.ts.map +1 -1
  12. package/dist/i18n/pt-BR.js +31 -0
  13. package/dist/i18n/pt-PT.d.ts.map +1 -1
  14. package/dist/i18n/pt-PT.js +31 -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/SgDatatable.d.ts +9 -1
  25. package/dist/inputs/SgDatatable.d.ts.map +1 -1
  26. package/dist/inputs/SgDatatable.js +62 -6
  27. package/dist/inputs/SgDatatable.meta.d.ts.map +1 -1
  28. package/dist/inputs/SgDatatable.meta.js +5 -3
  29. package/dist/inputs/SgFileUpload.d.ts +69 -0
  30. package/dist/inputs/SgFileUpload.d.ts.map +1 -0
  31. package/dist/inputs/SgFileUpload.js +234 -0
  32. package/dist/inputs/SgFileUpload.meta.d.ts +4 -0
  33. package/dist/inputs/SgFileUpload.meta.d.ts.map +1 -0
  34. package/dist/inputs/SgFileUpload.meta.js +71 -0
  35. package/dist/inputs/SgImageUpload.d.ts +20 -0
  36. package/dist/inputs/SgImageUpload.d.ts.map +1 -0
  37. package/dist/inputs/SgImageUpload.js +74 -0
  38. package/dist/inputs/SgImageUpload.meta.d.ts +4 -0
  39. package/dist/inputs/SgImageUpload.meta.d.ts.map +1 -0
  40. package/dist/inputs/SgImageUpload.meta.js +73 -0
  41. package/dist/inputs/SgInputDateTime.d.ts +29 -0
  42. package/dist/inputs/SgInputDateTime.d.ts.map +1 -0
  43. package/dist/inputs/SgInputDateTime.js +130 -0
  44. package/dist/inputs/SgInputDateTime.meta.d.ts +4 -0
  45. package/dist/inputs/SgInputDateTime.meta.d.ts.map +1 -0
  46. package/dist/inputs/SgInputDateTime.meta.js +73 -0
  47. package/dist/inputs/cameraLogic.d.ts +27 -0
  48. package/dist/inputs/cameraLogic.d.ts.map +1 -0
  49. package/dist/inputs/cameraLogic.js +57 -0
  50. package/dist/inputs/dateTimeLogic.d.ts +22 -0
  51. package/dist/inputs/dateTimeLogic.d.ts.map +1 -0
  52. package/dist/inputs/dateTimeLogic.js +140 -0
  53. package/dist/inputs/fileUploadLogic.d.ts +62 -0
  54. package/dist/inputs/fileUploadLogic.d.ts.map +1 -0
  55. package/dist/inputs/fileUploadLogic.js +254 -0
  56. package/dist/inputs/imageUploadLogic.d.ts +31 -0
  57. package/dist/inputs/imageUploadLogic.d.ts.map +1 -0
  58. package/dist/inputs/imageUploadLogic.js +70 -0
  59. package/dist/menus/SgDockMenu.d.ts.map +1 -1
  60. package/dist/menus/SgDockMenu.js +2 -0
  61. package/dist/sandbox.cjs +64 -53
  62. package/dist/style.css +1 -1
  63. package/package.json +3 -3
@@ -0,0 +1,130 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import React from "react";
4
+ import { SgInputText } from "./SgInputText";
5
+ import { resolveFieldError } from "../rhf";
6
+ import { t, useComponentsI18n } from "../i18n";
7
+ import { useDarkFlag } from "../gadgets/clock/themes/useDarkFlag";
8
+ import { formatWallClock, instantToWallClock, snapMinutes, validateDateTime, wallClockToInstant } from "./dateTimeLogic";
9
+ /** Teto do atributo HTML quando nao ha `maxDate` (o <input> aceitaria ano de 6 digitos). */
10
+ const WIDGET_MAX = "9999-12-31T23:59";
11
+ function localOf(value, timeZone, showSeconds) {
12
+ return instantToWallClock(value ?? undefined, timeZone, showSeconds);
13
+ }
14
+ export function SgInputDateTime(props) {
15
+ const i18n = useComponentsI18n();
16
+ const isDark = useDarkFlag();
17
+ const { value, defaultValue, onChange, minDate, maxDate, timeZone, showSeconds = false, minuteStep, inputProps, alwaysFloat, required, requiredMessage, validateOnBlur, validation, onValidation, error, ...rest } = props;
18
+ const rhfManaged = Boolean(rest.control && rest.name);
19
+ const controlled = value !== undefined;
20
+ const [innerLocal, setInnerLocal] = React.useState(() => localOf(defaultValue, timeZone, showSeconds));
21
+ const local = controlled ? localOf(value, timeZone, showSeconds) : innerLocal;
22
+ const labelText = rest.labelText ?? rest.label ?? "";
23
+ const [internalError, setInternalError] = React.useState(null);
24
+ const hasError = Boolean(error ?? internalError);
25
+ const [hasInteracted, setHasInteracted] = React.useState(false);
26
+ // Segmentos digitados pela metade: o <input type="datetime-local"> entrega "" (validity.badInput). Em modo controlado o
27
+ // React devolveria o `value` antigo e o campo voltaria ao valor anterior; enquanto parcial, o DOM segue sem ser reescrito.
28
+ const [partial, setPartial] = React.useState(false);
29
+ React.useEffect(() => {
30
+ setPartial(false);
31
+ }, [local]);
32
+ const minLocal = minDate ? instantToWallClock(minDate, timeZone, showSeconds) : undefined;
33
+ const maxLocal = maxDate ? instantToWallClock(maxDate, timeZone, showSeconds) : undefined;
34
+ const effectiveStep = showSeconds ? 1 : minuteStep && minuteStep > 1 ? minuteStep * 60 : undefined;
35
+ const runValidation = React.useCallback((text) => {
36
+ const report = (message) => {
37
+ setInternalError(message);
38
+ onValidation?.(message);
39
+ };
40
+ if (!text && required)
41
+ return report(requiredMessage ?? t(i18n, "components.inputs.required"));
42
+ if (!text)
43
+ return report(null);
44
+ const custom = validation?.(text) ?? null;
45
+ if (custom)
46
+ return report(custom);
47
+ const code = validateDateTime(text, { required, min: minDate, max: maxDate, timeZone });
48
+ if (code === "invalid")
49
+ return report(t(i18n, "components.inputs.datetime.invalid"));
50
+ if (code === "min" || code === "max" || code === "range") {
51
+ const min = minLocal ? formatWallClock(minLocal, i18n.locale, showSeconds) : "";
52
+ const max = maxLocal ? formatWallClock(maxLocal, i18n.locale, showSeconds) : "";
53
+ return report(code === "range"
54
+ ? t(i18n, "components.inputs.datetime.range", { min, max })
55
+ : code === "min"
56
+ ? t(i18n, "components.inputs.datetime.min", { min })
57
+ : t(i18n, "components.inputs.datetime.max", { max }));
58
+ }
59
+ return report(null);
60
+ }, [i18n, maxDate, maxLocal, minDate, minLocal, onValidation, required, requiredMessage, showSeconds, timeZone, validation]);
61
+ const emit = (text) => {
62
+ onChange?.(text ? wallClockToInstant(text, timeZone) : null, text);
63
+ };
64
+ const mergedInputProps = {
65
+ ...inputProps,
66
+ placeholder: " ",
67
+ min: minLocal,
68
+ max: maxLocal ?? WIDGET_MAX,
69
+ step: effectiveStep,
70
+ ...(rhfManaged ? {} : { value: partial ? "" : local }),
71
+ readOnly: inputProps?.readOnly,
72
+ onChange: (event) => {
73
+ const text = event.currentTarget.value;
74
+ setPartial(text === "" && Boolean(event.currentTarget.validity?.badInput));
75
+ setHasInteracted(true);
76
+ if (!controlled)
77
+ setInnerLocal(text);
78
+ if (validateOnBlur === false || internalError)
79
+ runValidation(text);
80
+ emit(text);
81
+ inputProps?.onChange?.(event);
82
+ },
83
+ onBlur: (event) => {
84
+ let text = event.currentTarget.value;
85
+ const snapped = snapMinutes(text, showSeconds ? undefined : minuteStep);
86
+ if (snapped !== text) {
87
+ text = snapped;
88
+ if (!controlled)
89
+ setInnerLocal(text);
90
+ emit(text);
91
+ }
92
+ if ((validateOnBlur ?? true) || hasInteracted)
93
+ runValidation(text);
94
+ inputProps?.onBlur?.(event);
95
+ }
96
+ };
97
+ const inputClassName = (() => {
98
+ if (mergedInputProps.className)
99
+ return mergedInputProps.className;
100
+ const elevationClass = props.elevation === "none" ? "" : props.elevation === "md" ? "shadow-md" : props.elevation === "lg" ? "shadow-lg" : "shadow-sm";
101
+ const baseClass = "peer h-11 w-full rounded-md bg-[var(--sg-input-bg,hsl(var(--background)))] text-[var(--sg-input-fg,hsl(var(--foreground)))] caret-[var(--sg-input-fg,hsl(var(--foreground)))] pl-3 pr-7 py-2.5 text-sm placeholder-transparent focus:outline-none";
102
+ const borderClass = hasError
103
+ ? "border border-[hsl(var(--destructive))] focus:border-[hsl(var(--destructive))] focus:ring-2 focus:ring-[hsl(var(--destructive)/0.25)]"
104
+ : "border border-border focus:border-[hsl(var(--primary))] focus:ring-2 focus:ring-[hsl(var(--primary)/0.25)]";
105
+ return [baseClass, borderClass, elevationClass].filter(Boolean).join(" ");
106
+ })();
107
+ const wrapperClassName = ["relative", isDark ? "sg-datetime-dark" : "sg-datetime-light"].join(" ");
108
+ return (_jsxs("div", { className: wrapperClassName, children: [_jsx("style", { children: `
109
+ input[type="datetime-local"]::-webkit-calendar-picker-indicator {
110
+ cursor: pointer;
111
+ }
112
+ .sg-datetime-dark input[type="datetime-local"] {
113
+ color-scheme: dark;
114
+ }
115
+ .sg-datetime-light input[type="datetime-local"] {
116
+ color-scheme: light;
117
+ }
118
+ ` }), labelText ? (_jsx("label", { htmlFor: rest.id, className: [
119
+ "pointer-events-none absolute left-3 top-0 z-10 -translate-y-1/2 bg-[var(--sg-input-bg,hsl(var(--background)))] px-1 text-[11px] font-medium leading-none",
120
+ hasError ? "text-[hsl(var(--destructive))]" : "text-foreground/70"
121
+ ].join(" "), children: labelText })) : null, _jsx(SgInputText, { ...rest, type: "datetime-local", error: resolveFieldError(error, internalError ?? undefined), inputProps: mergedInputProps, className: inputClassName, labelClassName: "sr-only", onClear: () => {
122
+ setInternalError(null);
123
+ onValidation?.(null);
124
+ setPartial(false);
125
+ if (!controlled)
126
+ setInnerLocal("");
127
+ emit("");
128
+ rest.onClear?.();
129
+ } })] }));
130
+ }
@@ -0,0 +1,4 @@
1
+ import type { SgAiHintsV0, SgMetaV0 } from "../ai-meta/types";
2
+ export declare const sgMeta: SgMetaV0;
3
+ export declare const aiHints: SgAiHintsV0;
4
+ //# sourceMappingURL=SgInputDateTime.meta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SgInputDateTime.meta.d.ts","sourceRoot":"","sources":["../../src/inputs/SgInputDateTime.meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE9D,eAAO,MAAM,MAAM,EAAE,QAqDpB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,WAmBrB,CAAC"}
@@ -0,0 +1,73 @@
1
+ export const sgMeta = {
2
+ version: "0.1",
3
+ componentId: "form.input.datetime",
4
+ package: "@seedgrid/fe-components",
5
+ exportName: "SgInputDateTime",
6
+ slug: "sg-input-date-time",
7
+ displayName: "SgInputDateTime",
8
+ category: "input",
9
+ subcategory: "datetime",
10
+ description: "Campo de data e hora que entrega e recebe instantes ISO 8601 em UTC, exibindo a hora no fuso escolhido, com limites, passo de minutos e validacao.",
11
+ tags: ["form", "date", "time", "datetime", "rhf"],
12
+ capabilities: ["rhf", "controlled", "min-max", "time-zone", "minute-step", "iso-utc"],
13
+ fieldSemantics: ["dateTime", "timestamp", "schedule", "appointment", "deadline"],
14
+ props: [
15
+ { name: "id", type: "string", required: true, description: "Identificador unico do campo.", semanticRole: "data", bindable: false },
16
+ { name: "name", type: "string", description: "Nome do campo para formularios.", semanticRole: "data", bindable: true },
17
+ { name: "label", type: "string", description: "Rotulo principal exibido ao usuario.", semanticRole: "label", bindable: true },
18
+ { name: "value", type: "string | Date | null", description: "Instante controlado (ISO 8601 ou Date).", semanticRole: "data", bindable: true },
19
+ { name: "defaultValue", type: "string | Date | null", description: "Instante inicial (nao controlado).", semanticRole: "data", bindable: true },
20
+ { name: "required", type: "boolean", default: false, description: "Marca o campo como obrigatorio.", semanticRole: "validation", bindable: true },
21
+ { name: "minDate", type: "string | Date", description: "Menor instante permitido.", semanticRole: "validation", bindable: true },
22
+ { name: "maxDate", type: "string | Date", description: "Maior instante permitido.", semanticRole: "validation", bindable: true },
23
+ { name: "timeZone", type: "string", default: "local", description: "Fuso do campo: local, UTC, deslocamento (+03:00) ou nome IANA.", semanticRole: "data", bindable: true },
24
+ { name: "showSeconds", type: "boolean", default: false, description: "Mostra e aceita segundos.", semanticRole: "appearance", bindable: true },
25
+ { name: "minuteStep", type: "number", description: "Passo dos minutos (5, 10, 15, 30).", semanticRole: "validation", bindable: true },
26
+ { name: "onChange", type: "(value: string | null, local: string) => void", description: "Instante ISO UTC (ou null) e texto local do campo.", semanticRole: "event", bindable: false }
27
+ ],
28
+ states: ["default", "focused", "disabled", "readOnly", "error"],
29
+ examples: [
30
+ {
31
+ id: "basic",
32
+ title: "Basico",
33
+ file: "apps/showcase/src/app/components/sg-input-date-time/samples/basico.tsx.sample",
34
+ kind: "sample"
35
+ },
36
+ {
37
+ id: "playground",
38
+ title: "Playground",
39
+ file: "apps/showcase/src/app/components/sg-input-date-time/sg-input-date-time.tsx.playground",
40
+ kind: "playground"
41
+ }
42
+ ],
43
+ showcase: {
44
+ route: "/components/sg-input-date-time",
45
+ hasPlayground: true,
46
+ hasPropsTable: true
47
+ },
48
+ sdui: {
49
+ rendererType: "field.datetime",
50
+ acceptsDataBinding: true,
51
+ defaultProps: { labelPosition: "float", clearButton: true }
52
+ }
53
+ };
54
+ export const aiHints = {
55
+ version: "0.1",
56
+ preferredUseCases: [
57
+ "Agendamentos, compromissos e prazos que exigem data e hora.",
58
+ "Campos de timestamp que precisam trafegar como instante UTC.",
59
+ "Formularios com limites de data e hora e fuso explicito."
60
+ ],
61
+ avoidUseCases: [
62
+ "Somente data; prefira SgInputDate.",
63
+ "Somente hora do dia sem data; prefira um campo de hora.",
64
+ "Texto livre; prefira SgInputText."
65
+ ],
66
+ synonyms: ["datetime input", "date time picker", "timestamp field", "appointment field"],
67
+ relatedEntityFields: ["createdAt", "scheduledAt", "startsAt", "endsAt", "dueAt", "timestamp"],
68
+ compositionHints: [
69
+ "Combinar dois campos (inicio/fim) com minDate/maxDate para intervalos.",
70
+ "Usar dentro de SgPanel em formularios de agenda."
71
+ ],
72
+ rankingSignals: { freeText: 0, structuredChoice: 0.1, date: 0.95, number: 0, denseLayout: 0.7 }
73
+ };
@@ -0,0 +1,27 @@
1
+ export type CameraFacing = "user" | "environment";
2
+ export type CameraGuide = "none" | "oval" | "rect";
3
+ export type CameraResolution = "low" | "medium" | "high";
4
+ export type CameraErrorCode = "permission" | "noCamera" | "failed";
5
+ export type GuideBox = {
6
+ x: number;
7
+ y: number;
8
+ width: number;
9
+ height: number;
10
+ };
11
+ /** Resolucao pedida ao dispositivo (largura x altura, paisagem). */
12
+ export declare function resolutionSize(resolution: CameraResolution): {
13
+ width: number;
14
+ height: number;
15
+ };
16
+ /**
17
+ * Caixa do guia de enquadramento, centralizada na area de pre-visualizacao `viewW` x `viewH` (px).
18
+ * `oval`: elipse em pe (largura/altura = 3/4), ate 70% da largura e 70% da altura.
19
+ * `rect`: retangulo deitado (largura/altura = 4/3), ate 90% da largura e 70% da altura.
20
+ * `none` => null. Valores arredondados ao pixel.
21
+ */
22
+ export declare function guideBox(guide: CameraGuide, viewW: number, viewH: number): GuideBox | null;
23
+ /** Nome do erro da plataforma (`NotAllowedError`, `CameraAccessDenied`...) => codigo do componente. */
24
+ export declare function classifyCameraError(name: string): CameraErrorCode;
25
+ /** Alterna entre frontal (`user`) e traseira (`environment`). */
26
+ export declare function nextFacing(facing: CameraFacing): CameraFacing;
27
+ //# sourceMappingURL=cameraLogic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cameraLogic.d.ts","sourceRoot":"","sources":["../../src/inputs/cameraLogic.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,aAAa,CAAC;AAClD,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AACnD,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;AACzD,MAAM,MAAM,eAAe,GAAG,YAAY,GAAG,UAAU,GAAG,QAAQ,CAAC;AAEnE,MAAM,MAAM,QAAQ,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/E,oEAAoE;AACpE,wBAAgB,cAAc,CAAC,UAAU,EAAE,gBAAgB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAS9F;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAa1F;AAYD,uGAAuG;AACvG,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,CAKjE;AAED,iEAAiE;AACjE,wBAAgB,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CAE7D"}
@@ -0,0 +1,57 @@
1
+ // Logica pura do SgCameraCapture (P134): resolucao, guia de enquadramento, classificacao de erro e troca de camera.
2
+ // Sem DOM: os vetores de paridade com o Flutter (`seedgrid_components/test/widgets/camera_vectors`) sao gerados
3
+ // DESTE modulo. O contrato das props e o mesmo nas duas plataformas.
4
+ /** Resolucao pedida ao dispositivo (largura x altura, paisagem). */
5
+ export function resolutionSize(resolution) {
6
+ switch (resolution) {
7
+ case "low":
8
+ return { width: 640, height: 480 };
9
+ case "high":
10
+ return { width: 1920, height: 1080 };
11
+ default:
12
+ return { width: 1280, height: 720 };
13
+ }
14
+ }
15
+ /**
16
+ * Caixa do guia de enquadramento, centralizada na area de pre-visualizacao `viewW` x `viewH` (px).
17
+ * `oval`: elipse em pe (largura/altura = 3/4), ate 70% da largura e 70% da altura.
18
+ * `rect`: retangulo deitado (largura/altura = 4/3), ate 90% da largura e 70% da altura.
19
+ * `none` => null. Valores arredondados ao pixel.
20
+ */
21
+ export function guideBox(guide, viewW, viewH) {
22
+ if (guide === "none" || viewW <= 0 || viewH <= 0)
23
+ return null;
24
+ const ratio = guide === "oval" ? 3 / 4 : 4 / 3;
25
+ const maxW = viewW * (guide === "oval" ? 0.7 : 0.9);
26
+ const maxH = viewH * 0.7;
27
+ const width = Math.min(maxW, maxH * ratio);
28
+ const height = width / ratio;
29
+ return {
30
+ x: Math.round((viewW - width) / 2),
31
+ y: Math.round((viewH - height) / 2),
32
+ width: Math.round(width),
33
+ height: Math.round(height)
34
+ };
35
+ }
36
+ const PERMISSION_NAMES = new Set([
37
+ "notallowederror",
38
+ "permissiondeniederror",
39
+ "securityerror",
40
+ "cameraaccessdenied",
41
+ "cameraaccessdeniedwithoutprompt",
42
+ "cameraaccessrestricted"
43
+ ]);
44
+ const NO_CAMERA_NAMES = new Set(["notfounderror", "devicesnotfounderror", "nocamera", "notreadableerror_nocamera"]);
45
+ /** Nome do erro da plataforma (`NotAllowedError`, `CameraAccessDenied`...) => codigo do componente. */
46
+ export function classifyCameraError(name) {
47
+ const key = name.trim().toLowerCase();
48
+ if (PERMISSION_NAMES.has(key))
49
+ return "permission";
50
+ if (NO_CAMERA_NAMES.has(key))
51
+ return "noCamera";
52
+ return "failed";
53
+ }
54
+ /** Alterna entre frontal (`user`) e traseira (`environment`). */
55
+ export function nextFacing(facing) {
56
+ return facing === "user" ? "environment" : "user";
57
+ }
@@ -0,0 +1,22 @@
1
+ export type DateTimeErrorCode = "required" | "invalid" | "min" | "max" | "range";
2
+ /** Deslocamento fixo em minutos ("+03:00" => 180) ou null se nao for um deslocamento. */
3
+ export declare function parseFixedOffset(timeZone: string): number | null;
4
+ /** Minutos a somar ao UTC para obter o relogio de parede de `instantMs` no fuso. */
5
+ export declare function zoneOffsetMinutes(instantMs: number, timeZone?: string): number;
6
+ /** Instante (ISO UTC ou Date) => "YYYY-MM-DDTHH:mm[:ss]" no fuso; "" se invalido/vazio. */
7
+ export declare function instantToWallClock(value: string | Date | undefined | null, timeZone?: string, showSeconds?: boolean): string;
8
+ /** "YYYY-MM-DDTHH:mm[:ss]" no fuso => ISO 8601 UTC ("...Z", ms = 000); null se invalido. */
9
+ export declare function wallClockToInstant(local: string, timeZone?: string): string | null;
10
+ /** Arredonda os minutos ao multiplo de `step` mais proximo (passo invalido ou <= 1 mantem o valor). */
11
+ export declare function snapMinutes(local: string, step?: number): string;
12
+ export type DateTimeValidationInput = {
13
+ required?: boolean;
14
+ min?: string | Date;
15
+ max?: string | Date;
16
+ timeZone?: string;
17
+ };
18
+ /** Codigo do erro (null = valido). `local` e o texto do campo ("" = vazio). min/max sao instantes. */
19
+ export declare function validateDateTime(local: string, input?: DateTimeValidationInput): DateTimeErrorCode | null;
20
+ /** Texto de exibicao de um relogio de parede: dd/mm/aaaa HH:mm (en-US: aaaa-mm-dd HH:mm). */
21
+ export declare function formatWallClock(local: string, locale: string, showSeconds?: boolean): string;
22
+ //# sourceMappingURL=dateTimeLogic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dateTimeLogic.d.ts","sourceRoot":"","sources":["../../src/inputs/dateTimeLogic.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG,OAAO,CAAC;AASjF,yFAAyF;AACzF,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAMhE;AAMD,oFAAoF;AACpF,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAuB9E;AAQD,2FAA2F;AAC3F,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI,EACvC,QAAQ,CAAC,EAAE,MAAM,EACjB,WAAW,UAAQ,GAClB,MAAM,CAMR;AAED,4FAA4F;AAC5F,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiBlF;AAED,uGAAuG;AACvG,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAkBhE;AAED,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,sGAAsG;AACtG,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,uBAA4B,GAAG,iBAAiB,GAAG,IAAI,CAY7G;AAED,6FAA6F;AAC7F,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,UAAQ,GAAG,MAAM,CAM1F"}
@@ -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"}