HextaUI

BetaVercel UI SDK 0.1.0-beta.1 üzerine kurulu. API’si değişebilir.

Token input

Vercel UI SDK üzerinde, yazılan metni kişi, dosya ve komut token’larıyla birleştiren, @ ve / önerileri sunan bir metin alanı.

Bağımlılıklar
  • @tabler/icons-react
  • @ui-sdk/core
  • @ui-sdk/react
Thanks ​Inès Moreau mention​, can you pair with
"use client"

import { IconAt } from "@tabler/icons-react"

import {
  TokenInput,
  TokenInputEditor,
  TokenInputToken,
  TokenInputTokenRemove,
  TriggerMenu,
  TriggerMenuContent,
  TriggerMenuEmpty,
  TriggerMenuItem,
  TriggerMenuList,
  type TokenInputValue,
  type TriggerMenuDataItem,
} from "@/components/ui/ui-sdk-token-input"

const people = [
  { value: "hollis", label: "Hollis Grant", keywords: ["design"] },
  { value: "ines", label: "Inès Moreau", keywords: ["engineering"] },
  { value: "kofi", label: "Kofi Mensah", keywords: ["engineering"] },
  { value: "wren", label: "Wren Takahashi", keywords: ["product"] },
  { value: "tomasz", label: "Tomasz Wójcik", keywords: ["support"] },
]

const draft: TokenInputValue = [
  "Thanks ",
  { type: "mention", value: "ines", label: "Inès Moreau" },
  ", can you pair with ",
]

function UiSdkTokenInputDemo() {
  return (
    <div className="w-full max-w-md">
      <TokenInput
        defaultValue={draft}
        renderToken={(token) => (
          <TokenInputToken>
            <IconAt aria-hidden="true" />
            {token.label}
            <TokenInputTokenRemove />
          </TokenInputToken>
        )}
      >
        <TokenInputEditor
          aria-label="Message"
          placeholder="Type @ to mention someone"
        />
        <TriggerMenu triggers={[{ char: "@", type: "mention", items: people }]}>
          <TriggerMenuContent>
            <TriggerMenuEmpty>No one matches.</TriggerMenuEmpty>
            <TriggerMenuList>
              {(state) =>
                (state.items as TriggerMenuDataItem[]).map((item) => (
                  <TriggerMenuItem
                    key={item.value}
                    value={item.value}
                    label={item.label}
                  >
                    <span
                      aria-hidden="true"
                      className="flex size-5 shrink-0 items-center justify-center rounded-full bg-muted text-xs font-medium text-muted-foreground"
                    >
                      {item.label.charAt(0)}
                    </span>
                    {item.label}
                  </TriggerMenuItem>
                ))
              }
            </TriggerMenuList>
          </TriggerMenuContent>
        </TriggerMenu>
      </TokenInput>
    </div>
  )
}

export { UiSdkTokenInputDemo }
pnpm dlx shadcn@latest add https://hextaui.com/r/ui-sdk-token-input.json

Bileşeni, HextaUI tema token'larını ve bileşenin bağımlı olduğu tüm HextaUI bileşenlerini ekler.

UI SDK, ui-sdk adlı bir cascade katmanında kendi CSS’ini biraz ekler. Sınıflarınızın kazanması için bu katmanı genel stil dosyanızın en üstünde, Tailwind import’unun üzerinde tanımlayın.

@layer ui-sdk;

@import "tailwindcss";
import {
  TokenInput,
  TokenInputEditor,
  type TokenInputValue,
} from "@/components/ui/ui-sdk-token-input"
const draft: TokenInputValue = [
  "Explain ",
  { type: "file", value: "src/routes.ts", label: "routes.ts" },
]

<TokenInput defaultValue={draft}>
  <TokenInputEditor aria-label="Prompt" />
</TokenInput>

Değer, metin ve token’lardan oluşan bir dizidir. Her token’ın bir type, uygulamanız için bir value ve insanların okuduğu bir label değeri vardır. Editör içeriğini, seçimi ve geri almayı kendisi yönetir; çizimi ona bırakın ve bir token’ın görünümünü değiştirmek için renderToken verin. Verilmezse her token etiketini ve bir kaldırma düğmesini gösterir.

