Skip to content

Components pt BR

Edgar Mesquita edited this page Aug 15, 2026 · 10 revisions

Componentes

🌐 Esta página em: English · Português

A biblioteca de componentes (eQuantic.UI.Components) é write-once: cada componente é autorado uma vez em C# contra o vocabulário abstrato do eQuantic.UI.Primitives, e realizado por alvo, DOM + CSS no web, pixels de GPU pelo Photon no nativo. Não são "duas APIs parecidas", é literalmente a mesma classe. A arquitetura (camadas, realizadores, paridade de hidratação) está documentada em Componentes write-once.

O modelo de componente

Tipo Descrição
StatelessComponent Depende só das propriedades dele. Sobrescreva Build(ComponentContext) para devolver a árvore.
StatefulComponent Guarda estado interno persistente; o SetState dispara uma reconstrução.

O ComponentContext é a porta sem modos por onde entra tudo que depende do alvo: o tema, a Densidade, a medição de texto (MeasureText/MonoAdvance) e os serviços de capacidade tomados por construtor (IThemeController, ITextClipboard, capacidades de dispositivo). Os componentes autoram tokens, nunca cores resolvidas, então uma árvore construída realiza em claro ou escuro.

Os eventos são propriedades tipadas (OnPressed, OnChanged, OnSelectionChanged, …) carregando delegates C#; o mesmo handler roda nos dois alvos.

A camada web de baixo nível (HtmlElement, ClassName, DynamicElement) continua existindo para as saídas de emergência só-web. Veja Estilo.

Catálogo

Ações e entradas: Button, IconButton, TextInput, SearchField, Checkbox, Switch, RadioGroup, Select, Slider, Stepper, SegmentedControl.

Superfícies e exibição: Card, Divider, Badge, Chip, Avatar, Banner, ProgressBar, EmptyState, Skeleton.

Navegação: Tabs, AppBar, BottomNavigation, NavigationRail, Breadcrumb, Pagination, PageIndicator, Menu, Drawer. A barra e a rail são os MESMOS destinos para duas larguras de janela: entregue uma lista de NavItem às duas dentro de um AdaptiveNode e o shell acompanha a janela, barra embaixo no telefone e rail na borda inicial no tablet, sem nenhum listener e sem um segundo estado. A barra aceita de 3 a 5 destinos, a rail de 3 a 7 (ela é mais alta do que a barra é larga); passando disso a resposta é um Drawer nas duas.

Desde 0.2.0-preview.29

A barra e o rail compartilham o MODELO de destino, não as métricas — o handoff dá métricas próprias a cada um (B4: pílula 56×26, glifo Md 24, rótulo 11/700; C16: pílula 52×30 numa célula 80×56, Dense 20, um Caption 12 que vai a 700 quando selecionado, e uma borda de 1dp na aresta final). O rail tinha sido construído a partir da barra, então era a barra de lado, e três shells desenhavam a borda que ele agora pinta sozinho.

Desde 0.2.0-preview.31

Os destinos do rail ficam onde o Alignment disser (o topo do Material por padrão, o centro quando há um header). Ele recebe o MainAlign do próprio vocabulário em vez de um enum com cara de rail, e esperava por uma fatia do transpilador, não por uma de design: um componente que repassa a propriedade enum dele para um slot do vocabulário precisava que essa propriedade chegasse ao gêmeo como UNIÃO e não como string pelada.

Desde 0.2.0-preview.31

O ListDetail é a terceira forma, e o primeiro componente do catálogo que é ele próprio ADAPTATIVO: uma lista ao lado de um detalhe numa janela larga, um painel de cada vez num telefone.

Desde 0.2.0-preview.31

O app é dono dos DADOS: um nó de lista, e um nó de detalhe quando algo é escolhido. O componente é dono da REGRA, que é a parte que todo app ia escrever de novo, e ia escrever duas vezes, porque as duas larguras parecem duas telas diferentes e não são:

