संवाद

वेब की सुविधाएं: <dialog> <dialog closedby> Invoker commands Popover overscroll-behavior

खास जानकारी

मोडल और नॉन-मोडल डायलॉग के लिए, नेटिव <dialog> एलिमेंट का इस्तेमाल करें. यह सुलभता सिमैंटिक, फ़ोकस हैंडलिंग, टॉप-लेयर रेंडरिंग, और Esc हैंडलिंग की सुविधा देता है. कस्टम ओवरले को ये सुविधाएं मैन्युअल तरीके से बनानी पड़ती हैं.

ओवरले का टाइप चुनें

मैच करने वाला पहला नियम चुनें. अगर आपको पक्के तौर पर नहीं पता है, तो नियम 1 का इस्तेमाल करें.

नियम 1: ओवरले, उपयोगकर्ता को रोकता है और पेज को ब्लॉक करता है (डिफ़ॉल्ट)

इसका इस्तेमाल पुष्टि करने, फ़ॉर्म भरने, साइन-इन करने, और नुकसान पहुंचाने वाली कार्रवाइयों के लिए करें. साथ ही, इसका इस्तेमाल किसी भी ऐसे "मोडल", "डायलॉग", "लाइटबॉक्स" या "पॉप-अप" अनुरोध के लिए करें जिसमें कुछ और न कहा गया हो.

<dialog> को मॉडल के तौर पर इस्तेमाल करें. इसे command="show-modal" के साथ डिक्लेरेटिव तरीके से (डिक्लेरेटिव डायलॉग और पॉपओवर कंट्रोल देखें) या showModal() के साथ स्क्रिप्ट से इस्तेमाल करें.

<button type="button" command="show-modal" commandfor="confirm">Delete</button>

<!-- MANDATORY: no role="dialog" or aria-modal; the native modal already provides them -->
<dialog id="confirm" aria-labelledby="confirm-title">
  <h2 id="confirm-title">Delete this item?</h2>
  <form method="dialog">
    <button value="cancel" autofocus>Cancel</button>
    <button value="delete">Delete</button>
  </form>
</dialog>

ब्राउज़र ये सुविधाएं देता है: इनर्ट पेज, फ़ोकस कंटेनमेंट, टॉप लेयर, ::backdrop, Esc और बैक-जेस्चर से खारिज करना, और फ़ोकस को वापस इन्वोकर पर ले जाना.

  • भूलकर भी role="dialog", aria-modal, फ़ोकस ट्रैप, inert टॉगल या Esc हैंडलर न जोड़ें.
  • इसे show() या open एट्रिब्यूट के साथ न खोलें.
  • ज़रूर करें: नीचे दिए गए सीएसएस का इस्तेमाल करके, पेज को पीछे की ओर स्क्रोल होने से रोकें. ज़्यादा जानकारी के लिए, मॉडल डायलॉग के नीचे मौजूद पेज को स्क्रोल होने से रोकना लेख पढ़ें.
  • html या body पर overflow: hidden या position: fixed का इस्तेमाल करके, स्क्रोलिंग को लॉक न करें.
/* MANDATORY (both rules): without them, scrolling over the backdrop or inside the dialog scrolls the page behind it */
dialog:modal {
  overscroll-behavior: contain; /* stops scroll gestures in the dialog chaining to the page */
}
dialog::backdrop {
  overflow: hidden; /* ::backdrop is not a scroll container by default, so it would ignore overscroll-behavior */
  overscroll-behavior: contain;
}

मेन्यू या टूलटिप के लिए नहीं: नियम 3 देखें.

नियम 2: पैनल खुला होने पर, उपयोगकर्ता पेज पर काम करता रहता है

इसका इस्तेमाल, खोज बार, इंस्पेक्टर, पैलेट, और कंपोज़ पैनल के लिए करें. ये अब भी सिमैंटिक तौर पर डायलॉग हैं.

पॉपओवर को खारिज करने की सुविधा वाले टॉप-लेयर पैनल के लिए, <dialog popover> का इस्तेमाल करें. auto को खारिज किया जा सकता है, लेकिन manual को नहीं. dialog.show() का इस्तेमाल सिर्फ़ तब करें, जब पैनल को सबसे ऊपर वाली लेयर में न रखना हो.

<button type="button" popovertarget="find">Find</button>
<dialog id="find" popover="manual" aria-labelledby="find-title">
  <h2 id="find-title">Find</h2>
  <input type="search" aria-label="Search">
  <button type="button" popovertarget="find" popovertargetaction="hide">Close</button>
