Temel kavramlar

JSX ve Şablonlar

JSX'in çekirdek çağrılarına inişi; metin ve öznitelik bağları, spread, class/style, x- direktifleri, derleme tanıları ve explain; her yazımın JSX'siz karşılığı.

MotifJS'te JSX çalışma anında bir sanal ağaç üretmez. @motifx/compiler her JSX elemanını canlı bir bileşen nesnesine (motifComponent çağrısı) çevirir; süslü parantez içindeki ifade de o bileşenin bir prop'u ya da üzerinde yapılan bir çağrı olur (attr.add, class.add, style, motif.on, bindings.*, controls.add). Canlı bir bağ kurulup kurulmayacağını ifadenin biçimi belirler: çocuk konumunda {note.title} ve {() => …} bağ kurar, {fmt(note.updatedAt)} gibi düz çağrı bir kez değerlendirilir (bkz. Metin ve reaktif ifadeler). Bu sayfa JSX içinde ne yazabileceğinizi ve her yazımın hangi çağrıya indiğini anlatır. Kod tercihinde Imperative seçiliyse örnekler aynı şeyin JSX'siz yazımını gösterir (bkz. JSX'siz Görünümler).

Örnekler başlangıç şablonundaki not uygulamasından alınmıştır. Note, Folder, FOLDERS ve NotesService Bileşenler sayfasındaki src/services/notes.ts dosyasındadır; note listedeki ya da açık olan notu gösterir.

Örneklerdeki this.notes bu servisi, this.state bileşenin reaktif durumunu gösterir.

Örneklerdeki notes FromService ile alınan servisi, state bileşenin reactive ile kurduğu yerel durumu gösterir.

Her eleman bir bileşendir

Derleyici HTML üretmez. Her eleman motifComponent (_mc) çağrısıyla bir bileşen nesnesi olur; ağaç, bu nesnelerin controls koleksiyonlarına kodla eklenerek kurulur. Boş klasör sayfasının (Bileşenler) yer tutucusu:

  override view() {
    return (
      <div class="placeholder">
        <p>{() => (this.notes.count(this.folder) ? "Bir not seçin ya da yenisini yazın." : FOLDERS[this.folder].empty)}</p>
        <button type="button" class="accent" onclick={() => this.create()}>
          Yeni not
        </button>
      </div>
    );
  }
  view() {
    return (
      <div class="placeholder">
        <p>{() => (this.notes.count(this.folder) ? "Bir not seçin ya da yenisini yazın." : FOLDERS[this.folder].empty)}</p>
        <button type="button" class="accent" onclick={() => this.create()}>
          Yeni not
        </button>
      </div>
    );
  }
return (
  <div class="placeholder">
    <p>{() => (notes.count(folder()) ? "Bir not seçin ya da yenisini yazın." : FOLDERS[folder()].empty)}</p>
    <button type="button" class="accent" onclick={() => create()}>
      Yeni not
    </button>
  </div>
);
  override view() {
    return motifComponent("div", (box: ComponentBase) => {
      box.class.add("placeholder");
      const message = motifComponent("p", (p: ComponentBase) => {
        p.bindings.ternary(
          () => this.notes.count(this.folder),
          (frame) => frame.navigate(motifComponent("text", (node: ComponentBase) => node.setText("Bir not seçin ya da yenisini yazın."))),
          (frame) => frame.navigate(motifComponent("text", (node: ComponentBase) => node.bindings.add("textContent", FOLDERS[this.folder], "empty"))),
        );
      });
      const create = motifComponent("button", (button: ComponentBase) => {
        button.attr.add({ type: "button" });
        button.class.add("accent");
        button.motif.on("click", () => this.create());
        button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Yeni not")));
      });
      box.controls.add(message, create);
    });
  }
  view() {
    return motifComponent("div", (box: ComponentBase) => {
      box.class.add("placeholder");
      const message = motifComponent("p", (p: ComponentBase) => {
        p.bindings.ternary(
          () => this.notes.count(this.folder),
          (frame) => frame.navigate(motifComponent("text", (node: ComponentBase) => node.setText("Bir not seçin ya da yenisini yazın."))),
          (frame) => frame.navigate(motifComponent("text", (node: ComponentBase) => node.bindings.add("textContent", FOLDERS[this.folder], "empty"))),
        );
      });
      const create = motifComponent("button", (button: ComponentBase) => {
        button.attr.add({ type: "button" });
        button.class.add("accent");
        button.motif.on("click", () => this.create());
        button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Yeni not")));
      });
      box.controls.add(message, create);
    });
  }
return motifComponent("div", (box: ComponentBase) => {
  box.class.add("placeholder");
  const message = motifComponent("p", (p: ComponentBase) => {
    p.bindings.ternary(
      () => notes.count(folder()),
      (frame) => frame.navigate(motifComponent("text", (node: ComponentBase) => node.setText("Bir not seçin ya da yenisini yazın."))),
      (frame) => frame.navigate(motifComponent("text", (node: ComponentBase) => node.bindings.add("textContent", FOLDERS[folder()], "empty"))),
    );
  });
  const createButton = motifComponent("button", (button: ComponentBase) => {
    button.attr.add({ type: "button" });
    button.class.add("accent");
    button.motif.on("click", () => create());
    button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Yeni not")));
  });
  box.controls.add(message, createButton);
});
  view() {
    return (
      <div class="placeholder">
        <p>{() => (this.notes.count(this.folder) ? "Bir not seçin ya da yenisini yazın." : FOLDERS[this.folder].empty)}</p>
        <button type="button" class="accent" onclick={() => this.create()}>
          Yeni not
        </button>
      </div>
    );
  }
return (
  <div class="placeholder">
    <p>{() => (notes.count(folder()) ? "Bir not seçin ya da yenisini yazın." : FOLDERS[folder()].empty)}</p>
    <button type="button" class="accent" onclick={() => create()}>
      Yeni not
    </button>
  </div>
);
  view() {
    return motifComponent("div", (box) => {
      box.class.add("placeholder");
      const message = motifComponent("p", (p) => {
        p.bindings.ternary(
          () => this.notes.count(this.folder),
          (frame) => frame.navigate(motifComponent("text", (node) => node.setText("Bir not seçin ya da yenisini yazın."))),
          (frame) => frame.navigate(motifComponent("text", (node) => node.bindings.add("textContent", FOLDERS[this.folder], "empty"))),
        );
      });
      const create = motifComponent("button", (button) => {
        button.attr.add({ type: "button" });
        button.class.add("accent");
        button.motif.on("click", () => this.create());
        button.controls.add(motifComponent("text", (node) => node.setText("Yeni not")));
      });
      box.controls.add(message, create);
    });
  }
return motifComponent("div", (box) => {
  box.class.add("placeholder");
  const message = motifComponent("p", (p) => {
    p.bindings.ternary(
      () => notes.count(folder()),
      (frame) => frame.navigate(motifComponent("text", (node) => node.setText("Bir not seçin ya da yenisini yazın."))),
      (frame) => frame.navigate(motifComponent("text", (node) => node.bindings.add("textContent", FOLDERS[folder()], "empty"))),
    );
  });
  const createButton = motifComponent("button", (button) => {
    button.attr.add({ type: "button" });
    button.class.add("accent");
    button.motif.on("click", () => create());
    button.controls.add(motifComponent("text", (node) => node.setText("Yeni not")));
  });
  box.controls.add(message, createButton);
});

Fonksiyon bileşeninde notes FromService ile alınan servis, state reactive ile kurulan yerel durumdur; folder(), visible(), create, archive ve remove bileşen fonksiyonunun içindeki yerel fonksiyonlardır.

JSX şu koda derlenir; JSX'siz yazım bunun okunur biçimidir:

view() {
  return _mc("div", {
    initializeComponent: sender => {
      sender.class.add("placeholder");
      const _comp2 = _mc("p", {
        initializeComponent: sender => {
          sender.bindings.ternary(() => this.notes.count(this.folder), frame => {
            frame.navigate(_mc("text", {
              initializeComponent: sender => {
                sender.setText("Bir not seçin ya da yenisini yazın.");
              }
            }, 1));
          }, frame => {
            frame.navigate(_mc("text", {
              initializeComponent: sender => {
                sender.bindings.add("textContent", FOLDERS[this.folder], "empty");
              },
              __childExpr: () => FOLDERS[this.folder].empty
            }));
          });
        }
      }, 1);
      sender.controls.add(_comp2);
      const _comp3 = _mc("button", {
        initializeComponent: sender => {
          sender.attr.add({
            "type": "button"
          });
          sender.class.add("accent");
          sender.motif.on("click", () => this.create());
          const _comp = _mc("text", {
            initializeComponent: sender => {
              sender.setText("Yeni not");
            }
          }, 1);
          sender.controls.add(_comp);
        }
      }, 1);
      sender.controls.add(_comp3);
    }
  }, 1);
}
return _mc("div", {
  initializeComponent: sender => {
    sender.class.add("placeholder");
    const _comp2 = _mc("p", {
      initializeComponent: sender => {
        sender.bindings.ternary(() => notes.count(folder()), frame => {
          frame.navigate(_mc("text", {
            initializeComponent: sender => {
              sender.setText("Bir not seçin ya da yenisini yazın.");
            }
          }, 1));
        }, frame => {
          frame.navigate(_mc("text", {
            initializeComponent: sender => {
              sender.bindings.add("textContent", FOLDERS[folder()], "empty");
            },
            __childExpr: () => FOLDERS[folder()].empty
          }));
        });
      }
    }, 1);
    sender.controls.add(_comp2);
    const _comp3 = _mc("button", {
      initializeComponent: sender => {
        sender.attr.add({
          "type": "button"
        });
        sender.class.add("accent");
        sender.motif.on("click", () => create());
        const _comp = _mc("text", {
          initializeComponent: sender => {
            sender.setText("Yeni not");
          }
        }, 1);
        sender.controls.add(_comp);
      }
    }, 1);
    sender.controls.add(_comp3);
  }
}, 1);

Buradan çıkan sonuçlar:

Düz metin bir "text" bileşenidir: motifComponent("text", (node) => node.setText("…")). Çocuk konumundaki getter için derleyici bindings.method yazar; elle yazımda metin için bindings.text(fn) kullanılır, bindings.method ise sonucu bileşen de olabilen getter içindir (bkz. Metin ve reaktif ifadeler).

Elemanlar ve iç içe yapı

Standart HTML etiketleri küçük harfle, bileşenler büyük harfle yazılır. Bileşen etiketi bir sınıfsa new ile kurulur, fonksiyonsa çağrılır, Options API nesnesiyse bileşene çevrilir (bkz. Bileşen Oluşturma). JSX'siz yazımda üçü de motifComponent(Bileşen, props) ile kurulur; etiketin içine yazılan metin ve elemanlar childs dizisine girer. Kenar çubuğundaki klasör grubu:

<div class="side__group">
  <h2 class="side__label">Klasörler</h2>
  <nav class="nav" aria-label="Klasörler">
    <RouterLink to="/notes" activeClass="active">Tüm notlar</RouterLink>
    <RouterLink to="/archive" activeClass="active">Arşiv</RouterLink>
  </nav>
</div>
motifComponent("div", (group: ComponentBase) => {
  group.class.add("side__group");
  const heading = motifComponent("h2", (h2: ComponentBase) => {
    h2.class.add("side__label");
    h2.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Klasörler")));
  });
  const nav = motifComponent("nav", (nav: ComponentBase) => {
    nav.class.add("nav");
    nav.attr.add({ "aria-label": "Klasörler" });
    nav.controls.add(
      motifComponent(RouterLink, { to: "/notes", activeClass: "active", childs: [motifComponent("text", (node: ComponentBase) => node.setText("Tüm notlar"))] }),
      motifComponent(RouterLink, { to: "/archive", activeClass: "active", childs: [motifComponent("text", (node: ComponentBase) => node.setText("Arşiv"))] }),
    );
  });
  group.controls.add(heading, nav);
});
<div class="side__group">
  <h2 class="side__label">Klasörler</h2>
  <nav class="nav" aria-label="Klasörler">
    <RouterLink to="/notes" activeClass="active">Tüm notlar</RouterLink>
    <RouterLink to="/archive" activeClass="active">Arşiv</RouterLink>
  </nav>
</div>
motifComponent("div", (group) => {
  group.class.add("side__group");
  const heading = motifComponent("h2", (h2) => {
    h2.class.add("side__label");
    h2.controls.add(motifComponent("text", (node) => node.setText("Klasörler")));
  });
  const nav = motifComponent("nav", (nav) => {
    nav.class.add("nav");
    nav.attr.add({ "aria-label": "Klasörler" });
    nav.controls.add(
      motifComponent(RouterLink, { to: "/notes", activeClass: "active", childs: [motifComponent("text", (node) => node.setText("Tüm notlar"))] }),
      motifComponent(RouterLink, { to: "/archive", activeClass: "active", childs: [motifComponent("text", (node) => node.setText("Arşiv"))] }),
    );
  });
  group.controls.add(heading, nav);
});

Fragment