new ListDetail(
    list: Inbox.List(_selected, i => SetState(() => _selected = i)),
    detail: _selected is { } chosen ? Inbox.Detail(chosen) : null,
    onBack: () => SetState(() => _selected = null))
{
    ListTitle = "Inbox",
    Title = _selected is { } open ? Inbox.Items[open].Title : null,
    Placeholder = EmptyState(Icons.Mail, "Escolha uma mensagem"),
}

Um campo responde pelas duas formas. Num telefone, Detail nulo é um LUGAR: a lista é a tela, e o voltar desescolhe; acima do TwoPaneFrom (840dp, a classe Expanded da spec) é apenas uma ausência, preenchida pelo Placeholder. A afordância de voltar é desenhada SÓ no painel compacto: na largura, os dois painéis estão visíveis, e um controle que te devolve para algo que você já vê é exatamente como um layout adaptativo passa a parecer quebrado. O dotnet new equantic-native --shell list-detail escafolda exatamente a chamada acima.

Cercas honestas, as duas sobre a troca de painel no compacto, que é uma navegação e não só um re-layout: o foco do teclado fica onde estava em vez de ir para o painel que chegou (o vocabulário ainda não tem um "foque esta subárvore" neutro de alvo), e o gesto de voltar da plataforma não está ligado ao OnBack (uma questão do host, por shell).

Overlays: Dialog, Toast, BottomSheet (arrastar para dispensar), Popover, Tooltip (revelado por hover, só ponteiro, zero JS).

Listas e dados: List/ListItem, ListView (com reciclagem, abaixo), Accordion, Table, DataTable, Spreadsheet (abaixo).

Uma linha tem 52 / 68 / 88 de altura para uma, duas e três linhas (o SubtitleLines = 2 pede a terceira, que existe para um subtítulo de configuração poder ocupar uma segunda frase), e a List recua cada divisor até onde a linha DE CIMA começa o texto dela: 16 + leading + gap, para que o fio nunca corte um ícone. Ela deriva esse número do slot: um Icon carrega o tamanho dele e um Avatar o degrau dele, e só um slot que o app construiu por conta própria precisa declarar LeadingWidth.

Desde 0.2.0-preview.31

Uma cerca, nomeada porque é lacuna e não preferência: uma lista SELECIONÁVEL ainda é uma sequência de paradas de Tab individuais, não uma parada única com ↑/↓ e Espaço. Por isso o ListItem.Selected declara aria-current="page" (destino de navegação) e não aria-selected num listbox: a parada que caminha pertence ao contêiner, e até a List compô-la, uma linha option sairia da ordem de Tab sem nada para recolocá-la.

Interação por toque: PullToRefresh, SwipeableRow.

Código: CodeBlock (só leitura) e CodeEditor (cursor, seleção, teclado). O modelo completo e as duas superfícies estão documentados na página do Editor de código.

Cada componente vem com pinos do realizador web, imagens golden nativas (claro + escuro), fixtures transpiladas pinadas executadas no vitest, e o showroom vivo (/ e /shared no DefaultUIDashboard: SSR + identidade de hidratação + interação verificados de ponta a ponta pela suíte do Playwright). Os sistemas que vêm junto com a biblioteca: o sistema de movimento por transição de estado (entrada/saída de Presence), o pipeline de ponteiro (hover, arrastar para dispensar com folga/cancelamento/deslize), o compositor de rolagem (fixação Sticky de verdade), os overlays ancorados, e a gestão de foco modal (abaixo).

O contrato de interação

O contrato de interação do design system (spec §10) é implementado pelos realizadores, então cada componente o herda em vez de improvisar por conta. O que segue é o comportamento publicado.

O hover é derivado, e nunca dispara no toque

Desde 0.2.0-preview.24