</dialog>

पेज इंटरैक्टिव बना रहता है और इनऐक्टिव नहीं होता.

  • ज़रूर शामिल करें.
  • showModal() का इस्तेमाल न करें, क्योंकि यह पेज को ब्लॉक कर देता है.

पुष्टि करने से जुड़े मैसेज ब्लॉक करने के लिए नहीं: नियम 1 देखें.

तीसरा नियम: ओवरले, डायलॉग नहीं है

इसका इस्तेमाल मेन्यू, ड्रॉपडाउन, टूलटिप, होवरकार्ड, और टोस्ट के लिए करें.

popover एट्रिब्यूट के साथ सिमैंटिक एलिमेंट का इस्तेमाल करें, न कि <dialog> का.

<button type="button" popovertarget="menu">Actions</button>
<ul id="menu" popover>
  <li><a href="/rename">Rename</a></li>
</ul>

अलार्म बंद करने का तरीका चुनें

डिफ़ॉल्ट तरीका, इस बात से तय होता है कि डायलॉग बॉक्स कैसे खोला गया है. अगर closedby मौजूद नहीं है या अमान्य है, तो showModal(), closerequest की तरह काम करता है. साथ ही, खोलने का कोई भी दूसरा तरीका none की तरह काम करता है.

  • सिर्फ़ Esc और बैक जेस्चर (मोडल डिफ़ॉल्ट): कुछ भी सेट न करें या closedby="closerequest" सेट करें.
  • बंद करने के लिए, बाहर भी क्लिक करें: closedby="any" का इस्तेमाल करें.
  • सिर्फ़ डेवलपर इसे बंद करता है (उदाहरण के लिए, सेव नहीं किए गए बदलावों वाला फ़ॉर्म): closedby="none" का इस्तेमाल करें और पुष्टि करने का पाथ जोड़ें. नीचे दी गई जानकारी देखें.

दिशा-निर्देश

ऐक्सेस किया जा सकने वाला नाम और प्रॉडक्ट या सेवा पर उपयोगकर्ता का सबसे पहले ध्यान जाना

  • हर डायलॉग को aria-labelledby से नाम दें, जो उसकी हेडिंग पर ले जाता हो. अगर कोई टाइटल नहीं दिख रहा है, तो aria-label का इस्तेमाल करें.
  • autofocus को प्राइमरी कंट्रोल या पहले फ़ील्ड पर रखें.
  • किसी कार्रवाई को रद्द करने की पुष्टि करने के लिए, सुरक्षित कार्रवाई (जैसे, रद्द करें या रखें) पर autofocus लगाएं.
  • बंद करने पर फ़ोकस वापस लाने के लिए कोड न लिखें. ब्राउज़र, इसे कॉल करने वाले को वापस भेज देता है.

खोलना

आखिरी हिस्सा

  • <form method="dialog"> बटन या command="close" का इस्तेमाल करके, डायलॉग बॉक्स को बंद करें.
  • closedby को सोच-समझकर चुनें: any (लाइट डिसमिस और Esc), closerequest (सिर्फ़ Esc, मॉडल डिफ़ॉल्ट) या none (सिर्फ़ डेवलपर के कंट्रोल वाला क्लोज़).
  • ज़रूर इस्तेमाल करें: 'रद्द करें' बटन के लिए, command="request-close" या requestClose() का इस्तेमाल करें. इन बटन को सेव नहीं किए गए बदलावों के लॉजिक का पालन करना चाहिए. वे सबसे पहले cancel को ट्रिगर करते हैं और closedby को अनदेखा करते हैं.
  • closedby को परतदार वृद्धि के तौर पर इस्तेमाल करें. Esc और मोबाइल पर बैक जेस्चर की मदद से, पहले से ही मोडल बंद किए जा सकते हैं.