Yan yana birden çok eleman döndürmek için fragment kullanılır. Fragment elemanı olmayan bir bileşendir: DOM'da açılış/kapanış yorum işaretçileri (<!--[--> … <!--]-->) arasında durur; <> etiketine öznitelik ve olay yazılamaz. <>…</> yazımının JSX'siz karşılığı motifFragment'tır; derleyici de bunu üretir. Not sayfası araç çubuğundaki üç düğmeyi bir fragment olarak döndürür:

  private actions(note: Note) {
    return (
      <>
        <button type="button" class="button--sm ghost" aria-pressed={() => note.pinned} onclick={() => this.notes.togglePin(note)}>
          {() => (note.pinned ? "Sabitlendi" : "Sabitle")}
        </button>
        <button type="button" class="button--sm ghost" onclick={() => this.archive(note)}>
          {() => (note.archived ? "Geri al" : "Arşivle")}
        </button>
        <button type="button" class="button--sm danger" onclick={() => (this.state.confirming = true)}>
          Sil
        </button>
      </>
    );
  }
  private actions(note: Note) {
    return (
      <>
        <button type="button" class="button--sm ghost" aria-pressed={() => note.pinned} onclick={() => notes.togglePin(note)}>
          {() => (note.pinned ? "Sabitlendi" : "Sabitle")}
        </button>
        <button type="button" class="button--sm ghost" onclick={() => archive(note)}>
          {() => (note.archived ? "Geri al" : "Arşivle")}
        </button>
        <button type="button" class="button--sm danger" onclick={() => (state.confirming = true)}>
          Sil
        </button>
      </>
    );
  }
  private actions(note: Note) {
    const pin = motifComponent("button", (button: ComponentBase) => {
      button.attr.add({ type: "button", "aria-pressed": () => note.pinned });
      button.class.add("button--sm ghost");
      button.motif.on("click", () => this.notes.togglePin(note));
      button.bindings.text(() => (note.pinned ? "Sabitlendi" : "Sabitle"));
    });
    const archive = motifComponent("button", (button: ComponentBase) => {
      button.attr.add({ type: "button" });
      button.class.add("button--sm ghost");
      button.motif.on("click", () => this.archive(note));
      button.bindings.text(() => (note.archived ? "Geri al" : "Arşivle"));
    });
    const remove = motifComponent("button", (button: ComponentBase) => {
      button.attr.add({ type: "button" });
      button.class.add("button--sm danger");
      button.motif.on("click", () => (this.state.confirming = true));
      button.setText("Sil");
    });
    return motifFragment({ childs: [pin, archive, remove] });
  }
  private actions(note: Note) {
    const pin = motifComponent("button", (button: ComponentBase) => {
      button.attr.add({ type: "button", "aria-pressed": () => note.pinned });
      button.class.add("button--sm ghost");
      button.motif.on("click", () => notes.togglePin(note));
      button.bindings.text(() => (note.pinned ? "Sabitlendi" : "Sabitle"));
    });
    const archiveButton = motifComponent("button", (button: ComponentBase) => {
      button.attr.add({ type: "button" });
      button.class.add("button--sm ghost");
      button.motif.on("click", () => archive(note));
      button.bindings.text(() => (note.archived ? "Geri al" : "Arşivle"));
    });
    const remove = motifComponent("button", (button: ComponentBase) => {
      button.attr.add({ type: "button" });
      button.class.add("button--sm danger");
      button.motif.on("click", () => (state.confirming = true));
      button.setText("Sil");
    });
    return motifFragment({ childs: [pin, archiveButton, remove] });
  }
  actions(note) {
    return (
      <>
        <button type="button" class="button--sm ghost" aria-pressed={() => note.pinned} onclick={() => this.notes.togglePin(note)}>
          {() => (note.pinned ? "Sabitlendi" : "Sabitle")}
        </button>
        <button type="button" class="button--sm ghost" onclick={() => this.archive(note)}>
          {() => (note.archived ? "Geri al" : "Arşivle")}
        </button>
        <button type="button" class="button--sm danger" onclick={() => (this.state.confirming = true)}>
          Sil
        </button>
      </>
    );
  }
  actions(note) {
    return (
      <>
        <button type="button" class="button--sm ghost" aria-pressed={() => note.pinned} onclick={() => notes.togglePin(note)}>
          {() => (note.pinned ? "Sabitlendi" : "Sabitle")}
        </button>
        <button type="button" class="button--sm ghost" onclick={() => archive(note)}>
          {() => (note.archived ? "Geri al" : "Arşivle")}
        </button>
        <button type="button" class="button--sm danger" onclick={() => (state.confirming = true)}>
          Sil
        </button>
      </>
    );
  }
  actions(note) {
    const pin = motifComponent("button", (button) => {
      button.attr.add({ type: "button", "aria-pressed": () => note.pinned });
      button.class.add("button--sm ghost");
      button.motif.on("click", () => this.notes.togglePin(note));
      button.bindings.text(() => (note.pinned ? "Sabitlendi" : "Sabitle"));
    });
    const archive = motifComponent("button", (button) => {
      button.attr.add({ type: "button" });
      button.class.add("button--sm ghost");
      button.motif.on("click", () => this.archive(note));
      button.bindings.text(() => (note.archived ? "Geri al" : "Arşivle"));
    });
    const remove = motifComponent("button", (button) => {
      button.attr.add({ type: "button" });
      button.class.add("button--sm danger");
      button.motif.on("click", () => (this.state.confirming = true));
      button.setText("Sil");
    });
    return motifFragment({ childs: [pin, archive, remove] });
  }
  actions(note) {
    const pin = motifComponent("button", (button) => {
      button.attr.add({ type: "button", "aria-pressed": () => note.pinned });
      button.class.add("button--sm ghost");
      button.motif.on("click", () => notes.togglePin(note));
      button.bindings.text(() => (note.pinned ? "Sabitlendi" : "Sabitle"));
    });
    const archiveButton = motifComponent("button", (button) => {
      button.attr.add({ type: "button" });
      button.class.add("button--sm ghost");
      button.motif.on("click", () => archive(note));
      button.bindings.text(() => (note.archived ? "Geri al" : "Arşivle"));
    });
    const remove = motifComponent("button", (button) => {
      button.attr.add({ type: "button" });
      button.class.add("button--sm danger");
      button.motif.on("click", () => (state.confirming = true));
      button.setText("Sil");
    });
    return motifFragment({ childs: [pin, archiveButton, remove] });
  }

Aynı fragment bir dal ya da yuva değeri olarak da yazılır: motifFragment({ initializeComponent: (f) => f.controls.add(a, b) }). Options API bileşeninin view()'i bir dizi de döndürebilir (bkz. Options API bileşenleri). Not sayfası kendisi de eleman bildirmediği için bir fragmenttir; view()'in döndürdüğü içerik onun işaretçileri arasına yerleşir (bkz. Kök eleman).

Metin ve reaktif ifadeler

Süslü parantez {...} içine ifade yazılır. İfadenin biçimi, reaktif olup olmadığını belirler. Her biçimin indiği çağrı ve JSX'siz karşılığı:

Yazım Davranış JSX'siz karşılığı
{"Notlar"} Statik; bir kez yazılır. motifComponent("text", (node) => node.setText("Notlar"))
{count} (yerel değişken) Korumalı getter: dizge/sayı tutuyorsa bir kez yazılır; fonksiyon tutuyorsa çağrılır ve canlı kalır; bileşen (ya da bileşen dizisi) tutuyorsa controls'a yerleşir; null/undefined boş metin olur. Değerin türüne göre node.setText(String(count)), span.bindings.text(fn) ya da controls.add(bileşen)
{fmt(note.updatedAt)} Düz çağrı; kurulumda bir kez değerlenir. node.setText(fmt(note.updatedAt))
{note.title} O alana tek yönlü bağ; yalnız o alan izlenir. node.bindings.add("textContent", note, "title")
{() => note.title.trim() \|\| "Adsız"} Getter; içinde okunan her reaktif alan izlenir, biri değişince yeniden hesaplanır. span.bindings.text(() => note.title.trim() \|\| "Adsız")
{`${note.title} · ${fmt(note.updatedAt)}`} Şablon dizesi getter'a sarılır; canlıdır. span.bindings.text(() => `${note.title} · ${fmt(note.updatedAt)}`)
{note.title ?? "Adsız"}, {a \|\| b} Canlı; sol taraf varsa (??: null/undefined değilse, \|\|: doğruysa) o, yoksa sağ taraf. Sağ taraf JSX de olabilir. span.bindings.text(() => note.title ?? "Adsız")
{this.badge(note)} (üye çağrısı) Sonuç bileşense bir Frame yuvası (reaktif değişimde yeniden değerlenir), dizgeyse canlı metin. li.bindings.method(() => this.badge(note))
{cond && <X/>}, {cond ? <A/> : <B/>}, {items.map(…)} Koşul ve liste bağları; bkz. Koşullu Gösterim ve Listeler. bindings.when, bindings.ternary, bindings.list
{this.childs}, {props.childs}, {[a, b]}, {örnek} Bileşen örneklerini yerleştirir. controls.add(...)

Sabit metin

Literal metin bir kez yazılır. Düz bir yerel değişken de ({count}, count bir sayıysa) böyledir: reaktif bir okuma içermediği için güncellenmez.