TokenInput
├── TokenInputEditor
└── TriggerMenu
    ├── TriggerMenuHint
    └── TriggerMenuContent
        ├── TriggerMenuEmpty
        └── TriggerMenuList
            └── TriggerMenuItem
                └── TriggerMenuItemIcon

renderToken
└── TokenInputToken
    └── TokenInputTokenRemove

Input içindeki bir TriggerMenu, biri @ ya da / gibi bir tetikleyici yazdığında token önerir. Token’ları kodunuz sağlıyorsa onu kullanmayın.

Dosyalar için @, başta skill’ler için / yazın. Bir dosya yüklenirken beklemede kalır; gönderince her skill updateToken() ile önce çalışıyor, sonra tamamlandı olarak işaretlenir. Enter gönderir, Shift+Enter satır ekler.

"use client"

import * as React from "react"
import {
  IconArrowUp,
  IconCheck,
  IconFile,
  IconLoader2,
  IconTerminal2,
} from "@tabler/icons-react"

import { Button } from "@/components/ui/button"
import {
  createTokenInputController,
  TokenInput,
  TokenInputEditor,
  TokenInputToken,
  TokenInputTokenRemove,
  toLabel,
  TriggerMenu,
  TriggerMenuContent,
  TriggerMenuEmpty,
  TriggerMenuHint,
  TriggerMenuItem,
  TriggerMenuItemIcon,
  TriggerMenuList,
  type Token,
  type TokenInputValue,
  type TriggerMenuDataItem,
} from "@/components/ui/ui-sdk-token-input"

const files = [
  { value: "src/app.tsx", label: "app.tsx", keywords: ["src"] },
  { value: "src/routes.ts", label: "routes.ts", keywords: ["src"] },
  { value: "src/styles.css", label: "styles.css", keywords: ["src"] },
  { value: "README.md", label: "README.md" },
  { value: "package.json", label: "package.json" },
]

const skills = [
  {
    value: "search",
    label: "search",
    arguments: [{ name: "query", hint: "<query>", required: true }],
  },
  { value: "review", label: "review" },
  { value: "summarize", label: "summarize" },
]

const loadFile = () => new Promise<void>((resolve) => setTimeout(resolve, 800))

function UiSdkTokenInputPrompt() {
  const [prompt] = React.useState(createTokenInputController)
  const [running, setRunning] = React.useState(false)
  const [sent, setSent] = React.useState<string | null>(null)
  const timer = React.useRef<ReturnType<typeof setTimeout> | undefined>(
    undefined
  )

  React.useEffect(() => () => clearTimeout(timer.current), [])

  function send(value: TokenInputValue) {
    if (running || !prompt.getLabel().trim()) return
    setRunning(true)
    const skillTokens = value.filter(
      (segment): segment is Token =>
        typeof segment !== "string" && segment.type === "skill"
    )
    for (const skill of skillTokens) {
      prompt.updateToken(skill.id!, { data: { ...skill.data, run: "running" } })
    }
    timer.current = setTimeout(() => {
      for (const skill of skillTokens) {
        prompt.updateToken(skill.id!, { data: { ...skill.data, run: "done" } })
      }
      setSent(toLabel(prompt.getValue()))
      prompt.clear()
      setRunning(false)
    }, 1200)
  }

  return (
    <div className="flex w-full max-w-md flex-col gap-2">
      <TokenInput
        controller={prompt}
        onSubmit={send}
        renderToken={(token) => (
          <TokenInputToken>
            {token.type === "skill" ? (
              <IconTerminal2 aria-hidden="true" />
            ) : (
              <IconFile aria-hidden="true" />
            )}
            {token.type === "skill" ? "/" + token.label : token.label}
            {token.data?.run === "running" ? (
              <IconLoader2
                aria-hidden="true"
                className="motion-safe:animate-spin"
              />
            ) : null}
            {token.data?.run === "done" ? (
              <IconCheck aria-hidden="true" className="text-success" />
            ) : null}
            <TokenInputTokenRemove />
          </TokenInputToken>
        )}
      >
        <div className="flex flex-col gap-2">
          <TokenInputEditor
            aria-label="Prompt"
            placeholder="Ask anything. Type @ for files or / for skills"
          />
          <Button
            type="button"
            size="icon-sm"
            className="self-end"
            aria-label={running ? "Sending" : "Send"}
            disabled={running}
            onClick={() => send(prompt.getValue())}
          >
            {running ? (
              <IconLoader2
                aria-hidden="true"
                className="motion-safe:animate-spin"
              />
            ) : (
              <IconArrowUp aria-hidden="true" />
            )}
          </Button>
        </div>
        <TriggerMenu
          triggers={[
            { char: "@", type: "file", items: files },
            { char: "/", boundary: "start", type: "skill", items: skills },
          ]}
          onSelect={(value, details) =>
            details.trigger === "@" ? loadFile() : undefined
          }
        >
          <TriggerMenuHint />
          <TriggerMenuContent>
            <TriggerMenuEmpty>No matches.</TriggerMenuEmpty>
            <TriggerMenuList>
              {(state) =>
                (state.items as TriggerMenuDataItem[]).map((item) => (
                  <TriggerMenuItem
                    key={item.value}
                    value={item.value}
                    label={item.label}
                  >
                    <TriggerMenuItemIcon>
                      {state.trigger === "/" ? (
                        <IconTerminal2 aria-hidden="true" />
                      ) : (
                        <IconFile aria-hidden="true" />
                      )}
                    </TriggerMenuItemIcon>
                    {item.label}
                    {item.value !== item.label ? (
                      <span className="ms-auto truncate font-mono text-xs text-muted-foreground">
                        {item.value}
                      </span>
                    ) : null}
                  </TriggerMenuItem>
                ))
              }
            </TriggerMenuList>
          </TriggerMenuContent>
        </TriggerMenu>
      </TokenInput>
      <p role="status" className="min-h-5 text-sm text-muted-foreground">
        {sent ? "Sent: " + sent : null}
      </p>
    </div>
  )
}