Não existe um sexto slot de cor: o preenchimento de hover de um controle preenchido é derivado (o ponto médio de canal de BasePressed, exposto como VariantColors.Hover), e as variantes discretas (Outline, Ghost, IconButton Standard) fazem hover em SurfaceSubtle, o preenchimento que o estado pressionado delas já usa. O Button e o IconButton autoram isso; no web, toda regra :hover que o motor atômico emite vive atrás de @media (hover: hover), então o hover grudento de toque de um browser não acha regra nenhuma, e no Photon um movimento marcado como toque pula o hover inteiramente. O pressionado sempre vence o hover, e a entrada/saída de hover pega o mesmo degrau de 100 ms do feedback de pressionado.

Controles de seleção compostos são UMA parada de Tab

Desde 0.2.0-preview.24

O RadioGroup e o SegmentedControl seguem o contrato nativo do <input type=radio>: o grupo é um único role="radiogroup" focável carregando o nome do grupo, as setas movem a seleção e dão a volta nas pontas (Baixo é próximo num radiogroup: ordem de leitura, não valor de slider), e cada opção é um role="radio" declarando aria-checked, fora da ordem de tabulação, ainda pressionável por ponteiro. O vocabulário carrega isso como PressableRole.Radio, e a forma foi adotada pelos compostos desde então: os itens do Tabs são role="tab" declarando aria-selected (uma aba é escolhida, não marcada) sob a parada única da tira, e as linhas de Menu/Select são menuitem/option (abaixo).

Superfícies modais tomam o teclado, e devolvem

Desde 0.2.0-preview.24

Um Overlay modal e aberto (que é sobre o que o Dialog, o Drawer e o BottomSheet são construídos) é um diálogo no web: role="dialog", aria-modal="true" (a página atrás fica inerte para tecnologia assistiva), e tabindex="-1" para que a própria camada seja o fallback de foco inicial (uma folha sem nada focável foca a folha, conforme a spec). Enquanto está aberta, Tab e Shift+Tab circulam dentro dela e uma guarda de focusin puxa de volta qualquer coisa que escape por outra rota; quando ela fecha (removida, fechada mantendo a montagem pelo Overlay.Motion, ou no meio da animação de saída) o foco volta ao elemento que a abriu, em todo caminho de fechamento. Diálogos empilham em último a entrar, primeiro a sair. O Toast nunca prende (ele é a camada não modal), e o Popover deliberadamente não é preso: um popover é uma superfície não modal, então o Tab sai dele.

Desde 0.2.0-preview.29

A camada também diz o NOME dela (Overlay.Label, já que o Dialog passa o título como aria-label), uma confirmação destrutiva é um role="alertdialog" anunciado assertivamente (Overlay.Alert), e o foco inicial prefere a ação SEGURA: o Dialog marca o botão Ghost dele (Button.InitialFocus), então Enter apertado por reflexo cancela em vez de destruir.

Os campos dizem à tecnologia assistiva o nome deles, e o erro

Desde 0.2.0-preview.24

O TextInput passa o rótulo dele à entrada como nome acessível, a legenda de ajuda/erro como descrição acessível (associada por aria-describedby, anunciada educadamente quando ela vira erro), e a condição de erro como aria-invalid. O que um leitor de tela ouve é exatamente a string que a pessoa que enxerga lê. O SearchField e a barra de busca do editor de código promovem o placeholder deles a um nome de verdade: um placeholder some sob o texto, então ele nunca substitui um nome.

O estado de um check é um atributo, nunca o nome

Desde 0.2.0-preview.29

O Checkbox é um role="checkbox" e o Switch um role="switch", cada um declarando o estado como aria-checked, com "mixed" para o checkbox indeterminado, e só nele, regra do próprio ARIA. O NOME acessível é para que o controle serve, e nunca muda quando o estado muda (um nome que alterna lê como outro controle aparecendo). No nativo a árvore de semântica carrega a mesma resposta (SemanticCheck Off/On/Mixed), que o macOS mapeia no valor numérico do AXCheckBox, e o VoiceOver anuncia o estado na língua do usuário, e foi por isso que as antigas strings "Marcado"/"Ativado" saíram do SDK por inteiro.

