@seedgrid/fe-components 2026.11.4 → 2026.11.6

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 (42) hide show
  1. package/dist/ai/seedgrid-components.manifest.json +1475 -11
  2. package/dist/buttons/SgButton.d.ts +41 -3
  3. package/dist/buttons/SgButton.d.ts.map +1 -1
  4. package/dist/buttons/SgButton.js +51 -5
  5. package/dist/buttons/SgButton.meta.d.ts.map +1 -1
  6. package/dist/buttons/SgButton.meta.js +27 -5
  7. package/dist/digits/fade-digit/SgFadeDigit.d.ts.map +1 -1
  8. package/dist/digits/fade-digit/SgFadeDigit.js +4 -1
  9. package/dist/gadgets/clock/SgClock.d.ts +25 -1
  10. package/dist/gadgets/clock/SgClock.d.ts.map +1 -1
  11. package/dist/gadgets/clock/SgClock.js +18 -3
  12. package/dist/gadgets/clock/SgClock.meta.d.ts.map +1 -1
  13. package/dist/gadgets/clock/SgClock.meta.js +2 -1
  14. package/dist/generated/compiledStyles.d.ts +1 -1
  15. package/dist/generated/compiledStyles.d.ts.map +1 -1
  16. package/dist/generated/compiledStyles.js +1 -1
  17. package/dist/i18n/SgComponentsI18nProvider.meta.d.ts.map +1 -1
  18. package/dist/i18n/SgComponentsI18nProvider.meta.js +6 -3
  19. package/dist/i18n/useComponentsI18n.meta.d.ts.map +1 -1
  20. package/dist/i18n/useComponentsI18n.meta.js +8 -2
  21. package/dist/index.d.ts +2 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +1 -1
  24. package/dist/inputs/SgMultiSelect.d.ts.map +1 -1
  25. package/dist/inputs/SgMultiSelect.js +4 -2
  26. package/dist/inputs/SgMultiSelectChips.d.ts.map +1 -1
  27. package/dist/inputs/SgMultiSelectChips.js +4 -2
  28. package/dist/layout/SgPageControl.d.ts +1 -0
  29. package/dist/layout/SgPageControl.d.ts.map +1 -1
  30. package/dist/layout/SgPageControl.js +3 -1
  31. package/dist/layout/SgToolBar.d.ts +6 -0
  32. package/dist/layout/SgToolBar.d.ts.map +1 -1
  33. package/dist/layout/SgToolBar.js +107 -5
  34. package/dist/layout/SgToolBar.meta.d.ts.map +1 -1
  35. package/dist/layout/SgToolBar.meta.js +8 -0
  36. package/dist/layout/toolbar-logic.d.ts +11 -0
  37. package/dist/layout/toolbar-logic.d.ts.map +1 -1
  38. package/dist/layout/toolbar-logic.js +25 -0
  39. package/dist/sandbox.cjs +58 -58
  40. package/dist/style.css +2 -2
  41. package/dist/wizard/SgWizard.js +1 -1
  42. package/package.json +3 -3
@@ -14,7 +14,24 @@ export type SgButtonCustomColors = {
14
14
  activeBg?: string;
15
15
  ring?: string;
16
16
  };