export { UiSdkTokenInputPrompt }

tokenDeletion, Backspace’in bir token’da ne yapacağını belirler. whole onu kaldırır, select önce seçer, revert onu yeniden metne çevirir.

"use client"

import * as React from "react"
import { IconFile } from "@tabler/icons-react"

import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import {
  TokenInput,
  TokenInputEditor,
  TokenInputToken,
  type TokenDeletion,
  type TokenInputValue,
} from "@/components/ui/ui-sdk-token-input"

const draft: TokenInputValue = [
  "Explain ",
  { type: "file", value: "src/routes.ts", label: "routes.ts" },
  " in two sentences.",
]

const modes: { value: TokenDeletion; label: string }[] = [
  { value: "whole", label: "Whole" },
  { value: "select", label: "Select" },
  { value: "revert", label: "Revert" },
]

function UiSdkTokenInputDeletion() {
  const [deletion, setDeletion] = React.useState<TokenDeletion>("whole")

  return (
    <div className="flex w-full max-w-md flex-col gap-3">
      <ToggleGroup
        aria-label="Backspace removes"
        value={[deletion]}
        onValueChange={(next) => {
          if (next[0]) setDeletion(next[0] as TokenDeletion)
        }}
        variant="outline"
        size="sm"
      >
        {modes.map((mode) => (
          <ToggleGroupItem key={mode.value} value={mode.value}>
            {mode.label}
          </ToggleGroupItem>
        ))}
      </ToggleGroup>
      <TokenInput
        key={deletion}
        tokenDeletion={deletion}
        defaultValue={draft}
        renderToken={(token) => (
          <TokenInputToken>
            <IconFile aria-hidden="true" />
            {token.label}
          </TokenInputToken>
        )}
      >
        <TokenInputEditor aria-label="Prompt" />
      </TokenInput>
    </div>
  )
}

export { UiSdkTokenInputDeletion }

createTokenInputController() ile oluşturulan bir controller, koddan token ekler, günceller ve kaldırır. Bir token’ı sonuçlanana kadar beklemede tutmak için resolve olarak bir promise verin.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import {
  createTokenInputController,
  TokenInput,
  TokenInputEditor,
  type Token,
  type TokenInputValue,
} from "@/components/ui/ui-sdk-token-input"

const draft: TokenInputValue = [
  "Compare ",
  { type: "file", value: "src/routes.ts", label: "routes.ts" },
  " with ",
]

const loadFile = () => new Promise<void>((resolve) => setTimeout(resolve, 800))

