معرفی command و commandfor

تاریخ انتشار: 7 مارس 2025

دکمه ها برای ساخت برنامه های وب پویا ضروری هستند. دکمه ها منوها را باز می کنند، عملکردها را تغییر می دهند و فرم ها را ارسال می کنند. آنها پایه و اساس تعامل در وب را فراهم می کنند. ساده و در دسترس بودن دکمه ها می تواند به چالش های شگفت انگیزی منجر شود. توسعه دهندگانی که میکرو فرانتاندها یا سیستم های جزء می سازند، می توانند با راه حل هایی مواجه شوند که پیچیده تر از حد لازم می شوند. در حالی که چارچوب ها کمک می کنند، وب می تواند در اینجا کارهای بیشتری انجام دهد.

Chrome 135 قابلیت‌های جدیدی را برای ارائه رفتار اعلانی با ویژگی‌های command جدید و commandfor ، بهبود و جایگزینی ویژگی‌های popovertargetaction و popovertarget معرفی می‌کند. این ویژگی‌های جدید را می‌توان به دکمه‌ها اضافه کرد و به مرورگر اجازه می‌دهد برخی از مسائل اصلی در مورد سادگی و دسترسی را برطرف کند و عملکرد مشترک داخلی را ارائه دهد.

الگوهای سنتی

ایجاد رفتارهای دکمه بدون چارچوب می تواند چالش های جالبی را با تکامل کد تولید ایجاد کند. در حالی که HTML کنترل‌کننده‌های onclick به دکمه‌ها ارائه می‌کند، به دلیل قوانین خط‌مشی امنیت محتوا (CSP) اینها اغلب در خارج از دموها یا آموزش‌ها غیرمجاز هستند. در حالی که این رویدادها بر روی عناصر دکمه ارسال می‌شوند، دکمه‌ها معمولاً در یک صفحه قرار می‌گیرند تا سایر عناصر را کنترل کنند که برای کنترل همزمان دو عنصر نیاز به کد دارند. همچنین باید اطمینان حاصل کنید که این تعامل برای کاربران فناوری کمکی قابل دسترسی است. این اغلب منجر به این می شود که کد کمی شبیه به این باشد:

<div class="menu-wrapper">
  <button class="menu-opener" aria-expanded="false">
    Open Menu
  </button>
  <div popover class="menu-content">
    <!-- ... -->
  </div>
</div>
<script type="module">
document.addEventListener('click', e => {  
  const button = e.target;
  if (button.matches('.menu-opener')) {
    const menu = button
      .closest('.menu-wrapper')
      .querySelector('.menu-content');
    if (menu) {
      button.setAttribute('aria-expanded', 'true');
      menu.showPopover();
      menu.addEventListener('toggle', e => {
        // reset back to aria-expanded=false on close
        if (e.newState == 'closed') {
          button.setAttribute('aria-expanded', 'false');
        }
      }, {once: true})
    }
  }
});
</script>

این رویکرد می تواند کمی شکننده باشد و هدف چارچوب ها بهبود ارگونومی است. یک الگوی رایج با چارچوبی مانند React ممکن است شامل نگاشت کلیک به یک تغییر وضعیت باشد:

function MyMenu({ children }) {
  const [isOpen, setIsOpen] = useState(false);
  const open = useCallback(() => setIsOpen(true), []);
  const handleToggle = useCallback((e) => {
      // popovers have light dismiss which influences our state
     setIsOpen(e.newState === 'open')
  }, []);
  const popoverRef = useRef(null);
  useEffect(() => {
    if (popoverRef.current) {
      if (isOpen) {
        popoverRef.current.showPopover();
      } else {
        popoverRef.current.hidePopover();
      }
    }
  }, [popoverRef, isOpen]);
  return (
    <>
      <button onClick={open} aria-expanded={isOpen}>
        Open Menu
      </button>
      <div popover onToggle={handleToggle} ref={popoverRef}>
        {children}
      </div>
    </>
  );
}

هدف بسیاری از فریمورک های دیگر نیز ارائه ارگونومی مشابه است، به عنوان مثال ممکن است در AlpineJS به صورت زیر نوشته شود:

<div x-data="{open: false}">
  <button @click="open = !open; $refs.popover.showPopover()" :aria-expanded="open">
    Open Menu
  </button>
  <div popover x-ref="popover" @toggle="open = $event.newState === 'open'">
    <!-- ... -->
  </div>
</div>

هنگام نوشتن این در Svelte ممکن است چیزی شبیه به این باشد:

<script>
  let popover;
  let open = false;
  function togglePopover() {
    open ? popover.hidePopover() : popover.showPopover();
    open = !open;
  }
</script>
<button on:click={togglePopover} aria-expanded={open}>
  Open Menu
</button>
<div bind:this={popover} popover>
  <!-- ... -->
</div>

برخی از سیستم‌ها یا کتابخانه‌های طراحی ممکن است با ارائه پوشش‌هایی در اطراف عناصر دکمه‌ای که تغییرات حالت را محصور می‌کنند، یک قدم فراتر بروند. این تغییرات حالت را در پشت یک جزء ماشه خلاصه می کند و کمی انعطاف پذیری برای ارگونومی بهبود یافته معامله می کند:

import {MenuTrigger, MenuContent} from 'my-design-system';
function MyMenu({children}) {
  return (
    <MenuTrigger>
      <button>Open Menu</button>
    </MenuTrigger>
    <MenuContent>{children}</MenuContent>
  );
}

الگوی فرمان و فرمان برای

با ویژگی های command و commandfor ، دکمه ها اکنون می توانند اقداماتی را بر روی عناصر دیگر به صورت آشکار انجام دهند و ارگونومی یک چارچوب را بدون به خطر انداختن انعطاف پذیری به ارمغان بیاورند. دکمه commandfor یک شناسه شبیه به ویژگی for می گیرد، در حالی که command مقادیر داخلی را می پذیرد و رویکرد قابل حمل و بصری تر را امکان پذیر می کند.

مثال: یک دکمه منوی باز با دستور و commandfor

HTML زیر روابطی را بین دکمه و منو تنظیم می کند که به مرورگر اجازه می دهد منطق و دسترسی را برای شما مدیریت کند. نیازی به مدیریت aria-expanded یا افزودن جاوا اسکریپت اضافی وجود ندارد.

<button commandfor="my-menu" command="show-popover">
Open Menu
</button>
<div popover id="my-menu">
  <!-- ... -->
</div>

مقایسه command و commandfor با popovertargetaction و popovertarget

اگر قبلا از popover استفاده کرده اید، ممکن است با ویژگی های popovertarget و popovertargetaction آشنا باشید. اینها به ترتیب مشابه commandfor و command کار می کنند - به جز اینکه مخصوص پاپاورها هستند. ویژگی های command و commandfor به طور کامل جایگزین این ویژگی های قدیمی می شوند. ویژگی‌های جدید از تمام ویژگی‌های قدیمی‌تر پشتیبانی می‌کنند و همچنین قابلیت‌های جدیدی را اضافه می‌کنند.

دستورات داخلی

ویژگی command دارای مجموعه ای از رفتارهای داخلی است که برای عناصر تعاملی به API های مختلف نگاشت می شوند:

  • show-popover : نقشه ها به el.showPopover() .
  • hide-popover : نقشه ها به el.hidePopover() .
  • toggle-popover : نقشه‌ها به el.togglePopover() .
  • show-modal : نقشه ها را به dialogEl.showModal() .
  • close : نقشه ها را به dialogEl.close() .

این دستورات به همتایان خود در جاوا اسکریپت نگاشت می شوند، در حالی که دسترسی را ساده می کنند (مانند ارائه aria-details و روابط معادل aria-expanded )، مدیریت تمرکز و موارد دیگر.

مثال: گفتگوی تایید با command و commandfor

<button commandfor="confirm-dialog" command="show-modal">
  Delete Record
</button>
<dialog id="confirm-dialog">
  <header>
    <h1>Delete Record?</h1>
    <button commandfor="confirm-dialog" command="close" aria-label="Close" value="close">
      <img role="none" src="/close-icon.svg">
    </button>
  </header>
  <p>Are you sure? This action cannot be undone</p>
  <footer>
    <button commandfor="confirm-dialog" command="close" value="cancel">
      Cancel
    </button>
    <button commandfor="confirm-dialog" command="close" value="delete">
      Delete
    </button>
  </footer>