17
- export type SgButtonProps = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "color" | "disabled"> & {
17
+ export type SgButtonProps = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "color" | "disabled" | "onClick"> & {
18
+ /**
19
+ * Clique do botão. Pode ser síncrono ou assíncrono (`(event) => unknown`).
20
+ *
21
+ * - Síncrono (abrir modal, alternar painel): funciona como sempre, nada muda.
22
+ * - Se DEVOLVER uma Promise (`async () => { await salvar(); }` ou `() => salvar()`), o botão entra em loading
23
+ * sozinho (spinner, desabilitado, aria-busy), ignora cliques até terminar (inclusive cliques seguidos antes do
24
+ * re-render) e volta ao normal ao terminar ou falhar. Protege contra duplo clique e reenvio e ajuda na
25
+ * idempotência da ação na tela (não substitui a chave de idempotência no servidor).
26
+ * - Cuidado: precisa DEVOLVER a Promise. `onClick={() => { salvar(); }}` (chaves sem return) NÃO protege.
27
+ * - Erro: o botão não engole o erro (sai do loading e relança como rejeição não tratada). Trate com try/catch
28
+ * dentro do onClick e avise o usuário (ex.: `sgWhistle.error`).
29
+ * - Use async só quando a ação puder demorar ou falhar (salvar, enviar, pagar); ações instantâneas ficam síncronas.
30
+ * - A prop `loading` manual continua valendo e vence (loading efetivo = `loading` || Promise pendente).
31
+ *
32
+ * O retorno é `unknown` só para não quebrar handlers existentes de corpo-expressão (`() => lista.push(x)`).
33
+ */
34
+ onClick?: (event: React.MouseEvent<HTMLButtonElement>) => unknown;
18
35
  /** Quando false, desabilita o botão. Padrão: true. */
19
36
  enabled?: boolean;
20
37
  severity?: Severity;
@@ -26,11 +43,31 @@ export type SgButtonProps = Omit<React.ButtonHTMLAttributes<HTMLButtonElement>,
26
43
  iconOnly?: boolean;
27
44
  leftIcon?: React.ReactNode;
28
45
  rightIcon?: React.ReactNode;
46
+ /** Exibe spinner e bloqueia o clique. Vence o loading automático do onClick: loading efetivo = `loading` || Promise pendente. */
29
47
  loading?: boolean;
30
48
  customColors?: SgButtonCustomColors;
31
49
  };
32
- export declare function resolveButtonColors(severity: Severity, custom?: SgButtonCustomColors): Required<SgButtonCustomColors>;
33
- export declare const SgButton: React.ForwardRefExoticComponent<Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "disabled" | "color"> & {
50
+ export declare function resolveButtonColors(severity: Severity, custom?: SgButtonCustomColors): Required<SgButtonCustomColors> & {
51
+ activeFg: string;
52
+ };
53
+ export declare const SgButton: React.ForwardRefExoticComponent<Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "disabled" | "color" | "onClick"> & {
54
+ /**
55
+ * Clique do botão. Pode ser síncrono ou assíncrono (`(event) => unknown`).
56
+ *
57
+ * - Síncrono (abrir modal, alternar painel): funciona como sempre, nada muda.
58
+ * - Se DEVOLVER uma Promise (`async () => { await salvar(); }` ou `() => salvar()`), o botão entra em loading
59
+ * sozinho (spinner, desabilitado, aria-busy), ignora cliques até terminar (inclusive cliques seguidos antes do
60
+ * re-render) e volta ao normal ao terminar ou falhar. Protege contra duplo clique e reenvio e ajuda na
61
+ * idempotência da ação na tela (não substitui a chave de idempotência no servidor).
62
+ * - Cuidado: precisa DEVOLVER a Promise. `onClick={() => { salvar(); }}` (chaves sem return) NÃO protege.
63
+ * - Erro: o botão não engole o erro (sai do loading e relança como rejeição não tratada). Trate com try/catch
64
+ * dentro do onClick e avise o usuário (ex.: `sgWhistle.error`).
65
+ * - Use async só quando a ação puder demorar ou falhar (salvar, enviar, pagar); ações instantâneas ficam síncronas.
66
+ * - A prop `loading` manual continua valendo e vence (loading efetivo = `loading` || Promise pendente).
67
+ *
68
+ * O retorno é `unknown` só para não quebrar handlers existentes de corpo-expressão (`() => lista.push(x)`).
69
+ */
70
+ onClick?: (event: React.MouseEvent<HTMLButtonElement>) => unknown;
34
71
  /** Quando false, desabilita o botão. Padrão: true. */
35
72
  enabled?: boolean;
36
73
  severity?: Severity;
@@ -42,6 +79,7 @@ export declare const SgButton: React.ForwardRefExoticComponent<Omit<React.Button
42
79
  iconOnly?: boolean;
43
80
  leftIcon?: React.ReactNode;
44
81
  rightIcon?: React.ReactNode;
82
+ /** Exibe spinner e bloqueia o clique. Vence o loading automático do onClick: loading efetivo = `loading` || Promise pendente. */
45
83
  loading?: boolean;
46
84
  customColors?: SgButtonCustomColors;
47
85
  } & React.RefAttributes<HTMLButtonElement>>;
@@ -1 +1 @@
1
- {"version":3,"file":"SgButton.d.ts","sourceRoot":"","sources":["../../src/buttons/SgButton.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,KAAK,QAAQ,GACT,SAAS,GACT,WAAW,GACX,SAAS,GACT,MAAM,GACN,SAAS,GACT,MAAM,GACN,QAAQ,CAAC;AAEb,KAAK,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC;AAEhD,KAAK,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE/B,KAAK,KAAK,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEzD,KAAK,SAAS,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;AAEtC,MAAM,MAAM,oBAAoB,GAAG;IACjC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC,GAAG;IACtG,sDAAsD;IACtD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,oBAAoB,CAAC;CACrC,CAAC;AAsEF,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,MAAM,CAAC,EAAE,oBAAoB,GAC5B,QAAQ,CAAC,oBAAoB,CAAC,CAYhC;AAoDD,eAAO,MAAM,QAAQ;IAtJnB,sDAAsD;cAC5C,OAAO;eACN,QAAQ;iBACN,UAAU;WAChB,IAAI;YACH,KAAK;gBACD,SAAS;YACb,KAAK,CAAC,SAAS;eACZ,OAAO;eACP,KAAK,CAAC,SAAS;gBACd,KAAK,CAAC,SAAS;cACjB,OAAO;mBACF,oBAAoB;2CAmNpC,CAAC"}
1
+ {"version":3,"file":"SgButton.d.ts","sourceRoot":"","sources":["../../src/buttons/SgButton.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,KAAK,QAAQ,GACT,SAAS,GACT,WAAW,GACX,SAAS,GACT,MAAM,GACN,SAAS,GACT,MAAM,GACN,QAAQ,CAAC;AAEb,KAAK,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC;AAEhD,KAAK,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAE/B,KAAK,KAAK,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEzD,KAAK,SAAS,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;AAEtC,MAAM,MAAM,oBAAoB,GAAG;IACjC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC,GAAG;IAClH;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,iBAAiB,CAAC,KAAK,OAAO,CAAC;IAClE,sDAAsD;IACtD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,IAAI,CAAC,EAAE,IAAI,CAAC;IACZ,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,KAAK,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC5B,iIAAiI;IACjI,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,oBAAoB,CAAC;CACrC,CAAC;AAoFF,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,MAAM,CAAC,EAAE,oBAAoB,GAC5B,QAAQ,CAAC,oBAAoB,CAAC,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAcvD;AAqDD,eAAO,MAAM,QAAQ;IAzLnB;;;;;;;;;;;;;;;OAeG;cACO,CAAC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,iBAAiB,CAAC,KAAK,OAAO;IACjE,sDAAsD;cAC5C,OAAO;eACN,QAAQ;iBACN,UAAU;WAChB,IAAI;YACH,KAAK;gBACD,SAAS;YACb,KAAK,CAAC,SAAS;eACZ,OAAO;eACP,KAAK,CAAC,SAAS;gBACd,KAAK,CAAC,SAAS;IAC3B,iIAAiI;cACvH,OAAO;mBACF,oBAAoB;2CAsQpC,CAAC"}
@@ -10,6 +10,8 @@ const PRESET = {
10
10
  fg: "var(--sg-btn-primary-fg, hsl(var(--primary-foreground)))",
11
11
  border: "var(--sg-btn-primary-border, hsl(var(--primary)))",
12
12
  hoverBg: "var(--sg-btn-primary-hover-bg, hsl(var(--primary)))",
13
+ hoverFg: "var(--sg-btn-primary-hover-fg, var(--sg-btn-primary-fg, hsl(var(--primary-foreground))))",
14
+ activeFg: "var(--sg-btn-primary-active-fg, var(--sg-btn-primary-fg, hsl(var(--primary-foreground))))",
13
15
  ring: "var(--sg-btn-primary-ring, hsl(var(--primary)/0.35))"
14
16
  },
15
17
  secondary: {
@@ -17,6 +19,8 @@ const PRESET = {
17
19
  fg: "var(--sg-btn-secondary-fg, hsl(var(--secondary-foreground)))",
18
20
  border: "var(--sg-btn-secondary-border, hsl(var(--secondary)))",
19
21
  hoverBg: "var(--sg-btn-secondary-hover-bg, hsl(var(--secondary)))",
22
+ hoverFg: "var(--sg-btn-secondary-hover-fg, var(--sg-btn-secondary-fg, hsl(var(--secondary-foreground))))",
23
+ activeFg: "var(--sg-btn-secondary-active-fg, var(--sg-btn-secondary-fg, hsl(var(--secondary-foreground))))",
20
24
  ring: "var(--sg-btn-secondary-ring, hsl(var(--secondary)/0.35))"
21
25
  },
22
26
  success: {
@@ -24,6 +28,8 @@ const PRESET = {
24
28
  fg: "var(--sg-btn-success-fg, hsl(var(--tertiary-foreground, var(--accent-foreground, 0 0% 100%))))",
25
29
  border: "var(--sg-btn-success-border, hsl(var(--tertiary, var(--accent))))",
26
30
  hoverBg: "var(--sg-btn-success-hover-bg, hsl(var(--tertiary, var(--accent))))",
31
+ hoverFg: "var(--sg-btn-success-hover-fg, var(--sg-btn-success-fg, hsl(var(--tertiary-foreground, var(--accent-foreground, 0 0% 100%)))))",
32
+ activeFg: "var(--sg-btn-success-active-fg, var(--sg-btn-success-fg, hsl(var(--tertiary-foreground, var(--accent-foreground, 0 0% 100%)))))",
27
33
  ring: "var(--sg-btn-success-ring, hsl(var(--tertiary, var(--accent))/0.35))"
28
34
  },
29
35
  info: {
@@ -31,6 +37,8 @@ const PRESET = {
31
37
  fg: "var(--sg-btn-info-fg, hsl(var(--secondary-foreground, var(--primary-foreground))))",
32
38
  border: "var(--sg-btn-info-border, hsl(var(--secondary, var(--primary))))",
33
39
  hoverBg: "var(--sg-btn-info-hover-bg, hsl(var(--secondary, var(--primary))))",
40
+ hoverFg: "var(--sg-btn-info-hover-fg, var(--sg-btn-info-fg, hsl(var(--secondary-foreground, var(--primary-foreground)))))",
41
+ activeFg: "var(--sg-btn-info-active-fg, var(--sg-btn-info-fg, hsl(var(--secondary-foreground, var(--primary-foreground)))))",
34
42
  ring: "var(--sg-btn-info-ring, hsl(var(--secondary, var(--primary))/0.35))"
35
43
  },
36
44
  warning: {
@@ -38,6 +46,8 @@ const PRESET = {
38
46
  fg: "var(--sg-btn-warning-fg, hsl(var(--secondary-foreground, var(--accent-foreground, 0 0% 100%))))",
39
47
  border: "var(--sg-btn-warning-border, hsl(var(--secondary, var(--accent))))",
40
48
  hoverBg: "var(--sg-btn-warning-hover-bg, hsl(var(--secondary, var(--accent))))",
49
+ hoverFg: "var(--sg-btn-warning-hover-fg, var(--sg-btn-warning-fg, hsl(var(--secondary-foreground, var(--accent-foreground, 0 0% 100%)))))",
50
+ activeFg: "var(--sg-btn-warning-active-fg, var(--sg-btn-warning-fg, hsl(var(--secondary-foreground, var(--accent-foreground, 0 0% 100%)))))",
41
51
  ring: "var(--sg-btn-warning-ring, hsl(var(--secondary, var(--accent))/0.35))"
42
52
  },
43
53
  help: {
@@ -45,6 +55,8 @@ const PRESET = {
45
55
  fg: "var(--sg-btn-help-fg, hsl(var(--secondary-foreground, var(--primary-foreground))))",
46
56
  border: "var(--sg-btn-help-border, hsl(var(--secondary, var(--primary))))",
47
57
  hoverBg: "var(--sg-btn-help-hover-bg, hsl(var(--secondary, var(--primary))))",
58
+ hoverFg: "var(--sg-btn-help-hover-fg, var(--sg-btn-help-fg, hsl(var(--secondary-foreground, var(--primary-foreground)))))",
59
+ activeFg: "var(--sg-btn-help-active-fg, var(--sg-btn-help-fg, hsl(var(--secondary-foreground, var(--primary-foreground)))))",
48
60
  ring: "var(--sg-btn-help-ring, hsl(var(--secondary, var(--primary))/0.35))"
49
61
  },
50
62
  danger: {
@@ -52,6 +64,8 @@ const PRESET = {
52
64
  fg: "var(--sg-btn-danger-fg, hsl(var(--destructive-foreground)))",
53
65
  border: "var(--sg-btn-danger-border, hsl(var(--destructive)))",
54
66
  hoverBg: "var(--sg-btn-danger-hover-bg, hsl(var(--destructive)))",
67
+ hoverFg: "var(--sg-btn-danger-hover-fg, var(--sg-btn-danger-fg, hsl(var(--destructive-foreground))))",
68
+ activeFg: "var(--sg-btn-danger-active-fg, var(--sg-btn-danger-fg, hsl(var(--destructive-foreground))))",
55
69
  ring: "var(--sg-btn-danger-ring, hsl(var(--destructive)/0.35))"
56
70
  },
57
71
  };
@@ -67,9 +81,11 @@ export function resolveButtonColors(severity, custom) {
67
81
  fg: custom?.fg ?? base.fg,
68
82
  border: custom?.border ?? base.border,
69
83
  hoverBg: custom?.hoverBg ?? base.hoverBg,
70
- hoverFg: custom?.hoverFg ?? (custom?.fg ?? base.fg),
84
+ // Sem cor customizada, cada estado usa o texto escolhido por contraste sobre o proprio fundo (tokens *-hover-fg/*-active-fg).
85
+ hoverFg: custom?.hoverFg ?? custom?.fg ?? base.hoverFg,
71
86
  hoverBorder: custom?.hoverBorder ?? (custom?.border ?? base.border),
72
87
  activeBg: custom?.activeBg ?? (custom?.hoverBg ?? base.hoverBg),
88
+ activeFg: custom?.hoverFg ?? custom?.fg ?? base.activeFg,
73
89
  ring: custom?.ring ?? base.ring
74
90
  };
75
91
  }
@@ -84,6 +100,7 @@ function buildVars(severity, custom) {
84
100
  ["--sg-btn-hover-fg"]: merged.hoverFg,
85
101
  ["--sg-btn-hover-border"]: merged.hoverBorder,
86
102
  ["--sg-btn-active-bg"]: merged.activeBg,
103
+ ["--sg-btn-active-fg"]: merged.activeFg,
87
104
  ["--sg-btn-ring"]: merged.ring,
88
105
  ["--sg-btn-tint"]: `var(--sg-${toneKey}-600)`
89
106
  };
@@ -91,7 +108,7 @@ function buildVars(severity, custom) {
91
108
  function appearanceClass(appearance) {
92
109
  switch (appearance) {
93
110
  case "solid":
94
- return cn("bg-[var(--sg-btn-bg)] text-[var(--sg-btn-fg)] border border-[var(--sg-btn-border)]", "hover:bg-[var(--sg-btn-hover-bg)] hover:text-[var(--sg-btn-hover-fg)] hover:border-[var(--sg-btn-hover-border)]", "active:bg-[var(--sg-btn-active-bg)]");
111
+ return cn("bg-[var(--sg-btn-bg)] text-[var(--sg-btn-fg)] border border-[var(--sg-btn-border)]", "hover:bg-[var(--sg-btn-hover-bg)] hover:text-[var(--sg-btn-hover-fg)] hover:border-[var(--sg-btn-hover-border)]", "active:bg-[var(--sg-btn-active-bg)] active:text-[var(--sg-btn-active-fg)]");
95
112
  case "outline":
96
113
  return cn("bg-transparent text-[var(--sg-btn-bg)] border border-[var(--sg-btn-border)]", "hover:bg-[rgb(var(--sg-btn-tint)/0.12)]", "active:bg-[rgb(var(--sg-btn-tint)/0.18)]");
97
114
  case "ghost":
@@ -108,16 +125,45 @@ function elevationClass(elevation) {
108
125
  return "";
109
126
  }
110
127
  }
111
- export const SgButton = React.forwardRef(({ severity = "primary", appearance = "solid", size, shape, elevation = "none", label, iconOnly, leftIcon, rightIcon, loading = false, enabled = true, className, children, style, customColors, type = "button", ...rest }, ref) => {
128
+ export const SgButton = React.forwardRef(({ severity = "primary", appearance = "solid", size, shape, elevation = "none", label, iconOnly, leftIcon, rightIcon, loading = false, enabled = true, className, children, style, customColors, type = "button", onClick, ...rest }, ref) => {
129
+ const [pending, setPending] = React.useState(false);
130
+ const pendingRef = React.useRef(false);
131
+ const mountedRef = React.useRef(true);
132
+ React.useEffect(() => {
133
+ mountedRef.current = true;
134
+ return () => {
135
+ mountedRef.current = false;
136
+ };
137
+ }, []);
138
+ const isLoading = loading || pending;
139
+ const handleClick = React.useCallback((event) => {
140
+ if (!onClick || !enabled || loading || pendingRef.current)
141
+ return;
142
+ const result = onClick(event);
143
+ if (!result || typeof result.then !== "function")
144
+ return;
145
+ pendingRef.current = true;
146
+ setPending(true);
147
+ const done = () => {
148
+ pendingRef.current = false;
149
+ if (mountedRef.current)
150
+ setPending(false);
151
+ };
152
+ // O erro da Promise nao e engolido: segue como rejeicao sem tratamento, como antes.
153
+ void result.then(done, (error) => {
154
+ done();
155
+ throw error;
156
+ });
157
+ }, [onClick, enabled, loading]);
112
158
  const resolvedChildren = children ?? label;
113
- const isDisabled = Boolean(!enabled || loading);
159
+ const isDisabled = Boolean(!enabled || isLoading);
114
160
  const inferredIconOnly = !resolvedChildren && (leftIcon || rightIcon);
115
161
  const isIconOnly = iconOnly ?? inferredIconOnly;
116
162
  const resolvedSize = size ?? (isIconOnly ? "sm" : "md");
117
163
  const resolvedShape = shape ?? (isIconOnly ? "circle" : "default");
118
164
  const s = SIZE[resolvedSize];
119
165
  const contentClass = cn("inline-flex min-w-0 items-center justify-center", !isIconOnly ? "w-full" : undefined, s.gap);
120
- return (_jsx("button", { ref: ref, type: type, disabled: isDisabled, style: { ...buildVars(severity, customColors), ...style }, className: cn("inline-flex min-w-0 max-w-full items-center justify-center select-none whitespace-normal break-words text-center", "font-medium", "transition-[background-color,color,border-color,box-shadow,transform] duration-150", "focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-[var(--sg-btn-ring)]", "disabled:opacity-60 disabled:cursor-not-allowed", isIconOnly ? cn("aspect-square", s.h) : cn(s.minH, s.py, s.px), s.text, resolvedShape === "rounded" ? "rounded-full" : resolvedShape === "circle" ? "rounded-full" : resolvedShape === "square" ? "rounded-none" : s.radius, appearanceClass(appearance), elevationClass(elevation), !isDisabled ? "active:translate-y-[0.5px]" : false, className), ...rest, children: loading ? (_jsxs("span", { className: contentClass, children: [_jsx(Spinner, { className: s.icon }), resolvedChildren ? _jsx("span", { className: "min-w-0 whitespace-normal break-words", children: resolvedChildren }) : null] })) : (_jsxs("span", { className: contentClass, children: [leftIcon ? _jsx("span", { className: cn("shrink-0", s.icon), children: leftIcon }) : null, resolvedChildren ? _jsx("span", { className: "min-w-0 whitespace-normal break-words", children: resolvedChildren }) : null, rightIcon ? _jsx("span", { className: cn("shrink-0", s.icon), children: rightIcon }) : null] })) }));
166
+ return (_jsx("button", { ref: ref, type: type, disabled: isDisabled, "aria-busy": isLoading ? true : undefined, style: { ...buildVars(severity, customColors), ...style }, className: cn("inline-flex min-w-0 max-w-full items-center justify-center select-none whitespace-normal break-words text-center", "font-medium", "transition-[background-color,color,border-color,box-shadow,transform] duration-150", "focus-visible:outline-none focus-visible:ring-4 focus-visible:ring-[var(--sg-btn-ring)]", "disabled:opacity-60 disabled:cursor-not-allowed", isIconOnly ? cn("aspect-square", s.h) : cn(s.minH, s.py, s.px), s.text, resolvedShape === "rounded" ? "rounded-full" : resolvedShape === "circle" ? "rounded-full" : resolvedShape === "square" ? "rounded-none" : s.radius, appearanceClass(appearance), elevationClass(elevation), !isDisabled ? "active:translate-y-[0.5px]" : false, className), ...rest, onClick: onClick ? handleClick : undefined, children: isLoading ? (_jsxs("span", { className: contentClass, children: [_jsx(Spinner, { className: s.icon }), resolvedChildren ? _jsx("span", { className: "min-w-0 whitespace-normal break-words", children: resolvedChildren }) : null] })) : (_jsxs("span", { className: contentClass, children: [leftIcon ? _jsx("span", { className: cn("shrink-0", s.icon), children: leftIcon }) : null, resolvedChildren ? _jsx("span", { className: "min-w-0 whitespace-normal break-words", children: resolvedChildren }) : null, rightIcon ? _jsx("span", { className: cn("shrink-0", s.icon), children: rightIcon }) : null] })) }));
121
167
  });
122
168
  SgButton.displayName = "SgButton";
123
169
  function Spinner({ className }) {
@@ -1 +1 @@
1
- {"version":3,"file":"SgButton.meta.d.ts","sourceRoot":"","sources":["../../src/buttons/SgButton.meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE9D,eAAO,MAAM,MAAM,EAAE,QAiHpB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,WAyBrB,CAAC"}
1
+ {"version":3,"file":"SgButton.meta.d.ts","sourceRoot":"","sources":["../../src/buttons/SgButton.meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE9D,eAAO,MAAM,MAAM,EAAE,QAoIpB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,WA6BrB,CAAC"}
@@ -47,7 +47,7 @@ export const sgMeta = {
47
47
  name: "loading",
48
48
  type: "boolean",
49
49
  default: false,
50
- description: "Exibe spinner e bloqueia interacao enquanto a acao esta em progresso.",
50
+ description: "Exibe spinner e bloqueia o clique. Continua valendo e vence o loading automático do onClick: o loading efetivo é loading ou operação pendente (Promise/Future devolvida pelo onClick).",
51
51
  semanticRole: "behavior",
52
52
  bindable: true
53
53
  },
@@ -75,8 +75,8 @@ export const sgMeta = {
75
75
  },
76
76
  {
77
77
  name: "onClick",
78
- type: "MouseEventHandler<HTMLButtonElement>",
79
- description: "Callback disparado quando a acao e executada.",
78
+ type: "(event: MouseEvent<HTMLButtonElement>) => unknown",
79
+ description: "Clique do botão; pode ser síncrono ou assíncrono. Síncrono (abrir modal, alternar): nada muda. Se DEVOLVER uma Promise/Future, como async () => { await salvar(); } ou () => salvar(), o botão entra em loading sozinho (spinner, desabilitado, aria-busy na web), ignora cliques até terminar (inclusive cliques seguidos antes de a tela atualizar) e volta ao normal ao terminar ou falhar. Protege contra duplo clique e reenvio e ajuda na idempotência da ação na tela (não substitui a chave de idempotência no servidor). Cuidado: a função precisa devolver a operação; com chaves e sem return (ou sem async), como () => { salvar(); } ou () { salvar(); }, não protege; já async () {...} sempre devolve. O erro não é engolido: o botão sai do loading e relança como rejeição não tratada; trate com try/catch dentro do onClick e avise o usuário (sgWhistle.error). Use async só quando a ação puder demorar ou falhar (salvar, enviar, pagar).",
80
80
  semanticRole: "event",
81
81
  bindable: false
82
82
  }
@@ -89,6 +89,24 @@ export const sgMeta = {
89
89
  file: "apps/showcase/src/app/components/sg-button/samples/basico.tsx.sample",
90
90
  kind: "sample"
91
91
  },
92
+ {
93
+ id: "auto-loading",
94
+ title: "Loading automático com Future",
95
+ file: "apps/showcase/src/app/components/sg-button/samples/loading-automatico-com-future.tsx.sample",
96
+ kind: "sample"
97
+ },
98
+ {
99
+ id: "sync-vs-async",
100
+ title: "onClick síncrono x assíncrono",
101
+ file: "apps/showcase/src/app/components/sg-button/samples/onclick-sincrono-x-assincrono.tsx.sample",
102
+ kind: "sample"
103
+ },
104
+ {
105
+ id: "async-error",
106
+ title: "Tratando erro no onClick assíncrono",
107
+ file: "apps/showcase/src/app/components/sg-button/samples/tratando-erro-no-onclick-assincrono.tsx.sample",
108
+ kind: "sample"
109
+ },
92
110
  {
93
111
  id: "playground",
94
112
  title: "Playground",
@@ -116,7 +134,8 @@ export const aiHints = {
116
134
  preferredUseCases: [
117
135
  "Acoes primarias e secundarias em formularios e telas CRUD.",
118
136
  "Submit, confirmacao, salvar, cancelar e outras acoes explicitas do usuario.",
119
- "Chamadas para acao com feedback visual de loading."
137
+ "Chamadas para acao com feedback visual de loading.",
138
+ "Salvar, enviar ou pagar: use onClick assíncrono (async () => { await salvar(); } ou () => salvar()) para o botão entrar em loading sozinho e ignorar duplo clique; acoes instantaneas (abrir modal, alternar) ficam sincronas."
120
139
  ],
121
140
  avoidUseCases: [
122
141
  "Agrupar conteudo ou estruturar layout; nesses casos prefira SgPanel, SgCard ou SgStack.",
@@ -127,7 +146,10 @@ export const aiHints = {
127
146
  relatedEntityFields: ["submit", "save", "cancel", "confirm", "delete"],
128
147
  compositionHints: [
129
148
  "Combinar com SgInputText e outros campos em formularios.",
130
- "Usar dentro de SgPanel ou SgDialog para acoes contextuais."
149
+ "Usar dentro de SgPanel ou SgDialog para acoes contextuais.",
150
+ "O onClick precisa DEVOLVER a Promise/Future para o loading automatico funcionar: onClick={() => { salvar(); }} (chaves sem return) nao protege; use () => salvar() ou async () => { await salvar(); }. No Flutter, async () {...} sempre devolve Future; () { salvar(); } nao.",
151
+ "O botao nao engole o erro do onClick assincrono: sai do loading e relanca como rejeicao nao tratada. Trate com try/catch dentro do onClick e avise o usuario com sgWhistle.error (Flutter: sgWhistle.error(SgWhistleOptions(...))).",
152
+ "O loading automatico protege a acao na tela (duplo clique/reenvio), mas nao substitui a chave de idempotencia no servidor. A prop loading manual vence: loading efetivo = loading ou operacao pendente."
131
153
  ],
132
154
  rankingSignals: {
133
155
  freeText: 0,
@@ -1 +1 @@
1
- {"version":3,"file":"SgFadeDigit.d.ts","sourceRoot":"","sources":["../../../src/digits/fade-digit/SgFadeDigit.tsx"],"names":[],"mappings":"AAOA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,gGAAgG;IAChG,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,KAAiB,EACjB,eAA2B,EAC3B,IAAI,EACJ,QAAa,EACb,SAAS,GACV,EAAE,gBAAgB,2CAyGlB"}
1
+ {"version":3,"file":"SgFadeDigit.d.ts","sourceRoot":"","sources":["../../../src/digits/fade-digit/SgFadeDigit.tsx"],"names":[],"mappings":"AAOA,MAAM,MAAM,gBAAgB,GAAG;IAC7B,gGAAgG;IAChG,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,KAAiB,EACjB,eAA2B,EAC3B,IAAI,EACJ,QAAa,EACb,SAAS,GACV,EAAE,gBAAgB,2CA6GlB"}
@@ -21,8 +21,11 @@ export function SgFadeDigit({ value, color = "#edebeb", backgroundColor = "#3332
21
21
  const [opacity, setOpacity] = React.useState(1);
22
22
  React.useEffect(() => {
23
23
  // Nothing to do — already showing the right value
24
- if (value === displayValue)
24
+ // (and restore opacity: a fade-out may have started for a value that came back to the current one)
25
+ if (value === displayValue) {
26
+ setOpacity(1);
25
27
  return;
28
+ }
26
29
  // Phase 1: fade out
27
30
  setOpacity(0);
28
31
  // Phase 2: after fade-out completes, swap text and fade in
@@ -10,6 +10,8 @@ export type SgClockProps = {
10
10
  format?: "12h" | "24h";
11
11
  showSeconds?: boolean;
12
12
  digitalStyle?: SgClockDigitalStyle;
13
+ /** Desenha o relogio digital quando `digitalStyle` e `"custom"`. Sem ele, cai no estilo `default`. */
14
+ digitBuilder?: SgClockDigitBuilder;
13
15
  secondHandMode?: "step" | "smooth";
14
16
  themeId?: string;
15
17
  theme?: SgClockTheme;
@@ -19,6 +21,28 @@ export type SgClockProps = {
19
21
  cardTitle?: React.ReactNode;
20
22
  cardProps?: Omit<SgCardProps, "children" | "title">;
21
23
  };
22
- export type SgClockDigitalStyle = "default" | "segment" | "sevenSegment" | "roller3d" | "flip" | "fade" | "matrix" | "neon" | "discard";
24
+ export type SgClockDigitalStyle = "default" | "segment" | "sevenSegment" | "roller3d" | "flip" | "fade" | "matrix" | "neon" | "discard" | "custom";
25
+ /** Partes do relogio digital entregues ao `digitBuilder` (as mesmas do construtor do Flutter). */
26
+ export type SgClockDigitalParts = {
27
+ /** Sempre com 2 digitos; em 12h vai de 01 a 12. */
28
+ hour: string;
29
+ minute: string;
30
+ /** Vazio quando `showSeconds` e falso. */
31
+ second: string;
32
+ /** So em 12h (`AM`/`PM` ou o que o Intl do locale devolve). */
33
+ dayPeriod: string | undefined;
34
+ is12h: boolean;
35
+ showSeconds: boolean;
36
+ /** `size` original do SgClock (`"sm"`, `"md"`, `"lg"` ou numero). */
37
+ size: "sm" | "md" | "lg" | number;
38
+ /** `HH:MM` ou `HH:MM:SS`. */
39
+ text: string;
40
+ /** `text` + periodo em maiusculas. */
41
+ textWithPeriod: string;
42
+ /** Numero = ele mesmo, sm 12, lg 28, md 16. */
43
+ sizePx: number;
44
+ };
45
+ /** Recebe as partes do relogio digital e devolve o desenho completo (digitos e separadores). */
46
+ export type SgClockDigitBuilder = (parts: SgClockDigitalParts) => React.ReactNode;
23
47
  export declare function SgClock(props: SgClockProps): import("react/jsx-runtime").JSX.Element;
24
48
  //# sourceMappingURL=SgClock.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SgClock.d.ts","sourceRoot":"","sources":["../../../src/gadgets/clock/SgClock.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAUnD,OAAO,EAAU,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AA2C/D,MAAM,MAAM,YAAY,GAAG;IACzB,UAAU,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAClC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;IACnC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,mBAAmB,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAChC,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC5B,SAAS,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC;CACrD,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAC3B,SAAS,GACT,SAAS,GACT,cAAc,GACd,UAAU,GACV,MAAM,GACN,MAAM,GACN,QAAQ,GACR,MAAM,GACN,SAAS,CAAC;AAugBd,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,2CAsG1C"}
1
+ {"version":3,"file":"SgClock.d.ts","sourceRoot":"","sources":["../../../src/gadgets/clock/SgClock.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAK/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAUnD,OAAO,EAAU,KAAK,WAAW,EAAE,MAAM,qBAAqB,CAAC;AA2C/D,MAAM,MAAM,YAAY,GAAG;IACzB,UAAU,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAC;IAClC,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;IACnC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,YAAY,CAAC,EAAE,mBAAmB,CAAC;IACnC,sGAAsG;IACtG,YAAY,CAAC,EAAE,mBAAmB,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IACnC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC;IAC5B,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAChC,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC5B,SAAS,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,UAAU,GAAG,OAAO,CAAC,CAAC;CACrD,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAC3B,SAAS,GACT,SAAS,GACT,cAAc,GACd,UAAU,GACV,MAAM,GACN,MAAM,GACN,QAAQ,GACR,MAAM,GACN,SAAS,GACT,QAAQ,CAAC;AAEb,kGAAkG;AAClG,MAAM,MAAM,mBAAmB,GAAG;IAChC,mDAAmD;IACnD,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,+DAA+D;IAC/D,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,KAAK,EAAE,OAAO,CAAC;IACf,WAAW,EAAE,OAAO,CAAC;IACrB,qEAAqE;IACrE,IAAI,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;IAClC,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,sCAAsC;IACtC,cAAc,EAAE,MAAM,CAAC;IACvB,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,gGAAgG;AAChG,MAAM,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,mBAAmB,KAAK,KAAK,CAAC,SAAS,CAAC;AA4hBlF,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,2CAwG1C"}
@@ -110,7 +110,7 @@ function AnalogClock({ size = 280, initialServerTime, timezone, locale = "pt-BR"
110
110
  (resolver ? resolveTheme(resolver, themeId, "classic") : getTheme(themeId) ?? getTheme("classic"));
111
111
  return (_jsx("div", { className: className, style: style, children: _jsxs("svg", { width: size, height: size, viewBox: "0 0 100 100", className: cn("block", dark ? "dark" : undefined), "aria-label": t(i18n, "components.gadgets.clock.analogAria"), children: [_jsx("g", { id: "theme", children: themeObj ? _jsx(ThemeLayer, { theme: themeObj, args: { size, dark } }) : null }), _jsxs("g", { id: "hands", children: [_jsx("g", { transform: `rotate(${hourDeg} 50 50)`, children: _jsx("line", { x1: "50", y1: "50", x2: "50", y2: "28", className: "stroke-neutral-800 dark:stroke-neutral-200", strokeWidth: "2.6", strokeLinecap: "round" }) }), _jsx("g", { transform: `rotate(${minDeg} 50 50)`, children: _jsx("line", { x1: "50", y1: "50", x2: "50", y2: "18", className: "stroke-neutral-800 dark:stroke-neutral-200", strokeWidth: "1.7", strokeLinecap: "round" }) }), showSeconds && (_jsx("g", { transform: `rotate(${secDeg} 50 50)`, children: _jsx("line", { x1: "50", y1: "54", x2: "50", y2: "14", className: "stroke-rose-500", strokeWidth: "0.9", strokeLinecap: "round" }) }))] }), _jsx("circle", { cx: "50", cy: "50", r: "2.2", className: "fill-neutral-800 dark:fill-neutral-200" }), showSeconds ? _jsx("circle", { cx: "50", cy: "50", r: "1.1", className: "fill-rose-500" }) : null, centerOverlay ? (_jsx("foreignObject", { x: "35", y: "35", width: "30", height: "30", children: _jsx("div", { className: "flex h-full w-full items-center justify-center", children: centerOverlay }) })) : null] }) }));
112
112
  }
113
- function DigitalClock({ initialServerTime, timezone, locale = "pt-BR", format = "24h", showSeconds = true, size = "md", digitalStyle = "default", className, style }) {
113
+ function DigitalClock({ initialServerTime, timezone, locale = "pt-BR", format = "24h", showSeconds = true, size = "md", digitalStyle = "default", digitBuilder, className, style }) {
114
114
  const i18n = useComponentsI18n();
115
115
  const { nowMs, hasProvider, providerTick } = useClockNowMs(initialServerTime);
116
116
  void providerTick;
@@ -137,6 +137,21 @@ function DigitalClock({ initialServerTime, timezone, locale = "pt-BR", format =
137
137
  const fontSize = typeof size === "number" ? { fontSize: `${size}px`, lineHeight: 1 } : undefined;
138
138
  const sizePx = digitalSizeToNumber(size);
139
139
  const withPeriod = format === "12h" && dayPeriod ? `${text} ${dayPeriod.toUpperCase()}` : text;
140
+ if (digitalStyle === "custom" && digitBuilder) {
141
+ const is12h = format === "12h";
142
+ return (_jsx("div", { className: className, style: style, "aria-label": t(i18n, "components.gadgets.clock.digitalAria"), children: digitBuilder({
143
+ hour,
144
+ minute,
145
+ second: showSeconds ? second : "",
146
+ dayPeriod: is12h && dayPeriod ? dayPeriod : undefined,
147
+ is12h,
148
+ showSeconds,
149
+ size,
150
+ text,
151
+ textWithPeriod: withPeriod,
152
+ sizePx
153
+ }) }));
154
+ }
140
155
  if (digitalStyle === "flip") {
141
156
  const hNum = Number.parseInt(hour, 10) || 0;
142
157
  const mNum = Number.parseInt(minute, 10) || 0;
@@ -275,7 +290,7 @@ function DigitalClock({ initialServerTime, timezone, locale = "pt-BR", format =
275
290
  }
276
291
  export function SgClock(props) {
277
292
  const i18n = useComponentsI18n();
278
- const { clockStyle = "digital", size = "md", initialServerTime, timezone, locale = "pt-BR", format = "24h", showSeconds = true, digitalStyle = "default", secondHandMode = "step", themeId = "classic", theme, className, style, centerOverlay, cardTitle, cardProps } = props;
293
+ const { clockStyle = "digital", size = "md", initialServerTime, timezone, locale = "pt-BR", format = "24h", showSeconds = true, digitalStyle = "default", digitBuilder, secondHandMode = "step", themeId = "classic", theme, className, style, centerOverlay, cardTitle, cardProps } = props;
279
294
  const resolvedCardTitle = cardTitle ?? t(i18n, "components.gadgets.clock.title");
280
295
  const resolvedDragPersistKey = React.useMemo(() => {
281
296
  const explicitPersistKey = cardProps?.dragPersistKey;
@@ -299,6 +314,6 @@ export function SgClock(props) {
299
314
  const clockNode = clockStyle === "analog" ? ((() => {
300
315
  const analogSize = typeof size === "number" ? size : size === "sm" ? 140 : size === "lg" ? 320 : 240;
301
316
  return (_jsx(AnalogClock, { size: analogSize, initialServerTime: initialServerTime, themeId: themeId, theme: theme, timezone: timezone, locale: locale, showSeconds: showSeconds, secondHandMode: secondHandMode, className: className, style: style, centerOverlay: centerOverlay }));
302
- })()) : (_jsx(DigitalClock, { timezone: timezone, initialServerTime: initialServerTime, locale: locale, format: format, showSeconds: showSeconds, size: size, digitalStyle: digitalStyle, className: className, style: style }));
317
+ })()) : (_jsx(DigitalClock, { timezone: timezone, initialServerTime: initialServerTime, locale: locale, format: format, showSeconds: showSeconds, size: size, digitalStyle: digitalStyle, digitBuilder: digitBuilder, className: className, style: style }));
303
318
  return (_jsx(SgCard, { title: resolvedCardTitle, collapsible: true, defaultOpen: true, collapseToggleAlign: "right", collapseIconSize: 20, draggable: true, bgColor: "rgb(var(--sg-surface,var(--sg-bg)))", bgColorTitle: "rgb(var(--sg-surface-2,var(--sg-surface,var(--sg-bg))))", cardStyle: "outlined", ...cardProps, className: mergedCardClassName, bodyClassName: mergedCardBodyClassName, style: mergedCardStyle, dragPersistKey: resolvedDragPersistKey, children: clockNode }));
304
319
  }
@@ -1 +1 @@
1
- {"version":3,"file":"SgClock.meta.d.ts","sourceRoot":"","sources":["../../../src/gadgets/clock/SgClock.meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEjE,eAAO,MAAM,MAAM,EAAE,QA8BpB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,WAmBrB,CAAC"}
1
+ {"version":3,"file":"SgClock.meta.d.ts","sourceRoot":"","sources":["../../../src/gadgets/clock/SgClock.meta.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAEjE,eAAO,MAAM,MAAM,EAAE,QA+BpB,CAAC;AAEF,eAAO,MAAM,OAAO,EAAE,WAmBrB,CAAC"}
@@ -13,7 +13,8 @@ export const sgMeta = {
13
13
  fieldSemantics: ["clock", "timeDisplay", "timezoneClock", "digitalClock", "analogClock"],
14
14
  props: [
15
15
  { name: "clockStyle", type: '"digital" | "analog"', default: "digital", description: "Modo principal de exibicao do relogio.", semanticRole: "appearance", bindable: true },
16
- { name: "digitalStyle", type: '"default" | "segment" | "sevenSegment" | "roller3d" | "flip" | "fade" | "matrix" | "neon" | "discard"', default: "default", description: "Estilo visual do relogio digital.", semanticRole: "appearance", bindable: true },
16
+ { name: "digitalStyle", type: '"default" | "segment" | "sevenSegment" | "roller3d" | "flip" | "fade" | "matrix" | "neon" | "discard" | "custom"', default: "default", description: "Estilo visual do relogio digital.", semanticRole: "appearance", bindable: true },
17
+ { name: "digitBuilder", type: "(parts: SgClockDigitalParts) => ReactNode", description: "Desenha o relogio digital quando digitalStyle e custom; sem ele, usa o estilo default.", semanticRole: "appearance", bindable: false },
17
18
  { name: "timezone", type: "string", description: "Timezone usada no calculo da hora exibida.", semanticRole: "behavior", bindable: true },
18
19
  { name: "format", type: '"12h" | "24h"', default: "24h", description: "Formato horario do relogio.", semanticRole: "behavior", bindable: true },
19
20
  { name: "showSeconds", type: "boolean", default: true, description: "Exibe ou oculta os segundos.", semanticRole: "appearance", bindable: true },