function UiSdkTokenInputController() {
  const [prompt] = React.useState(createTokenInputController)
  const firstToken = () =>
    prompt
      .getValue()
      .find((segment): segment is Token => typeof segment !== "string")

  return (
    <div className="flex w-full max-w-md flex-col gap-3">
      <div
        className="flex flex-wrap gap-2"
        onMouseDown={(event) => event.preventDefault()}
      >
        <Button
          variant="outline"
          size="sm"
          onClick={() =>
            prompt.insert(
              { type: "file", value: "package.json", label: "package.json" },
              { at: "end", resolve: loadFile() }
            )
          }
        >
          Attach file
        </Button>
        <Button
          variant="outline"
          size="sm"
          onClick={() => {
            const token = firstToken()
            if (token) prompt.updateToken(token.id!, { label: token.value })
          }}
        >
          Show path
        </Button>
        <Button
          variant="outline"
          size="sm"
          onClick={() => {
            const token = firstToken()
            if (token) prompt.removeToken(token.id!)
          }}
        >
          Remove first
        </Button>
        <Button variant="ghost" size="sm" onClick={() => prompt.undo()}>
          Undo
        </Button>
        <Button variant="ghost" size="sm" onClick={() => prompt.clear()}>
          Clear
        </Button>
      </div>
      <TokenInput controller={prompt} defaultValue={draft}>
        <TokenInputEditor aria-label="Prompt" />
      </TokenInput>
    </div>
  )
}

export { UiSdkTokenInputController }

Bir name ile input, her token’ı [@label](type:value) olarak yazarak metnini gönderir. required boş gönderimi engeller, sıfırlama defaultValue değerine döner.

"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import {
  TokenInput,
  TokenInputEditor,
  type TokenInputValue,
} from "@/components/ui/ui-sdk-token-input"

const draft: TokenInputValue = [
  "Explain ",
  { type: "file", value: "src/routes.ts", label: "routes.ts" },
  " in two sentences.",
]

function UiSdkTokenInputForm() {
  const [submitted, setSubmitted] = React.useState<string | null>(null)

  return (
    <form
      className="flex w-full max-w-md flex-col gap-3"
      onSubmit={(event) => {
        event.preventDefault()
        setSubmitted(String(new FormData(event.currentTarget).get("prompt")))
      }}
      onReset={() => setSubmitted(null)}
    >
      <TokenInput name="prompt" required defaultValue={draft}>
        <TokenInputEditor aria-label="Prompt" />
      </TokenInput>
      <div className="flex gap-2">
        <Button type="submit" size="sm">
          Submit
        </Button>
        <Button type="reset" variant="outline" size="sm">
          Reset
        </Button>
      </div>
      <p role="status" className="text-sm wrap-anywhere text-muted-foreground">
        {submitted ? (
          <>
            Submitted prompt=
            <span className="font-mono text-foreground">{submitted}</span>
          </>
        ) : (
          "Nothing submitted yet."
        )}
      </p>
    </form>
  )
}

export { UiSdkTokenInputForm }

submitShortcut="mod+enter" ile Enter satır ekler, Command ya da Control+Enter gönderir; Japonca ve Çince yazan pek çok kişi bunu tercih eder.

"use client"

import * as React from "react"

import { Kbd } from "@/components/ui/kbd"
import {
  TokenInput,
  TokenInputEditor,
  toLabel,
} from "@/components/ui/ui-sdk-token-input"

function UiSdkTokenInputSubmitShortcut() {
  const [sent, setSent] = React.useState<string | null>(null)

  return (
    <div className="flex w-full max-w-md flex-col gap-2">
      <TokenInput
        submitShortcut="mod+enter"
        onSubmit={(value) => setSent(toLabel(value))}
      >
        <TokenInputEditor
          aria-label="Message"
          placeholder="Write a message. Enter adds a line."
        />
      </TokenInput>
      <p
        role="status"
        className="flex flex-wrap items-center gap-1.5 text-sm wrap-anywhere text-muted-foreground"
      >
        {sent ? (
          "Sent: " + sent
        ) : (
          <>
            Send with <Kbd>⌘</Kbd>
            <Kbd>Enter</Kbd> or <Kbd>Ctrl</Kbd>
            <Kbd>Enter</Kbd>
          </>
        )}
      </p>
    </div>
  )
}