</dialog>

با کلیک بر روی دکمه Delete Record، دیالوگ به صورت مدال باز می شود، در حالی که با کلیک بر روی دکمه های Close ، Cancel یا Delete ، دیالوگ بسته می شود و همچنین یک رویداد "close" در گفتگو ارسال می شود، که دارای ویژگی returnValue مطابق با مقدار دکمه است. این امر نیاز به جاوا اسکریپت را فراتر از یک شنونده رویداد در گفتگو کاهش می دهد تا مشخص شود چه کاری باید انجام شود:

dialog.addEventListener("close", (event) => {
  if (event.target.returnValue == "cancel") {
    console.log("cancel was clicked");
  } else if (event.target.returnValue == "close") {
    console.log("close was clicked");
  } else if (event.target.returnValue == "delete") {
    console.log("delete was clicked");
  }
});

دستورات سفارشی

علاوه بر دستورات داخلی، می توانید دستورات سفارشی را با استفاده از پیشوند -- تعریف کنید. دستورات سفارشی یک رویداد "command" را روی عنصر هدف ارسال می کنند (درست مانند دستورات داخلی)، اما در غیر این صورت هرگز هیچ منطق اضافی مانند مقادیر داخلی انجام نمی دهند. این انعطاف‌پذیری را برای اجزای سازنده ایجاد می‌کند که می‌توانند به روش‌های مختلف به دکمه‌ها واکنش نشان دهند، بدون اینکه نیازی به ارائه مولفه‌های بسته‌بندی، عبور از DOM برای عنصر هدف، یا نقشه‌برداری کلیک‌های دکمه‌ای برای وضعیت تغییرات باشند. این به شما امکان می دهد یک API در HTML برای اجزای خود ارائه دهید:

<button commandfor="the-image" command="--rotate-landscape">
 Landscape
</button>
<button commandfor="the-image" command="--rotate-portrait">
 Portrait
</button>

<img id="the-image" src="photo.jpg">

<script type="module">
  const image = document.getElementById("the-image");
  image.addEventListener("command", (event) => {
   if ( event.command == "--rotate-landscape" ) {
    image.style.rotate = "-90deg"
   } else if ( event.command == "--rotate-portrait" ) {
    image.style.rotate = "0deg"
   }
  });
</script>

دستورات در ShadowDOM

با توجه به اینکه ویژگی commandfor یک ID می گیرد، محدودیت هایی در مورد عبور از سایه DOM وجود دارد. در این موارد می توانید از JavaScript API برای تنظیم ویژگی .commandForElement استفاده کنید که می تواند هر عنصری را در بین ریشه های سایه تنظیم کند:

<my-element>
  <template shadowrootmode=open>
    <button command="show-popover">Show popover</button>
    <slot></slot>
  </template>
  <div popover><!-- ... --></div>
</my-element>
<script>
customElements.define("my-element", class extends HTMLElement {
  connectedCallback() {
    const popover = this.querySelector('[popover]');
    // The commandForElement can set cross-shadow root elements.
    this.shadowRoot.querySelector('button').commandForElement = popover;
  }
});
</script>

پیشنهادهای آتی ممکن است راهی برای به اشتراک گذاشتن مراجع در سراسر مرزهای سایه ارائه دهند، مانند پیشنهاد هدف مرجع .

بعدش چی؟

ما به بررسی احتمالات برای دستورات داخلی جدید، برای پوشش عملکردهای رایجی که وب سایت ها استفاده می کنند، ادامه خواهیم داد. ایده های پیشنهادی در Open UI Proposal پوشش داده شده است. برخی از ایده هایی که قبلا بررسی شده اند:

  • باز کردن و بستن عناصر <details> .
  • یک دستور "show-picker" برای عناصر <input> و <select> ، نگاشت به showPicker() .
  • دستورات پخش برای عناصر <video> و <audio> .
  • کپی محتوای متنی از عناصر

ما از ورودی انجمن استقبال می‌کنیم—اگر پیشنهادی دارید، در ثبت مشکل در Open UI Issue Tracker تردید نکنید.

بیشتر بدانید

اطلاعات بیشتر در مورد command و commandfor در مشخصات و MDN بیابید.