Uma navegação diz ONDE VOCÊ ESTÁ

Desde 0.2.0-preview.31

Uma barra anunciava os três destinos dela de forma idêntica: "Atividade, botão", "Busca, botão", "Perfil, botão", porque o ativo era uma pílula e um tom de cor, que não é absolutamente nada para um leitor de tela. É a única informação de estado que uma navegação existe para carregar, e nenhuma das três grafias que o vocabulário tinha conseguia dizê-la: um destino não é PRESSIONADO (ele não alterna), não é MARCADO (não se está escolhendo nada) e não é SELECIONADO (uma aba troca um painel; um destino muda ONDE você está).

Então o PressableRole.Destination entrou no conjunto, e o Link.Current ao lado dele para o idioma de navegação do próprio web. O BottomNavigation, o NavigationRail e um ListItem marcado como Selected declaram isso por você; uma barra lateral feita à mão com Links diz Current = true naquele que casa com a rota.

  • Web: aria-current="page", e só no atual. O aria-current="false" é legal, inútil e lido em voz alta, então nove links anunciando "não atual" é pior que silêncio.
  • Nativo: a árvore de semântica carrega o bit, e cada ponte o reporta com a palavra mais próxima que a plataforma dela tem: o trait Selected no iOS, o accessibilitySelected no macOS e o Selected do node info no Android.
  • Ordem de Tab: um destino MANTÉM a parada de Tab dele, ao contrário de uma aba ou de um radio. Uma navegação é uma lista de links, não um composto com uma entrada só, e quem chega na barra pelo teclado espera percorrê-la.

Tooltips alcançam o teclado, e o Escape os dispensa

Desde 0.2.0-preview.29

O foco de teclado revela um Tooltip exatamente como o hover (:has(:focus-visible), focus-visible e não focus-within, porque tooltip abrindo a cada clique de mouse atrapalha), a pílula é um role="tooltip" de verdade que o gatilho aponta com aria-describedby (o leitor de tela ouve a dica depois do nome do controle sem nada estar revelado), e o Escape esconde um tooltip revelado SEM mover ponteiro nem foco, que é a cláusula de dispensa do WCAG 1.4.13. Sair do gatilho limpa a supressão, então a próxima visita revela de novo: Esc é um "agora não", nunca um "nunca mais".

Um gatilho diz se está aberto

Desde 0.2.0-preview.29

Um chevron rodado é pintura, e pintura não diz nada à tecnologia assistiva. O Pressable.Expanded declara a divulgação: aria-expanded no web, o bit expandido do nó de semântica nativo (o "expanded"/"collapsed" do VoiceOver no macOS). E null traça a mesma linha do Selected: "não controla divulgação nenhuma" é resposta diferente de "controla uma, fechada agora". Os cabeçalhos do Accordion, o campo do Select e o gatilho do Menu declaram todos.

Menus e selects falam o padrão inteiro deles

Desde 0.2.0-preview.29

Um Menu aberto é um role="menu" de linhas menuitem (comandos desabilitados incluídos, anunciados esmaecidos em vez de escondidos), com o gatilho carregando aria-haspopup/aria-controls. Um Select aberto é o padrão combobox completo: o CAMPO é o role="combobox", o painel um role="listbox" de linhas option numeradas declarando aria-selected. E nos dois, o destaque de teclado que as setas movem, que de outro modo seria só pintura, é declarado como aria-activedescendant no gatilho, então um leitor de tela seguindo o teclado ouve cada passo. O foco em si nunca sai do gatilho, que é o arranjo do próprio padrão combobox.

As strings embutidas vivem atrás de uma única costura

Desde 0.2.0-preview.24

