@seedgrid/fe-components 2026.11.4 → 2026.11.5

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 (33) hide show
  1. package/dist/ai/seedgrid-components.manifest.json +74 -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/gadgets/clock/SgClock.d.ts +25 -1
  8. package/dist/gadgets/clock/SgClock.d.ts.map +1 -1
  9. package/dist/gadgets/clock/SgClock.js +18 -3
  10. package/dist/gadgets/clock/SgClock.meta.d.ts.map +1 -1
  11. package/dist/gadgets/clock/SgClock.meta.js +2 -1
  12. package/dist/generated/compiledStyles.d.ts +1 -1
  13. package/dist/generated/compiledStyles.d.ts.map +1 -1
  14. package/dist/generated/compiledStyles.js +1 -1
  15. package/dist/i18n/SgComponentsI18nProvider.meta.d.ts.map +1 -1
  16. package/dist/i18n/SgComponentsI18nProvider.meta.js +6 -3
  17. package/dist/i18n/useComponentsI18n.meta.d.ts.map +1 -1
  18. package/dist/i18n/useComponentsI18n.meta.js +8 -2
  19. package/dist/index.d.ts +2 -2
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +1 -1
  22. package/dist/layout/SgToolBar.d.ts +6 -0
  23. package/dist/layout/SgToolBar.d.ts.map +1 -1
  24. package/dist/layout/SgToolBar.js +107 -5
  25. package/dist/layout/SgToolBar.meta.d.ts.map +1 -1
  26. package/dist/layout/SgToolBar.meta.js +8 -0
  27. package/dist/layout/toolbar-logic.d.ts +11 -0
  28. package/dist/layout/toolbar-logic.d.ts.map +1 -1
  29. package/dist/layout/toolbar-logic.js +25 -0
  30. package/dist/sandbox.cjs +58 -58
  31. package/dist/style.css +2 -2
  32. package/dist/wizard/SgWizard.js +1 -1
  33. package/package.json +3 -3
@@ -10493,7 +10493,7 @@
10493
10493
  "type": "boolean",
10494
10494
  "required": false,
10495
10495
  "default": false,
10496
- "description": "Exibe spinner e bloqueia interacao enquanto a acao esta em progresso.",
10496
+ "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).",
10497
10497
  "semanticRole": "behavior",
10498
10498
  "bindable": true
10499
10499
  },
@@ -10524,9 +10524,9 @@
10524
10524
  },
10525
10525
  {
10526
10526
  "name": "onClick",
10527
- "type": "MouseEventHandler<HTMLButtonElement>",
10527
+ "type": "(event: MouseEvent<HTMLButtonElement>) => unknown",
10528
10528
  "required": false,
10529
- "description": "Callback disparado quando a acao e executada.",
10529
+ "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).",
10530
10530
  "semanticRole": "event",
10531
10531
  "bindable": false
10532
10532
  },
@@ -10563,6 +10563,24 @@
10563
10563
  "file": "apps/showcase/src/app/components/sg-button/samples/basico.tsx.sample",
10564
10564
  "kind": "sample"
10565
10565
  },
