@softize/opus 8.8.0 → 8.8.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softize/opus",
3
- "version": "8.8.0",
3
+ "version": "8.8.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",
@@ -305,13 +305,18 @@ function CustomSelect(props: Exclude<SelectProps, SelectNativeProps>): React.Rea
305
305
  * onde não existe seleção persistente pra exibir.
306
306
  */
307
307
  const triggerText = selectedOption?.label ?? ''
308
- /** `triggerLabel` pode ser JSX (a barra do composer mostra a chave curta), e `value` de
309
- * input só aceita texto — então ele vai num `<span>` por cima, com o input `sr-only`
310
- * guardando o `id` e o rótulo textual pro leitor de tela. */
311
- const customTrigger = !props.multiple && selectedOption?.triggerLabel !== undefined
312
308
 
313
309
  return (
314
- <Popover open={open} onOpenChange={setOpen}>
310
+ <Popover
311
+ open={open}
312
+ onOpenChange={(v) => {
313
+ // Fechar devolve o foco pro gatilho, e o gatilho abre AO RECEBER FOCO. Sem armar a
314
+ // guarda em TODO fechamento (não só no Esc), o seletor reabria sozinho — o
315
+ // "abre, fecha e abre de novo" que aparecia no primeiro clique.
316
+ if (!v) closingRef.current = true
317
+ setOpen(v)
318
+ }}
319
+ >
315
320
  <div ref={rootRef} data-slot="select" className={cn(ghost ? 'min-w-0' : 'min-w-40', className)}>
316
321
  <CommandPrimitive data-slot="select-command" shouldFilter={canSearch && onSearch === undefined} className="overflow-visible">
317
322
  <PopoverAnchor asChild>
@@ -361,13 +366,13 @@ function CustomSelect(props: Exclude<SelectProps, SelectNativeProps>): React.Rea
361
366
  </span>
362
367
  )
363
368
  })}
364
- {/* Input somente-leitura em vez de `<span>`: ele carrega o `id`, e é o que
365
- faz o `htmlFor` do Label achar um campo (clicar no rótulo foca e abre).
366
- Um `<div>` não é rotulável, e um `<button>` não pode conter os botões de
367
- remover chip do modo múltiplo. */}
368
- {customTrigger && (
369
- <span data-slot="select-trigger-label" className="min-w-0 truncate text-sm">
370
- {selectedOption?.triggerLabel}
369
+ {/* O rótulo é SEMPRE um `<span>`. Quando ele morava no `value` de um input,
370
+ o texto rolava pro fim ao ganhar foco (o browser leva o cursor pro final)
371
+ e o começo do título sumia num campo estreito, a pessoa via o meio de
372
+ uma frase. `<span>` trunca com reticências, que é o certo. */}
373
+ {!props.multiple && (
374
+ <span data-slot="select-trigger-label" className={cn('min-w-0 flex-1 truncate text-left text-sm', selectedOption === undefined && 'text-muted-foreground')}>
375
+ {selectedOption?.triggerLabel ?? (selectedOption?.label ?? placeholder)}
371
376
  </span>
372
377
  )}
373
378
  <input
@@ -404,10 +409,7 @@ function CustomSelect(props: Exclude<SelectProps, SelectNativeProps>): React.Rea
404
409
  }
405
410
  }}
406
411
  placeholder={props.multiple && selectedValues.length > 0 ? '' : placeholder}
407
- className={cn(
408
- 'min-w-0 flex-1 cursor-pointer bg-transparent text-sm caret-transparent outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed',
409
- customTrigger && 'sr-only',
410
- )}
412
+ className="sr-only"
411
413
  />
412
414
  {clearable === true && selectedValues.length > 0 && disabled !== true && (
413
415
  <button
@@ -461,11 +463,16 @@ function CustomSelect(props: Exclude<SelectProps, SelectNativeProps>): React.Rea
461
463
  'pointer-events-auto overflow-hidden p-0',
462
464
  ghost ? 'w-auto min-w-56 max-w-80' : 'w-(--radix-popover-trigger-width) min-w-40',
463
465
  )}
464
- // Com busca, o foco VAI pra caixa dentro da lista: abrir e já poder digitar é
465
- // o ponto do padrão. Sem busca, segura o foco no gatilho (nada pra digitar).
466
- onOpenAutoFocus={(e) => {
467
- if (!canSearch) e.preventDefault()
468
- }}
466
+ /*
467
+ * O foco ao abrir é SEMPRE nosso o Radix nunca mexe.
468
+ *
469
+ * Deixá-lo agir com busca parecia certo (ele focaria o primeiro elemento
470
+ * focável, que é a caixa), mas ele roda DEPOIS do nosso efeito e movia o foco
471
+ * pro contêiner do conteúdo: a busca ganhava o cursor e o perdia em seguida,
472
+ * que é exatamente o "ganha foco e some" relatado. Com dois donos do foco, o
473
+ * último a escrever vence — e quem é o último depende da ordem de montagem.
474
+ */
475
+ onOpenAutoFocus={(e) => e.preventDefault()}
469
476
  onCloseAutoFocus={(e) => e.preventDefault()}
470
477
  onInteractOutside={(e) => {
471
478
  const target = e.target as HTMLElement