बंद करने के लिए हुक करना

  • dialog.returnValue को close लिसनर में पढ़ें, ताकि यह पता चल सके कि किस बटन का इस्तेमाल किया गया था. इसके बाद, इसे dialog.returnValue = '' से रीसेट करें. Esc और अन्य क्लोज़ रिक्वेस्ट कोई वैल्यू सेट नहीं करती हैं. साथ ही, ब्राउज़र के हिसाब से यह तय होता है कि वे पिछली वैल्यू को मिटाते हैं या नहीं. इसलिए, रीसेट किए बिना पुरानी वैल्यू लीक हो सकती है. '' को "बिना चुने खारिज किया गया" के तौर पर मानें.
  • Esc और request-close को इंटरसेप्ट करने के लिए, cancel का इस्तेमाल करें. हालांकि, उपयोगकर्ता की ओर से चालू किए बिना, Esc को बार-बार दबाने के लिए, preventDefault() पर भरोसा न करें.
  • ज़रूर इस्तेमाल करें closedby="none", ताकि अधूरे फ़ॉर्म के लिए साफ़ तौर पर पुष्टि करने का चरण जोड़ा जा सके.
  • ज़रूर बताएं कि cancel को रोकने पर डायलॉग बॉक्स क्यों खुला रहा: इसके लिए, एक मैसेज दिखाएं और फ़ोकस पाने वाले कंट्रोल (जैसे, खारिज करें) से aria-describedby के साथ उसका रेफ़रंस दें, ताकि फ़ोकस हटने पर स्क्रीन रीडर उसे पढ़ सके. इसके बिना, फ़ोकस किसी नए बटन पर जाने पर सिर्फ़ उस बटन का नाम सुनाई देता है.
  • पुष्टि करने वाले कंट्रोल को पॉइंटर के नीचे न ले जाएं. मैसेज के लिए जगह रिज़र्व करें (इसे visibility से छिपाएं, display या hidden से नहीं) और डिस्ट्रक्टिव बटन को वहां जोड़ें जहां मौजूदा बटन न हिलें (उदाहरण के लिए, दाईं ओर अलाइन की गई लाइन में सबसे पहले), ताकि उसी जगह पर दोबारा क्लिक करने से उपयोगकर्ता के बदलाव खारिज न हों.

नॉन-मोडल डायलॉग

  • टॉप-लेयर पैनल के लिए, <dialog popover> (auto या manual) का इस्तेमाल करें. अगर आपको टॉप लेयर नहीं चाहिए, तो dialog.show() का इस्तेमाल करें.
  • हर नॉन-मोडल डायलॉग में, 'बंद करें' बटन को साफ़ तौर पर दिखाएं.

बैकड्रॉप और स्टाइलिंग

सुलभता

  • role="dialog", aria-modal, मैन्युअल फ़ोकस ट्रैप या नेटिव मॉडल पर टॉगल करने के लिए inert को न जोड़ें.
  • ज़रूर करें: बंद करने के कंट्रोल के लिए, ऐक्सेस किए जा सकने वाले नाम के साथ नेटिव <button> का इस्तेमाल करें.
  • डायलॉग टाइटल के लिए, एक ही हेडिंग लेवल का इस्तेमाल करें.

फ़ॉलबैक रणनीतियां

closedby (dialog-closedby)

closedby के बिना भी, Esc और बैक जेस्चर का इस्तेमाल करके मॉडल को बंद किया जा सकता है. हालांकि, मॉडल के बाहर क्लिक करने पर वह बंद नहीं होगा. इसे ग्रेसफ़ुल डिग्रेडेशन कहा जाता है. closedby="none" को भी इसी तरह अनदेखा किया जाता है: Esc, cancel को ट्रिगर करता है और फिर डायलॉग को बंद कर देता है. सेव नहीं किए गए बदलावों को सुरक्षित रखने वाले गार्ड को closedby="none" के बजाय, cancel हैंडलर में रखें. इससे, उस जगह को भी सुरक्षित रखा जा सकता है जहां closedby मौजूद नहीं है. अगर आपके बेसलाइन टारगेट को लाइट डिसमिस की ज़रूरत है, तो इसे वहां जोड़ें जहां closedby मौजूद नहीं है:

if (!('closedBy' in HTMLDialogElement.prototype)) {
  // Test the pointer position, not event.target: the <dialog> is also the target for its own
  // padding and the gaps between its children, so a target check would close on those clicks
  const outside = (e, dialog) => {
    const r = dialog.getBoundingClientRect();
    return e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom;
  };
  for (const dialog of document.querySelectorAll('dialog[closedby="any"]')) {
    let pressedOutside = false;
    dialog.addEventListener('pointerdown', (e) => { pressedOutside = outside(e, dialog); });
    // Require the press and the release outside, so a text-selection drag that ends on the backdrop does not dismiss
    dialog.addEventListener('pointerup', (e) => { if (pressedOutside && outside(e, dialog)) dialog.close(); });
  }
}