Nenhum componente fixa uma string voltada ao usuário: as afordâncias de dispensar, o placeholder de busca e os rótulos da barra de busca leem todos o SdkStrings, a costura respaldada por resx que o trilho de localização preenche por cultura. O conjunto ENCOLHEU no preview.29: os antigos anúncios de alternância ("Marcado", "Ativado") saíram quando o estado de um check virou aria-checked, porque a plataforma anuncia o estado na língua do usuário, o que nenhum resx nosso alcançaria.

ListView: a lista com reciclagem

Uma lista que só CONSTRÓI as linhas que você consegue ver: dê a ela uma contagem, uma extensão fixa por item e um construtor por índice, e ela materializa a janela visível mais uma margem de sobrevarredura. O resto da lista são dois espaçadores, então o layout (e a barra de rolagem) veem a altura de conteúdo verdadeira enquanto dez mil linhas custam o que uma tela cheia custa. Write-once: o mesmo C# roda no Photon e no web.

new ListView(count: 10_000, itemExtent: 44, itemBuilder: i => RowFor(i))
{
    Width = SizeValue.Fill,
    Height = SizeValue.Fill,   // a lista é uma JANELA, dê uma a ela
}

A janela converge em vez de bloquear: o primeiro frame constrói contra um palpite de viewport, o frame realizado reporta o viewport e o deslocamento verdadeiros pelos canais de saída do ScrollView (OnScrolled / OnViewportChanged, ligados nos dois alvos), e o frame seguinte constrói a janela corrigida. Os pixels rolam sem reconstrução; o estado só muda quando uma linha cruza a margem de sobrevarredura. Escopo (v1): listas verticais com ItemExtent fixo; extensões variáveis não estão incluídas.

Regras de layout que fazem janelas funcionarem em todo lugar (invariantes do SDK, não tarefas do app):

  • O quadro #app do shell tem altura EXATA de viewport (height: 100dvh, grid, filhos min-height: 0): uma página de APP (raiz Height = Fill) resolve para exatamente um viewport e rola internamente; uma página de DOCUMENTO (raiz de altura automática) transborda o quadro e o body rola como um documento sempre rola.
  • Um ScrollView sem Height explícito assume height: 100% no web, porque uma view de rolagem É a janela que o pai dá a ela, nunca o conteúdo dela (paridade nativa: o realizador entrega a ela os limites de layout e corta sempre).
  • A hidratação adota os marcadores data-eq-* do framework a partir da árvore do cliente, porque o SSR não pode conhecer identidades do lado do cliente, e as varreduras da passada posterior acham os elementos delas por eles.

Spreadsheet: a grade editável

Uma planilha com usabilidade de Excel, construída sobre o mesmo princípio do Editor de código: o controller carrega tudo, os pixels são aritmética. Um modelo C# + um componente C# renderizam no Photon e no DOM; o build do browser transpila exatamente as mesmas fontes.

O modelo (eQuantic.UI.Primitives/Sheet, compartilhado verbatim):

  • SheetDocument: células esparsas (vazia é ausente), tamanhos por linha/coluna, extensão lógica. O SetCell/SetRowHeight/SetColWidth é o caminho de carga/preview, deliberadamente não desfazível.
  • CellRef/SheetRange: endereçamento A1; seleção âncora/foco em 2D. As células indexam por um int empacotado (linha × 16384 + coluna) porque um record struct é uma chave inútil num Map do JS.
  • SheetController: a semântica do Excel como métodos. Setas/Shift estendem, Ctrl+seta salta para a borda dos dados, Tab/Enter caminham (e dão a volta) dentro de uma seleção enquanto o retângulo fica parado (célula ativa ≠ foco da seleção, deliberadamente), seleções pela faixa de cabeçalho, inserir/excluir/redimensionar linha/coluna com desfazer/refazer esparso baseado em inverso, copiar/colar TSV falando a citação do Excel, e o rascunho de edição na célula: BeginEdit/TypeIntoDraft/CommitEdit vivem no controller, então os dois alvos editam de forma idêntica e um rascunho comitado desfaz como um passo só.
  • SheetKeymap: O teclado ÚNICO. Digitar substitui (a entrada rápida do Excel), F2 edita no lugar, Enter/Tab comitam e avançam, Escape descarta, as setas comitam e movem, ⌘A/⌘Z. O host nativo e o rebaixamento web delegam os dois aqui; o dialeto não pode bifurcar.