export { UiSdkTokenInputSubmitShortcut }

Codec token’ları metne ve geri çevirir; burada <@id> biçiminde. copy="label" ile kopyalama okunan metni yazar, geri yapıştırınca token’lar yine gelir.

"use client"

import * as React from "react"

import {
  fromText,
  TokenInput,
  TokenInputEditor,
  toText,
  type TokenCodec,
} from "@/components/ui/ui-sdk-token-input"

const chat: TokenCodec = {
  format: (token) => "<@" + token.value + ">",
  parse: (text) =>
    text.split(/(<@[\w.-]+>)/).map((part) => {
      const id = /^<@([\w.-]+)>$/.exec(part)?.[1]
      return id ? { type: "mention", value: id, label: "@" + id } : part
    }),
}

const source = "Ping <@hollis> and <@ines> about the launch"

function UiSdkTokenInputCodec() {
  const [text, setText] = React.useState(source)

  return (
    <div className="flex w-full max-w-md flex-col gap-2">
      <TokenInput
        codec={chat}
        copy="label"
        defaultValue={fromText(source, chat)}
        onValueChange={(value) => setText(toText(value, chat))}
      >
        <TokenInputEditor aria-label="Message" />
      </TokenInput>
      <p className="font-mono text-xs wrap-anywhere text-muted-foreground">
        {text}
      </p>
    </div>
  )
}

export { UiSdkTokenInputCodec }

Devre dışı bir input odak almaz, değişmez, kaldırma düğmelerini gizler ve hiçbir şey göndermez.

import {
  TokenInput,
  TokenInputEditor,
  type TokenInputValue,
} from "@/components/ui/ui-sdk-token-input"

const draft: TokenInputValue = [
  "Summarize ",
  { type: "file", value: "README.md", label: "README.md" },
  " for the release notes.",
]

function UiSdkTokenInputDisabled() {
  return (
    <div className="w-full max-w-md">
      <TokenInput disabled defaultValue={draft}>
        <TokenInputEditor aria-label="Prompt" />
      </TokenInput>
    </div>
  )
}

export { UiSdkTokenInputDisabled }

readOnly ile metin odak alabilir, seçilebilir ve gönderilir, ama yazma, yapıştırma ve bırakma onu değiştirmez.

import {
  TokenInput,
  TokenInputEditor,
  type TokenInputValue,
} from "@/components/ui/ui-sdk-token-input"

const draft: TokenInputValue = [
  "Summarize ",
  { type: "file", value: "README.md", label: "README.md" },
  " for the release notes.",
]

function UiSdkTokenInputReadOnly() {
  return (
    <div className="w-full max-w-md">
      <TokenInput readOnly defaultValue={draft}>
        <TokenInputEditor aria-label="Prompt" />
      </TokenInput>
    </div>
  )
}

export { UiSdkTokenInputReadOnly }

Editör sayfanın yönünü izler ve bir token sağdan sola metin içinde tek parça kalır.

import {
  TokenInput,
  TokenInputEditor,
  type TokenInputValue,
} from "@/components/ui/ui-sdk-token-input"

const draft: TokenInputValue = [
  "اشرح ",
  { type: "file", value: "src/routes.ts", label: "routes.ts" },
  " في جملتين.",
]

function UiSdkTokenInputRtl() {
  return (
    <div dir="rtl" className="w-full max-w-md">
      <TokenInput defaultValue={draft}>
        <TokenInputEditor aria-label="الطلب" />
      </TokenInput>
    </div>
  )
}

export { UiSdkTokenInputRtl }
TuşAction
BackspaceDeleteİmlecin önündeki ya da arkasındaki token’ı tokenDeletion’a göre kaldırır.
←→İmleci tek basışta bir token’ın ötesine taşır. İmleç asla token içinde durmaz.
↑↓Menü açıkken öneriler arasında gezinir.
EnterGönderir. Menü açıkken vurgulanan öneriyi seçer.
Shift + EnterYeni bir satır başlatır.
⌘ + EnterCtrl + EntersubmitShortcut mod+enter olduğunda gönderir. Enter bu durumda yeni satır başlatır.
⌘ + ZCtrl + ZSon adımı geri alır. Yazma gruplanır; yapıştırmalar ve token değişiklikleri ayrı adımlardır.
EscÖnerileri kapatır.

