From e322a8e008719359b5a66603a658daa9dbd48804 Mon Sep 17 00:00:00 2001 From: Brian Hanson Date: Wed, 8 Apr 2026 16:30:12 -0500 Subject: [PATCH 01/37] Inital craft-info-icon Replaces the infoIcon and new Craft.InfoIcon code --- .../components/info-icon/info-icon.stories.ts | 25 ++++ .../src/components/info-icon/info-icon.ts | 95 ++++++++++++++ .../visually-hidden/visually-hidden.ts | 33 +++++ packages/craftcms-cp/src/index.ts | 2 + src/Cp/Html/ElementHtml.php | 5 +- .../legacy/web/assets/cp/src/js/InfoIcon.js | 120 ++---------------- 6 files changed, 166 insertions(+), 114 deletions(-) create mode 100644 packages/craftcms-cp/src/components/info-icon/info-icon.stories.ts create mode 100644 packages/craftcms-cp/src/components/info-icon/info-icon.ts create mode 100644 packages/craftcms-cp/src/components/visually-hidden/visually-hidden.ts diff --git a/packages/craftcms-cp/src/components/info-icon/info-icon.stories.ts b/packages/craftcms-cp/src/components/info-icon/info-icon.stories.ts new file mode 100644 index 00000000000..96d1e7d06d0 --- /dev/null +++ b/packages/craftcms-cp/src/components/info-icon/info-icon.stories.ts @@ -0,0 +1,25 @@ +import type {Meta, StoryObj} from '@storybook/web-components-vite'; +import './info-icon'; + +const meta: Meta = { + title: 'Components/Info Icon', + tags: ['autodocs'], + args: {}, + render: (args) => { + return ` + + This is the content for the tooltip + `; + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + label: 'More Info', + icon: 'circle-info', + }, +}; diff --git a/packages/craftcms-cp/src/components/info-icon/info-icon.ts b/packages/craftcms-cp/src/components/info-icon/info-icon.ts new file mode 100644 index 00000000000..9f3934dd79b --- /dev/null +++ b/packages/craftcms-cp/src/components/info-icon/info-icon.ts @@ -0,0 +1,95 @@ +import {t} from '@src/utilities/translate'; +import {html, LitElement} from 'lit'; +import {property, query, queryAssignedElements, state} from 'lit/decorators.js'; + +import '../button/button'; +import '../icon/icon'; +import '../tooltip/tooltip'; +import '../visually-hidden/visually-hidden'; + +export default class CraftInfoIcon extends LitElement { + @property() label = t('More Info'); + + @property() icon = 'circle-info'; + + @property({type: Boolean, reflect: true}) disabled = false; + + @property() override id: string; + + @state() status = ''; + + @query('c-tooltip') tooltip!: HTMLElement; + + #eventController = new AbortController(); + + override connectedCallback() { + super.connectedCallback(); + + // Recreate event controller if it was aborted + if (this.#eventController.signal.aborted) { + this.#eventController = new AbortController(); + } + + if (!this.id) { + this.id = `info-icon-${Math.random().toString(36).slice(2, 8)}`; + } + + const {signal} = this.#eventController; + + this.addEventListener( + 'wa-after-show', + () => { + this.status = ''; + setTimeout(() => { + this.status = 'Some new status'; + }, 200); + }, + {signal} + ); + + this.addEventListener( + 'wa-after-hide', + () => { + this.status = ''; + }, + {signal} + ); + } + + override disconnectedCallback() { + this.#eventController.abort(); + super.disconnectedCallback(); + } + + override render() { + return html` +
+ + ${this.status} + + + + + + + +
+ `; + } +} + +if (!customElements.get('craft-info-icon')) { + customElements.define('craft-info-icon', CraftInfoIcon); +} + +declare global { + interface HTMLElementTagNameMap { + 'craft-info-icon': CraftInfoIcon; + } +} diff --git a/packages/craftcms-cp/src/components/visually-hidden/visually-hidden.ts b/packages/craftcms-cp/src/components/visually-hidden/visually-hidden.ts new file mode 100644 index 00000000000..3aca980a7bb --- /dev/null +++ b/packages/craftcms-cp/src/components/visually-hidden/visually-hidden.ts @@ -0,0 +1,33 @@ +import {css, html, LitElement} from 'lit'; +import CraftInfoIcon from '@src/components/info-icon/info-icon'; +import {property} from 'lit/decorators.js'; + +export default class CraftVisuallyHidden extends LitElement { + static override styles = css` + :host(:not([debug])) { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip: rect(0 0 0 0); + clip-path: inset(50%); + white-space: nowrap; + } + `; + + @property({type: Boolean, reflect: true}) debug = false; + + protected override render(): unknown { + return html``; + } +} + +if (!customElements.get('craft-visually-hidden')) { + customElements.define('craft-visually-hidden', CraftVisuallyHidden); +} + +declare global { + interface HTMLElementTagNameMap { + 'craft-visually-hidden': CraftVisuallyHidden; + } +} diff --git a/packages/craftcms-cp/src/index.ts b/packages/craftcms-cp/src/index.ts index 7e946712af0..36bc79c1bae 100644 --- a/packages/craftcms-cp/src/index.ts +++ b/packages/craftcms-cp/src/index.ts @@ -21,6 +21,7 @@ export {default as CraftSelect} from './components/select/select.js'; export {default as CraftOption} from './components/option/option.js'; export {default as CraftDropdown} from './components/dropdown/dropdown.js'; export {default as CraftIcon} from './components/icon/icon.js'; +export {default as CraftInfoIcon} from './components/info-icon/info-icon.js'; export {default as CraftTabs} from './components/tabs/tabs.js'; export {default as CraftCard} from './components/card/card.js'; export {default as CraftTab} from './components/tab/tab.js'; @@ -46,6 +47,7 @@ export {default as CraftProgress} from './components/progress/progress.js'; export {default as CraftProgressBar} from './components/progress-bar/progress-bar.js'; export {default as CraftRadioGroup} from './components/radio-group/radio-group.js'; export {default as CraftRadio} from './components/radio/radio.js'; +export {default as CraftVisuallyHidden} from './components/visually-hidden/visually-hidden.js'; /* plop:component */ export * from './utilities/cookies.js'; diff --git a/src/Cp/Html/ElementHtml.php b/src/Cp/Html/ElementHtml.php index cd4cb9661da..214815cf706 100644 --- a/src/Cp/Html/ElementHtml.php +++ b/src/Cp/Html/ElementHtml.php @@ -153,9 +153,8 @@ public function chipHtml(Chippable $component, array $config = []): string /** @var Chippable&Describable $component */ $description = $component->getDescription(); if ($description) { - $labelHtml .= Html::tag('span', - $this->contentHtml->parseMarkdown(Html::encode($description)), - ['class' => 'info']); + $labelHtml .= Html::tag('craft-info-icon', + $this->contentHtml->parseMarkdown(Html::encode($description))); } } diff --git a/yii2-adapter/legacy/web/assets/cp/src/js/InfoIcon.js b/yii2-adapter/legacy/web/assets/cp/src/js/InfoIcon.js index e062ea948b8..a464550c6e2 100644 --- a/yii2-adapter/legacy/web/assets/cp/src/js/InfoIcon.js +++ b/yii2-adapter/legacy/web/assets/cp/src/js/InfoIcon.js @@ -4,119 +4,17 @@ * Info icon class */ Craft.InfoIcon = Garnish.Base.extend({ - $container: null, - $icon: null, - $liveRegion: null, - content: null, - hud: null, - + /** + * + * @param HTMLElement icon + */ init: function (icon) { - if ($(icon).hasClass('disabled')) { - return; - } - - this.$icon = $(icon); - this.$liveRegion = $('', { - role: 'status', - class: 'visually-hidden', - }); - - if (this.$icon.data('infoicon')) { - console.warn('Double-instantiating an info icon on an element'); - this.content = this.$icon.data('infoicon').content; - this.$icon.data('infoicon').destroy(); - } else { - this.content = this.$icon.html(); - this.$icon - .html('') - .attr({ - tabindex: 0, - role: 'button', - type: 'button', - 'aria-label': Craft.t('app', 'More info'), - }) - .wrap( - $('', { - class: 'infoicon-container', - }) - ); - - this.$container = this.$icon.parent(); - this.$container.append(this.$liveRegion); - } - - this.$icon.data('infoicon', this); - - if ( - this.$icon[0].previousSibling && - this.$icon[0].previousSibling.nodeType === Node.TEXT_NODE - ) { - // Make sure it's in a .nowrap container - const $parent = this.$icon.parent(); - if (!$parent.hasClass('nowrap')) { - // Find the last word in the text - const m = this.$icon[0].previousSibling.nodeValue.match(/[^\s\-]+\s*$/); - if (m) { - this.$icon[0].previousSibling.nodeValue = - this.$icon[0].previousSibling.nodeValue.substring(0, m.index); - $('', { - class: 'nowrap', - html: m[0].replace(/\s+$/, '') + ' ', - }) - .insertAfter(this.$icon[0].previousSibling) - .append(this.$icon); - } - } + this.icon = document.createElement('craft-info-icon'); + if (icon.classList.contains('disabled')) { + this.icon.setAttribute('disabled', ''); } + this.icon.innerHTML = icon.innerHTML; - this.addListener(this.$icon, 'click', (ev) => { - ev.preventDefault(); - ev.stopPropagation(); - this.showHud(); - }); - - this.addListener(this.$icon, 'keydown', (ev) => { - if ( - !(this.hud && this.hud.showing) && - [Garnish.SPACE_KEY, Garnish.RETURN_KEY].includes(ev.keyCode) - ) { - ev.preventDefault(); - ev.stopPropagation(); - this.showHud(); - } - }); - }, - - showHud: function (ev) { - if (!this.hud) { - this.hud = new Garnish.HUD(this.$icon, this.content, { - hudClass: 'hud info-hud', - closeOtherHUDs: false, - onShow: () => { - Garnish.uiLayerManager.registerShortcut(Garnish.SPACE_KEY, () => { - this.hud.hide(); - }); - - this.$liveRegion.html(''); - - setTimeout(() => { - this.$liveRegion.html(this.content); - }, 200); - }, - onHide: () => { - this.$liveRegion.html(''); - }, - }); - Craft.initUiElements(this.hud.$body); - } else { - this.hud.show(); - } - }, - - destroy: function () { - this.hud?.destroy(); - this.$icon.removeData('infoicon'); - this.removeAllListeners(this.$icon); - this.base(); + icon.replaceWith(this.icon); }, }); From 13eb47cff52982559525b7e11b5ef228d17e4613 Mon Sep 17 00:00:00 2001 From: Brian Hanson Date: Wed, 8 Apr 2026 22:34:37 -0500 Subject: [PATCH 02/37] Replace AdminTable info icon --- resources/js/components/AdminTable/AdminTable.vue | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/resources/js/components/AdminTable/AdminTable.vue b/resources/js/components/AdminTable/AdminTable.vue index 900120b824f..4e63630f863 100644 --- a/resources/js/components/AdminTable/AdminTable.vue +++ b/resources/js/components/AdminTable/AdminTable.vue @@ -195,19 +195,9 @@ From 6d83f06056d63ec8bc5b203ab437ca86ef863c10 Mon Sep 17 00:00:00 2001 From: Brian Hanson Date: Wed, 8 Apr 2026 22:34:50 -0500 Subject: [PATCH 03/37] Deprecation warning --- yii2-adapter/legacy/web/assets/cp/src/js/InfoIcon.js | 1 + 1 file changed, 1 insertion(+) diff --git a/yii2-adapter/legacy/web/assets/cp/src/js/InfoIcon.js b/yii2-adapter/legacy/web/assets/cp/src/js/InfoIcon.js index a464550c6e2..7ac1f8853c9 100644 --- a/yii2-adapter/legacy/web/assets/cp/src/js/InfoIcon.js +++ b/yii2-adapter/legacy/web/assets/cp/src/js/InfoIcon.js @@ -9,6 +9,7 @@ Craft.InfoIcon = Garnish.Base.extend({ * @param HTMLElement icon */ init: function (icon) { + console.warn('Craft.InfoIcon is deprecated. Use instead.'); this.icon = document.createElement('craft-info-icon'); if (icon.classList.contains('disabled')) { this.icon.setAttribute('disabled', ''); From 79e1b35624740c5ff754d633f4bd7336fbeaa25e Mon Sep 17 00:00:00 2001 From: Brian Hanson Date: Wed, 8 Apr 2026 22:35:00 -0500 Subject: [PATCH 04/37] Only one info-icon at a time --- .../components/info-icon/info-icon.stories.ts | 11 +++++++ .../src/components/info-icon/info-icon.ts | 29 ++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/packages/craftcms-cp/src/components/info-icon/info-icon.stories.ts b/packages/craftcms-cp/src/components/info-icon/info-icon.stories.ts index 96d1e7d06d0..f17461b287b 100644 --- a/packages/craftcms-cp/src/components/info-icon/info-icon.stories.ts +++ b/packages/craftcms-cp/src/components/info-icon/info-icon.stories.ts @@ -23,3 +23,14 @@ export const Default: Story = { icon: 'circle-info', }, }; + +export const Multiple: Story = { + render: () => { + return ` +
+ Tooltip content for icon 1 + Tooltip content for icon 2 + Tooltip content for icon 3 +
`; + }, +}; diff --git a/packages/craftcms-cp/src/components/info-icon/info-icon.ts b/packages/craftcms-cp/src/components/info-icon/info-icon.ts index 9f3934dd79b..c407bc635a8 100644 --- a/packages/craftcms-cp/src/components/info-icon/info-icon.ts +++ b/packages/craftcms-cp/src/components/info-icon/info-icon.ts @@ -5,9 +5,12 @@ import {property, query, queryAssignedElements, state} from 'lit/decorators.js'; import '../button/button'; import '../icon/icon'; import '../tooltip/tooltip'; +import type CraftTooltip from '../tooltip/tooltip'; import '../visually-hidden/visually-hidden'; export default class CraftInfoIcon extends LitElement { + static #openInstance: CraftInfoIcon | null = null; + @property() label = t('More Info'); @property() icon = 'circle-info'; @@ -36,6 +39,24 @@ export default class CraftInfoIcon extends LitElement { const {signal} = this.#eventController; + this.addEventListener( + 'wa-show', + () => { + if ( + CraftInfoIcon.#openInstance && + CraftInfoIcon.#openInstance !== this + ) { + const otherTooltip = + CraftInfoIcon.#openInstance.renderRoot.querySelector( + 'c-tooltip' + ); + otherTooltip?.hide(); + } + CraftInfoIcon.#openInstance = this; + }, + {signal} + ); + this.addEventListener( 'wa-after-show', () => { @@ -50,6 +71,9 @@ export default class CraftInfoIcon extends LitElement { this.addEventListener( 'wa-after-hide', () => { + if (CraftInfoIcon.#openInstance === this) { + CraftInfoIcon.#openInstance = null; + } this.status = ''; }, {signal} @@ -57,6 +81,9 @@ export default class CraftInfoIcon extends LitElement { } override disconnectedCallback() { + if (CraftInfoIcon.#openInstance === this) { + CraftInfoIcon.#openInstance = null; + } this.#eventController.abort(); super.disconnectedCallback(); } @@ -67,7 +94,7 @@ export default class CraftInfoIcon extends LitElement { ${this.status} - + Date: Thu, 9 Apr 2026 10:38:22 -0500 Subject: [PATCH 05/37] Update link field instructions with new icon --- yii2-adapter/legacy/web/assets/cp/dist/cp.js | 2 +- yii2-adapter/legacy/web/assets/cp/dist/cp.js.map | 2 +- yii2-adapter/legacy/web/assets/cp/src/js/Craft.js | 11 ++++++----- yii2-adapter/legacy/web/assets/cp/src/js/InfoIcon.js | 4 +++- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/yii2-adapter/legacy/web/assets/cp/dist/cp.js b/yii2-adapter/legacy/web/assets/cp/dist/cp.js index 4e54c339121..3947e06987e 100644 --- a/yii2-adapter/legacy/web/assets/cp/dist/cp.js +++ b/yii2-adapter/legacy/web/assets/cp/dist/cp.js @@ -1,2 +1,2 @@ -(function(){var __webpack_modules__={333:function(){Craft.CpModal=Garnish.Modal.extend({action:null,namespace:null,showingLoadSpinner:!1,$loadSpinner:null,$container:null,$body:null,$content:null,$sidebar:null,$footer:null,$cancelBtn:null,$saveBtn:null,showingSidebar:!1,cancelToken:null,ignoreFailedRequest:!1,fieldsWithErrors:null,init:function(t,e){this.action=t,this.setSettings(e,Craft.CpModal.defaults),this.fieldsWithErrors=[],this.$body=$("
",{class:"cpmodal-body"}),this.$content=$("
",{class:"cpmodal-content"}).appendTo(this.$body),this.$footer=$("
",{class:"cpmodal-footer hidden"}),$("
",{class:"flex-grow"}).appendTo(this.$footer);const i=$("
",{class:"flex flex-nowrap"}).appendTo(this.$footer);this.$loadSpinner=$("
",{class:"spinner",title:Craft.t("app","Loading"),"aria-label":Craft.t("app","Loading")}).prependTo(i),this.$cancelBtn=$("\n \n
\n
\n
\n`).prependTo(this.$container),this.menu=this.$chip.find(".action-btn").disclosureMenu().data("disclosureMenu"),this.initChip()},createTextInput:function(t){this.reset(),this.$textInput=Craft.ui.createTextInput(this.settings.inputAttributes).attr("name",this.settings.inputName).val(t).prependTo(this.$container),this.initTextInput(),this.$textInput.trigger("input")},switchToTextInput:function(){const t=this.removeFirstPrefix(this.$hiddenInput.val());this.createTextInput(t)},initTextInput:function(){this.addListener(this.$textInput,"input",()=>{const t=this.normalize(this.$textInput.val());this.$hiddenInput.val(t),this.field.updateLabel(this.removePrefix(t))}),this.addListener(this.$textInput,"blur",()=>{this.maybeSwitchToChip()}),this.addListener(this.$textInput,"keydown",t=>{t.keyCode===Garnish.ESC_KEY&&this.maybeSwitchToChip()&&(t.stopPropagation(),this.$chip.find("a").focus())})},normalize:function(t){if(!(t=Craft.trim(t)))return"";const e=this.ensurePrefix(t);return this.validate(e)?e:t},validate:function(t){return!!(t=s.toASCII(t)).match(new RegExp(this.settings.pattern,"i"))},maybeSwitchToChip:function(){if(!this.$textInput?.length)return;const t=this.normalize(this.$textInput.val());return!(!t||!this.validate(t)||(this.createChip(t),0))},initChip:function(){const t=this.menu.addItem({label:Craft.t("app","View in a new tab"),icon:async()=>await Craft.ui.icon("share")}),e=this.menu.addItem({label:Craft.t("app","Edit"),icon:async()=>await Craft.ui.icon("pencil")}),i=this.menu.addItem({label:Craft.t("app","Copy URL"),icon:async()=>await Craft.ui.icon("link")});this.menu.addHr(),this.menu.addGroup();const s=this.menu.addItem({label:"Remove",icon:async()=>await Craft.ui.icon("xmark"),destructive:!0});this.addListener(i,"activate",()=>{Craft.ui.createCopyTextPrompt({label:"Full URL",value:this.$hiddenInput.val().replace(/ /g,"+")})}),this.addListener(t,"activate",()=>{window.open(this.$chip.find("a").attr("href"))}),this.addListener(e,"activate",()=>{this.switchToTextInput(),this.$textInput.focus()}),this.addListener(s,"activate",()=>{this.createTextInput(""),this.$textInput.focus()})},reset:function(){this.$textInput?.remove(),this.$chip?.remove(),this.menu?.destroy(),this.$textInput=this.$chip=this.menu=null}},{defaults:{prefixes:null,pattern:null,textInputAttributes:{}}})},1761:function(){Craft.ThumbsElementIndexView=Craft.BaseElementIndexView.extend({getElementContainer:function(){return this.$container.children("ul")}})},2034:function(){Craft.BaseElementSelectorModal=Garnish.Modal.extend({elementType:null,elementIndex:null,supportSidebarToggleView:!1,$body:null,$content:null,$footer:null,$selectBtn:null,$sidebar:null,$sources:null,$sourceToggles:null,$sidebarToggleBtn:null,$sidebarCloseBtn:null,$mainHeading:null,$main:null,$search:null,$elements:null,$tbody:null,$primaryButtons:null,$secondaryButtons:null,$cancelBtn:null,init:function(t,e){this.elementType=t,this.setSettings(e,Craft.BaseElementSelectorModal.defaults);const i="elementSelectorModalHeading-"+Math.floor(1e6*Math.random()),s=$("
",{class:"modal elementselectormodal","aria-labelledby":i}).appendTo(Garnish.$bod),n=$("
",{class:this.settings.showTitle?"header":"visually-hidden"}).appendTo(s);$("

",{id:i,text:this.settings.modalTitle}).appendTo(n);const a=$("
",{class:"body"}).append($("
",{class:"spinner big"})).appendTo(s);this.$footer=$("
",{class:"footer"}).appendTo(s),this.settings.fullscreen&&(s.addClass("fullscreen"),this.settings.minGutter=0),this.base(s,this.settings),this.$secondaryButtons=$('
').appendTo(this.$footer),this.$primaryButtons=$('
').appendTo(this.$footer),this.$cancelBtn=$("
").appendTo(this.$promptChoices).find("input");this.addListener(l,"click",function(){r.removeClass("disabled")})}this.addListener(r,"activate",function(t){var e=$(t.currentTarget).parents(".modal").find("input[name=promptAction]:checked").val(),i=this.$promptApplyToRemainingCheckbox.prop("checked");this._selectPromptChoice(e,i)}),this.addListener(a,"activate",function(){var t=this.$promptApplyToRemainingCheckbox.prop("checked");this._selectPromptChoice("cancel",t)}),s&&(this.$promptApplyToRemainingContainer.show(),this.$promptApplyToRemainingLabel.html(" "+Craft.t("app","Apply this to the {number} remaining conflicts?",{number:s}))),this.modal.show(),this.modal.removeListener(Garnish.Modal.$shade,"click"),this.addListener(Garnish.Modal.$shade,"click","_cancelPrompt")},_selectPromptChoice:function(t,e){this.$prompt.fadeOut("fast",()=>{this.modal.hide(),this._promptCallback(t,e)})},_cancelPrompt:function(){this._selectPromptChoice("cancel",!0)}})},3374:function(){Craft.BaseUploader=Garnish.Base.extend({allowedKinds:null,$element:null,$fileInput:null,settings:null,fsType:null,formData:{},events:{},_rejectedFiles:{},_extensionList:null,_inProgressCounter:0,init:function(t,e){this._rejectedFiles={size:[],type:[],limit:[]},this.$element=t,this.settings=$.extend({},Craft.BaseUploader.defaults,e),this.formData=this.settings.formData,this.$fileInput=this.settings.fileInput||t,this.events=this.settings.events,this.settings.url||(this.settings.url=this.settings.replace?Craft.getActionUrl(this.settings.replaceAction):Craft.getActionUrl(this.settings.createAction)),this.settings.allowedKinds&&this.settings.allowedKinds.length&&("string"==typeof this.settings.allowedKinds&&(this.settings.allowedKinds=[this.settings.allowedKinds]),this.allowedKinds=this.settings.allowedKinds,delete this.settings.allowedKinds)},setParams:function(t){void 0!==Craft.csrfTokenName&&void 0!==Craft.csrfTokenValue&&(t[Craft.csrfTokenName]=Craft.csrfTokenValue),this.formData=t},getInProgress:function(){return this._inProgressCounter},isLastUpload:function(){return this.getInProgress()<2},processErrorMessages:function(){var t;this._rejectedFiles.type.length&&(t=1===this._rejectedFiles.type.length?"The file {files} could not be uploaded. The allowed file kinds are: {kinds}.":"The files {files} could not be uploaded. The allowed file kinds are: {kinds}.",t=Craft.t("app",t,{files:this._rejectedFiles.type.join(", "),kinds:this.allowedKinds.join(", ")}),this._rejectedFiles.type=[],Craft.cp.displayError(t)),this._rejectedFiles.size.length&&(t=1===this._rejectedFiles.size.length?"The file {files} could not be uploaded, because it exceeds the maximum upload size of {size}.":"The files {files} could not be uploaded, because they exceeded the maximum upload size of {size}.",t=Craft.t("app",t,{files:this._rejectedFiles.size.join(", "),size:this.humanFileSize(this.settings.maxFileSize)}),this._rejectedFiles.size=[],Craft.cp.displayError(t)),this._rejectedFiles.limit.length&&(t=1===this._rejectedFiles.limit.length?"The file {files} could not be uploaded, because the field limit has been reached.":"The files {files} could not be uploaded, because the field limit has been reached.",t=Craft.t("app",t,{files:this._rejectedFiles.limit.join(", ")}),this._rejectedFiles.limit=[],Craft.cp.displayError(t))},humanFileSize:function(t){var e=1024;if(t=e);return t.toFixed(1)+" "+["kB","MB","GB","TB","PB","EB","ZB","YB"][i]},_createExtensionList:function(){this._extensionList=[];for(var t=0;t