O componente (eQuantic.UI.Components/Spreadsheet.cs):

  • Cabeçalhos de coluna fixos no topo; cabeçalhos de linha rolam COM as linhas; a janela visível de células é embrulhada numa SheetSurface que vive DENTRO da rolagem, então as marcas transladam com o conteúdo e um clique numa grade rolada seleciona a célula que de fato está sob o ponteiro, por construção.
  • As linhas virtualizam do jeito do ListView (janela + espaçadores); a faixa de seleção, o anel da célula ativa e o rascunho+cursor de edição pintam SOBRE as células, no componente, então os dois alvos são visualmente idênticos porque não há nada do lado do realizador para divergir.
  • Seleção no núcleo do Excel: cliques em cabeçalho selecionam a linha/coluna inteira deles (o canto seleciona tudo, e os cabeçalhos sombreiam quando a faixa da seleção os cruza); shift+clique estica a partir da âncora sem mover a célula ativa; um arrasto simples varre um intervalo nos dois alvos.
  • A alça de preenchimento, o quadradinho do Excel no canto inferior direito da seleção, arrasta um preview pelo eixo DOMINANTE e despeja o bloco de origem por ele ao soltar, ladrilhando com volta em fase nas quatro direções, como UM passo de desfazer. ⌘D/⌘R despejam para baixo/direita pelo mesmo motor no keymap compartilhado. A semântica do gesto (BeginFill/UpdateFill/CommitFill, Fill, FillDown, FillRight, SelectTo) vive no controller, então os dois alvos preenchem de forma idêntica.
  • Redimensionar por arrasto: a aresta final de cada cabeçalho carrega uma pega Draggable invisível de 6dp (Stack + Positioned, Follows = false). Os movimentos fazem preview direto no documento (SetColWidth/SetRowHeight); a soltura rebobina o preview e assenta o gesto inteiro como UM Controller.Resize desfazível. Os pisos (MinColWidth 24, MinRowHeight 14) mantêm uma lasca agarrável, limitados no componente, não no Min/Max do nó (esses reconstroem no meio do arrasto). Dentro do ScrollView a pega vence a rolagem: uma superfície de arrasto é o gesto mais específico (regra do host, PressDown).
  • Ligação da interação: o host nativo roteia cliques/seleção por arrasto/teclas/área de transferência TSV/texto seguro para IME ao controller; o rebaixamento web (lowerSheetSurface) é uma div focável com role="grid" e user-select: none (um arrasto de seleção pinta a faixa, não a varredura azul do browser), keydown → o SheetKeymap transpilado, duplo clique edita, copiar/recortar/colar pegam carona nos eventos de área de transferência do próprio browser, em TSV.
  • Cursores de ponteiro são vocabulário (BoxStyle.Cursor, o espelho do cursor do CSS): o web emite a declaração, o Photon registra uma CursorRegion que o CursorAt do host responde do topo para baixo, e o shell do macOS mapeia para NSCursor. As pegas de redimensionamento dizem col-resize/row-resize e a alça de preenchimento diz crosshair, nos dois alvos, a partir do mesmo código de componente.

Escopo (v1): sem motor de fórmulas, sem formatação por célula, sem mesclas, sem painéis congelados. A virtualização é só vertical (as colunas materializam), e não há rolagem 2D, então colunas além da largura do painel são cortadas. As suítes de controller, edição e componente dirigem os dois eixos de redimensionamento pelo host; o gêmeo transpilado roda os mesmos movimentos sob o vitest, mais specs de superfície web com eventos DOM de verdade.