10566
+ {
10567
+ "id": "auto-loading",
10568
+ "title": "Loading automático com Future",
10569
+ "file": "apps/showcase/src/app/components/sg-button/samples/loading-automatico-com-future.tsx.sample",
10570
+ "kind": "sample"
10571
+ },
10572
+ {
10573
+ "id": "sync-vs-async",
10574
+ "title": "onClick síncrono x assíncrono",
10575
+ "file": "apps/showcase/src/app/components/sg-button/samples/onclick-sincrono-x-assincrono.tsx.sample",
10576
+ "kind": "sample"
10577
+ },
10578
+ {
10579
+ "id": "async-error",
10580
+ "title": "Tratando erro no onClick assíncrono",
10581
+ "file": "apps/showcase/src/app/components/sg-button/samples/tratando-erro-no-onclick-assincrono.tsx.sample",
10582
+ "kind": "sample"
10583
+ },
10566
10584
  {
10567
10585
  "id": "playground",
10568
10586
  "title": "Playground",
@@ -10590,7 +10608,8 @@
10590
10608
  "preferredUseCases": [
10591
10609
  "Acoes primarias e secundarias em formularios e telas CRUD.",
10592
10610
  "Submit, confirmacao, salvar, cancelar e outras acoes explicitas do usuario.",
10593
- "Chamadas para acao com feedback visual de loading."
10611
+ "Chamadas para acao com feedback visual de loading.",
10612
+ "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."
10594
10613
  ],
10595
10614
  "avoidUseCases": [
10596
10615
  "Agrupar conteudo ou estruturar layout; nesses casos prefira SgPanel, SgCard ou SgStack.",
@@ -10612,7 +10631,10 @@
10612
10631
  ],
10613
10632
  "compositionHints": [
10614
10633
  "Combinar com SgInputText e outros campos em formularios.",
10615
- "Usar dentro de SgPanel ou SgDialog para acoes contextuais."
10634
+ "Usar dentro de SgPanel ou SgDialog para acoes contextuais.",
10635
+ "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.",
10636
+ "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(...))).",
10637
+ "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."
10616
10638
  ],
10617
10639
  "rankingSignals": {
10618
10640
  "freeText": 0,
@@ -18108,6 +18130,15 @@
18108
18130
  "semanticRole": "behavior",
18109
18131
  "bindable": true
18110
18132
  },
18133
+ {
18134
+ "name": "overflow",
18135
+ "type": "boolean",
18136
+ "required": false,
18137
+ "default": false,
18138
+ "description": "Move para um menu '...' os botoes que nao cabem na largura disponivel; abaixo de 600px abre como painel inferior.",
18139
+ "semanticRole": "behavior",
18140
+ "bindable": true
18141
+ },
18111
18142
  {
18112
18143
  "name": "children",
18113
18144
  "type": "ReactNode",
@@ -19114,13 +19145,21 @@
19114
19145
  },
19115
19146
  {
19116
19147
  "name": "digitalStyle",
19117
- "type": "\"default\" | \"segment\" | \"sevenSegment\" | \"roller3d\" | \"flip\" | \"fade\" | \"matrix\" | \"neon\" | \"discard\"",
19148
+ "type": "\"default\" | \"segment\" | \"sevenSegment\" | \"roller3d\" | \"flip\" | \"fade\" | \"matrix\" | \"neon\" | \"discard\" | \"custom\"",
19118
19149
  "required": false,
19119
19150
  "default": "default",
19120
19151
  "description": "Estilo visual do relogio digital.",
19121
19152
  "semanticRole": "appearance",
19122
19153
  "bindable": true
19123
19154
  },
19155
+ {
19156
+ "name": "digitBuilder",
19157
+ "type": "(parts: SgClockDigitalParts) => ReactNode",
19158
+ "required": false,
19159
+ "description": "Desenha o relogio digital quando digitalStyle e custom; sem ele, usa o estilo default.",
19160
+ "semanticRole": "appearance",
19161
+ "bindable": false
19162
+ },
19124
19163
  {
19125
19164
  "name": "timezone",
19126
19165
  "type": "string",
@@ -23745,20 +23784,38 @@
23745
23784
  "examples": [
23746
23785
  {
23747
23786
  "id": "basic",
23748
- "title": "Uso basico",
23787
+ "title": "Uso básico",
23749
23788
  "file": "apps/showcase/src/app/components/providers/sg-components-i18n-provider/samples/uso-basico.tsx.sample",
23750
23789
  "kind": "sample"
23751
23790
  },
23752
23791
  {
23753
23792
  "id": "dynamic",
23754
- "title": "Troca dinamica de locale",
23793
+ "title": "Troca dinâmica de locale",
23755
23794
  "file": "apps/showcase/src/app/components/providers/sg-components-i18n-provider/samples/troca-dinamica-de-locale.tsx.sample",
23756
23795
  "kind": "sample"
23796
+ },
23797
+ {
23798
+ "id": "global",
23799
+ "title": "API imperativa (setComponentsI18n)",
23800
+ "file": "apps/showcase/src/app/components/providers/sg-components-i18n-provider/samples/api-imperativa-setcomponentsi18n.tsx.sample",
23801
+ "kind": "sample"
23802
+ },
23803
+ {
23804
+ "id": "custom",
23805
+ "title": "Sobrescrita de mensagens",
23806
+ "file": "apps/showcase/src/app/components/providers/sg-components-i18n-provider/samples/sobrescrita-de-mensagens.tsx.sample",
23807
+ "kind": "sample"
23808
+ },
23809
+ {
23810
+ "id": "playground",
23811
+ "title": "Playground",
23812
+ "file": "apps/showcase/src/app/components/providers/sg-components-i18n-provider/sg-components-i18n-provider.tsx.playground",
23813
+ "kind": "playground"
23757
23814
  }
23758
23815
  ],
23759
23816
  "showcase": {
23760
23817
  "route": "/components/providers/sg-components-i18n-provider",
23761
- "hasPlayground": false,
23818
+ "hasPlayground": true,
23762
23819
  "hasPropsTable": true
23763
23820
  },
23764
23821
  "sdui": {
@@ -23870,14 +23927,20 @@
23870
23927
  },
23871
23928
  {
23872
23929
  "id": "locale-aware",
23873
- "title": "Componentes conscientes do locale",
23930
+ "title": "Construindo componentes cientes de locale",
23874
23931
  "file": "apps/showcase/src/app/components/hooks/use-components-i18n/samples/componentes-conscientes-do-locale.tsx.sample",
23875
23932
  "kind": "sample"
23933
+ },
23934
+ {
23935
+ "id": "playground",
23936
+ "title": "Playground",
23937
+ "file": "apps/showcase/src/app/components/hooks/use-components-i18n/use-components-i18n.tsx.playground",
23938
+ "kind": "playground"
23876
23939
  }
23877
23940
  ],
23878
23941
  "showcase": {
23879
23942
  "route": "/components/hooks/use-components-i18n",
23880
- "hasPlayground": false,
23943
+ "hasPlayground": true,
23881
23944
  "hasPropsTable": true
23882
23945
  }
23883
23946
  },
@@ -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,
@@ -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 },