overscroll-behavior

स्क्रोल कंटेनमेंट को परतदार वृद्धि के तौर पर इस्तेमाल करें. इसकी सुविधा का पता न लगाएं: @supports प्रॉपर्टी को काम करने वाली प्रॉपर्टी के तौर पर रिपोर्ट करता है. भले ही, स्क्रोल न किए जा सकने वाले कंटेनर इसे अनदेखा कर दें. जहां इसे लागू नहीं किया जाता है, वहां डायलॉग में अब भी स्क्रोल मौजूद होता है. ऐसा तब होता है, जब उसका कॉन्टेंट ओवरफ़्लो हो जाता है. साथ ही, showModal() अब भी पेज को इनर्ट बनाता है. html या body पर overflow: hidden या position: fixed का इस्तेमाल न करें. इससे रूट स्क्रोलबार हट जाता है और लेआउट बदल जाता है.

इनवोकर कमांड (command / commandfor)

अगर आपके Baseline टारगेट में Invoker Commands शामिल नहीं है, तो वही मार्कअप रखें और शर्त के साथ polyfill लोड करें. इससे आपको परतदार वृद्धि जैसा ही अनुभव मिलेगा. इंटरफ़ेस पर इसका पता लगाएं और इसे बिना किसी शर्त के कभी लोड न करें:

// MANDATORY: feature detect on HTMLButtonElement.prototype and load only when native support is missing
if (!('commandForElement' in HTMLButtonElement.prototype)) {
  import('invokers-polyfill'); // npm install invokers-polyfill
}

सिर्फ़ नियम 2 और 3 में पॉपओवर का इस्तेमाल किया जाता है.

पॉपओवर के लिए फ़ॉलबैक और ब्राउज़र की सुविधा

Popover API ज़्यादातर प्रोग्रेसिव एन्हांसमेंट है. हालांकि, इसके मुख्य व्यवहार — टॉप-लेयर प्रमोशन, लाइट-डिसमिस, और popovertarget को शुरू करना — सिर्फ़ सीएसएस के बराबर नहीं हैं. पुराने ब्राउज़र के लिए, polyfill की ज़रूरत होती है. अगर आपको polyfill का इस्तेमाल नहीं करना है, तो मैन्युअल फ़ॉलबैक की ज़रूरत होती है.

पॉलीफ़िल: पुराने ब्राउज़र में popover एट्रिब्यूट का इस्तेमाल करने के लिए, शर्त के मुताबिक @oddbird/popover-polyfill लोड करें. ज़रूरी: HTMLElement.prototype पर popover प्रॉपर्टी की जांच करके, सुविधा का पता लगाएं. साथ ही, पॉलीफ़िल को सिर्फ़ तब लोड करें, जब नेटिव सपोर्ट मौजूद न हो. इसे बिना किसी शर्त के लोड न करें.

बंडलर या इंपोर्ट मैप के साथ:

// MANDATORY: Feature detect 'popover' on HTMLElement.prototype.
if (!("popover" in HTMLElement.prototype)) {
  import("@oddbird/popover-polyfill");
}

बंडलर के बिना, <script type="module"> में सीडीएन से इंपोर्ट करें:

<script type="module">
  if (!("popover" in HTMLElement.prototype)) {
    import("https://unpkg.com/@oddbird/popover-polyfill@latest/dist/popover.min.js");
  }
</script>

स्टाइलिंग से जुड़ी चेतावनी: polyfill, असल :popover-open स्यूडो-क्लास को तय नहीं कर सकता. इसलिए, यह इसके बजाय .\:popover-open क्लास लागू करता है. ज़रूरी: दोनों को :is() या :where() के साथ जोड़ें. ऐसा न करने पर, जिन ब्राउज़र में :popover-open नहीं है वे पूरे नियम को खारिज कर देते हैं:

[popover]:is(:popover-open, .\:popover-open) {
  display: block;
}

इसके अलावा, बिना polyfill के लेगसी फ़ॉलबैक के लिए, position: fixed का इस्तेमाल करें और getBoundingClientRect() की मदद से मैन्युअल तरीके से निर्देशांकों का हिसाब लगाएं. इसके अलावा, अगर इस्तेमाल के उदाहरण के लिए यह स्वीकार्य है, तो inset: auto के साथ डिफ़ॉल्ट पोज़िशनिंग का इस्तेमाल करें.