Editör çok satırlı bir metin kutusudur. İçindeki bir tetikleyici menü aria-autocomplete ve aria-haspopup ekler; ok tuşları önerilerde gezinirken odak metinde kalır. Her token etiketi ve türüyle okunur, örneğin “routes.ts dosya”; eklenmesi ya da kaldırılması duyurulur. Editörü aria-label ile ya da çevresindeki bir etiketle adlandırın.

Backspace bir token’ı klavyeden kaldırdığı için kaldırma düğmeleri ekran okuyuculardan gizlenir. Dokunmatik ekranlarda dokunma alanları büyür, bekleyen bir token yalnızca harekete izin verildiğinde nabız gibi atar ve seçili token zorunlu renkler modunda sistem vurgu kenarlığı alır.

Her parça aynı adlı UI SDK parçasını sarar ve render ile parçanın durumuna bağlı bir fonksiyon olabilen className dahil tüm prop’larını kabul eder.

PropTürVarsayılan
value
TokenInputValue–
defaultValue
TokenInputValue–
onValueChangeYazma, yapıştırma, menüler, geri alma ve controller çağrılarında çalışır. details nedeni ve kaldırılan token’ları listeler.
(value, details) => void–
onSubmitGönderme kısayolunda çalışır; bir menü açıksa ya da bir giriş yöntemi metin oluşturuyorsa çalışmaz.
(value, details) => void–
submitShortcut
"enter" | "mod+enter""enter"
tokenDeletion
"whole" | "select" | "revert""whole"
renderTokenBir token çizer. Varsayılan olarak etiketli ve kaldırma düğmeli bir TokenInputToken.
(token: Token) => ReactNode–
copy
"codec" | "label""codec"
codec
TokenCodec–
getTokenDescriptionBir token’ın ekran okuyuculara etiketi dışında söylediği.
(token: Token) => string–
history
{ limit, groupDelay }–
name
string–
form
string–
required
booleanfalse
disabled
booleanfalse
readOnly
booleanfalse
controllercreateTokenInputController() ile oluşturulur.
TokenInputController–
PropTürVarsayılan
placeholderInput boşken aria-placeholder ile gösterilir.
string–
render
ReactElement | (props, state) => ReactElement–
ÖznitelikAçıklama
data-slot="token-input-editor"Editörü CSS’te hedeflemek için.
data-emptyInput boşken bulunur.
data-disabledDevre dışıyken bulunur.
data-readonlySalt okunur olduğunda bulunur.
ÖznitelikAçıklama
data-slot="token-input-token"Bir token’ı CSS’te hedeflemek için.
data-token-typeToken’ın türü, örneğin file ya da mention.
data-selectedToken seçimdeyken ya da kaldırılmak üzere seçiliyken bulunur.
data-pendingToken yüklenirken bulunur.
data-unresolvedToken kullanıcıya ihtiyaç duyduğu sürece bulunur, örneğin belirsiz bir anma.

Token’ını kaldırır ve odağı editörde tutar. children verilmezse bir Tabler X simgesi çizer; input devre dışı ya da salt okunurken kendini gizler.

PropTürVarsayılan
triggersMenüyü açan karakterler, her birinin eklediği token türü ve öğeleri.
TriggerDefinition[]–
onSelectBir öğe seçildiğinde çalışır. Token’ı beklemede eklemek için bir promise döndürün.
(value, details) => void | Promise–
autoHighlight
booleantrue
loop
booleantrue
PropTürVarsayılan
align
"start" | "center" | "end""start"
sideOffset
number6
collisionPadding
number8
ÖznitelikAçıklama
data-slot="trigger-menu-content"Popup’ı CSS’te hedeflemek için.
data-triggerGeçerli eşleşmenin tetikleyicisi, örneğin @; böylece her liste farklı görünebilir.
data-starting-styleAçılır pencere animasyonla girerken bulunur.
data-ending-styleAçılır pencere animasyonla çıkarken bulunur.
PropTürVarsayılan
value
T–
labelİçerik düz metin değilse eklenen ve eşleştirilen metin.
string–
disabled
booleanfalse
ÖznitelikAçıklama
data-highlightedVurgulanan öğede bulunur. Odak editörde kalır.
data-disabledÖğe seçilemiyorsa bulunur.