diff --git a/components/ILIAS/IndividualAssessment/tests/Settings/ilIndividualAssessmentInfoSettingsTest.php b/components/ILIAS/IndividualAssessment/tests/Settings/ilIndividualAssessmentInfoSettingsTest.php index c31adadada56..9feb027ef93f 100755 --- a/components/ILIAS/IndividualAssessment/tests/Settings/ilIndividualAssessmentInfoSettingsTest.php +++ b/components/ILIAS/IndividualAssessment/tests/Settings/ilIndividualAssessmentInfoSettingsTest.php @@ -70,6 +70,7 @@ public function test_to_form_input() $refinery = new ILIAS\Refinery\Factory($df, $lng); $f = new ILIAS\UI\Implementation\Component\Input\Field\Factory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(\ILIAS\UI\Implementation\Component\Input\UploadLimitResolver::class), new ILIAS\UI\Implementation\Component\SignalGenerator(), $df, diff --git a/components/ILIAS/IndividualAssessment/tests/Settings/ilIndividualAssessmentSettingsTest.php b/components/ILIAS/IndividualAssessment/tests/Settings/ilIndividualAssessmentSettingsTest.php index 5b320c22f325..2722f4764eab 100755 --- a/components/ILIAS/IndividualAssessment/tests/Settings/ilIndividualAssessmentSettingsTest.php +++ b/components/ILIAS/IndividualAssessment/tests/Settings/ilIndividualAssessmentSettingsTest.php @@ -64,6 +64,7 @@ public function test_to_form_input() $refinery = new ILIAS\Refinery\Factory($df, $lng); $f = new ILIAS\UI\Implementation\Component\Input\Field\Factory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(\ILIAS\UI\Implementation\Component\Input\UploadLimitResolver::class), new ILIAS\UI\Implementation\Component\SignalGenerator(), $df, diff --git a/components/ILIAS/IndividualAssessment/tests/ilIndividualAssessmentUserGradingTest.php b/components/ILIAS/IndividualAssessment/tests/ilIndividualAssessmentUserGradingTest.php index 06a55b042693..5a682053977a 100755 --- a/components/ILIAS/IndividualAssessment/tests/ilIndividualAssessmentUserGradingTest.php +++ b/components/ILIAS/IndividualAssessment/tests/ilIndividualAssessmentUserGradingTest.php @@ -127,6 +127,7 @@ public function testToFormInput(): void $refinery = new ILIAS\Refinery\Factory($df, $lng); $f = new ILIAS\UI\Implementation\Component\Input\Field\Factory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(\ILIAS\UI\Implementation\Component\Input\UploadLimitResolver::class), new ILIAS\UI\Implementation\Component\SignalGenerator(), $df, diff --git a/components/ILIAS/OrgUnit/tests/ilModulesOrgUnitTypeTest.php b/components/ILIAS/OrgUnit/tests/ilModulesOrgUnitTypeTest.php index 15cd22d74e8f..a1cfb4a77d1d 100644 --- a/components/ILIAS/OrgUnit/tests/ilModulesOrgUnitTypeTest.php +++ b/components/ILIAS/OrgUnit/tests/ilModulesOrgUnitTypeTest.php @@ -41,6 +41,8 @@ public function mockGetAmdForm(array $available_records, ilOrgUnitType $type): S class ilModulesOrgUnitTypeTest extends TestCase { + use NameSourceStubs; + public function getRefinery(): \ILIAS\Refinery\Factory { $data_factory = new \ILIAS\Data\Factory(); @@ -52,30 +54,36 @@ public function getRefinery(): \ILIAS\Refinery\Factory public function getUIFactory(): NoUIFactory { $node_factory = $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class); + $has_dynamic_inputs_name_source = $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class); $language = $this->createMock(ilLanguage::class); $filter_factory = $this->createMock(Component\Input\Container\Filter\Factory::class); $view_control_factory = $this->createMock(Component\Input\Container\ViewControl\Factory::class); $control_factory = $this->createMock(Component\Input\ViewControl\Factory::class); $upload_limit_resolver = $this->createMock(Component\Input\UploadLimitResolver::class); $refinery = $this->getRefinery(); + $name_source = $this->createCountingNameSourceStub('input_'); $factory = new class ( $node_factory, + $has_dynamic_inputs_name_source, $language, $filter_factory, $view_control_factory, $control_factory, $upload_limit_resolver, - $refinery + $refinery, + $name_source, ) extends NoUIFactory { public function __construct( protected $node_factory, + protected $has_dynamic_inputs_name_source, protected $language, protected $filter_factory, protected $view_control_factory, protected $control_factory, protected $upload_limit_resolver, - protected $refinery + protected $refinery, + protected $name_source, ) { } @@ -86,6 +94,7 @@ public function input(): Component\Input\Factory $field_factory = new Component\Input\Field\Factory( $this->node_factory, + $this->has_dynamic_inputs_name_source, $this->upload_limit_resolver, $signal_generator, $data_factory, @@ -95,7 +104,8 @@ public function input(): Component\Input\Factory $form_factory = new Component\Input\Container\Form\Factory( $field_factory, - $signal_generator + $signal_generator, + $this->name_source, ); $container_factory = new Component\Input\Container\Factory( $form_factory, diff --git a/components/ILIAS/StudyProgramme/tests/ilStudyProgrammeAssessmentSettingsTest.php b/components/ILIAS/StudyProgramme/tests/ilStudyProgrammeAssessmentSettingsTest.php index 4f660dd31276..cfb9df2763d0 100755 --- a/components/ILIAS/StudyProgramme/tests/ilStudyProgrammeAssessmentSettingsTest.php +++ b/components/ILIAS/StudyProgramme/tests/ilStudyProgrammeAssessmentSettingsTest.php @@ -122,6 +122,7 @@ public function testToFormInput(): void $f = new ILIAS\UI\Implementation\Component\Input\Field\Factory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(\ILIAS\UI\Implementation\Component\Input\UploadLimitResolver::class), new ILIAS\UI\Implementation\Component\SignalGenerator(), $df, diff --git a/components/ILIAS/StudyProgramme/tests/ilStudyProgrammeAutoMailSettingsTest.php b/components/ILIAS/StudyProgramme/tests/ilStudyProgrammeAutoMailSettingsTest.php index b4829215fcda..5054d424146d 100755 --- a/components/ILIAS/StudyProgramme/tests/ilStudyProgrammeAutoMailSettingsTest.php +++ b/components/ILIAS/StudyProgramme/tests/ilStudyProgrammeAutoMailSettingsTest.php @@ -196,6 +196,7 @@ public function testToFormInput(): void $f = new ILIAS\UI\Implementation\Component\Input\Field\Factory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(\ILIAS\UI\Implementation\Component\Input\UploadLimitResolver::class), new ILIAS\UI\Implementation\Component\SignalGenerator(), $df, diff --git a/components/ILIAS/StudyProgramme/tests/ilStudyProgrammeDeadlineSettingsTest.php b/components/ILIAS/StudyProgramme/tests/ilStudyProgrammeDeadlineSettingsTest.php index 958490fdf834..ffd38312d198 100755 --- a/components/ILIAS/StudyProgramme/tests/ilStudyProgrammeDeadlineSettingsTest.php +++ b/components/ILIAS/StudyProgramme/tests/ilStudyProgrammeDeadlineSettingsTest.php @@ -89,6 +89,7 @@ public function testToFormInput(): void $f = new ILIAS\UI\Implementation\Component\Input\Field\Factory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(\ILIAS\UI\Implementation\Component\Input\UploadLimitResolver::class), new ILIAS\UI\Implementation\Component\SignalGenerator(), $df, diff --git a/components/ILIAS/StudyProgramme/tests/ilStudyProgrammeTypeSettingsTest.php b/components/ILIAS/StudyProgramme/tests/ilStudyProgrammeTypeSettingsTest.php index b97f3bc03bf9..d377d9067fdf 100755 --- a/components/ILIAS/StudyProgramme/tests/ilStudyProgrammeTypeSettingsTest.php +++ b/components/ILIAS/StudyProgramme/tests/ilStudyProgrammeTypeSettingsTest.php @@ -50,6 +50,7 @@ public function testToFormInput(): void $f = new ILIAS\UI\Implementation\Component\Input\Field\Factory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(\ILIAS\UI\Implementation\Component\Input\UploadLimitResolver::class), new ILIAS\UI\Implementation\Component\SignalGenerator(), $df, diff --git a/components/ILIAS/StudyProgramme/tests/ilStudyProgrammeValidityOfAchievedQualificationSettingsTest.php b/components/ILIAS/StudyProgramme/tests/ilStudyProgrammeValidityOfAchievedQualificationSettingsTest.php index b03f8cb9d1a1..24cc40b1df6c 100755 --- a/components/ILIAS/StudyProgramme/tests/ilStudyProgrammeValidityOfAchievedQualificationSettingsTest.php +++ b/components/ILIAS/StudyProgramme/tests/ilStudyProgrammeValidityOfAchievedQualificationSettingsTest.php @@ -149,6 +149,7 @@ public function testToFormInput(): void $f = new ILIAS\UI\Implementation\Component\Input\Field\Factory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(\ILIAS\UI\Implementation\Component\Input\UploadLimitResolver::class), new ILIAS\UI\Implementation\Component\SignalGenerator(), $df, diff --git a/components/ILIAS/StudyProgramme/tests/model/Settings/ilStudyProgrammeSubSettingsTest.php b/components/ILIAS/StudyProgramme/tests/model/Settings/ilStudyProgrammeSubSettingsTest.php index 8dddc44d9f80..e4ce1bb20d04 100644 --- a/components/ILIAS/StudyProgramme/tests/model/Settings/ilStudyProgrammeSubSettingsTest.php +++ b/components/ILIAS/StudyProgramme/tests/model/Settings/ilStudyProgrammeSubSettingsTest.php @@ -47,6 +47,7 @@ public function setUp(): void $this->refinery = new Refinery($this->data_factory, $this->lng); $this->field_factory = new FieldFactory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(UploadLimitResolver::class), new SignalGenerator(), $this->data_factory, diff --git a/components/ILIAS/StudyProgramme/tests/types/ilStudyProgrammeTypeInfoTest.php b/components/ILIAS/StudyProgramme/tests/types/ilStudyProgrammeTypeInfoTest.php index 07d2f0cc54b8..a97d4af83357 100755 --- a/components/ILIAS/StudyProgramme/tests/types/ilStudyProgrammeTypeInfoTest.php +++ b/components/ILIAS/StudyProgramme/tests/types/ilStudyProgrammeTypeInfoTest.php @@ -127,6 +127,7 @@ public function testToFormInput(): void $f = new ILIAS\UI\Implementation\Component\Input\Field\Factory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(\ILIAS\UI\Implementation\Component\Input\UploadLimitResolver::class), new ILIAS\UI\Implementation\Component\SignalGenerator(), $df, diff --git a/components/ILIAS/Test/tests/Scoring/Settings/ScoreSettingsTest.php b/components/ILIAS/Test/tests/Scoring/Settings/ScoreSettingsTest.php index 88132d15e0fb..0964289c87f7 100755 --- a/components/ILIAS/Test/tests/Scoring/Settings/ScoreSettingsTest.php +++ b/components/ILIAS/Test/tests/Scoring/Settings/ScoreSettingsTest.php @@ -243,6 +243,7 @@ public function testScoreSettingsSectionSummary(): void $field_factory = new ILIAS\UI\Implementation\Component\Input\Field\Factory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(\ILIAS\UI\Implementation\Component\Input\UploadLimitResolver::class), new \ILIAS\UI\Implementation\Component\SignalGenerator(), $data_factory, diff --git a/components/ILIAS/UI/UI.php b/components/ILIAS/UI/UI.php index 395619bf91c2..2c1ed5c79d42 100644 --- a/components/ILIAS/UI/UI.php +++ b/components/ILIAS/UI/UI.php @@ -249,6 +249,7 @@ public function init( $internal[UI\Implementation\Component\SignalGeneratorInterface::class], $internal[UI\Implementation\Component\Modal\InterruptiveItem\Factory::class], $internal[UI\Implementation\Component\Input\Field\Factory::class], + $internal[UI\Implementation\Component\Input\DefaultNameSource::class], ); $internal[UI\Implementation\Component\SignalGeneratorInterface::class] = static fn() => new UI\Implementation\Component\SignalGenerator(); @@ -278,6 +279,7 @@ public function init( new UI\Implementation\Component\Dropzone\File\Factory( $internal[UI\Implementation\Component\SignalGeneratorInterface::class], $internal[UI\Implementation\Component\Input\Field\Factory::class], + $internal[UI\Implementation\Component\Input\DefaultNameSource::class], ); $internal[UI\Implementation\Component\Popover\Factory::class] = static fn() => @@ -322,6 +324,7 @@ public function init( $internal[UI\Implementation\Component\Input\Field\Factory::class] = static fn() => new UI\Implementation\Component\Input\Field\Factory( $internal[UI\Implementation\Component\Input\Field\Node\Factory::class], + $internal[UI\Implementation\Component\Input\HasDynamicInputsNameSource::class], $internal[UI\Implementation\Component\Input\UploadLimitResolver::class], $internal[UI\Implementation\Component\SignalGeneratorInterface::class], $pull[Data\Factory::class], @@ -345,16 +348,19 @@ public function init( new UI\Implementation\Component\Input\Container\Form\Factory( $internal[UI\Implementation\Component\Input\Field\Factory::class], $internal[UI\Implementation\Component\SignalGeneratorInterface::class], + $internal[UI\Implementation\Component\Input\DefaultNameSource::class], ); $internal[UI\Implementation\Component\Input\Container\Filter\Factory::class] = static fn() => new UI\Implementation\Component\Input\Container\Filter\Factory( $internal[UI\Implementation\Component\SignalGeneratorInterface::class], $internal[UI\Implementation\Component\Input\Field\Factory::class], + $internal[UI\Implementation\Component\Input\DefaultNameSource::class], ); $internal[UI\Implementation\Component\Input\Container\ViewControl\Factory::class] = static fn() => new UI\Implementation\Component\Input\Container\ViewControl\Factory( $internal[UI\Implementation\Component\SignalGeneratorInterface::class], $internal[UI\Implementation\Component\Input\ViewControl\Factory::class], + $internal[UI\Implementation\Component\Input\DefaultNameSource::class], ); $internal[UI\Implementation\Component\Input\ViewControl\Factory::class] = static fn() => new UI\Implementation\Component\Input\ViewControl\Factory( @@ -364,6 +370,12 @@ public function init( $internal[UI\Implementation\Component\SignalGeneratorInterface::class], $use[Language\Language::class], ); + $internal[UI\Implementation\Component\Input\DefaultNameSource::class] = static fn() => + new UI\Implementation\Component\Input\DefaultNameSource(); + $internal[UI\Implementation\Component\Input\HasDynamicInputsNameSource::class] = static fn() => + new UI\Implementation\Component\Input\HasDynamicInputsNameSource( + $internal[UI\Implementation\Component\Input\DefaultNameSource::class], + ); $internal[UI\Implementation\Component\Table\Factory::class] = static fn() => new UI\Implementation\Component\Table\Factory( diff --git a/components/ILIAS/UI/docs/ROADMAP.md b/components/ILIAS/UI/docs/ROADMAP.md index 1b4909ca86b1..884e5822e51c 100755 --- a/components/ILIAS/UI/docs/ROADMAP.md +++ b/components/ILIAS/UI/docs/ROADMAP.md @@ -291,6 +291,25 @@ Storage of paramters in DataTable and SequenceNavigation look very much alike; in favor of those and further/future components the implementation should be realized as a trait to be used by several components. +### Remove all internal usages of `withDedicatedName()` (beginner, 8h) + +`ILIAS\UI\Component\Input\Container\Container::withDedicatedName()` and +`ILIAS\UI\Component\Input\Input::withDedicatedName()` are currently used internally in a few places. When this happens, +we sometimes override already provided dedicated names (of the consumer) and introduce a hidden dependance on an +implementation detail. Since consumers MUST NOT rely on the names of inputs during data processing, this behaviour adds +complexity without clear benefit. Removing these internal usages will streamline naming and make the framework easier to +understand and maintain. This MAY be the first step towards removing `withDedicatedName()` altogether, if no advantage +will arise soon. + +### Remove `OrderingTableContextRenderer` and revise `Table\Ordering` composition (advanced, ~5d) + +The `ILIAS\UI\Implementation\Component\Input\Field\OrderingTableContextRenderer` was introduced during the refactoring +of `ILIAS\UI\Implementation\Component\Input\NameSource`. Its a temporary workaround that replaced the implementation of +such name source as an inline anonymous class inside the table context renderer. This name source was used in order to +bypass input mechanics in order to set the ordering row ids as dedicated input names directly as the actual input name. +This highlights the fact that `Table\Ordering` is some unfinished hybrid between a form input container and a table, +which is not ideal. The next step is to revisit the composition and interal handling of the ordering table, remove this +temporary workaround, and ensure the table complies with the normal mechanics of our inputs. ## Long Term diff --git a/components/ILIAS/UI/resources/js/Input/Field/dist/input.factory.min.js b/components/ILIAS/UI/resources/js/Input/Field/dist/input.factory.min.js index 04d1d0b07553..3e313cf75c81 100644 --- a/components/ILIAS/UI/resources/js/Input/Field/dist/input.factory.min.js +++ b/components/ILIAS/UI/resources/js/Input/Field/dist/input.factory.min.js @@ -12,7 +12,7 @@ * https://www.ilias.de * https://github.com/ILIAS-eLearning */ -!function(t,e,n){"use strict";class i{textarea;remainder=null;constructor(t){if(this.textarea=document.getElementById(t),null===this.textarea)throw new Error(`Could not find textarea for input-id '${t}'.`);if(this.shouldShowRemainder()){if(this.remainder=this.textarea.parentNode.querySelector('[data-action="remainder"]'),!this.remainder instanceof HTMLSpanElement)throw new Error(`Could not find remainder-element for input-id '${t}'.`);this.textarea.addEventListener("input",(()=>{this.updateRemainderCountHook()}))}}updateRemainderCountHook(){this.shouldShowRemainder()&&null!==this.remainder&&(this.remainder.innerHTML=(this.textarea.maxLength-this.textarea.value.length).toString())}updateTextareaContent(t,e=null,n=null){if(!this.isDisabled()){if(this.isContentTooLarge(t))return this.updateRemainderCountHook(),void this.textarea.focus();e=e??this.textarea.selectionStart,n=n??this.textarea.selectionEnd,this.textarea.value=t,ethis.textarea.selectionEnd?this.textarea.selectionStart:this.textarea.selectionEnd}getLinesBeforeSelection(){return a(this.textarea.value).slice(0,s(this.getTextBeforeSelection()))}getLinesAfterSelection(){const t=a(this.textarea.value);return t.slice(s(this.getTextBeforeSelection()+this.getTextOfSelection())+1,t.length)}getLinesOfSelection(){const t=a(this.textarea.value);return t.slice(this.getLinesBeforeSelection().length,t.length-this.getLinesAfterSelection().length)}isContentTooLarge(t){const e=this.getMaxLength();return!(e<0)&&e0}getMaxLength(){return Number(this.textarea.getAttribute("maxlength")??-1)}isDisabled(){return this.textarea.disabled}}function s(t){return(t.match(/\n/g)??[]).length}function a(t){return t.split(/\n/)}class o{instances=[];init(t){if(void 0!==this.instances[t])throw new Error(`Textarea with input-id '${t}' has already been initialized.`);this.instances[t]=new i(t)}get(t){return this.instances[t]??null}}class r{preview_parameter;preview_url;constructor(t,e){this.preview_parameter=t,this.preview_url=e}async getPreviewHtmlOf(t){if(0===t.length)return"";let e=new FormData;return e.append(this.preview_parameter,t),(await fetch(this.preview_url,{method:"POST",body:e})).text()}}const l="textarea",d="preview";class c extends i{preview_history=[];preview_renderer;content_wrappers;view_controls;actions;constructor(t,e){super(e);const n=this.textarea.closest(".c-field-markdown");if(null===n)throw new Error(`Could not find input-wrapper for input-id '${e}'.`);this.preview_renderer=t,this.content_wrappers=function(t){const e=new Map;return e.set(l,t.querySelector("textarea")),e.set(d,t.querySelector(".c-field-markdown__preview")),e.forEach((t=>{if(null===t)throw new Error("Could not find all content-wrappers for markdown-input.")})),e}(n),this.view_controls=function(t){const e=t.querySelector(".il-viewcontrol-mode")?.getElementsByTagName("button");if(!e instanceof HTMLCollection||2!==e.length)throw new Error("Could not find exactly two view-controls.");return[...e]}(n),this.actions=function(t){const e=t.querySelector(".c-field-markdown__actions")?.getElementsByTagName("button");if(e instanceof HTMLCollection)return[...e];return[]}(n);let i=!0;this.textarea.addEventListener("keydown",(t=>{i=this.handleEnterKeyBeforeInsertionHook(t)})),this.textarea.addEventListener("keyup",(t=>{this.handleEnterKeyAfterInsertionHook(t,i)})),this.actions.forEach((t=>{t.addEventListener("click",(t=>{this.performMarkdownActionHook(t)}))})),this.view_controls.forEach((t=>{t.addEventListener("click",(()=>{this.toggleViewingModeHook()}))}))}handleEnterKeyAfterInsertionHook(t,e){if(!e||!p(t))return;const n=this.getLinesBeforeSelection().pop();void 0!==n&&m(n)?this.applyTransformationToSelection(u):void 0!==n&&v(n)&&this.insertSingleEnumeration()}handleEnterKeyBeforeInsertionHook(t){if(!p(t))return!1;const e=this.getLinesOfSelection().shift();if(void 0===e||!((e.match(/((^(\s*-)|(^(\s*\d+\.)))\s*)$/g)??[]).length>0))return!0;let n=this.getLinesBeforeSelection().join("\n"),i=this.getLinesAfterSelection().join("\n");return n.length>0&&(n+="\n"),i.length>0&&(i=`\n${i}`),this.updateTextareaContent(n+i,this.getAbsoluteSelectionStart()-e.length,this.getAbsoluteSelectionEnd()-e.length),t.preventDefault(),!1}performMarkdownActionHook(t){const e=function(t){const e=t.closest("span[data-action]");if(!e instanceof HTMLSpanElement)return null;if(!e.hasAttribute("data-action"))return null;return e.dataset.action}(t.target);switch(e){case"insert-heading":this.insertCharactersAroundSelection("# ","");break;case"insert-link":this.insertCharactersAroundSelection("[","](url)");break;case"insert-bold":this.insertCharactersAroundSelection("**","**");break;case"insert-italic":this.insertCharactersAroundSelection("_","_");break;case"insert-bullet-points":this.applyTransformationToSelection(u);break;case"insert-enumeration":this.isMultilineTextSelected()?this.applyTransformationToSelection(h):this.insertSingleEnumeration();break;default:throw new Error(`Could not perform markdown-action '${e}'.`)}}toggleViewingModeHook(){this.content_wrappers.forEach((t=>{g(t,"hidden")})),this.view_controls.forEach((t=>{g(t,"engaged")})),this.isDisabled()||this.actions.forEach((t=>{t.disabled=!t.disabled;const e=t.querySelector(".glyph");null!==e&&g(e,"disabled")})),this.maybeUpdatePreviewContent()}insertSingleEnumeration(){const t=this.getLinesOfSelection();if(1!==t.length)return void this.textarea.focus();const e=this.getLinesBeforeSelection(),n=e.length-1;let i=n>=0?function(t){const e=t.match(/([0-9]+)/);if(null!==e)return parseInt(e[0]);return null}(e[n])??0:0;const s=h(t,++i),a=function(t,e=0){if(t.length<1)return[];const n=[];for(const i of t){if(!v(i))break;n.push(i.replace(/([0-9]+)/,(++e).toString()))}n.length>0&&(t=n.concat(t.slice(n.length)));return t}(this.getLinesAfterSelection(),i);let o=e.join("\n");const r=a.join("\n");let l=s.join("\n");o.length>0&&l.length>0&&(o+="\n"),l.length>0&&r.length>0&&(l+="\n");const d=o+l+r,c=d.length-this.textarea.value.length;this.updateTextareaContent(d,this.getAbsoluteSelectionStart()+c,this.getAbsoluteSelectionEnd()+c)}applyTransformationToSelection(t){if(!t instanceof Function)throw new Error(`Transformation must be an instance of Function, ${typeof t} given.`);const e=t(this.getLinesOfSelection());if(!e instanceof Array)throw new Error(`Transformation must return an instance of Array, ${typeof e} returned.`);const n=e.length>1;let i=this.getLinesBeforeSelection().join("\n");const s=this.getLinesAfterSelection().join("\n");let a=e.join("\n");i.length>0&&a.length>0&&(i+="\n"),a.length>0&&s.length>0&&(a+="\n");const o=i+a+s,r=o.length-this.textarea.value.length,l=n?i.length:this.getAbsoluteSelectionStart()+r,d=n?l+a.length-1:this.getAbsoluteSelectionEnd()+r;this.updateTextareaContent(o,l,d)}maybeUpdatePreviewContent(){const t=this.preview_history[this.preview_history.length-1]??"",e=this.textarea.value;e!==t&&(this.preview_history.push(e),this.preview_renderer.getPreviewHtmlOf(e).then((t=>{this.content_wrappers.get(d).innerHTML=t})))}getBulletPointTransformation(){return u}getEnumerationTransformation(){return h}}function u(t){const e=[],n=!m(t[0]??"");for(const i of t)e.push(n?`- ${i}`:f(i));return e}function h(t,e=1){const n=[],i=!v(t[0]??"");for(const s of t)n.push(i?`${e++}. ${s}`:f(s));return n}function g(t,e){t.classList.contains(e)?t.classList.remove(e):t.classList.add(e)}function p(t){return t instanceof KeyboardEvent&&"Enter"===t.code}function f(t){return t.replace(/((^(\s*[-])|(^(\s*\d+\.)))\s*)/g,"")}function m(t){return(t.match(/^(\s*[-])/g)??[]).length>0}function v(t){return(t.match(/^(\s*\d+\.)/g)??[]).length>0}class w{instances=[];init(t,e,n){if(void 0!==this.instances[t])throw new Error(`Markdown with input-id '${t}' has already been initialized.`);this.instances[t]=new c(new r(n,e),t)}get(t){return this.instances[t]??null}}class y{constructor(t,e,n,i,s,a=null,o=null,r=null){this.id=t,this.name=e,this.element=n,this.selectButton=i,this.drilldownParentLevel=s,this.drilldownButton=a,this.listElement=o,this.renderUrl=r}}const b="data-node-id",T="data-node-name",S="data-render-url",x="data-ddindex",E="c-input-node",O="c-input-tree_select",D=`${E}__async`,C=`${E}__leaf`,I=`${E}--selected`,A="hidden",M="disabled",N=".glyph",_=`.${E}`,L=`.${O}`,k=`.${O}__selection`,P='[data-action="remove"]',B='[data-action="select"]',j=`.${E}__select`,V=".c-drilldown__menulevel--trigger";function F(t){return function(t){return t.classList.contains(D)}(t)&&t.hasAttribute(S)?t.getAttribute(S):null}function H(t){return!t.classList.contains(C)&&t.classList.contains(E)}function q(t,e=null){return t.reduce(((t,e)=>{const n=function(t){const e=t.getAttribute(b);if(null===e)throw new Error("Could not find data-node-id attribute.");return e}(e);if(t.has(n))throw new Error(`Node '${n}' has already been parsed. There might be a rendering issue.`);return t.set(n,new y(n,function(t){const e=t.querySelector(`[${T}]`);if(null===e)throw new Error("Could not find element with data-node-name attribute.");return e.textContent}(e),e,function(t){const e=t.querySelector(`:scope > ${j}`);if(null===e)throw new Error("Could not find node select button.");return e}(e),function(t){const e=t.closest(`ul[${x}]`);if(null===e)throw new Error("Could not find drilldown menu of node.");return e.getAttribute(x)}(e),function(t){if(!H(t))return null;const e=t.querySelector(`${V}`);if(null===e)throw new Error("Could not find drilldown menu button of branch node.");return e}(e),function(t){if(!H(t))return null;const e=t.querySelector("ul");if(null===e)throw new Error("Could not find list element of branch node.");return e}(e),F(e)))}),new Map(e??[]))}function R(t,e){for(let n=0;nn.shift()??""))}function U(t,e){t.classList.toggle(I,e)}class W{#t;#e=new Set;#n=new Set;#i=new Set;#s;#a;#o;#r;#l;#d;#c;#u;#h;#g;#p;#f;constructor(t,e,n,i,s,a,o,r,l,d,c,u,h,g){this.#t=t,this.#s=n,this.#a=i,this.#o=s,this.#r=a,this.#l=o,this.#d=r,this.#c=l,this.#u=d,this.#h=c,this.#g=u,this.#p=h,this.#f=g,e.on(this.#p.ownerDocument,this.#r.getBackSignal(),(()=>{this.#m()})),this.#p.querySelectorAll('[data-action="close"]').forEach((t=>{t.addEventListener("click",(()=>{this.#v()}))})),this.#r.addEngageListener((t=>{this.#w(t)})),this.#g.addEventListener("click",(()=>{this.#y()})),this.#t.forEach((t=>{this.#b(t)})),this.#c.querySelectorAll("li").forEach((t=>{const e=function(t){const e=t.getAttribute(b);if(null===e)throw new Error(`Could not find '${b}' attribbute of element.`);return e}(t);this.#T(t,e),this.selectNode(e)})),this.#w(this.#r.getCurrentLevel()),this.#S()}unselectNode(t){if(this.#x(t),this.#S(),this.#E(t),this.#t.has(t)){const e=this.#t.get(t);U(e.element,!1),this.#O(e.selectButton,e.name)}this.#f(this)}selectNode(t){if(this.#D(t),this.#S(),this.#t.has(t)){const e=this.#t.get(t);U(e.element,!0),this.#C(e.selectButton,e.name),this.#I(e)}this.#f(this)}engageNode(t){if(!this.#t.has(t))return;const e=this.#t.get(t).drilldownParentLevel;this.#r.getCurrentLevel()!==e&&this.#r.getParentLevel()!==e&&this.#r.engageLevel(e)}getSelection(){return new Set(this.#e)}getNodes(){return new Map(this.#t)}async#A(t){var e,n,i;if(!this.#n.has(t.id)&&!this.#i.has(t.id))try{this.#i.add(t.id);const s=await this.#a.loadContent(t.renderUrl);t.listElement.append(...s.children),this.#r.parseLevels();const a=q((i=t.listElement,Array.from(i.querySelectorAll(_))),this.#t),o=(e=a,n=this.#t,Array.from(e.entries()).filter((([t])=>!n.has(t))).map((([,t])=>t)));this.#t=a,R(o,(t=>{this.#e.has(t.id)?this.selectNode(t.id):this.unselectNode(t.id),this.#b(t)})),this.#n.add(t.id)}catch(t){throw new Error(`Could not render async node children: ${t.message}`)}finally{this.#i.delete(t.id)}}#M(t){R(function(t,e,n=255){const i=[];let s=t;for(let t=0;t{const e=t.getAttribute(b);if(null===e||!this.#t.has(e))throw new Error(`Could not find '${b}' of node element.`);const n=this.#t.get(e);this.#N(n)}))}#N(t){const e=this.#s.createContent(this.#d).querySelector(".crumb");e.setAttribute(x,t.drilldownParentLevel),e.firstElementChild.textContent=t.name,e.addEventListener("click",(()=>{this.#r.engageLevel(t.drilldownParentLevel),t.drilldownButton.click()})),this.#l.append(e)}#m(){const t=this.#l.querySelectorAll(".crumb");t.item(t.length-1)?.remove()}#_(){R(this.#l.querySelectorAll(".crumb"),(t=>{t.remove()}))}#w(t){if("0"===t)return void this.#_();const e=this.#p.querySelector(`ul[${x}="${t}"]`)?.closest(_)?.getAttribute(b);if(null===e||!this.#t.has(e))throw new Error(`Could not find node for drilldown-level '${t}'.`);const n=this.#t.get(e);this.#_(),this.#M(n),null!==n.renderUrl&&this.#A(n)}#T(t,e){t.querySelector(P)?.addEventListener("click",(()=>{this.unselectNode(e),t.remove()}))}#L(t,e){t.addEventListener("click",(()=>{this.#e.has(e.id)?this.unselectNode(e.id):this.selectNode(e.id)}))}#I(t){if(null!==this.#c.querySelector(`li[${b}="${t.id}"]`))return;const e=this.#s.createContent(this.#u),n=e.querySelector("[data-node-id]");n.setAttribute(b,t.id),n.querySelector(`[${T}]`).textContent=t.name,n.querySelector("input").value=t.id,this.#T(n,t.id),this.#c.append(...e.children)}#E(t){this.#c.querySelector(`li[${b}="${t}"]`)?.remove()}#b(t){this.#L(t.selectButton,t)}#O(t,e){t.querySelector(P)?.classList.add(A),t.querySelector(B)?.classList.remove(A),t.setAttribute("aria-label",this.#k("select_node",e))}#C(t,e){t.querySelector(B)?.classList.add(A),t.querySelector(P)?.classList.remove(A),t.setAttribute("aria-label",this.#k("unselect_node",e))}#S(){this.#h.disabled=this.#e.size<=0}#x(t){this.#e.has(t)&&this.#e.delete(t)}#D(t){this.#e.has(t)||this.#e.add(t)}#k(t,...e){return $(this.#o.txt(t),e)}#v(){this.#p.close()}#y(){this.#p.showModal()}}function z(t,e){const n=t.createDocumentFragment();return n.append(...e),n}function K(t,e,n){t.querySelectorAll(`[${n}]`).forEach((t=>{const i=t.getAttribute(n);if(!e.has(i))throw new Error(`Element references '${i}' which does not exist.`);t.setAttribute(n,e.get(i))}))}class X{#P;constructor(t){this.#P=t}createContent(t){const e=t.content.cloneNode(!0),n=new Map;return e.querySelectorAll("[id]").forEach((t=>{const e=function(t=""){return`${t}${Date.now().toString(36)}_${Math.random().toString(36).substring(2)}`}("il_ui_fw_");n.set(t.id,e),t.id=e})),e.querySelectorAll("[for]").forEach((t=>{t.htmlFor=n.get(t.htmlFor)})),K(e,n,"aria-describedby"),K(e,n,"aria-labelledby"),K(e,n,"aria-controls"),K(e,n,"aria-owns"),z(this.#P,e.children)}}class J{#P;constructor(t){this.#P=t}loadContent(t){return fetch(t.toString()).then((t=>t.text())).then((t=>this.#B(t))).then((t=>z(this.#P,t))).catch((e=>{throw new Error(`Could not render element(s) from '${t}': ${e.message}`)}))}#j(t){const e=this.#P.createElement("script");return t.hasAttribute("type")&&e.setAttribute("type",t.getAttribute("type")),t.hasAttribute("src")&&e.setAttribute("src",t.getAttribute("src")),t.textContent.length>0&&(e.textContent=t.textContent),e}#B(t){const e=this.#P.createElement("div");return e.innerHTML=t.trim(),e.querySelectorAll("script").forEach((t=>{const e=this.#j(t);t.replaceWith(e)})),e.children}}function Q(t){return Array.from(t.querySelectorAll(_))}function G(){return t=>{!function(t){const e=t.getNodes(),n=t.getSelection();e.forEach(((t,e)=>{n.size>0?(t.selectButton.disabled=!n.has(e),t.selectButton.querySelector(N).classList.toggle(M,!n.has(e))):(t.selectButton.disabled=!1,t.selectButton.querySelector(N).classList.toggle(M,!1))}))}(t),function(t){const e=t.getSelection();if(1===e.size){const n=e.values()?.next()?.value;t.getNodes().has(n)&&t.engageNode(n)}}(t)}}function Y(t){return t?()=>{}:t=>{!function(t){const e=Array.from(t.getSelection()),n=t.getNodes();for(let s=0;s{t.selectButton.disabled=!1,t.selectButton.querySelector(N).classList.remove(M)})),n.forEach((t=>{const n=e.get(t);null!==n&&null!==n.listElement&&n.listElement.querySelectorAll(j).forEach((t=>{t.disabled=!0,t.querySelector(N).classList.add(M)}))}))}(t)}}class Z{#V=new Map;#F;#H;#o;#P;constructor(t,e,n,i){this.#F=t,this.#H=e,this.#o=n,this.#P=i}initTreeMultiSelect(t,e){if(this.#V.has(t))throw new Error(`TreeSelect '${t}' already exists.`);const[n,i,s,a,o,r,l,d]=this.#q(t),c=this.#R(i),u=new W(q(Q(l)),this.#F,new X(this.#P),new J(this.#P),this.#o,c,s,a,o,r,d,n,l,Y(e));return this.#V.set(t,u),u}initTreeSelect(t){if(this.#V.has(t))throw new Error(`TreeSelect '${t}' already exists.`);const[e,n,i,s,a,o,r,l]=this.#q(t),d=this.#R(n),c=new W(q(Q(r)),this.#F,new X(this.#P),new J(this.#P),this.#o,d,i,s,a,o,l,e,r,G());return this.#V.set(t,c),c}getInstance(t){return this.#V.has(t)?this.#V.get(t):null}#q(t){const e=this.#P.getElementById(t),n=e?.closest(L),i=n?.querySelector(".breadcrumb"),s=n?.querySelector(".modal-body > template"),a=n?.querySelector(k),o=a?.querySelector(":scope > template"),r=n?.querySelector("dialog"),l=r?.querySelector(".btn-primary");if(null===i||null===s||null===a||null===o||null===l||null===e||null===r)throw new Error(`Could not find some element(s) for Tree Select Input '${t}'.`);return[e,n,i,s,a,o,r,l]}#R(t){const e=t.querySelector(".c-drilldown");if(null===e||!e.hasAttribute("id"))throw new Error("Could not find drilldown element.");const n=this.#H.getInstance(e.id);if(null===e)throw new Error("Could not find drilldown instance.");return n}}class tt{#$;constructor(t){this.#$=t}on(t,e,n){this.#$(t).on(e,n)}off(t,e,n){this.#$(t).off(e,n)}} +!function(t,e,n){"use strict";class i{textarea;remainder=null;constructor(t){if(this.textarea=document.getElementById(t),null===this.textarea)throw new Error(`Could not find textarea for input-id '${t}'.`);if(this.shouldShowRemainder()){if(this.remainder=this.textarea.parentNode.querySelector('[data-action="remainder"]'),!this.remainder instanceof HTMLSpanElement)throw new Error(`Could not find remainder-element for input-id '${t}'.`);this.textarea.addEventListener("input",(()=>{this.updateRemainderCountHook()}))}}updateRemainderCountHook(){this.shouldShowRemainder()&&null!==this.remainder&&(this.remainder.innerHTML=(this.textarea.maxLength-this.textarea.value.length).toString())}updateTextareaContent(t,e=null,n=null){if(!this.isDisabled()){if(this.isContentTooLarge(t))return this.updateRemainderCountHook(),void this.textarea.focus();e=e??this.textarea.selectionStart,n=n??this.textarea.selectionEnd,this.textarea.value=t,ethis.textarea.selectionEnd?this.textarea.selectionStart:this.textarea.selectionEnd}getLinesBeforeSelection(){return a(this.textarea.value).slice(0,s(this.getTextBeforeSelection()))}getLinesAfterSelection(){const t=a(this.textarea.value);return t.slice(s(this.getTextBeforeSelection()+this.getTextOfSelection())+1,t.length)}getLinesOfSelection(){const t=a(this.textarea.value);return t.slice(this.getLinesBeforeSelection().length,t.length-this.getLinesAfterSelection().length)}isContentTooLarge(t){const e=this.getMaxLength();return!(e<0)&&e0}getMaxLength(){return Number(this.textarea.getAttribute("maxlength")??-1)}isDisabled(){return this.textarea.disabled}}function s(t){return(t.match(/\n/g)??[]).length}function a(t){return t.split(/\n/)}class o{instances=[];init(t){if(void 0!==this.instances[t])throw new Error(`Textarea with input-id '${t}' has already been initialized.`);this.instances[t]=new i(t)}get(t){return this.instances[t]??null}}class r{preview_parameter;preview_url;constructor(t,e){this.preview_parameter=t,this.preview_url=e}async getPreviewHtmlOf(t){if(0===t.length)return"";let e=new FormData;return e.append(this.preview_parameter,t),(await fetch(this.preview_url,{method:"POST",body:e})).text()}}const l="textarea",d="preview";class c extends i{preview_history=[];preview_renderer;content_wrappers;view_controls;actions;constructor(t,e){super(e);const n=this.textarea.closest(".c-field-markdown");if(null===n)throw new Error(`Could not find input-wrapper for input-id '${e}'.`);this.preview_renderer=t,this.content_wrappers=function(t){const e=new Map;return e.set(l,t.querySelector("textarea")),e.set(d,t.querySelector(".c-field-markdown__preview")),e.forEach((t=>{if(null===t)throw new Error("Could not find all content-wrappers for markdown-input.")})),e}(n),this.view_controls=function(t){const e=t.querySelector(".il-viewcontrol-mode")?.getElementsByTagName("button");if(!e instanceof HTMLCollection||2!==e.length)throw new Error("Could not find exactly two view-controls.");return[...e]}(n),this.actions=function(t){const e=t.querySelector(".c-field-markdown__actions")?.getElementsByTagName("button");if(e instanceof HTMLCollection)return[...e];return[]}(n);let i=!0;this.textarea.addEventListener("keydown",(t=>{i=this.handleEnterKeyBeforeInsertionHook(t)})),this.textarea.addEventListener("keyup",(t=>{this.handleEnterKeyAfterInsertionHook(t,i)})),this.actions.forEach((t=>{t.addEventListener("click",(t=>{this.performMarkdownActionHook(t)}))})),this.view_controls.forEach((t=>{t.addEventListener("click",(()=>{this.toggleViewingModeHook()}))}))}handleEnterKeyAfterInsertionHook(t,e){if(!e||!p(t))return;const n=this.getLinesBeforeSelection().pop();void 0!==n&&m(n)?this.applyTransformationToSelection(u):void 0!==n&&v(n)&&this.insertSingleEnumeration()}handleEnterKeyBeforeInsertionHook(t){if(!p(t))return!1;const e=this.getLinesOfSelection().shift();if(void 0===e||!((e.match(/((^(\s*-)|(^(\s*\d+\.)))\s*)$/g)??[]).length>0))return!0;let n=this.getLinesBeforeSelection().join("\n"),i=this.getLinesAfterSelection().join("\n");return n.length>0&&(n+="\n"),i.length>0&&(i=`\n${i}`),this.updateTextareaContent(n+i,this.getAbsoluteSelectionStart()-e.length,this.getAbsoluteSelectionEnd()-e.length),t.preventDefault(),!1}performMarkdownActionHook(t){const e=function(t){const e=t.closest("span[data-action]");if(!e instanceof HTMLSpanElement)return null;if(!e.hasAttribute("data-action"))return null;return e.dataset.action}(t.target);switch(e){case"insert-heading":this.insertCharactersAroundSelection("# ","");break;case"insert-link":this.insertCharactersAroundSelection("[","](url)");break;case"insert-bold":this.insertCharactersAroundSelection("**","**");break;case"insert-italic":this.insertCharactersAroundSelection("_","_");break;case"insert-bullet-points":this.applyTransformationToSelection(u);break;case"insert-enumeration":this.isMultilineTextSelected()?this.applyTransformationToSelection(h):this.insertSingleEnumeration();break;default:throw new Error(`Could not perform markdown-action '${e}'.`)}}toggleViewingModeHook(){this.content_wrappers.forEach((t=>{g(t,"hidden")})),this.view_controls.forEach((t=>{g(t,"engaged")})),this.isDisabled()||this.actions.forEach((t=>{t.disabled=!t.disabled;const e=t.querySelector(".glyph");null!==e&&g(e,"disabled")})),this.maybeUpdatePreviewContent()}insertSingleEnumeration(){const t=this.getLinesOfSelection();if(1!==t.length)return void this.textarea.focus();const e=this.getLinesBeforeSelection(),n=e.length-1;let i=n>=0?function(t){const e=t.match(/([0-9]+)/);if(null!==e)return parseInt(e[0]);return null}(e[n])??0:0;const s=h(t,++i),a=function(t,e=0){if(t.length<1)return[];const n=[];for(const i of t){if(!v(i))break;n.push(i.replace(/([0-9]+)/,(++e).toString()))}n.length>0&&(t=n.concat(t.slice(n.length)));return t}(this.getLinesAfterSelection(),i);let o=e.join("\n");const r=a.join("\n");let l=s.join("\n");o.length>0&&l.length>0&&(o+="\n"),l.length>0&&r.length>0&&(l+="\n");const d=o+l+r,c=d.length-this.textarea.value.length;this.updateTextareaContent(d,this.getAbsoluteSelectionStart()+c,this.getAbsoluteSelectionEnd()+c)}applyTransformationToSelection(t){if(!t instanceof Function)throw new Error(`Transformation must be an instance of Function, ${typeof t} given.`);const e=t(this.getLinesOfSelection());if(!e instanceof Array)throw new Error(`Transformation must return an instance of Array, ${typeof e} returned.`);const n=e.length>1;let i=this.getLinesBeforeSelection().join("\n");const s=this.getLinesAfterSelection().join("\n");let a=e.join("\n");i.length>0&&a.length>0&&(i+="\n"),a.length>0&&s.length>0&&(a+="\n");const o=i+a+s,r=o.length-this.textarea.value.length,l=n?i.length:this.getAbsoluteSelectionStart()+r,d=n?l+a.length-1:this.getAbsoluteSelectionEnd()+r;this.updateTextareaContent(o,l,d)}maybeUpdatePreviewContent(){const t=this.preview_history[this.preview_history.length-1]??"",e=this.textarea.value;e!==t&&(this.preview_history.push(e),this.preview_renderer.getPreviewHtmlOf(e).then((t=>{this.content_wrappers.get(d).innerHTML=t})))}getBulletPointTransformation(){return u}getEnumerationTransformation(){return h}}function u(t){const e=[],n=!m(t[0]??"");for(const i of t)e.push(n?`- ${i}`:f(i));return e}function h(t,e=1){const n=[],i=!v(t[0]??"");for(const s of t)n.push(i?`${e++}. ${s}`:f(s));return n}function g(t,e){t.classList.contains(e)?t.classList.remove(e):t.classList.add(e)}function p(t){return t instanceof KeyboardEvent&&"Enter"===t.code}function f(t){return t.replace(/((^(\s*[-])|(^(\s*\d+\.)))\s*)/g,"")}function m(t){return(t.match(/^(\s*[-])/g)??[]).length>0}function v(t){return(t.match(/^(\s*\d+\.)/g)??[]).length>0}class w{instances=[];init(t,e,n){if(void 0!==this.instances[t])throw new Error(`Markdown with input-id '${t}' has already been initialized.`);this.instances[t]=new c(new r(n,e),t)}get(t){return this.instances[t]??null}}class y{constructor(t,e,n,i,s,a=null,o=null,r=null){this.id=t,this.name=e,this.element=n,this.selectButton=i,this.drilldownParentLevel=s,this.drilldownButton=a,this.listElement=o,this.renderUrl=r}}const b="data-node-id",T="data-node-name",S="data-render-url",x="data-ddindex",E="c-input-node",O="c-input-tree_select",D=`${E}__async`,I=`${E}__leaf`,C=`${E}--selected`,A="hidden",M="disabled",N=".glyph",_=`.${E}`,L=`.${O}`,k=`.${O}__selection`,P='[data-action="remove"]',B='[data-action="select"]',j=`.${E}__select`,V=".c-drilldown__menulevel--trigger";function F(t){return function(t){return t.classList.contains(D)}(t)&&t.hasAttribute(S)?t.getAttribute(S):null}function H(t){return!t.classList.contains(I)&&t.classList.contains(E)}function q(t,e=null){return t.reduce(((t,e)=>{const n=function(t){const e=t.getAttribute(b);if(null===e)throw new Error("Could not find data-node-id attribute.");return e}(e);if(t.has(n))throw new Error(`Node '${n}' has already been parsed. There might be a rendering issue.`);return t.set(n,new y(n,function(t){const e=t.querySelector(`[${T}]`);if(null===e)throw new Error("Could not find element with data-node-name attribute.");return e.textContent}(e),e,function(t){const e=t.querySelector(`:scope > ${j}`);if(null===e)throw new Error("Could not find node select button.");return e}(e),function(t){const e=t.closest(`ul[${x}]`);if(null===e)throw new Error("Could not find drilldown menu of node.");return e.getAttribute(x)}(e),function(t){if(!H(t))return null;const e=t.querySelector(`${V}`);if(null===e)throw new Error("Could not find drilldown menu button of branch node.");return e}(e),function(t){if(!H(t))return null;const e=t.querySelector("ul");if(null===e)throw new Error("Could not find list element of branch node.");return e}(e),F(e)))}),new Map(e??[]))}function R(t,e){for(let n=0;nn.shift()??""))}function U(t,e){t.classList.toggle(C,e)}class W{#t;#e=new Set;#n=new Set;#i=new Set;#s;#a;#o;#r;#l;#d;#c;#u;#h;#g;#p;#f;constructor(t,e,n,i,s,a,o,r,l,d,c,u,h,g){this.#t=t,this.#s=n,this.#a=i,this.#o=s,this.#r=a,this.#l=o,this.#d=r,this.#c=l,this.#u=d,this.#h=c,this.#g=u,this.#p=h,this.#f=g,e.on(this.#p.ownerDocument,this.#r.getBackSignal(),(()=>{this.#m()})),this.#p.querySelectorAll('[data-action="close"]').forEach((t=>{t.addEventListener("click",(()=>{this.#v()}))})),this.#r.addEngageListener((t=>{this.#w(t)})),this.#g.addEventListener("click",(()=>{this.#y()})),this.#t.forEach((t=>{this.#b(t)})),this.#c.querySelectorAll("li").forEach((t=>{const e=function(t){const e=t.getAttribute(b);if(null===e)throw new Error(`Could not find '${b}' attribbute of element.`);return e}(t);this.#T(t,e),this.selectNode(e)})),this.#w(this.#r.getCurrentLevel()),this.#S()}unselectNode(t){if(this.#x(t),this.#S(),this.#E(t),this.#t.has(t)){const e=this.#t.get(t);U(e.element,!1),this.#O(e.selectButton,e.name)}this.#f(this)}selectNode(t){if(this.#D(t),this.#S(),this.#t.has(t)){const e=this.#t.get(t);U(e.element,!0),this.#I(e.selectButton,e.name),this.#C(e)}this.#f(this)}engageNode(t){if(!this.#t.has(t))return;const e=this.#t.get(t).drilldownParentLevel;this.#r.getCurrentLevel()!==e&&this.#r.getParentLevel()!==e&&this.#r.engageLevel(e)}getSelection(){return new Set(this.#e)}getNodes(){return new Map(this.#t)}async#A(t){var e,n,i;if(!this.#n.has(t.id)&&!this.#i.has(t.id))try{this.#i.add(t.id);const s=await this.#a.loadContent(t.renderUrl);t.listElement.append(...s.children),this.#r.parseLevels();const a=q((i=t.listElement,Array.from(i.querySelectorAll(_))),this.#t),o=(e=a,n=this.#t,Array.from(e.entries()).filter((([t])=>!n.has(t))).map((([,t])=>t)));this.#t=a,R(o,(t=>{this.#e.has(t.id)?this.selectNode(t.id):this.unselectNode(t.id),this.#b(t)})),this.#n.add(t.id)}catch(t){throw new Error(`Could not render async node children: ${t.message}`)}finally{this.#i.delete(t.id)}}#M(t){R(function(t,e,n=255){const i=[];let s=t;for(let t=0;t{const e=t.getAttribute(b);if(null===e||!this.#t.has(e))throw new Error(`Could not find '${b}' of node element.`);const n=this.#t.get(e);this.#N(n)}))}#N(t){const e=this.#s.createContent(this.#d).querySelector(".crumb");e.setAttribute(x,t.drilldownParentLevel),e.firstElementChild.textContent=t.name,e.addEventListener("click",(()=>{this.#r.engageLevel(t.drilldownParentLevel),t.drilldownButton.click()})),this.#l.append(e)}#m(){const t=this.#l.querySelectorAll(".crumb");t.item(t.length-1)?.remove()}#_(){R(this.#l.querySelectorAll(".crumb"),(t=>{t.remove()}))}#w(t){if("0"===t)return void this.#_();const e=this.#p.querySelector(`ul[${x}="${t}"]`)?.closest(_)?.getAttribute(b);if(null===e||!this.#t.has(e))throw new Error(`Could not find node for drilldown-level '${t}'.`);const n=this.#t.get(e);this.#_(),this.#M(n),null!==n.renderUrl&&this.#A(n)}#T(t,e){t.querySelector(P)?.addEventListener("click",(()=>{this.unselectNode(e),t.remove()}))}#L(t,e){t.addEventListener("click",(()=>{this.#e.has(e.id)?this.unselectNode(e.id):this.selectNode(e.id)}))}#C(t){if(null!==this.#c.querySelector(`li[${b}="${t.id}"]`))return;const e=this.#s.createContent(this.#u),n=e.querySelector("[data-node-id]");n.setAttribute(b,t.id),n.querySelector(`[${T}]`).textContent=t.name,n.querySelector("input").value=t.id,this.#T(n,t.id),this.#c.append(...e.children)}#E(t){this.#c.querySelector(`li[${b}="${t}"]`)?.remove()}#b(t){this.#L(t.selectButton,t)}#O(t,e){t.querySelector(P)?.classList.add(A),t.querySelector(B)?.classList.remove(A),t.setAttribute("aria-label",this.#k("select_node",e))}#I(t,e){t.querySelector(B)?.classList.add(A),t.querySelector(P)?.classList.remove(A),t.setAttribute("aria-label",this.#k("unselect_node",e))}#S(){this.#h.disabled=this.#e.size<=0}#x(t){this.#e.has(t)&&this.#e.delete(t)}#D(t){this.#e.has(t)||this.#e.add(t)}#k(t,...e){return $(this.#o.txt(t),e)}#v(){this.#p.close()}#y(){this.#p.showModal()}}function z(t,e){const n=t.createDocumentFragment();return n.append(...e),n}function K(t,e,n){t.querySelectorAll(`[${n}]`).forEach((t=>{const i=t.getAttribute(n);if(!e.has(i))throw new Error(`Element references '${i}' which does not exist.`);t.setAttribute(n,e.get(i))}))}class X{#P;constructor(t){this.#P=t}createContent(t){const e=t.content.cloneNode(!0),n=new Map;return e.querySelectorAll("[id]").forEach((t=>{const e=function(t=""){return`${t}${Date.now().toString(36)}_${Math.random().toString(36).substring(2)}`}("il_ui_fw_");n.set(t.id,e),t.id=e})),e.querySelectorAll("[for]").forEach((t=>{t.htmlFor=n.get(t.htmlFor)})),K(e,n,"aria-describedby"),K(e,n,"aria-labelledby"),K(e,n,"aria-controls"),K(e,n,"aria-owns"),z(this.#P,e.children)}}class G{#P;constructor(t){this.#P=t}loadContent(t){return fetch(t.toString()).then((t=>t.text())).then((t=>this.#B(t))).then((t=>z(this.#P,t))).catch((e=>{throw new Error(`Could not render element(s) from '${t}': ${e.message}`)}))}#j(t){const e=this.#P.createElement("script");return t.hasAttribute("type")&&e.setAttribute("type",t.getAttribute("type")),t.hasAttribute("src")&&e.setAttribute("src",t.getAttribute("src")),t.textContent.length>0&&(e.textContent=t.textContent),e}#B(t){const e=this.#P.createElement("div");return e.innerHTML=t.trim(),e.querySelectorAll("script").forEach((t=>{const e=this.#j(t);t.replaceWith(e)})),e.children}}function J(t){return Array.from(t.querySelectorAll(_))}function Q(){return t=>{!function(t){const e=t.getNodes(),n=t.getSelection();e.forEach(((t,e)=>{n.size>0?(t.selectButton.disabled=!n.has(e),t.selectButton.querySelector(N).classList.toggle(M,!n.has(e))):(t.selectButton.disabled=!1,t.selectButton.querySelector(N).classList.toggle(M,!1))}))}(t),function(t){const e=t.getSelection();if(1===e.size){const n=e.values()?.next()?.value;t.getNodes().has(n)&&t.engageNode(n)}}(t)}}function Y(t){return t?()=>{}:t=>{!function(t){const e=Array.from(t.getSelection()),n=t.getNodes();for(let s=0;s{t.selectButton.disabled=!1,t.selectButton.querySelector(N).classList.remove(M)})),n.forEach((t=>{const n=e.get(t);null!==n&&null!==n.listElement&&n.listElement.querySelectorAll(j).forEach((t=>{t.disabled=!0,t.querySelector(N).classList.add(M)}))}))}(t)}}class Z{#V=new Map;#F;#H;#o;#P;constructor(t,e,n,i){this.#F=t,this.#H=e,this.#o=n,this.#P=i}initTreeMultiSelect(t,e){if(this.#V.has(t))throw new Error(`TreeSelect '${t}' already exists.`);const[n,i,s,a,o,r,l,d]=this.#q(t),c=this.#R(i),u=new W(q(J(l)),this.#F,new X(this.#P),new G(this.#P),this.#o,c,s,a,o,r,d,n,l,Y(e));return this.#V.set(t,u),u}initTreeSelect(t){if(this.#V.has(t))throw new Error(`TreeSelect '${t}' already exists.`);const[e,n,i,s,a,o,r,l]=this.#q(t),d=this.#R(n),c=new W(q(J(r)),this.#F,new X(this.#P),new G(this.#P),this.#o,d,i,s,a,o,l,e,r,Q());return this.#V.set(t,c),c}getInstance(t){return this.#V.has(t)?this.#V.get(t):null}#q(t){const e=this.#P.getElementById(t),n=e?.closest(L),i=n?.querySelector(".breadcrumb"),s=n?.querySelector(".modal-body > template"),a=n?.querySelector(k),o=a?.querySelector(":scope > template"),r=n?.querySelector("dialog"),l=r?.querySelector(".btn-primary");if(null===i||null===s||null===a||null===o||null===l||null===e||null===r)throw new Error(`Could not find some element(s) for Tree Select Input '${t}'.`);return[e,n,i,s,a,o,r,l]}#R(t){const e=t.querySelector(".c-drilldown");if(null===e||!e.hasAttribute("id"))throw new Error("Could not find drilldown element.");const n=this.#H.getInstance(e.id);if(null===e)throw new Error("Could not find drilldown instance.");return n}}class tt{#$;constructor(t){this.#$=t}on(t,e,n){this.#$(t).on(e,n)}off(t,e,n){this.#$(t).off(e,n)}} /* Tagify v4.33.2 - tags input component By: Yair Even-Or @@ -38,4 +38,4 @@ This Software may not be rebranded and sold as a library under any other name other than "Tagify" (by owner) or as part of another library. - */var et="​";function nt(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,i=new Array(e);n/g,">").replace(/"/g,""").replace(/`|'/g,"'"):t}function ut(t){var e=Object.prototype.toString.call(t).split(" ")[1].slice(0,-1);return t===Object(t)&&"Array"!=e&&"Function"!=e&&"RegExp"!=e&&"HTMLUnknownElement"!=e}function ht(t,e,n){var i,s;function a(t,e){for(var n in e)if(e.hasOwnProperty(n)){if(ut(e[n])){ut(t[n])?a(t[n],e[n]):t[n]=Object.assign({},e[n]);continue}if(Array.isArray(e[n])){t[n]=Object.assign([],e[n]);continue}t[n]=e[n]}}return i=t,(null!=(s=Object)&&"undefined"!=typeof Symbol&&s[Symbol.hasInstance]?s[Symbol.hasInstance](i):i instanceof s)||(t={}),a(t,e),n&&a(t,n),t}function gt(){var t=[],e={},n=!0,i=!1,s=void 0;try{for(var a,o=arguments[Symbol.iterator]();!(n=(a=o.next()).done);n=!0){var r=a.value,l=!0,d=!1,c=void 0;try{for(var u,h=r[Symbol.iterator]();!(l=(u=h.next()).done);l=!0){var g=u.value;ut(g)?e[g.value]||(t.push(g),e[g.value]=1):t.includes(g)||t.push(g)}}catch(t){d=!0,c=t}finally{try{l||null==h.return||h.return()}finally{if(d)throw c}}}}catch(t){i=!0,s=t}finally{try{n||null==o.return||o.return()}finally{if(i)throw s}}return t}function pt(t){return String.prototype.normalize?"string"==typeof t?t.normalize("NFD").replace(/[\u0300-\u036f]/g,""):void 0:t}var ft=function(){return/(?=.*chrome)(?=.*android)/i.test(navigator.userAgent)};function mt(){return([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,(function(t){return(t^crypto.getRandomValues(new Uint8Array(1))[0]&15>>t/4).toString(16)}))}function vt(t){var e;return yt.call(this,t)&&(null==t||null===(e=t.classList)||void 0===e?void 0:e.contains(this.settings.classNames.tag))}function wt(t){return yt.call(this,t)&&(null==t?void 0:t.closest(this.settings.classNames.tagSelector))}function yt(t){var e;return(null==t||null===(e=t.closest)||void 0===e?void 0:e.call(t,this.settings.classNames.namespaceSelector))===this.DOM.scope}function bt(t,e){var n=window.getSelection();return e=e||n.getRangeAt(0),"string"==typeof t&&(t=document.createTextNode(t)),e&&(e.deleteContents(),e.insertNode(t)),t}function Tt(t,e,n){return t?(e&&(t.__tagifyTagData=n?e:ht({},t.__tagifyTagData||{},e)),t.__tagifyTagData):(st.warn("tag element doesn't exist",{tagElm:t,data:e}),e)}function St(t){if(t&&t.parentNode){var e=t,n=window.getSelection(),i=n.getRangeAt(0);n.rangeCount&&(i.setStartAfter(e),i.collapse(!0),n.removeAllRanges(),n.addRange(i))}}function xt(t,e){t.forEach((function(t){if(Tt(t.previousSibling)||!t.previousSibling){var n=document.createTextNode("​");t.before(n),e&&St(n)}}))}var Et={delimiters:",",pattern:null,tagTextProp:"value",maxTags:1/0,callbacks:{},addTagOnBlur:!0,addTagOn:["blur","tab","enter"],onChangeAfterBlur:!0,duplicates:!1,whitelist:[],blacklist:[],enforceWhitelist:!1,userInput:!0,focusable:!0,keepInvalidTags:!1,createInvalidTags:!0,mixTagsAllowedAfter:/,|\.|\:|\s/,mixTagsInterpolator:["[[","]]"],backspace:!0,skipInvalid:!1,pasteAsTags:!0,editTags:{clicks:2,keepInvalid:!0},transformTag:function(){},trim:!0,a11y:{focusableTags:!1},mixMode:{insertAfterTag:" "},autoComplete:{enabled:!0,rightKey:!1,tabKey:!1},classNames:{namespace:"tagify",mixMode:"tagify--mix",selectMode:"tagify--select",input:"tagify__input",focus:"tagify--focus",tagNoAnimation:"tagify--noAnim",tagInvalid:"tagify--invalid",tagNotAllowed:"tagify--notAllowed",scopeLoading:"tagify--loading",hasMaxTags:"tagify--hasMaxTags",hasNoTags:"tagify--noTags",empty:"tagify--empty",inputInvalid:"tagify__input--invalid",dropdown:"tagify__dropdown",dropdownWrapper:"tagify__dropdown__wrapper",dropdownHeader:"tagify__dropdown__header",dropdownFooter:"tagify__dropdown__footer",dropdownItem:"tagify__dropdown__item",dropdownItemActive:"tagify__dropdown__item--active",dropdownItemHidden:"tagify__dropdown__item--hidden",dropdownItemSelected:"tagify__dropdown__item--selected",dropdownInital:"tagify__dropdown--initial",tag:"tagify__tag",tagText:"tagify__tag-text",tagX:"tagify__tag__removeBtn",tagLoading:"tagify__tag--loading",tagEditing:"tagify__tag--editable",tagFlash:"tagify__tag--flash",tagHide:"tagify__tag--hide"},dropdown:{classname:"",enabled:2,maxItems:10,searchKeys:["value","searchBy"],fuzzySearch:!0,caseSensitive:!1,accentedSearch:!0,includeSelectedTags:!1,escapeHTML:!0,highlightFirst:!0,closeOnSelect:!0,clearOnSelect:!0,position:"all",appendTarget:null},hooks:{beforeRemoveTag:function(){return Promise.resolve()},beforePaste:function(){return Promise.resolve()},suggestionClick:function(){return Promise.resolve()},beforeKeyDown:function(){return Promise.resolve()}}};function Ot(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function Dt(t){for(var e=1;et.length)&&(e=t.length);for(var n=0,i=new Array(e);n0&&void 0!==arguments[0])||arguments[0],e=this.dropdown.events.callbacks,n=this.listeners.dropdown=this.listeners.dropdown||{position:this.dropdown.position.bind(this,null),onKeyDown:e.onKeyDown.bind(this),onMouseOver:e.onMouseOver.bind(this),onMouseLeave:e.onMouseLeave.bind(this),onClick:e.onClick.bind(this),onScroll:e.onScroll.bind(this)},i=t?"addEventListener":"removeEventListener";"manual"!=this.settings.dropdown.position&&(document[i]("scroll",n.position,!0),window[i]("resize",n.position),window[i]("keydown",n.onKeyDown)),this.DOM.dropdown[i]("mouseover",n.onMouseOver),this.DOM.dropdown[i]("mouseleave",n.onMouseLeave),this.DOM.dropdown[i]("mousedown",n.onClick),this.DOM.dropdown.content[i]("scroll",n.onScroll)},callbacks:{onKeyDown:function(t){var e=this;if(this.state.hasFocus&&!this.state.composing){var n=this.settings,i=n.dropdown.includeSelectedTags,s=this.DOM.dropdown.querySelector(n.classNames.dropdownItemActiveSelector),a=this.dropdown.getSuggestionDataByNode(s),o="mix"==n.mode,r="select"==n.mode;n.hooks.beforeKeyDown(t,{tagify:this}).then((function(l){switch(t.key){case"ArrowDown":case"ArrowUp":case"Down":case"Up":t.preventDefault();var d=e.dropdown.getAllSuggestionsRefs(),c="ArrowUp"==t.key||"Up"==t.key;s&&(s=e.dropdown.getNextOrPrevOption(s,!c)),s&&s.matches(n.classNames.dropdownItemSelector)||(s=d[c?d.length-1:0]),e.dropdown.highlightOption(s,!0);break;case"PageUp":case"PageDown":var u;t.preventDefault();var h=e.dropdown.getAllSuggestionsRefs(),g=Math.floor(e.DOM.dropdown.content.clientHeight/(null===(u=h[0])||void 0===u?void 0:u.offsetHeight))||1,p="PageUp"===t.key;if(s){var f=h.indexOf(s),m=p?Math.max(0,f-g):Math.min(h.length-1,f+g);s=h[m]}else s=h[0];e.dropdown.highlightOption(s,!0);break;case"Home":case"End":t.preventDefault();var v=e.dropdown.getAllSuggestionsRefs();s=v["Home"===t.key?0:v.length-1],e.dropdown.highlightOption(s,!0);break;case"Escape":case"Esc":e.dropdown.hide();break;case"ArrowRight":if(e.state.actions.ArrowLeft||n.autoComplete.rightKey)return;case"Tab":var w=!n.autoComplete.rightKey||!n.autoComplete.tabKey;if(!o&&!r&&s&&w&&!e.state.editing&&a){t.preventDefault();var y=e.dropdown.getMappedValue(a);return e.state.autoCompleteData=a,e.input.autocomplete.set.call(e,y),!1}return!0;case"Enter":t.preventDefault(),e.state.actions.selectOption=!0,setTimeout((function(){return e.state.actions.selectOption=!1}),100),n.hooks.suggestionClick(t,{tagify:e,tagData:a,suggestionElm:s}).then((function(){if(s){var n=i?s:e.dropdown.getNextOrPrevOption(s,!c);e.dropdown.selectOption(s,t,(function(){if(n){var t=n.getAttribute("value");n=e.dropdown.getSuggestionNodeByValue(t),e.dropdown.highlightOption(n)}}))}else e.dropdown.hide(),o||e.addTags(e.state.inputText.trim(),!0)})).catch((function(t){return st.warn(t)}));break;case"Backspace":if(o||e.state.editing.scope)return;var b=e.input.raw.call(e);""!=b&&8203!=b.charCodeAt(0)||(!0===n.backspace?e.removeTags():"edit"==n.backspace&&setTimeout(e.editTag.bind(e),0))}}))}},onMouseOver:function(t){var e=t.target.closest(this.settings.classNames.dropdownItemSelector);this.dropdown.highlightOption(e)},onMouseLeave:function(t){this.dropdown.highlightOption()},onClick:function(t){var e=this;if(0==t.button&&t.target!=this.DOM.dropdown&&t.target!=this.DOM.dropdown.content){var n=t.target.closest(this.settings.classNames.dropdownItemSelector),i=this.dropdown.getSuggestionDataByNode(n);this.state.actions.selectOption=!0,setTimeout((function(){return e.state.actions.selectOption=!1}),100),this.settings.hooks.suggestionClick(t,{tagify:this,tagData:i,suggestionElm:n}).then((function(){n?e.dropdown.selectOption(n,t):e.dropdown.hide()})).catch((function(t){return st.warn(t)}))}},onScroll:function(t){var e=t.target,n=e.scrollTop/(e.scrollHeight-e.parentNode.clientHeight)*100;this.trigger("dropdown:scroll",{percentage:Math.round(n)})}}},refilter:function(t){t=t||this.state.dropdown.query||"",this.suggestedListItems=this.dropdown.filterListItems(t),this.dropdown.fill(),this.suggestedListItems.length||this.dropdown.hide(),this.trigger("dropdown:updated",this.DOM.dropdown)},getSuggestionDataByNode:function(t){for(var e,n=t&&t.getAttribute("value"),i=this.suggestedListItems.length;i--;){if(ut(e=this.suggestedListItems[i])&&e.value==n)return e;if(e==n)return{value:e}}},getSuggestionNodeByValue:function(t){return this.dropdown.getAllSuggestionsRefs().find((function(e){return e.getAttribute("value")===t}))},getNextOrPrevOption:function(t){var e=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],n=this.dropdown.getAllSuggestionsRefs(),i=n.findIndex((function(e){return e===t}));return e?n[i+1]:n[i-1]},highlightOption:function(t,e){var n,i=this.settings.classNames.dropdownItemActive;if(this.state.ddItemElm&&(this.state.ddItemElm.classList.remove(i),this.state.ddItemElm.removeAttribute("aria-selected")),!t)return this.state.ddItemData=null,this.state.ddItemElm=null,void this.input.autocomplete.suggest.call(this);n=this.dropdown.getSuggestionDataByNode(t),this.state.ddItemData=n,this.state.ddItemElm=t,t.classList.add(i),t.setAttribute("aria-selected",!0),e&&(t.parentNode.scrollTop=t.clientHeight+t.offsetTop-t.parentNode.clientHeight),this.settings.autoComplete&&(this.input.autocomplete.suggest.call(this,n),this.dropdown.position())},selectOption:function(t,e,n){var i=this,s=this.settings,a=s.dropdown.includeSelectedTags,o=s.dropdown,r=o.clearOnSelect,l=o.closeOnSelect;if(!t)return this.addTags(this.state.inputText,!0),void(l&&this.dropdown.hide());e=e||{};var d=t.getAttribute("value"),c="noMatch"==d,u="mix"==s.mode,h=this.suggestedListItems.find((function(t){var e;return(null!==(e=t.value)&&void 0!==e?e:t)==d}));if(this.trigger("dropdown:select",{data:h,elm:t,event:e}),h||c){if(this.state.editing){var g=this.normalizeTags([h])[0];h=s.transformTag.call(this,g)||g,this.onEditTagDone(null,ht({__isValid:!0},h))}else this[u?"addMixTags":"addTags"]([h||this.input.raw.call(this)],r);(u||this.DOM.input.parentNode)&&(setTimeout((function(){i.DOM.input.focus(),i.toggleFocusClass(!0)})),l&&setTimeout(this.dropdown.hide.bind(this)),a?n&&n():(t.addEventListener("transitionend",(function(){i.dropdown.fillHeaderFooter(),setTimeout((function(){t.remove(),i.dropdown.refilter(),n&&n()}),100)}),{once:!0}),t.classList.add(this.settings.classNames.dropdownItemHidden)))}else l&&setTimeout(this.dropdown.hide.bind(this))},selectAll:function(t){this.suggestedListItems.length=0,this.dropdown.hide(),this.dropdown.filterListItems("");var e=this.dropdown.filterListItems("");return t||(e=this.state.dropdown.suggestions),this.addTags(e,!0),this},filterListItems:function(t,e){var n,i,s,a,o,r,l=function(){var t,l,d=void 0,c=void 0;t=p[y],i=(null!=(l=Object)&&"undefined"!=typeof Symbol&&l[Symbol.hasInstance]?l[Symbol.hasInstance](t):t instanceof l)?p[y]:{value:p[y]};var f,m=Object.keys(i).some((function(t){return w.includes(t)}))?w:["value"];u.fuzzySearch&&!e.exact?(a=m.reduce((function(t,e){return t+" "+(i[e]||"")}),"").toLowerCase().trim(),u.accentedSearch&&(a=pt(a),r=pt(r)),d=0==a.indexOf(r),c=a===r,f=a,s=r.toLowerCase().split(" ").every((function(t){return f.includes(t.toLowerCase())}))):(d=!0,s=m.some((function(t){var n=""+(i[t]||"");return u.accentedSearch&&(n=pt(n),r=pt(r)),u.caseSensitive||(n=n.toLowerCase()),c=n===r,e.exact?n===r:0==n.indexOf(r)}))),o=!u.includeSelectedTags&&n.isTagDuplicate(ut(i)?i.value:i),s&&!o&&(c&&d?g.push(i):"startsWith"==u.sortby&&d?h.unshift(i):h.push(i))},d=this,c=this.settings,u=c.dropdown,h=(e=e||{},[]),g=[],p=c.whitelist,f=u.maxItems>=0?u.maxItems:1/0,m=u.includeSelectedTags,v="function"==typeof u.sortby,w=u.searchKeys,y=0;if(!(t="select"==c.mode&&this.value.length&&this.value[0][c.tagTextProp]==t?"":t)||!w.length){h=m?p:p.filter((function(t){return!d.isTagDuplicate(ut(t)?t.value:t)}));var b=v?u.sortby(h,r):h.slice(0,f);return this.state.dropdown.suggestions=b,b}for(r=u.caseSensitive?""+t:(""+t).toLowerCase();y[\r\n ]+\<").split(/>\s+<").trim():""},fillHeaderFooter:function(){var t=this.dropdown.filterListItems(this.state.dropdown.query),e=this.parseTemplate("dropdownHeader",[t]),n=this.parseTemplate("dropdownFooter",[t]),i=this.dropdown.getHeaderRef(),s=this.dropdown.getFooterRef();e&&(null==i||i.parentNode.replaceChild(e,i)),n&&(null==s||s.parentNode.replaceChild(n,s))},position:function(t){var e=this.settings.dropdown,n=this.dropdown.getAppendTarget();if("manual"!=e.position&&n){var i,s,a,o,r,l,d,c,u,h,g=this.DOM.dropdown,p=e.RTL,f=n===document.body,m=n===this.DOM.scope,v=f?window.pageYOffset:n.scrollTop,w=document.fullscreenElement||document.webkitFullscreenElement||document.documentElement,y=w.clientHeight,b=Math.max(w.clientWidth||0,window.innerWidth||0),T=b>480?e.position:"all",S=this.DOM["input"==T?"input":"scope"];if(t=t||g.clientHeight,this.state.dropdown.visible){if("text"==T?(a=(i=function(){var t=document.getSelection();if(t.rangeCount){var e,n,i=t.getRangeAt(0),s=i.startContainer,a=i.startOffset;if(a>0)return(n=document.createRange()).setStart(s,a-1),n.setEnd(s,a),{left:(e=n.getBoundingClientRect()).right,top:e.top,bottom:e.bottom};if(s.getBoundingClientRect)return s.getBoundingClientRect()}return{left:-9999,top:-9999}}()).bottom,s=i.top,o=i.left,r="auto"):(l=function(t){var e=0,n=0;for(t=t.parentNode;t&&t!=w;)e+=t.offsetTop||0,n+=t.offsetLeft||0,t=t.parentNode;return{top:e,left:n}}(n),i=S.getBoundingClientRect(),s=m?-1:i.top-l.top,a=(m?i.height:i.bottom-l.top)-1,o=m?-1:i.left-l.left,r=i.width+"px"),!f){var x=function(){for(var t=0,n=e.appendTarget.parentNode;n;)t+=n.scrollTop||0,n=n.parentNode;return t}();s+=x,a+=x}var E;s=Math.floor(s),a=Math.ceil(a),c=b-o<120,u=((d=null!==(E=e.placeAbove)&&void 0!==E?E:y-i.bottom\n ').concat(this.settings.templates.input.call(this),"\n ").concat(et,"\n ")},input:function(){var t=this.settings,e=t.placeholder||et;return"')},tag:function(t,e){var n=e.settings;return'\n \n
\n ').concat(t[n.tagTextProp]||t.value,"\n
\n
")},dropdown:function(t){var e=t.dropdown,n="manual"==e.position;return'
\n
\n
')},dropdownContent:function(t){var e=this.settings.templates,n=this.state.dropdown.suggestions;return"\n ".concat(e.dropdownHeader.call(this,n),"\n ").concat(t,"\n ").concat(e.dropdownFooter.call(this,n),"\n ")},dropdownItem:function(t){return"
').concat(t.mappedValue||t.value,"
")},dropdownHeader:function(t){return"
')},dropdownFooter:function(t){var e=t.length-this.settings.dropdown.maxItems;return e>0?"
\n ').concat(e," more items. Refine your search.\n
"):""},dropdownItemNoMatch:null};function jt(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,i=new Array(e);nt.length)&&(e=t.length);for(var n=0,i=new Array(e);n0&&void 0!==arguments[0])||arguments[0],n=this.settings,i=this.events.callbacks,s=e?"addEventListener":"removeEventListener";if(!this.state.mainEvents||!e){for(var a in this.state.mainEvents=e,e&&!this.listeners.main&&(this.events.bindGlobal.call(this),this.settings.isJQueryPlugin&&jQuery(this.DOM.originalInput).on("tagify.removeAllTags",this.removeAllTags.bind(this))),t=this.listeners.main=this.listeners.main||{keydown:["input",i.onKeydown.bind(this)],click:["scope",i.onClickScope.bind(this)],dblclick:"select"!=n.mode&&["scope",i.onDoubleClickScope.bind(this)],paste:["input",i.onPaste.bind(this)],drop:["input",i.onDrop.bind(this)],compositionstart:["input",i.onCompositionStart.bind(this)],compositionend:["input",i.onCompositionEnd.bind(this)]})t[a]&&this.DOM[t[a][0]][s](a,t[a][1]);var o=this.listeners.main.inputMutationObserver||new MutationObserver(i.onInputDOMChange.bind(this));o.disconnect(),"mix"==n.mode&&o.observe(this.DOM.input,{childList:!0}),this.events.bindOriginaInputListener.call(this)}},bindOriginaInputListener:function(t){var e=(t||0)+500;this.listeners.main&&(clearInterval(this.listeners.main.originalInputValueObserverInterval),this.listeners.main.originalInputValueObserverInterval=setInterval(this.events.callbacks.observeOriginalInputValue.bind(this),e))},bindGlobal:function(t){var e,n=this.events.callbacks,i=t?"removeEventListener":"addEventListener";if(this.listeners&&(t||!this.listeners.global)){this.listeners.global=this.listeners.global||[{type:this.isIE?"keydown":"input",target:this.DOM.input,cb:n[this.isIE?"onInputIE":"onInput"].bind(this)},{type:"keydown",target:window,cb:n.onWindowKeyDown.bind(this)},{type:"focusin",target:this.DOM.scope,cb:n.onFocusBlur.bind(this)},{type:"focusout",target:this.DOM.scope,cb:n.onFocusBlur.bind(this)},{type:"click",target:document,cb:n.onClickAnywhere.bind(this),useCapture:!0}];var s=!0,a=!1,o=void 0;try{for(var r,l=this.listeners.global[Symbol.iterator]();!(s=(r=l.next()).done);s=!0)(e=r.value).target[i](e.type,e.cb,!!e.useCapture)}catch(t){a=!0,o=t}finally{try{s||null==l.return||l.return()}finally{if(a)throw o}}}},unbindGlobal:function(){this.events.bindGlobal.call(this,!0)},callbacks:{onFocusBlur:function(t){var e,n,i=this.settings,s=wt.call(this,t.relatedTarget),a=vt.call(this,t.relatedTarget),o=t.target.classList.contains(i.classNames.tagX),r="focusin"==t.type,l="focusout"==t.type;o&&"mix"!=i.mode&&this.DOM.input.focus(),s&&r&&!a&&!o&&this.toggleFocusClass(this.state.hasFocus=+new Date);var d=t.target?this.trim(this.DOM.input.textContent):"",c=null===(n=this.value)||void 0===n||null===(e=n[0])||void 0===e?void 0:e[i.tagTextProp],u=i.dropdown.enabled>=0,h={relatedTarget:t.relatedTarget},g=this.state.actions.selectOption&&(u||!i.dropdown.closeOnSelect),p=this.state.actions.addNew&&u;if(l){if(t.relatedTarget===this.DOM.scope)return this.dropdown.hide(),void this.DOM.input.focus();this.postUpdate(),i.onChangeAfterBlur&&this.triggerChangeEvent()}if(!(g||p||o))if(this.state.hasFocus=!(!r&&!s)&&+new Date,this.toggleFocusClass(this.state.hasFocus),"mix"!=i.mode){if(r){if(!i.focusable)return;var f=0===i.dropdown.enabled&&!this.state.dropdown.visible,m=!a||"select"===i.mode,v=this.DOM.scope.querySelector(this.settings.classNames.tagTextSelector);return this.trigger("focus",h),void(f&&m&&(this.dropdown.show(this.value.length?"":void 0),this.setRangeAtStartEnd(!1,v)))}if(l){if(this.trigger("blur",h),this.loading(!1),"select"==i.mode){if(this.value.length){var w=this.getTagElms()[0];d=this.trim(w.textContent)}c===d&&(d="")}d&&!this.state.actions.selectOption&&i.addTagOnBlur&&i.addTagOn.includes("blur")&&this.addTags(d,!0)}s||(this.DOM.input.removeAttribute("style"),this.dropdown.hide())}else r?this.trigger("focus",h):l&&(this.trigger("blur",h),this.loading(!1),this.dropdown.hide(),this.state.dropdown.visible=void 0,this.setStateSelection())},onCompositionStart:function(t){this.state.composing=!0},onCompositionEnd:function(t){this.state.composing=!1},onWindowKeyDown:function(t){var e,n=this.settings,i=document.activeElement,s=wt.call(this,i)&&this.DOM.scope.contains(i),a=i===this.DOM.input,o=s&&i.hasAttribute("readonly"),r=this.DOM.scope.querySelector(this.settings.classNames.tagTextSelector),l=this.state.dropdown.visible;if(("Tab"===t.key&&l||this.state.hasFocus||s&&!o)&&!a){e=i.nextElementSibling;var d=t.target.classList.contains(n.classNames.tagX);switch(t.key){case"Backspace":n.readonly||this.state.editing||(this.removeTags(i),(e||this.DOM.input).focus());break;case"Enter":if(d)return void this.removeTags(t.target.parentNode);n.a11y.focusableTags&&vt.call(this,i)&&setTimeout(this.editTag.bind(this),0,i);break;case"ArrowDown":this.state.dropdown.visible||"mix"==n.mode||this.dropdown.show();break;case"Tab":null==r||r.focus()}}},onKeydown:function(t){var e=this,n=this.settings;if(!this.state.composing&&n.userInput){"select"==n.mode&&n.enforceWhitelist&&this.value.length&&"Tab"!=t.key&&t.preventDefault();var i=this.trim(t.target.textContent);this.trigger("keydown",{event:t}),n.hooks.beforeKeyDown(t,{tagify:this}).then((function(s){if("mix"==n.mode){switch(t.key){case"Left":case"ArrowLeft":e.state.actions.ArrowLeft=!0;break;case"Delete":case"Backspace":if(e.state.editing)return;var a=document.getSelection(),o="Delete"==t.key&&a.anchorOffset==(a.anchorNode.length||0),r=a.anchorNode.previousSibling,l=1==a.anchorNode.nodeType||!a.anchorOffset&&r&&1==r.nodeType&&a.anchorNode.previousSibling;!function(t){var e=document.createElement("div");t.replace(/\&#?[0-9a-z]+;/gi,(function(t){return e.innerHTML=t,e.innerText}))}(e.DOM.input.innerHTML);var d,c,u,h=e.getTagElms(),g=1===a.anchorNode.length&&a.anchorNode.nodeValue==String.fromCharCode(8203);if("edit"==n.backspace&&l)return d=1==a.anchorNode.nodeType?null:a.anchorNode.previousElementSibling,setTimeout(e.editTag.bind(e),0,d),void t.preventDefault();if(ft()&&qt(l,Element))return u=dt(l),l.hasAttribute("readonly")||l.remove(),e.DOM.input.focus(),void setTimeout((function(){St(u),e.DOM.input.click()}));if("BR"==a.anchorNode.nodeName)return;if((o||l)&&1==a.anchorNode.nodeType?c=0==a.anchorOffset?o?h[0]:null:h[Math.min(h.length,a.anchorOffset)-1]:o?c=a.anchorNode.nextElementSibling:qt(l,Element)&&(c=l),3==a.anchorNode.nodeType&&!a.anchorNode.nodeValue&&a.anchorNode.previousElementSibling&&t.preventDefault(),(l||o)&&!n.backspace)return void t.preventDefault();if("Range"!=a.type&&!a.anchorOffset&&a.anchorNode==e.DOM.input&&"Delete"!=t.key)return void t.preventDefault();if("Range"!=a.type&&c&&c.hasAttribute("readonly"))return void St(dt(c));"Delete"==t.key&&g&&Tt(a.anchorNode.nextSibling)&&e.removeTags(a.anchorNode.nextSibling)}return!0}var p="manual"==n.dropdown.position;switch(t.key){case"Backspace":"select"==n.mode&&n.enforceWhitelist&&e.value.length?e.removeTags():e.state.dropdown.visible&&"manual"!=n.dropdown.position||""!=t.target.textContent&&8203!=i.charCodeAt(0)||(!0===n.backspace?e.removeTags():"edit"==n.backspace&&setTimeout(e.editTag.bind(e),0));break;case"Esc":case"Escape":if(e.state.dropdown.visible)return;t.target.blur();break;case"Down":case"ArrowDown":e.state.dropdown.visible||e.dropdown.show();break;case"ArrowRight":var f=e.state.inputSuggestion||e.state.ddItemData;if(f&&n.autoComplete.rightKey)return void e.addTags([f],!0);break;case"Tab":return!0;case"Enter":if(e.state.dropdown.visible&&!p)return;t.preventDefault();var m=e.state.autoCompleteData||i;setTimeout((function(){e.state.dropdown.visible&&!p||e.state.actions.selectOption||!n.addTagOn.includes(t.key.toLowerCase())||(e.addTags([m],!0),e.state.autoCompleteData=null)}))}})).catch((function(t){return t}))}},onInput:function(t){this.postUpdate();var e=this.settings;if("mix"==e.mode)return this.events.callbacks.onMixTagsInput.call(this,t);var n=this.input.normalize.call(this,void 0,{trim:!1}),i=n.length>=e.dropdown.enabled,s={value:n,inputElm:this.DOM.input},a=this.validateTag({value:n});"select"==e.mode&&this.toggleScopeValidation(a),s.isValid=a,this.state.inputText!=n&&(this.input.set.call(this,n,!1),-1!=n.search(e.delimiters)?this.addTags(n)&&this.input.set.call(this):e.dropdown.enabled>=0&&this.dropdown[i?"show":"hide"](n),this.trigger("input",s))},onMixTagsInput:function(t){var e,n,i,s,a,o,r,l,d=this,c=this.settings,u=this.value.length,h=this.getTagElms(),g=document.createDocumentFragment(),p=window.getSelection().getRangeAt(0),f=[].map.call(h,(function(t){return Tt(t).value}));if("deleteContentBackward"==t.inputType&&ft()&&this.events.callbacks.onKeydown.call(this,{target:t.target,key:"Backspace"}),xt(this.getTagElms()),this.value.slice().forEach((function(t){t.readonly&&!f.includes(t.value)&&g.appendChild(d.createTagElem(t))})),g.childNodes.length&&(p.insertNode(g),this.setRangeAtStartEnd(!1,g.lastChild)),h.length!=u)return this.value=[].map.call(this.getTagElms(),(function(t){return Tt(t)})),void this.update({withoutChangeEvent:!0});if(this.hasMaxTags())return!0;if(window.getSelection&&(o=window.getSelection()).rangeCount>0&&3==o.anchorNode.nodeType){if((p=o.getRangeAt(0).cloneRange()).collapse(!0),p.setStart(o.focusNode,0),i=(e=p.toString().slice(0,p.endOffset)).split(c.pattern).length-1,(n=e.match(c.pattern))&&(s=e.slice(e.lastIndexOf(n[n.length-1]))),s){if(this.state.actions.ArrowLeft=!1,this.state.tag={prefix:s.match(c.pattern)[0],value:s.replace(c.pattern,"")},this.state.tag.baseOffset=o.baseOffset-this.state.tag.value.length,l=this.state.tag.value.match(c.delimiters))return this.state.tag.value=this.state.tag.value.replace(c.delimiters,""),this.state.tag.delimiters=l[0],this.addTags(this.state.tag.value,c.dropdown.clearOnSelect),void this.dropdown.hide();a=this.state.tag.value.length>=c.dropdown.enabled;try{r=(r=this.state.flaggedTags[this.state.tag.baseOffset]).prefix==this.state.tag.prefix&&r.value[0]==this.state.tag.value[0],this.state.flaggedTags[this.state.tag.baseOffset]&&!this.state.tag.value&&delete this.state.flaggedTags[this.state.tag.baseOffset]}catch(t){}(r||i500||!e.focusable)?this.state.dropdown.visible?this.dropdown.hide():0===e.dropdown.enabled&&"mix"!=e.mode&&this.dropdown.show(this.value.length?"":void 0):"select"!=e.mode||0!==e.dropdown.enabled||this.state.dropdown.visible||(this.events.callbacks.onDoubleClickScope.call(this,function(t,e){return e=null!=e?e:{},Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(e)):function(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);n.push.apply(n,i)}return n}(Object(e)).forEach((function(n){Object.defineProperty(t,n,Object.getOwnPropertyDescriptor(e,n))})),t}(function(t){for(var e=1;e=this.settings.dropdown.enabled&&(this.state.editing&&(this.state.editing.value=o),this.dropdown.show(o)),this.trigger("edit:input",{tag:i,index:s,data:ht({},this.value[s],{newValue:o}),event:e})},onEditTagPaste:function(t,e){var n=(e.clipboardData||window.clipboardData).getData("Text");e.preventDefault();var i=bt(n);this.setRangeAtStartEnd(!1,i)},onEditTagClick:function(t,e){this.events.callbacks.onClickScope.call(this,e)},onEditTagFocus:function(t){this.state.editing={scope:t,input:t.querySelector("[contenteditable]")}},onEditTagBlur:function(t,e){var n=vt.call(this,e.relatedTarget);if("select"==this.settings.mode&&n&&e.relatedTarget.contains(e.target))this.dropdown.hide();else if(this.state.editing&&(this.state.hasFocus||this.toggleFocusClass(),this.DOM.scope.contains(document.activeElement)||this.trigger("blur",{}),this.DOM.scope.contains(t))){var i,s,a,o=this.settings,r=t.closest("."+o.classNames.tag),l=Tt(r),d=this.input.normalize.call(this,t),c=(Ht(i={},o.tagTextProp,d),Ht(i,"__tagId",l.__tagId),i),u=l.__originalData,h=this.editTagChangeDetected(ht(l,c)),g=this.validateTag(c);if(d)if(h){var p;if(s=this.hasMaxTags(),a=ht({},u,(Ht(p={},o.tagTextProp,this.trim(d)),Ht(p,"__isValid",g),p)),o.transformTag.call(this,a,u),!0!==(g=(!s||!0===u.__isValid)&&this.validateTag(a))){if(this.trigger("invalid",{data:a,tag:r,message:g}),o.editTags.keepInvalid)return;o.keepInvalidTags?a.__isValid=g:a=u}else o.keepInvalidTags&&(delete a.title,delete a["aria-invalid"],delete a.class);this.onEditTagDone(r,a)}else this.onEditTagDone(r,u);else this.onEditTagDone(r)}},onEditTagkeydown:function(t,e){if(!this.state.composing)switch(this.trigger("edit:keydown",{event:t}),t.key){case"Esc":case"Escape":this.state.editing=!1,e.__tagifyTagData.__originalData.value?e.parentNode.replaceChild(e.__tagifyTagData.__originalHTML,e):e.remove();break;case"Enter":case"Tab":t.preventDefault(),setTimeout((function(){return t.target.blur()}),0)}},onDoubleClickScope:function(t){var e=t.target.closest("."+this.settings.classNames.tag);if(e){var n,i,s=Tt(e),a=this.settings;!1!==(null==s?void 0:s.editable)&&(n=e.classList.contains(this.settings.classNames.tagEditing),i=e.hasAttribute("readonly"),a.readonly||n||i||!this.settings.editTags||!a.userInput||(this.events.callbacks.onEditTagFocus.call(this,e),this.editTag(e)),this.toggleFocusClass(!0),"select"!=a.mode&&this.trigger("dblclick",{tag:e,index:this.getNodeIndex(e),data:Tt(e)}))}},onInputDOMChange:function(t){var e=this;t.forEach((function(t){t.addedNodes.forEach((function(t){if("

"==t.outerHTML)t.replaceWith(document.createElement("br"));else if(1==t.nodeType&&t.querySelector(e.settings.classNames.tagSelector)){var n,i=document.createTextNode("");3==t.childNodes[0].nodeType&&"BR"!=t.previousSibling.nodeName&&(i=document.createTextNode("\n")),(n=t).replaceWith.apply(n,Rt([i].concat(Rt(Rt(t.childNodes).slice(0,-1))))),St(i)}else if(vt.call(e,t)){var s;if(3!=(null===(s=t.previousSibling)||void 0===s?void 0:s.nodeType)||t.previousSibling.textContent||t.previousSibling.remove(),t.previousSibling&&"BR"==t.previousSibling.nodeName){t.previousSibling.replaceWith("\n​");for(var a=t.nextSibling,o="";a;)o+=a.textContent,a=a.nextSibling;o.trim()&&St(t.previousSibling)}else t.previousSibling&&!Tt(t.previousSibling)||t.before("​")}})),t.removedNodes.forEach((function(t){t&&"BR"==t.nodeName&&vt.call(e,n)&&(e.removeTags(n),e.fixFirefoxLastTagNoCaret())}))}));var n=this.DOM.input.lastChild;n&&""==n.nodeValue&&n.remove(),n&&"BR"==n.nodeName||this.DOM.input.appendChild(document.createElement("br"))}}};function Ut(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,i=new Array(e);nt.map((t=>t.value)),dropdown:{enabled:e.dropdownSuggestionsStartAfter,maxItems:e.dropdownMaxItems,closeOnSelect:e.dropdownCloseOnSelect,highlightFirst:e.highlight},transformTag(t){t.display||(t.display=t.value,t.value=encodeURIComponent(t.value)),t.display=t.display.replace(//g,">")},templates:{wrapper(t,e){return`
\n ${this.settings.templates.input.call(this)}\n ​\n
`},tag:t=>`
\n \n
\n ${t.display}\n
\n
`,dropdownItem:t=>`
\n ${t.display}\n
`}}}(e.id,n));o.addTags(i),void 0!==s&&o.on("input",(t=>{!function(t,e,n,i,s,a){Qt instanceof AbortController&&Qt.abort(),Qt=new AbortController,t.whitelist=null,void 0!==Gt&&(t.DOM.scope.ownerDocument.defaultView.clearTimeout(Gt),Gt=void 0),s.detail.value.length{const e=s.detail.value;n.writeParameter(i,e),t.loading(!0),fetch(n.getUrl().toString(),{signal:Qt.signal}).then((t=>t.json())).catch((()=>{})).then((n=>{t.whitelist=n,t.loading(!1).dropdown.show(e)}))}),a))}(o,n.suggestionStarts,s,a,t,n.autocompleteTriggerTimeout)}))}Jt.prototype={_dropdown:Lt,placeCaretAfterNode:St,getSetTagData:Tt,helpers:{sameStr:at,removeCollectionProp:ot,omit:rt,isObject:ut,parseHTML:lt,escapeHTML:ct,extend:ht,concatWithoutDups:gt,getUID:mt,isNodeTag:vt},customEventsList:["change","add","remove","invalid","input","paste","click","keydown","focus","blur","edit:input","edit:beforeUpdate","edit:updated","edit:start","edit:keydown","dropdown:show","dropdown:hide","dropdown:select","dropdown:updated","dropdown:noMatch","dropdown:scroll"],dataProps:["__isValid","__removed","__originalData","__originalHTML","__tagId"],trim:function(t){return this.settings.trim&&t&&"string"==typeof t?t.trim():t},parseHTML:lt,templates:Bt,parseTemplate:function(t,e){return lt((t=this.settings.templates[t]||t).apply(this,e))},set whitelist(t){var e=t&&Array.isArray(t);this.settings.whitelist=e?t:[],this.setPersistedData(e?t:[],"whitelist")},get whitelist(){return this.settings.whitelist},set userInput(t){this.settings.userInput=!!t,this.setContentEditable(!!t)},get userInput(){return this.settings.userInput},generateClassSelectors:function(t){var e=function(e){var n=e;Object.defineProperty(t,n+"Selector",{get:function(){return"."+this[n].split(" ")[0]}})};for(var n in t)e(n)},applySettings:function(t,e){var n,i;Et.templates=this.templates;var s=ht({},Et,"mix"==e.mode?{dropdown:{position:"text"}}:{}),a=this.settings=ht({},s,e);if(a.disabled=t.hasAttribute("disabled"),a.readonly=a.readonly||t.hasAttribute("readonly"),a.placeholder=ct(t.getAttribute("placeholder")||a.placeholder||""),a.required=t.hasAttribute("required"),this.generateClassSelectors(a.classNames),this.isIE&&(a.autoComplete=!1),["whitelist","blacklist"].forEach((function(e){var n=t.getAttribute("data-"+e);n&&zt(n=n.split(a.delimiters),Array)&&(a[e]=n)})),"autoComplete"in e&&!ut(e.autoComplete)&&(a.autoComplete=Et.autoComplete,a.autoComplete.enabled=e.autoComplete),"mix"==a.mode&&(a.pattern=a.pattern||/@/,a.autoComplete.rightKey=!0,a.delimiters=e.delimiters||null,a.tagTextProp&&!a.dropdown.searchKeys.includes(a.tagTextProp)&&a.dropdown.searchKeys.push(a.tagTextProp)),t.pattern)try{a.pattern=new RegExp(t.pattern)}catch(t){}if(a.delimiters){a._delimiters=a.delimiters;try{a.delimiters=new RegExp(this.settings.delimiters,"g")}catch(t){}}a.disabled&&(a.userInput=!1),this.TEXTS=Kt({},Pt,a.texts||{}),"select"==a.mode&&(a.dropdown.includeSelectedTags=!0),("select"!=a.mode||(null===(n=e.dropdown)||void 0===n?void 0:n.enabled))&&a.userInput||(a.dropdown.enabled=0),a.dropdown.appendTarget=(null===(i=e.dropdown)||void 0===i?void 0:i.appendTarget)||document.body,void 0===a.dropdown.includeSelectedTags&&(a.dropdown.includeSelectedTags=a.duplicates);var o=this.getPersistedData("whitelist");Array.isArray(o)&&(this.whitelist=Array.isArray(a.whitelist)?gt(a.whitelist,o):o)},getAttributes:function(t){var e,n=this.getCustomAttributes(t),i="";for(e in n)i+=" "+e+(void 0!==t[e]?'="'.concat(n[e],'"'):"");return i},getCustomAttributes:function(t){if(!ut(t))return"";var e,n={};for(e in t)"__"!=e.slice(0,2)&&"class"!=e&&t.hasOwnProperty(e)&&void 0!==t[e]&&(n[e]=ct(t[e]));return n},setStateSelection:function(){var t=window.getSelection(),e={anchorOffset:t.anchorOffset,anchorNode:t.anchorNode,range:t.getRangeAt&&t.rangeCount&&t.getRangeAt(0)};return this.state.selection=e,e},getCSSVars:function(){var t,e,n=getComputedStyle(this.DOM.scope,null);this.CSSVars={tagHideTransition:(t=function(t){if(!t)return{};var e=(t=t.trim().split(" ")[0]).split(/\d+/g).filter((function(t){return t})).pop().trim();return{value:+t.split(e).filter((function(t){return t}))[0].trim(),unit:e}}(("tag-hide-transition",n.getPropertyValue("--tag-hide-transition"))),e=t.value,"s"==t.unit?1e3*e:e)}},build:function(t){var e=this.DOM,n=t.closest("label");this.settings.mixMode.integrated?(e.originalInput=null,e.scope=t,e.input=t):(e.originalInput=t,e.originalInput_tabIndex=t.tabIndex,e.scope=this.parseTemplate("wrapper",[t,this.settings]),e.input=e.scope.querySelector(this.settings.classNames.inputSelector),t.parentNode.insertBefore(e.scope,t),t.tabIndex=-1),n&&n.setAttribute("for","")},destroy:function(){var t;this.events.unbindGlobal.call(this),null===(t=this.DOM.scope.parentNode)||void 0===t||t.removeChild(this.DOM.scope),this.DOM.originalInput.tabIndex=this.DOM.originalInput_tabIndex,delete this.DOM.originalInput.__tagify,this.dropdown.hide(!0),this.removeAllCustomListeners(),clearTimeout(this.dropdownHide__bindEventsTimeout),clearInterval(this.listeners.main.originalInputValueObserverInterval)},loadOriginalValues:function(t){var e,n=this.settings;if(this.state.blockChangeEvent=!0,void 0===t){var i=this.getPersistedData("value");t=i&&!this.DOM.originalInput.value?i:n.mixMode.integrated?this.DOM.input.textContent:this.DOM.originalInput.value}if(this.removeAllTags(),t)if("mix"==n.mode)this.parseMixTags(t),(e=this.DOM.input.lastChild)&&"BR"==e.tagName||this.DOM.input.insertAdjacentHTML("beforeend","
");else{try{zt(JSON.parse(t),Array)&&(t=JSON.parse(t))}catch(t){}this.addTags(t,!0).forEach((function(t){return t&&t.classList.add(n.classNames.tagNoAnimation)}))}else this.postUpdate();this.state.lastOriginalValueReported=n.mixMode.integrated?"":this.DOM.originalInput.value},cloneEvent:function(t){var e={};for(var n in t)"path"!=n&&(e[n]=t[n]);return e},loading:function(t){return this.state.isLoading=t,this.DOM.scope.classList[t?"add":"remove"](this.settings.classNames.scopeLoading),this},tagLoading:function(t,e){return t&&t.classList[e?"add":"remove"](this.settings.classNames.tagLoading),this},toggleClass:function(t,e){"string"==typeof t&&this.DOM.scope.classList.toggle(t,e)},toggleScopeValidation:function(t){var e=!0===t||void 0===t;!this.settings.required&&t&&t===this.TEXTS.empty&&(e=!0),this.toggleClass(this.settings.classNames.tagInvalid,!e),this.DOM.scope.title=e?"":t},toggleFocusClass:function(t){this.toggleClass(this.settings.classNames.focus,!!t)},setPlaceholder:function(t){var e=this;["data","aria"].forEach((function(n){return e.DOM.input.setAttribute("".concat(n,"-placeholder"),t)}))},triggerChangeEvent:function(){if(!this.settings.mixMode.integrated){var t=this.DOM.originalInput,e=this.state.lastOriginalValueReported!==t.value,n=new CustomEvent("change",{bubbles:!0});e&&(this.state.lastOriginalValueReported=t.value,n.simulated=!0,t._valueTracker&&t._valueTracker.setValue(Math.random()),t.dispatchEvent(n),this.trigger("change",this.state.lastOriginalValueReported),t.value=this.state.lastOriginalValueReported)}},events:$t,fixFirefoxLastTagNoCaret:function(){},setRangeAtStartEnd:function(t,e){if(e){t="number"==typeof t?t:!!t,e=e.lastChild||e;var n=document.getSelection();if(zt(n.focusNode,Element)&&!this.DOM.input.contains(n.focusNode))return!0;try{n.rangeCount>=1&&["Start","End"].forEach((function(i){return n.getRangeAt(0)["set"+i](e,t||e.length)}))}catch(t){console.warn(t)}}},insertAfterTag:function(t,e){if(e=e||this.settings.mixMode.insertAfterTag,t&&t.parentNode&&e)return e="string"==typeof e?document.createTextNode(e):e,t.parentNode.insertBefore(e,t.nextSibling),e},editTagChangeDetected:function(t){var e=t.__originalData;for(var n in e)if(!this.dataProps.includes(n)&&t[n]!=e[n])return!0;return!1},getTagTextNode:function(t){return t.querySelector(this.settings.classNames.tagTextSelector)},setTagTextNode:function(t,e){this.getTagTextNode(t).innerHTML=ct(e)},editTag:function(t,e){var n=this;t=t||this.getLastTag(),e=e||{};var i=this.settings,s=this.getTagTextNode(t),a=this.getNodeIndex(t),o=Tt(t),r=this.events.callbacks,l=!0,d="select"==i.mode;if(!d&&this.dropdown.hide(),s){if(!zt(o,Object)||!("editable"in o)||o.editable)return o=Tt(t,{__originalData:ht({},o),__originalHTML:t.cloneNode(!0)}),Tt(o.__originalHTML,o.__originalData),s.setAttribute("contenteditable",!0),t.classList.add(i.classNames.tagEditing),this.events.callbacks.onEditTagFocus.call(this,t),s.addEventListener("click",r.onEditTagClick.bind(this,t)),s.addEventListener("blur",r.onEditTagBlur.bind(this,this.getTagTextNode(t))),s.addEventListener("input",r.onEditTagInput.bind(this,s)),s.addEventListener("paste",r.onEditTagPaste.bind(this,s)),s.addEventListener("keydown",(function(e){return r.onEditTagkeydown.call(n,e,t)})),s.addEventListener("compositionstart",r.onCompositionStart.bind(this)),s.addEventListener("compositionend",r.onCompositionEnd.bind(this)),e.skipValidation||(l=this.editTagToggleValidity(t)),s.originalIsValid=l,this.trigger("edit:start",{tag:t,index:a,data:o,isValid:l}),s.focus(),!d&&this.setRangeAtStartEnd(!1,s),0===i.dropdown.enabled&&!d&&this.dropdown.show(),this.state.hasFocus=!0,this}else st.warn("Cannot find element in Tag template: .",i.classNames.tagTextSelector)},editTagToggleValidity:function(t,e){var n;if(e=e||Tt(t))return(n=!("__isValid"in e)||!0===e.__isValid)||this.removeTagsFromValue(t),this.update(),t.classList.toggle(this.settings.classNames.tagNotAllowed,!n),e.__isValid=n,e.__isValid;st.warn("tag has no data: ",t,e)},onEditTagDone:function(t,e){t=t||this.state.editing.scope,e=e||{};var n,i,s=this.settings,a={tag:t,index:this.getNodeIndex(t),previousData:Tt(t),data:e};this.trigger("edit:beforeUpdate",a,{cloneData:!1}),this.state.editing=!1,delete e.__originalData,delete e.__originalHTML,t&&t.parentNode&&((void 0!==(i=e[s.tagTextProp])?null===(n=(i+="").trim)||void 0===n?void 0:n.call(i):s.tagTextProp in e?void 0:e.value)?(t=this.replaceTag(t,e),this.editTagToggleValidity(t,e),s.a11y.focusableTags?t.focus():"select"!=s.mode&&St(t)):this.removeTags(t)),this.trigger("edit:updated",a),s.dropdown.closeOnSelect&&this.dropdown.hide(),this.settings.keepInvalidTags&&this.reCheckInvalidTags()},replaceTag:function(t,e){e&&""!==e.value&&void 0!==e.value||(e=t.__tagifyTagData),e.__isValid&&1!=e.__isValid&&ht(e,this.getInvalidTagAttrs(e,e.__isValid));var n=this.createTagElem(e);return t.parentNode.replaceChild(n,t),this.updateValueByDOMTags(),n},updateValueByDOMTags:function(){var t=this;this.value.length=0;var e=this.settings.classNames,n=[e.tagNotAllowed.split(" ")[0],e.tagHide];[].forEach.call(this.getTagElms(),(function(e){Xt(e.classList).some((function(t){return n.includes(t)}))||t.value.push(Tt(e))})),this.update(),this.dropdown.refilter()},injectAtCaret:function(t,e){var n;if(e=e||(null===(n=this.state.selection)||void 0===n?void 0:n.range),"string"==typeof t&&(t=document.createTextNode(t)),!e&&t)return this.appendMixTags(t),this;var i=bt(t,e);return this.setRangeAtStartEnd(!1,i),this.updateValueByDOMTags(),this.update(),this},input:{set:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",e=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],n=this.settings,i=n.dropdown.closeOnSelect;this.state.inputText=t,e&&(this.DOM.input.innerHTML=ct(""+t),t&&this.toggleClass(n.classNames.empty,!this.DOM.input.innerHTML)),!t&&i&&this.dropdown.hide.bind(this),this.input.autocomplete.suggest.call(this),this.input.validate.call(this)},raw:function(){return this.DOM.input.textContent},validate:function(){var t=!this.state.inputText||!0===this.validateTag({value:this.state.inputText});return this.DOM.input.classList.toggle(this.settings.classNames.inputInvalid,!t),t},normalize:function(t,e){var n=t||this.DOM.input,i=[];n.childNodes.forEach((function(t){return 3==t.nodeType&&i.push(t.nodeValue)})),i=i.join("\n");try{i=i.replace(/(?:\r\n|\r|\n)/g,this.settings.delimiters.source.charAt(0))}catch(t){}return i=i.replace(/\s/g," "),(null==e?void 0:e.trim)?this.trim(i):i},autocomplete:{suggest:function(t){if(this.settings.autoComplete.enabled){"object"!=typeof(t=t||{value:""})&&(t={value:t});var e=this.dropdown.getMappedValue(t);if("number"!=typeof e){var n=this.state.inputText.toLowerCase(),i=e.substr(0,this.state.inputText.length).toLowerCase(),s=e.substring(this.state.inputText.length);e&&this.state.inputText&&i==n?(this.DOM.input.setAttribute("data-suggest",s),this.state.inputSuggestion=t):(this.DOM.input.removeAttribute("data-suggest"),delete this.state.inputSuggestion)}}},set:function(t){var e=this.DOM.input.getAttribute("data-suggest"),n=t||(e?this.state.inputText+e:null);return!!n&&("mix"==this.settings.mode?this.replaceTextWithNode(document.createTextNode(this.state.tag.prefix+n)):(this.input.set.call(this,n),this.setRangeAtStartEnd(!1,this.DOM.input)),this.input.autocomplete.suggest.call(this),this.dropdown.hide(),!0)}}},getTagIdx:function(t){return this.value.findIndex((function(e){return e.__tagId==(t||{}).__tagId}))},getNodeIndex:function(t){var e=0;if(t)for(;t=t.previousElementSibling;)e++;return e},getTagElms:function(){for(var t=arguments.length,e=new Array(t),n=0;n=this.settings.maxTags&&this.TEXTS.exceed},setReadonly:function(t,e){var n=this.settings;this.DOM.scope.contains(document.activeElement)&&document.activeElement.blur(),n[e||"readonly"]=t,this.DOM.scope[(t?"set":"remove")+"Attribute"](e||"readonly",!0),this.settings.userInput=!0,this.setContentEditable(!t)},setContentEditable:function(t){this.DOM.input.contentEditable=t,this.DOM.input.tabIndex=t?0:-1},setDisabled:function(t){this.setReadonly(t,"disabled")},normalizeTags:function(t){var e=this,n=this.settings,i=n.whitelist,s=n.delimiters,a=n.mode,o=n.tagTextProp,r=[],l=!!i&&zt(i[0],Object),d=Array.isArray(t),c=d&&t[0].value,u=function(t){return(t+"").split(s).reduce((function(t,n){var i,s=e.trim(n);return s&&t.push((Wt(i={},o,s),Wt(i,"value",s),i)),t}),[])};if("number"==typeof t&&(t=t.toString()),"string"==typeof t){if(!t.trim())return[];t=u(t)}else d&&(t=t.reduce((function(t,n){if(ut(n)){var i=ht({},n);o in i||(o="value"),i[o]=e.trim(i[o]),(i[o]||0===i[o])&&t.push(i)}else if(null!=n&&""!==n&&void 0!==n){var s;(s=t).push.apply(s,Xt(u(n)))}return t}),[]));return l&&!c&&(t.forEach((function(t){var n=r.map((function(t){return t.value})),i=e.dropdown.filterListItems.call(e,t[o],{exact:!0});e.settings.duplicates||(i=i.filter((function(t){return!n.includes(t.value)})));var s=i.length>1?e.getWhitelistItem(t[o],o,i):i[0];s&&zt(s,Object)?r.push(s):"mix"!=a&&(null==t.value&&(t.value=t[o]),r.push(t))})),r.length&&(t=r)),t},parseMixTags:function(t){var e=this,n=this.settings,i=n.mixTagsInterpolator,s=n.duplicates,a=n.transformTag,o=n.enforceWhitelist,r=n.maxTags,l=n.tagTextProp,d=[];t=t.split(i[0]).map((function(t,n){var c,u,h,g=t.split(i[1]),p=g[0],f=d.length==r;try{if(p==+p)throw Error;u=JSON.parse(p)}catch(t){u=e.normalizeTags(p)[0]||{value:p}}if(a.call(e,u),f||!(g.length>1)||o&&!e.isTagWhitelisted(u.value)||!s&&e.isTagDuplicate(u.value)){if(t)return n?i[0]+t:t}else u[c=u[l]?l:"value"]=e.trim(u[c]),h=e.createTagElem(u),d.push(u),h.classList.add(e.settings.classNames.tagNoAnimation),g[0]=h.outerHTML,e.value.push(u);return g.join("")})).join(""),this.DOM.input.innerHTML=t,this.DOM.input.appendChild(document.createTextNode("")),this.DOM.input.normalize();var c=this.getTagElms();return c.forEach((function(t,e){return Tt(t,d[e])})),this.update({withoutChangeEvent:!0}),xt(c,this.state.hasFocus),t},replaceTextWithNode:function(t,e){if(this.state.tag||e){e=e||this.state.tag.prefix+this.state.tag.value;var n,i,s=this.state.selection||window.getSelection(),a=s.anchorNode,o=this.state.tag.delimiters?this.state.tag.delimiters.length:0;return a.splitText(s.anchorOffset-o),-1==(n=a.nodeValue.lastIndexOf(e))||(i=a.splitText(n),t&&a.parentNode.replaceChild(t,i)),!0}},prepareNewTagNode:function(t,e){e=e||{};var n=this.settings,i=[],s={},a=Object.assign({},t,{value:t.value+""});if(t=Object.assign({},a),n.transformTag.call(this,t),t.__isValid=this.hasMaxTags()||this.validateTag(t),!0!==t.__isValid){if(e.skipInvalid)return;if(ht(s,this.getInvalidTagAttrs(t,t.__isValid),{__preInvalidData:a}),t.__isValid==this.TEXTS.duplicate&&this.flashTag(this.getTagElmByValue(t.value)),!n.createInvalidTags)return void i.push(t.value)}return"readonly"in t&&(t.readonly?s["aria-readonly"]=!0:delete t.readonly),{tagElm:this.createTagElem(t,s),tagData:t,aggregatedInvalidInput:i}},postProcessNewTagNode:function(t,e){var n=this,i=this.settings,s=e.__isValid;s&&!0===s?this.value.push(e):(this.trigger("invalid",{data:e,index:this.value.length,tag:t,message:s}),i.keepInvalidTags||setTimeout((function(){return n.removeTags(t,!0)}),1e3)),this.dropdown.position()},selectTag:function(t,e){var n=this;if(!this.settings.enforceWhitelist||this.isTagWhitelisted(e.value)){this.state.actions.selectOption&&setTimeout((function(){return n.setRangeAtStartEnd(!1,n.DOM.input)}));var i=this.getLastTag();return i?this.replaceTag(i,e):this.appendTag(t),this.value[0]=e,this.update(),this.trigger("add",{tag:t,data:e}),[t]}},addEmptyTag:function(t){var e=ht({value:""},t||{}),n=this.createTagElem(e);Tt(n,e),this.appendTag(n),this.editTag(n,{skipValidation:!0}),this.toggleFocusClass(!0)},addTags:function(t,e,n){var i=this,s=[],a=this.settings,o=[],r=document.createDocumentFragment(),l=[];if(!t||0==t.length)return s;switch(t=this.normalizeTags(t),a.mode){case"mix":return this.addMixTags(t);case"select":e=!1,this.removeAllTags()}return this.DOM.input.removeAttribute("style"),t.forEach((function(t){var e=i.prepareNewTagNode(t,{skipInvalid:n||a.skipInvalid});if(e){var d=e.tagElm;if(t=e.tagData,o=e.aggregatedInvalidInput,s.push(d),"select"==a.mode)return i.selectTag(d,t);r.appendChild(d),i.postProcessNewTagNode(d,t),l.push({tagElm:d,tagData:t})}})),this.appendTag(r),l.forEach((function(t){var e=t.tagElm,n=t.tagData;return i.trigger("add",{tag:e,index:i.getTagIdx(n),data:n})})),this.update(),t.length&&e&&(this.input.set.call(this,a.createInvalidTags?"":o.join(a._delimiters)),this.setRangeAtStartEnd(!1,this.DOM.input)),this.dropdown.refilter(),s},addMixTags:function(t){var e=this;if((t=this.normalizeTags(t))[0].prefix||this.state.tag)return this.prefixedTextToTag(t[0]);var n=document.createDocumentFragment();return t.forEach((function(t){var i=e.prepareNewTagNode(t);n.appendChild(i.tagElm),e.insertAfterTag(i.tagElm),e.postProcessNewTagNode(i.tagElm,i.tagData)})),this.appendMixTags(n),n.children},appendMixTags:function(t){var e=!!this.state.selection;e?this.injectAtCaret(t):(this.DOM.input.focus(),(e=this.setStateSelection()).range.setStart(this.DOM.input,e.range.endOffset),e.range.setEnd(this.DOM.input,e.range.endOffset),this.DOM.input.appendChild(t),this.updateValueByDOMTags(),this.update())},prefixedTextToTag:function(t){var e,n,i,s=this,a=this.settings,o=null===(e=this.state.tag)||void 0===e?void 0:e.delimiters;if(t.prefix=t.prefix||this.state.tag?this.state.tag.prefix:(a.pattern.source||a.pattern)[0],i=this.prepareNewTagNode(t),n=i.tagElm,this.replaceTextWithNode(n)||this.DOM.input.appendChild(n),setTimeout((function(){return n.classList.add(s.settings.classNames.tagNoAnimation)}),300),this.update(),!o){var r=this.insertAfterTag(n)||n;setTimeout(St,0,r)}return this.state.tag=null,this.postProcessNewTagNode(n,i.tagData),n},appendTag:function(t){var e=this.DOM,n=e.input;e.scope.insertBefore(t,n)},createTagElem:function(t,e){t.__tagId=mt();var n,i=ht({},t,Kt({value:ct(t.value+"")},e));return function(t){for(var e,n=document.createNodeIterator(t,NodeFilter.SHOW_TEXT,null,!1);e=n.nextNode();)e.textContent.trim()||e.parentNode.removeChild(e)}(n=this.parseTemplate("tag",[i,this])),Tt(n,t),n},reCheckInvalidTags:function(){var t=this,e=this.settings;this.getTagElms(e.classNames.tagNotAllowed).forEach((function(n,i){var s=Tt(n),a=t.hasMaxTags(),o=t.validateTag(s),r=!0===o&&!a;if("select"==e.mode&&t.toggleScopeValidation(o),r)return s=s.__preInvalidData?s.__preInvalidData:{value:s.value},t.replaceTag(n,s);n.title=a||o}))},removeTags:function(t,e,n){var i,s=this,a=this.settings;if(t=t&&zt(t,HTMLElement)?[t]:zt(t,Array)?t:t?[t]:[this.getLastTag()].filter((function(t){return t})),i=t.reduce((function(t,e){e&&"string"==typeof e&&(e=s.getTagElmByValue(e));var n=Tt(e);return e&&n&&!n.readonly&&t.push({node:e,idx:s.getTagIdx(n),data:Tt(e,{__removed:!0})}),t}),[]),n="number"==typeof n?n:this.CSSVars.tagHideTransition,"select"==a.mode&&(n=0,this.input.set.call(this)),1==i.length&&"select"!=a.mode&&i[0].node.classList.contains(a.classNames.tagNotAllowed)&&(e=!0),i.length)return a.hooks.beforeRemoveTag(i,{tagify:this}).then((function(){var t=function(t){t.node.parentNode&&(t.node.parentNode.removeChild(t.node),e?a.keepInvalidTags&&this.trigger("remove",{tag:t.node,index:t.idx}):(this.trigger("remove",{tag:t.node,index:t.idx,data:t.data}),this.dropdown.refilter(),this.dropdown.position(),this.DOM.input.normalize(),a.keepInvalidTags&&this.reCheckInvalidTags()))};n&&n>10&&1==i.length?function(e){e.node.style.width=parseFloat(window.getComputedStyle(e.node).width)+"px",document.body.clientTop,e.node.classList.add(a.classNames.tagHide),setTimeout(t.bind(this),n,e)}.call(s,i[0]):i.forEach(t.bind(s)),e||(s.removeTagsFromValue(i.map((function(t){return t.node}))),s.update(),"select"==a.mode&&a.userInput&&s.setContentEditable(!0))})).catch((function(t){}))},removeTagsFromDOM:function(){this.getTagElms().forEach((function(t){return t.remove()}))},removeTagsFromValue:function(t){var e=this;(t=Array.isArray(t)?t:[t]).forEach((function(t){var n=Tt(t),i=e.getTagIdx(n);i>-1&&e.value.splice(i,1)}))},removeAllTags:function(t){var e=this;t=t||{},this.value=[],"mix"==this.settings.mode?this.DOM.input.innerHTML="":this.removeTagsFromDOM(),this.dropdown.refilter(),this.dropdown.position(),this.state.dropdown.visible&&setTimeout((function(){e.DOM.input.focus()})),"select"==this.settings.mode&&(this.input.set.call(this),this.settings.userInput&&this.setContentEditable(!0)),this.update(t)},postUpdate:function(){this.state.blockChangeEvent=!1;var t,e,n=this.settings,i=n.classNames,s="mix"==n.mode?n.mixMode.integrated?this.DOM.input.textContent:this.DOM.originalInput.value.trim():this.value.length+this.input.raw.call(this).length;this.toggleClass(i.hasMaxTags,this.value.length>=n.maxTags),this.toggleClass(i.hasNoTags,!this.value.length),this.toggleClass(i.empty,!s),"select"==n.mode&&this.toggleScopeValidation(null===(e=this.value)||void 0===e||null===(t=e[0])||void 0===t?void 0:t.__isValid)},setOriginalInputValue:function(t){var e=this.DOM.originalInput;this.settings.mixMode.integrated||(e.value=t,e.tagifyValue=e.value,this.setPersistedData(t,"value"))},update:function(t){clearTimeout(this.debouncedUpdateTimeout),this.debouncedUpdateTimeout=setTimeout(function(){var e=this.getInputValue();this.setOriginalInputValue(e),this.settings.onChangeAfterBlur&&(t||{}).withoutChangeEvent||this.state.blockChangeEvent||this.triggerChangeEvent(),this.postUpdate()}.bind(this),100),this.events.bindOriginaInputListener.call(this,100)},getInputValue:function(){var t=this.getCleanValue();return"mix"==this.settings.mode?this.getMixedTagsAsString(t):t.length?this.settings.originalInputValueFormat?this.settings.originalInputValueFormat(t):JSON.stringify(t):""},getCleanValue:function(t){return ot(t||this.value,this.dataProps)},getMixedTagsAsString:function(){var t="",e=this,n=this.settings,i=n.originalInputValueFormat||JSON.stringify,s=n.mixTagsInterpolator;return function n(a){a.childNodes.forEach((function(a){if(1==a.nodeType){var o=Tt(a);if("BR"==a.tagName&&(t+="\r\n"),o&&vt.call(e,a)){if(o.__removed)return;t+=s[0]+i(rt(o,e.dataProps))+s[1]}else a.getAttribute("style")||["B","I","U"].includes(a.tagName)?t+=a.textContent:"DIV"!=a.tagName&&"P"!=a.tagName||(t+="\r\n",n(a))}else t+=a.textContent}))}(this.DOM.input),t}},Jt.prototype.removeTag=Jt.prototype.removeTags;class Zt{#U;#W;#z;#K;#X;#J;#Q;#G;#Y;#Z;#tt;#et;#nt;#it;#st;#at=null;constructor(t,e,n,i,s,a,o,r,l,d,c,u){this.#U=t,this.#Z=e,this.#W=n,this.#z=i,this.#K=s,this.#X=a,this.#nt=o,this.#it=u,this.#st=this.#it.innerHTML,this.#Y=r,this.#J=l,this.#Q=d,this.#G=c,this.#et=!1,this.#tt=!1,this.#W.addEventListener("input",(t=>{this.filterItemsSearch(t)})),this.#Y.addEventListener("click",(()=>{this.setFiltered(!1)})),this.#J.addEventListener("click",(()=>{this.toggleVisibility()})),"radio-field-input"===this.#z&&this.#X.forEach((t=>{t.addEventListener("change",(()=>{this.scrollListToTop()}))}))}isEngaged(){return this.#et}isFiltered(){return this.#tt}setFiltered(t){this.#tt!==t&&(this.#tt=t,t?(this.#Y.style.removeProperty("display"),this.#it.style.removeProperty("display")):(this.#W.value="",this.#Y.style.display="none",this.#it.style.display="none",this.#nt.style.display="none",this.#ot()))}toggleVisibility(){this.isEngaged()?(this.#et=!1,this.#U.classList.remove("engaged"),this.setFiltered(!1),this.#J.setAttribute("aria-expanded","false"),this.#Q.style.removeProperty("display"),this.#G.style.display="none"):(this.#et=!0,this.#U.classList.add("engaged"),this.#J.setAttribute("aria-expanded","true"),this.#Q.style.display="none",this.#G.style.removeProperty("display"))}#rt(t){this.#U.ownerDocument.defaultView.clearTimeout(this.#at),this.#at=this.#U.ownerDocument.defaultView.setTimeout((()=>{this.#it.textContent="",this.#U.ownerDocument.defaultView.requestAnimationFrame((()=>{this.#it.textContent=t}))}),500)}#lt(t){const e=$(this.#st,t);this.#rt(e)}filterItemsSearch(t){const e=t.target.value.toLowerCase();this.setFiltered(!!e);let n=0,i=!1;this.#X.forEach((t=>{t.textContent.toLowerCase().includes(e)?(n+=1,i=!0,te(t)):function(t){t.style.display="none"}(t)})),this.#lt(n.toString()),""!==e&&!1===i?this.#nt.style.removeProperty("display"):(""===e||i)&&(this.#nt.style.display="none")}#ot(){this.#X.forEach((t=>te(t)))}scrollListToTop(){this.#Z.scrollTo({top:0,behavior:"smooth"})}}function te(t){t.style.removeProperty("display")}class ee{#V=new Map;init(t){if(void 0===t)throw new TypeError("During init of an InputHasOptionFilter an undefined element was passed to the factory.");if(this.#V.has(t.id))throw new Error(`A InputHasOptionFilter with id '${t.id}' has already been initialized.`);const e=t,n=e.querySelector(".c-input--has-option-filter__field"),i=e.querySelector(".c-input--has-option-filter__search-input input"),s=e.getAttribute("data-il-ui-component"),a=e.querySelector(".c-field--has-option-filter__list"),o=a.querySelectorAll(".c-field--has-option-filter__item"),r=e.querySelector(".message-no-match"),l=e.querySelector('.c-input--has-option-filter__synopsis [role="status"]'),d=e.querySelector(".c-input--has-option-filter__clear-search"),c=e.querySelector(".c-input--has-option-filter__visibility-toggle"),u=c.querySelector(".text-expand"),h=c.querySelector(".text-collapse"),g=new Zt(t,n,i,s,a,o,r,d,c,u,h,l);return this.#V.set(t.id,g),g}get(t){return this.#V.has(t)?this.#V.get(t):null}}var ne;e.UI=e.UI||{},e.UI.Input=e.UI.Input||{},(ne=e.UI.Input).textarea=new o,ne.mustacheVariables={init:(t,e)=>function(t,e){e.querySelectorAll(".c-input--has-mustache-variables__definitions > li > a").forEach((e=>{const n=function(t){const e=t.textContent.match(/^\s*\{\{([^}]+)\}\}\s*$/);return e?e[1].trim():null}(e);e.addEventListener("click",(()=>{t.insertCharactersAroundSelection(`{{${n}}}`,"")}))}))}(t,e)},ne.markdown=new w,ne.optionFilter=new ee,ne.treeSelect=new Z(new tt(t),e.UI.menu.drilldown,{txt:t=>e.Language.txt(t)},n),ne.tagInput=ne.tag||{},ne.tagInput.init=(t,e,n,i,s)=>Yt(Jt,t,e,n,i,s)}($,il,document); + */var et="​";function nt(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,i=new Array(e);n/g,">").replace(/"/g,""").replace(/`|'/g,"'"):t}function ut(t){var e=Object.prototype.toString.call(t).split(" ")[1].slice(0,-1);return t===Object(t)&&"Array"!=e&&"Function"!=e&&"RegExp"!=e&&"HTMLUnknownElement"!=e}function ht(t,e,n){var i,s;function a(t,e){for(var n in e)if(e.hasOwnProperty(n)){if(ut(e[n])){ut(t[n])?a(t[n],e[n]):t[n]=Object.assign({},e[n]);continue}if(Array.isArray(e[n])){t[n]=Object.assign([],e[n]);continue}t[n]=e[n]}}return i=t,(null!=(s=Object)&&"undefined"!=typeof Symbol&&s[Symbol.hasInstance]?s[Symbol.hasInstance](i):i instanceof s)||(t={}),a(t,e),n&&a(t,n),t}function gt(){var t=[],e={},n=!0,i=!1,s=void 0;try{for(var a,o=arguments[Symbol.iterator]();!(n=(a=o.next()).done);n=!0){var r=a.value,l=!0,d=!1,c=void 0;try{for(var u,h=r[Symbol.iterator]();!(l=(u=h.next()).done);l=!0){var g=u.value;ut(g)?e[g.value]||(t.push(g),e[g.value]=1):t.includes(g)||t.push(g)}}catch(t){d=!0,c=t}finally{try{l||null==h.return||h.return()}finally{if(d)throw c}}}}catch(t){i=!0,s=t}finally{try{n||null==o.return||o.return()}finally{if(i)throw s}}return t}function pt(t){return String.prototype.normalize?"string"==typeof t?t.normalize("NFD").replace(/[\u0300-\u036f]/g,""):void 0:t}var ft=function(){return/(?=.*chrome)(?=.*android)/i.test(navigator.userAgent)};function mt(){return([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,(function(t){return(t^crypto.getRandomValues(new Uint8Array(1))[0]&15>>t/4).toString(16)}))}function vt(t){var e;return yt.call(this,t)&&(null==t||null===(e=t.classList)||void 0===e?void 0:e.contains(this.settings.classNames.tag))}function wt(t){return yt.call(this,t)&&(null==t?void 0:t.closest(this.settings.classNames.tagSelector))}function yt(t){var e;return(null==t||null===(e=t.closest)||void 0===e?void 0:e.call(t,this.settings.classNames.namespaceSelector))===this.DOM.scope}function bt(t,e){var n=window.getSelection();return e=e||n.getRangeAt(0),"string"==typeof t&&(t=document.createTextNode(t)),e&&(e.deleteContents(),e.insertNode(t)),t}function Tt(t,e,n){return t?(e&&(t.__tagifyTagData=n?e:ht({},t.__tagifyTagData||{},e)),t.__tagifyTagData):(st.warn("tag element doesn't exist",{tagElm:t,data:e}),e)}function St(t){if(t&&t.parentNode){var e=t,n=window.getSelection(),i=n.getRangeAt(0);n.rangeCount&&(i.setStartAfter(e),i.collapse(!0),n.removeAllRanges(),n.addRange(i))}}function xt(t,e){t.forEach((function(t){if(Tt(t.previousSibling)||!t.previousSibling){var n=document.createTextNode("​");t.before(n),e&&St(n)}}))}var Et={delimiters:",",pattern:null,tagTextProp:"value",maxTags:1/0,callbacks:{},addTagOnBlur:!0,addTagOn:["blur","tab","enter"],onChangeAfterBlur:!0,duplicates:!1,whitelist:[],blacklist:[],enforceWhitelist:!1,userInput:!0,focusable:!0,keepInvalidTags:!1,createInvalidTags:!0,mixTagsAllowedAfter:/,|\.|\:|\s/,mixTagsInterpolator:["[[","]]"],backspace:!0,skipInvalid:!1,pasteAsTags:!0,editTags:{clicks:2,keepInvalid:!0},transformTag:function(){},trim:!0,a11y:{focusableTags:!1},mixMode:{insertAfterTag:" "},autoComplete:{enabled:!0,rightKey:!1,tabKey:!1},classNames:{namespace:"tagify",mixMode:"tagify--mix",selectMode:"tagify--select",input:"tagify__input",focus:"tagify--focus",tagNoAnimation:"tagify--noAnim",tagInvalid:"tagify--invalid",tagNotAllowed:"tagify--notAllowed",scopeLoading:"tagify--loading",hasMaxTags:"tagify--hasMaxTags",hasNoTags:"tagify--noTags",empty:"tagify--empty",inputInvalid:"tagify__input--invalid",dropdown:"tagify__dropdown",dropdownWrapper:"tagify__dropdown__wrapper",dropdownHeader:"tagify__dropdown__header",dropdownFooter:"tagify__dropdown__footer",dropdownItem:"tagify__dropdown__item",dropdownItemActive:"tagify__dropdown__item--active",dropdownItemHidden:"tagify__dropdown__item--hidden",dropdownItemSelected:"tagify__dropdown__item--selected",dropdownInital:"tagify__dropdown--initial",tag:"tagify__tag",tagText:"tagify__tag-text",tagX:"tagify__tag__removeBtn",tagLoading:"tagify__tag--loading",tagEditing:"tagify__tag--editable",tagFlash:"tagify__tag--flash",tagHide:"tagify__tag--hide"},dropdown:{classname:"",enabled:2,maxItems:10,searchKeys:["value","searchBy"],fuzzySearch:!0,caseSensitive:!1,accentedSearch:!0,includeSelectedTags:!1,escapeHTML:!0,highlightFirst:!0,closeOnSelect:!0,clearOnSelect:!0,position:"all",appendTarget:null},hooks:{beforeRemoveTag:function(){return Promise.resolve()},beforePaste:function(){return Promise.resolve()},suggestionClick:function(){return Promise.resolve()},beforeKeyDown:function(){return Promise.resolve()}}};function Ot(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}function Dt(t){for(var e=1;et.length)&&(e=t.length);for(var n=0,i=new Array(e);n0&&void 0!==arguments[0])||arguments[0],e=this.dropdown.events.callbacks,n=this.listeners.dropdown=this.listeners.dropdown||{position:this.dropdown.position.bind(this,null),onKeyDown:e.onKeyDown.bind(this),onMouseOver:e.onMouseOver.bind(this),onMouseLeave:e.onMouseLeave.bind(this),onClick:e.onClick.bind(this),onScroll:e.onScroll.bind(this)},i=t?"addEventListener":"removeEventListener";"manual"!=this.settings.dropdown.position&&(document[i]("scroll",n.position,!0),window[i]("resize",n.position),window[i]("keydown",n.onKeyDown)),this.DOM.dropdown[i]("mouseover",n.onMouseOver),this.DOM.dropdown[i]("mouseleave",n.onMouseLeave),this.DOM.dropdown[i]("mousedown",n.onClick),this.DOM.dropdown.content[i]("scroll",n.onScroll)},callbacks:{onKeyDown:function(t){var e=this;if(this.state.hasFocus&&!this.state.composing){var n=this.settings,i=n.dropdown.includeSelectedTags,s=this.DOM.dropdown.querySelector(n.classNames.dropdownItemActiveSelector),a=this.dropdown.getSuggestionDataByNode(s),o="mix"==n.mode,r="select"==n.mode;n.hooks.beforeKeyDown(t,{tagify:this}).then((function(l){switch(t.key){case"ArrowDown":case"ArrowUp":case"Down":case"Up":t.preventDefault();var d=e.dropdown.getAllSuggestionsRefs(),c="ArrowUp"==t.key||"Up"==t.key;s&&(s=e.dropdown.getNextOrPrevOption(s,!c)),s&&s.matches(n.classNames.dropdownItemSelector)||(s=d[c?d.length-1:0]),e.dropdown.highlightOption(s,!0);break;case"PageUp":case"PageDown":var u;t.preventDefault();var h=e.dropdown.getAllSuggestionsRefs(),g=Math.floor(e.DOM.dropdown.content.clientHeight/(null===(u=h[0])||void 0===u?void 0:u.offsetHeight))||1,p="PageUp"===t.key;if(s){var f=h.indexOf(s),m=p?Math.max(0,f-g):Math.min(h.length-1,f+g);s=h[m]}else s=h[0];e.dropdown.highlightOption(s,!0);break;case"Home":case"End":t.preventDefault();var v=e.dropdown.getAllSuggestionsRefs();s=v["Home"===t.key?0:v.length-1],e.dropdown.highlightOption(s,!0);break;case"Escape":case"Esc":e.dropdown.hide();break;case"ArrowRight":if(e.state.actions.ArrowLeft||n.autoComplete.rightKey)return;case"Tab":var w=!n.autoComplete.rightKey||!n.autoComplete.tabKey;if(!o&&!r&&s&&w&&!e.state.editing&&a){t.preventDefault();var y=e.dropdown.getMappedValue(a);return e.state.autoCompleteData=a,e.input.autocomplete.set.call(e,y),!1}return!0;case"Enter":t.preventDefault(),e.state.actions.selectOption=!0,setTimeout((function(){return e.state.actions.selectOption=!1}),100),n.hooks.suggestionClick(t,{tagify:e,tagData:a,suggestionElm:s}).then((function(){if(s){var n=i?s:e.dropdown.getNextOrPrevOption(s,!c);e.dropdown.selectOption(s,t,(function(){if(n){var t=n.getAttribute("value");n=e.dropdown.getSuggestionNodeByValue(t),e.dropdown.highlightOption(n)}}))}else e.dropdown.hide(),o||e.addTags(e.state.inputText.trim(),!0)})).catch((function(t){return st.warn(t)}));break;case"Backspace":if(o||e.state.editing.scope)return;var b=e.input.raw.call(e);""!=b&&8203!=b.charCodeAt(0)||(!0===n.backspace?e.removeTags():"edit"==n.backspace&&setTimeout(e.editTag.bind(e),0))}}))}},onMouseOver:function(t){var e=t.target.closest(this.settings.classNames.dropdownItemSelector);this.dropdown.highlightOption(e)},onMouseLeave:function(t){this.dropdown.highlightOption()},onClick:function(t){var e=this;if(0==t.button&&t.target!=this.DOM.dropdown&&t.target!=this.DOM.dropdown.content){var n=t.target.closest(this.settings.classNames.dropdownItemSelector),i=this.dropdown.getSuggestionDataByNode(n);this.state.actions.selectOption=!0,setTimeout((function(){return e.state.actions.selectOption=!1}),100),this.settings.hooks.suggestionClick(t,{tagify:this,tagData:i,suggestionElm:n}).then((function(){n?e.dropdown.selectOption(n,t):e.dropdown.hide()})).catch((function(t){return st.warn(t)}))}},onScroll:function(t){var e=t.target,n=e.scrollTop/(e.scrollHeight-e.parentNode.clientHeight)*100;this.trigger("dropdown:scroll",{percentage:Math.round(n)})}}},refilter:function(t){t=t||this.state.dropdown.query||"",this.suggestedListItems=this.dropdown.filterListItems(t),this.dropdown.fill(),this.suggestedListItems.length||this.dropdown.hide(),this.trigger("dropdown:updated",this.DOM.dropdown)},getSuggestionDataByNode:function(t){for(var e,n=t&&t.getAttribute("value"),i=this.suggestedListItems.length;i--;){if(ut(e=this.suggestedListItems[i])&&e.value==n)return e;if(e==n)return{value:e}}},getSuggestionNodeByValue:function(t){return this.dropdown.getAllSuggestionsRefs().find((function(e){return e.getAttribute("value")===t}))},getNextOrPrevOption:function(t){var e=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],n=this.dropdown.getAllSuggestionsRefs(),i=n.findIndex((function(e){return e===t}));return e?n[i+1]:n[i-1]},highlightOption:function(t,e){var n,i=this.settings.classNames.dropdownItemActive;if(this.state.ddItemElm&&(this.state.ddItemElm.classList.remove(i),this.state.ddItemElm.removeAttribute("aria-selected")),!t)return this.state.ddItemData=null,this.state.ddItemElm=null,void this.input.autocomplete.suggest.call(this);n=this.dropdown.getSuggestionDataByNode(t),this.state.ddItemData=n,this.state.ddItemElm=t,t.classList.add(i),t.setAttribute("aria-selected",!0),e&&(t.parentNode.scrollTop=t.clientHeight+t.offsetTop-t.parentNode.clientHeight),this.settings.autoComplete&&(this.input.autocomplete.suggest.call(this,n),this.dropdown.position())},selectOption:function(t,e,n){var i=this,s=this.settings,a=s.dropdown.includeSelectedTags,o=s.dropdown,r=o.clearOnSelect,l=o.closeOnSelect;if(!t)return this.addTags(this.state.inputText,!0),void(l&&this.dropdown.hide());e=e||{};var d=t.getAttribute("value"),c="noMatch"==d,u="mix"==s.mode,h=this.suggestedListItems.find((function(t){var e;return(null!==(e=t.value)&&void 0!==e?e:t)==d}));if(this.trigger("dropdown:select",{data:h,elm:t,event:e}),h||c){if(this.state.editing){var g=this.normalizeTags([h])[0];h=s.transformTag.call(this,g)||g,this.onEditTagDone(null,ht({__isValid:!0},h))}else this[u?"addMixTags":"addTags"]([h||this.input.raw.call(this)],r);(u||this.DOM.input.parentNode)&&(setTimeout((function(){i.DOM.input.focus(),i.toggleFocusClass(!0)})),l&&setTimeout(this.dropdown.hide.bind(this)),a?n&&n():(t.addEventListener("transitionend",(function(){i.dropdown.fillHeaderFooter(),setTimeout((function(){t.remove(),i.dropdown.refilter(),n&&n()}),100)}),{once:!0}),t.classList.add(this.settings.classNames.dropdownItemHidden)))}else l&&setTimeout(this.dropdown.hide.bind(this))},selectAll:function(t){this.suggestedListItems.length=0,this.dropdown.hide(),this.dropdown.filterListItems("");var e=this.dropdown.filterListItems("");return t||(e=this.state.dropdown.suggestions),this.addTags(e,!0),this},filterListItems:function(t,e){var n,i,s,a,o,r,l=function(){var t,l,d=void 0,c=void 0;t=p[y],i=(null!=(l=Object)&&"undefined"!=typeof Symbol&&l[Symbol.hasInstance]?l[Symbol.hasInstance](t):t instanceof l)?p[y]:{value:p[y]};var f,m=Object.keys(i).some((function(t){return w.includes(t)}))?w:["value"];u.fuzzySearch&&!e.exact?(a=m.reduce((function(t,e){return t+" "+(i[e]||"")}),"").toLowerCase().trim(),u.accentedSearch&&(a=pt(a),r=pt(r)),d=0==a.indexOf(r),c=a===r,f=a,s=r.toLowerCase().split(" ").every((function(t){return f.includes(t.toLowerCase())}))):(d=!0,s=m.some((function(t){var n=""+(i[t]||"");return u.accentedSearch&&(n=pt(n),r=pt(r)),u.caseSensitive||(n=n.toLowerCase()),c=n===r,e.exact?n===r:0==n.indexOf(r)}))),o=!u.includeSelectedTags&&n.isTagDuplicate(ut(i)?i.value:i),s&&!o&&(c&&d?g.push(i):"startsWith"==u.sortby&&d?h.unshift(i):h.push(i))},d=this,c=this.settings,u=c.dropdown,h=(e=e||{},[]),g=[],p=c.whitelist,f=u.maxItems>=0?u.maxItems:1/0,m=u.includeSelectedTags,v="function"==typeof u.sortby,w=u.searchKeys,y=0;if(!(t="select"==c.mode&&this.value.length&&this.value[0][c.tagTextProp]==t?"":t)||!w.length){h=m?p:p.filter((function(t){return!d.isTagDuplicate(ut(t)?t.value:t)}));var b=v?u.sortby(h,r):h.slice(0,f);return this.state.dropdown.suggestions=b,b}for(r=u.caseSensitive?""+t:(""+t).toLowerCase();y[\r\n ]+\<").split(/>\s+<").trim():""},fillHeaderFooter:function(){var t=this.dropdown.filterListItems(this.state.dropdown.query),e=this.parseTemplate("dropdownHeader",[t]),n=this.parseTemplate("dropdownFooter",[t]),i=this.dropdown.getHeaderRef(),s=this.dropdown.getFooterRef();e&&(null==i||i.parentNode.replaceChild(e,i)),n&&(null==s||s.parentNode.replaceChild(n,s))},position:function(t){var e=this.settings.dropdown,n=this.dropdown.getAppendTarget();if("manual"!=e.position&&n){var i,s,a,o,r,l,d,c,u,h,g=this.DOM.dropdown,p=e.RTL,f=n===document.body,m=n===this.DOM.scope,v=f?window.pageYOffset:n.scrollTop,w=document.fullscreenElement||document.webkitFullscreenElement||document.documentElement,y=w.clientHeight,b=Math.max(w.clientWidth||0,window.innerWidth||0),T=b>480?e.position:"all",S=this.DOM["input"==T?"input":"scope"];if(t=t||g.clientHeight,this.state.dropdown.visible){if("text"==T?(a=(i=function(){var t=document.getSelection();if(t.rangeCount){var e,n,i=t.getRangeAt(0),s=i.startContainer,a=i.startOffset;if(a>0)return(n=document.createRange()).setStart(s,a-1),n.setEnd(s,a),{left:(e=n.getBoundingClientRect()).right,top:e.top,bottom:e.bottom};if(s.getBoundingClientRect)return s.getBoundingClientRect()}return{left:-9999,top:-9999}}()).bottom,s=i.top,o=i.left,r="auto"):(l=function(t){var e=0,n=0;for(t=t.parentNode;t&&t!=w;)e+=t.offsetTop||0,n+=t.offsetLeft||0,t=t.parentNode;return{top:e,left:n}}(n),i=S.getBoundingClientRect(),s=m?-1:i.top-l.top,a=(m?i.height:i.bottom-l.top)-1,o=m?-1:i.left-l.left,r=i.width+"px"),!f){var x=function(){for(var t=0,n=e.appendTarget.parentNode;n;)t+=n.scrollTop||0,n=n.parentNode;return t}();s+=x,a+=x}var E;s=Math.floor(s),a=Math.ceil(a),c=b-o<120,u=((d=null!==(E=e.placeAbove)&&void 0!==E?E:y-i.bottom\n ').concat(this.settings.templates.input.call(this),"\n ").concat(et,"\n ")},input:function(){var t=this.settings,e=t.placeholder||et;return"')},tag:function(t,e){var n=e.settings;return'\n \n
\n ').concat(t[n.tagTextProp]||t.value,"\n
\n
")},dropdown:function(t){var e=t.dropdown,n="manual"==e.position;return'
\n
\n
')},dropdownContent:function(t){var e=this.settings.templates,n=this.state.dropdown.suggestions;return"\n ".concat(e.dropdownHeader.call(this,n),"\n ").concat(t,"\n ").concat(e.dropdownFooter.call(this,n),"\n ")},dropdownItem:function(t){return"
').concat(t.mappedValue||t.value,"
")},dropdownHeader:function(t){return"
')},dropdownFooter:function(t){var e=t.length-this.settings.dropdown.maxItems;return e>0?"
\n ').concat(e," more items. Refine your search.\n
"):""},dropdownItemNoMatch:null};function jt(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,i=new Array(e);nt.length)&&(e=t.length);for(var n=0,i=new Array(e);n0&&void 0!==arguments[0])||arguments[0],n=this.settings,i=this.events.callbacks,s=e?"addEventListener":"removeEventListener";if(!this.state.mainEvents||!e){for(var a in this.state.mainEvents=e,e&&!this.listeners.main&&(this.events.bindGlobal.call(this),this.settings.isJQueryPlugin&&jQuery(this.DOM.originalInput).on("tagify.removeAllTags",this.removeAllTags.bind(this))),t=this.listeners.main=this.listeners.main||{keydown:["input",i.onKeydown.bind(this)],click:["scope",i.onClickScope.bind(this)],dblclick:"select"!=n.mode&&["scope",i.onDoubleClickScope.bind(this)],paste:["input",i.onPaste.bind(this)],drop:["input",i.onDrop.bind(this)],compositionstart:["input",i.onCompositionStart.bind(this)],compositionend:["input",i.onCompositionEnd.bind(this)]})t[a]&&this.DOM[t[a][0]][s](a,t[a][1]);var o=this.listeners.main.inputMutationObserver||new MutationObserver(i.onInputDOMChange.bind(this));o.disconnect(),"mix"==n.mode&&o.observe(this.DOM.input,{childList:!0}),this.events.bindOriginaInputListener.call(this)}},bindOriginaInputListener:function(t){var e=(t||0)+500;this.listeners.main&&(clearInterval(this.listeners.main.originalInputValueObserverInterval),this.listeners.main.originalInputValueObserverInterval=setInterval(this.events.callbacks.observeOriginalInputValue.bind(this),e))},bindGlobal:function(t){var e,n=this.events.callbacks,i=t?"removeEventListener":"addEventListener";if(this.listeners&&(t||!this.listeners.global)){this.listeners.global=this.listeners.global||[{type:this.isIE?"keydown":"input",target:this.DOM.input,cb:n[this.isIE?"onInputIE":"onInput"].bind(this)},{type:"keydown",target:window,cb:n.onWindowKeyDown.bind(this)},{type:"focusin",target:this.DOM.scope,cb:n.onFocusBlur.bind(this)},{type:"focusout",target:this.DOM.scope,cb:n.onFocusBlur.bind(this)},{type:"click",target:document,cb:n.onClickAnywhere.bind(this),useCapture:!0}];var s=!0,a=!1,o=void 0;try{for(var r,l=this.listeners.global[Symbol.iterator]();!(s=(r=l.next()).done);s=!0)(e=r.value).target[i](e.type,e.cb,!!e.useCapture)}catch(t){a=!0,o=t}finally{try{s||null==l.return||l.return()}finally{if(a)throw o}}}},unbindGlobal:function(){this.events.bindGlobal.call(this,!0)},callbacks:{onFocusBlur:function(t){var e,n,i=this.settings,s=wt.call(this,t.relatedTarget),a=vt.call(this,t.relatedTarget),o=t.target.classList.contains(i.classNames.tagX),r="focusin"==t.type,l="focusout"==t.type;o&&"mix"!=i.mode&&this.DOM.input.focus(),s&&r&&!a&&!o&&this.toggleFocusClass(this.state.hasFocus=+new Date);var d=t.target?this.trim(this.DOM.input.textContent):"",c=null===(n=this.value)||void 0===n||null===(e=n[0])||void 0===e?void 0:e[i.tagTextProp],u=i.dropdown.enabled>=0,h={relatedTarget:t.relatedTarget},g=this.state.actions.selectOption&&(u||!i.dropdown.closeOnSelect),p=this.state.actions.addNew&&u;if(l){if(t.relatedTarget===this.DOM.scope)return this.dropdown.hide(),void this.DOM.input.focus();this.postUpdate(),i.onChangeAfterBlur&&this.triggerChangeEvent()}if(!(g||p||o))if(this.state.hasFocus=!(!r&&!s)&&+new Date,this.toggleFocusClass(this.state.hasFocus),"mix"!=i.mode){if(r){if(!i.focusable)return;var f=0===i.dropdown.enabled&&!this.state.dropdown.visible,m=!a||"select"===i.mode,v=this.DOM.scope.querySelector(this.settings.classNames.tagTextSelector);return this.trigger("focus",h),void(f&&m&&(this.dropdown.show(this.value.length?"":void 0),this.setRangeAtStartEnd(!1,v)))}if(l){if(this.trigger("blur",h),this.loading(!1),"select"==i.mode){if(this.value.length){var w=this.getTagElms()[0];d=this.trim(w.textContent)}c===d&&(d="")}d&&!this.state.actions.selectOption&&i.addTagOnBlur&&i.addTagOn.includes("blur")&&this.addTags(d,!0)}s||(this.DOM.input.removeAttribute("style"),this.dropdown.hide())}else r?this.trigger("focus",h):l&&(this.trigger("blur",h),this.loading(!1),this.dropdown.hide(),this.state.dropdown.visible=void 0,this.setStateSelection())},onCompositionStart:function(t){this.state.composing=!0},onCompositionEnd:function(t){this.state.composing=!1},onWindowKeyDown:function(t){var e,n=this.settings,i=document.activeElement,s=wt.call(this,i)&&this.DOM.scope.contains(i),a=i===this.DOM.input,o=s&&i.hasAttribute("readonly"),r=this.DOM.scope.querySelector(this.settings.classNames.tagTextSelector),l=this.state.dropdown.visible;if(("Tab"===t.key&&l||this.state.hasFocus||s&&!o)&&!a){e=i.nextElementSibling;var d=t.target.classList.contains(n.classNames.tagX);switch(t.key){case"Backspace":n.readonly||this.state.editing||(this.removeTags(i),(e||this.DOM.input).focus());break;case"Enter":if(d)return void this.removeTags(t.target.parentNode);n.a11y.focusableTags&&vt.call(this,i)&&setTimeout(this.editTag.bind(this),0,i);break;case"ArrowDown":this.state.dropdown.visible||"mix"==n.mode||this.dropdown.show();break;case"Tab":null==r||r.focus()}}},onKeydown:function(t){var e=this,n=this.settings;if(!this.state.composing&&n.userInput){"select"==n.mode&&n.enforceWhitelist&&this.value.length&&"Tab"!=t.key&&t.preventDefault();var i=this.trim(t.target.textContent);this.trigger("keydown",{event:t}),n.hooks.beforeKeyDown(t,{tagify:this}).then((function(s){if("mix"==n.mode){switch(t.key){case"Left":case"ArrowLeft":e.state.actions.ArrowLeft=!0;break;case"Delete":case"Backspace":if(e.state.editing)return;var a=document.getSelection(),o="Delete"==t.key&&a.anchorOffset==(a.anchorNode.length||0),r=a.anchorNode.previousSibling,l=1==a.anchorNode.nodeType||!a.anchorOffset&&r&&1==r.nodeType&&a.anchorNode.previousSibling;!function(t){var e=document.createElement("div");t.replace(/\&#?[0-9a-z]+;/gi,(function(t){return e.innerHTML=t,e.innerText}))}(e.DOM.input.innerHTML);var d,c,u,h=e.getTagElms(),g=1===a.anchorNode.length&&a.anchorNode.nodeValue==String.fromCharCode(8203);if("edit"==n.backspace&&l)return d=1==a.anchorNode.nodeType?null:a.anchorNode.previousElementSibling,setTimeout(e.editTag.bind(e),0,d),void t.preventDefault();if(ft()&&qt(l,Element))return u=dt(l),l.hasAttribute("readonly")||l.remove(),e.DOM.input.focus(),void setTimeout((function(){St(u),e.DOM.input.click()}));if("BR"==a.anchorNode.nodeName)return;if((o||l)&&1==a.anchorNode.nodeType?c=0==a.anchorOffset?o?h[0]:null:h[Math.min(h.length,a.anchorOffset)-1]:o?c=a.anchorNode.nextElementSibling:qt(l,Element)&&(c=l),3==a.anchorNode.nodeType&&!a.anchorNode.nodeValue&&a.anchorNode.previousElementSibling&&t.preventDefault(),(l||o)&&!n.backspace)return void t.preventDefault();if("Range"!=a.type&&!a.anchorOffset&&a.anchorNode==e.DOM.input&&"Delete"!=t.key)return void t.preventDefault();if("Range"!=a.type&&c&&c.hasAttribute("readonly"))return void St(dt(c));"Delete"==t.key&&g&&Tt(a.anchorNode.nextSibling)&&e.removeTags(a.anchorNode.nextSibling)}return!0}var p="manual"==n.dropdown.position;switch(t.key){case"Backspace":"select"==n.mode&&n.enforceWhitelist&&e.value.length?e.removeTags():e.state.dropdown.visible&&"manual"!=n.dropdown.position||""!=t.target.textContent&&8203!=i.charCodeAt(0)||(!0===n.backspace?e.removeTags():"edit"==n.backspace&&setTimeout(e.editTag.bind(e),0));break;case"Esc":case"Escape":if(e.state.dropdown.visible)return;t.target.blur();break;case"Down":case"ArrowDown":e.state.dropdown.visible||e.dropdown.show();break;case"ArrowRight":var f=e.state.inputSuggestion||e.state.ddItemData;if(f&&n.autoComplete.rightKey)return void e.addTags([f],!0);break;case"Tab":return!0;case"Enter":if(e.state.dropdown.visible&&!p)return;t.preventDefault();var m=e.state.autoCompleteData||i;setTimeout((function(){e.state.dropdown.visible&&!p||e.state.actions.selectOption||!n.addTagOn.includes(t.key.toLowerCase())||(e.addTags([m],!0),e.state.autoCompleteData=null)}))}})).catch((function(t){return t}))}},onInput:function(t){this.postUpdate();var e=this.settings;if("mix"==e.mode)return this.events.callbacks.onMixTagsInput.call(this,t);var n=this.input.normalize.call(this,void 0,{trim:!1}),i=n.length>=e.dropdown.enabled,s={value:n,inputElm:this.DOM.input},a=this.validateTag({value:n});"select"==e.mode&&this.toggleScopeValidation(a),s.isValid=a,this.state.inputText!=n&&(this.input.set.call(this,n,!1),-1!=n.search(e.delimiters)?this.addTags(n)&&this.input.set.call(this):e.dropdown.enabled>=0&&this.dropdown[i?"show":"hide"](n),this.trigger("input",s))},onMixTagsInput:function(t){var e,n,i,s,a,o,r,l,d=this,c=this.settings,u=this.value.length,h=this.getTagElms(),g=document.createDocumentFragment(),p=window.getSelection().getRangeAt(0),f=[].map.call(h,(function(t){return Tt(t).value}));if("deleteContentBackward"==t.inputType&&ft()&&this.events.callbacks.onKeydown.call(this,{target:t.target,key:"Backspace"}),xt(this.getTagElms()),this.value.slice().forEach((function(t){t.readonly&&!f.includes(t.value)&&g.appendChild(d.createTagElem(t))})),g.childNodes.length&&(p.insertNode(g),this.setRangeAtStartEnd(!1,g.lastChild)),h.length!=u)return this.value=[].map.call(this.getTagElms(),(function(t){return Tt(t)})),void this.update({withoutChangeEvent:!0});if(this.hasMaxTags())return!0;if(window.getSelection&&(o=window.getSelection()).rangeCount>0&&3==o.anchorNode.nodeType){if((p=o.getRangeAt(0).cloneRange()).collapse(!0),p.setStart(o.focusNode,0),i=(e=p.toString().slice(0,p.endOffset)).split(c.pattern).length-1,(n=e.match(c.pattern))&&(s=e.slice(e.lastIndexOf(n[n.length-1]))),s){if(this.state.actions.ArrowLeft=!1,this.state.tag={prefix:s.match(c.pattern)[0],value:s.replace(c.pattern,"")},this.state.tag.baseOffset=o.baseOffset-this.state.tag.value.length,l=this.state.tag.value.match(c.delimiters))return this.state.tag.value=this.state.tag.value.replace(c.delimiters,""),this.state.tag.delimiters=l[0],this.addTags(this.state.tag.value,c.dropdown.clearOnSelect),void this.dropdown.hide();a=this.state.tag.value.length>=c.dropdown.enabled;try{r=(r=this.state.flaggedTags[this.state.tag.baseOffset]).prefix==this.state.tag.prefix&&r.value[0]==this.state.tag.value[0],this.state.flaggedTags[this.state.tag.baseOffset]&&!this.state.tag.value&&delete this.state.flaggedTags[this.state.tag.baseOffset]}catch(t){}(r||i500||!e.focusable)?this.state.dropdown.visible?this.dropdown.hide():0===e.dropdown.enabled&&"mix"!=e.mode&&this.dropdown.show(this.value.length?"":void 0):"select"!=e.mode||0!==e.dropdown.enabled||this.state.dropdown.visible||(this.events.callbacks.onDoubleClickScope.call(this,function(t,e){return e=null!=e?e:{},Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(e)):function(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);n.push.apply(n,i)}return n}(Object(e)).forEach((function(n){Object.defineProperty(t,n,Object.getOwnPropertyDescriptor(e,n))})),t}(function(t){for(var e=1;e=this.settings.dropdown.enabled&&(this.state.editing&&(this.state.editing.value=o),this.dropdown.show(o)),this.trigger("edit:input",{tag:i,index:s,data:ht({},this.value[s],{newValue:o}),event:e})},onEditTagPaste:function(t,e){var n=(e.clipboardData||window.clipboardData).getData("Text");e.preventDefault();var i=bt(n);this.setRangeAtStartEnd(!1,i)},onEditTagClick:function(t,e){this.events.callbacks.onClickScope.call(this,e)},onEditTagFocus:function(t){this.state.editing={scope:t,input:t.querySelector("[contenteditable]")}},onEditTagBlur:function(t,e){var n=vt.call(this,e.relatedTarget);if("select"==this.settings.mode&&n&&e.relatedTarget.contains(e.target))this.dropdown.hide();else if(this.state.editing&&(this.state.hasFocus||this.toggleFocusClass(),this.DOM.scope.contains(document.activeElement)||this.trigger("blur",{}),this.DOM.scope.contains(t))){var i,s,a,o=this.settings,r=t.closest("."+o.classNames.tag),l=Tt(r),d=this.input.normalize.call(this,t),c=(Ht(i={},o.tagTextProp,d),Ht(i,"__tagId",l.__tagId),i),u=l.__originalData,h=this.editTagChangeDetected(ht(l,c)),g=this.validateTag(c);if(d)if(h){var p;if(s=this.hasMaxTags(),a=ht({},u,(Ht(p={},o.tagTextProp,this.trim(d)),Ht(p,"__isValid",g),p)),o.transformTag.call(this,a,u),!0!==(g=(!s||!0===u.__isValid)&&this.validateTag(a))){if(this.trigger("invalid",{data:a,tag:r,message:g}),o.editTags.keepInvalid)return;o.keepInvalidTags?a.__isValid=g:a=u}else o.keepInvalidTags&&(delete a.title,delete a["aria-invalid"],delete a.class);this.onEditTagDone(r,a)}else this.onEditTagDone(r,u);else this.onEditTagDone(r)}},onEditTagkeydown:function(t,e){if(!this.state.composing)switch(this.trigger("edit:keydown",{event:t}),t.key){case"Esc":case"Escape":this.state.editing=!1,e.__tagifyTagData.__originalData.value?e.parentNode.replaceChild(e.__tagifyTagData.__originalHTML,e):e.remove();break;case"Enter":case"Tab":t.preventDefault(),setTimeout((function(){return t.target.blur()}),0)}},onDoubleClickScope:function(t){var e=t.target.closest("."+this.settings.classNames.tag);if(e){var n,i,s=Tt(e),a=this.settings;!1!==(null==s?void 0:s.editable)&&(n=e.classList.contains(this.settings.classNames.tagEditing),i=e.hasAttribute("readonly"),a.readonly||n||i||!this.settings.editTags||!a.userInput||(this.events.callbacks.onEditTagFocus.call(this,e),this.editTag(e)),this.toggleFocusClass(!0),"select"!=a.mode&&this.trigger("dblclick",{tag:e,index:this.getNodeIndex(e),data:Tt(e)}))}},onInputDOMChange:function(t){var e=this;t.forEach((function(t){t.addedNodes.forEach((function(t){if("

"==t.outerHTML)t.replaceWith(document.createElement("br"));else if(1==t.nodeType&&t.querySelector(e.settings.classNames.tagSelector)){var n,i=document.createTextNode("");3==t.childNodes[0].nodeType&&"BR"!=t.previousSibling.nodeName&&(i=document.createTextNode("\n")),(n=t).replaceWith.apply(n,Rt([i].concat(Rt(Rt(t.childNodes).slice(0,-1))))),St(i)}else if(vt.call(e,t)){var s;if(3!=(null===(s=t.previousSibling)||void 0===s?void 0:s.nodeType)||t.previousSibling.textContent||t.previousSibling.remove(),t.previousSibling&&"BR"==t.previousSibling.nodeName){t.previousSibling.replaceWith("\n​");for(var a=t.nextSibling,o="";a;)o+=a.textContent,a=a.nextSibling;o.trim()&&St(t.previousSibling)}else t.previousSibling&&!Tt(t.previousSibling)||t.before("​")}})),t.removedNodes.forEach((function(t){t&&"BR"==t.nodeName&&vt.call(e,n)&&(e.removeTags(n),e.fixFirefoxLastTagNoCaret())}))}));var n=this.DOM.input.lastChild;n&&""==n.nodeValue&&n.remove(),n&&"BR"==n.nodeName||this.DOM.input.appendChild(document.createElement("br"))}}};function Ut(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,i=new Array(e);nt.map((t=>t.value)),dropdown:{enabled:e.dropdownSuggestionsStartAfter,maxItems:e.dropdownMaxItems,closeOnSelect:e.dropdownCloseOnSelect,highlightFirst:e.highlight},transformTag(t){t.display||(t.display=t.value,t.value=encodeURIComponent(t.value)),t.display=t.display.replace(//g,">")},templates:{wrapper(t,e){return`
\n ${this.settings.templates.input.call(this)}\n ​\n
`},tag:t=>`
\n \n
\n ${t.display}\n
\n
`,dropdownItem:t=>`
\n ${t.display}\n
`}}}(e.id,n));o.addTags(i),void 0!==s&&o.on("input",(t=>{!function(t,e,n,i,s,a){Jt instanceof AbortController&&Jt.abort(),Jt=new AbortController,t.whitelist=null,void 0!==Qt&&(t.DOM.scope.ownerDocument.defaultView.clearTimeout(Qt),Qt=void 0),s.detail.value.length{const e=s.detail.value;n.writeParameter(i,e),t.loading(!0),fetch(n.getUrl().toString(),{signal:Jt.signal}).then((t=>t.json())).catch((()=>{})).then((n=>{t.whitelist=n,t.loading(!1).dropdown.show(e)}))}),a))}(o,n.suggestionStarts,s,a,t,n.autocompleteTriggerTimeout)}))}Gt.prototype={_dropdown:Lt,placeCaretAfterNode:St,getSetTagData:Tt,helpers:{sameStr:at,removeCollectionProp:ot,omit:rt,isObject:ut,parseHTML:lt,escapeHTML:ct,extend:ht,concatWithoutDups:gt,getUID:mt,isNodeTag:vt},customEventsList:["change","add","remove","invalid","input","paste","click","keydown","focus","blur","edit:input","edit:beforeUpdate","edit:updated","edit:start","edit:keydown","dropdown:show","dropdown:hide","dropdown:select","dropdown:updated","dropdown:noMatch","dropdown:scroll"],dataProps:["__isValid","__removed","__originalData","__originalHTML","__tagId"],trim:function(t){return this.settings.trim&&t&&"string"==typeof t?t.trim():t},parseHTML:lt,templates:Bt,parseTemplate:function(t,e){return lt((t=this.settings.templates[t]||t).apply(this,e))},set whitelist(t){var e=t&&Array.isArray(t);this.settings.whitelist=e?t:[],this.setPersistedData(e?t:[],"whitelist")},get whitelist(){return this.settings.whitelist},set userInput(t){this.settings.userInput=!!t,this.setContentEditable(!!t)},get userInput(){return this.settings.userInput},generateClassSelectors:function(t){var e=function(e){var n=e;Object.defineProperty(t,n+"Selector",{get:function(){return"."+this[n].split(" ")[0]}})};for(var n in t)e(n)},applySettings:function(t,e){var n,i;Et.templates=this.templates;var s=ht({},Et,"mix"==e.mode?{dropdown:{position:"text"}}:{}),a=this.settings=ht({},s,e);if(a.disabled=t.hasAttribute("disabled"),a.readonly=a.readonly||t.hasAttribute("readonly"),a.placeholder=ct(t.getAttribute("placeholder")||a.placeholder||""),a.required=t.hasAttribute("required"),this.generateClassSelectors(a.classNames),this.isIE&&(a.autoComplete=!1),["whitelist","blacklist"].forEach((function(e){var n=t.getAttribute("data-"+e);n&&zt(n=n.split(a.delimiters),Array)&&(a[e]=n)})),"autoComplete"in e&&!ut(e.autoComplete)&&(a.autoComplete=Et.autoComplete,a.autoComplete.enabled=e.autoComplete),"mix"==a.mode&&(a.pattern=a.pattern||/@/,a.autoComplete.rightKey=!0,a.delimiters=e.delimiters||null,a.tagTextProp&&!a.dropdown.searchKeys.includes(a.tagTextProp)&&a.dropdown.searchKeys.push(a.tagTextProp)),t.pattern)try{a.pattern=new RegExp(t.pattern)}catch(t){}if(a.delimiters){a._delimiters=a.delimiters;try{a.delimiters=new RegExp(this.settings.delimiters,"g")}catch(t){}}a.disabled&&(a.userInput=!1),this.TEXTS=Kt({},Pt,a.texts||{}),"select"==a.mode&&(a.dropdown.includeSelectedTags=!0),("select"!=a.mode||(null===(n=e.dropdown)||void 0===n?void 0:n.enabled))&&a.userInput||(a.dropdown.enabled=0),a.dropdown.appendTarget=(null===(i=e.dropdown)||void 0===i?void 0:i.appendTarget)||document.body,void 0===a.dropdown.includeSelectedTags&&(a.dropdown.includeSelectedTags=a.duplicates);var o=this.getPersistedData("whitelist");Array.isArray(o)&&(this.whitelist=Array.isArray(a.whitelist)?gt(a.whitelist,o):o)},getAttributes:function(t){var e,n=this.getCustomAttributes(t),i="";for(e in n)i+=" "+e+(void 0!==t[e]?'="'.concat(n[e],'"'):"");return i},getCustomAttributes:function(t){if(!ut(t))return"";var e,n={};for(e in t)"__"!=e.slice(0,2)&&"class"!=e&&t.hasOwnProperty(e)&&void 0!==t[e]&&(n[e]=ct(t[e]));return n},setStateSelection:function(){var t=window.getSelection(),e={anchorOffset:t.anchorOffset,anchorNode:t.anchorNode,range:t.getRangeAt&&t.rangeCount&&t.getRangeAt(0)};return this.state.selection=e,e},getCSSVars:function(){var t,e,n=getComputedStyle(this.DOM.scope,null);this.CSSVars={tagHideTransition:(t=function(t){if(!t)return{};var e=(t=t.trim().split(" ")[0]).split(/\d+/g).filter((function(t){return t})).pop().trim();return{value:+t.split(e).filter((function(t){return t}))[0].trim(),unit:e}}(("tag-hide-transition",n.getPropertyValue("--tag-hide-transition"))),e=t.value,"s"==t.unit?1e3*e:e)}},build:function(t){var e=this.DOM,n=t.closest("label");this.settings.mixMode.integrated?(e.originalInput=null,e.scope=t,e.input=t):(e.originalInput=t,e.originalInput_tabIndex=t.tabIndex,e.scope=this.parseTemplate("wrapper",[t,this.settings]),e.input=e.scope.querySelector(this.settings.classNames.inputSelector),t.parentNode.insertBefore(e.scope,t),t.tabIndex=-1),n&&n.setAttribute("for","")},destroy:function(){var t;this.events.unbindGlobal.call(this),null===(t=this.DOM.scope.parentNode)||void 0===t||t.removeChild(this.DOM.scope),this.DOM.originalInput.tabIndex=this.DOM.originalInput_tabIndex,delete this.DOM.originalInput.__tagify,this.dropdown.hide(!0),this.removeAllCustomListeners(),clearTimeout(this.dropdownHide__bindEventsTimeout),clearInterval(this.listeners.main.originalInputValueObserverInterval)},loadOriginalValues:function(t){var e,n=this.settings;if(this.state.blockChangeEvent=!0,void 0===t){var i=this.getPersistedData("value");t=i&&!this.DOM.originalInput.value?i:n.mixMode.integrated?this.DOM.input.textContent:this.DOM.originalInput.value}if(this.removeAllTags(),t)if("mix"==n.mode)this.parseMixTags(t),(e=this.DOM.input.lastChild)&&"BR"==e.tagName||this.DOM.input.insertAdjacentHTML("beforeend","
");else{try{zt(JSON.parse(t),Array)&&(t=JSON.parse(t))}catch(t){}this.addTags(t,!0).forEach((function(t){return t&&t.classList.add(n.classNames.tagNoAnimation)}))}else this.postUpdate();this.state.lastOriginalValueReported=n.mixMode.integrated?"":this.DOM.originalInput.value},cloneEvent:function(t){var e={};for(var n in t)"path"!=n&&(e[n]=t[n]);return e},loading:function(t){return this.state.isLoading=t,this.DOM.scope.classList[t?"add":"remove"](this.settings.classNames.scopeLoading),this},tagLoading:function(t,e){return t&&t.classList[e?"add":"remove"](this.settings.classNames.tagLoading),this},toggleClass:function(t,e){"string"==typeof t&&this.DOM.scope.classList.toggle(t,e)},toggleScopeValidation:function(t){var e=!0===t||void 0===t;!this.settings.required&&t&&t===this.TEXTS.empty&&(e=!0),this.toggleClass(this.settings.classNames.tagInvalid,!e),this.DOM.scope.title=e?"":t},toggleFocusClass:function(t){this.toggleClass(this.settings.classNames.focus,!!t)},setPlaceholder:function(t){var e=this;["data","aria"].forEach((function(n){return e.DOM.input.setAttribute("".concat(n,"-placeholder"),t)}))},triggerChangeEvent:function(){if(!this.settings.mixMode.integrated){var t=this.DOM.originalInput,e=this.state.lastOriginalValueReported!==t.value,n=new CustomEvent("change",{bubbles:!0});e&&(this.state.lastOriginalValueReported=t.value,n.simulated=!0,t._valueTracker&&t._valueTracker.setValue(Math.random()),t.dispatchEvent(n),this.trigger("change",this.state.lastOriginalValueReported),t.value=this.state.lastOriginalValueReported)}},events:$t,fixFirefoxLastTagNoCaret:function(){},setRangeAtStartEnd:function(t,e){if(e){t="number"==typeof t?t:!!t,e=e.lastChild||e;var n=document.getSelection();if(zt(n.focusNode,Element)&&!this.DOM.input.contains(n.focusNode))return!0;try{n.rangeCount>=1&&["Start","End"].forEach((function(i){return n.getRangeAt(0)["set"+i](e,t||e.length)}))}catch(t){console.warn(t)}}},insertAfterTag:function(t,e){if(e=e||this.settings.mixMode.insertAfterTag,t&&t.parentNode&&e)return e="string"==typeof e?document.createTextNode(e):e,t.parentNode.insertBefore(e,t.nextSibling),e},editTagChangeDetected:function(t){var e=t.__originalData;for(var n in e)if(!this.dataProps.includes(n)&&t[n]!=e[n])return!0;return!1},getTagTextNode:function(t){return t.querySelector(this.settings.classNames.tagTextSelector)},setTagTextNode:function(t,e){this.getTagTextNode(t).innerHTML=ct(e)},editTag:function(t,e){var n=this;t=t||this.getLastTag(),e=e||{};var i=this.settings,s=this.getTagTextNode(t),a=this.getNodeIndex(t),o=Tt(t),r=this.events.callbacks,l=!0,d="select"==i.mode;if(!d&&this.dropdown.hide(),s){if(!zt(o,Object)||!("editable"in o)||o.editable)return o=Tt(t,{__originalData:ht({},o),__originalHTML:t.cloneNode(!0)}),Tt(o.__originalHTML,o.__originalData),s.setAttribute("contenteditable",!0),t.classList.add(i.classNames.tagEditing),this.events.callbacks.onEditTagFocus.call(this,t),s.addEventListener("click",r.onEditTagClick.bind(this,t)),s.addEventListener("blur",r.onEditTagBlur.bind(this,this.getTagTextNode(t))),s.addEventListener("input",r.onEditTagInput.bind(this,s)),s.addEventListener("paste",r.onEditTagPaste.bind(this,s)),s.addEventListener("keydown",(function(e){return r.onEditTagkeydown.call(n,e,t)})),s.addEventListener("compositionstart",r.onCompositionStart.bind(this)),s.addEventListener("compositionend",r.onCompositionEnd.bind(this)),e.skipValidation||(l=this.editTagToggleValidity(t)),s.originalIsValid=l,this.trigger("edit:start",{tag:t,index:a,data:o,isValid:l}),s.focus(),!d&&this.setRangeAtStartEnd(!1,s),0===i.dropdown.enabled&&!d&&this.dropdown.show(),this.state.hasFocus=!0,this}else st.warn("Cannot find element in Tag template: .",i.classNames.tagTextSelector)},editTagToggleValidity:function(t,e){var n;if(e=e||Tt(t))return(n=!("__isValid"in e)||!0===e.__isValid)||this.removeTagsFromValue(t),this.update(),t.classList.toggle(this.settings.classNames.tagNotAllowed,!n),e.__isValid=n,e.__isValid;st.warn("tag has no data: ",t,e)},onEditTagDone:function(t,e){t=t||this.state.editing.scope,e=e||{};var n,i,s=this.settings,a={tag:t,index:this.getNodeIndex(t),previousData:Tt(t),data:e};this.trigger("edit:beforeUpdate",a,{cloneData:!1}),this.state.editing=!1,delete e.__originalData,delete e.__originalHTML,t&&t.parentNode&&((void 0!==(i=e[s.tagTextProp])?null===(n=(i+="").trim)||void 0===n?void 0:n.call(i):s.tagTextProp in e?void 0:e.value)?(t=this.replaceTag(t,e),this.editTagToggleValidity(t,e),s.a11y.focusableTags?t.focus():"select"!=s.mode&&St(t)):this.removeTags(t)),this.trigger("edit:updated",a),s.dropdown.closeOnSelect&&this.dropdown.hide(),this.settings.keepInvalidTags&&this.reCheckInvalidTags()},replaceTag:function(t,e){e&&""!==e.value&&void 0!==e.value||(e=t.__tagifyTagData),e.__isValid&&1!=e.__isValid&&ht(e,this.getInvalidTagAttrs(e,e.__isValid));var n=this.createTagElem(e);return t.parentNode.replaceChild(n,t),this.updateValueByDOMTags(),n},updateValueByDOMTags:function(){var t=this;this.value.length=0;var e=this.settings.classNames,n=[e.tagNotAllowed.split(" ")[0],e.tagHide];[].forEach.call(this.getTagElms(),(function(e){Xt(e.classList).some((function(t){return n.includes(t)}))||t.value.push(Tt(e))})),this.update(),this.dropdown.refilter()},injectAtCaret:function(t,e){var n;if(e=e||(null===(n=this.state.selection)||void 0===n?void 0:n.range),"string"==typeof t&&(t=document.createTextNode(t)),!e&&t)return this.appendMixTags(t),this;var i=bt(t,e);return this.setRangeAtStartEnd(!1,i),this.updateValueByDOMTags(),this.update(),this},input:{set:function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",e=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],n=this.settings,i=n.dropdown.closeOnSelect;this.state.inputText=t,e&&(this.DOM.input.innerHTML=ct(""+t),t&&this.toggleClass(n.classNames.empty,!this.DOM.input.innerHTML)),!t&&i&&this.dropdown.hide.bind(this),this.input.autocomplete.suggest.call(this),this.input.validate.call(this)},raw:function(){return this.DOM.input.textContent},validate:function(){var t=!this.state.inputText||!0===this.validateTag({value:this.state.inputText});return this.DOM.input.classList.toggle(this.settings.classNames.inputInvalid,!t),t},normalize:function(t,e){var n=t||this.DOM.input,i=[];n.childNodes.forEach((function(t){return 3==t.nodeType&&i.push(t.nodeValue)})),i=i.join("\n");try{i=i.replace(/(?:\r\n|\r|\n)/g,this.settings.delimiters.source.charAt(0))}catch(t){}return i=i.replace(/\s/g," "),(null==e?void 0:e.trim)?this.trim(i):i},autocomplete:{suggest:function(t){if(this.settings.autoComplete.enabled){"object"!=typeof(t=t||{value:""})&&(t={value:t});var e=this.dropdown.getMappedValue(t);if("number"!=typeof e){var n=this.state.inputText.toLowerCase(),i=e.substr(0,this.state.inputText.length).toLowerCase(),s=e.substring(this.state.inputText.length);e&&this.state.inputText&&i==n?(this.DOM.input.setAttribute("data-suggest",s),this.state.inputSuggestion=t):(this.DOM.input.removeAttribute("data-suggest"),delete this.state.inputSuggestion)}}},set:function(t){var e=this.DOM.input.getAttribute("data-suggest"),n=t||(e?this.state.inputText+e:null);return!!n&&("mix"==this.settings.mode?this.replaceTextWithNode(document.createTextNode(this.state.tag.prefix+n)):(this.input.set.call(this,n),this.setRangeAtStartEnd(!1,this.DOM.input)),this.input.autocomplete.suggest.call(this),this.dropdown.hide(),!0)}}},getTagIdx:function(t){return this.value.findIndex((function(e){return e.__tagId==(t||{}).__tagId}))},getNodeIndex:function(t){var e=0;if(t)for(;t=t.previousElementSibling;)e++;return e},getTagElms:function(){for(var t=arguments.length,e=new Array(t),n=0;n=this.settings.maxTags&&this.TEXTS.exceed},setReadonly:function(t,e){var n=this.settings;this.DOM.scope.contains(document.activeElement)&&document.activeElement.blur(),n[e||"readonly"]=t,this.DOM.scope[(t?"set":"remove")+"Attribute"](e||"readonly",!0),this.settings.userInput=!0,this.setContentEditable(!t)},setContentEditable:function(t){this.DOM.input.contentEditable=t,this.DOM.input.tabIndex=t?0:-1},setDisabled:function(t){this.setReadonly(t,"disabled")},normalizeTags:function(t){var e=this,n=this.settings,i=n.whitelist,s=n.delimiters,a=n.mode,o=n.tagTextProp,r=[],l=!!i&&zt(i[0],Object),d=Array.isArray(t),c=d&&t[0].value,u=function(t){return(t+"").split(s).reduce((function(t,n){var i,s=e.trim(n);return s&&t.push((Wt(i={},o,s),Wt(i,"value",s),i)),t}),[])};if("number"==typeof t&&(t=t.toString()),"string"==typeof t){if(!t.trim())return[];t=u(t)}else d&&(t=t.reduce((function(t,n){if(ut(n)){var i=ht({},n);o in i||(o="value"),i[o]=e.trim(i[o]),(i[o]||0===i[o])&&t.push(i)}else if(null!=n&&""!==n&&void 0!==n){var s;(s=t).push.apply(s,Xt(u(n)))}return t}),[]));return l&&!c&&(t.forEach((function(t){var n=r.map((function(t){return t.value})),i=e.dropdown.filterListItems.call(e,t[o],{exact:!0});e.settings.duplicates||(i=i.filter((function(t){return!n.includes(t.value)})));var s=i.length>1?e.getWhitelistItem(t[o],o,i):i[0];s&&zt(s,Object)?r.push(s):"mix"!=a&&(null==t.value&&(t.value=t[o]),r.push(t))})),r.length&&(t=r)),t},parseMixTags:function(t){var e=this,n=this.settings,i=n.mixTagsInterpolator,s=n.duplicates,a=n.transformTag,o=n.enforceWhitelist,r=n.maxTags,l=n.tagTextProp,d=[];t=t.split(i[0]).map((function(t,n){var c,u,h,g=t.split(i[1]),p=g[0],f=d.length==r;try{if(p==+p)throw Error;u=JSON.parse(p)}catch(t){u=e.normalizeTags(p)[0]||{value:p}}if(a.call(e,u),f||!(g.length>1)||o&&!e.isTagWhitelisted(u.value)||!s&&e.isTagDuplicate(u.value)){if(t)return n?i[0]+t:t}else u[c=u[l]?l:"value"]=e.trim(u[c]),h=e.createTagElem(u),d.push(u),h.classList.add(e.settings.classNames.tagNoAnimation),g[0]=h.outerHTML,e.value.push(u);return g.join("")})).join(""),this.DOM.input.innerHTML=t,this.DOM.input.appendChild(document.createTextNode("")),this.DOM.input.normalize();var c=this.getTagElms();return c.forEach((function(t,e){return Tt(t,d[e])})),this.update({withoutChangeEvent:!0}),xt(c,this.state.hasFocus),t},replaceTextWithNode:function(t,e){if(this.state.tag||e){e=e||this.state.tag.prefix+this.state.tag.value;var n,i,s=this.state.selection||window.getSelection(),a=s.anchorNode,o=this.state.tag.delimiters?this.state.tag.delimiters.length:0;return a.splitText(s.anchorOffset-o),-1==(n=a.nodeValue.lastIndexOf(e))||(i=a.splitText(n),t&&a.parentNode.replaceChild(t,i)),!0}},prepareNewTagNode:function(t,e){e=e||{};var n=this.settings,i=[],s={},a=Object.assign({},t,{value:t.value+""});if(t=Object.assign({},a),n.transformTag.call(this,t),t.__isValid=this.hasMaxTags()||this.validateTag(t),!0!==t.__isValid){if(e.skipInvalid)return;if(ht(s,this.getInvalidTagAttrs(t,t.__isValid),{__preInvalidData:a}),t.__isValid==this.TEXTS.duplicate&&this.flashTag(this.getTagElmByValue(t.value)),!n.createInvalidTags)return void i.push(t.value)}return"readonly"in t&&(t.readonly?s["aria-readonly"]=!0:delete t.readonly),{tagElm:this.createTagElem(t,s),tagData:t,aggregatedInvalidInput:i}},postProcessNewTagNode:function(t,e){var n=this,i=this.settings,s=e.__isValid;s&&!0===s?this.value.push(e):(this.trigger("invalid",{data:e,index:this.value.length,tag:t,message:s}),i.keepInvalidTags||setTimeout((function(){return n.removeTags(t,!0)}),1e3)),this.dropdown.position()},selectTag:function(t,e){var n=this;if(!this.settings.enforceWhitelist||this.isTagWhitelisted(e.value)){this.state.actions.selectOption&&setTimeout((function(){return n.setRangeAtStartEnd(!1,n.DOM.input)}));var i=this.getLastTag();return i?this.replaceTag(i,e):this.appendTag(t),this.value[0]=e,this.update(),this.trigger("add",{tag:t,data:e}),[t]}},addEmptyTag:function(t){var e=ht({value:""},t||{}),n=this.createTagElem(e);Tt(n,e),this.appendTag(n),this.editTag(n,{skipValidation:!0}),this.toggleFocusClass(!0)},addTags:function(t,e,n){var i=this,s=[],a=this.settings,o=[],r=document.createDocumentFragment(),l=[];if(!t||0==t.length)return s;switch(t=this.normalizeTags(t),a.mode){case"mix":return this.addMixTags(t);case"select":e=!1,this.removeAllTags()}return this.DOM.input.removeAttribute("style"),t.forEach((function(t){var e=i.prepareNewTagNode(t,{skipInvalid:n||a.skipInvalid});if(e){var d=e.tagElm;if(t=e.tagData,o=e.aggregatedInvalidInput,s.push(d),"select"==a.mode)return i.selectTag(d,t);r.appendChild(d),i.postProcessNewTagNode(d,t),l.push({tagElm:d,tagData:t})}})),this.appendTag(r),l.forEach((function(t){var e=t.tagElm,n=t.tagData;return i.trigger("add",{tag:e,index:i.getTagIdx(n),data:n})})),this.update(),t.length&&e&&(this.input.set.call(this,a.createInvalidTags?"":o.join(a._delimiters)),this.setRangeAtStartEnd(!1,this.DOM.input)),this.dropdown.refilter(),s},addMixTags:function(t){var e=this;if((t=this.normalizeTags(t))[0].prefix||this.state.tag)return this.prefixedTextToTag(t[0]);var n=document.createDocumentFragment();return t.forEach((function(t){var i=e.prepareNewTagNode(t);n.appendChild(i.tagElm),e.insertAfterTag(i.tagElm),e.postProcessNewTagNode(i.tagElm,i.tagData)})),this.appendMixTags(n),n.children},appendMixTags:function(t){var e=!!this.state.selection;e?this.injectAtCaret(t):(this.DOM.input.focus(),(e=this.setStateSelection()).range.setStart(this.DOM.input,e.range.endOffset),e.range.setEnd(this.DOM.input,e.range.endOffset),this.DOM.input.appendChild(t),this.updateValueByDOMTags(),this.update())},prefixedTextToTag:function(t){var e,n,i,s=this,a=this.settings,o=null===(e=this.state.tag)||void 0===e?void 0:e.delimiters;if(t.prefix=t.prefix||this.state.tag?this.state.tag.prefix:(a.pattern.source||a.pattern)[0],i=this.prepareNewTagNode(t),n=i.tagElm,this.replaceTextWithNode(n)||this.DOM.input.appendChild(n),setTimeout((function(){return n.classList.add(s.settings.classNames.tagNoAnimation)}),300),this.update(),!o){var r=this.insertAfterTag(n)||n;setTimeout(St,0,r)}return this.state.tag=null,this.postProcessNewTagNode(n,i.tagData),n},appendTag:function(t){var e=this.DOM,n=e.input;e.scope.insertBefore(t,n)},createTagElem:function(t,e){t.__tagId=mt();var n,i=ht({},t,Kt({value:ct(t.value+"")},e));return function(t){for(var e,n=document.createNodeIterator(t,NodeFilter.SHOW_TEXT,null,!1);e=n.nextNode();)e.textContent.trim()||e.parentNode.removeChild(e)}(n=this.parseTemplate("tag",[i,this])),Tt(n,t),n},reCheckInvalidTags:function(){var t=this,e=this.settings;this.getTagElms(e.classNames.tagNotAllowed).forEach((function(n,i){var s=Tt(n),a=t.hasMaxTags(),o=t.validateTag(s),r=!0===o&&!a;if("select"==e.mode&&t.toggleScopeValidation(o),r)return s=s.__preInvalidData?s.__preInvalidData:{value:s.value},t.replaceTag(n,s);n.title=a||o}))},removeTags:function(t,e,n){var i,s=this,a=this.settings;if(t=t&&zt(t,HTMLElement)?[t]:zt(t,Array)?t:t?[t]:[this.getLastTag()].filter((function(t){return t})),i=t.reduce((function(t,e){e&&"string"==typeof e&&(e=s.getTagElmByValue(e));var n=Tt(e);return e&&n&&!n.readonly&&t.push({node:e,idx:s.getTagIdx(n),data:Tt(e,{__removed:!0})}),t}),[]),n="number"==typeof n?n:this.CSSVars.tagHideTransition,"select"==a.mode&&(n=0,this.input.set.call(this)),1==i.length&&"select"!=a.mode&&i[0].node.classList.contains(a.classNames.tagNotAllowed)&&(e=!0),i.length)return a.hooks.beforeRemoveTag(i,{tagify:this}).then((function(){var t=function(t){t.node.parentNode&&(t.node.parentNode.removeChild(t.node),e?a.keepInvalidTags&&this.trigger("remove",{tag:t.node,index:t.idx}):(this.trigger("remove",{tag:t.node,index:t.idx,data:t.data}),this.dropdown.refilter(),this.dropdown.position(),this.DOM.input.normalize(),a.keepInvalidTags&&this.reCheckInvalidTags()))};n&&n>10&&1==i.length?function(e){e.node.style.width=parseFloat(window.getComputedStyle(e.node).width)+"px",document.body.clientTop,e.node.classList.add(a.classNames.tagHide),setTimeout(t.bind(this),n,e)}.call(s,i[0]):i.forEach(t.bind(s)),e||(s.removeTagsFromValue(i.map((function(t){return t.node}))),s.update(),"select"==a.mode&&a.userInput&&s.setContentEditable(!0))})).catch((function(t){}))},removeTagsFromDOM:function(){this.getTagElms().forEach((function(t){return t.remove()}))},removeTagsFromValue:function(t){var e=this;(t=Array.isArray(t)?t:[t]).forEach((function(t){var n=Tt(t),i=e.getTagIdx(n);i>-1&&e.value.splice(i,1)}))},removeAllTags:function(t){var e=this;t=t||{},this.value=[],"mix"==this.settings.mode?this.DOM.input.innerHTML="":this.removeTagsFromDOM(),this.dropdown.refilter(),this.dropdown.position(),this.state.dropdown.visible&&setTimeout((function(){e.DOM.input.focus()})),"select"==this.settings.mode&&(this.input.set.call(this),this.settings.userInput&&this.setContentEditable(!0)),this.update(t)},postUpdate:function(){this.state.blockChangeEvent=!1;var t,e,n=this.settings,i=n.classNames,s="mix"==n.mode?n.mixMode.integrated?this.DOM.input.textContent:this.DOM.originalInput.value.trim():this.value.length+this.input.raw.call(this).length;this.toggleClass(i.hasMaxTags,this.value.length>=n.maxTags),this.toggleClass(i.hasNoTags,!this.value.length),this.toggleClass(i.empty,!s),"select"==n.mode&&this.toggleScopeValidation(null===(e=this.value)||void 0===e||null===(t=e[0])||void 0===t?void 0:t.__isValid)},setOriginalInputValue:function(t){var e=this.DOM.originalInput;this.settings.mixMode.integrated||(e.value=t,e.tagifyValue=e.value,this.setPersistedData(t,"value"))},update:function(t){clearTimeout(this.debouncedUpdateTimeout),this.debouncedUpdateTimeout=setTimeout(function(){var e=this.getInputValue();this.setOriginalInputValue(e),this.settings.onChangeAfterBlur&&(t||{}).withoutChangeEvent||this.state.blockChangeEvent||this.triggerChangeEvent(),this.postUpdate()}.bind(this),100),this.events.bindOriginaInputListener.call(this,100)},getInputValue:function(){var t=this.getCleanValue();return"mix"==this.settings.mode?this.getMixedTagsAsString(t):t.length?this.settings.originalInputValueFormat?this.settings.originalInputValueFormat(t):JSON.stringify(t):""},getCleanValue:function(t){return ot(t||this.value,this.dataProps)},getMixedTagsAsString:function(){var t="",e=this,n=this.settings,i=n.originalInputValueFormat||JSON.stringify,s=n.mixTagsInterpolator;return function n(a){a.childNodes.forEach((function(a){if(1==a.nodeType){var o=Tt(a);if("BR"==a.tagName&&(t+="\r\n"),o&&vt.call(e,a)){if(o.__removed)return;t+=s[0]+i(rt(o,e.dataProps))+s[1]}else a.getAttribute("style")||["B","I","U"].includes(a.tagName)?t+=a.textContent:"DIV"!=a.tagName&&"P"!=a.tagName||(t+="\r\n",n(a))}else t+=a.textContent}))}(this.DOM.input),t}},Gt.prototype.removeTag=Gt.prototype.removeTags;class Zt{#U;#W;#z;#K;#X;#G;#J;#Q;#Y;#Z;#tt;#et;#nt;#it;#st;#at=null;constructor(t,e,n,i,s,a,o,r,l,d,c,u){this.#U=t,this.#Z=e,this.#W=n,this.#z=i,this.#K=s,this.#X=a,this.#nt=o,this.#it=u,this.#st=this.#it.innerHTML,this.#Y=r,this.#G=l,this.#J=d,this.#Q=c,this.#et=!1,this.#tt=!1,this.#W.addEventListener("input",(t=>{this.filterItemsSearch(t)})),this.#Y.addEventListener("click",(()=>{this.setFiltered(!1)})),this.#G.addEventListener("click",(()=>{this.toggleVisibility()})),"radio-field-input"===this.#z&&this.#X.forEach((t=>{t.addEventListener("change",(()=>{this.scrollListToTop()}))}))}isEngaged(){return this.#et}isFiltered(){return this.#tt}setFiltered(t){this.#tt!==t&&(this.#tt=t,t?(this.#Y.style.removeProperty("display"),this.#it.style.removeProperty("display")):(this.#W.value="",this.#Y.style.display="none",this.#it.style.display="none",this.#nt.style.display="none",this.#ot()))}toggleVisibility(){this.isEngaged()?(this.#et=!1,this.#U.classList.remove("engaged"),this.setFiltered(!1),this.#G.setAttribute("aria-expanded","false"),this.#J.style.removeProperty("display"),this.#Q.style.display="none"):(this.#et=!0,this.#U.classList.add("engaged"),this.#G.setAttribute("aria-expanded","true"),this.#J.style.display="none",this.#Q.style.removeProperty("display"))}#rt(t){this.#U.ownerDocument.defaultView.clearTimeout(this.#at),this.#at=this.#U.ownerDocument.defaultView.setTimeout((()=>{this.#it.textContent="",this.#U.ownerDocument.defaultView.requestAnimationFrame((()=>{this.#it.textContent=t}))}),500)}#lt(t){const e=$(this.#st,t);this.#rt(e)}filterItemsSearch(t){const e=t.target.value.toLowerCase();this.setFiltered(!!e);let n=0,i=!1;this.#X.forEach((t=>{t.textContent.toLowerCase().includes(e)?(n+=1,i=!0,te(t)):function(t){t.style.display="none"}(t)})),this.#lt(n.toString()),""!==e&&!1===i?this.#nt.style.removeProperty("display"):(""===e||i)&&(this.#nt.style.display="none")}#ot(){this.#X.forEach((t=>te(t)))}scrollListToTop(){this.#Z.scrollTo({top:0,behavior:"smooth"})}}function te(t){t.style.removeProperty("display")}class ee{#V=new Map;init(t){if(void 0===t)throw new TypeError("During init of an InputHasOptionFilter an undefined element was passed to the factory.");if(this.#V.has(t.id))throw new Error(`A InputHasOptionFilter with id '${t.id}' has already been initialized.`);const e=t,n=e.querySelector(".c-input--has-option-filter__field"),i=e.querySelector(".c-input--has-option-filter__search-input input"),s=e.getAttribute("data-il-ui-component"),a=e.querySelector(".c-field--has-option-filter__list"),o=a.querySelectorAll(".c-field--has-option-filter__item"),r=e.querySelector(".message-no-match"),l=e.querySelector('.c-input--has-option-filter__synopsis [role="status"]'),d=e.querySelector(".c-input--has-option-filter__clear-search"),c=e.querySelector(".c-input--has-option-filter__visibility-toggle"),u=c.querySelector(".text-expand"),h=c.querySelector(".text-collapse"),g=new Zt(t,n,i,s,a,o,r,d,c,u,h,l);return this.#V.set(t.id,g),g}get(t){return this.#V.has(t)?this.#V.get(t):null}}function ne(t,e,n){t.querySelectorAll(`[${n}]`).forEach((t=>{var i,s;t.setAttribute(n,(i=t.getAttribute(n),s=e,i.replace(/\[(\d*)\]$/,`[${s}]`)))}))}class ie extends X{#dt;constructor(t,e){super(t),this.#dt=e}createContent(t){const e=super.createContent(t);return ne(e,this.#dt,"data-il-ui-input-name"),ne(e,this.#dt,"name"),this.#dt+=1,e}}var se;e.UI=e.UI||{},e.UI.Input=e.UI.Input||{},(se=e.UI.Input).textarea=new o,se.mustacheVariables={init:(t,e)=>function(t,e){e.querySelectorAll(".c-input--has-mustache-variables__definitions > li > a").forEach((e=>{const n=function(t){const e=t.textContent.match(/^\s*\{\{([^}]+)\}\}\s*$/);return e?e[1].trim():null}(e);e.addEventListener("click",(()=>{t.insertCharactersAroundSelection(`{{${n}}}`,"")}))}))}(t,e)},se.markdown=new w,se.optionFilter=new ee,se.treeSelect=new Z(new tt(t),e.UI.menu.drilldown,{txt:t=>e.Language.txt(t)},n),se.tagInput=se.tag||{},se.tagInput.init=(t,e,n,i,s)=>Yt(Gt,t,e,n,i,s),se.HasDynamicInputsTemplateRenderer=ie}($,il,document); diff --git a/components/ILIAS/UI/resources/js/Input/Field/file.js b/components/ILIAS/UI/resources/js/Input/Field/file.js index bc9572a8246b..1eb80ebb609f 100755 --- a/components/ILIAS/UI/resources/js/Input/Field/file.js +++ b/components/ILIAS/UI/resources/js/Input/Field/file.js @@ -170,6 +170,10 @@ il.UI.Input = il.UI.Input || {}; forceChunking: should_upload_be_chunked, chunkSize: chunk_size_in_bytes, form: action_button.closest('form'), + templateRenderer: new il.UI.Input.HasDynamicInputsTemplateRenderer( + document, + current_file_count, + ), // override default rendering function. addedfile: file => { @@ -375,7 +379,9 @@ il.UI.Input = il.UI.Input || {}; return; } - let preview = il.UI.core.TemplateRenderer.createContent(dropzones[input_id].options.customPreviewTemplate); + let preview = dropzones[input_id].options.templateRenderer.createContent( + dropzones[input_id].options.customPreviewTemplate, + ); // add file info to preview and setup expansion toggles. preview.querySelector('[data-dz-name]').innerText = file.name; diff --git a/components/ILIAS/UI/resources/js/Input/Field/src/HasDynamicInputsTemplateRenderer.js b/components/ILIAS/UI/resources/js/Input/Field/src/HasDynamicInputsTemplateRenderer.js new file mode 100644 index 000000000000..5d51155b0011 --- /dev/null +++ b/components/ILIAS/UI/resources/js/Input/Field/src/HasDynamicInputsTemplateRenderer.js @@ -0,0 +1,71 @@ +/** + * This file is part of ILIAS, a powerful learning management system + * published by ILIAS open source e-Learning e.V. + * + * ILIAS is licensed with the GPL-3.0, + * see https://www.gnu.org/licenses/gpl-3.0.en.html + * You should have received a copy of said license along with the + * source code, too. + * + * If this is not the case or you just want to try ILIAS, you'll find + * us at: + * https://www.ilias.de + * https://github.com/ILIAS-eLearning + */ + +import TemplateRenderer from '../../../Core/src/TemplateRenderer.js'; + +/** + * Updates all string values of the given attributeName and replaces the last [] or [n] array- + * postfix with a concrete newIndex (i.e. input_1[input_2][] becomes input_1[input_2][0]) + * + * @param {HTMLElement} parentElement + * @param {number} newIndex + * @param {string} attributeName + */ +function replaceAllAttributeValueIndices(parentElement, newIndex, attributeName) { + parentElement.querySelectorAll(`[${attributeName}]`).forEach((child) => { + child.setAttribute( + attributeName, + replaceSingleAttributeValueIndex(child.getAttribute(attributeName), newIndex), + ); + }); +} + +/** + * @param {string} name + * @param {number} index + */ +function replaceSingleAttributeValueIndex(name, index) { + return name.replace(/\[(\d*)\]$/, `[${index}]`); +} + +/** + * @author Thibeau Fuhrer + */ +export default class HasDynamicInputsTemplateRenderer extends TemplateRenderer { + /** @type {number} */ + #currentInputGroupIndex; + + /** + * @param {Document} document + * @param {number} currentInputGroupIndex + */ + constructor(document, currentInputGroupIndex) { + super(document); + this.#currentInputGroupIndex = currentInputGroupIndex; + } + + /** + * @inheritDoc + */ + createContent(template) { + const newInputFragment = super.createContent(template); + + replaceAllAttributeValueIndices(newInputFragment, this.#currentInputGroupIndex, 'data-il-ui-input-name'); + replaceAllAttributeValueIndices(newInputFragment, this.#currentInputGroupIndex, 'name'); + this.#currentInputGroupIndex += 1; + + return newInputFragment; + } +} diff --git a/components/ILIAS/UI/resources/js/Input/Field/src/input.factory.js b/components/ILIAS/UI/resources/js/Input/Field/src/input.factory.js index 80b46d79062f..e6940e8494dd 100755 --- a/components/ILIAS/UI/resources/js/Input/Field/src/input.factory.js +++ b/components/ILIAS/UI/resources/js/Input/Field/src/input.factory.js @@ -35,6 +35,7 @@ import JQueryEventListener from '../../../Core/src/JQueryEventListener.js'; import Tagify from '@yaireo/tagify'; import tag from './Tag/tag.js'; import OptionFilterFactory from './OptionFilter/OptionFilterFactory.js'; +import HasDynamicInputsTemplateRenderer from './HasDynamicInputsTemplateRenderer.js'; il.UI = il.UI || {}; il.UI.Input = il.UI.Input || {}; @@ -59,4 +60,7 @@ il.UI.Input = il.UI.Input || {}; Input.tagInput = Input.tag || {}; Input.tagInput.init = (input, config, value, autocompleteEndpoint, autocompleteToken) => tag( Tagify, input, config, value, autocompleteEndpoint, autocompleteToken); + + // @todo: remove this once file input is migrated. + Input.HasDynamicInputsTemplateRenderer = HasDynamicInputsTemplateRenderer; }(il.UI.Input)); diff --git a/components/ILIAS/UI/src/Component/Input/Input.php b/components/ILIAS/UI/src/Component/Input/Input.php index c706da2970eb..e55ef4fee988 100755 --- a/components/ILIAS/UI/src/Component/Input/Input.php +++ b/components/ILIAS/UI/src/Component/Input/Input.php @@ -71,8 +71,6 @@ public function withAdditionalTransformation(Transformation $trafo); /** * Sets an optional dedicated name for this input which is used in the NAME * attribute of the rendered input (instead of the auto-generated 'input_x'). - * If the same dedicated name is used more than once, a counter will be - * added to the name. * * The dedicated name is inherited by all child inputs (e.g. for groups * or sections) and added to their name in a path-like format. diff --git a/components/ILIAS/UI/src/Implementation/Component/Dropzone/File/Factory.php b/components/ILIAS/UI/src/Implementation/Component/Dropzone/File/Factory.php index 7b10fb47ad8b..8b39544f1877 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Dropzone/File/Factory.php +++ b/components/ILIAS/UI/src/Implementation/Component/Dropzone/File/Factory.php @@ -20,7 +20,7 @@ namespace ILIAS\UI\Implementation\Component\Dropzone\File; -use ILIAS\UI\Implementation\Component\Input\FormInputNameSource; +use ILIAS\UI\Implementation\Component\Input\NameSource; use ILIAS\UI\Implementation\Component\SignalGeneratorInterface; use ILIAS\UI\Component\Dropzone\File\Factory as FileDropzoneFactory; use ILIAS\UI\Implementation\Component\Input\Field\Factory as FieldFactory; @@ -32,13 +32,11 @@ */ class Factory implements FileDropzoneFactory { - protected SignalGeneratorInterface $signal_generator; - protected FieldFactory $field_factory; - - public function __construct(SignalGeneratorInterface $signal_generator, FieldFactory $field_factory) - { - $this->signal_generator = $signal_generator; - $this->field_factory = $field_factory; + public function __construct( + protected SignalGeneratorInterface $signal_generator, + protected FieldFactory $field_factory, + protected NameSource $name_source, + ) { } public function standard( @@ -51,7 +49,7 @@ public function standard( return new Standard( $this->signal_generator, $this->field_factory, - new FormInputNameSource(), + $this->name_source, $title, $message, $post_url, @@ -70,7 +68,7 @@ public function wrapper( return new Wrapper( $this->signal_generator, $this->field_factory, - new FormInputNameSource(), + $this->name_source, $title, $content, $post_url, diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Container/Container.php b/components/ILIAS/UI/src/Implementation/Component/Input/Container/Container.php index d8c0a8c8d7f4..5a9149016744 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Container/Container.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Container/Container.php @@ -39,15 +39,13 @@ abstract class Container implements C\Input\Container\Container protected ?Transformation $transformation = null; protected ?string $error = null; protected ?string $dedicated_name = null; - protected CI\Input\NameSource $name_source; protected ?Result $result = null; /** * For the implementation of NameSource. */ - public function __construct(NameSource $name_source) + public function __construct(protected NameSource $name_source) { - $this->name_source = clone $name_source; } /** @@ -71,7 +69,7 @@ public function withRequest(ServerRequestInterface $request): self public function withInput(InputData $input_data): self { $clone = clone $this; - $clone->input_group = $this->getInputGroup()->withInput($input_data); + $clone->input_group = $this->getInputGroup()->withInput($input_data)->withNameFrom($this->name_source->withReset()); $clone->result = $clone->input_group->getContent(); if (!$clone->result->isok()) { @@ -127,7 +125,7 @@ public function withDedicatedName(string $dedicated_name): self $clone->dedicated_name = $dedicated_name; $clone->input_group = $clone->input_group ->withDedicatedName($dedicated_name) - ->withNameFrom($clone->name_source); + ->withNameFrom($clone->name_source->withReset()); return $clone; } @@ -141,7 +139,7 @@ public function getInputGroup(): C\Input\Group */ protected function setInputGroup(C\Input\Group $input_group): void { - $this->input_group = $input_group->withNameFrom($this->name_source); + $this->input_group = $input_group->withNameFrom($this->name_source->withReset()); } /** diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Container/Filter/Factory.php b/components/ILIAS/UI/src/Implementation/Component/Input/Container/Filter/Factory.php index 39e61f686d35..bfee7df5c74c 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Container/Filter/Factory.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Container/Filter/Factory.php @@ -22,19 +22,16 @@ use ILIAS\UI\Component\Input\Container\Filter as F; use ILIAS\UI\Implementation\Component\Input\Field; +use ILIAS\UI\Implementation\Component\Input\NameSource; use ILIAS\UI\Implementation\Component\SignalGeneratorInterface; class Factory implements F\Factory { - protected SignalGeneratorInterface $signal_generator; - protected Field\Factory $field_factory; - public function __construct( - SignalGeneratorInterface $signal_generator, - Field\Factory $field_factory + protected SignalGeneratorInterface $signal_generator, + protected Field\Factory $field_factory, + protected NameSource $name_source, ) { - $this->signal_generator = $signal_generator; - $this->field_factory = $field_factory; } public function standard( @@ -52,6 +49,7 @@ public function standard( return new Standard( $this->signal_generator, $this->field_factory, + $this->name_source, $toggle_action_on, $toggle_action_off, $expand_action, diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Container/Filter/Filter.php b/components/ILIAS/UI/src/Implementation/Component/Input/Container/Filter/Filter.php index 2f6b697302b2..58a59e508cd8 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Container/Filter/Filter.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Container/Filter/Filter.php @@ -33,7 +33,7 @@ /** * This implements commonalities between all Filters. */ -abstract class Filter implements C\Input\Container\Filter\Filter, CI\Input\NameSource +abstract class Filter implements C\Input\Container\Filter\Filter { use ComponentHelper; use JavaScriptBindable; @@ -106,6 +106,7 @@ abstract class Filter implements C\Input\Container\Filter\Filter, CI\Input\NameS public function __construct( SignalGeneratorInterface $signal_generator, CI\Input\Field\Factory $field_factory, + protected CI\Input\NameSource $name_source, $toggle_action_on, $toggle_action_off, $expand_action, @@ -131,7 +132,7 @@ public function __construct( $this->checkArgListElements("input", $inputs, $classes); $this->initSignals(); - $this->input_group = $field_factory->group($inputs)->withNameFrom($this); + $this->input_group = $field_factory->group($inputs)->withNameFrom($this->name_source->withReset()); foreach ($is_input_rendered as $r) { $this->checkBoolArg("is_input_rendered", $r); @@ -250,36 +251,6 @@ protected function extractParamData(ServerRequestInterface $request): InputData return new QueryParamsFromServerRequest($request); } - /** - * Implementation of NameSource - * - * @inheritdoc - */ - public function getNewName(): string - { - $name = "filter_input_$this->count"; - $this->count++; - - return $name; - } - - /** - * Implementation of NameSource - * for using dedicated names in filter fields - */ - public function getNewDedicatedName(string $dedicated_name): string - { - if ($dedicated_name == 'filter_input') { - return $this->getNewName(); - } - if (in_array($dedicated_name, $this->used_names)) { - return $dedicated_name . '_' . $this->count++; - } else { - $this->used_names[] = $dedicated_name; - return $dedicated_name; - } - } - /** * @inheritdoc */ diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Container/Form/Factory.php b/components/ILIAS/UI/src/Implementation/Component/Input/Container/Form/Factory.php index 79c724ecb33d..e6021dc3ce7c 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Container/Form/Factory.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Container/Form/Factory.php @@ -30,6 +30,7 @@ class Factory implements F\Factory public function __construct( protected Input\Field\Factory $field_factory, protected SignalGeneratorInterface $signal_generator, + protected Input\NameSource $name_source, ) { } @@ -38,7 +39,7 @@ public function standard(string $post_url, array $inputs): Standard return new Standard( $this->signal_generator, $this->field_factory, - new Input\FormInputNameSource(), + $this->name_source, $post_url, $inputs ); diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Container/ViewControl/Factory.php b/components/ILIAS/UI/src/Implementation/Component/Input/Container/ViewControl/Factory.php index 658f91959ce6..c6af2b2ce3b4 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Container/ViewControl/Factory.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Container/ViewControl/Factory.php @@ -30,15 +30,15 @@ class Factory implements V\Factory public function __construct( protected SignalGeneratorInterface $signal_generator, protected Input\ViewControl\Factory $view_control_factory, + protected Input\NameSource $name_source, ) { - $this->signal_generator = $signal_generator; } public function standard(array $controls): Standard { return new Standard( $this->signal_generator, - new Input\FormInputNameSource(), + $this->name_source, $this->view_control_factory, $controls ); diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/DefaultNameSource.php b/components/ILIAS/UI/src/Implementation/Component/Input/DefaultNameSource.php new file mode 100755 index 000000000000..fecded937bec --- /dev/null +++ b/components/ILIAS/UI/src/Implementation/Component/Input/DefaultNameSource.php @@ -0,0 +1,66 @@ + + */ +class DefaultNameSource implements NameSource +{ + protected const NAME_DELIMITER = '/'; + protected const DEFAULT_NAME = 'input_'; + + /** @var array */ + protected array $used_input_name_lookup_table = []; + protected ?string $parent_name = null; + protected int $count = 0; + + public function getNextName(?string $dedicated_name = null): string + { + $new_name = $dedicated_name ?? (self::DEFAULT_NAME . $this->count++); + if (null !== $this->parent_name) { + $new_name = $this->parent_name . self::NAME_DELIMITER . $new_name; + } + if (isset($this->used_input_name_lookup_table[$new_name])) { + throw new \LogicException("Input name '$new_name' was already generated."); + } + $this->used_input_name_lookup_table[$new_name] = true; + return $new_name; + } + + public function withParentName(string $parent_name): static + { + $clone = clone $this; + $clone->parent_name = $parent_name; + $clone->count = 0; + return $clone; + } + + public function withReset(): static + { + $clone = clone $this; + $clone->used_input_name_lookup_table = []; + $clone->parent_name = null; + $clone->count = 0; + return $clone; + } +} diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/DynamicInputsNameSource.php b/components/ILIAS/UI/src/Implementation/Component/Input/DynamicInputsNameSource.php deleted file mode 100755 index e2dc99cfa0df..000000000000 --- a/components/ILIAS/UI/src/Implementation/Component/Input/DynamicInputsNameSource.php +++ /dev/null @@ -1,43 +0,0 @@ - - */ -class DynamicInputsNameSource extends FormInputNameSource -{ - protected string $parent_input_name; - - public function __construct(string $parent_input_name) - { - $this->parent_input_name = $parent_input_name; - } - - public function getNewName(): string - { - return "$this->parent_input_name[" . parent::getNewName() . "][]"; - } -} diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Factory.php b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Factory.php index 4d7170a879fe..eb4d614f3ca0 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Factory.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Factory.php @@ -20,6 +20,7 @@ namespace ILIAS\UI\Implementation\Component\Input\Field; +use ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource; use ILIAS\UI\Implementation\Component\Input\UploadLimitResolver; use ILIAS\Data; use ILIAS\UI\Component\Input\Container\Form\FormInput; @@ -33,6 +34,7 @@ class Factory implements I\Factory { public function __construct( protected Node\Factory $node_factory, + protected HasDynamicInputsNameSource $has_dynamic_inputs_name_source, protected UploadLimitResolver $upload_limit_resolver, protected SignalGeneratorInterface $signal_generator, protected Data\Factory $data_factory, @@ -127,6 +129,7 @@ public function file( $this->data_factory, $this, $this->refinery, + $this->has_dynamic_inputs_name_source, $this->upload_limit_resolver, $handler, $label, @@ -147,6 +150,7 @@ public function image( $this->data_factory, $this, $this->refinery, + $this->has_dynamic_inputs_name_source, $this->upload_limit_resolver, $upload_handler, $image_purpose, @@ -195,6 +199,7 @@ public function treeSelect( $this->lng, $this->data_factory, $this->refinery, + $this->has_dynamic_inputs_name_source, $this->hidden(), $node_retrieval, $label, @@ -211,6 +216,7 @@ public function treeMultiSelect( $this->lng, $this->data_factory, $this->refinery, + $this->has_dynamic_inputs_name_source, $this->hidden(), $node_retrieval, $label, diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Field/FieldRendererFactory.php b/components/ILIAS/UI/src/Implementation/Component/Input/Field/FieldRendererFactory.php index 5d3372541d81..b8549f6710a2 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Field/FieldRendererFactory.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Field/FieldRendererFactory.php @@ -53,6 +53,21 @@ public function getRendererInContext(Component\Component $component, array $cont $this->upload_limit_resolver ); } + if (in_array('OrderingTable', $contexts, true) + && in_array('OrderingRowTable', $contexts, true) + && in_array('NumericFieldInput', $contexts, true) + ) { + return new OrderingTableContextRenderer( + $this->ui_factory, + $this->tpl_factory, + $this->lng, + $this->js_binding, + $this->image_path_resolver, + $this->data_factory, + $this->help_text_retriever, + $this->upload_limit_resolver + ); + } return new Renderer( $this->ui_factory, $this->tpl_factory, diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Field/File.php b/components/ILIAS/UI/src/Implementation/Component/Input/Field/File.php index b35556cc1f05..36ea03931c29 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Field/File.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Field/File.php @@ -20,6 +20,7 @@ namespace ILIAS\UI\Implementation\Component\Input\Field; +use ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource; use ILIAS\UI\Implementation\Component\Input\UploadLimitResolver; use ILIAS\UI\Component\Input\Field\UploadHandler; use ILIAS\UI\Component\Input\Field\FileUpload; @@ -55,6 +56,7 @@ public function __construct( DataFactory $data_factory, Factory $field_factory, Refinery $refinery, + HasDynamicInputsNameSource $has_dynamic_inputs_name_source, UploadLimitResolver $upload_limit_resolver, C\Input\Field\UploadHandler $handler, string $label, @@ -70,6 +72,7 @@ public function __construct( $language, $data_factory, $refinery, + $has_dynamic_inputs_name_source, $this->createDynamicInputsTemplate($field_factory, $metadata_input), $label, $byline, diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Group.php b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Group.php index 7ce7d2976a8b..6a42bc0513b8 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Group.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Group.php @@ -115,11 +115,10 @@ public function getUpdateOnLoadCode(): Closure /** * @inheritdoc */ - public function withNameFrom(NameSource $source, ?string $parent_name = null): self + public function withNameFrom(NameSource $source): static { - /** @var $clone self */ - $clone = parent::withNameFrom($source, $parent_name); - $clone->setInputs($this->nameInputs($source, $clone->getName())); + $clone = parent::withNameFrom($source); + $clone->setInputs($this->nameInputs($source->withParentName($clone->getName()))); return $clone; } diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Field/HasDynamicInputs.php b/components/ILIAS/UI/src/Implementation/Component/Input/Field/HasDynamicInputs.php index 94c2fbb2c769..d02a3def7c4e 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Field/HasDynamicInputs.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Field/HasDynamicInputs.php @@ -20,8 +20,8 @@ namespace ILIAS\UI\Implementation\Component\Input\Field; -use ILIAS\UI\Implementation\Component\Input\DynamicInputDataIterator; -use ILIAS\UI\Implementation\Component\Input\DynamicInputsNameSource; +use ILIAS\UI\Implementation\Component\Input\HasDynamicInputsDataIterator; +use ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource; use ILIAS\UI\Implementation\Component\Input\NameSource; use ILIAS\UI\Component\Input\InputData; use ILIAS\UI\Component\Input\Container\Form\FormInput as FormInputInterface; @@ -52,6 +52,7 @@ public function __construct( protected Language $language, DataFactory $data_factory, Refinery $refinery, + protected HasDynamicInputsNameSource $has_dynamic_inputs_name_source, protected FormInputInterface $dynamic_input_template, string $label, ?string $byline @@ -119,18 +120,24 @@ public function withDisabled(bool $is_disabled): self return $clone; } - public function withNameFrom(NameSource $source, ?string $parent_name = null): self + public function withNameFrom(NameSource $source): static { - $clone = parent::withNameFrom($source, $parent_name); + $clone = parent::withNameFrom($source); - $clone->dynamic_input_template = $clone->getTemplateForDynamicInputs()->withNameFrom( - new DynamicInputsNameSource($clone->getName()) - ); + $template_name_source = $this->has_dynamic_inputs_name_source + ->withReset() + ->withIndices(false) + ->withParentName($clone->getName()); + + $clone->dynamic_input_template = $clone->dynamic_input_template->withNameFrom($template_name_source); + + $generated_inputs_name_source = $this->has_dynamic_inputs_name_source + ->withReset() + ->withIndices(true) + ->withParentName($clone->getName()); foreach ($clone->generated_dynamic_inputs as $key => $input) { - $clone->generated_dynamic_inputs[$key] = $input->withNameFrom( - new DynamicInputsNameSource($clone->getName()) - ); + $clone->generated_dynamic_inputs[$key] = $input->withNameFrom($generated_inputs_name_source->withResetDefaultNameSource()); } return $clone; @@ -146,7 +153,7 @@ public function withInput(InputData $post_data): self $contains_error = false; $contents = []; - foreach ((new DynamicInputDataIterator($post_data, $clone->getName())) as $index => $input_data) { + foreach ((new HasDynamicInputsDataIterator($post_data, $clone->getName())) as $index => $input_data) { $clone->generated_dynamic_inputs[$index] = $clone->getTemplateForDynamicInputs()->withInput($input_data); if ($clone->generated_dynamic_inputs[$index]->getContent()->isOk()) { $contents[] = $clone->generated_dynamic_inputs[$index]->getContent()->value(); diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Image.php b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Image.php index 1714fd0885ff..a128bd0fb838 100644 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Image.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Image.php @@ -19,6 +19,7 @@ namespace ILIAS\UI\Implementation\Component\Input\Field; +use ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource; use ILIAS\UI\Implementation\Component\Input\UploadLimitResolver; use ILIAS\UI\Component\Input\Container\Form\FormInput; use ILIAS\UI\Component as C; @@ -37,6 +38,7 @@ public function __construct( DataFactory $data_factory, Factory $field_factory, Refinery $refinery, + HasDynamicInputsNameSource $has_dynamic_inputs_name_source, UploadLimitResolver $upload_limit_resolver, C\Input\Field\UploadHandler $handler, protected ImagePurpose $image_purpose, @@ -49,6 +51,7 @@ public function __construct( $data_factory, $field_factory, $refinery, + $has_dynamic_inputs_name_source, $upload_limit_resolver, $handler, $label, diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Field/OrderingTableContextRenderer.php b/components/ILIAS/UI/src/Implementation/Component/Input/Field/OrderingTableContextRenderer.php new file mode 100644 index 000000000000..de4d725ee05d --- /dev/null +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Field/OrderingTableContextRenderer.php @@ -0,0 +1,43 @@ + + */ +class OrderingTableContextRenderer extends Renderer +{ + protected function applyName(FormInput $component, Template $tpl): string + { + $name = $component->getDedicatedName(); + if (null === $name) { + throw new \LogicException('Internal FormInput of Ordering Table MUST have a dedicated name.'); + } + $tpl->setVariable("NAME", $name); + return $name; + } +} diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Renderer.php b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Renderer.php index 65abfedcd4be..12ce117d3589 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Field/Renderer.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Field/Renderer.php @@ -234,7 +234,7 @@ protected function wrapInFormContext( return $tpl->get(); } - protected function applyName(FormInput $component, Template $tpl): ?string + protected function applyName(F\FormInput $component, Template $tpl): ?string { $name = $component->getName(); $tpl->setVariable("NAME", $name); @@ -950,10 +950,10 @@ public function registerResources(ResourceRegistry $registry): void $registry->register('assets/js/dropzone.js'); $registry->register('assets/js/input.js'); $registry->register('assets/js/core.js'); - $registry->register('assets/js/file.js'); // workaround to manipulate the order of scripts $registry->register('assets/js/drilldown.min.js'); $registry->register('assets/js/input.factory.min.js'); + $registry->register('assets/js/file.js'); } /** diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Field/TreeMultiSelect.php b/components/ILIAS/UI/src/Implementation/Component/Input/Field/TreeMultiSelect.php index d2712a5ed560..a97fc7138207 100644 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Field/TreeMultiSelect.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Field/TreeMultiSelect.php @@ -19,6 +19,7 @@ namespace ILIAS\UI\Implementation\Component\Input\Field; +use ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource; use ILIAS\UI\Component as C; use ILIAS\Language\Language; use ILIAS\Refinery\Constraint; @@ -36,6 +37,7 @@ public function __construct( Language $language, DataFactory $data_factory, Refinery $refinery, + HasDynamicInputsNameSource $has_dynamic_inputs_name_source, C\Input\Container\Form\FormInput $dynamic_input_template, protected C\Input\Field\Node\NodeRetrieval $node_retrieval, string $label, @@ -45,6 +47,7 @@ public function __construct( $language, $data_factory, $refinery, + $has_dynamic_inputs_name_source, $dynamic_input_template, $label, $byline diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Field/TreeSelect.php b/components/ILIAS/UI/src/Implementation/Component/Input/Field/TreeSelect.php index c7b96ee55300..bea20548a6ca 100644 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Field/TreeSelect.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Field/TreeSelect.php @@ -19,6 +19,7 @@ namespace ILIAS\UI\Implementation\Component\Input\Field; +use ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource; use ILIAS\UI\Component as C; use ILIAS\Language\Language; use ILIAS\Refinery\Constraint; @@ -34,6 +35,7 @@ public function __construct( Language $language, DataFactory $data_factory, Refinery $refinery, + HasDynamicInputsNameSource $has_dynamic_inputs_name_source, C\Input\Container\Form\FormInput $dynamic_input_template, protected C\Input\Field\Node\NodeRetrieval $node_retrieval, string $label, @@ -43,6 +45,7 @@ public function __construct( $language, $data_factory, $refinery, + $has_dynamic_inputs_name_source, $dynamic_input_template, $label, $byline diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/FormInputNameSource.php b/components/ILIAS/UI/src/Implementation/Component/Input/FormInputNameSource.php deleted file mode 100755 index 94df2abb8938..000000000000 --- a/components/ILIAS/UI/src/Implementation/Component/Input/FormInputNameSource.php +++ /dev/null @@ -1,54 +0,0 @@ - - */ -class FormInputNameSource implements NameSource -{ - private int $count = 0; - private array $used_names = []; - - /** - * @inheritDoc - */ - public function getNewName(): string - { - return 'input_' . $this->count++; - } - - public function getNewDedicatedName(string $dedicated_name): string - { - if ($dedicated_name == 'input') { - return $this->getNewName(); - } - if (in_array($dedicated_name, $this->used_names)) { - return $dedicated_name . '_' . $this->count++; - } else { - $this->used_names[] = $dedicated_name; - return $dedicated_name; - } - } -} diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Group.php b/components/ILIAS/UI/src/Implementation/Component/Input/Group.php index e7a1136230f1..49dd6fe4205e 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Group.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Group.php @@ -113,11 +113,11 @@ public function withInput(InputData $input): self /** * @return Input[] */ - protected function nameInputs(NameSource $source, string $parent_name): array + protected function nameInputs(NameSource $source): array { $named_inputs = []; foreach ($this->getInputs() as $key => $input) { - $named_inputs[$key] = $input->withNameFrom($source, $parent_name); + $named_inputs[$key] = $input->withNameFrom($source); } return $named_inputs; diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/DynamicInputDataIterator.php b/components/ILIAS/UI/src/Implementation/Component/Input/HasDynamicInputsDataIterator.php similarity index 94% rename from components/ILIAS/UI/src/Implementation/Component/Input/DynamicInputDataIterator.php rename to components/ILIAS/UI/src/Implementation/Component/Input/HasDynamicInputsDataIterator.php index 0dbd5fcef5d1..6c44537fd64c 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/DynamicInputDataIterator.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/HasDynamicInputsDataIterator.php @@ -26,7 +26,7 @@ /** * @author Thibeau Fuhrer */ -class DynamicInputDataIterator implements Iterator +class HasDynamicInputsDataIterator implements Iterator { protected string $parent_input_name; protected array $post_data; @@ -44,7 +44,7 @@ public function current(): ?InputData $entry = []; // for each input of the dynamic input template, the input data must // be mapped to the rendered name, similar to one delivered by - // DynamicInputsNameSource for further processing. + // HasDynamicInputsNameSource for further processing. foreach ($this->post_data as $input_name => $data) { $dynamic_input_name = "$this->parent_input_name[$input_name][]"; $entry[$dynamic_input_name] = $data[$this->index]; diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/HasDynamicInputsNameSource.php b/components/ILIAS/UI/src/Implementation/Component/Input/HasDynamicInputsNameSource.php new file mode 100755 index 000000000000..732ecb13e5ee --- /dev/null +++ b/components/ILIAS/UI/src/Implementation/Component/Input/HasDynamicInputsNameSource.php @@ -0,0 +1,116 @@ + + */ +class HasDynamicInputsNameSource implements NameSource +{ + /** @var array (name => index) */ + protected array $used_input_name_index_map = []; + protected bool $should_generate_indices = false; + protected ?string $parent_name = null; + + public function __construct( + protected NameSource $default_name_source, + ) { + } + + public function getNextName(?string $dedicated_name = null): string + { + if (null === $this->parent_name) { + throw new \LogicException('Cannot generate input-names without parent-name.'); + } + $new_name_without_array_postfix = $this->parent_name . "[{$this->default_name_source->getNextName($dedicated_name)}]"; + if ($this->should_generate_indices) { + return $this->addArrayPostfixWithIndex($new_name_without_array_postfix); + } + return $this->addArrayPostfix($new_name_without_array_postfix); + } + + public function withParentName(string $parent_name): static + { + $clone = clone $this; + $clone->parent_name = $parent_name; + return $clone; + } + + public function withReset(): static + { + $clone = clone $this; + $clone->default_name_source = $clone->default_name_source->withReset(); + $clone->used_input_name_index_map = []; + $clone->should_generate_indices = false; + $clone->parent_name = null; + return $clone; + } + + /** + * Get a NameSource like this, but reset its default NameSource. + * + * This MUST be called before a new generated input (group) is named, otherwise + * these inputs will not have the same name(s). + */ + public function withResetDefaultNameSource(): static + { + $clone = clone $this; + $clone->default_name_source = $clone->default_name_source->withReset(); + return $clone; + } + + /** + * Get a NameSource like this, but change the way the array postfix is treated. + * + * Indices MUST be used for input names that are ultimately rendered, otherwise + * some inputs like e.g. Radio will not operate properly on client. + * + * Indices MUST NOT be used for input names of templates, otherwise mapping them + * inside @see HasDynamicInputs::withInput() is not possible. + */ + public function withIndices(bool $with_indices): static + { + $clone = clone $this; + $clone->should_generate_indices = $with_indices; + return $clone; + } + + protected function addArrayPostfixWithIndex(string $new_name_without_array_postfix): string + { + $index = $this->used_input_name_index_map[$new_name_without_array_postfix] ?? 0; + $this->used_input_name_index_map[$new_name_without_array_postfix] = $index + 1; + + return "{$new_name_without_array_postfix}[$index]"; + } + + protected function addArrayPostfix(string $new_name_without_array_postfix): string + { + return "{$new_name_without_array_postfix}[]"; + } +} diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/Input.php b/components/ILIAS/UI/src/Implementation/Component/Input/Input.php index 11a417405f70..474ebfa832fc 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/Input.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/Input.php @@ -34,7 +34,7 @@ use LogicException; use Generator; use InvalidArgumentException; -use ILIAS\UI\Implementation\Component\Input\DynamicInputsNameSource; +use ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource; /** * This implements commonalities between inputs. @@ -197,17 +197,10 @@ final public function getName(): ?string /** * @inheritdoc */ - public function withNameFrom(NameSource $source, ?string $parent_name = null): self + public function withNameFrom(NameSource $source): static { $clone = clone $this; - if ($source instanceof DynamicInputsNameSource) { - $clone->name = ''; - } else { - $clone->name = ($parent_name !== null) ? $parent_name . '/' : ''; - } - $clone->name .= ($clone->dedicated_name !== null) - ? $source->getNewDedicatedName($clone->dedicated_name) - : $source->getNewName(); + $clone->name = $source->getNextName($this->dedicated_name); return $clone; } diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/InputInternal.php b/components/ILIAS/UI/src/Implementation/Component/Input/InputInternal.php index 41e69b9a5da2..6e5162c78e07 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/InputInternal.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/InputInternal.php @@ -33,10 +33,8 @@ interface InputInternal extends Input { /** * Get an input like this one, with a different name. - * - * @return static */ - public function withNameFrom(NameSource $source); + public function withNameFrom(NameSource $source): static; /** * The name of the input as used in HTML. diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/NameSource.php b/components/ILIAS/UI/src/Implementation/Component/Input/NameSource.php index 04b44de5e313..22e833f537f8 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/NameSource.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/NameSource.php @@ -21,12 +21,38 @@ namespace ILIAS\UI\Implementation\Component\Input; /** - * Describes a source for input names. + * Generates input names for both HTML rendering and input data processing. + * + * A NameSource acts as a stateful builder for hierarchical input names. Its + * state must be reset with withReset() before starting a new naming process. + * Each Input retrieves a name via getNextName(). For nested Inputs, the previous + * name SHOULD be passed to withParentName(), creating a path-like hierarchy + * where parent and child names are concatenated using some delimiter. */ interface NameSource { /** - * Generates a unique name on every call. + * Get the next input name for the current naming process. + * + * A dedicated name will take precedence over a generated one. + * + * @throws \LogicException if there are duplicate names on one level. + */ + public function getNextName(?string $dedicated_name = null): string; + + /** + * Get a NameSource like this, but provide it with a parent name. + * + * The parent name will be concatenated before the next input name and is + * separated by some delimiter. + */ + public function withParentName(string $parent_name): static; + + /** + * Get a NameSource like this, but reset its internal state. + * + * Use this method before starting a new naming process to ensure + * previous state does not affect new name generation. */ - public function getNewName(): string; + public function withReset(): static; } diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/ViewControl/Group.php b/components/ILIAS/UI/src/Implementation/Component/Input/ViewControl/Group.php index 26dffc987476..78663cc0b962 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/ViewControl/Group.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/ViewControl/Group.php @@ -54,11 +54,11 @@ public function __construct( /** * @inheritdoc */ - public function withNameFrom(NameSource $source, ?string $parent_name = null): self + public function withNameFrom(NameSource $source): static { /** @var $clone self */ - $clone = parent::withNameFrom($source, $parent_name); - $clone->setInputs($this->nameInputs($source, $clone->getName())); + $clone = parent::withNameFrom($source); + $clone->setInputs($this->nameInputs($source->withParentName($clone->getName()))); return $clone; } diff --git a/components/ILIAS/UI/src/Implementation/Component/Input/ViewControl/GroupDecorator.php b/components/ILIAS/UI/src/Implementation/Component/Input/ViewControl/GroupDecorator.php index 3405c0a3c179..d2b471c22fa7 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Input/ViewControl/GroupDecorator.php +++ b/components/ILIAS/UI/src/Implementation/Component/Input/ViewControl/GroupDecorator.php @@ -25,6 +25,7 @@ use ILIAS\Data\Result; use ILIAS\UI\Component\Input\InputData; use ILIAS\UI\Implementation\Component\Input\NameSource; +use ILIAS\UI\Implementation\Component\Input\Input; /** * @author Thibeau Fuhrer @@ -64,10 +65,10 @@ public function withAdditionalTransformation(Transformation $trafo): self /** * @inheritDoc */ - public function withNameFrom(NameSource $source, ?string $parent_name = null): self + public function withNameFrom(NameSource $source): static { $clone = clone $this; - $clone->setInputGroup($clone->getInputGroup()->withNameFrom($source, $parent_name)); + $clone->setInputGroup($clone->getInputGroup()->withNameFrom($source)); return $clone; } diff --git a/components/ILIAS/UI/src/Implementation/Component/Modal/Factory.php b/components/ILIAS/UI/src/Implementation/Component/Modal/Factory.php index ac351325a32d..c2f9208ecb90 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Modal/Factory.php +++ b/components/ILIAS/UI/src/Implementation/Component/Modal/Factory.php @@ -25,7 +25,7 @@ use ILIAS\UI\Component\Image\Image; use ILIAS\UI\Implementation\Component\SignalGeneratorInterface; use ILIAS\UI\Component\Modal\InterruptiveItem\Factory as ItemFactory; -use ILIAS\UI\Implementation\Component\Input\FormInputNameSource; +use ILIAS\UI\Implementation\Component\Input\NameSource; use ILIAS\UI\Implementation\Component\Input\Field\Factory as FieldFactory; use ILIAS\UI\Component\Card\Card; @@ -35,6 +35,7 @@ public function __construct( protected SignalGeneratorInterface $signal_generator, protected InterruptiveItem\Factory $item_factory, protected FieldFactory $field_factory, + protected NameSource $name_source, ) { } @@ -53,7 +54,7 @@ public function roundtrip(string $title, Component\Component|array|null $content return new RoundTrip( $this->signal_generator, $this->field_factory, - new FormInputNameSource(), + $this->name_source, $title, $content, $inputs, diff --git a/components/ILIAS/UI/src/Implementation/Component/Table/Renderer.php b/components/ILIAS/UI/src/Implementation/Component/Table/Renderer.php index 5f11031c401d..22b349176f2e 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Table/Renderer.php +++ b/components/ILIAS/UI/src/Implementation/Component/Table/Renderer.php @@ -564,21 +564,9 @@ public function renderOrderingRow(Component\Table\OrderingRow $component, Render return $cell_tpl->get(); } - $namesource = new class () implements NameSource { - public function getNewName(): string - { - return ''; - } - public function getNewDedicatedName(string $dedicated_name): string - { - return $dedicated_name; - } - }; - $numeric_label = $this->txt("ui_table_order"); $input = $this->getUIFactory()->input()->field()->numeric($numeric_label) ->withDedicatedName($component->getId()) - ->withNameFrom($namesource) ->withValue($component->getPosition() * 10); $cell_tpl->setVariable('ORDER_INPUT', $default_renderer->render($input)); diff --git a/components/ILIAS/UI/tests/Component/Dropzone/File/FileTestBase.php b/components/ILIAS/UI/tests/Component/Dropzone/File/FileTestBase.php index 7382aaaa61fb..ad099af36614 100755 --- a/components/ILIAS/UI/tests/Component/Dropzone/File/FileTestBase.php +++ b/components/ILIAS/UI/tests/Component/Dropzone/File/FileTestBase.php @@ -27,12 +27,15 @@ use ILIAS\UI\Implementation as I; use ILIAS\UI\Component as C; use ILIAS\Data\Factory; +use NameSourceStubs; /** * @author Thibeau Fuhrer */ abstract class FileTestBase extends \ILIAS_UI_TestBase { + use NameSourceStubs; + protected C\Dropzone\File\Factory $factory; protected I\Component\Input\Field\File $input; private I\Component\Button\Factory $button_factory; @@ -44,6 +47,7 @@ public function setUp(): void $signal_generator = new I\Component\SignalGenerator(); $field_factory = new I\Component\Input\Field\Factory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(I\Component\Input\UploadLimitResolver::class), $signal_generator, $this->getDataFactory(), @@ -54,6 +58,7 @@ public function setUp(): void $this->factory = new I\Component\Dropzone\File\Factory( $signal_generator, $field_factory, + $this->createFixedNameSourceStub(''), ); $this->input = $field_factory->file($this->createMock(C\Input\Field\UploadHandler::class), ''); diff --git a/components/ILIAS/UI/tests/Component/Input/Container/Filter/FilterFactoryTest.php b/components/ILIAS/UI/tests/Component/Input/Container/Filter/FilterFactoryTest.php index db1dfe22a4c8..db8cfd19d2a4 100755 --- a/components/ILIAS/UI/tests/Component/Input/Container/Filter/FilterFactoryTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Container/Filter/FilterFactoryTest.php @@ -27,6 +27,8 @@ class FilterFactoryTest extends AbstractFactoryTestCase { + use NameSourceStubs; + public static array $kitchensink_info_settings = [ "standard" => [ "context" => false, @@ -44,12 +46,14 @@ final public function buildFactory(): Factory new SignalGenerator(), new \ILIAS\UI\Implementation\Component\Input\Field\Factory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(\ILIAS\UI\Implementation\Component\Input\UploadLimitResolver::class), new SignalGenerator(), $df, new ILIAS\Refinery\Factory($df, $language), $language - ) + ), + $this->createFixedNameSourceStub('name'), ); } diff --git a/components/ILIAS/UI/tests/Component/Input/Container/Filter/FilterInputTest.php b/components/ILIAS/UI/tests/Component/Input/Container/Filter/FilterInputTest.php index ecaa93ad5e7e..197064097bf0 100644 --- a/components/ILIAS/UI/tests/Component/Input/Container/Filter/FilterInputTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Container/Filter/FilterInputTest.php @@ -63,11 +63,14 @@ public function legacy(): I\Legacy\Factory class FilterInputTest extends ILIAS_UI_TestBase { + use NameSourceStubs; + protected function buildFactory(): I\Input\Container\Filter\Factory { return new I\Input\Container\Filter\Factory( new I\SignalGenerator(), - $this->buildInputFactory() + $this->buildInputFactory(), + $this->createFixedNameSourceStub('name'), ); } @@ -77,6 +80,7 @@ protected function buildInputFactory(): I\Input\Field\Factory $language = $this->createMock(ILIAS\Language\Language::class); return new I\Input\Field\Factory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(\ILIAS\UI\Implementation\Component\Input\UploadLimitResolver::class), new I\SignalGenerator(), $df, diff --git a/components/ILIAS/UI/tests/Component/Input/Container/Filter/FilterTest.php b/components/ILIAS/UI/tests/Component/Input/Container/Filter/FilterTest.php index 0e1a0b1ee7bf..43bfb72204ea 100755 --- a/components/ILIAS/UI/tests/Component/Input/Container/Filter/FilterTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Container/Filter/FilterTest.php @@ -32,16 +32,6 @@ use PHPUnit\Framework\MockObject\MockObject; use ILIAS\UI\Component\Input\Field\Group; -class FixedNameSourceFilter implements NameSource -{ - public string $name = "name"; - - public function getNewName(): string - { - return $this->name; - } -} - class ConcreteFilter extends Filter { public array $inputs; @@ -52,6 +42,7 @@ class ConcreteFilter extends Filter public function __construct( SignalGenerator $signal_generator, Input\Field\Factory $field_factory, + NameSource $name_source, $toggle_action_on, $toggle_action_off, $expand_action, @@ -67,6 +58,7 @@ public function __construct( parent::__construct( $signal_generator, $field_factory, + $name_source, $toggle_action_on, $toggle_action_off, $expand_action, @@ -112,11 +104,14 @@ public function setInputs(array $inputs): void */ class FilterTest extends ILIAS_UI_TestBase { + use NameSourceStubs; + protected function buildFactory(): Input\Container\Filter\Factory { return new ILIAS\UI\Implementation\Component\Input\Container\Filter\Factory( new SignalGenerator(), - $this->buildInputFactory() + $this->buildInputFactory(), + $this->createFixedNameSourceStub('name'), ); } @@ -126,6 +121,7 @@ protected function buildInputFactory(): Input\Field\Factory $language = $this->createMock(ILIAS\Language\Language::class); return new ILIAS\UI\Implementation\Component\Input\Field\Factory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(\ILIAS\UI\Implementation\Component\Input\UploadLimitResolver::class), new SignalGenerator(), $df, @@ -175,52 +171,12 @@ public function buildDataFactory(): Data\Factory return new Data\Factory(); } - public function testGetInputs(): void - { - $f = $this->buildFactory(); - $if = $this->buildInputFactory(); - $name_source = new FixedNameSourceFilter(); - - $inputs = [$if->text(""), $if->select("", [])]; - $inputs_rendered = [true, true]; - $filter = $f->standard( - "#", - "#", - "#", - "#", - "#", - "#", - $inputs, - $inputs_rendered, - false, - false - ); - - $seen_names = []; - $inputs = $filter->getInputs(); - - foreach ($inputs as $input) { - $name = $input->getName(); - $name_source->name = $name; - - // name is a string - $this->assertIsString($name); - - // only name is attached - $input = array_shift($inputs); - $this->assertEquals($input->withNameFrom($name_source), $input); - - // every name can only be contained once. - $this->assertNotContains($name, $seen_names); - $seen_names[] = $name; - } - } - public function testExtractParamData(): void { $filter = new ConcreteFilter( new SignalGenerator(), $this->buildInputFactory(), + $this->createFixedNameSourceStub('name'), "#", "#", "#", @@ -273,6 +229,7 @@ public function testWithRequest(): void $filter = new ConcreteFilter( new SignalGenerator(), $this->buildInputFactory(), + $this->createFixedNameSourceStub('name'), "#", "#", "#", @@ -326,6 +283,7 @@ public function testGetData(): void $filter = new ConcreteFilter( new SignalGenerator(), $this->buildInputFactory(), + $this->createFixedNameSourceStub('name'), "#", "#", "#", diff --git a/components/ILIAS/UI/tests/Component/Input/Container/Filter/StandardFilterTest.php b/components/ILIAS/UI/tests/Component/Input/Container/Filter/StandardFilterTest.php index 99f20a3d5fbc..d8c6488c1f28 100755 --- a/components/ILIAS/UI/tests/Component/Input/Container/Filter/StandardFilterTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Container/Filter/StandardFilterTest.php @@ -79,11 +79,16 @@ public function listing(): I\Listing\Factory class StandardFilterTest extends ILIAS_UI_TestBase { + use NameSourceStubs; + + protected string $filter_input_name = 'filter_input_name'; + protected function buildFactory(): I\Input\Container\Filter\Factory { return new I\Input\Container\Filter\Factory( new I\SignalGenerator(), - $this->buildInputFactory() + $this->buildInputFactory(), + $this->createFixedNameSourceStub($this->filter_input_name), ); } @@ -93,6 +98,7 @@ protected function buildInputFactory(): I\Input\Field\Factory $language = $this->createMock(ILIAS\Language\Language::class); return new I\Input\Field\Factory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(\ILIAS\UI\Implementation\Component\Input\UploadLimitResolver::class), new I\SignalGenerator(), $df, @@ -215,7 +221,7 @@ public function testRenderActivatedCollapsed(): void
- + @@ -226,7 +232,7 @@ public function testRenderActivatedCollapsed(): void
- @@ -353,7 +359,7 @@ public function testRenderDeactivatedCollapsed(): void
- + @@ -364,7 +370,7 @@ public function testRenderDeactivatedCollapsed(): void
- @@ -491,7 +497,7 @@ public function testRenderActivatedExpanded(): void
- + @@ -502,7 +508,7 @@ public function testRenderActivatedExpanded(): void
- @@ -629,7 +635,7 @@ public function testRenderDeactivatedExpanded(): void
- + @@ -640,7 +646,7 @@ public function testRenderDeactivatedExpanded(): void
- @@ -700,32 +706,4 @@ public function testRenderDeactivatedExpanded(): void $this->assertHTMLEquals($this->brutallyTrimHTML($expected), $this->brutallyTrimHTML($html)); } - - public function testDedicatedNames(): void - { - $f = $this->buildFactory(); - $if = $this->buildInputFactory(); - $inputs = [ - $if->text("Title")->withDedicatedName('title'), - $if->select("Selection", ["one" => "One", "two" => "Two", "three" => "Three"])->withDedicatedName('selection'), - $if->multiSelect("Multi Selection", ["one" => "Num One", "two" => "Num Two", "three" => "Num Three"]) - ]; - $filter = $f->standard( - "#", - "#", - "#", - "#", - "#", - "#", - $inputs, - [true, true, true], - true, - true - ); - - $inputs = $filter->getInputs(); - $this->assertEquals('filter_input_0/title', $inputs[0]->getName()); - $this->assertEquals('filter_input_0/selection', $inputs[1]->getName()); - $this->assertEquals('filter_input_0/filter_input_1', $inputs[2]->getName()); - } } diff --git a/components/ILIAS/UI/tests/Component/Input/Container/Form/FormFactoryTest.php b/components/ILIAS/UI/tests/Component/Input/Container/Form/FormFactoryTest.php index 1a39744b934b..9a55e77a6cfb 100755 --- a/components/ILIAS/UI/tests/Component/Input/Container/Form/FormFactoryTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Container/Form/FormFactoryTest.php @@ -28,6 +28,8 @@ class FormFactoryTest extends AbstractFactoryTestCase { + use NameSourceStubs; + public static array $kitchensink_info_settings = [ "standard" => [ "context" => false, @@ -44,13 +46,15 @@ final public function buildFactory(): I\Container\Form\Factory return new I\Container\Form\Factory( new I\Field\Factory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(\ILIAS\UI\Implementation\Component\Input\UploadLimitResolver::class), $signal_generator, $df, new Factory($df, $language), $language ), - $signal_generator + $signal_generator, + $this->createCountingNameSourceStub('input_'), ); } diff --git a/components/ILIAS/UI/tests/Component/Input/Container/Form/FormTest.php b/components/ILIAS/UI/tests/Component/Input/Container/Form/FormTest.php index 4a41cbc748b1..114c458e506e 100755 --- a/components/ILIAS/UI/tests/Component/Input/Container/Form/FormTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Container/Form/FormTest.php @@ -34,16 +34,6 @@ use ILIAS\Refinery\Factory as Refinery; use PHPUnit\Framework\MockObject\MockObject; -class FixedNameSource implements NameSource -{ - public string $name = "name"; - - public function getNewName(): string - { - return $this->name; - } -} - class ConcreteForm extends Form { public ?InputData $input_data = null; @@ -84,6 +74,8 @@ public function setInputs(array $inputs): void */ class FormTest extends ILIAS_UI_TestBase { + use NameSourceStubs; + /** * @var ILIAS\Language\Language|mixed|MockObject */ @@ -94,7 +86,8 @@ protected function buildFactory(): Input\Container\Form\Factory { return new Input\Container\Form\Factory( $this->buildInputFactory(), - new SignalGenerator() + new SignalGenerator(), + $this->createFixedNameSourceStub('name'), ); } @@ -104,6 +97,7 @@ protected function buildInputFactory(): Input\Field\Factory $this->language = $this->createMock(ILIAS\Language\Language::class); return new Input\Field\Factory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(\ILIAS\UI\Implementation\Component\Input\UploadLimitResolver::class), new SignalGenerator(), $df, @@ -136,39 +130,9 @@ public function buildDataFactory(): Data\Factory return new Data\Factory(); } - public function testGetInputs(): void - { - $this->buildFactory(); - $if = $this->buildInputFactory(); - $name_source = new FixedNameSource(); - - $inputs = [$if->text(""), $if->text("")]; - $form = new ConcreteForm($this->buildInputFactory(), new DefNamesource(), $inputs); - - $seen_names = []; - $form_inputs = $form->getInputs(); - $this->assertSameSize($inputs, $form_inputs); - - foreach ($form_inputs as $input) { - $name = $input->getName(); - $name_source->name = $name; - - // name is a string - $this->assertIsString($name); - - // only name is attached - $input = array_shift($form_inputs); - $this->assertEquals($input->withNameFrom($name_source), $input); - - // every name can only be contained once. - $this->assertNotContains($name, $seen_names); - $seen_names[] = $name; - } - } - public function testExtractPostData(): void { - $form = new ConcreteForm($this->buildInputFactory(), new DefNamesource(), []); + $form = new ConcreteForm($this->buildInputFactory(), $this->createFixedNameSourceStub('name'), []); $request = $this->createMock(ServerRequestInterface::class); $request ->expects($this->once()) @@ -206,7 +170,7 @@ public function testWithRequest(): void ->method("getContent") ->willReturn($df->ok(0)); - $form = new ConcreteForm($this->buildInputFactory(), new DefNamesource(), []); + $form = new ConcreteForm($this->buildInputFactory(), $this->createFixedNameSourceStub('name'), []); $form->setInputs([$input_1, $input_2]); $form->input_data = $input_data; @@ -245,7 +209,7 @@ public function testWithRequestRespectsKeys(): void ->method("getContent") ->willReturn($df->ok(0)); - $form = new ConcreteForm($this->buildInputFactory(), new DefNamesource(), []); + $form = new ConcreteForm($this->buildInputFactory(), $this->createFixedNameSourceStub('name'), []); $form->setInputs(["foo" => $input_1, "bar" => $input_2]); $form->input_data = $input_data; @@ -285,7 +249,7 @@ public function testGetData(): void ->method("withInput") ->willReturn($input_2); - $form = new ConcreteForm($this->buildInputFactory(), new DefNamesource(), []); + $form = new ConcreteForm($this->buildInputFactory(), $this->createFixedNameSourceStub('name'), []); $form->setInputs([$input_1, $input_2]); $form = $form->withRequest($request); $this->assertEquals([1, 2], $form->getData()); @@ -320,7 +284,7 @@ public function testGetDataRespectsKeys(): void ->method("withInput") ->willReturn($input_2); - $form = new ConcreteForm($this->buildInputFactory(), new DefNamesource(), []); + $form = new ConcreteForm($this->buildInputFactory(), $this->createFixedNameSourceStub('name'), []); $form->setInputs(["foo" => $input_1, "bar" => $input_2]); $form = $form->withRequest($request); $this->assertEquals(["foo" => 1, "bar" => 2], $form->getData()); @@ -355,7 +319,7 @@ public function testGetDataFaulty(): void ->method("withInput") ->willReturn($input_2); - $form = new ConcreteForm($this->buildInputFactory(), new DefNamesource(), []); + $form = new ConcreteForm($this->buildInputFactory(), $this->createFixedNameSourceStub('name'), []); $form->setInputs(["foo" => $input_1, "bar" => $input_2]); $i18n = "THERE IS SOME ERROR IN THIS GROUP"; @@ -399,7 +363,7 @@ public function testWithAdditionalTransformation(): void ->method("withInput") ->willReturn($input_2); - $form = new ConcreteForm($this->buildInputFactory(), new DefNamesource(), []); + $form = new ConcreteForm($this->buildInputFactory(), $this->createFixedNameSourceStub('name'), []); $form->setInputs([$input_1, $input_2]); $form2 = $form->withAdditionalTransformation($this->buildTransformation(function () { @@ -421,7 +385,7 @@ public function testNameInputsRespectsKeys(): void 1 => $if->text(""), $if->text(""), ]; - $form = new ConcreteForm($this->buildInputFactory(), new DefNamesource(), []); + $form = new ConcreteForm($this->buildInputFactory(), $this->createFixedNameSourceStub('name'), []); $form->setInputs($inputs); $named_inputs = $form->getInputs(); $this->assertEquals(array_keys($inputs), array_keys($named_inputs)); @@ -474,7 +438,7 @@ public function testFormWithoutRequiredField(): void $f = $this->buildFactory(); $if = $this->buildInputFactory(); $inputs = [$if->text(""), $if->text("")]; - $form = new ConcreteForm($this->buildInputFactory(), new DefNamesource(), $inputs); + $form = new ConcreteForm($this->buildInputFactory(), $this->createFixedNameSourceStub('name'), $inputs); $this->assertFalse($form->hasRequiredInputs()); } @@ -487,7 +451,7 @@ public function testFormWithRequiredField(): void $if->text("")->withRequired(true), $if->text("") ]; - $form = new ConcreteForm($this->buildInputFactory(), new DefNamesource(), $inputs); + $form = new ConcreteForm($this->buildInputFactory(), $this->createFixedNameSourceStub('name'), $inputs); $this->assertTrue($form->hasRequiredInputs()); } } diff --git a/components/ILIAS/UI/tests/Component/Input/Container/Form/FormWithoutSubmitButtonsTest.php b/components/ILIAS/UI/tests/Component/Input/Container/Form/FormWithoutSubmitButtonsTest.php index 733688529f02..0ec2ab998f1a 100755 --- a/components/ILIAS/UI/tests/Component/Input/Container/Form/FormWithoutSubmitButtonsTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Container/Form/FormWithoutSubmitButtonsTest.php @@ -29,35 +29,17 @@ use ILIAS\Language\Language; use Psr\Http\Message\ServerRequestInterface; use ILIAS\UI\Implementation\Component\Button\Factory as ButtonFactory; +use NameSourceStubs; require_once(__DIR__ . "/../../../../Base.php"); -class InputNameSource implements NameSource -{ - public int $count = 0; - - public function getNewName(): string - { - $name = "form_input_$this->count"; - $this->count++; - - return $name; - } - - public function getNewDedicatedName(string $dedicated_name): string - { - $name = $dedicated_name . "_$this->count"; - $this->count++; - - return $name; - } -} - /** * @author Thibeau Fuhrer */ class FormWithoutSubmitButtonsTest extends \ILIAS_UI_TestBase { + use NameSourceStubs; + protected SignalGenerator $signal_generator; protected NameSource $namesource; protected Refinery $refinery; @@ -67,7 +49,7 @@ class FormWithoutSubmitButtonsTest extends \ILIAS_UI_TestBase public function setUp(): void { $this->signal_generator = new \SignalGeneratorMock(); - $this->namesource = new InputNameSource(); + $this->namesource = $this->createCountingNameSourceStub('input_'); $this->language = $this->getLanguage(); $this->refinery = new Refinery( new \ILIAS\Data\Factory(), @@ -156,17 +138,18 @@ static function ($value): bool { ) ); + $input_name = 'input_name_1'; $form = new StandardForm( $this->signal_generator, $this->buildInputFactory(), - $this->namesource, + $this->createFixedNameSourceStub($input_name), $post_url, [$dummy_input] ); $request = $this->createMock(ServerRequestInterface::class); $request->method('getParsedBody')->willReturn([ - 'form_0/form_input_1' => '', + $input_name => '', ]); $form = $form->withRequest($request); @@ -194,6 +177,7 @@ protected function buildInputFactory(): InputFactory $df = new \ILIAS\Data\Factory(); return new \ILIAS\UI\Implementation\Component\Input\Field\Factory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(\ILIAS\UI\Implementation\Component\Input\UploadLimitResolver::class), $this->signal_generator, $df, diff --git a/components/ILIAS/UI/tests/Component/Input/Container/Form/StandardFormTest.php b/components/ILIAS/UI/tests/Component/Input/Container/Form/StandardFormTest.php index c50961ee797d..2fed7f3e540c 100755 --- a/components/ILIAS/UI/tests/Component/Input/Container/Form/StandardFormTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Container/Form/StandardFormTest.php @@ -46,40 +46,21 @@ public function button(): Factory } } -class InputNameSource implements NameSource -{ - public int $count = 0; - - public function getNewName(): string - { - $name = "input_{$this->count}"; - $this->count++; - - return $name; - } - - public function getNewDedicatedName(string $dedicated_name): string - { - $name = $dedicated_name . "_{$this->count}"; - $this->count++; - - return $name; - } -} - /** * Test on standard form implementation. */ class StandardFormTest extends ILIAS_UI_TestBase { use CommonFieldRendering; + use NameSourceStubs; protected function buildFactory(): I\Input\Container\Form\Factory { - + [$name_source] = $this->getDefaultNameSourceStub(); return new I\Input\Container\Form\Factory( $this->getFieldFactory(), - new SignalGenerator() + new SignalGenerator(), + $name_source, ); } @@ -104,14 +85,14 @@ public function testGetPostURL(): void protected function getTextFieldHtml(): string { + [,$name] = $this->getDefaultNameSourceStub(); return $this->getFormWrappedHtml( 'text-field-input', 'label', - '', + '', 'byline', 'id_1', null, - 'form/input_0' ); } @@ -233,6 +214,7 @@ public function testRenderWithErrorOnField(): void $if = new ILIAS\UI\Implementation\Component\Input\Field\Factory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(\ILIAS\UI\Implementation\Component\Input\UploadLimitResolver::class), new SignalGenerator(), $df, @@ -247,21 +229,22 @@ public function testRenderWithErrorOnField(): void $input = $input->withAdditionalTransformation($fail); - $form = new Form\Standard(new SignalGenerator(), $if, new InputNameSource(), '', [$input]); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $form = new Form\Standard(new SignalGenerator(), $if,$name_source, '', [$input]); $request = $this->createMock(ServerRequestInterface::class); $request ->expects($this->once()) ->method("getParsedBody") ->willReturn([ - 'form_0/input_1' => '' + $name => '' ]); $form = $form->withRequest($request); $this->assertNull($form->getData()); $html = $this->brutallyTrimHTML($r->render($form)); - $expected = $this->brutallyTrimHTML(' + $expected = <<
@@ -271,9 +254,9 @@ public function testRenderWithErrorOnField(): void
ui_error:testing error message
-
-
+
ui_error:This is invalid...
@@ -285,7 +268,8 @@ public function testRenderWithErrorOnField(): void
-'); +HTML; + $expected = $this->brutallyTrimHTML($expected); $this->assertEquals($expected, $html); $this->assertHTMLEquals($expected, $html); } @@ -300,6 +284,7 @@ public function testRenderWithErrorOnForm(): void $if = new ILIAS\UI\Implementation\Component\Input\Field\Factory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(\ILIAS\UI\Implementation\Component\Input\UploadLimitResolver::class), new SignalGenerator(), $df, @@ -312,7 +297,8 @@ public function testRenderWithErrorOnForm(): void }, "This is a fail on form."); $input = $if->text("label", "byline"); - $form = new Form\Standard(new SignalGenerator(), $if, new InputNameSource(), '', [$input]); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $form = new Form\Standard(new SignalGenerator(), $if, $name_source, '', [$input]); $form = $form->withAdditionalTransformation($fail); $request = $this->createMock(ServerRequestInterface::class); @@ -320,7 +306,7 @@ public function testRenderWithErrorOnForm(): void ->expects($this->once()) ->method("getParsedBody") ->willReturn([ - 'form_0/input_1' => '' + $name => '' ]); $form = $form->withRequest($request); @@ -330,11 +316,10 @@ public function testRenderWithErrorOnForm(): void $field_html = $this->getFormWrappedHtml( 'text-field-input', 'label', - '', + '', 'byline', 'id_1', null, - 'form_0/input_1' ); $html = $this->brutallyTrimHTML($r->render($form)); @@ -363,15 +348,14 @@ public function testStandardFormRenderWithRequired(): void $r = $this->getDefaultRenderer(); $html = $this->brutallyTrimHTML($r->render($form)); - + [,$name] = $this->getDefaultNameSourceStub(); $field_html = $this->getFormWrappedHtml( 'text-field-input', 'label*', - '', + '', 'byline', 'id_1', null, - 'form/input_0' ); $expected = $this->brutallyTrimHTML(' diff --git a/components/ILIAS/UI/tests/Component/Input/Container/ViewControl/ViewControlContainerTest.php b/components/ILIAS/UI/tests/Component/Input/Container/ViewControl/ViewControlContainerTest.php index 759049e9378f..17aaac604220 100755 --- a/components/ILIAS/UI/tests/Component/Input/Container/ViewControl/ViewControlContainerTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Container/ViewControl/ViewControlContainerTest.php @@ -25,7 +25,6 @@ use ILIAS\UI\Implementation\Component\Input\ViewControl as Control; use ILIAS\UI\Implementation\Component\Input\ArrayInputData; use ILIAS\UI\Implementation\Component\Input\Container\ViewControl as VC; -use ILIAS\UI\Implementation\Component\Input\FormInputNameSource; use ILIAS\Data; use ILIAS\Refinery\Factory as Refinery; use Psr\Http\Message\ServerRequestInterface; @@ -34,6 +33,8 @@ class ViewControlContainerTest extends ILIAS_UI_TestBase { + use NameSourceStubs; + protected function buildDataFactory(): Data\Factory { return new Data\Factory(); @@ -50,12 +51,14 @@ protected function buildContainerFactory(): VC\Factory return new VC\Factory( new I\SignalGenerator(), $this->buildVCFactory(), + $this->createCountingNameSourceStub('input_'), ); } protected function buildFieldFactory(): FieldFactory { return new FieldFactory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(UploadLimitResolver::class), new I\SignalGenerator(), $this->buildDataFactory(), @@ -82,27 +85,6 @@ public function testViewControlContainerConstruct(): void $this->assertInstanceOf(I\Signal::class, $vc->getSubmissionSignal()); } - public function testViewControlContainerWithControls(): void - { - $c_factory = $this->buildVCFactory(); - $controls = [ - $c_factory->fieldSelection([]), - $c_factory->sortation([]), - $c_factory->pagination() - ]; - - $name_source = new FormInputNameSource(); - $vc = $this->buildContainerFactory()->standard($controls); - $this->assertSameSize($controls, $vc->getInputs()); - - $named = array_map( - fn($input) => $input->withNameFrom($name_source, 'view_control'), - $vc->getInputs() - ); - - $this->assertEquals($named, $vc->getInputs()); - } - public function testViewControlContainerWithRequest(): void { $request = $this->createMock(ServerRequestInterface::class); @@ -110,9 +92,9 @@ public function testViewControlContainerWithRequest(): void ->expects($this->once()) ->method("getQueryParams") ->willReturn([ - 'view_control/input_0' => ['a1', 'a3'], - 'view_control/input_1/input_2' => 'a2', - 'view_control/input_1/input_3' => 'DESC' + 'input_1' => ['a1', 'a3'], + 'input_3' => 'a2', + 'input_4' => 'DESC' ]); $c_factory = $this->buildVCFactory(); @@ -190,9 +172,9 @@ public function testExtractCurrentValues(): void $this->assertEquals( [ - 'view_control/input_0' => ['a1', 'a3'], - 'view_control/input_1/input_2' => 'a2', - 'view_control/input_1/input_3' => 'DESC' + 'input_1' => ['a1', 'a3'], + 'input_3' => 'a2', + 'input_4' => 'DESC' ], $data ); diff --git a/components/ILIAS/UI/tests/Component/Input/DefaultNameSourceTest.php b/components/ILIAS/UI/tests/Component/Input/DefaultNameSourceTest.php new file mode 100755 index 000000000000..869f1186e29d --- /dev/null +++ b/components/ILIAS/UI/tests/Component/Input/DefaultNameSourceTest.php @@ -0,0 +1,98 @@ + + */ +class DefaultNameSourceTest extends TestCase +{ + public function testGetNextName(): void + { + $name_source = new DefaultNameSource(); + + $this->assertEquals('input_0', $name_source->getNextName()); + $this->assertEquals('input_1', $name_source->getNextName()); + $this->assertEquals('input_2', $name_source->getNextName()); + } + + public function testGetNextNameWithParentName(): void + { + $name_source = new DefaultNameSource(); + $parent_name = 'some_parent_name'; + $name_source = $name_source->withParentName($parent_name); + + $this->assertEquals("$parent_name/input_0", $name_source->getNextName()); + $this->assertEquals("$parent_name/input_1", $name_source->getNextName()); + $this->assertEquals("$parent_name/input_2", $name_source->getNextName()); + } + + public function testGetNextNameWithReset(): void + { + $name_source = new DefaultNameSource(); + $name_source->getNextName(); + $name_source->getNextName('some_dedicated_name'); + $name_source = $name_source->withParentName('some_parent_name'); + $name_source = $name_source->withReset(); + + $this->assertEquals('input_0', $name_source->getNextName()); + } + + public function testGetNextNameWithDedicatedName(): void + { + $name_source = new DefaultNameSource(); + $dedicated_name = 'some_dedicated_name'; + + $this->assertEquals($dedicated_name, $name_source->getNextName($dedicated_name)); + $this->expectException(\LogicException::class); + $name_source->getNextName($dedicated_name); + } + + public function testGetNextNameWithDedicatedNameAndWithParentName(): void + { + $name_source = new DefaultNameSource(); + $dedicated_name = 'some_dedicated_name'; + $parent_name = 'some_parent_name'; + + $this->assertEquals($dedicated_name, $name_source->getNextName($dedicated_name)); + $name_source = $name_source->withParentName($parent_name); + $this->assertEquals("$parent_name/$dedicated_name", $name_source->getNextName($dedicated_name)); + $this->expectException(\LogicException::class); + $name_source->getNextName($dedicated_name); + } + + public function testWithParentName(): void + { + $name_source_1 = new DefaultNameSource(); + $name_source_2 = $name_source_1->withParentName('some_parent_name'); + $this->assertNotSame($name_source_1, $name_source_2); + } + + public function testWithReset(): void + { + $name_source_1 = new DefaultNameSource(); + $name_source_2 = $name_source_1->withReset(); + $this->assertNotSame($name_source_1, $name_source_2); + } +} diff --git a/components/ILIAS/UI/tests/Component/Input/DynamicInputsNameSourceTest.php b/components/ILIAS/UI/tests/Component/Input/DynamicInputsNameSourceTest.php deleted file mode 100755 index da7eb33519ff..000000000000 --- a/components/ILIAS/UI/tests/Component/Input/DynamicInputsNameSourceTest.php +++ /dev/null @@ -1,47 +0,0 @@ - - */ -class DynamicInputsNameSourceTest extends TestCase -{ - public function testNewNameGeneration(): void - { - $expected_parent_name = 'parent_input_name_xyz'; - - $name_source = new DynamicInputsNameSource($expected_parent_name); - - $this->assertEquals( - $expected_parent_name . "[input_0][]", - $name_source->getNewName() - ); - - $this->assertEquals( - $expected_parent_name . "[input_1][]", - $name_source->getNewName() - ); - } -} diff --git a/components/ILIAS/UI/tests/Component/Input/Field/CheckboxInputTest.php b/components/ILIAS/UI/tests/Component/Input/Field/CheckboxInputTest.php index 490b585b1f6f..345de89e6aad 100755 --- a/components/ILIAS/UI/tests/Component/Input/Field/CheckboxInputTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/CheckboxInputTest.php @@ -27,17 +27,16 @@ use ILIAS\UI\Component\Input\Field; use ILIAS\Refinery\Factory as Refinery; use ILIAS\Data; +use ILIAS\UI\Implementation\Component\Input\NameSource; class CheckboxInputTest extends ILIAS_UI_TestBase { use CommonFieldRendering; - protected DefNamesource $name_source; protected Refinery $refinery; public function setUp(): void { - $this->name_source = new DefNamesource(); $this->refinery = new Refinery($this->createMock(Data\Factory::class), $this->createMock(ILIAS\Language\Language::class)); } @@ -56,11 +55,12 @@ public function testRender(): void $f = $this->getFieldFactory(); $label = "label"; $byline = "byline"; - $checkbox = $f->checkbox($label, $byline)->withNameFrom($this->name_source); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $checkbox = $f->checkbox($label, $byline)->withNameFrom($name_source); $expected = $this->getFormWrappedHtml( 'checkbox-field-input', $label, - '', + '', $byline, 'id_1' ); @@ -72,9 +72,10 @@ public function testRenderValue(): void $f = $this->getFieldFactory(); $label = "label"; $value = true; - $checkbox = $f->checkbox($label)->withValue($value)->withNameFrom($this->name_source); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $checkbox = $f->checkbox($label)->withValue($value)->withNameFrom($name_source); - $expected = ''; + $expected = ''; $this->assertStringContainsString($expected, $this->render($checkbox)); } @@ -95,7 +96,8 @@ public function testCommonRendering(): void { $f = $this->getFieldFactory(); $label = "label"; - $checkbox = $f->checkbox($label, null)->withNameFrom($this->name_source); + [$name_source] = $this->getDefaultNameSourceStub(); + $checkbox = $f->checkbox($label, null)->withNameFrom($name_source); $this->testWithError($checkbox); $this->testWithNoByline($checkbox); @@ -108,13 +110,14 @@ public function testTrueContent(): void { $f = $this->getFieldFactory(); $label = "label"; - $checkbox = $f->checkbox($label)->withNameFrom($this->name_source); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $checkbox = $f->checkbox($label)->withNameFrom($name_source); $input_data = $this->createMock(InputData::class); $input_data ->expects($this->atLeastOnce()) ->method("getOr") - ->with("name_0", "") + ->with($name, "") ->willReturn("checked"); $checkbox_true = $checkbox->withInput($input_data); @@ -127,13 +130,14 @@ public function testFalseContent(): void { $f = $this->getFieldFactory(); $label = "label"; - $checkbox = $f->checkbox($label)->withNameFrom($this->name_source); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $checkbox = $f->checkbox($label)->withNameFrom($name_source); $input_data = $this->createMock(InputData::class); $input_data ->expects($this->atLeastOnce()) ->method("getOr") - ->with("name_0", "") + ->with($name, "") ->willReturn(""); $checkbox_false = $checkbox->withInput($input_data); @@ -146,8 +150,9 @@ public function testDisabledContent(): void { $f = $this->getFieldFactory(); $label = "label"; + [$name_source] = $this->getDefaultNameSourceStub(); $checkbox = $f->checkbox($label) - ->withNameFrom($this->name_source) + ->withNameFrom($name_source) ->withDisabled(true) ->withValue(true) ->withInput($this->createMock(InputData::class)) @@ -162,8 +167,9 @@ public function testTransformation(): void $f = $this->getFieldFactory(); $label = "label"; $new_value = "NEW_VALUE"; + [$name_source] = $this->getDefaultNameSourceStub(); $checkbox = $f->checkbox($label) - ->withNameFrom($this->name_source) + ->withNameFrom($name_source) ->withDisabled(true) ->withValue(true) ->withAdditionalTransformation($this->refinery->custom()->transformation(function ($v) use (&$called, $new_value): string { diff --git a/components/ILIAS/UI/tests/Component/Input/Field/ColorSelectTest.php b/components/ILIAS/UI/tests/Component/Input/Field/ColorSelectTest.php index b8c03cf8f621..151ebd17d1a1 100755 --- a/components/ILIAS/UI/tests/Component/Input/Field/ColorSelectTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/ColorSelectTest.php @@ -33,13 +33,6 @@ class ColorSelectTest extends ILIAS_UI_TestBase { use CommonFieldRendering; - protected DefNamesource $name_source; - - public function setUp(): void - { - $this->name_source = new DefNamesource(); - } - public function testImplementsFactoryInterface(): void { $f = $this->getFieldFactory(); @@ -53,12 +46,13 @@ public function testRender(): void $f = $this->getFieldFactory(); $label = "label"; $byline = "byline"; - $cp = $f->colorSelect($label, $byline)->withNameFrom($this->name_source); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $cp = $f->colorSelect($label, $byline)->withNameFrom($name_source); $expected = $this->getFormWrappedHtml( 'color-select-field-input', $label, - '', + '', $byline, 'id_1' ); @@ -69,7 +63,8 @@ public function testCommonRendering(): void { $f = $this->getFieldFactory(); $label = "label"; - $color_select = $f->colorSelect($label, null)->withNameFrom($this->name_source); + [$name_source] = $this->getDefaultNameSourceStub(); + $color_select = $f->colorSelect($label, null)->withNameFrom($name_source); $this->testWithError($color_select); $this->testWithNoByline($color_select); @@ -84,14 +79,15 @@ public function testRenderValue(): void $label = "label"; $byline = "byline"; $value = "value_0"; + [$name_source, $name] = $this->getDefaultNameSourceStub(); $cp = $f->colorSelect($label, $byline) ->withValue($value) - ->withNameFrom($this->name_source); + ->withNameFrom($name_source); $expected = $this->getFormWrappedHtml( 'color-select-field-input', $label, - '', + '', $byline, 'id_1' ); @@ -103,9 +99,9 @@ public function testValueRequired(): void $f = $this->getFieldFactory(); $label = "label"; $byline = "byline"; - $name = "name_0"; + [$name_source, $name] = $this->getDefaultNameSourceStub(); $cp = $f->colorSelect($label, $byline) - ->withNameFrom($this->name_source) + ->withNameFrom($name_source) ->withRequired(true); $cp1 = $cp->withInput(new DefInputData([$name => "#FFF"])); diff --git a/components/ILIAS/UI/tests/Component/Input/Field/CommonFieldRendering.php b/components/ILIAS/UI/tests/Component/Input/Field/CommonFieldRendering.php index 167f1d3e02dd..0ae5d940d8c6 100644 --- a/components/ILIAS/UI/tests/Component/Input/Field/CommonFieldRendering.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/CommonFieldRendering.php @@ -25,14 +25,33 @@ use ILIAS\Data; use ILIAS\UI\Component\Input\Container\Form\FormInput; use ILIAS\UI\Implementation\Render\JavaScriptBinding; +use PHPUnit\Framework\MockObject\MockObject; trait CommonFieldRendering { + use NameSourceStubs; + + protected string $default_input_name = 'default_input_name'; + protected string $dynamic_input_name = 'dynamic_input_name'; + + /** @return array{0: I\Input\NameSource&MockObject, 1: string} */ + protected function getDefaultNameSourceStub(): array + { + return [$this->createFixedNameSourceStub($this->default_input_name), $this->default_input_name]; + } + + /** @return array{0: I\Input\HasDynamicInputsNameSource&MockObject, 1: string} */ + protected function getDefaultHasDynamicInputNameSourceStub(): array + { + return [$this->createFixedHasDynamicInputsNameSourceStub($this->dynamic_input_name), $this->dynamic_input_name]; + } + protected function getFieldFactory(): I\Input\Field\Factory { $df = new Data\Factory(); return new I\Input\Field\Factory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createFixedHasDynamicInputsNameSourceStub($this->dynamic_input_name), $this->createMock(UploadLimitResolver::class), new SignalGenerator(), $df, @@ -73,7 +92,7 @@ protected function testWithNoByline(FormInput $component): void protected function testWithDisabled(FormInput $component): void { $type = $this->getDefaultRenderer()->getComponentCanonicalNameAttribute($component); - $expected = '
assertStringContainsString($expected, $this->render($component->withDisabled(true))); } @@ -120,11 +139,12 @@ protected function getFormWrappedHtml( ?string $byline = null, ?string $label_id = null, ?string $js_id = null, - ?string $name = 'name_0', + ?string $name = null, ): string { $label_id = $label_id ? " for=\"$label_id\"" : ''; $tab = $label_id ? '' : ' tabindex="0"'; $js_id = $js_id ? " id=\"$js_id\"" : ''; + $name = $name ?? $this->default_input_name; if ($type === 'section-field-input') { $headline_tag_open = "

"; $headline_tag_close = "

"; diff --git a/components/ILIAS/UI/tests/Component/Input/Field/DateTimeInputTest.php b/components/ILIAS/UI/tests/Component/Input/Field/DateTimeInputTest.php index 9fe4e6354672..836c3a668061 100755 --- a/components/ILIAS/UI/tests/Component/Input/Field/DateTimeInputTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/DateTimeInputTest.php @@ -33,13 +33,11 @@ class DateTimeInputTest extends ILIAS_UI_TestBase { use CommonFieldRendering; - protected DefNamesource $name_source; protected Data\Factory $data_factory; protected I\Input\Field\Factory $factory; public function setUp(): void { - $this->name_source = new DefNamesource(); $this->data_factory = new Data\Factory(); $this->factory = $this->buildFactory(); } @@ -75,6 +73,7 @@ protected function buildFactory(): I\Input\Field\Factory return new I\Input\Field\Factory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(\ILIAS\UI\Implementation\Component\Input\UploadLimitResolver::class), new SignalGenerator(), $this->data_factory, @@ -193,8 +192,9 @@ public function testRender(): void public function testCommonRendering(): void { + [$name_source] = $this->getDefaultNameSourceStub(); $datetime = $this->factory->dateTime('label') - ->withNameFrom($this->name_source); + ->withNameFrom($name_source); $this->testWithError($datetime); $this->testWithNoByline($datetime); diff --git a/components/ILIAS/UI/tests/Component/Input/Field/DurationInputTest.php b/components/ILIAS/UI/tests/Component/Input/Field/DurationInputTest.php index 8b3530ad6800..3efb5d3ce8a9 100755 --- a/components/ILIAS/UI/tests/Component/Input/Field/DurationInputTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/DurationInputTest.php @@ -34,14 +34,12 @@ class DurationInputTest extends ILIAS_UI_TestBase { use CommonFieldRendering; - protected DefNamesource $name_source; protected Data\Factory $data_factory; protected I\Input\Field\Factory $factory; protected Language $lng; public function setUp(): void { - $this->name_source = new DefNamesource(); $this->data_factory = new Data\Factory(); $this->factory = $this->buildFactory(); } @@ -64,6 +62,7 @@ protected function buildFactory(): I\Input\Field\Factory { return new I\Input\Field\Factory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(\ILIAS\UI\Implementation\Component\Input\UploadLimitResolver::class), new SignalGenerator(), $this->data_factory, @@ -163,8 +162,9 @@ public function testWithoutByline(): void public function testRender(): \ILIAS\UI\Component\Input\Field\Duration { + [$name_source, $name] = $this->getDefaultNameSourceStub(); $duration = $this->factory->duration('label', 'byline') - ->withNameFrom($this->name_source); + ->withNameFrom($name_source); $label_start = 'duration_default_label_start'; $label_end = 'duration_default_label_end'; @@ -172,25 +172,23 @@ public function testRender(): \ILIAS\UI\Component\Input\Field\Duration 'date-time-field-input', $label_start, '
- +
', null, 'id_1', null, - 'name_0/start_1' ); $f2 = $this->getFormWrappedHtml( 'date-time-field-input', $label_end, '
- +
', null, 'id_2', null, - 'name_0/end_2' ); $expected = $this->getFormWrappedHtml( @@ -208,6 +206,7 @@ public function testRenderWithDifferentLabels($duration): void { $other_start_label = 'other startlabel'; $other_end_label = 'other endlabel'; + [,$name] = $this->getDefaultNameSourceStub(); $duration = $duration->withLabels($other_start_label, $other_end_label); @@ -215,25 +214,23 @@ public function testRenderWithDifferentLabels($duration): void 'date-time-field-input', $other_start_label, '
- +
', null, 'id_1', null, - 'name_0/start_1' ); $f2 = $this->getFormWrappedHtml( 'date-time-field-input', $other_end_label, '
- +
', null, 'id_2', null, - 'name_0/end_2' ); $expected = $this->getFormWrappedHtml( @@ -247,8 +244,9 @@ public function testRenderWithDifferentLabels($duration): void public function testCommonRendering(): void { + [$name_source] = $this->getDefaultNameSourceStub(); $duration = $this->factory->duration('label') - ->withNameFrom($this->name_source); + ->withNameFrom($name_source); $this->testWithError($duration); $this->testWithNoByline($duration); $this->testWithRequired($duration); diff --git a/components/ILIAS/UI/tests/Component/Input/Field/FieldFactoryTest.php b/components/ILIAS/UI/tests/Component/Input/Field/FieldFactoryTest.php index a449c6a70202..5af084f2c090 100755 --- a/components/ILIAS/UI/tests/Component/Input/Field/FieldFactoryTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/FieldFactoryTest.php @@ -73,6 +73,7 @@ final public function buildFactory(): I\Input\Field\Factory $language = $this->createMock(ILIAS\Language\Language::class); return new I\Input\Field\Factory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(\ILIAS\UI\Implementation\Component\Input\UploadLimitResolver::class), new SignalGenerator(), $df, diff --git a/components/ILIAS/UI/tests/Component/Input/Field/FileInputTest.php b/components/ILIAS/UI/tests/Component/Input/Field/FileInputTest.php index 4d8a24a2b145..3ba671cec374 100755 --- a/components/ILIAS/UI/tests/Component/Input/Field/FileInputTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/FileInputTest.php @@ -58,13 +58,6 @@ class FileInputTest extends ILIAS_UI_TestBase { use CommonFieldRendering; - protected DefNamesource $name_source; - - public function setUp(): void - { - $this->name_source = new DefNamesource(); - } - protected function brutallyTrimHTML(string $html): string { $html = str_replace(" />", "/>", $html); @@ -145,7 +138,9 @@ public function testRender(): void $f = $this->getFieldFactory(); $label = "label"; $byline = "byline"; - $file_input = $f->file($this->getUploadHandler(), $label, $byline)->withNameFrom($this->name_source); + [$name_source, $default_name] = $this->getDefaultNameSourceStub(); + [,$dynamic_name] = $this->getDefaultHasDynamicInputNameSourceStub(); + $file_input = $f->file($this->getUploadHandler(), $label, $byline)->withNameFrom($name_source); $expected = $this->getFormWrappedHtml( 'file-field-input', @@ -161,7 +156,7 @@ public function testRender(): void class="glyphicon glyphicon-remove" aria-hidden="true">
+ name="' . $dynamic_name . '" value="" />
@@ -185,8 +180,9 @@ class="ui-input-file-input-error-msg" data-dz-error-msg>
public function testCommonRendering(): void { $f = $this->getFieldFactory(); + [$name_source] = $this->getDefaultNameSourceStub(); $file_input = $f->file($this->getUploadHandler(), 'label', null) - ->withNameFrom($this->name_source); + ->withNameFrom($name_source); $this->testWithError($file_input); $this->testWithNoByline($file_input); $this->testWithRequired($file_input); @@ -204,12 +200,14 @@ public function testRenderValue(): void $test_file_info->method('getName')->willReturn("test file name 1"); $test_file_info->method('getSize')->willReturn(1001); + [$name_source, $default_name] = $this->getDefaultNameSourceStub(); + [,$dynamic_name] = $this->getDefaultHasDynamicInputNameSourceStub(); $file_input = $this->getFieldFactory()->file( $this->getUploadHandler($test_file_info), "", )->withValue([ $test_file_id, - ])->withNameFrom($this->name_source); + ])->withNameFrom($name_source); $expected = $this->getFormWrappedHtml( 'file-field-input', @@ -231,7 +229,7 @@ public function testRenderValue(): void
@@ -245,7 +243,7 @@ public function testRenderValue(): void class="glyphicon glyphicon-remove" aria-hidden="true">
+ name="' . $dynamic_name . '" value="" />
@@ -272,6 +270,9 @@ public function testRenderWithMetadata(): void $factory = $this->getFieldFactory(); $label = 'file_input'; $metadata_input = $factory->text("text_input"); + [$name_source, $default_name] = $this->getDefaultNameSourceStub(); + [,$dynamic_name] = $this->getDefaultHasDynamicInputNameSourceStub(); + $file_input = $factory->file( ($u = $this->getUploadHandler()), $label, @@ -282,7 +283,7 @@ public function testRenderWithMetadata(): void "file_id", "" ] - ])->withNameFrom($this->name_source); + ])->withNameFrom($name_source); $expected = $this->getFormWrappedHtml( 'file-field-input', @@ -302,10 +303,10 @@ class="glyphicon glyphicon-triangle-bottom" aria-hidden="true">
@@ -326,10 +327,10 @@ class="glyphicon glyphicon-triangle-bottom" aria-hidden="true">
@@ -365,6 +366,8 @@ public function testRenderWithMetadataValue(): void $factory = $this->getFieldFactory(); $label = 'file_input'; + [$name_source, $default_name] = $this->getDefaultNameSourceStub(); + [,$dynamic_name] = $this->getDefaultHasDynamicInputNameSourceStub(); $metadata_input = $factory->text("text_input"); $file_input = $factory->file( $u = $this->getUploadHandler($test_file_info), @@ -376,7 +379,7 @@ public function testRenderWithMetadataValue(): void $test_file_id, "test", ] - ])->withNameFrom($this->name_source); + ])->withNameFrom($name_source); $expected = $this->getFormWrappedHtml( @@ -397,11 +400,11 @@ class="glyphicon glyphicon-triangle-bottom" aria-hidden="true">
@@ -421,10 +424,10 @@ class="glyphicon glyphicon-triangle-bottom" aria-hidden="true">
@@ -442,7 +445,7 @@ class="c-field-text" />
', null, null, - 'id_6' + 'id_6', ); $this->assertEquals($expected, $this->render($file_input)); } diff --git a/components/ILIAS/UI/tests/Component/Input/Field/HasDynamicInputsTest.php b/components/ILIAS/UI/tests/Component/Input/Field/HasDynamicInputsTest.php index b9a11b761d8e..09a752eab43f 100755 --- a/components/ILIAS/UI/tests/Component/Input/Field/HasDynamicInputsTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/HasDynamicInputsTest.php @@ -28,12 +28,20 @@ use ILIAS\Data\Factory as DataFactory; use ILIAS\Language\Language; use Closure; +use NameSourceStubs; +use ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource; +use PHPUnit\Framework\MockObject\MockObject; /** * @author Thibeau Fuhrer */ class HasDynamicInputsTest extends TestCase { + use NameSourceStubs; + + protected HasDynamicInputsNameSource & MockObject $has_dynamic_inputs_name_source_stub; + protected string $dynamic_input_name = 'dynamic_input_name'; + protected HasDynamicInputs $input; protected DataFactory $data_factory; protected Language $language; @@ -41,10 +49,19 @@ class HasDynamicInputsTest extends TestCase public function setUp(): void { + $this->has_dynamic_inputs_name_source_stub = $this->createFixedHasDynamicInputsNameSourceStub($this->dynamic_input_name); $this->data_factory = $this->createMock(DataFactory::class); $this->language = $this->createMock(Language::class); $this->refinery = $this->createMock(Refinery::class); - $this->input = new class ($this->language, $this->data_factory, $this->refinery, $this->getTestInputTemplate(), 'test_input_name', 'test_byline') extends HasDynamicInputs { + $this->input = new class ( + $this->language, + $this->data_factory, + $this->refinery, + $this->has_dynamic_inputs_name_source_stub, + $this->getTestInputTemplate(), + 'test_input_name', + 'test_byline' + ) extends HasDynamicInputs { public function getUpdateOnLoadCode(): Closure { return static function () { @@ -121,43 +138,41 @@ public function testDynamicInputDisabilityAfterDuplication(): void $this->assertTrue($dynamic_input->isDisabled()); } - /** - * the input names are always the same, because the names generated from - * DynamicInputsNameSource are stackable. - */ - public function testDynamicInputNameGeneration(): void + public function testWithNameFrom(): void { - $input_name = 'test_name[input_0][]'; - $dynamic_input = $this->input->withValue(['', '']); - $dynamic_input = $dynamic_input->withNameFrom( - $this->getTestNameSource() - ); - - $this->assertEquals( - $input_name, - $dynamic_input->getTemplateForDynamicInputs()->getName() - ); - - $generated_inputs = $dynamic_input->getGeneratedDynamicInputs(); - $this->assertEquals( - $input_name, - $generated_inputs[0]->getName() - ); - - $this->assertEquals( - $input_name, - $generated_inputs[1]->getName() - ); + $default_input_name = 'default_input_name'; + $default_name_source = $this->createFixedNameSourceStub($default_input_name); + + $this->has_dynamic_inputs_name_source_stub->expects($this->atLeast(1))->method('withReset'); + $this->has_dynamic_inputs_name_source_stub->expects($this->atLeast(1))->method('withParentName')->with($default_input_name); + $this->has_dynamic_inputs_name_source_stub->method('withIndices')->willReturnCallback(function($arg) { + static $count = 0; + $expected = [false, true]; + $this->assertEquals($expected[$count], $arg); + $count++; + return $this->has_dynamic_inputs_name_source_stub; + }); + + $dynamic_input = $this->input->withNameFrom($default_name_source); } - protected function getTestNameSource(): NameSource + public function testWithNameFromWithDedicatedName(): void { - return new class () implements NameSource { - public function getNewName(): string - { - return 'test_name'; - } - }; + $dedicated_name = 'dedicated_input_name'; + $default_name_source = $this->createRelayArgumentNameSourceStub(); + + $this->has_dynamic_inputs_name_source_stub->expects($this->exactly(2))->method('withReset'); + $this->has_dynamic_inputs_name_source_stub->expects($this->exactly(2))->method('withParentName')->with($dedicated_name); + $this->has_dynamic_inputs_name_source_stub->expects($this->exactly(2))->method('withResetDefaultNameSource'); + $this->has_dynamic_inputs_name_source_stub->method('withIndices')->willReturnCallback(function($arg) { + static $count = 0; + $expected = [false, true]; + $this->assertEquals($expected[$count], $arg); + $count++; + return $this->has_dynamic_inputs_name_source_stub; + }); + + $dynamic_input = $this->input->withDedicatedName($dedicated_name)->withValue(['', ''])->withNameFrom($default_name_source); } protected function getTestInputTemplate() diff --git a/components/ILIAS/UI/tests/Component/Input/Field/HiddenInputTest.php b/components/ILIAS/UI/tests/Component/Input/Field/HiddenInputTest.php index a506ba7e2819..7581861ceb0e 100755 --- a/components/ILIAS/UI/tests/Component/Input/Field/HiddenInputTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/HiddenInputTest.php @@ -30,13 +30,14 @@ class HiddenInputTest extends ILIAS_UI_TestBase { use CommonFieldRendering; + use NameSourceStubs; - protected DefNamesource $name_source; + protected I\Input\NameSource $name_source; protected I\Input\Field\Hidden $input; public function setUp(): void { - $this->name_source = new DefNamesource(); + $this->name_source = $this->createCountingNameSourceStub('name_'); $this->input = new I\Input\Field\Hidden( new Data\Factory(), new Refinery( diff --git a/components/ILIAS/UI/tests/Component/Input/Field/InputTest.php b/components/ILIAS/UI/tests/Component/Input/Field/InputTest.php index 11f7ad9ed4e6..edb0dc529288 100755 --- a/components/ILIAS/UI/tests/Component/Input/Field/InputTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/InputTest.php @@ -51,27 +51,6 @@ public function getUpdateOnLoadCode(): Closure } } -class DefNamesource implements NameSource -{ - public int $count = 0; - - public function getNewName(): string - { - $name = "name_{$this->count}"; - $this->count++; - - return $name; - } - - public function getNewDedicatedName($dedicated_name = 'dedicated_name'): string - { - $name = $dedicated_name . "_{$this->count}"; - $this->count++; - - return $name; - } -} - class DefInputData implements InputData { public array $values = array(); @@ -122,11 +101,13 @@ public function has($name): bool */ class InputTest extends ILIAS_UI_TestBase { + use NameSourceStubs; + protected DataFactory $data_factory; protected Refinery $refinery; protected DefInput $input; protected DefInput $dedicated_input; - protected DefNamesource $name_source; + protected NameSource $name_source; protected FormInput $named_input; public function setUp(): void @@ -141,7 +122,7 @@ public function setUp(): void "byline" ); $this->named_input = $this->input->withDedicatedName('dedicated_name'); - $this->name_source = new DefNamesource(); + $this->name_source = $this->createCountingNameSourceStub('name_'); } public function testConstructor(): void @@ -220,24 +201,22 @@ public function testWithValueThrows(): void $this->assertEquals(null, $this->input->getValue()); } - public function testWithName(): void + public function testWithNameFrom(): void { $name = "name_0"; $input = $this->input->withNameFrom($this->name_source); $this->assertEquals(null, $this->input->getName()); $this->assertEquals($name, $input->getName()); $this->assertNotSame($this->input, $input); - $this->assertEquals(1, $this->name_source->count); } public function testWithNameForNamedInput(): void { $name = "dedicated_name_0"; - $input = $this->named_input->withNameFrom($this->name_source); + $input = $this->named_input->withDedicatedName($name)->withNameFrom($this->createRelayArgumentNameSourceStub()); $this->assertEquals(null, $this->named_input->getName()); $this->assertEquals($name, $input->getName()); $this->assertNotSame($this->named_input, $input); - $this->assertEquals(1, $this->name_source->count); } public function testWithError(): void diff --git a/components/ILIAS/UI/tests/Component/Input/Field/LinkInputTest.php b/components/ILIAS/UI/tests/Component/Input/Field/LinkInputTest.php index b54aa37b6832..f4a61d04d396 100755 --- a/components/ILIAS/UI/tests/Component/Input/Field/LinkInputTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/LinkInputTest.php @@ -34,13 +34,6 @@ class LinkInputTest extends ILIAS_UI_TestBase { use CommonFieldRendering; - private DefNamesource $name_source; - - public function setUp(): void - { - $this->name_source = new DefNamesource(); - } - public function testImplementsFactoryInterface(): void { $factory = $this->getFieldFactory(); @@ -54,25 +47,24 @@ public function testRendering(): void $factory = $this->getFieldFactory(); $label = "Test Label"; $byline = "Test Byline"; - $link = $factory->link($label, $byline)->withNameFrom($this->name_source); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $link = $factory->link($label, $byline)->withNameFrom($name_source); $f1 = $this->getFormWrappedHtml( 'text-field-input', 'ui_link_label', - '', + '', null, 'id_1', null, - 'name_0/label_1' ); $f2 = $this->getFormWrappedHtml( 'url-field-input', 'ui_link_url', - '', + '', null, 'id_2', null, - 'name_0/url_2' ); $expected = $this->getFormWrappedHtml( @@ -88,7 +80,8 @@ public function testCommonRendering(): void { $f = $this->getFieldFactory(); $label = "label"; - $link = $f->link($label, null)->withNameFrom($this->name_source); + [$name_source] = $this->getDefaultNameSourceStub(); + $link = $f->link($label, null)->withNameFrom($name_source); $this->testWithError($link); $this->testWithNoByline($link); @@ -101,16 +94,7 @@ public function testProducesNullWhenNoDataExists(): void { $f = $this->getFieldFactory(); $input = $f->link("", "") - ->withNameFrom(new class () implements NameSource { - public function getNewName(): string - { - return "name"; - } - public function getNewDedicatedName(): string - { - return "dedicated_name"; - } - }); + ->withNameFrom($this->createFixedNameSourceStub('name')); $input = $input->withInput(new class () implements InputData { public function getOr($_, $default): string { diff --git a/components/ILIAS/UI/tests/Component/Input/Field/MarkdownTest.php b/components/ILIAS/UI/tests/Component/Input/Field/MarkdownTest.php index 2d7afe672527..e9fe06f05cde 100755 --- a/components/ILIAS/UI/tests/Component/Input/Field/MarkdownTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/MarkdownTest.php @@ -33,6 +33,7 @@ use ILIAS\Refinery\Factory as Refinery; use ILIAS\Data\Factory as DataFactory; use ILIAS\UI\Implementation\Render\JavaScriptBinding; +use ILIAS\UI\Implementation\Component\Input\NameSource; require_once(__DIR__ . "/../../../../../../../vendor/composer/vendor/autoload.php"); require_once(__DIR__ . "/../../../Base.php"); @@ -43,11 +44,13 @@ */ class MarkdownTest extends ILIAS_UI_TestBase { + use NameSourceStubs; + protected const TEST_ASYNC_URL = 'https://localhost'; protected const TEST_PARAMETER_NAME = 'preview'; protected MarkdownRenderer $markdown_renderer; - protected DefNamesource $name_source; + protected NameSource $name_source; protected FieldFactory $factory; protected ViewControlMode $view_control_mock; @@ -62,7 +65,7 @@ public function setUp(): void { $this->markdown_renderer = $this->getMarkdownRendererMock(); $this->factory = $this->buildMinimalFieldFactory(); - $this->name_source = new DefNamesource(); + $this->name_source = $this->createCountingNameSourceStub('name_'); $this->view_control_mock = $this->getViewControlModeStub(); $this->numberedlist_glyph_mock = $this->getGlyphStub('numberedlist'); @@ -474,6 +477,7 @@ protected function buildMinimalFieldFactory(): FieldFactory { return new FieldFactory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(UploadLimitResolver::class), new SignalGenerator(), $this->createMock(DataFactory::class), diff --git a/components/ILIAS/UI/tests/Component/Input/Field/MultiSelectInputTest.php b/components/ILIAS/UI/tests/Component/Input/Field/MultiSelectInputTest.php index b1601ad13b34..9a21811d78a5 100755 --- a/components/ILIAS/UI/tests/Component/Input/Field/MultiSelectInputTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/MultiSelectInputTest.php @@ -36,13 +36,6 @@ class MultiSelectInputTest extends ILIAS_UI_TestBase use CommonFieldRendering; use HasOptionFilterTestHelper; - protected DefNamesource $name_source; - - public function setUp(): void - { - $this->name_source = new DefNamesource(); - } - public function testImplementsFactoryInterface(): void { $f = $this->getFieldFactory(); @@ -75,12 +68,7 @@ public function testOnlyAcceptsActualOptionsFromClientSide(): void "2" => "Pick 2" ); $ms = $f->multiSelect("label", $options, "byline") - ->withNameFrom(new class () implements NameSource { - public function getNewName(): string - { - return "name"; - } - }); + ->withNameFrom($this->createFixedNameSourceStub('name')); $ms = $ms->withInput(new class () implements InputData { /** * @return string[] @@ -107,10 +95,9 @@ public function testRender(): void "1" => "Pick 1", "2" => "Pick 2" ); + [$name_source, $name] = $this->getDefaultNameSourceStub(); $ms = $f->multiSelect("label", $options, "byline") - ->withNameFrom($this->name_source); - - $name = $ms->getName(); + ->withNameFrom($name_source); $label = $ms->getLabel(); $byline = $ms->getByline(); $expected_options = ""; @@ -143,11 +130,10 @@ public function testRenderValue(): void "2" => "Pick 2" ); $value = array_keys($options)[1]; + [$name_source, $name] = $this->getDefaultNameSourceStub(); $ms = $f->multiSelect("label", $options, "byline") - ->withNameFrom($this->name_source) + ->withNameFrom($name_source) ->withValue([$value]); - - $name = $ms->getName(); $label = $ms->getLabel(); $byline = $ms->getByline(); $expected_options = ""; @@ -197,7 +183,8 @@ public function testCommonRendering(): void { $f = $this->getFieldFactory(); $label = "label"; - $multi_select = $f->multiSelect($label, [], null)->withNameFrom($this->name_source); + [$name_source] = $this->getDefaultNameSourceStub(); + $multi_select = $f->multiSelect($label, [], null)->withNameFrom($name_source); $this->testWithError($multi_select); $this->testWithNoByline($multi_select); @@ -211,14 +198,13 @@ public function testRenderNoOptions(): void $r = $this->getDefaultRenderer(); $f = $this->getFieldFactory(); $options = []; + [$name_source, $name] = $this->getDefaultNameSourceStub(); $ms = $f->multiSelect("label", $options, "byline") - ->withNameFrom($this->name_source)->withDisabled(true); - - $name = $ms->getName(); + ->withNameFrom($name_source)->withDisabled(true); $label = $ms->getLabel(); $byline = $ms->getByline(); $expected = ' -
+
    diff --git a/components/ILIAS/UI/tests/Component/Input/Field/NumericInputTest.php b/components/ILIAS/UI/tests/Component/Input/Field/NumericInputTest.php index 504982a0cf1d..b144dc952436 100755 --- a/components/ILIAS/UI/tests/Component/Input/Field/NumericInputTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/NumericInputTest.php @@ -34,13 +34,6 @@ class NumericInputTest extends ILIAS_UI_TestBase { use CommonFieldRendering; - protected DefNamesource $name_source; - - public function setUp(): void - { - $this->name_source = new DefNamesource(); - } - public function testImplementsFactoryInterface(): void { $f = $this->getFieldFactory(); @@ -57,12 +50,13 @@ public function testRender(): void $f = $this->getFieldFactory(); $label = "label"; $byline = "byline"; - $numeric = $f->numeric($label, $byline)->withNameFrom($this->name_source); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $numeric = $f->numeric($label, $byline)->withNameFrom($name_source); $expected = $this->getFormWrappedHtml( 'numeric-field-input', $label, - '', + '', $byline, 'id_1' ); @@ -73,7 +67,8 @@ public function testCommonRendering(): void { $f = $this->getFieldFactory(); $label = "label"; - $numeric = $f->numeric($label)->withNameFrom($this->name_source); + [$name_source] = $this->getDefaultNameSourceStub(); + $numeric = $f->numeric($label)->withNameFrom($name_source); $this->testWithError($numeric); $this->testWithNoByline($numeric); @@ -87,12 +82,13 @@ public function testRenderValue(): void $f = $this->getFieldFactory(); $label = "label"; $value = "10"; - $numeric = $f->numeric($label)->withValue($value)->withNameFrom($this->name_source); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $numeric = $f->numeric($label)->withValue($value)->withNameFrom($name_source); $expected = $this->getFormWrappedHtml( 'numeric-field-input', $label, - '', + '', null, 'id_1' ); @@ -102,8 +98,9 @@ public function testRenderValue(): void public function testNullValue(): \ILIAS\UI\Component\Input\Container\Form\FormInput { $f = $this->getFieldFactory(); - $post_data = new DefInputData(['name_0' => null]); - $field = $f->numeric('')->withNameFrom($this->name_source); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $post_data = new DefInputData([$name => null]); + $field = $f->numeric('')->withNameFrom($name_source); $field_required = $field->withRequired(true); $value = $field->withInput($post_data)->getContent(); @@ -118,7 +115,8 @@ public function testNullValue(): \ILIAS\UI\Component\Input\Container\Form\FormIn #[\PHPUnit\Framework\Attributes\Depends('testNullValue')] public function testEmptyValue(\ILIAS\UI\Component\Input\Container\Form\FormInput $field): void { - $post_data = new DefInputData(['name_0' => '']); + [,$name] = $this->getDefaultNameSourceStub(); + $post_data = new DefInputData([$name => '']); $field_required = $field->withRequired(true); $value = $field->withInput($post_data)->getContent(); @@ -133,7 +131,8 @@ public function testEmptyValue(\ILIAS\UI\Component\Input\Container\Form\FormInpu #[\PHPUnit\Framework\Attributes\Depends('testNullValue')] public function testZeroIsValidValue(\ILIAS\UI\Component\Input\Container\Form\FormInput $field): void { - $post_data = new DefInputData(['name_0' => 0]); + [,$name] = $this->getDefaultNameSourceStub(); + $post_data = new DefInputData([$name => 0]); $field_required = $field->withRequired(true); $value = $field->withInput($post_data)->getContent(); @@ -148,7 +147,8 @@ public function testZeroIsValidValue(\ILIAS\UI\Component\Input\Container\Form\Fo #[\PHPUnit\Framework\Attributes\Depends('testNullValue')] public function testConstraintForRequirementForFloat(\ILIAS\UI\Component\Input\Container\Form\FormInput $field): void { - $post_data = new DefInputData(['name_0' => 1.1]); + [,$name] = $this->getDefaultNameSourceStub(); + $post_data = new DefInputData([$name => 1.1]); $field_required = $field->withRequired(true); $value = $field->withInput($post_data)->getContent(); @@ -164,7 +164,8 @@ public function testConstraintForRequirementForFloat(\ILIAS\UI\Component\Input\C #[\PHPUnit\Framework\Attributes\Depends('testNullValue')] public function testFloatValue(\ILIAS\UI\Component\Input\Container\Form\FormInput $field): void { - $post_data = new DefInputData(['name_0' => 1.1]); + [,$name] = $this->getDefaultNameSourceStub(); + $post_data = new DefInputData([$name => 1.1]); $value = $field ->withStepSize(.1) ->withInput($post_data) @@ -176,7 +177,8 @@ public function testFloatValue(\ILIAS\UI\Component\Input\Container\Form\FormInpu #[\PHPUnit\Framework\Attributes\Depends('testNullValue')] public function testFloatValueOffsetStep(\ILIAS\UI\Component\Input\Container\Form\FormInput $field): void { - $post_data = new DefInputData(['name_0' => 1.2]); + [,$name] = $this->getDefaultNameSourceStub(); + $post_data = new DefInputData([$name => 1.2]); $value = $field ->withStepSize(.5) ->withInput($post_data) @@ -188,7 +190,8 @@ public function testFloatValueOffsetStep(\ILIAS\UI\Component\Input\Container\For #[\PHPUnit\Framework\Attributes\Depends('testNullValue')] public function testFloatValueForInt(\ILIAS\UI\Component\Input\Container\Form\FormInput $field): void { - $post_data = new DefInputData(['name_0' => 2]); + [,$name] = $this->getDefaultNameSourceStub(); + $post_data = new DefInputData([$name => 2]); $value = $field ->withStepSize(.5) ->withInput($post_data) @@ -203,15 +206,16 @@ public function testDecimalsTransformationsStack(): void $data_factory = new DataFactory(); $language = $this->createMock(ILIAS\Language\Language::class); $refinery = new Refinery($data_factory, $language); + [$name_source, $name] = $this->getDefaultNameSourceStub(); $f = $this->getFieldFactory(); - $field = $f->numeric('')->withNameFrom($this->name_source) + $field = $f->numeric('')->withNameFrom($name_source) ->withStepSize(.2) ->withAdditionalTransformation( $refinery->custom()->transformation(fn(float $v): float => $v + 1) ); - $post_data = new DefInputData(['name_0' => 1]); + $post_data = new DefInputData([$name => 1]); $value = $field ->withInput($post_data) diff --git a/components/ILIAS/UI/tests/Component/Input/Field/OptionalGroupInputTest.php b/components/ILIAS/UI/tests/Component/Input/Field/OptionalGroupInputTest.php index 1541570b5c7a..106922331740 100755 --- a/components/ILIAS/UI/tests/Component/Input/Field/OptionalGroupInputTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/OptionalGroupInputTest.php @@ -39,6 +39,7 @@ abstract class Input12 extends FormInput class OptionalGroupInputTest extends ILIAS_UI_TestBase { + use NameSourceStubs; use CommonFieldRendering; /** @@ -84,12 +85,7 @@ public function setUp(): void [$this->child1, $this->child2], "LABEL", "BYLINE" - ))->withNameFrom(new class () implements NameSource { - public function getNewName(): string - { - return "name0"; - } - }); + ))->withNameFrom($this->createFixedNameSourceStub('name0')); } public function testWithDisabledDisablesChildren(): void @@ -392,13 +388,14 @@ public function testCommonRendering(): void { $f = $this->getFieldFactory(); $label = "label"; + [$name_source] = $this->getDefaultNameSourceStub(); $og = $f->optionalGroup( [ "field_1" => $f->text("f"), "field_2" => $f->text("f2") ], $label - )->withNameFrom((new DefNamesource())); + )->withNameFrom($name_source); $this->testWithError($og); $this->testWithNoByline($og); diff --git a/components/ILIAS/UI/tests/Component/Input/Field/PasswordInputTest.php b/components/ILIAS/UI/tests/Component/Input/Field/PasswordInputTest.php index 14b741c641d5..00f4c8c8fb3b 100755 --- a/components/ILIAS/UI/tests/Component/Input/Field/PasswordInputTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/PasswordInputTest.php @@ -62,13 +62,6 @@ class PasswordInputTest extends ILIAS_UI_TestBase { use CommonFieldRendering; - protected DefNamesource $name_source; - - public function setUp(): void - { - $this->name_source = new DefNamesource(); - } - public function testImplementsFactoryInterface(): void { $f = $this->getFieldFactory(); @@ -82,8 +75,8 @@ public function testRender(): void $f = $this->getFieldFactory(); $label = "label"; $byline = "byline"; - $name = "name_0"; - $pwd = $f->password($label, $byline)->withNameFrom($this->name_source); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $pwd = $f->password($label, $byline)->withNameFrom($name_source); $expected = $this->getFormWrappedHtml( 'password-field-input', $label, @@ -102,7 +95,8 @@ public function testCommonRendering(): void { $f = $this->getFieldFactory(); $label = "label"; - $pwd = $f->password($label)->withNameFrom($this->name_source); + [$name_source] = $this->getDefaultNameSourceStub(); + $pwd = $f->password($label)->withNameFrom($name_source); $this->testWithError($pwd); $this->testWithNoByline($pwd); @@ -116,8 +110,8 @@ public function testRenderValue(): void $f = $this->getFieldFactory(); $label = "label"; $value = "value_0"; - $name = "name_0"; - $pwd = $f->password($label)->withValue($value)->withNameFrom($this->name_source); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $pwd = $f->password($label)->withValue($value)->withNameFrom($name_source); $expected = $this->getFormWrappedHtml( 'password-field-input', $label, @@ -136,8 +130,8 @@ public function testValueRequired(): void { $f = $this->getFieldFactory(); $label = "label"; - $name = "name_0"; - $pwd = $f->password($label)->withNameFrom($this->name_source)->withRequired(true); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $pwd = $f->password($label)->withNameFrom($name_source)->withRequired(true); $pwd1 = $pwd->withInput(new DefInputData([$name => "0"])); $value1 = $pwd1->getContent(); @@ -152,7 +146,8 @@ public function testValueType(): void { $f = $this->getFieldFactory(); $label = "label"; - $pwd = $f->password($label)->withNameFrom($this->name_source); + [$name_source] = $this->getDefaultNameSourceStub(); + $pwd = $f->password($label)->withNameFrom($name_source); $this->assertNull($pwd->getValue()); $post = new _PWDInputData(); diff --git a/components/ILIAS/UI/tests/Component/Input/Field/RadioInputTest.php b/components/ILIAS/UI/tests/Component/Input/Field/RadioInputTest.php index 3afc9425e4ac..9fa4b125241e 100755 --- a/components/ILIAS/UI/tests/Component/Input/Field/RadioInputTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/RadioInputTest.php @@ -34,23 +34,17 @@ class RadioInputTest extends ILIAS_UI_TestBase use CommonFieldRendering; use HasOptionFilterTestHelper; - protected DefNamesource $name_source; - - public function setUp(): void - { - $this->name_source = new DefNamesource(); - } - protected function buildRadio(bool $with_has_option_filter = false): \ILIAS\UI\Component\Input\Container\Form\FormInput { $f = $this->getFieldFactory(); $label = "label"; $byline = "byline"; + [$name_source] = $this->getDefaultNameSourceStub(); $return = $f ->radio($label, $byline) ->withOption('value0', 'label0', 'byline0') ->withOption('1', 'label1', 'byline1') - ->withNameFrom($this->name_source); + ->withNameFrom($name_source); if ($with_has_option_filter) { $return = $return->withHasOptionFilter(); } @@ -136,7 +130,8 @@ public function testRenderWithHasOptionFilter(): void public function testCommonRendering(): void { $f = $this->getFieldFactory(); - $radio = $f->radio('label', null)->withNameFrom($this->name_source); + [$name_source] = $this->getDefaultNameSourceStub(); + $radio = $f->radio('label', null)->withNameFrom($name_source); $this->testWithError($radio); $this->testWithNoByline($radio); diff --git a/components/ILIAS/UI/tests/Component/Input/Field/RatingInputTest.php b/components/ILIAS/UI/tests/Component/Input/Field/RatingInputTest.php index abf44a8d6bee..84a1b06af379 100644 --- a/components/ILIAS/UI/tests/Component/Input/Field/RatingInputTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/RatingInputTest.php @@ -33,21 +33,15 @@ class RatingInputTest extends ILIAS_UI_TestBase { use CommonFieldRendering; - protected DefNamesource $name_source; - - public function setUp(): void - { - $this->name_source = new DefNamesource(); - } - protected function buildRating(): \ILIAS\UI\Component\Input\Container\Form\FormInput { $f = $this->getFieldFactory(); $label = "label"; $byline = "byline"; + [$name_source] = $this->getDefaultNameSourceStub(); return $f ->rating($label, $byline) - ->withNameFrom($this->name_source); + ->withNameFrom($name_source); } public function testRatingImplementsFactoryInterface(): void @@ -61,36 +55,39 @@ public function testRatingImplementsFactoryInterface(): void public function testRatingRenderBasic(): void { $rating = $this->buildRating(); - $expected = $this->getFormWrappedHtml( - 'rating-field-input', - 'label', - ' + [,$name] = $this->getDefaultNameSourceStub(); + $html = <<
    - + - + - + - + - +
    - +
- ', +HTML; + + $expected = $this->getFormWrappedHtml( + 'rating-field-input', + 'label', + $html, 'byline', null ); @@ -105,8 +102,9 @@ public function testRatingRenderFull(): void ->withValue(FiveStarRatingScale::GOOD) ->withCurrentAverage(3); - $expected = $this->brutallyTrimHTML( - '
+ [,$name] = $this->getDefaultNameSourceStub(); + $expected = <<
@@ -121,25 +119,25 @@ public function testRatingRenderFull(): void
- + - + - + - + - +
- +
@@ -147,16 +145,17 @@ public function testRatingRenderFull(): void
- ' - ); - - $this->assertEquals($expected, $this->render($rating)); + +HTML; + $expected = $this->brutallyTrimHTML($expected); + $this->assertEquals($this->brutallyTrimHTML($expected), $this->render($rating)); } public function testCommonRendering(): void { $f = $this->getFieldFactory(); - $rating = $f->rating("label", null)->withNameFrom($this->name_source); + [$name_source] = $this->getDefaultNameSourceStub(); + $rating = $f->rating("label", null)->withNameFrom($name_source); $this->testWithError($rating); $this->testWithNoByline($rating); diff --git a/components/ILIAS/UI/tests/Component/Input/Field/SectionInputTest.php b/components/ILIAS/UI/tests/Component/Input/Field/SectionInputTest.php index f6258447df72..04799b985f5e 100755 --- a/components/ILIAS/UI/tests/Component/Input/Field/SectionInputTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/SectionInputTest.php @@ -24,18 +24,12 @@ use ILIAS\UI\Implementation\Component\Input\Field; use ILIAS\Data; +use ILIAS\UI\Implementation\Component\Input\NameSource; class SectionInputTest extends ILIAS_UI_TestBase { use CommonFieldRendering; - protected DefNamesource $name_source; - - public function setUp(): void - { - $this->name_source = new DefNamesource(); - } - public function testSectionRendering(): void { $f = $this->getFieldFactory(); @@ -45,24 +39,23 @@ public function testSectionRendering(): void ]; $label = 'section label'; $byline = 'section byline'; - $section = $f->section($inputs, $label, $byline)->withNameFrom($this->name_source); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $section = $f->section($inputs, $label, $byline)->withNameFrom($name_source); $f1 = $this->getFormWrappedHtml( 'text-field-input', 'input1', - '', + '', 'in 1', 'id_1', null, - 'name_0/name_1' ); $f2 = $this->getFormWrappedHtml( 'text-field-input', 'input2', - '', + '', 'in 2', 'id_2', null, - 'name_0/name_2' ); $expected = $this->getFormWrappedHtml( 'section-field-input', @@ -82,7 +75,8 @@ public function testCommonRendering(): void $f->text("input1") ]; $label = 'section label'; - $section = $f->section($inputs, $label)->withNameFrom($this->name_source); + [$name_source] = $this->getDefaultNameSourceStub(); + $section = $f->section($inputs, $label)->withNameFrom($name_source); $this->testWithError($section); $this->testWithNoByline($section); diff --git a/components/ILIAS/UI/tests/Component/Input/Field/SelectInputTest.php b/components/ILIAS/UI/tests/Component/Input/Field/SelectInputTest.php index 1474eb3b70e9..4b505d5c2d89 100644 --- a/components/ILIAS/UI/tests/Component/Input/Field/SelectInputTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/SelectInputTest.php @@ -39,13 +39,6 @@ class SelectInputTest extends ILIAS_UI_TestBase { use CommonFieldRendering; - protected DefNamesource $name_source; - - public function setUp(): void - { - $this->name_source = new DefNamesource(); - } - public function testOnlyValuesFromOptionsAreAcceptableClientSideValues(): void { $options = ["one" => "Eins", "two" => "Zwei", "three" => "Drei"]; @@ -80,13 +73,14 @@ public function testEmptyStringIsAcceptableClientSideValueIfSelectIsNotRequired( public function testEmptyStringCreatesErrorIfSelectIsRequired(): void { $options = []; + [$name_source] = $this->getDefaultNameSourceStub(); $select = $this->getFieldFactory()->select( "", $options, "" ) ->withRequired(true) - ->withNameFrom($this->name_source); + ->withNameFrom($name_source); $data = $this->createMock(InputData::class); $data->expects($this->once()) @@ -119,12 +113,13 @@ public function testRender(): void $label = "label"; $byline = "byline"; $options = ["one" => "One", "two" => "Two", "three" => "Three"]; - $select = $f->select($label, $options, $byline)->withNameFrom($this->name_source); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $select = $f->select($label, $options, $byline)->withNameFrom($name_source); $expected = $this->getFormWrappedHtml( 'select-field-input', $label, ' - @@ -144,12 +139,13 @@ public function testRenderValue(): void $label = "label"; $byline = "byline"; $options = ["one" => "One", "two" => "Two", "three" => "Three"]; - $select = $f->select($label, $options, $byline)->withNameFrom($this->name_source)->withValue("one"); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $select = $f->select($label, $options, $byline)->withNameFrom($name_source)->withValue("one"); $expected = $this->getFormWrappedHtml( 'select-field-input', $label, ' - @@ -166,7 +162,8 @@ public function testCommonRendering(): void { $f = $this->getFieldFactory(); $label = "label"; - $select = $f->select($label, [], null)->withNameFrom($this->name_source); + [$name_source] = $this->getDefaultNameSourceStub(); + $select = $f->select($label, [], null)->withNameFrom($name_source); $this->testWithError($select); $this->testWithNoByline($select); @@ -182,8 +179,9 @@ public function testWithValueAndRequiredDoesNotContainNull(): void $label = "label"; $byline = "byline"; $options = ["something_value" => "something"]; + [$name_source] = $this->getDefaultNameSourceStub(); $select = $f->select($label, $options, $byline) - ->withNameFrom($this->name_source); + ->withNameFrom($name_source); $html_without = $this->brutallyTrimHTML($this->getDefaultRenderer()->render($select)); diff --git a/components/ILIAS/UI/tests/Component/Input/Field/SwitchableGroupInputTest.php b/components/ILIAS/UI/tests/Component/Input/Field/SwitchableGroupInputTest.php index 13d59c4d344d..e5f7509445f2 100755 --- a/components/ILIAS/UI/tests/Component/Input/Field/SwitchableGroupInputTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/SwitchableGroupInputTest.php @@ -48,6 +48,7 @@ public function isClientSideValueOk($value): bool class SwitchableGroupInputTest extends ILIAS_UI_TestBase { use CommonFieldRendering; + use NameSourceStubs; /** * @var Group1|mixed|MockObject @@ -108,18 +109,14 @@ public function setUp(): void ["child1" => $this->child1, "child2" => $this->child2], "LABEL", "BYLINE" - ))->withNameFrom(new class () implements NameSource { - public function getNewName(): string - { - return "name0"; - } - }); + ))->withNameFrom($this->createFixedNameSourceStub('name0')); } protected function buildFactory(): I\Input\Field\Factory { return new I\Input\Field\Factory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(\ILIAS\UI\Implementation\Component\Input\UploadLimitResolver::class), new SignalGenerator(), $this->data_factory, @@ -511,6 +508,7 @@ public function testCommonRendering(): void { $f = $this->getFieldFactory(); $label = "label"; + [$name_source, $name] = $this->getDefaultNameSourceStub(); $group1 = $f->group([ "field_1" => $f->text("f") @@ -525,7 +523,7 @@ public function testCommonRendering(): void "g2" => $group2 ], $label - )->withNameFrom((new DefNamesource())); + )->withNameFrom($name_source); $this->testWithError($sg); $this->testWithNoByline($sg); diff --git a/components/ILIAS/UI/tests/Component/Input/Field/TagInputTest.php b/components/ILIAS/UI/tests/Component/Input/Field/TagInputTest.php index 5b9bb9811916..5ec2e6d34dd9 100755 --- a/components/ILIAS/UI/tests/Component/Input/Field/TagInputTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/TagInputTest.php @@ -37,13 +37,6 @@ class TagInputTest extends ILIAS_UI_TestBase { use CommonFieldRendering; - protected DefNamesource $name_source; - - public function setUp(): void - { - $this->name_source = new DefNamesource(); - } - #[\PHPUnit\Framework\Attributes\DoesNotPerformAssertions] public function testImplementsFactoryInterface(): void { @@ -62,13 +55,14 @@ public function testRender(): void $label = "label"; $byline = "byline"; $tags = ["lorem", "ipsum", "dolor",]; - $tag = $f->tag($label, $tags, $byline)->withNameFrom($this->name_source); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $tag = $f->tag($label, $tags, $byline)->withNameFrom($name_source); $expected = $this->getFormWrappedHtml( 'tag-field-input', $label, '
- +
', $byline, @@ -81,7 +75,8 @@ public function testRender(): void public function testCommonRendering(): void { $f = $this->getFieldFactory(); - $tag = $f->tag('label', [], null)->withNameFrom($this->name_source); + [$name_source] = $this->getDefaultNameSourceStub(); + $tag = $f->tag('label', [], null)->withNameFrom($name_source); $this->testWithError($tag); $this->testWithNoByline($tag); @@ -94,10 +89,10 @@ public function testValueRequired(): void { $f = $this->getFieldFactory(); $label = "label"; - $name = "name_0"; + [$name_source, $name] = $this->getDefaultNameSourceStub(); $tags = ["lorem", "ipsum", "dolor",]; /** @var I\Input\Field\Tag $tag */ - $tag = $f->tag($label, $tags)->withNameFrom($this->name_source)->withRequired(true); + $tag = $f->tag($label, $tags)->withNameFrom($name_source)->withRequired(true); $raw_value1 = "lorem,ipsum"; $expected_result = ['lorem', 'ipsum']; @@ -112,10 +107,10 @@ public function testEmptyStringAsInputLeadToException(): void { $f = $this->getFieldFactory(); $label = "label"; - $name = "name_0"; + [$name_source, $name] = $this->getDefaultNameSourceStub(); $tags = ["lorem", "ipsum", "dolor",]; /** @var I\Input\Field\Tag $tag */ - $tag = $f->tag($label, $tags)->withNameFrom($this->name_source)->withRequired(true); + $tag = $f->tag($label, $tags)->withNameFrom($name_source)->withRequired(true); $tag2 = $tag->withInput(new DefInputData([$name => ''])); $result = $tag2->getContent(); @@ -132,10 +127,10 @@ public function testStringAsInputAsRequired(): void { $f = $this->getFieldFactory(); $label = "label"; - $name = "name_0"; + [$name_source, $name] = $this->getDefaultNameSourceStub(); $tags = ["lorem", "ipsum", "dolor",]; /** @var I\Input\Field\Tag $tag */ - $tag = $f->tag($label, $tags)->withNameFrom($this->name_source)->withRequired(true); + $tag = $f->tag($label, $tags)->withNameFrom($name_source)->withRequired(true); $tag2 = $tag->withInput(new DefInputData([$name => 'test'])); $result = $tag2->getContent(); @@ -147,10 +142,10 @@ public function testNullValueLeadsToException(): void { $f = $this->getFieldFactory(); $label = "label"; - $name = "name_0"; + [$name_source, $name] = $this->getDefaultNameSourceStub(); $tags = ["lorem", "ipsum", "dolor",]; - $tag = $f->tag($label, $tags)->withNameFrom($this->name_source)->withRequired(true); + $tag = $f->tag($label, $tags)->withNameFrom($name_source)->withRequired(true); $tag2 = $tag->withInput(new DefInputData([$name => null])); $value2 = $tag2->getContent(); $this->assertTrue($value2->isError()); @@ -162,11 +157,12 @@ public function testUserCreatedNotAllowed(): void $f = $this->getFieldFactory(); $tags = ["lorem", "ipsum", "dolor",]; - $tag = $f->tag("label", $tags)->withUserCreatedTagsAllowed(false)->withNameFrom($this->name_source); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $tag = $f->tag("label", $tags)->withUserCreatedTagsAllowed(false)->withNameFrom($name_source); $tag1 = $tag->withInput( new DefInputData( - ["name_0" => "lorem,ipsum"] + [$name => "lorem,ipsum"] ) ); $value1 = $tag1->getContent(); @@ -179,7 +175,7 @@ public function testUserCreatedNotAllowed(): void $tag1 = $tag->withInput( new DefInputData( - ["name_0" => "conseptetuer,ipsum"] + [$name => "conseptetuer,ipsum"] ) ); $value1 = $tag1->getContent(); @@ -190,8 +186,9 @@ public function testMaxTagsOk(): void { $f = $this->getFieldFactory(); - $tag = $f->tag("label", [])->withMaxTags(3)->withNameFrom($this->name_source)->withInput( - new DefInputData(["name_0" => "lorem,ipsum"]) + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $tag = $f->tag("label", [])->withMaxTags(3)->withNameFrom($name_source)->withInput( + new DefInputData([$name => "lorem,ipsum"]) ); $value = $tag->getContent(); $this->assertTrue($value->isOk()); @@ -202,9 +199,10 @@ public function testMaxTagsNotOk(): void $f = $this->getFieldFactory(); $this->expectException(InvalidArgumentException::class); - $f->tag("label", [])->withMaxTags(2)->withNameFrom($this->name_source)->withInput( + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $f->tag("label", [])->withMaxTags(2)->withNameFrom($name_source)->withInput( new DefInputData( - ["name_0" => "lorem,ipsum,dolor"] + [$name => "lorem,ipsum,dolor"] ) ); } @@ -213,8 +211,9 @@ public function testMaxTaglengthTagsOk(): void { $f = $this->getFieldFactory(); - $tag = $f->tag("label", [])->withTagMaxLength(10)->withNameFrom($this->name_source)->withInput( - new DefInputData(["name_0" => "lorem,ipsum"]) + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $tag = $f->tag("label", [])->withTagMaxLength(10)->withNameFrom($name_source)->withInput( + new DefInputData([$name => "lorem,ipsum"]) ); $value = $tag->getContent(); $this->assertTrue($value->isOk()); @@ -224,10 +223,11 @@ public function testMaxTaglengthTagsNotOk(): void { $f = $this->getFieldFactory(); + [$name_source, $name] = $this->getDefaultNameSourceStub(); $this->expectException(InvalidArgumentException::class); - $f->tag("label", [])->withTagMaxLength(2)->withNameFrom($this->name_source)->withInput( + $f->tag("label", [])->withTagMaxLength(2)->withNameFrom($name_source)->withInput( new DefInputData( - ["name_0" => "lorem,ipsum,dolor"] + [$name => "lorem,ipsum,dolor"] ) ); } @@ -246,8 +246,8 @@ public static function getUITagSpecialCharValues(): array public function testUITagInputSpecialChars(string ...$tags): void { $f = $this->getFieldFactory(); - $name = "name_0"; - $tag = $f->tag('', $tags)->withNameFrom($this->name_source); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $tag = $f->tag('', $tags)->withNameFrom($name_source); $encoded_tags = array_map('rawurlencode', $tags); diff --git a/components/ILIAS/UI/tests/Component/Input/Field/TextInputTest.php b/components/ILIAS/UI/tests/Component/Input/Field/TextInputTest.php index 346787ce6cee..77833e11302c 100755 --- a/components/ILIAS/UI/tests/Component/Input/Field/TextInputTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/TextInputTest.php @@ -33,13 +33,6 @@ class TextInputTest extends ILIAS_UI_TestBase { use CommonFieldRendering; - protected DefNamesource $name_source; - - public function setUp(): void - { - $this->name_source = new DefNamesource(); - } - public function testImplementsFactoryInterface(): void { $f = $this->getFieldFactory(); @@ -55,11 +48,12 @@ public function testRender(): void $f = $this->getFieldFactory(); $label = "label"; $byline = "byline"; - $text = $f->text($label, $byline)->withNameFrom($this->name_source); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $text = $f->text($label, $byline)->withNameFrom($name_source); $expected = $this->getFormWrappedHtml( 'text-field-input', $label, - '', + '', $byline, 'id_1' ); @@ -70,7 +64,8 @@ public function testCommonRendering(): void { $f = $this->getFieldFactory(); $label = "label"; - $text = $f->text($label)->withNameFrom($this->name_source); + [$name_source] = $this->getDefaultNameSourceStub(); + $text = $f->text($label)->withNameFrom($name_source); $this->testWithError($text); $this->testWithNoByline($text); @@ -84,11 +79,12 @@ public function testRenderValue(): void $f = $this->getFieldFactory(); $label = "label"; $value = "value"; - $text = $f->text($label)->withValue($value)->withNameFrom($this->name_source); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $text = $f->text($label)->withValue($value)->withNameFrom($name_source); $expected = $this->getFormWrappedHtml( 'text-field-input', $label, - '', + '', null, 'id_1' ); @@ -116,11 +112,12 @@ public function testRenderMaxValue(): void { $f = $this->getFieldFactory(); $label = "label"; - $text = $f->text($label)->withNameFrom($this->name_source)->withMaxLength(8); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $text = $f->text($label)->withNameFrom($name_source)->withMaxLength(8); $expected = $this->getFormWrappedHtml( 'text-field-input', $label, - '', + '', null, 'id_1' ); @@ -131,8 +128,8 @@ public function testValueRequired(): void { $f = $this->getFieldFactory(); $label = "label"; - $name = "name_0"; - $text = $f->text($label)->withNameFrom($this->name_source)->withRequired(true); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $text = $f->text($label)->withNameFrom($name_source)->withRequired(true); $text1 = $text->withInput(new DefInputData([$name => "0"])); $value1 = $text1->getContent(); @@ -147,9 +144,9 @@ public function testValueRequired(): void public function testStripsTags(): void { $f = $this->getFieldFactory(); - $name = "name_0"; + [$name_source, $name] = $this->getDefaultNameSourceStub(); $text = $f->text("") - ->withNameFrom($this->name_source) + ->withNameFrom($name_source) ->withInput(new DefInputData([$name => ""])); $content = $text->getContent(); @@ -159,9 +156,9 @@ public function testStripsTags(): void public function testWithoutStripsTags(): void { $f = $this->getFieldFactory(); - $name = "name_0"; + [$name_source, $name] = $this->getDefaultNameSourceStub(); $text = $f->text("") - ->withNameFrom($this->name_source) + ->withNameFrom($name_source) ->withoutStripTags() ->withInput(new DefInputData([$name => ""])); diff --git a/components/ILIAS/UI/tests/Component/Input/Field/TextareaTest.php b/components/ILIAS/UI/tests/Component/Input/Field/TextareaTest.php index cdaca0bb3e01..717fc7d6ded8 100755 --- a/components/ILIAS/UI/tests/Component/Input/Field/TextareaTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/TextareaTest.php @@ -33,13 +33,6 @@ class TextareaTest extends ILIAS_UI_TestBase { use CommonFieldRendering; - private DefNamesource $name_source; - - public function setUp(): void - { - $this->name_source = new DefNamesource(); - } - public function testImplementsFactoryInterface(): void { $f = $this->getFieldFactory(); @@ -119,12 +112,13 @@ public function testRenderer(): void $f = $this->getFieldFactory(); $label = "label"; $byline = "byline"; - $textarea = $f->textarea($label, $byline)->withNameFrom($this->name_source); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $textarea = $f->textarea($label, $byline)->withNameFrom($name_source); $expected = $this->getFormWrappedHtml( 'textarea-field-input', $label, ' - + ', $byline, 'id_1', @@ -137,7 +131,8 @@ public function testCommonRendering(): void { $f = $this->getFieldFactory(); $label = "label"; - $textarea = $f->textarea($label)->withNameFrom($this->name_source); + [$name_source] = $this->getDefaultNameSourceStub(); + $textarea = $f->textarea($label)->withNameFrom($name_source); $this->testWithError($textarea); $this->testWithNoByline($textarea); @@ -152,12 +147,13 @@ public function testRendererWithMinLimit(): void $label = "label"; $min = 5; $byline = "This is just a byline Min: " . $min; - $textarea = $f->textarea($label, $byline)->withMinLimit($min)->withNameFrom($this->name_source); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $textarea = $f->textarea($label, $byline)->withMinLimit($min)->withNameFrom($name_source); $expected = $this->getFormWrappedHtml( 'textarea-field-input', $label, ' - + ', $byline, 'id_1', @@ -172,12 +168,13 @@ public function testRendererWithMaxLimit(): void $label = "label"; $max = 20; $byline = "This is just a byline Max: " . $max; - $textarea = $f->textarea($label, $byline)->withMaxLimit($max)->withNameFrom($this->name_source); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $textarea = $f->textarea($label, $byline)->withMaxLimit($max)->withNameFrom($name_source); $expected = $this->getFormWrappedHtml( 'textarea-field-input', $label, ' - +
ui_chars_remaining20
', $byline, @@ -191,14 +188,14 @@ public function testRendererWithMinAndMaxLimit(): void { $f = $this->getFieldFactory(); $r = $this->getDefaultRenderer(); - $name = "name_0"; $id = "id_1"; $label = "label"; $min = 5; $max = 20; $byline = "This is just a byline Min: " . $min . " Max: " . $max; + [$name_source, $name] = $this->getDefaultNameSourceStub(); $textarea = $f->textarea($label, $byline)->withMinLimit($min)->withMaxLimit($max)->withNameFrom( - $this->name_source + $name_source ); $expected = $this->brutallyTrimHTML(" @@ -214,9 +211,9 @@ public function testRendererCounterWithValue(): void $id = 'id_1'; $label = "label"; $byline = "byline"; - $name = "name_0"; $value = "Lorem ipsum dolor sit"; - $textarea = $f->textarea($label, $byline)->withValue($value)->withNameFrom($this->name_source); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $textarea = $f->textarea($label, $byline)->withValue($value)->withNameFrom($name_source); $expected = $this->brutallyTrimHTML("
@@ -229,9 +226,9 @@ public function testRendererCounterWithValue(): void public function testStripsTags(): void { $f = $this->getFieldFactory(); - $name = "name_0"; + [$name_source, $name] = $this->getDefaultNameSourceStub(); $text = $f->textarea("") - ->withNameFrom($this->name_source) + ->withNameFrom($name_source) ->withInput(new DefInputData([$name => ""])); $content = $text->getContent(); @@ -241,9 +238,9 @@ public function testStripsTags(): void public function testWithoutStripsTags(): void { $f = $this->getFieldFactory(); - $name = "name_0"; + [$name_source, $name] = $this->getDefaultNameSourceStub(); $text = $f->textarea("") - ->withNameFrom($this->name_source) + ->withNameFrom($name_source) ->withoutStripTags() ->withInput(new DefInputData([$name => ""])); @@ -257,12 +254,11 @@ public function testWithMustacheVariables(): void $id = 'id_1'; $label = "label"; $byline = "byline"; - $name = "name_1"; + [$name_source, $name] = $this->getDefaultNameSourceStub(); $value = "Lorem ipsum dolor sit"; $textarea = $f->textarea($label, $byline) ->withValue($value) - ->withNameFrom($this->name_source) - ->withNameFrom($this->name_source) + ->withNameFrom($name_source) ->withMustacheVariables( [ 'var1' => 'Test Variable 1', diff --git a/components/ILIAS/UI/tests/Component/Input/Field/TreeMultiSelectTest.php b/components/ILIAS/UI/tests/Component/Input/Field/TreeMultiSelectTest.php index 061f117e5882..7dc4b1ec7127 100644 --- a/components/ILIAS/UI/tests/Component/Input/Field/TreeMultiSelectTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/TreeMultiSelectTest.php @@ -32,6 +32,8 @@ */ class TreeMultiSelectTest extends \ILIAS_UI_TestBase { + use NameSourceStubs; + protected Component\Button\Bulky & MockObject $bulky_stub; protected string $bulky_html; protected Component\Link\Standard & MockObject $link_stub; @@ -40,6 +42,8 @@ class TreeMultiSelectTest extends \ILIAS_UI_TestBase protected string $drilldown_html; protected Component\Breadcrumbs\Breadcrumbs & MockObject $breadcrumbs_stub; protected string $breadcrumbs_html; + protected Component\Input\HasDynamicInputsNameSource & MockObject $has_dynamic_inputs_name_source_stub; + protected string $dynamic_input_name; protected Component\SignalGeneratorInterface $signal_generator; protected Component\Menu\Factory $menu_factory; @@ -51,6 +55,9 @@ protected function setUp(): void [$this->drilldown_stub, $this->drilldown_html] = $this->getDrilldownStub(); [$this->breadcrumbs_stub, $this->breadcrumbs_html] = $this->getBreadcrumbsStub(); + $this->dynamic_input_name = 'dynamic_input_name'; + $this->has_dynamic_inputs_name_source_stub = $this->createFixedHasDynamicInputsNameSourceStub($this->dynamic_input_name); + $this->signal_generator = new IncrementalSignalGenerator(); $this->menu_factory = $this->createMock(Component\Menu\Factory::class); @@ -243,17 +250,18 @@ public function testRenderDrilldownMenu(): void [$leaf_stub, $leaf_html] = $this->getLeafStub(); $tree_select_label = 'some tree select label'; + $fixed_input_name = 'fixed_input_name'; $node_retrieval = $this->getNodeRetrieval([$leaf_stub]); $component = $this->getFieldFactory()->treeMultiSelect($node_retrieval, $tree_select_label); - $component = $component->withNameFrom(new DefNamesource()); + $component = $component->withNameFrom($this->createFixedNameSourceStub($fixed_input_name)); $renderer = $this->getDefaultRenderer(null, [$leaf_stub]); $expected_html = << - +
+
@@ -296,7 +304,7 @@ public function testRenderDrilldownMenu(): void - + @@ -346,6 +354,7 @@ protected function getFieldFactory(?Field\Node\Factory $node_factory = null): Fi { return new Field\Factory( ($node_factory) ?: $this->createMock(Field\Node\Factory::class), + $this->has_dynamic_inputs_name_source_stub, $this->createMock(Component\Input\UploadLimitResolver::class), $this->signal_generator, $this->getDataFactory(), diff --git a/components/ILIAS/UI/tests/Component/Input/Field/TreeSelectTest.php b/components/ILIAS/UI/tests/Component/Input/Field/TreeSelectTest.php index e13ce55d8499..d0e9790f2cda 100644 --- a/components/ILIAS/UI/tests/Component/Input/Field/TreeSelectTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/TreeSelectTest.php @@ -32,6 +32,8 @@ */ class TreeSelectTest extends \ILIAS_UI_TestBase { + use NameSourceStubs; + protected Component\Button\Bulky & MockObject $bulky_stub; protected string $bulky_html; protected Component\Link\Standard & MockObject $link_stub; @@ -40,6 +42,8 @@ class TreeSelectTest extends \ILIAS_UI_TestBase protected string $drilldown_html; protected Component\Breadcrumbs\Breadcrumbs & MockObject $breadcrumbs_stub; protected string $breadcrumbs_html; + protected Component\Input\HasDynamicInputsNameSource & MockObject $has_dynamic_inputs_name_source_stub; + protected string $dynamic_input_name; protected Component\SignalGeneratorInterface $signal_generator; protected Component\Menu\Factory $menu_factory; @@ -51,6 +55,9 @@ protected function setUp(): void [$this->drilldown_stub, $this->drilldown_html] = $this->getDrilldownStub(); [$this->breadcrumbs_stub, $this->breadcrumbs_html] = $this->getBreadcrumbsStub(); + $this->dynamic_input_name = 'dynamic_input_name'; + $this->has_dynamic_inputs_name_source_stub = $this->createFixedHasDynamicInputsNameSourceStub($this->dynamic_input_name); + $this->signal_generator = new IncrementalSignalGenerator(); $this->menu_factory = $this->createMock(Component\Menu\Factory::class); @@ -243,17 +250,18 @@ public function testRenderDrilldownMenu(): void [$leaf_stub, $leaf_html] = $this->getLeafStub(); $tree_select_label = 'some tree select label'; + $fixed_input_name = 'fixed_input_name'; $node_retrieval = $this->getNodeRetrieval([$leaf_stub]); $component = $this->getFieldFactory()->treeSelect($node_retrieval, $tree_select_label); - $component = $component->withNameFrom(new DefNamesource()); + $component = $component->withNameFrom($this->createFixedNameSourceStub($fixed_input_name)); $renderer = $this->getDefaultRenderer(null, [$leaf_stub]); $expected_html = << - +
+
@@ -296,7 +304,7 @@ public function testRenderDrilldownMenu(): void - + @@ -345,6 +353,7 @@ protected function getFieldFactory(?Field\Node\Factory $node_factory = null): Fi { return new Field\Factory( ($node_factory) ?: $this->createMock(Field\Node\Factory::class), + $this->has_dynamic_inputs_name_source_stub, $this->createMock(Component\Input\UploadLimitResolver::class), $this->signal_generator, $this->getDataFactory(), diff --git a/components/ILIAS/UI/tests/Component/Input/Field/UrlInputTest.php b/components/ILIAS/UI/tests/Component/Input/Field/UrlInputTest.php index ec6cf9d75843..07bc1bc68655 100755 --- a/components/ILIAS/UI/tests/Component/Input/Field/UrlInputTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/UrlInputTest.php @@ -33,13 +33,6 @@ class UrlInputTest extends ILIAS_UI_TestBase { use CommonFieldRendering; - private DefNamesource $name_source; - - public function setUp(): void - { - $this->name_source = new DefNamesource(); - } - public function testImplementsFactoryInterface(): void { $factory = $this->getFieldFactory(); @@ -55,11 +48,12 @@ public function testRendering(): void $renderer = $this->getDefaultRenderer(); $label = "Test Label"; $byline = "Test Byline"; - $url = $factory->url($label, $byline)->withNameFrom($this->name_source); + [$name_source, $name] = $this->getDefaultNameSourceStub(); + $url = $factory->url($label, $byline)->withNameFrom($name_source); $expected = $this->getFormWrappedHtml( 'url-field-input', $label, - '', + '', $byline, 'id_1' ); @@ -71,12 +65,13 @@ public function testRenderValue(): void $factory = $this->getFieldFactory(); $label = "Test Label"; $value = "https://www.ilias.de/"; + [$name_source, $name] = $this->getDefaultNameSourceStub(); $url = $factory->url($label)->withValue($value) - ->withNameFrom($this->name_source); + ->withNameFrom($name_source); $expected = $this->getFormWrappedHtml( 'url-field-input', $label, - '', + '', null, 'id_1' ); @@ -86,7 +81,8 @@ public function testRenderValue(): void public function testCommonRendering(): void { $f = $this->getFieldFactory(); - $url = $f->url('label', null)->withNameFrom($this->name_source); + [$name_source] = $this->getDefaultNameSourceStub(); + $url = $f->url('label', null)->withNameFrom($name_source); $this->testWithError($url); $this->testWithNoByline($url); diff --git a/components/ILIAS/UI/tests/Component/Input/FormInputNameSourceTest.php b/components/ILIAS/UI/tests/Component/Input/FormInputNameSourceTest.php deleted file mode 100755 index be4b2370be5d..000000000000 --- a/components/ILIAS/UI/tests/Component/Input/FormInputNameSourceTest.php +++ /dev/null @@ -1,60 +0,0 @@ - - */ -class FormInputNameSourceTest extends TestCase -{ - public function testNewNameGeneration(): void - { - $name_source = new FormInputNameSource(); - - $this->assertEquals( - 'input_0', - $name_source->getNewName() - ); - - $this->assertEquals( - 'input_1', - $name_source->getNewName() - ); - - $this->assertEquals( - 'dedicated', - $name_source->getNewDedicatedName('dedicated') - ); - - $this->assertEquals( - 'input_2', - $name_source->getNewName() - ); - - $this->assertEquals( - 'dedicated_3', - $name_source->getNewDedicatedName('dedicated') - ); - } -} diff --git a/components/ILIAS/UI/tests/Component/Input/DynamicInputDataIteratorTest.php b/components/ILIAS/UI/tests/Component/Input/HasDynamicInputsDataIteratorTest.php similarity index 89% rename from components/ILIAS/UI/tests/Component/Input/DynamicInputDataIteratorTest.php rename to components/ILIAS/UI/tests/Component/Input/HasDynamicInputsDataIteratorTest.php index 71c26f084bcc..9b86c038fdcc 100755 --- a/components/ILIAS/UI/tests/Component/Input/DynamicInputDataIteratorTest.php +++ b/components/ILIAS/UI/tests/Component/Input/HasDynamicInputsDataIteratorTest.php @@ -20,20 +20,20 @@ namespace ILIAS\Tests\UI\Component\Input; -use ILIAS\UI\Implementation\Component\Input\DynamicInputDataIterator; +use ILIAS\UI\Implementation\Component\Input\HasDynamicInputsDataIterator; +use ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource; use ILIAS\UI\Component\Input\InputData; use PHPUnit\Framework\TestCase; use LogicException; -use ILIAS\UI\Implementation\Component\Input\DynamicInputsNameSource; /** * @author Thibeau Fuhrer */ -class DynamicInputDataIteratorTest extends TestCase +class HasDynamicInputsDataIteratorTest extends TestCase { public function testValidityWithEmptyData(): void { - $iterator = new DynamicInputDataIterator( + $iterator = new HasDynamicInputsDataIterator( $this->getTestInputData([]), 'test_name_1' ); @@ -45,7 +45,7 @@ public function testValidityWithEmptyData(): void public function testValidityWithData(): void { - $iterator = new DynamicInputDataIterator( + $iterator = new HasDynamicInputsDataIterator( $this->getTestInputData([ 'test_input_1' => [ [ @@ -80,7 +80,7 @@ public function testCurrentValue(): void ] ]; - $iterator = new DynamicInputDataIterator( + $iterator = new HasDynamicInputsDataIterator( $this->getTestInputData($fake_post_array), $parent_input_name ); diff --git a/components/ILIAS/UI/tests/Component/Input/HasDynamicInputsNameSourceTest.php b/components/ILIAS/UI/tests/Component/Input/HasDynamicInputsNameSourceTest.php new file mode 100755 index 000000000000..ce1c8b066538 --- /dev/null +++ b/components/ILIAS/UI/tests/Component/Input/HasDynamicInputsNameSourceTest.php @@ -0,0 +1,132 @@ + + */ +class HasDynamicInputsNameSourceTest extends TestCase +{ + use NameSourceStubs; + + public function testGetNextNameWithoutIndices(): void + { + $prefix = 'input_'; + $new_name_source = $this->createCountingNameSourceStub($prefix); + $expected_parent_name = 'parent_input_name_xyz'; + + $name_source = new HasDynamicInputsNameSource($new_name_source); + $name_source = $name_source->withParentName($expected_parent_name)->withIndices(false); + + $this->assertEquals( + $expected_parent_name . "[{$prefix}0][]", + $name_source->getNextName() + ); + + $this->assertEquals( + $expected_parent_name . "[{$prefix}1][]", + $name_source->getNextName() + ); + } + + public function testGetNextNameWitIndices(): void + { + $prefix = 'input_'; + $new_name_source = $this->createCountingNameSourceStub($prefix); + $expected_parent_name = 'parent_input_name_xyz'; + + $name_source = new HasDynamicInputsNameSource($new_name_source); + $name_source = $name_source->withParentName($expected_parent_name)->withIndices(true); + + $this->assertEquals( + $expected_parent_name . "[{$prefix}0][0]", + $name_source->getNextName() + ); + + $this->assertEquals( + $expected_parent_name . "[{$prefix}1][0]", + $name_source->getNextName() + ); + } + + public function testGetNextNameWitIndicesAndWithResetDefaultNameSource(): void + { + $prefix = 'input_'; + $start_count_one = 0; + $start_count_two = 0; + $expected_parent_name = 'parent_input_name_xyz'; + + $new_name_source_two = $this->createCountingNameSourceStub($prefix, $start_count_two); + + // do not use createCountingNameSourceStub(), since this will return itself first on withReset(). + $new_name_source_one = $this->createMock(NameSource::class); + $new_name_source_one->method('getNextName')->willReturnCallback(static function () use ($prefix, &$start_count_one) { + return $prefix . ($start_count_one++); + }); + $new_name_source_one->method('withParentName')->willReturnSelf(); + $new_name_source_one->method('withReset')->willReturn($new_name_source_two); + + $name_source = new HasDynamicInputsNameSource($new_name_source_one); + $name_source = $name_source->withParentName($expected_parent_name)->withIndices(true); + + $this->assertEquals( + $expected_parent_name . "[{$prefix}0][0]", + $name_source->getNextName() + ); + $this->assertEquals( + $expected_parent_name . "[{$prefix}1][0]", + $name_source->getNextName() + ); + + $name_source = $name_source->withResetDefaultNameSource(); + + $this->assertEquals( + $expected_parent_name . "[{$prefix}0][1]", + $name_source->getNextName() + ); + $this->assertEquals( + $expected_parent_name . "[{$prefix}1][1]", + $name_source->getNextName() + ); + } + + public function testWithResetOnDefaultNameSource(): void + { + $default_name_source = $this->createFixedNameSourceStub(''); + $default_name_source->expects($this->exactly(2))->method('withReset'); + $name_source = new HasDynamicInputsNameSource($default_name_source); + $name_source = $name_source->withResetDefaultNameSource(); + $name_source = $name_source->withReset(); + } + + public function testWithParentNameIsMandatory(): void + { + $default_name_source = $this->createMock(NameSource::class); + $name_source = new HasDynamicInputsNameSource($default_name_source); + $this->expectException(\LogicException::class); + $name_source->getNextName(); + } +} diff --git a/components/ILIAS/UI/tests/Component/Input/NameSourceStubs.php b/components/ILIAS/UI/tests/Component/Input/NameSourceStubs.php new file mode 100644 index 000000000000..e8e552cecd91 --- /dev/null +++ b/components/ILIAS/UI/tests/Component/Input/NameSourceStubs.php @@ -0,0 +1,84 @@ + + */ +trait NameSourceStubs +{ + protected function createFixedNameSourceStub(string $name): NameSource&MockObject + { + $stub = $this->createMock(NameSource::class); + $stub->method('getNextName')->willReturn($name); + $stub->method('withParentName')->willReturnSelf(); + $stub->method('withReset')->willReturnSelf(); + return $stub; + } + + protected function createRelayArgumentNameSourceStub(): NameSource&MockObject + { + $stub = $this->createMock(NameSource::class); + $stub->method('getNextName')->willReturnCallback(function(?string $arg) { + if (null === $arg) { + $this->fail('Only use this NameSource stub if Input::withDedicatedName() is used.'); + } + return $arg; + }); + $stub->method('withParentName')->willReturnSelf(); + $stub->method('withReset')->willReturnSelf(); + return $stub; + } + + protected function createFixedHasDynamicInputsNameSourceStub(string $name): HasDynamicInputsNameSource&MockObject + { + $stub = $this->createMock(HasDynamicInputsNameSource::class); + $stub->method('getNextName')->willReturn($name); + $stub->method('withParentName')->willReturnSelf(); + $stub->method('withResetDefaultNameSource')->willReturnSelf(); + $stub->method('withIndices')->willReturnSelf(); + $stub->method('withReset')->willReturnSelf(); + return $stub; + } + + /** + * Attention: please only use this method instead of createFixedNameSourceStub() if it matters + * that multiple input's have different names. I.e. input data processing with withInput(). + * Everything else should be done with static names from createFixedNameSourceStub(). + * + * Note: most usages of this method are probably wrong at the moment. Usages should be checked + * and migrated towards createFixedNameSourceStub() while working on these test cases again. + */ + protected function createCountingNameSourceStub(string $prefix, int $start_count = 0): NameSource&MockObject + { + $stub = $this->createMock(NameSource::class); + $stub->method('getNextName')->willReturnCallback(static function () use ($prefix, &$start_count) { + return $prefix . ($start_count++); + }); + $stub->method('withParentName')->willReturnSelf(); + $stub->method('withReset')->willReturnSelf(); + return $stub; + } +} diff --git a/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlFieldSelectionTest.php b/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlFieldSelectionTest.php index ab4fa81effe4..9d9038f82563 100755 --- a/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlFieldSelectionTest.php +++ b/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlFieldSelectionTest.php @@ -69,7 +69,7 @@ public function testViewControlFieldSelectionWithInput(): void ->willReturn($v); $vc = $this->buildVCFactory()->fieldSelection($options) - ->withNameFrom($this->getNamesource()) + ->withNameFrom($this->createCountingNameSourceStub('name_')) ->withInput($input); $df = $this->buildDataFactory(); diff --git a/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlGroupTest.php b/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlGroupTest.php index 55329b88574a..694c8c96789e 100644 --- a/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlGroupTest.php +++ b/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlGroupTest.php @@ -27,6 +27,8 @@ class ViewControlGroupTest extends ViewControlTestBase { + use NameSourceStubs; + public function testViewControlGroupCreation(): void { $f = $this->buildVCFactory(); @@ -41,7 +43,7 @@ public function testViewControlGroupGetContent(): void { $f = $this->buildVCFactory(); $input = $this->createMock(InputData::class); - $namesource = new DefNamesource(); + $namesource = $this->createCountingNameSourceStub('input_'); $group = $f->group( [ diff --git a/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlInputGenericTest.php b/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlInputGenericTest.php index f0249f0f4c97..440b3e9b112e 100755 --- a/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlInputGenericTest.php +++ b/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlInputGenericTest.php @@ -68,7 +68,7 @@ public function testViewControlWithInput(): void ->willReturn($v); $vc = $this->getViewControl() - ->withNameFrom($this->getNamesource()) + ->withNameFrom($this->createCountingNameSourceStub('name_')) ->withInput($input); $df = $this->buildDataFactory(); diff --git a/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlModeTest.php b/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlModeTest.php index 30d7499739f7..10c415bb35a4 100644 --- a/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlModeTest.php +++ b/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlModeTest.php @@ -67,7 +67,7 @@ public function testViewControlModeWithInput(): void ->willReturn($v); $vc = $this->buildVCFactory()->mode($options) - ->withNameFrom($this->getNamesource()) + ->withNameFrom($this->createCountingNameSourceStub('name_')) ->withInput($input); $df = $this->buildDataFactory(); diff --git a/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlPaginationTest.php b/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlPaginationTest.php index aa4d659a5157..2a4c33c37af4 100755 --- a/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlPaginationTest.php +++ b/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlPaginationTest.php @@ -142,7 +142,7 @@ public function testViewControlPaginationWithInput( ); $vc = $this->buildVCFactory()->pagination() - ->withNameFrom($this->getNamesource()) + ->withNameFrom($this->createCountingNameSourceStub('name_')) ->withInput($input); $df = $this->buildDataFactory(); diff --git a/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlSortationTest.php b/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlSortationTest.php index f78a6de6a860..410b2c9f3f9f 100755 --- a/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlSortationTest.php +++ b/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlSortationTest.php @@ -70,7 +70,7 @@ public function testViewControlSortationWithInput(): void ); $vc = $this->buildVCFactory()->sortation($options) - ->withNameFrom($this->getNamesource()) + ->withNameFrom($this->createCountingNameSourceStub('name_')) ->withInput($input); $df = $this->buildDataFactory(); diff --git a/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlTestBase.php b/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlTestBase.php index 6ca1688c7ce0..d3acba792dfc 100644 --- a/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlTestBase.php +++ b/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlTestBase.php @@ -29,19 +29,7 @@ abstract class ViewControlTestBase extends ILIAS_UI_TestBase { - protected function getNamesource() - { - return new class () implements NameSource { - public int $count = 0; - public function getNewName(): string - { - $name = "name_{$this->count}"; - $this->count++; - - return $name; - } - }; - } + use NameSourceStubs; protected function buildDataFactory(): Data\Factory { @@ -60,6 +48,7 @@ protected function buildFieldFactory(): FieldFactory { return new FieldFactory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(UploadLimitResolver::class), new SignalGenerator(), $this->buildDataFactory(), diff --git a/components/ILIAS/UI/tests/Component/Launcher/LauncherInlineTest.php b/components/ILIAS/UI/tests/Component/Launcher/LauncherInlineTest.php index 504e8680ffcb..05cd682066b5 100755 --- a/components/ILIAS/UI/tests/Component/Launcher/LauncherInlineTest.php +++ b/components/ILIAS/UI/tests/Component/Launcher/LauncherInlineTest.php @@ -25,15 +25,21 @@ use ILIAS\UI\Implementation\Component as I; use ILIAS\Data\URI; use ILIAS\Refinery\Factory as Refinery; +use PHPUnit\Framework\MockObject\MockObject; class LauncherInlineTest extends ILIAS_UI_TestBase { + use NameSourceStubs; + protected ILIAS\Data\Factory $df; protected ILIAS\Language\Language $language; + protected I\Input\NameSource & MockObject $name_source; + protected string $input_name = 'input_name'; public function setUp(): void { $this->df = new \ILIAS\Data\Factory(); + $this->name_source = $this->createFixedNameSourceStub($this->input_name); } protected function getInputFactory(): I\Input\Field\Factory @@ -41,6 +47,7 @@ protected function getInputFactory(): I\Input\Field\Factory $this->language = $this->createMock(ILIAS\Language\Language::class); return new I\Input\Field\Factory( $this->createMock(\ILIAS\UI\Implementation\Component\Input\Field\Node\Factory::class), + $this->createMock(\ILIAS\UI\Implementation\Component\Input\HasDynamicInputsNameSource::class), $this->createMock(I\Input\UploadLimitResolver::class), new I\SignalGenerator(), $this->df, @@ -54,7 +61,8 @@ protected function getModalFactory(): I\Modal\Factory return new I\Modal\Factory( new I\SignalGenerator(), new I\Modal\InterruptiveItem\Factory(), - $this->getInputFactory() + $this->getInputFactory(), + $this->name_source, ); } @@ -68,6 +76,7 @@ public function getUIFactory(): NoUIFactory $factory = new class () extends NoUIFactory { public I\SignalGenerator $sig_gen; public I\Input\Field\Factory $input_factory; + public I\Input\NameSource $name_source; public function button(): I\Button\Factory { @@ -86,12 +95,14 @@ public function modal(): I\Modal\Factory return new I\Modal\Factory( $this->sig_gen, new I\Modal\InterruptiveItem\Factory(), - $this->input_factory + $this->input_factory, + $this->name_source, ); } }; $factory->sig_gen = new I\SignalGenerator(); $factory->input_factory = $this->getInputFactory(); + $factory->name_source = $this->name_source; return $factory; } @@ -178,14 +189,8 @@ public function testLauncherInlineWithFields(): void $l->getModal()->getContent()[0] ); - $ns = new class () extends I\Input\FormInputNameSource { - public function getNewName(): string - { - return 'form/input_0'; - } - }; $this->assertEquals( - [$field->withNameFrom($ns)], + [$field->withNameFrom($this->name_source)], $l->getModal()->getInputs() ); } @@ -229,10 +234,10 @@ public function testLauncherInlineRendering(): void