Um selo de canto, e o contrato que o Positioned é

O Positioned é um contrato com o PAI: como um peso de flex, ele não significa nada em nenhum outro lugar. Ele tem que ser filho de um Stack, e até a 0.2.0-preview.18 isso significava filho direto: um componente cujo Build devolvia um não era reconhecido, degradava para o filho dele, e entrava no fluxo. Um botão de canto renderizava acima da laje a que ele pertencia, colado na aresta esquerda, nos dois alvos e em silêncio.

Os dois realizadores agora resolvem através do componente, então isto funciona:

Stack(children: [
    CodeSlab(code),
    CopyAction(code),      // o Build dele devolve Positioned(top: 0, end: 0)
])

Um filho posicionado continua não participando do tamanho de CONTEÚDO do stack, ou um selo de canto faria crescer a laje sobre a qual ele se apoia.

Saber quando algo está na tela

Desde 0.2.0-preview.20

InView(Heading(section.Title), visible => SetState(() => _active = visible ? section.Id : _active))

A pergunta que um índice de conteúdo de fato faz é para qual título o leitor está olhando, e o único jeito de responder isso era a posição de rolagem da página, que vive num ScrollView, então o artigo tinha que ser embrulhado num. Isso muda o modelo de rolagem da página inteira: o cabeçalho sticky para de grudar na janela, a restauração de rolagem do próprio browser para de funcionar, e a barra de endereço para de se esconder num celular.

Um deslocamento cru também não teria ajudado muito. Ele é um número nas coordenadas da página, e responder "este título já passou dele" precisa da posição de cada título, uma medição que um componente write-once não tem por que fazer, e não consegue fazer do mesmo jeito nos dois alvos. Presença é a pergunta, então presença é o que isto reporta.

  • Dispara só nas transições: verdadeiro quando o filho entra em vista, falso quando ele sai. Nunca uma vez por frame enquanto ele fica ali.
  • O Threshold (0–1) pede mais que uma lasca: um título metade fora do topo não é aquele em que o leitor está.
  • Não visível é o estado assumido, então um componente que monta com o filho fora da tela não ouve nada. Trinta títulos não anunciam trinta ausências.
  • Transparente para o layout: ele não ocupa espaço e não desenha nada.

No web ele é um IntersectionObserver: sem listener de rolagem, então sem trabalho nos frames em que nada cruza, e a medição acontece fora da thread principal. O Photon compara os limites com a superfície uma vez por frame, num percurso que ele já está fazendo.

Os dois cortam. Uma linha rolada para fora de um ScrollView, ou de um Box que corta, não está na tela. No Photon o que conta como visível se estreita a cada corte, do mesmo jeito que pintura e entrada já se estreitam; no web o observador corta contra todo ancestral sozinho. Então uma lista em janela só carrega o que o leitor consegue ver, que é o trabalho que isto torna barato. Corte estreitado no Photon desde a 0.2.0-preview.21.

Duas coisas tinham que ser verdade no web antes de qualquer disso reportar algo, e as duas eram falsas quando isto foi publicado (corrigido na 0.2.0-preview.21):

  • O marcador vai no FILHO, nunca no embrulho transparente para o layout. O display: contents não gera caixa, então um observador apontado ao embrulho fica olhando um retângulo 0×0 na origem. Ele reporta fielmente, sobre um lugar onde o título nunca está.
  • Os observadores anexam depois que a passada foi ESCRITA. Uma passada produz uma árvore; o gerente de render a escreve assim que a passada retorna. Comitar dentro da passada procurava elementos que ainda não existiam, não achava nenhum, e limpava as declarações, então uma página recém-hidratada carregava todo nó marcado sem observador, e só um re-render posterior os anexava. Numa página onde mais nada muda, isso é nunca.

