@softize/opus 12.5.0 → 12.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,14 @@ Depois de qualquer bump, rode os gates (`typecheck` · `test` · `opus check` ·
7
7
  `opus copy --check` · `base copy check` · `manifest:check`) — eles apontam o que a
8
8
  mudança cobra do seu código.
9
9
 
10
+ ## 12.5.1 — 2026-08-24
11
+
12
+ `Dock` passa a fazer o roving tabindex que uma `toolbar` promete: a barra tem um único ponto de
13
+ entrada no Tab, e as setas andam entre as ações a partir dele. Antes as setas eram um caminho a
14
+ mais e a barra continuava cobrando um Tab por ícone, que é justamente o que o padrão evita.
15
+
16
+ A mudança é interna ao componente e não altera a API.
17
+
10
18
  ## 12.5.0 — 2026-08-24
11
19
 
12
20
  Cache vira porta do protocolo. `CacheAdapter` chega aos handlers como `ctx.cache`, com a superfície
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softize/opus",
3
- "version": "12.5.0",
3
+ "version": "12.5.1",
4
4
  "description": "End-to-end action protocol for TypeScript. Single package with subpath exports (core + adapters).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -36,6 +36,18 @@ export interface DockProps {
36
36
  export function Dock({ position = 'bottom', label, className, children }: DockProps): React.ReactElement {
37
37
  const ref = React.useRef<HTMLDivElement>(null)
38
38
 
39
+ // Roving tabindex: uma toolbar tem UM ponto de entrada no Tab; dentro dela, as setas andam.
40
+ // Sem isto as setas seriam um caminho a mais, e a barra continuaria cobrando um Tab por ícone.
41
+ const roving = React.useCallback((focado?: HTMLElement) => {
42
+ const items = Array.from(ref.current?.querySelectorAll<HTMLButtonElement>('button') ?? [])
43
+ const entrada = items.find((item) => item === focado) ?? items.find((item) => !item.disabled)
44
+ for (const item of items) item.tabIndex = item === entrada ? 0 : -1
45
+ }, [])
46
+
47
+ React.useEffect(() => {
48
+ roving()
49
+ })
50
+
39
51
  // Navegação de toolbar: as setas andam entre as ações, e Home/End vão às pontas. Sem isso
40
52
  // uma barra com dez ícones cobra dez Tabs de quem navega por teclado.
41
53
  const onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => {
@@ -54,7 +66,10 @@ export function Dock({ position = 'bottom', label, className, children }: DockPr
54
66
  : event.key === 'ArrowRight'
55
67
  ? (current + 1) % items.length
56
68
  : (current - 1 + items.length) % items.length
57
- items[next]?.focus()
69
+ const alvo = items[next]
70
+ if (alvo === undefined) return
71
+ roving(alvo)
72
+ alvo.focus()
58
73
  }
59
74
 
60
75
  return (
@@ -65,6 +80,7 @@ export function Dock({ position = 'bottom', label, className, children }: DockPr
65
80
  aria-label={label}
66
81
  aria-orientation="horizontal"
67
82
  onKeyDown={onKeyDown}
83
+ onFocus={(event) => roving(event.target as HTMLElement)}
68
84
  className={cn(
69
85
  'absolute z-10 flex max-w-[calc(100%-1.5rem)] flex-row items-center gap-1.5 overflow-x-auto',
70
86
  'rounded-2xl border border-border bg-background/95 p-1.5 shadow-lg backdrop-blur',