<h2>Notlar</h2>
<p class="list__empty">{"Henüz not yok."}</p>
const heading = motifComponent("h2", (h2: ComponentBase) => h2.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Notlar"))));
const empty = motifComponent("p", (p: ComponentBase) => {
  p.class.add("list__empty");
  p.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Henüz not yok.")));
});
<h2>Notlar</h2>
<p class="list__empty">{"Henüz not yok."}</p>
const heading = motifComponent("h2", (h2) => h2.controls.add(motifComponent("text", (node) => node.setText("Notlar"))));
const empty = motifComponent("p", (p) => {
  p.class.add("list__empty");
  p.controls.add(motifComponent("text", (node) => node.setText("Henüz not yok.")));
});

Reaktif model alanı

Reaktif bir nesnenin alanını doğrudan yazarsanız o alana tek yönlü bağ kurulur. Alan değiştiğinde yalnız o metin düğümü güncellenir:

<span>{note.title}</span>
const title = motifComponent("span", (span: ComponentBase) => span.controls.add(motifComponent("text", (node: ComponentBase) => node.bindings.add("textContent", note, "title"))));
<span>{note.title}</span>
const title = motifComponent("span", (span) => span.controls.add(motifComponent("text", (node) => node.bindings.add("textContent", note, "title"))));

Getter ile reaktif ifade

Hesaplanmış bir değeri canlı tutmak için ifadeyi bir ok fonksiyonuna alın; fonksiyon içindeki reaktif okumalar izlenir, biri değişince metin yeniden hesaplanır. Not satırının başlığı ve ilk satırı:

<span>{() => note.title.trim() || "Adsız"}</span>
<p>{() => note.body.split("\n")[0] || "Metin yok"}</p>
const title = motifComponent("span", (span: ComponentBase) => span.bindings.text(() => note.title.trim() || "Adsız"));
const preview = motifComponent("p", (p: ComponentBase) => p.bindings.text(() => note.body.split("\n")[0] || "Metin yok"));
<span>{() => note.title.trim() || "Adsız"}</span>
<p>{() => note.body.split("\n")[0] || "Metin yok"}</p>
const title = motifComponent("span", (span) => span.bindings.text(() => note.title.trim() || "Adsız"));
const preview = motifComponent("p", (p) => p.bindings.text(() => note.body.split("\n")[0] || "Metin yok"));

Kural: Bir ifadenin değerinin zamanla değişmesini ve DOM'a yansımasını istiyorsanız ya doğrudan bir reaktif alan (note.title) ya da bir getter (() => …) yazın. Doğrudan yazılan yolda yalnız son alan izlenir, önündeki nesne kurulumda bir kez okunur: {state.note.title} o notun başlığı değişince güncellenir, state.note başka bir nota çevrilince eski notta kalır. Yolun tamamını izlemek için getter yazın ({() => state.note?.title}); ?. içeren yol ({state.note?.title}) de getter'a sarılır ve canlı kalır. Düz bir yerel değişken statik kalır: const label = state.items.length + " not" ile yazılan {label} yeni not eklense de değişmez, {() => state.items.length + " not"} değişir.

Öznitelikler (attributes)

Öznitelikler değer ya da değer döndüren fonksiyon alır. Fonksiyon canlı bir bağdır; literal değer bir kez yazılır. JSX'te derleyici üye erişimini (note.pinned), işlemleri (!state.confirming, a + b), şablon dizesini ve ?. zincirini kendiliğinden getter'a sarar, bunlar da canlı kalır (ayrıntı aşağıda). Not sayfasından:

<input class="editor__title" placeholder="Adsız" value={() => note.title} />
<a rel="router" href={() => `/${this.folder}`}>Geri</a>
<button type="button" disabled={() => this.state.confirming}>Arşivle</button>
<span aria-pressed={note.pinned} data-id={note.id}>…</span>
<input class="editor__title" placeholder="Adsız" value={() => note.title} />
<a rel="router" href={() => `/${folder()}`}>Geri</a>
<button type="button" disabled={() => state.confirming}>Arşivle</button>
<span aria-pressed={note.pinned} data-id={note.id}>…</span>
const title = motifComponent("input", (input: ComponentBase) => {
  input.class.add("editor__title");
  input.attr.add({ placeholder: "Adsız" });
  input.bindings.add("value", () => note.title);
});
const back = motifComponent("a", (a: ComponentBase) => {
  a.attr.add({ rel: "router", href: () => `/${this.folder}` });
  a.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Geri")));
});
const archive = motifComponent("button", (button: ComponentBase) => {
  button.attr.add({ type: "button", disabled: () => this.state.confirming });
  button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Arşivle")));
});
const flag = motifComponent("span", (span: ComponentBase) => span.attr.add({ "aria-pressed": () => note.pinned, "data-id": note.id }));
const title = motifComponent("input", (input: ComponentBase) => {
  input.class.add("editor__title");
  input.attr.add({ placeholder: "Adsız" });
  input.bindings.add("value", () => note.title);
});
const back = motifComponent("a", (a: ComponentBase) => {
  a.attr.add({ rel: "router", href: () => `/${folder()}` });
  a.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Geri")));
});
const archive = motifComponent("button", (button: ComponentBase) => {
  button.attr.add({ type: "button", disabled: () => state.confirming });
  button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Arşivle")));
});
const flag = motifComponent("span", (span: ComponentBase) => span.attr.add({ "aria-pressed": () => note.pinned, "data-id": note.id }));
<input class="editor__title" placeholder="Adsız" value={() => note.title} />
<a rel="router" href={() => `/${this.folder}`}>Geri</a>
<button type="button" disabled={() => this.state.confirming}>Arşivle</button>
<span aria-pressed={note.pinned} data-id={note.id}>…</span>
<input class="editor__title" placeholder="Adsız" value={() => note.title} />
<a rel="router" href={() => `/${folder()}`}>Geri</a>
<button type="button" disabled={() => state.confirming}>Arşivle</button>
<span aria-pressed={note.pinned} data-id={note.id}>…</span>
const title = motifComponent("input", (input) => {
  input.class.add("editor__title");
  input.attr.add({ placeholder: "Adsız" });
  input.bindings.add("value", () => note.title);
});
const back = motifComponent("a", (a) => {
  a.attr.add({ rel: "router", href: () => `/${this.folder}` });
  a.controls.add(motifComponent("text", (node) => node.setText("Geri")));
});
const archive = motifComponent("button", (button) => {
  button.attr.add({ type: "button", disabled: () => this.state.confirming });
  button.controls.add(motifComponent("text", (node) => node.setText("Arşivle")));
});
const flag = motifComponent("span", (span) => span.attr.add({ "aria-pressed": () => note.pinned, "data-id": note.id }));
const title = motifComponent("input", (input) => {
  input.class.add("editor__title");
  input.attr.add({ placeholder: "Adsız" });
  input.bindings.add("value", () => note.title);
});
const back = motifComponent("a", (a) => {
  a.attr.add({ rel: "router", href: () => `/${folder()}` });
  a.controls.add(motifComponent("text", (node) => node.setText("Geri")));
});
const archive = motifComponent("button", (button) => {
  button.attr.add({ type: "button", disabled: () => state.confirming });
  button.controls.add(motifComponent("text", (node) => node.setText("Arşivle")));
});
const flag = motifComponent("span", (span) => span.attr.add({ "aria-pressed": () => note.pinned, "data-id": note.id }));

value, checked ve selected özellik bağıdır (bindings.add("value", getter)), diğerleri attr.add ile yazılır.

Düz DOM etiketinde literal değerler olduğu gibi attr.add'e geçer: sayı, eksi sayı, true/false ve null (dizi ve new X() de değer olarak geçer; eleman metotlarında argüman olur). false, null ve undefined özniteliği yazmaz (varsa kaldırır); true ve değersiz yazım özniteliği boş değerle ("") yazar; sayılar dizgeye çevrilir:

JSX Elemanda
<button disabled={false} /> disabled yok
<button disabled />, <li hidden={true} /> disabled="", hidden=""
<li tabindex={0} /> tabindex="0"
<li title={null} /> title yok

aria-* ve data-* özniteliklerinde boolean değer metin olarak yazılır: aria-selected={false} → aria-selected="false", data-pinned={true} → data-pinned="true"; null/undefined yine özniteliği kaldırır. Nesne ya da dizi değer tek bir öznitelik değeri olarak metne çevrilir (data-ids={[1, 2]} → data-ids="1,2").

Değişken okuyan bir ifade (a + b, !state.confirming, note.title) getter'a sarılır ve canlı kalır: aria-expanded={!state.confirming}, confirming değişince güncellenir. Tek başına bir değişken (attr={v}) olduğu gibi geçer; yalnız fonksiyon tutuyorsa canlıdır. Düz fonksiyon çağrısı (data-updated={fmt(note.updatedAt)}) bir kez değerlendirilir; canlı kalması için () => fmt(note.updatedAt) yazın.

Güvenlik: etikete yazılan öznitelikler ve attr.add değerleri süzmeden yazar. href, src, action, formaction gibi URL özniteliklerine kullanıcıdan gelen bir değer bağlanıyorsa javascript: ile başlayan bir değer tıklamada kod çalıştırır. Böyle değerleri bağlamadan önce izin verilen şemalara (https:, mailto:, göreli adres) göre denetleyin, uymayanı # yapın. attr.add({ innerHTML }) de x-html gibi HTML'i olduğu gibi yazar. Yalnız spread dışarıdan gelen innerHTML, srcdoc ve javascript: URL'lerini kendiliğinden atlar.

Eleman metotları

Düz DOM etiketinde adı aşağıdaki tablodaki eleman metotlarından biri olan prop öznitelik yazmaz, o metodu çağırır; tabloda olmayan metot adları (setCustomValidity, stepUp …) çağrılmaz, düz öznitelik olarak yazılır. Değer getter (ya da reaktif alan) ise her değişimde yeniden uygulanır; çağrı bir mikro görev sonra yapılır. JSX'siz yazımda aynı ad attr.add'e verilir, çağrıya çeviren çalışma zamanıdır. Silme onayı bir dialog'da açılıp kapanır, onay açılınca odak "Sil" düğmesine gider:

<dialog class="confirm" showModal={() => this.state.confirming}>
  <button type="button" class="danger" focus={() => this.state.confirming} onclick={() => this.remove(note)}>Sil</button>
</dialog>
<dialog class="confirm" showModal={() => state.confirming}>
  <button type="button" class="danger" focus={() => state.confirming} onclick={() => remove(note)}>Sil</button>
</dialog>
motifComponent("dialog", (dialog: ComponentBase) => {
  dialog.class.add("confirm");
  dialog.attr.add({ showModal: () => this.state.confirming });
  dialog.controls.add(motifComponent("button", (button: ComponentBase) => {
    button.attr.add({ type: "button", focus: () => this.state.confirming });
    button.class.add("danger");
    button.motif.on("click", () => this.remove(note));
    button.setText("Sil");
  }));
});
motifComponent("dialog", (dialog: ComponentBase) => {
  dialog.class.add("confirm");
  dialog.attr.add({ showModal: () => state.confirming });
  dialog.controls.add(motifComponent("button", (button: ComponentBase) => {
    button.attr.add({ type: "button", focus: () => state.confirming });
    button.class.add("danger");
    button.motif.on("click", () => remove(note));
    button.setText("Sil");
  }));
});
<dialog class="confirm" showModal={() => this.state.confirming}>
  <button type="button" class="danger" focus={() => this.state.confirming} onclick={() => this.remove(note)}>Sil</button>
</dialog>
<dialog class="confirm" showModal={() => state.confirming}>
  <button type="button" class="danger" focus={() => state.confirming} onclick={() => remove(note)}>Sil</button>
</dialog>
motifComponent("dialog", (dialog) => {
  dialog.class.add("confirm");
  dialog.attr.add({ showModal: () => this.state.confirming });
  dialog.controls.add(motifComponent("button", (button) => {
    button.attr.add({ type: "button", focus: () => this.state.confirming });
    button.class.add("danger");
    button.motif.on("click", () => this.remove(note));
    button.setText("Sil");
  }));
});
motifComponent("dialog", (dialog) => {
  dialog.class.add("confirm");
  dialog.attr.add({ showModal: () => state.confirming });
  dialog.controls.add(motifComponent("button", (button) => {
    button.attr.add({ type: "button", focus: () => state.confirming });
    button.class.add("danger");
    button.motif.on("click", () => remove(note));
    button.setText("Sil");
  }));
});
Prop true false Diğer değer
focus focus() blur() nesne → focus(nesne), diğer → focus()
show, showModal çağırır close() doğru (truthy) değerde argümansız çağırır
showPopover çağırır hidePopover() doğru değerde argümansız çağırır
togglePopover togglePopover(true) togglePopover(false) doğru değerde togglePopover()
play play() pause() doğru → play(), yanlış → pause()
select select() seçimi kaldırır doğru → select(), yanlış → seçimi kaldırır
requestFullscreen, requestPointerLock çağırır çıkar doğru → çağırır, yanlış → çıkar
close, hidePopover, requestSubmit, checkValidity, reportValidity, showPicker, load çağırır — doğru değerde metot(değer) — ör. close('iptal'), requestSubmit(düğme)
blur, click, pause, submit, reset, exitFullscreen, exitPointerLock, requestPictureInPicture, exitPictureInPicture çağırır — doğru değerde argümansız çağırır
scrollIntoView scrollIntoView() scrollIntoView(false) null/undefined → scrollIntoView(), diğerleri scrollIntoView(değer)
scrollTo, scrollBy — — yalnız nesne ya da dizi: metot(değer)
setSelectionRange, setRangeText, setPointerCapture, releasePointerCapture, fastSeek metot(true) — null/undefined dışında metot(değer)

Dizi değer argümanlara açılır (setSelectionRange={[0, 5]} → setSelectionRange(0, 5)), nesne değer tek argüman olarak geçer (scrollTo={{ top: 0 }}, focus={{ preventScroll: true }}); ikisinde de öznitelik yazılmaz. focus yalnız tam false için blur() çağırır; undefined, null, 0 ve '' de focus() demektir, bu yüzden focus={() => state.confirming} yazacaksanız alanı false ile başlatın. exitFullscreen, exitPointerLock ve exitPictureInPicture document üzerinde çağrılır. Eleman o metoda sahip değilse değer düz öznitelik olarak yazılır. Bileşen etiketinde (<NoteFields focus={…}/>) bu adlar sıradan prop'tur.

Spread — `

Bir düz DOM elemanına yayılan nesne, öznitelikler doğrudan yazılmış gibi uygulanır. JSX'siz karşılığı nesneyi motifComponent'in ikinci argümanı olarak vermektir; DOM prop'larını uygulayan çalışma zamanıdır. TextField aldığı her şeyi input'a yayar:

type TextFieldProps = JSX.IntrinsicElements["input"];

export function TextField(props: TextFieldProps) {
  return <input {...props} />;
}
import { motifComponent } from "@motifx/core";

type TextFieldProps = JSX.IntrinsicElements["input"];

export function TextField(props: TextFieldProps) {
  return motifComponent("input", { ...props });
}
export function TextField(props) {
  return <input {...props} />;
}
import { motifComponent } from "@motifx/core";

export function TextField(props) {
  return motifComponent("input", { ...props });
}

Not sayfası başlık alanını onunla kurar:

<TextField class="editor__title" aria-label="Başlık" value={() => note.title} oninput={(e) => (note.title = (e.target as HTMLInputElement).value)} />
motifComponent(TextField, { class: "editor__title", "aria-label": "Başlık", value: () => note.title, oninput: (e: Event) => (note.title = (e.target as HTMLInputElement).value) });
<TextField class="editor__title" aria-label="Başlık" value={() => note.title} oninput={(e) => (note.title = e.target.value)} />
motifComponent(TextField, { class: "editor__title", "aria-label": "Başlık", value: () => note.title, oninput: (e) => (note.title = e.target.value) });
Anahtar Uygulama
class / className class.add — doğrudan yazılan class ile toplanır
style style() — string, nesne veya getter
value / checked / selected özellik bağı (bindings.add)
on* (onclick, onClick, oninput:once) DOM olayı
diğer her anahtar (id, title, aria-*, data-*, viewBox…) attribute (attr.add)
innerHTML / srcdoc yazılmaz (geliştirme modunda MJX124 uyarısı)
href / src / action / formaction / xlink:href değeri javascript: ile başlıyorsa yazılmaz, getter'da o anki değer kaldırılır (geliştirme modunda MJX125 uyarısı)

Spread nesnesi çoğu zaman dışarıdan gelen veriyi taşıdığı için bu iki yol kapalıdır. Güvendiğiniz HTML için x-html ya da etikete doğrudan yazılan srcdoc (<iframe srcdoc={html}>), bilerek yazılan bir javascript: bağlantısı için etikete doğrudan yazılan öznitelik (<a href="javascript:void 0">) kullanılır; bunlar süzülmez.

Getter (() => …) değerler canlıdır. Parametre bekleyen fonksiyonlar (renderItem: (x) => …) öznitelik sayılmaz ve atlanır. ref, key, options, transition, initializeComponent, childs, x-* ve yaşam döngüsü prop'ları çerçeveye gider.

Sıralama: spread yapıcıda uygulanır, etikete doğrudan yazılan öznitelikler derleyicinin ürettiği initializeComponent'te — aynı attribute'ta doğrudan yazılan kazanır (<input {...p} id="title"/>), class ise birleşir. Spread'deki değer getter olsa da böyledir: doğrudan yazım o özniteliğin sahibi olur, getter o özniteliğe bir daha yazmaz; spread'in diğer getter'ları canlı kalır. Aynı kural value, checked ve selected'da ve style'ın anahtarlarında da geçerlidir: doğrudan yazılan style={{ color: "blue" }} spread'deki style getter'ının yalnız color anahtarını alır, getter'ın öteki anahtarları canlı kalır; doğrudan yazılan style="…" dizesi özniteliğin tamamını alır.

Bileşen etiketinde bilinen bir DOM olay adı (oninput, onclick) bileşenin elemanına dinleyici olarak bağlanır ve props'a hiç girmez. Yukarıdaki TextField bir fonksiyon olduğu için etiketin oninput'u döndürdüğü input'a bağlanır. Kendi elemanı olmayan bir sınıf view()'de <input {...this.props} /> yazsa props'ta oninput bulunmaz; elemanı olan sınıf ve el'i etiket adı (el: "input") ya da hazır DOM düğümü olan Options bileşeni ise etiketten yalnız aşağıdaki ortak öznitelikleri ve olayları alır, value gibi diğerleri this.props'ta kalır.

Yalnızca elemana ait olmayan düz değerleri (items: [...]) DOM elemanına yaymayın; attribute olarak yazılırlar.

Bileşen etiketinde: ortak öznitelikler bileşenin elemanına düşer (attribute fallthrough)

<CountBadge class="nav__count" id="count-notes" count={…}/> yazıldığında yalnızca ortak öznitelikler — class/className (bileşenin kendi sınıfıyla birleşir), style, id, tabindex, role, aria-*, data-* — bileşenin kendi elemanına otomatik uygulanır; getter'lar canlıdır. Bileşenin kendi onConfigured/initializeComponent/view'ında yazdığı aynı attribute kazanır (uygulama yapıcıda, bunlardan önce olur). Veri ve geri çağrı prop'ları (count, note, title, onSave, disabled…) elemana yazılmaz — title bilerek listede değil, bileşenlerde yaygın bir veri prop'u adıdır. Tüm prop'lar, düşenler dâhil, bileşenin props'unda okunabilir kalır (sınıfta this.props, fonksiyonda parametre, Options'ta fabrikanın parametresi). TypeScript'te bu öznitelikler ve DOM olay prop'ları her bileşen etiketinde kabul edilir, yapıcısı kendi prop tipini alan bileşende de; bileşen aynı adlı bir prop tanımlıyorsa onun kendi tipi kullanılır.

Kenar çubuğundaki klasör bağlantısı not sayısını küçük bir rozette gösterir:

import { Component, read, type Bind } from "@motifx/core";

export class CountBadge extends Component<HTMLSpanElement, { count: Bind<number> }> {
  override onConfigured() {
    this.class.add("badge");
  }

  override view() {
    return <>{() => String(read(this.props.count))}</>;
  }
}
import { Component, ComponentBase, motifComponent, read, type Bind } from "@motifx/core";

export class CountBadge extends Component<HTMLSpanElement, { count: Bind<number> }> {
  static override elementTag = "span";

  override onConfigured() {
    this.class.add("badge");
  }

  override view() {
    return motifComponent("text", (node: ComponentBase) => node.bindings.text(() => String(read(this.props.count))));
  }
}
import { read, type Bind } from "@motifx/core";

export function CountBadge(props: { count: Bind<number> }) {
  return <span class="badge">{() => String(read(props.count))}</span>;
}
import { ComponentBase, motifComponent, read, type Bind } from "@motifx/core";

export function CountBadge(props: { count: Bind<number> }) {
  return motifComponent("span", (span: ComponentBase) => {
    span.class.add("badge");
    span.bindings.text(() => String(read(props.count)));
  });
}
import { motifOptions, read, type Bind } from "@motifx/core";

export const CountBadge = (props: { count: Bind<number> }) => motifOptions({
  el: "span",

  onConfigured() {
    this.class.add("badge");
  },

  view() {
    return <>{() => String(read(props.count))}</>;
  },
});
import { ComponentBase, motifComponent, motifOptions, read, type Bind } from "@motifx/core";

export const CountBadge = (props: { count: Bind<number> }) => motifOptions({
  el: "span",

  onConfigured() {
    this.class.add("badge");
  },

  view() {
    return motifComponent("text", (node: ComponentBase) => node.bindings.text(() => String(read(props.count))));
  },
});
import { Component, read } from "@motifx/core";

export class CountBadge extends Component {
  static elementTag = "span";

  onConfigured() {
    this.class.add("badge");
  }

  view() {
    return <>{() => String(read(this.props.count))}</>;
  }
}
import { Component, motifComponent, read } from "@motifx/core";

export class CountBadge extends Component {
  static elementTag = "span";

  onConfigured() {
    this.class.add("badge");
  }

  view() {
    return motifComponent("text", (node) => node.bindings.text(() => String(read(this.props.count))));
  }
}
import { read } from "@motifx/core";

export function CountBadge(props) {
  return <span class="badge">{() => String(read(props.count))}</span>;
}
import { motifComponent, read } from "@motifx/core";

export function CountBadge(props) {
  return motifComponent("span", (span) => {
    span.class.add("badge");
    span.bindings.text(() => String(read(props.count)));
  });
}
import { motifOptions, read } from "@motifx/core";

export const CountBadge = (props) => motifOptions({
  el: "span",

  onConfigured() {
    this.class.add("badge");
  },

  view() {
    return <>{() => String(read(props.count))}</>;
  },
});
import { motifComponent, motifOptions, read } from "@motifx/core";

export const CountBadge = (props) => motifOptions({
  el: "span",

  onConfigured() {
    this.class.add("badge");
  },

  view() {
    return motifComponent("text", (node) => node.bindings.text(() => String(read(props.count))));
  },
});
<CountBadge class="nav__count" id="count-notes" count={() => this.notes.count("notes")} />
<CountBadge class="nav__count" id="count-notes" count={() => notes.count("notes")} />
motifComponent(CountBadge, { class: "nav__count", id: "count-notes", count: () => this.notes.count("notes") });
motifComponent(CountBadge, { class: "nav__count", id: "count-notes", count: () => notes.count("notes") });
<CountBadge class="nav__count" id="count-notes" count={() => this.notes.count("notes")} />
<CountBadge class="nav__count" id="count-notes" count={() => notes.count("notes")} />
motifComponent(CountBadge, { class: "nav__count", id: "count-notes", count: () => this.notes.count("notes") });
motifComponent(CountBadge, { class: "nav__count", id: "count-notes", count: () => notes.count("notes") });

Üç biçimde de sonuç aynıdır: <span class="nav__count badge" id="count-notes"> (etiketteki nav__count yapıcıda, bileşenin kendi badge'i kurulumda eklenir). Sınıf bileşeninde eleman generic'ten (Component<HTMLSpanElement>), fonksiyonda döndürülen kökten, Options'ta el'den gelir. Fonksiyon bileşeninin kökü <span {...props}/> ise aynı değer ikinci kez uygulanmaz. Elemanı olmayan (fragment) bileşende bu öznitelikler sessizce yok sayılır; DOM olayları ise içeriğin üst düzey elemanlarını dinler (bkz. Kök eleman).

class ve className

İkisi de desteklenir. Değer; string, dizi, nesne veya getter fonksiyonu olabilir. Not satırının sınıfları:

<a class="row" />
<a class={() => `row${open() ? " active" : ""}`} />
<a class={`row ${note.title.trim() ? "" : "untitled"}`} />
const fixed = motifComponent("a", (row: ComponentBase) => row.class.add("row"));
const active = motifComponent("a", (row: ComponentBase) => row.class.add(() => `row${open() ? " active" : ""}`));
const untitled = motifComponent("a", (row: ComponentBase) => row.class.add(() => `row ${note.title.trim() ? "" : "untitled"}`));
<a class="row" />
<a class={() => `row${open() ? " active" : ""}`} />
<a class={`row ${note.title.trim() ? "" : "untitled"}`} />
const fixed = motifComponent("a", (row) => row.class.add("row"));
const active = motifComponent("a", (row) => row.class.add(() => `row${open() ? " active" : ""}`));
const untitled = motifComponent("a", (row) => row.class.add(() => `row ${note.title.trim() ? "" : "untitled"}`));

Birinci satır sabit sınıftır, ikincisi getter'dır, üçüncüdeki şablon dizesi getter'a sarılır ve canlı kalır.

Nesnede anahtar sınıf adı, değer koşuldur: doğruysa sınıf eklenir, değilse kaldırılır. Düz DOM etiketinde true/false gibi sabitler bir kez uygulanır; diğer her değer getter'a sarılır ve canlıdır. Nesne bir dizinin içinde de yazılabilir; dizideki her öğe tek başına yazılmış gibi işlenir. class.add'e verilen nesnede ise fonksiyon olmayan değer çağrı anında bir kez okunur. Bileşen etiketinde de böyledir: derleyici orada yalnız ternary'yi sarar, class değeri bileşenin elemanına class.add ile uygulanır; <CountBadge class={{ empty: count === 0 }} /> yazımındaki koşul, şablon dizesi ve class={note.cls} etiket kurulurken bir kez değerlenir. Canlı koşulu JSX'siz yazımda ve bileşen etiketinde siz fonksiyon olarak verirsiniz ({ active: () => open() }, class={() => …}):

<a class={{ row: true, active: open(), untitled: !note.title.trim() }} />
<a class={["row", { active: () => open() }]} />
const flags = motifComponent("a", (row: ComponentBase) => row.class.add({ row: true, active: () => open(), untitled: () => !note.title.trim() }));
const mixed = motifComponent("a", (row: ComponentBase) => row.class.add(["row", { active: () => open() }]));
<a class={{ row: true, active: open(), untitled: !note.title.trim() }} />
<a class={["row", { active: () => open() }]} />
const flags = motifComponent("a", (row) => row.class.add({ row: true, active: () => open(), untitled: () => !note.title.trim() }));
const mixed = motifComponent("a", (row) => row.class.add(["row", { active: () => open() }]));

Kurulumdan sonra sınıflar bileşenin class yardımcısıyla yönetilir: class.add, class.remove, class.has (sınıfta ve Options'ta this.class, fonksiyon bileşeninde kök bileşenin class'ı) (bkz. Görünüş).

style

style string, nesne veya getter alır:

<a style="opacity: 0.6;" />
<a style={{ borderLeft: "3px solid var(--accent)" }} />
<a style={() => ({ opacity: note.archived ? 0.6 : 1 })} />
const dimmed = motifComponent("a", (row: ComponentBase) => row.style("opacity: 0.6;"));
const marked = motifComponent("a", (row: ComponentBase) => row.style({ borderLeft: "3px solid var(--accent)" }));
const live = motifComponent("a", (row: ComponentBase) => row.style(() => ({ opacity: note.archived ? 0.6 : 1 })));
<a style="opacity: 0.6;" />
<a style={{ borderLeft: "3px solid var(--accent)" }} />
<a style={() => ({ opacity: note.archived ? 0.6 : 1 })} />
const dimmed = motifComponent("a", (row) => row.style("opacity: 0.6;"));
const marked = motifComponent("a", (row) => row.style({ borderLeft: "3px solid var(--accent)" }));
const live = motifComponent("a", (row) => row.style(() => ({ opacity: note.archived ? 0.6 : 1 })));

Getter canlıdır: bağımlılıklar değişince yeniden uygulanır; getter'ın yeni sonucunda bulunmayan anahtarlar temizlenir ({color} → {opacity} geçişinde color silinir). Getter string döndürürse cssText tümüyle değişir (o elemandaki diğer inline stiller de gider). x-style={...} style ile birebir aynıdır. style(fn) tek izleyici tutar; yeniden çağrılırsa önceki durur. Getter'dan sonra nesneyle yazılan anahtarlar (style({ color: "blue" })) getter'dan alınır, getter onlara bir daha yazmaz; getter'dan sonra yazılan dize getter'ı durdurur. -- ile başlayan CSS değişkenleri nesnede ve getter'da öteki anahtarlar gibi yazılır (style={{ "--accent": note.color }}); değeri null ya da boş dizge olan değişken kaldırılır.

Hazır bileşenleri çocuk olarak yerleştirme

JSX parçalarını bir değişkende toplayıp çocuk konumuna koyabilirsiniz; değer bir bileşen ya da bileşen dizisiyse metne çevrilmeden controls'a yerleştirilir. Değer bileşen değilse (dizge, sayı…) her zamanki reaktif metin bağı kurulur; bir prop'un bileşen mi veri mi olduğunu ayrıca bildirmeniz gerekmez. JSX'siz yazımda bu ayrımı siz yaparsınız: bileşen controls.add ile, metin bir "text" bileşeniyle yerleşir.

Prop'a verilen JSX süslü parantezsiz de yazılabilir: toolbar=<div class="toolbar">…</div> ile toolbar={<div class="toolbar">…</div>} aynı derlenir.

Sayfaların ortak çerçevesi Pane, üstte bir araç çubuğu bileşeni, altında bir başlık metni alır:

import { Component, type ComponentBase } from "@motifx/core";

export class Pane extends Component<HTMLDivElement, { toolbar?: ComponentBase; title: string }> {
  override onConfigured() {
    this.class.add("pane");
  }

  override view() {
    return (
      <>
        {this.props.toolbar}
        <h2>{this.props.title}</h2>
      </>
    );
  }
}
import { Component, ComponentBase, motifComponent, motifFragment } from "@motifx/core";

export class Pane extends Component<HTMLDivElement, { toolbar?: ComponentBase; title: string }> {
  static override elementTag = "div";

  override onConfigured() {
    this.class.add("pane");
  }

  override view() {
    const heading = motifComponent("h2", (h2: ComponentBase) => h2.controls.add(motifComponent("text", (node: ComponentBase) => node.bindings.add("textContent", this.props, "title"))));
    return motifFragment({ childs: this.props.toolbar ? [this.props.toolbar, heading] : [heading] });
  }
}
import type { ComponentBase } from "@motifx/core";

export function Pane(props: { toolbar?: ComponentBase; title: string }) {
  return (
    <div class="pane">
      {props.toolbar}
      <h2>{props.title}</h2>
    </div>
  );
}
import { ComponentBase, motifComponent } from "@motifx/core";

export function Pane(props: { toolbar?: ComponentBase; title: string }) {
  return motifComponent("div", (pane: ComponentBase) => {
    pane.class.add("pane");
    if (props.toolbar) pane.controls.add(props.toolbar);
    pane.controls.add(motifComponent("h2", (h2: ComponentBase) => h2.controls.add(motifComponent("text", (node: ComponentBase) => node.bindings.add("textContent", props, "title")))));
  });
}
import { motifOptions, type ComponentBase } from "@motifx/core";

export const Pane = (props: { toolbar?: ComponentBase; title: string }) => motifOptions({
  el: "div",

  onConfigured() {
    this.class.add("pane");
  },

  view() {
    return (
      <>
        {props.toolbar}
        <h2>{props.title}</h2>
      </>
    );
  },
});
import { ComponentBase, motifComponent, motifFragment, motifOptions } from "@motifx/core";

export const Pane = (props: { toolbar?: ComponentBase; title: string }) => motifOptions({
  el: "div",

  onConfigured() {
    this.class.add("pane");
  },

  view() {
    const heading = motifComponent("h2", (h2: ComponentBase) => h2.controls.add(motifComponent("text", (node: ComponentBase) => node.bindings.add("textContent", props, "title"))));
    return motifFragment({ childs: props.toolbar ? [props.toolbar, heading] : [heading] });
  },
});
import { Component } from "@motifx/core";

export class Pane extends Component {
  static elementTag = "div";

  onConfigured() {
    this.class.add("pane");
  }

  view() {
    return (
      <>
        {this.props.toolbar}
        <h2>{this.props.title}</h2>
      </>
    );
  }
}
import { Component, motifComponent, motifFragment } from "@motifx/core";

export class Pane extends Component {
  static elementTag = "div";

  onConfigured() {
    this.class.add("pane");
  }

  view() {
    const heading = motifComponent("h2", (h2) => h2.controls.add(motifComponent("text", (node) => node.bindings.add("textContent", this.props, "title"))));
    return motifFragment({ childs: this.props.toolbar ? [this.props.toolbar, heading] : [heading] });
  }
}
export function Pane(props) {
  return (
    <div class="pane">
      {props.toolbar}
      <h2>{props.title}</h2>
    </div>
  );
}
import { motifComponent } from "@motifx/core";

export function Pane(props) {
  return motifComponent("div", (pane) => {
    pane.class.add("pane");
    if (props.toolbar) pane.controls.add(props.toolbar);
    pane.controls.add(motifComponent("h2", (h2) => h2.controls.add(motifComponent("text", (node) => node.bindings.add("textContent", props, "title")))));
  });
}
import { motifOptions } from "@motifx/core";

export const Pane = (props) => motifOptions({
  el: "div",

  onConfigured() {
    this.class.add("pane");
  },

  view() {
    return (
      <>
        {props.toolbar}
        <h2>{props.title}</h2>
      </>
    );
  },
});
import { motifComponent, motifFragment, motifOptions } from "@motifx/core";

export const Pane = (props) => motifOptions({
  el: "div",

  onConfigured() {
    this.class.add("pane");
  },

  view() {
    const heading = motifComponent("h2", (h2) => h2.controls.add(motifComponent("text", (node) => node.bindings.add("textContent", props, "title"))));
    return motifFragment({ childs: props.toolbar ? [props.toolbar, heading] : [heading] });
  },
});
<Pane toolbar={<div class="toolbar"><span class="toolbar__spacer"></span></div>} title="Ayarlar" />
const toolbar = motifComponent("div", (bar: ComponentBase) => {
  bar.class.add("toolbar");
  bar.controls.add(motifComponent("span", (span: ComponentBase) => span.class.add("toolbar__spacer")));
});
motifComponent(Pane, { toolbar, title: "Ayarlar" });
<Pane toolbar={<div class="toolbar"><span class="toolbar__spacer"></span></div>} title="Ayarlar" />
const toolbar = motifComponent("div", (bar) => {
  bar.class.add("toolbar");
  bar.controls.add(motifComponent("span", (span) => span.class.add("toolbar__spacer")));
});
motifComponent(Pane, { toolbar, title: "Ayarlar" });

Şablon sonradan değişecekse getter yazın

Yerleştirme kararı kurulum anında verilir ve tek seferliktir: prop kurulurken bileşen tutuyorsa yerleşir, sonradan değişse bile DOM güncellenmez (yerleşen bileşenin kendi bağları her zamanki gibi canlıdır). Prop'lar etiket kurulurken bir kez alınan düz bir nesnedir: toolbar={toolbar} gibi düz bir ifade, sonradan atanan şablonu çocuğa ulaştırmaz; {() => props.toolbar} getter'ı da bunu izlemez, çünkü içinde reaktif bir okuma yoktur.

Şablon başta null olup sonradan gelecekse ya da çalışırken takas edilecekse ebeveyn onu ternary ya da getter ile verir (toolbar={state.confirming ? <div class="toolbar">…</div> : null}; prop'taki ternary daima getter'a sarılır). Çocuk, prop'u Bind<ComponentBase | null> bildirir ve getter içinde read() ile okur; bu bir yuva (Frame) kurar ve her değişimde getter'ın o anki sonucunu yerleştirir:

<div class="pane">{() => read(props.toolbar)}</div>
const pane = motifComponent("div", (pane: ComponentBase) => {
  pane.class.add("pane");
  pane.bindings.method(() => read(props.toolbar));
});
<div class="pane">{() => read(props.toolbar)}</div>
const pane = motifComponent("div", (pane) => {
  pane.class.add("pane");
  pane.bindings.method(() => read(props.toolbar));
});

read() olmadan yazılan {() => props.toolbar} getter prop'unu izlemez: yalnız ilk şablonu yerleştirir, şablon başta null ise hiçbir şey göstermez. Getter yazmadığınız hâlde bir bileşen sonradan metin bağına düşerse çerçeve DOM'a hiçbir şey yazmaz ve geliştirme modunda MJX204 uyarısıyla getter biçimini önerir.

Ham HTML — x-html

Bir elemanın innerHTML'ini reaktif olarak ayarlamak için x-html kullanın; JSX'siz karşılığı bindings.html. Örnekteki renderMarkdown, çıktısını temizleyen bir Markdown kütüphanesinin fonksiyonudur:

<article class="preview" x-html={() => renderMarkdown(note.body)}></article>
const preview = motifComponent("article", (article: ComponentBase) => {
  article.class.add("preview");
  article.bindings.html(() => renderMarkdown(note.body));
});
<article class="preview" x-html={() => renderMarkdown(note.body)}></article>
const preview = motifComponent("article", (article) => {
  article.class.add("preview");
  article.bindings.html(() => renderMarkdown(note.body));
});

Güvenlik: x-html işaretlenmemiş HTML enjekte eder. Yalnız güvendiğiniz içerikte, ya da çıktısını temizleyen bir dönüştürücüyle kullanın.

Olaylar

on... prop'ları DOM olaylarına bağlanır; JSX'siz karşılığı motif.on. İşleyici (sender, event) ya da yalnız (event) imzasıyla yazılır; MotifJS parametre sayısına bakar:

<button type="button" onclick={() => this.notes.togglePin(note)}>Sabitle</button>
<input class="editor__title" oninput={(e) => (note.title = (e.target as HTMLInputElement).value)} />
<a rel="router" href={`/${this.folder}`} onclick={(sender, e) => sender.class.add("visited")}>Geri</a>
<form class="confirm" onsubmit:prevent={() => this.remove(note)}>…</form>
<button type="button" onclick={() => notes.togglePin(note)}>Sabitle</button>
<input class="editor__title" oninput={(e) => (note.title = (e.target as HTMLInputElement).value)} />
<a rel="router" href={`/${folder()}`} onclick={(sender, e) => sender.class.add("visited")}>Geri</a>
<form class="confirm" onsubmit:prevent={() => remove(note)}>…</form>
const pin = motifComponent("button", (button: ComponentBase) => {
  button.attr.add({ type: "button" });
  button.motif.on("click", () => this.notes.togglePin(note));
  button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Sabitle")));
});
const title = motifComponent("input", (input: ComponentBase) => {
  input.class.add("editor__title");
  input.motif.on("input", (_sender: ComponentBase, e: Event) => (note.title = (e.target as HTMLInputElement).value));
});
const back = motifComponent("a", (a: ComponentBase) => {
  a.attr.add({ rel: "router", href: () => `/${this.folder}` });
  a.motif.on("click", (sender: ComponentBase, _e: Event) => sender.class.add("visited"));
  a.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Geri")));
});
const confirm = motifComponent("form", (form: ComponentBase) => {
  form.class.add("confirm");
  form.motif.on("submit:prevent", () => this.remove(note));
});
const pin = motifComponent("button", (button: ComponentBase) => {
  button.attr.add({ type: "button" });
  button.motif.on("click", () => notes.togglePin(note));
  button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Sabitle")));
});
const title = motifComponent("input", (input: ComponentBase) => {
  input.class.add("editor__title");
  input.motif.on("input", (_sender: ComponentBase, e: Event) => (note.title = (e.target as HTMLInputElement).value));
});
const back = motifComponent("a", (a: ComponentBase) => {
  a.attr.add({ rel: "router", href: () => `/${folder()}` });
  a.motif.on("click", (sender: ComponentBase, _e: Event) => sender.class.add("visited"));
  a.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Geri")));
});
const confirm = motifComponent("form", (form: ComponentBase) => {
  form.class.add("confirm");
  form.motif.on("submit:prevent", () => remove(note));
});
<button type="button" onclick={() => this.notes.togglePin(note)}>Sabitle</button>
<input class="editor__title" oninput={(e) => (note.title = e.target.value)} />
<a rel="router" href={`/${this.folder}`} onclick={(sender, e) => sender.class.add("visited")}>Geri</a>
<form class="confirm" onsubmit:prevent={() => this.remove(note)}>…</form>
<button type="button" onclick={() => notes.togglePin(note)}>Sabitle</button>
<input class="editor__title" oninput={(e) => (note.title = e.target.value)} />
<a rel="router" href={`/${folder()}`} onclick={(sender, e) => sender.class.add("visited")}>Geri</a>
<form class="confirm" onsubmit:prevent={() => remove(note)}>…</form>
const pin = motifComponent("button", (button) => {
  button.attr.add({ type: "button" });
  button.motif.on("click", () => this.notes.togglePin(note));
  button.controls.add(motifComponent("text", (node) => node.setText("Sabitle")));
});
const title = motifComponent("input", (input) => {
  input.class.add("editor__title");
  input.motif.on("input", (_sender, e) => (note.title = e.target.value));
});
const back = motifComponent("a", (a) => {
  a.attr.add({ rel: "router", href: () => `/${this.folder}` });
  a.motif.on("click", (sender, _e) => sender.class.add("visited"));
  a.controls.add(motifComponent("text", (node) => node.setText("Geri")));
});
const confirm = motifComponent("form", (form) => {
  form.class.add("confirm");
  form.motif.on("submit:prevent", () => this.remove(note));
});
const pin = motifComponent("button", (button) => {
  button.attr.add({ type: "button" });
  button.motif.on("click", () => notes.togglePin(note));
  button.controls.add(motifComponent("text", (node) => node.setText("Sabitle")));
});
const title = motifComponent("input", (input) => {
  input.class.add("editor__title");
  input.motif.on("input", (_sender, e) => (note.title = e.target.value));
});
const back = motifComponent("a", (a) => {
  a.attr.add({ rel: "router", href: () => `/${folder()}` });
  a.motif.on("click", (sender, _e) => sender.class.add("visited"));
  a.controls.add(motifComponent("text", (node) => node.setText("Geri")));
});
const confirm = motifComponent("form", (form) => {
  form.class.add("confirm");
  form.motif.on("submit:prevent", () => remove(note));
});

İşleyicinin fırlattığı hata (async işleyicide reddedilen söz dahil) MJX123 koduyla errorHandler'a bildirilir.

İşleyici konumuna yazılan ternary (onclick={a ? b : c}) çalışmaz; bkz. Özniteliklerde ternary. Olay adları küçük harfle yazılır (onclick, onchange, oninput). Değiştiriciler (:once, :prevent, :stop vb.) desteklenir; JSX öznitelik adı tek bir : alabildiği için etikette öznitelik başına bir değiştirici yazılır, motif.on("click:once:prevent", fn) ise zincirler — ayrıntı için Olaylar.

on:ad / on-ad / on_ad yazımı işleyiciyi olduğu gibi etiketin bileşenine motif.on("ad", fn) ile bağlar (yalnız önek atılır: on-note-saved → note-saved); derleyicinin tanımadığı DOM olayları (mouseenter) ve özel olaylar (motif.trigger) için kullanılır, aynı parametre sayısı kuralı geçerlidir:

<NoteRow note={note} folder={this.folder} on:mouseenter={() => (this.state.hovered = note.id)} />
<button type="button" on:click={(sender, e) => sender.motif.hide()}>Kapat</button>
<NoteRow note={note} folder={folder()} on:mouseenter={() => (state.hovered = note.id)} />
<button type="button" on:click={(sender, e) => sender.motif.hide()}>Kapat</button>
const row = motifComponent(NoteRow, {
  note,
  folder: this.folder,
  initializeComponent: (row: ComponentBase) => row.motif.on("mouseenter", () => (this.state.hovered = note.id)),
});
const close = motifComponent("button", (button: ComponentBase) => {
  button.attr.add({ type: "button" });
  button.motif.on("click", (sender: ComponentBase, _e: Event) => sender.motif.hide());
  button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Kapat")));
});
const row = motifComponent(NoteRow, {
  note,
  folder: folder(),
  initializeComponent: (row: ComponentBase) => row.motif.on("mouseenter", () => (state.hovered = note.id)),
});
const close = motifComponent("button", (button: ComponentBase) => {
  button.attr.add({ type: "button" });
  button.motif.on("click", (sender: ComponentBase, _e: Event) => sender.motif.hide());
  button.controls.add(motifComponent("text", (node: ComponentBase) => node.setText("Kapat")));
});
<NoteRow note={note} folder={this.folder} on:mouseenter={() => (this.state.hovered = note.id)} />
<button type="button" on:click={(sender, e) => sender.motif.hide()}>Kapat</button>
<NoteRow note={note} folder={folder()} on:mouseenter={() => (state.hovered = note.id)} />
<button type="button" on:click={(sender, e) => sender.motif.hide()}>Kapat</button>
const row = motifComponent(NoteRow, {
  note,
  folder: this.folder,
  initializeComponent: (row) => row.motif.on("mouseenter", () => (this.state.hovered = note.id)),
});
const close = motifComponent("button", (button) => {
  button.attr.add({ type: "button" });
  button.motif.on("click", (sender, _e) => sender.motif.hide());
  button.controls.add(motifComponent("text", (node) => node.setText("Kapat")));
});
const row = motifComponent(NoteRow, {
  note,
  folder: folder(),
  initializeComponent: (row) => row.motif.on("mouseenter", () => (state.hovered = note.id)),
});
const close = motifComponent("button", (button) => {
  button.attr.add({ type: "button" });
  button.motif.on("click", (sender, _e) => sender.motif.hide());
  button.controls.add(motifComponent("text", (node) => node.setText("Kapat")));
});

Bileşen etiketinde on: ile verilen işleyici bileşenin elemanına bağlanır; fonksiyon bileşeninde bu, döndürülen köktür (NoteRow'da <a>).

Özel x- öznitelikleri

Derleyicinin tanıdığı, DOM özniteliği olmayan yardımcı prop'lar ve indikleri çağrılar:

Öznitelik İşlev JSX'siz karşılığı
x-ref={(s) => ...} / x-ref={this.alan} / x-ref={alan} Etiketin bileşen örneğini yakalar; kısa ref ile aynıdır ve iki biçim her etikette çalışır. Aynı etikette ref ile x-ref birlikte yazılırsa ikisi de kaynaktaki sırayla çalışır — ayrıntı için Eleman ve Görünüş. nesne biçiminde ref: (s) => …
x-key={...} key ile aynıdır (indexkey de kabul edilir); bkz. key ile öğe kimliği. indexkey: () => …
x-text={() => metin} Elemanın bütün textContent'ini reaktif ayarlar; elemanda başka çocuk varsa ilk değişimde onları DOM'dan siler. Kardeşleriyle duran canlı metin için çocuk konumunda {() => metin} yazın. bindings.text(fn)
x-html={() => html} innerHTML'i reaktif ayarlar. bindings.html(fn)
x-value={() => değer} Elemanın value özelliğini tek yönlü, reaktif ayarlar; iki yön için x-model. bindings.value(fn)
x-model={() => state.query} İki yönlü bağ; ayrıntı için Formlar. bindings.model(() => state.query, (v) => (state.query = v))
x-watch={() => ...} Etiketin bileşenine bağlı bir izleyici kurar: fonksiyon içindeki reaktif okumalar izlenir, değişimde yeniden çalışır; bileşen serbest bırakılınca durur. DOM'a bir şey yazmaz. bindings.watch(fn)
x-wait={() => bool} Koşul true iken bileşeni bekletir/gizler. Koşullu gösterimin birincil yolu. preconfig: (s) => s.bindings.wait(fn)
x-display={() => bool} x-wait'in tersi: koşul true iken gösterir, false iken gizler. preconfig: (s) => s.bindings.display(fn)
x-chunk / x-chunk={50} Elemanın çocuklarını dilim dilim kurar: birlikte eklenen ilk 100 çocuk (sayı verilmişse ilk 50) hemen kurulur, kalanı iz bırakıp sonraki karelerde kurulur. Bir karede kurulacak sayıyı çerçeve kare süresine bakarak belirler; sayı verilmişse her karede en az o kadar kurulur. Altındaki {liste.map(...)} satırları sırası gelince oluşturulur. Bkz. Büyük listeler. options: { chunk: true } / options: { chunk: 50 }
x-style={...} style ile aynı (getter canlı). style(...)
x-building, x-built, x-config, x-configured, x-mounted, x-activated, x-deactivated, x-disposing, x-disposed, x-initializing, x-initialized, x-visibilitychanged Yaşam döngüsü kancaları (prop biçiminde; x: yazımı da geçerlidir). Aynı etikette onBuilt ile x-built gibi birden çok yazım birleşir ve hepsi kaynaktaki sırayla çalışır. nesne biçiminde onBuilt: (s, e) => … ya da kurulumda s.motif.on("x:built", …)

x-text, x-html, x-value, x-model ve x-watch getter, metot başvurusu ya da nesne ister; düz dizge/boolean verilirse derleme MJX011 ile durur. x-wait ve x-display dahil her direktifte fonksiyon tutan değişken ya da üye getter olarak çağrılır: x-display={isAuthor} ve x-wait={this.isLoading} (this ile çağrılır), x-display={() => isAuthor()} ile aynı çalışır. x-chunk bunun dışındadır: değersiz yazılır ya da sayı alır (x-chunk, x-chunk={50}, x-chunk={this.minRows}); dizge, nesne ya da ok fonksiyonu verilirse derleme MJX012 ile durur. İşlev tutan bir değişken ya da üye verilirse işlev bileşen kurulurken bir kez çağrılır ve dönen değer kullanılır; değer sonradan değişirse izlenmez. options: { chunk } aynı kurala uyar.

Çekirdeğin direktifleri dışında x ile başlayan adlar sıradan addır: xField, x-field ya da x:field yazıldığı gibi kalır. Bileşen etiketinde aynı adla prop olur (this.props["x-field"]), düz etikette özniteliğe yazılır. Derleyici bu adlar için uyarı vermez; yanlış yazılmış bir direktif de (x-wiat) sıradan prop olarak kalır.

x-wait ve x-display bileşen kurulmadan önce değerlendirilir; bu yüzden JSX'siz yazımda preconfig'e yazılır, initializeComponent'e değil. Not listesi, ilk not gelene kadar satır kutusunu bekletir; boş liste paragrafı yalnız görünür satır yokken gösterilir:

<div class="rows" x-wait={() => this.notes.state.items.length === 0}>…</div>
<p class="list__empty" x-display={() => this.visible.length === 0}>…</p>
<div class="rows" x-wait={() => notes.state.items.length === 0}>…</div>
<p class="list__empty" x-display={() => visible().length === 0}>…</p>
motifComponent("div", {
  preconfig: (rows: ComponentBase) => rows.bindings.wait(() => this.notes.state.items.length === 0),
  initializeComponent: (rows: ComponentBase) => rows.class.add("rows"),
});
motifComponent("p", {
  preconfig: (p: ComponentBase) => p.bindings.display(() => this.visible.length === 0),
  initializeComponent: (p: ComponentBase) => p.class.add("list__empty"),
});
motifComponent("div", {
  preconfig: (rows: ComponentBase) => rows.bindings.wait(() => notes.state.items.length === 0),
  initializeComponent: (rows: ComponentBase) => rows.class.add("rows"),
});
motifComponent("p", {
  preconfig: (p: ComponentBase) => p.bindings.display(() => visible().length === 0),
  initializeComponent: (p: ComponentBase) => p.class.add("list__empty"),
});
<div class="rows" x-wait={() => this.notes.state.items.length === 0}>…</div>
<p class="list__empty" x-display={() => this.visible.length === 0}>…</p>
<div class="rows" x-wait={() => notes.state.items.length === 0}>…</div>
<p class="list__empty" x-display={() => visible().length === 0}>…</p>
motifComponent("div", {
  preconfig: (rows) => rows.bindings.wait(() => this.notes.state.items.length === 0),
  initializeComponent: (rows) => rows.class.add("rows"),
});
motifComponent("p", {
  preconfig: (p) => p.bindings.display(() => this.visible.length === 0),
  initializeComponent: (p) => p.class.add("list__empty"),
});
motifComponent("div", {
  preconfig: (rows) => rows.bindings.wait(() => notes.state.items.length === 0),
  initializeComponent: (rows) => rows.class.add("rows"),
});
motifComponent("p", {
  preconfig: (p) => p.bindings.display(() => visible().length === 0),
  initializeComponent: (p) => p.class.add("list__empty"),
});

Koşul ilk değerlendirmede true ise (x-display için false) bileşen hiç kurulmaz, alt ağacı da DOM'a girmez. Sonradan açılınca bir kez kurulur, tekrar kapanınca örnek korunur. {koşul && <X/>} ise her açılışta yeni örnek kurar ve her kapanışta serbest bırakır; ayrıntılı karşılaştırma için bkz. Koşullu Gösterim ve Listeler.

Özniteliklerde ternary

Bir öznitelik ya da bileşen prop'una yazılan üçlü operatör daima tembel sarılır: <Setting name="Tema" hint={settings.state.theme === "system" ? "Sisteme uyar." : "Elle seçildi."} /> derlenince prop hint: () => settings.state.theme === "system" ? … : … olur.

Bu bilinçli bir tasarım kararıdır: sarma olmasaydı ifade view() sırasında bir kez değerlenir ve donardı. Karşılığı, prop'u alan bileşenin onu Bind<T> olarak bildirip read() ile okumasıdır; Setting'in hint prop'u böyledir (bkz. Düz değer ve canlı değer). Düz tipli bir prop'u doğrudan kullanan bileşen bu sözleşmeyi çiğner ve çalışma zamanında bir fonksiyon alır.

Bileşen prop'unda diğer ifadeler ({p.a}, {f()}, {a && b}) sarılmaz, etiket kurulurken alınan değer olarak geçer; düz DOM etiketinde ise {p.a} ve {a && b} getter'a sarılıp canlı kalır, {f()} bir kez değerlendirilir (bkz. Öznitelikler). JSX'siz yazımda sarma yoktur; canlı olmasını istediğiniz prop'u siz getter olarak verirsiniz: motifComponent(Setting, { name: "Tema", hint: () => (… ? "Sisteme uyar." : "Elle seçildi.") }).

Olay özniteliğindeki ternary de sarılır, ama bu getter'ı okuyan bir alıcı yoktur: onclick={note.pinned ? undefined : () => notes.archive(note)} dinleyici olarak ok fonksiyonunu kaydeder; tıklamada seçilen işleyici döndürülür ama çağrılmaz, derleme uyarısı ya da motif-lint bulgusu da çıkmaz. on:click, onclick:once ve bileşen etiketindeki olay için de böyledir. Koşulu işleyicinin içine yazın: onclick={() => { if (!note.pinned) notes.archive(note); }}.

Derleme zamanı uyarıları

@motifx/compiler, sessizce yanlış çalışan birkaç yazımı derleme sırasında terminale bildirir. Uyarılar derlemeyi durdurmaz; kodun yanında dosya, satır ve kod çerçevesiyle görünür.

MJX004, MJX017 ve MJX018 için denetim şu kökleri reaktif sayar: this.state ve this.props; sınıfta alan başlatıcısında ya da constructor'da reactive, useModel, createSignal, createComputed veya createLazyComputed ile kurulan alanlar; aynı dosyada bu fabrikalarla kurulan modül store'ları; getter içinde bunlardan alınan takma adlar (const rows = this.state.rows, const { rows } = this.state). Başka dosyadan içe alınan store'u, bir fonksiyon içinde yaratılan ya da bir fonksiyondan dönen store'u, enjekte edilen servisi ve fonksiyon bileşeninin props'unu tanımaz; oradaki aynı kalıbı derleyici görmez. Getter içinde map, forEach, filter, reduce, flatMap, some, every, find gibi senkron dizi metotlarına verilen geri çağrılar getter'ın parçası sayılır; olay işleyicileri, setTimeout ve then geri çağrıları sayılmaz.

Kod Ne yakalar Doğrusu
MJX001 JSX çocuk konumunda parametreli blok gövdeli ok fonksiyonu içinde yerel değişken tanımlayıp koşul döndürmek. Derleyici koşulu kapanışın dışına taşır, değişken dallarda görünmez ve ReferenceError alırsınız. (Parametresiz okta yerel değişkenler korunur.) İfade gövdeli ok kullanın ya da mantığı adlandırılmış bir metoda taşıyın.
MJX002 Bileşen etiketinde derleyicinin tanıdığı bir DOM olay adının camelCase yazımı (onChange, onClick). Geri çağrı prop'u sanılır ama bileşenin elemanına DOM dinleyicisi olur; props.onChange tanımsız kalır. Derleyicinin tanımadığı adlar (onmouseenter, onpointer…, ontouch…, onfocusin, onanimation…, ontransition…) bileşen etiketinde dinleyici kurmaz: değer sıradan prop olarak geçer, uyarı da çıkmaz. Çakışmayan bir ad seçin (onValueChange, onConfirm). Tanınan bir olayda kasıtlı DOM dinleyicisi için küçük harf yazın: <Button onclick={…}> — bu uyarı üretmez. Tanınmayan bir olayı bileşenin elemanında dinlemek için on: yazımını kullanın: <NoteRow on:mouseenter={…}> (bkz. on:ad yazımı). Elemanı olmayan (fragment) bileşende dinleyici içeriğin üst düzey elemanlarını dinler.
MJX004 Reaktif getter içinde (JSX getter'ı, öznitelik getter'ı, direktif, effect, createComputed, bindings.*) reaktif bir liste find/findIndex/findLast/some/every ve x.alan === değer biçiminde bir yüklemle taranıyor: her koşu eşleşmeye kadar her elemanın o alanını okur ve abone olur; bu getter'ı taşıyan n bileşen varsa her değişiklik n² okuma demektir. Liste this. köklü ya da aynı dosyada reactive/useModel/signal ile kurulmuş bir store olmalı; includes, filter, map ve alan karşılaştırması olmayan yüklemler uyarmaz. Elemanı anahtarlı bir haritadan ya da bir kez hesaplanan bir computed'den alın; liste yalnız o anda gerekiyorsa untracked(() => list.find(...)) ile okuyun. Bkz. Her satırın listeyi taradığı tuzak.
MJX015 Sınıf bileşeninde bir ComponentBase metodunu (build, dispose, style, setText …) ya da erişicisini (context, isWait …) ezen üye bir yolda super.<ad>'a ulaşmadan bitiyor: hiç çağırmıyor ya da erken return, tek dallı if, döngü, catch veya geri çağrı içinde çağırıyor. MotifJS'in o metottaki işi o yolda çalışmaz (boş kalan bileşen, gezinmede sökülmeyen sayfa, kaybolan stil). Denetim JSX içermeyen .ts / .js dosyalarında da çalışır; taban sınıf dosyalar arasında izlenir. Üyeyi yeniden adlandırın ya da super.<ad>(…)'ı her yolda çağırın. build'de this.isBuilt / this.isDisposed / this.isWait, dispose'ta this.isDisposed ile yapılan erken dönüş muaftır; temel metot da o durumda bir şey yapmaz. Bkz. Sınıf üyelerinin adları.
MJX016 JSX çocuk konumundaki switch ya da return a ? b : c getter'ı switchCase/ternary'ye çevrilemedi: literal olmayan case etiketi, switch'ten önce return ya da JSX üreten deyim, switch'i terk eden break. Getter yazıldığı gibi bindings.method olarak çalışır; dal tutulmaz, içerik her değişimde yeniden kurulur. Etiketi literal yapın ya da koşulu dalların içine taşıyın; dal tutma gerekmiyorsa uyarı bilgi amaçlıdır.
MJX017 Reaktif getter, okuduğu reaktif bir değeri yazıyor: ++/--, += gibi birleşik atama ya da getter'ın başka yerde okuduğu alana düz atama ({() => this.state.n++}, effect(() => { stats.runs++; })). Okuduğunu yazan getter kendini tetikler, çalışma anında MJX203 ile durdurulur. Reaktif kök: this.state, this.props, sınıfta reactive/useModel/signal ile kurulan alanlar ve aynı dosyadaki modül store'ları. Getter'ın okumadığı bir alana düz atama (this.state.query = seed), düz sınıf alanına yazma (this.loadedId = id), olay işleyicilerindeki (onclick={() => …}) yazmalar ve getter'ın dışarıda izleyerek okumadığı bir değere untracked(...) içinde yapılan yazmalar uyarmaz. Yazmayı olay işleyicisine ya da bir metoda taşıyın; kalacaksa o değerin okumasını untracked içine alın ki getter ona abone olmasın (x++ ve birleşik atamada ifadenin tamamını, bir kilit bayrağında ise koşuldaki okumayı).
MJX018 Reaktif getter içinde reaktif liste yerinde değiştiriliyor: sort/reverse her zaman (listeyi okuyup yazarlar: {() => this.state.items.sort(cmp)}), push/pop/shift/unshift/splice ise getter o listeyi ayrıca okuyorsa (stats.log.length gibi). Listeyi okuyan getter kendini tetikler. Kopya üstünde çalışma ([...list].sort(), list.slice().sort()), yalnız yazan effect(() => { log.push(x); }) ve olay işleyicileri uyarmaz. Kopyayı sıralayın ya da sıralı listeyi bir alanda/computed'de tutun.

Uyarıları kapatmak için Vite eklentisine diagnostics: false verin:

compiler({ diagnostics: false })
compiler({ diagnostics: false })

Derleme hataları

Aşağıdaki yazımlar çalışan koda indirilemez; derleyici derlemeyi durdurur ve hatayı dosya, satır ve kod çerçevesiyle verir. Hata nesnesinin code alanı koddur.

Kod Ne yakalar Doğrusu
MJX008 Çocuk konumunda function () { … } ifadesi. Ok fonksiyonu yazın: {() => …}.
MJX009 DOM olayına dizge ya da boolean değer (onclick="go()"). İşleyici fonksiyon verin: onclick={() => go()}.
MJX010 Yaşam döngüsü kancasına fonksiyon olmayan değer (onBuilt="x"). Fonksiyon, fonksiyon listesi ya da fonksiyona başvuru verin.
MJX011 x-text, x-html, x-value, x-model, x-watch direktifine getter'a çevrilemeyen değer (x-text="düz"). Getter ya da başvuru verin: x-text={() => note.title}.
MJX012 x-wait/x-display direktifine nesne değişmezi; x-chunk direktifine sayı olamayacak değer (dizge, nesne, ok fonksiyonu). Boolean döndüren bir ifade ya da getter verin; x-chunk'ı değersiz yazın ya da sayı verin: x-chunk={50}.
MJX013 Derleyicinin desteklemediği bir JSX biçimi: ad alanlı etiket adı (<svg:rect>) ya da çocuk konumunda spread ({...items}). HTML etiketi olmayan, tiresiz küçük harfli bir ad (<card>) bu hatayı vermez; kapsamdaki aynı adlı tanımlayıcıya çözülür, böyle bir tanımlayıcı yoksa hata derlemede değil çalışma anında ReferenceError olarak gelir. Ad alanı önekini kaldırın (<rect>). Hazır bileşen dizisini {items} ile yerleştirin, veriden listeyi {items.map(…)} ile kurun.

Tip-bilinçli denetim: motif-lint (MJX005, MJX015)

Derleyici prop tiplerini göremez. Öznitelikteki ternary daima () => … ile sarıldığı için (bkz. Özniteliklerde ternary) <NoteRow folder={note.archived ? "archive" : "notes"}/> yazımında folder düz Folder bildirilmişse bileşen çalışma anında bir fonksiyon alır; TypeScript de sorun görmez, çünkü ternary'nin tipi o birleşimdir. Bu boşluğu TypeScript program'ı üzerinden çalışan ayrı bir denetim kapatır:

npx motif-lint                      # ./tsconfig.json
npx motif-lint -p tsconfig.app.json src/pages/NotePage.tsx   # tek proje / tek dosya
npx motif-lint --json --no-fail     # CI için makine çıktısı, çıkış kodu 0
Kod Ne yakalar Doğrusu
MJX005 Bileşen prop'una ternary yazılmış ama prop'un bildirilen tipi fonksiyon kabul etmiyor (folder: Folder). Bileşende prop'u Bind<T> bildirip read()/toGetter() ile okuyun. Değer gerçekten statikse ternary'yi JSX dışında hesaplayıp değişkeni geçin.
MJX015 Derleme uyarısıyla aynı kural (super'a ulaşmayan override). motif-lint taban sınıfı TypeScript tip denetleyicisiyle çözer ve tsconfig'teki bütün dosyaları tarar; Vite eklentisinin görmediği dosyalar (ör. testler) da denetlenir. Bkz. yukarıdaki MJX015.

Kural DOM etiketlerinde, Bind<T>/any/unknown/fonksiyon tipli prop'larda, key/x-*/on:* adlarında ve generic (value: T) prop'larda sessizdir. Bulgu varsa çıkış kodu 1'dir; package.json içine "lint:jsx": "motif-lint" koyup CI'ya bağlayabilirsiniz. tsconfig okunamazsa komut MJX014 hatasıyla durur.

Bu ifade neye derlendi? — explain

JSX, MotifJS'te bir görünüm dili değil, bağ API'sinin (bindings.add, bindings.when, controls.add…) kısa yazımıdır; derleyici de bir dönüştürücü değil, klasik anlamda bir lowering aşamasıdır. Kaynakla çıktı arasında benzerlik aramak anlamsızdır; ama "bu çalışınca ne olacak" sorusu tahmine bırakılmaz. Derleyicinin -S bayrağı gibi çalışan explain, her JSX ifadesi için indiği gerçek çağrıyı (üretilmiş koddan alınır, tahmin değildir), reaktiflik sınıfını ve bağımlılık yüzeyini yazar:

npx motif-explain src/pages/SettingsPage.tsx               # okunur döküm
npx motif-explain src/pages/SettingsPage.tsx --site prop   # yalnızca bileşen prop'ları
npx motif-explain src/pages/SettingsPage.tsx --json --code # makine çıktısı + üretilen kod

Şu dosya için:

import { Component } from "@motifx/core";
import { NotesService } from "../services/notes";
import { SettingsService } from "../services/settings";
import { Setting } from "../parts/Setting";

const archived = (count: number) => `${count} arşivlenmiş not`;

export default class SettingsPage extends Component {
  notes = this.getService(NotesService)!;
  settings = this.getService(SettingsService)!;

  override view() {
    return (
      <section class="page" title={archived(this.notes.count("archive"))}>
        <p>{this.settings.state.theme}</p>
        <small>{() => archived(this.notes.count("archive"))}</small>
        <Setting name="Tema" hint={this.settings.state.theme === "system" ? "Sisteme uyar." : "Elle seçildi."} />
      </section>
    );
  }
}
import { Component } from "@motifx/core";
import { NotesService } from "../services/notes";
import { SettingsService } from "../services/settings";
import { Setting } from "../parts/Setting";

const archived = (count) => `${count} arşivlenmiş not`;

export default class SettingsPage extends Component {
  notes = this.getService(NotesService);
  settings = this.getService(SettingsService);

  view() {
    return (
      <section class="page" title={archived(this.notes.count("archive"))}>
        <p>{this.settings.state.theme}</p>
        <small>{() => archived(this.notes.count("archive"))}</small>
        <Setting name="Tema" hint={this.settings.state.theme === "system" ? "Sisteme uyar." : "Elle seçildi."} />
      </section>
    );
  }
}
import { FromService } from "@motifx/core";
import { NotesService } from "../services/notes";
import { SettingsService } from "../services/settings";
import { Setting } from "../parts/Setting";

const archived = (count: number) => `${count} arşivlenmiş not`;

export default function SettingsPage() {
  const notes = FromService(NotesService)!;
  const settings = FromService(SettingsService)!;

  return (
    <section class="page" title={archived(notes.count("archive"))}>
      <p>{settings.state.theme}</p>
      <small>{() => archived(notes.count("archive"))}</small>
      <Setting name="Tema" hint={settings.state.theme === "system" ? "Sisteme uyar." : "Elle seçildi."} />
    </section>
  );
}
import { FromService } from "@motifx/core";
import { NotesService } from "../services/notes";
import { SettingsService } from "../services/settings";
import { Setting } from "../parts/Setting";

const archived = (count) => `${count} arşivlenmiş not`;

export default function SettingsPage() {
  const notes = FromService(NotesService);
  const settings = FromService(SettingsService);

  return (
    <section class="page" title={archived(notes.count("archive"))}>
      <p>{settings.state.theme}</p>
      <small>{() => archived(notes.count("archive"))}</small>
      <Setting name="Tema" hint={settings.state.theme === "system" ? "Sisteme uyar." : "Elle seçildi."} />
    </section>
  );
}
import { motifOptions } from "@motifx/core";
import { NotesService } from "../services/notes";
import { SettingsService } from "../services/settings";
import { Setting } from "../parts/Setting";

const archived = (count: number) => `${count} arşivlenmiş not`;

export default () => motifOptions({
  el: null,

  view() {
    const notes = this.getService(NotesService)!;
    const settings = this.getService(SettingsService)!;
    return (
      <section class="page" title={archived(notes.count("archive"))}>
        <p>{settings.state.theme}</p>
        <small>{() => archived(notes.count("archive"))}</small>
        <Setting name="Tema" hint={settings.state.theme === "system" ? "Sisteme uyar." : "Elle seçildi."} />
      </section>
    );
  },
});
import { motifOptions } from "@motifx/core";
import { NotesService } from "../services/notes";
import { SettingsService } from "../services/settings";
import { Setting } from "../parts/Setting";

const archived = (count) => `${count} arşivlenmiş not`;

export default () => motifOptions({
  el: null,

  view() {
    const notes = this.getService(NotesService);
    const settings = this.getService(SettingsService);
    return (
      <section class="page" title={archived(notes.count("archive"))}>
        <p>{settings.state.theme}</p>
        <small>{() => archived(notes.count("archive"))}</small>
        <Setting name="Tema" hint={settings.state.theme === "system" ? "Sisteme uyar." : "Elle seçildi."} />
      </section>
    );
  },
});

Döküm:

[motifjs explain] src/pages/SettingsPage.tsx — 9 expressions
  14:16    attr/attr.literal        class="page"
           => sender.class.add("page")
           STATIC · deps: none — no binding is created
  14:29    attr/attr.call           title={archived(this.notes.count("archive"))}
           => sender.attr.add({ "title": archived(this.notes.count("archive")) })
           ONCE · deps: none — evaluated once during setup, then frozen
           note: a plain function call is passed as a VALUE; write `() => f(x)` to keep it live
  15:9     element/element.dom      <p>
           => sender.controls.add(_mc(<p>, …))
           STATIC · deps: none — the element itself is built once; bindings inside it are their own effects
  15:12    child/text.field         {this.settings.state.theme}
           => sender.bindings.add("textContent", this.settings.state, "theme")
           LIVE · deps: this.settings.state.theme — this field only (exact)
  16:9     element/element.dom      <small>
           => sender.controls.add(_mc(<small>, …))
           STATIC · deps: none — the element itself is built once; bindings inside it are their own effects
  16:16    child/method             {() => archived(this.notes.count("archive"))}
           => sender.bindings.method(() => archived(this.notes.count("archive")))
           LIVE · deps: ALL reactive fields read inside the getter; collected again on every run
           note: reactive text if the result is text, a Frame if it is a component (the instance is rebuilt on every change)
  17:9     element/element.component <Setting name="Tema" hint={this.settings.state.theme === "system" ? "Sisteme uyar." : "El…
           => sender.controls.add(_mc(<Setting>, …))
           STATIC · deps: none — the element itself is built once; bindings inside it are their own effects
  17:18    prop/prop.literal        name="Tema"
           => name: "Tema"
           STATIC · deps: none — no binding is created
  17:30    prop/prop.ternary        hint={this.settings.state.theme === "system" ? "Sisteme uyar." : "Elle seçildi."}
           => hint: () => this.settings.state.theme === "system" ? "Sisteme uyar." : "Elle seçildi."
           RECEIVER · deps: wherever the receiving component reads the getter; nowhere if it does not
           note: ternary wrapped lazily (by design): the receiving component should declare the prop as Bind<T> and read it with read()/toGetter()
[motifjs explain] src/pages/SettingsPage.jsx — 9 expressions
  14:16    attr/attr.literal        class="page"
           => sender.class.add("page")
           STATIC · deps: none — no binding is created
  14:29    attr/attr.call           title={archived(this.notes.count("archive"))}
           => sender.attr.add({ "title": archived(this.notes.count("archive")) })
           ONCE · deps: none — evaluated once during setup, then frozen
           note: a plain function call is passed as a VALUE; write `() => f(x)` to keep it live
  15:9     element/element.dom      <p>
           => sender.controls.add(_mc(<p>, …))
           STATIC · deps: none — the element itself is built once; bindings inside it are their own effects
  15:12    child/text.field         {this.settings.state.theme}
           => sender.bindings.add("textContent", this.settings.state, "theme")
           LIVE · deps: this.settings.state.theme — this field only (exact)
  16:9     element/element.dom      <small>
           => sender.controls.add(_mc(<small>, …))
           STATIC · deps: none — the element itself is built once; bindings inside it are their own effects
  16:16    child/method             {() => archived(this.notes.count("archive"))}
           => sender.bindings.method(() => archived(this.notes.count("archive")))
           LIVE · deps: ALL reactive fields read inside the getter; collected again on every run
           note: reactive text if the result is text, a Frame if it is a component (the instance is rebuilt on every change)
  17:9     element/element.component <Setting name="Tema" hint={this.settings.state.theme === "system" ? "Sisteme uyar." : "El…
           => sender.controls.add(_mc(<Setting>, …))
           STATIC · deps: none — the element itself is built once; bindings inside it are their own effects
  17:18    prop/prop.literal        name="Tema"
           => name: "Tema"
           STATIC · deps: none — no binding is created
  17:30    prop/prop.ternary        hint={this.settings.state.theme === "system" ? "Sisteme uyar." : "Elle seçildi."}
           => hint: () => this.settings.state.theme === "system" ? "Sisteme uyar." : "Elle seçildi."
           RECEIVER · deps: wherever the receiving component reads the getter; nowhere if it does not
           note: ternary wrapped lazily (by design): the receiving component should declare the prop as Bind<T> and read it with read()/toGetter()
[motifjs explain] src/pages/SettingsPage.tsx — 9 expressions
  13:14    attr/attr.literal        class="page"
           => sender.class.add("page")
           STATIC · deps: none — no binding is created
  13:27    attr/attr.call           title={archived(notes.count("archive"))}
           => sender.attr.add({ "title": archived(notes.count("archive")) })
           ONCE · deps: none — evaluated once during setup, then frozen
           note: a plain function call is passed as a VALUE; write `() => f(x)` to keep it live
  14:7     element/element.dom      <p>
           => sender.controls.add(_mc(<p>, …))
           STATIC · deps: none — the element itself is built once; bindings inside it are their own effects
  14:10    child/text.field         {settings.state.theme}
           => sender.bindings.add("textContent", settings.state, "theme")
           LIVE · deps: settings.state.theme — this field only (exact)
  15:7     element/element.dom      <small>
           => sender.controls.add(_mc(<small>, …))
           STATIC · deps: none — the element itself is built once; bindings inside it are their own effects
  15:14    child/method             {() => archived(notes.count("archive"))}
           => sender.bindings.method(() => archived(notes.count("archive")))
           LIVE · deps: ALL reactive fields read inside the getter; collected again on every run
           note: reactive text if the result is text, a Frame if it is a component (the instance is rebuilt on every change)
  16:7     element/element.component <Setting name="Tema" hint={settings.state.theme === "system" ? "Sisteme uyar." : "Elle se…
           => sender.controls.add(_mc(<Setting>, …))
           STATIC · deps: none — the element itself is built once; bindings inside it are their own effects
  16:16    prop/prop.literal        name="Tema"
           => name: "Tema"
           STATIC · deps: none — no binding is created
  16:28    prop/prop.ternary        hint={settings.state.theme === "system" ? "Sisteme uyar." : "Elle seçildi."}
           => hint: () => settings.state.theme === "system" ? "Sisteme uyar." : "Elle seçildi."
           RECEIVER · deps: wherever the receiving component reads the getter; nowhere if it does not
           note: ternary wrapped lazily (by design): the receiving component should declare the prop as Bind<T> and read it with read()/toGetter()
[motifjs explain] src/pages/SettingsPage.jsx — 9 expressions
  13:14    attr/attr.literal        class="page"
           => sender.class.add("page")
           STATIC · deps: none — no binding is created
  13:27    attr/attr.call           title={archived(notes.count("archive"))}
           => sender.attr.add({ "title": archived(notes.count("archive")) })
           ONCE · deps: none — evaluated once during setup, then frozen
           note: a plain function call is passed as a VALUE; write `() => f(x)` to keep it live
  14:7     element/element.dom      <p>
           => sender.controls.add(_mc(<p>, …))
           STATIC · deps: none — the element itself is built once; bindings inside it are their own effects
  14:10    child/text.field         {settings.state.theme}
           => sender.bindings.add("textContent", settings.state, "theme")
           LIVE · deps: settings.state.theme — this field only (exact)
  15:7     element/element.dom      <small>
           => sender.controls.add(_mc(<small>, …))
           STATIC · deps: none — the element itself is built once; bindings inside it are their own effects
  15:14    child/method             {() => archived(notes.count("archive"))}
           => sender.bindings.method(() => archived(notes.count("archive")))
           LIVE · deps: ALL reactive fields read inside the getter; collected again on every run
           note: reactive text if the result is text, a Frame if it is a component (the instance is rebuilt on every change)
  16:7     element/element.component <Setting name="Tema" hint={settings.state.theme === "system" ? "Sisteme uyar." : "Elle se…
           => sender.controls.add(_mc(<Setting>, …))
           STATIC · deps: none — the element itself is built once; bindings inside it are their own effects
  16:16    prop/prop.literal        name="Tema"
           => name: "Tema"
           STATIC · deps: none — no binding is created
  16:28    prop/prop.ternary        hint={settings.state.theme === "system" ? "Sisteme uyar." : "Elle seçildi."}
           => hint: () => settings.state.theme === "system" ? "Sisteme uyar." : "Elle seçildi."
           RECEIVER · deps: wherever the receiving component reads the getter; nowhere if it does not
           note: ternary wrapped lazily (by design): the receiving component should declare the prop as Bind<T> and read it with read()/toGetter()
[motifjs explain] src/pages/SettingsPage.tsx — 9 expressions
  15:16    attr/attr.literal        class="page"
           => sender.class.add("page")
           STATIC · deps: none — no binding is created
  15:29    attr/attr.call           title={archived(notes.count("archive"))}
           => sender.attr.add({ "title": archived(notes.count("archive")) })
           ONCE · deps: none — evaluated once during setup, then frozen
           note: a plain function call is passed as a VALUE; write `() => f(x)` to keep it live
  16:9     element/element.dom      <p>
           => sender.controls.add(_mc(<p>, …))
           STATIC · deps: none — the element itself is built once; bindings inside it are their own effects
  16:12    child/text.field         {settings.state.theme}
           => sender.bindings.add("textContent", settings.state, "theme")
           LIVE · deps: settings.state.theme — this field only (exact)
  17:9     element/element.dom      <small>
           => sender.controls.add(_mc(<small>, …))
           STATIC · deps: none — the element itself is built once; bindings inside it are their own effects
  17:16    child/method             {() => archived(notes.count("archive"))}
           => sender.bindings.method(() => archived(notes.count("archive")))
           LIVE · deps: ALL reactive fields read inside the getter; collected again on every run
           note: reactive text if the result is text, a Frame if it is a component (the instance is rebuilt on every change)
  18:9     element/element.component <Setting name="Tema" hint={settings.state.theme === "system" ? "Sisteme uyar." : "Elle se…
           => sender.controls.add(_mc(<Setting>, …))
           STATIC · deps: none — the element itself is built once; bindings inside it are their own effects
  18:18    prop/prop.literal        name="Tema"
           => name: "Tema"
           STATIC · deps: none — no binding is created
  18:30    prop/prop.ternary        hint={settings.state.theme === "system" ? "Sisteme uyar." : "Elle seçildi."}
           => hint: () => settings.state.theme === "system" ? "Sisteme uyar." : "Elle seçildi."
           RECEIVER · deps: wherever the receiving component reads the getter; nowhere if it does not
           note: ternary wrapped lazily (by design): the receiving component should declare the prop as Bind<T> and read it with read()/toGetter()
[motifjs explain] src/pages/SettingsPage.jsx — 9 expressions
  15:16    attr/attr.literal        class="page"
           => sender.class.add("page")
           STATIC · deps: none — no binding is created
  15:29    attr/attr.call           title={archived(notes.count("archive"))}
           => sender.attr.add({ "title": archived(notes.count("archive")) })
           ONCE · deps: none — evaluated once during setup, then frozen
           note: a plain function call is passed as a VALUE; write `() => f(x)` to keep it live
  16:9     element/element.dom      <p>
           => sender.controls.add(_mc(<p>, …))
           STATIC · deps: none — the element itself is built once; bindings inside it are their own effects
  16:12    child/text.field         {settings.state.theme}
           => sender.bindings.add("textContent", settings.state, "theme")
           LIVE · deps: settings.state.theme — this field only (exact)
  17:9     element/element.dom      <small>
           => sender.controls.add(_mc(<small>, …))
           STATIC · deps: none — the element itself is built once; bindings inside it are their own effects
  17:16    child/method             {() => archived(notes.count("archive"))}
           => sender.bindings.method(() => archived(notes.count("archive")))
           LIVE · deps: ALL reactive fields read inside the getter; collected again on every run
           note: reactive text if the result is text, a Frame if it is a component (the instance is rebuilt on every change)
  18:9     element/element.component <Setting name="Tema" hint={settings.state.theme === "system" ? "Sisteme uyar." : "Elle se…
           => sender.controls.add(_mc(<Setting>, …))
           STATIC · deps: none — the element itself is built once; bindings inside it are their own effects
  18:18    prop/prop.literal        name="Tema"
           => name: "Tema"
           STATIC · deps: none — no binding is created
  18:30    prop/prop.ternary        hint={settings.state.theme === "system" ? "Sisteme uyar." : "Elle seçildi."}
           => hint: () => settings.state.theme === "system" ? "Sisteme uyar." : "Elle seçildi."
           RECEIVER · deps: wherever the receiving component reads the getter; nowhere if it does not
           note: ternary wrapped lazily (by design): the receiving component should declare the prop as Bind<T> and read it with read()/toGetter()

Reaktiflik sınıfları:

Sınıf Anlamı
LIVE Bir effect kurulur; bağımlılıklar değişince DOM güncellenir.
STATIC Bağ kurulmaz: literal değerler (dizge, sayı, eksi sayı, boolean, null), değersiz öznitelik, JSX değerli prop, transition/options gibi sabit yazımlar ve elemanın kendisi (<p>, <Setting …/>); eleman bir kez kurulur, içindeki ifadeler kendi satırlarında görünür.
ONCE İfade kurulum anında bir kez değerlenir ve bir daha okunmaz (attr={f(x)}, prop={nesne}). prop={nesne} yazımında alıcı nesnenin kendisini alır: reaktif nesnenin alanlarını bir bağ içinde okursa yerinde değişimleri görür, kaynakta nesnenin yerine başkası konursa görmez.
RECEIVER İfade fonksiyon olarak geçer; canlılık alıcı bileşenin Bind<T> sözleşmesine bağlıdır.
RUNTIME Değer türü çalışma anında belli olur: fonksiyonsa canlı, değilse bir kez (attr={degisken}).

Aynı dökümü geliştirme sunucusunda görmek için Vite eklentisine explain verin; dizge ya da RegExp yalnızca eşleşen dosyaları döker, çıktı kodu değişmez:

compiler({ explain: "pages/SettingsPage" })
compiler({ explain: "pages/SettingsPage" })

Programatik kullanım: import { explain } from '@motifx/compiler' → explain(kaynak, dosyaAdı) bir MotifExplanation[] döndürür (site, shape, source, lowered, reactive, deps, note).

key ile öğe kimliği

Liste öğelerine key verin; satırlar key ile değil öğe nesnesiyle eşleşir. JSX'siz karşılığı indexkey'dir. Geliştirme modunda yinelenen anahtar MJX202 ile bildirilir. Ayrıntı için bkz. Koşullu Gösterim ve Listeler:

{this.visible.map((note) => <NoteRow key={note.id} note={note} folder={this.folder} />)}
{visible().map((note) => <NoteRow key={note.id} note={note} folder={folder()} />)}
const list = motifComponent({
  initializeComponent: (holder: ComponentBase) => holder.bindings.list(() => this.visible, (note: Note) => motifComponent(NoteRow, { indexkey: () => note.id, note, folder: this.folder })),
});
const list = motifComponent({
  initializeComponent: (holder: ComponentBase) => holder.bindings.list(() => visible(), (note: Note) => motifComponent(NoteRow, { indexkey: () => note.id, note, folder: folder() })),
});
{this.visible.map((note) => <NoteRow key={note.id} note={note} folder={this.folder} />)}
{visible().map((note) => <NoteRow key={note.id} note={note} folder={folder()} />)}
const list = motifComponent({
  initializeComponent: (holder) => holder.bindings.list(() => this.visible, (note) => motifComponent(NoteRow, { indexkey: () => note.id, note, folder: this.folder })),
});
const list = motifComponent({
  initializeComponent: (holder) => holder.bindings.list(() => visible(), (note) => motifComponent(NoteRow, { indexkey: () => note.id, note, folder: folder() })),
});

Geçişler — transition prop'u

Bir bileşene giriş/çıkış animasyonu vermek için transition prop'u kullanılır. String verirseniz sınıf adları o önekten türetilen bir CSS sınıf geçişi olur; nesne verirseniz özel sınıf adları belirtebilirsiniz. JSX'siz yazımda nesne biçiminin anahtarıdır:

<a class="row" transition="fade">…</a>
motifComponent("a", { transition: "fade", initializeComponent: (a: ComponentBase) => a.class.add("row") });
<a class="row" transition="fade">…</a>
motifComponent("a", { transition: "fade", initializeComponent: (a) => a.class.add("row") });

Bu, fade-enter-from/active/to ve fade-leave-from/active/to CSS sınıflarını uygular. Ayrıntı ve WAAPI (keyframe) tabanlı animasyon için Stil ve Animasyon.

transition düz DOM etiketinde öznitelik yazmaz, geçiş motoruna gider. options prop'u da öyledir: <li options={{ hideStrategy: 'detach', disableDisposal: true }}/> içindeki hideStrategy ve disableDisposal o etiketin motif.options'ına kopyalanır, DOM'a öznitelik yazılmaz; JSX'siz yazımda options da nesne biçiminin anahtarıdır.

Sonraki adım

Aynı görünümü JSX olmadan, bileşen çağrılarıyla yazmak için JSX'siz Görünümler bölümüne geçin; reaktif durumun ayrıntıları Reaktivite bölümündedir.