Onde um link deixa o leitor

Desde 0.2.0-preview.23

Link("/docs/Storage", Text("Storage")) { KeepsPosition = true }

Uma navegação começa a nova página no topo dela, o que está certo para um link que te leva a outro lugar e errado para um que troca um painel ao lado de uma lista em que você estava na metade. Uma barra lateral de documentação é o caso que dá nome a isso: o shell é preservado e só umas duas centenas de nós são remendados, e aí tudo salta para o topo, o que lê, para quem está olhando, exatamente como se a página tivesse recarregado.

Chrome que mantém a PRÓPRIA rolagem nunca teve o problema e não precisa disto. Isto é para os layouts em que a lista e o conteúdo dividem uma rolagem.

Mostrar um overlay que nada abriu

Desde 0.2.0-preview.16

InFlow(Dialog("Delete this?", "It cannot be undone.", [new DialogAction("Delete")]))
InFlow(Drawer(content))          // sem precisar de `open: true`, veja abaixo
InFlow(BottomSheet(content))
InFlow(Popover(trigger, panel))

Um diálogo, uma gaveta, uma folha e um popover só existem depois que algo os abre, e quando isso acontece eles tomam o viewport inteiro: um véu sobre a página, a superfície centrada ou fixada à frente dela, um atalho de Escape, uma animação de entrada. Uma página que quer MOSTRAR um deles (documentação, uma revisão de design, uma suíte de regressão visual) quer a superfície e nada disso. Cinco componentes renderizavam em branco.

O InFlow declara a intenção; cada overlay a responde construindo o painel dele e nada mais, porque o componente é a única coisa que sabe qual é a superfície dele.

No fluxo não há aberto ou fechado: o painel está posicionado, portanto ele está lá. O Open governa se a CAMADA existe, e não há camada. Uma gaveta se aperta ao conteúdo dela em vez de preencher o viewport (altura cheia pertence à aresta em que ela está fixada, e ela não tem aresta); um popover desenha o painel dele sem o gatilho; um PullToRefresh descansa com o indicador descoberto, já que não há o que puxar.

Não é um modo de preview: a superfície é real, com ações reais. O que é descartado é a apresentação. E a intenção termina no nó: um diálogo aberto de verdade ao lado de um previsualizado continua tomando o viewport.

Mostrar um estado que não aconteceu

Desde 0.2.0-preview.15

Simulated(SimulatedState.Hovered, Button("Save"))
Simulated(SimulatedState.Pressed | SimulatedState.Focused, IconButton(Icons.Check))

Um botão pressionado não pode ser entregue a um construtor: pressionado é algo que o host observa, não algo que uma árvore declara. Então uma galeria de documentação, uma revisão de design e uma suíte de regressão visual só conseguiam mostrar o estado de repouso de um controle, e os três estados que um leitor mais quer comparar eram os três que página nenhuma conseguia desenhar.

Um nó em vez de uma propriedade em todo componente interativo: ele compõe, cobre os componentes que ainda não existem, e funciona para um controle aninhado três níveis dentro de um cartão sendo previsualizado. Previews aninhados se COMBINAM: um cartão em hover segurando um botão pressionado são dois nós, e o interno não desliga o hover do externo.

Ele muda só o que é DESENHADO. Nada é invocado, nenhum estado é entrado, nenhum handler roda, e o estado termina no nó: o controle ao lado se comporta normalmente.

No web, :hover e :focus-visible não podem ser forçados pelo CSS (nada pode, que é o ponto de uma pseudo-classe), então os diffs que essas regras carregam são dobrados na regra base. As mesmas declarações, então o preview é a coisa real em vez de uma aproximação dela. Um toque simulado reusa o próprio corpo de seletor que um toque real usa, que é o que impede os dois de divergirem.

Veja também

Clone this wiki locally