diff --git a/abstract/ActivityBlock.js b/abstract/ActivityBlock.js index 3155f04f1..6878ed45e 100644 --- a/abstract/ActivityBlock.js +++ b/abstract/ActivityBlock.js @@ -138,9 +138,7 @@ export class ActivityBlock extends Block { /** @type {string | null} */ const currentActivity = this.$['*currentActivity']; - /** @type {Set} */ - let blocksRegistry = this.$['*blocksRegistry']; - const hasCurrentActivityInCtx = !![...blocksRegistry].find( + const hasCurrentActivityInCtx = !![...this.blocksRegistry].find( (block) => block instanceof ActivityBlock && block.activityType === currentActivity, ); @@ -177,10 +175,8 @@ export class ActivityBlock extends Block { } let couldOpenActivity = !!nextActivity; if (nextActivity) { - /** @type {Set} */ - let blocksRegistry = this.$['*blocksRegistry']; - const nextActivityBlock = [...blocksRegistry].find((block) => block.activityType === nextActivity); - couldOpenActivity = nextActivityBlock?.couldOpenActivity ?? false; + const nextActivityBlock = [...this.blocksRegistry].find((block) => block.activityType === nextActivity); + couldOpenActivity = /** @type {ActivityBlock} */ (nextActivityBlock)?.couldOpenActivity ?? false; } nextActivity = couldOpenActivity ? nextActivity : undefined; this.$['*currentActivity'] = nextActivity; diff --git a/abstract/Block.js b/abstract/Block.js index 47fefeac2..f4e68c554 100644 --- a/abstract/Block.js +++ b/abstract/Block.js @@ -12,6 +12,7 @@ import { blockCtx } from './CTX.js'; import { LocaleManager, localeStateKey } from './LocaleManager.js'; import { l10nProcessor } from './l10nProcessor.js'; import { sharedConfigKey } from './sharedConfigKey.js'; +import { A11y } from './a11y.js'; const TAG_PREFIX = 'lr-'; @@ -96,10 +97,7 @@ export class Block extends BaseComponent { * @returns {Boolean} */ hasBlockInCtx(callback) { - // @ts-ignore TODO: fix this - /** @type {Set} */ - let blocksRegistry = this.$['*blocksRegistry']; - for (let block of blocksRegistry) { + for (let block of this.blocksRegistry) { if (callback(block)) { return true; } @@ -180,6 +178,10 @@ export class Block extends BaseComponent { this.add('*localeManager', new LocaleManager(this)); } + if (!this.has('*a11y')) { + this.add('*a11y', new A11y()); + } + this.sub(localeStateKey('locale-id'), (localeId) => { const direction = getLocaleDirection(localeId); this.style.direction = direction === 'ltr' ? '' : direction; @@ -191,9 +193,18 @@ export class Block extends BaseComponent { return this.has('*localeManager') ? this.$['*localeManager'] : null; } + /** @returns {A11y | null} */ + get a11y() { + return this.has('*a11y') ? this.$['*a11y'] : null; + } + + /** @type {Set} */ + get blocksRegistry() { + return this.$['*blocksRegistry']; + } + destroyCallback() { - /** @type {Set} */ - let blocksRegistry = this.$['*blocksRegistry']; + let blocksRegistry = this.blocksRegistry; blocksRegistry.delete(this); this.localeManager?.destroyL10nBindings(this); @@ -220,6 +231,7 @@ export class Block extends BaseComponent { Data.deleteCtx(this.ctxName); this.localeManager?.destroy(); + this.a11y?.destroy(); } /** diff --git a/abstract/SolutionBlock.js b/abstract/SolutionBlock.js index 1f466032c..4f5e7eed7 100644 --- a/abstract/SolutionBlock.js +++ b/abstract/SolutionBlock.js @@ -8,6 +8,11 @@ export class SolutionBlock extends Block { init$ = uploaderBlockCtx(this); _template = null; + initCallback() { + super.initCallback(); + this.a11y?.registerBlock(this); + } + static set template(value) { this._template = svgIconsSprite + value + /** HTML */ ``; } diff --git a/abstract/UploaderBlock.js b/abstract/UploaderBlock.js index fadb23975..5f1d67a69 100644 --- a/abstract/UploaderBlock.js +++ b/abstract/UploaderBlock.js @@ -308,8 +308,7 @@ export class UploaderBlock extends ActivityBlock { return; } - /** @type {Set} */ - const blocksRegistry = this.$['*blocksRegistry']; + const blocksRegistry = this.blocksRegistry; /** * @param {import('./Block').Block} block * @returns {block is import('../blocks/SourceBtn/SourceBtn.js').SourceBtn} diff --git a/abstract/a11y.js b/abstract/a11y.js new file mode 100644 index 000000000..f29f1dce6 --- /dev/null +++ b/abstract/a11y.js @@ -0,0 +1,118 @@ +// @ts-check +import { startKeyUX, hiddenKeyUX, jumpKeyUX, focusGroupKeyUX, pressKeyUX } from 'keyux'; + +/** + * MinimalWindow interface is not exported by keyux, so we import it here using tricky way. + * + * @typedef {Parameters[0]} MinimalWindow + */ + +/** + * This is global window wrapper that allows to scope event listeners to a specific part of the DOM. + * + * It is used to scope the key UX to the widget. + * + * @implements {MinimalWindow} + */ +class ScopedMinimalWindow { + /** + * @private + * @type {Map void>} + */ + _listeners = new Map(); + + /** + * @private + * @type {Node[]} + */ + _scope = []; + + /** + * @param {'keydown' | 'keyup'} type + * @param {(event: Event) => void} listener + */ + addEventListener(type, listener) { + /** @param {Event} e */ + const wrappedListener = (e) => { + const target = e.target; + if (!target) return; + if (this._scope.some((el) => el === e.target || el.contains(/** @type {Node} */ (target)))) { + listener(e); + } + }; + this._listeners.set(listener, wrappedListener); + window.addEventListener(type, wrappedListener); + } + + /** + * @param {'keydown' | 'keyup'} type + * @param {(event: {}) => void} listener + */ + removeEventListener(type, listener) { + const wrappedListener = this._listeners.get(listener); + if (wrappedListener) { + window.removeEventListener(type, wrappedListener); + } + this._listeners.delete(listener); + } + + get CustomEvent() { + return window.CustomEvent; + } + + get document() { + return window.document; + } + + get navigator() { + return window.navigator; + } + + /** @param {Node} scope */ + registerScope(scope) { + this._scope.push(scope); + } + + destroy() { + this._scope = []; + this._listeners.forEach((listener, originalListener) => { + window.removeEventListener('keydown', listener); + window.removeEventListener('keyup', listener); + this._listeners.delete(originalListener); + }); + } +} + +export class A11y { + /** + * @private + * @type {(() => void) | undefined} + */ + _destroyKeyUX; + + /** + * @private + * @type {ScopedMinimalWindow} + */ + _scopedWindow; + + constructor() { + this._scopedWindow = new ScopedMinimalWindow(); + this._destroyKeyUX = startKeyUX(this._scopedWindow, [ + focusGroupKeyUX(), + pressKeyUX('is-pressed'), + jumpKeyUX(), + hiddenKeyUX(), + ]); + } + + /** @param {import('./Block.js').Block} scope */ + registerBlock(scope) { + this._scopedWindow.registerScope(scope); + } + + destroy() { + this._destroyKeyUX?.(); + this._scopedWindow.destroy(); + } +} diff --git a/blocks/CloudImageEditor/src/EditorButtonControl.js b/blocks/CloudImageEditor/src/EditorButtonControl.js index e8cf27665..3dd6b66cc 100644 --- a/blocks/CloudImageEditor/src/EditorButtonControl.js +++ b/blocks/CloudImageEditor/src/EditorButtonControl.js @@ -16,11 +16,6 @@ export class EditorButtonControl extends Block { this._titleEl = this.ref['title-el']; this._iconEl = this.ref['icon-el']; - this.setAttribute('role', 'button'); - if (this.tabIndex === -1) { - this.tabIndex = 0; - } - this.sub('title', (title) => { let titleEl = this._titleEl; if (titleEl) { @@ -42,7 +37,8 @@ export class EditorButtonControl extends Block { } EditorButtonControl.template = /* HTML */ ` -
- -
{{title}}
+ `; diff --git a/blocks/CloudImageEditor/src/EditorFilterControl.js b/blocks/CloudImageEditor/src/EditorFilterControl.js index b027af778..2a8e92e97 100644 --- a/blocks/CloudImageEditor/src/EditorFilterControl.js +++ b/blocks/CloudImageEditor/src/EditorFilterControl.js @@ -148,7 +148,8 @@ export class EditorFilterControl extends EditorButtonControl { } EditorFilterControl.template = /* HTML */ ` -
-
- + `; diff --git a/blocks/CloudImageEditor/src/EditorToolbar.js b/blocks/CloudImageEditor/src/EditorToolbar.js index 10b3ffb22..6c946cb9c 100644 --- a/blocks/CloudImageEditor/src/EditorToolbar.js +++ b/blocks/CloudImageEditor/src/EditorToolbar.js @@ -25,8 +25,7 @@ function renderTabToggle(id) { ref="tab-toggle-${id}" data-id="${id}" icon="${id}" - tabindex="0" - set="onclick: on.clickTab;" + set="onclick: on.clickTab; aria-role:tab_role; aria-controls:tab_${id}" > @@ -36,10 +35,14 @@ function renderTabToggle(id) { /** @param {String} id */ function renderTabContent(id) { return /* HTML */ ` - +
-
+
@@ -115,6 +118,10 @@ export class EditorToolbar extends Block { this._activateTab(id, { fromViewer: false }); } }, + tab_role: 'tab', + [`tab_${TabId.TUNING}`]: `tab_${TabId.TUNING}`, + [`tab_${TabId.CROP}`]: `tab_${TabId.CROP}`, + [`tab_${TabId.FILTERS}`]: `tab_${TabId.FILTERS}`, }; /** @private */ @@ -392,14 +399,18 @@ EditorToolbar.template = /* HTML */ `
- +
${ALL_TABS.map(renderTabContent).join('')}
-
${ALL_TABS.map(renderTabToggle).join('')}
+
diff --git a/blocks/CloudImageEditor/src/css/common.css b/blocks/CloudImageEditor/src/css/common.css index 84ed9c58a..67d0e0ca6 100644 --- a/blocks/CloudImageEditor/src/css/common.css +++ b/blocks/CloudImageEditor/src/css/common.css @@ -340,7 +340,13 @@ lr-editor-operation-control { --down-opacity: 1; --down-filter: 0.6; --down-background: var(--uc-secondary); +} +lr-editor-button-control > button, +lr-editor-crop-button-control > button, +lr-editor-filter-control > button, +lr-editor-operation-control > button { + all: unset; position: relative; display: grid; grid-template-columns: var(--l-base-min-width) auto; @@ -393,13 +399,14 @@ lr-editor-filter-control.not_active { --idle-color-rgb: var(--uc-secondary-foreground); } -lr-editor-button-control > .before, -lr-editor-operation-control > .before, -lr-editor-crop-button-control > .before, -lr-editor-filter-control > .before { +lr-editor-button-control > button::before, +lr-editor-operation-control > button::before, +lr-editor-crop-button-control > button::before, +lr-editor-filter-control > button::before { position: absolute; - right: 0px; - left: 0px; + content: ''; + right: 0; + left: 0; z-index: -1; width: 100%; height: 100%; @@ -408,21 +415,21 @@ lr-editor-filter-control > .before { transition: var(--transition-duration-3); } -lr-editor-button-control > .title, -lr-editor-operation-control > .title, -lr-editor-crop-button-control > .title, -lr-editor-filter-control > .title { +lr-editor-button-control > button .title, +lr-editor-operation-control > button .title, +lr-editor-crop-button-control > button .title, +lr-editor-filter-control > button .title { padding-right: var(--cldtr-gap-mid-1); font-size: 0.7em; letter-spacing: 1.004px; text-transform: uppercase; } -lr-editor-filter-control > .preview { +lr-editor-filter-control > button .preview { position: absolute; - right: 0px; - left: 0px; - z-index: 1; + right: 0; + left: 0; + z-index: -1; width: 100%; height: var(--l-base-height); background-repeat: no-repeat; @@ -780,7 +787,7 @@ lr-editor-toolbar .slider { padding-left: var(--l-slider-padding); } -lr-btn-ui { +lr-btn-ui > button { --filter-effect: var(--idle-brightness); --opacity-effect: var(--idle-opacity); --color-effect: var(--idle-color-rgb); @@ -792,6 +799,7 @@ lr-btn-ui { filter var(--transition-duration-2) ); + all: unset; display: inline-flex; align-items: center; box-sizing: var(--css-box-sizing, border-box); @@ -843,7 +851,7 @@ lr-btn-ui .icon_hidden { margin: 0; } -lr-btn-ui.primary { +lr-btn-ui.primary > button { --idle-color-rgb: var(--uc-primary-foreground); --idle-brightness: 1; --idle-opacity: 1; @@ -862,7 +870,7 @@ lr-btn-ui.primary { --active-background: var(--uc-primary); } -lr-btn-ui.primary-icon { +lr-btn-ui.primary-icon > button { --idle-color-rgb: var(--uc-primary); --idle-brightness: 1; --idle-opacity: 1; @@ -884,7 +892,7 @@ lr-btn-ui.primary-icon { width: var(--uc-button-size); } -lr-btn-ui.secondary { +lr-btn-ui.secondary > button { --idle-color-rgb: var(--uc-secondary-foreground); --idle-brightness: 1; --idle-opacity: 1; @@ -903,7 +911,7 @@ lr-btn-ui.secondary { --active-background: transparent; } -lr-btn-ui.secondary-icon { +lr-btn-ui.secondary-icon > button { --idle-color-rgb: var(--uc-secondary-foreground); --idle-brightness: 1; --idle-opacity: 1; @@ -925,7 +933,7 @@ lr-btn-ui.secondary-icon { width: var(--uc-button-size); } -lr-btn-ui.tab { +lr-btn-ui.tab > button { --idle-color-rgb: var(--uc-secondary-foreground); --idle-brightness: 1; --idle-opacity: 1; @@ -947,7 +955,7 @@ lr-btn-ui.tab { width: var(--uc-button-size); } -lr-btn-ui.default { +lr-btn-ui.default > button { --idle-color-rgb: var(--uc-secondary-foreground); --idle-brightness: 1; --idle-opacity: 1; @@ -1091,3 +1099,8 @@ lr-presence-toggle.initial { display: none !important; transition: none !important; } + +[lr-cloud-image-editor] [role='button']:focus-visible, +[lr-cloud-image-editor] button:focus-visible { + outline: 1px auto -webkit-focus-ring-color; +} diff --git a/blocks/CloudImageEditor/src/elements/button/LrBtnUi.js b/blocks/CloudImageEditor/src/elements/button/LrBtnUi.js index 993954036..971036316 100644 --- a/blocks/CloudImageEditor/src/elements/button/LrBtnUi.js +++ b/blocks/CloudImageEditor/src/elements/button/LrBtnUi.js @@ -15,6 +15,8 @@ export class LrBtnUi extends Block { icon: '', iconCss: this._iconCss(), theme: null, + 'aria-role': '', + 'aria-controls': '', }; this.defineAccessor('active', (active) => { @@ -54,13 +56,17 @@ export class LrBtnUi extends Block { this._iconSingle = false; }); - this.setAttribute('role', 'button'); - if (this.tabIndex === -1) { - this.tabIndex = 0; - } if (!this.hasAttribute('theme')) { this.setAttribute('theme', 'default'); } + + this.defineAccessor('aria-role', (value) => { + this.$['aria-role'] = value || ''; + }); + + this.defineAccessor('aria-controls', (value) => { + this.$['aria-controls'] = value || ''; + }); } set reverse(val) { @@ -73,9 +79,12 @@ export class LrBtnUi extends Block { } } } + LrBtnUi.bindAttributes({ text: 'text', icon: 'icon', reverse: 'reverse', theme: 'theme' }); LrBtnUi.template = /* HTML */ ` - -
{{text}}
+ `; diff --git a/blocks/CloudImageEditor/src/elements/slider/SliderUi.js b/blocks/CloudImageEditor/src/elements/slider/SliderUi.js index bab0180bc..daff4c273 100644 --- a/blocks/CloudImageEditor/src/elements/slider/SliderUi.js +++ b/blocks/CloudImageEditor/src/elements/slider/SliderUi.js @@ -173,7 +173,6 @@ SliderUi.template = /* HTML */ ` class="input" type="range" ref="input-el" - tabindex="0" set="oninput: on.sliderInput; onchange: on.sliderChange; @min: min; @max: max; @value: defaultValue;" /> `; diff --git a/blocks/Copyright/copyright.css b/blocks/Copyright/copyright.css index 037e291d6..713c10171 100644 --- a/blocks/Copyright/copyright.css +++ b/blocks/Copyright/copyright.css @@ -21,6 +21,10 @@ lr-copyright .credits { background-color var(--uc-transition); } +lr-copyright .credits:focus-visible { + outline: 1px auto -webkit-focus-ring-color; +} + lr-copyright .credits:hover { opacity: 1; background-color: var(--uc-muted); diff --git a/blocks/DropArea/DropArea.js b/blocks/DropArea/DropArea.js index a48620ddc..4106d125c 100644 --- a/blocks/DropArea/DropArea.js +++ b/blocks/DropArea/DropArea.js @@ -165,10 +165,22 @@ export class DropArea extends UploaderBlock { }); if (this.$.isClickable) { - // @private - this._onAreaClicked = () => { - this.openSystemDialog(); + /** + * @private + * @param {KeyboardEvent | Event} event + */ + this._onAreaClicked = (event) => { + if (event.type === 'keydown') { + // @ts-ignore + if (event.code === 'Space' || event.code === 'Enter') { + this.openSystemDialog(); + } + } else if (event.type === 'click') { + this.openSystemDialog(); + } }; + + this.addEventListener('keydown', this._onAreaClicked); this.addEventListener('click', this._onAreaClicked); } } @@ -228,6 +240,7 @@ export class DropArea extends UploaderBlock { this._destroyDropzone?.(); this._destroyContentWrapperDropzone?.(); if (this._onAreaClicked) { + this.removeEventListener('keydown', this._onAreaClicked); this.removeEventListener('click', this._onAreaClicked); } } diff --git a/blocks/FileItem/FileItem.js b/blocks/FileItem/FileItem.js index ea143310b..9ff9f7a81 100644 --- a/blocks/FileItem/FileItem.js +++ b/blocks/FileItem/FileItem.js @@ -434,10 +434,15 @@ FileItem.template = /* HTML */ ` {{errorText}}
- - -`; +Select.template = /* HTML */ ` `; diff --git a/blocks/Select/select.css b/blocks/Select/select.css index 7cc0a5c91..b4558f913 100644 --- a/blocks/Select/select.css +++ b/blocks/Select/select.css @@ -2,19 +2,21 @@ lr-select { display: inline-flex; } -lr-select > button { +lr-select select { position: relative; display: inline-flex; align-items: center; - padding-right: 0 !important; /* todo: get rid of important */ + justify-content: center; + height: var(--uc-button-size); + padding: 0 14px; + font-size: 1em; + font-family: inherit; + white-space: nowrap; + border: none; + border-radius: var(--uc-radius); + cursor: pointer; + user-select: none; + transition: background-color var(--uc-transition); color: var(--uc-secondary-foreground); background-color: var(--uc-secondary); } - -lr-select > button > select { - position: absolute; - display: block; - width: 100%; - height: 100%; - opacity: 0; -} diff --git a/blocks/SourceBtn/SourceBtn.js b/blocks/SourceBtn/SourceBtn.js index 3eeeddddb..c0c514cee 100644 --- a/blocks/SourceBtn/SourceBtn.js +++ b/blocks/SourceBtn/SourceBtn.js @@ -80,7 +80,6 @@ export class SourceBtn extends UploaderBlock { super.initCallback(); this.initTypes(); - this.setAttribute('role', 'button'); this.defineAccessor( 'type', /** @param {string} val */ @@ -133,9 +132,12 @@ export class SourceBtn extends UploaderBlock { }; } } + SourceBtn.template = /* HTML */ ` - -
+ `; SourceBtn.bindAttributes({ // @ts-expect-error symbiote types bug diff --git a/blocks/SourceBtn/source-btn.css b/blocks/SourceBtn/source-btn.css index 0d2fbf401..d208b300d 100644 --- a/blocks/SourceBtn/source-btn.css +++ b/blocks/SourceBtn/source-btn.css @@ -1,4 +1,4 @@ -lr-source-btn { +lr-source-btn > button { display: flex; align-items: center; margin-bottom: 2px; @@ -10,13 +10,16 @@ lr-source-btn { background-color var(--uc-transition), color var(--uc-transition); user-select: none; + width: 100%; + background-color: unset; + height: unset; } -lr-source-btn:last-child { +lr-source-btn:last-child > button { margin-bottom: 0; } -lr-source-btn:hover { +lr-source-btn > button:hover { color: var(--uc-primary); background-color: var(--uc-primary-transparent); } diff --git a/demo/cloud-image-editor.html b/demo/cloud-image-editor.html index 6e507fe7b..811ebbe9a 100644 --- a/demo/cloud-image-editor.html +++ b/demo/cloud-image-editor.html @@ -13,7 +13,8 @@ "imports": { "@symbiotejs/symbiote": "./node_modules/@symbiotejs/symbiote/build/symbiote.js", "@uploadcare/upload-client": "./node_modules/@uploadcare/upload-client/dist/esm/index.browser.mjs", - "@uploadcare/image-shrink": "./node_modules/@uploadcare/image-shrink/dist/esm/index.browser.mjs" + "@uploadcare/image-shrink": "./node_modules/@uploadcare/image-shrink/dist/esm/index.browser.mjs", + "keyux": "./node_modules/keyux/index.js" } } diff --git a/demo/raw-inline.html b/demo/raw-inline.html index 8a2f1d608..1c76c07ef 100644 --- a/demo/raw-inline.html +++ b/demo/raw-inline.html @@ -20,7 +20,8 @@ "imports": { "@symbiotejs/symbiote": "./node_modules/@symbiotejs/symbiote/build/symbiote.js", "@uploadcare/upload-client": "./node_modules/@uploadcare/upload-client/dist/esm/index.browser.mjs", - "@uploadcare/image-shrink": "./node_modules/@uploadcare/image-shrink/dist/esm/index.browser.mjs" + "@uploadcare/image-shrink": "./node_modules/@uploadcare/image-shrink/dist/esm/index.browser.mjs", + "keyux": "./node_modules/keyux/index.js" } } diff --git a/demo/raw-minimal.html b/demo/raw-minimal.html index 6b8bf75a6..0d30c6652 100644 --- a/demo/raw-minimal.html +++ b/demo/raw-minimal.html @@ -20,7 +20,8 @@ "imports": { "@symbiotejs/symbiote": "./node_modules/@symbiotejs/symbiote/build/symbiote.js", "@uploadcare/upload-client": "./node_modules/@uploadcare/upload-client/dist/esm/index.browser.mjs", - "@uploadcare/image-shrink": "./node_modules/@uploadcare/image-shrink/dist/esm/index.browser.mjs" + "@uploadcare/image-shrink": "./node_modules/@uploadcare/image-shrink/dist/esm/index.browser.mjs", + "keyux": "./node_modules/keyux/index.js" } } diff --git a/demo/raw-regular.html b/demo/raw-regular.html index 80f7c6279..cb6cc9539 100644 --- a/demo/raw-regular.html +++ b/demo/raw-regular.html @@ -20,7 +20,8 @@ "imports": { "@symbiotejs/symbiote": "./node_modules/@symbiotejs/symbiote/build/symbiote.js", "@uploadcare/upload-client": "./node_modules/@uploadcare/upload-client/dist/esm/index.browser.mjs", - "@uploadcare/image-shrink": "./node_modules/@uploadcare/image-shrink/dist/esm/index.browser.mjs" + "@uploadcare/image-shrink": "./node_modules/@uploadcare/image-shrink/dist/esm/index.browser.mjs", + "keyux": "./node_modules/keyux/index.js" } } diff --git a/locales/file-uploader/ar.js b/locales/file-uploader/ar.js index afb6b6568..c66d40c07 100644 --- a/locales/file-uploader/ar.js +++ b/locales/file-uploader/ar.js @@ -108,4 +108,6 @@ export default { 'images-only-accepted': 'يتم قبول ملفات الصور فقط.', 'file-type-not-allowed': 'رفع هذه أنواع الملفات غير مسموح به.', 'some-files-were-not-uploaded': 'لم يتم رفع بعض الملفات.', + 'file-item-edit-button': 'زر التحرير', + 'file-item-remove-button': 'زر الإزالة', }; diff --git a/locales/file-uploader/az.js b/locales/file-uploader/az.js index 85458955c..ff03049fe 100644 --- a/locales/file-uploader/az.js +++ b/locales/file-uploader/az.js @@ -100,4 +100,6 @@ export default { 'images-only-accepted': 'Yalnız şəkil faylları qəbul edilir.', 'file-type-not-allowed': 'Bu fayl növlərinin yüklənməsinə icazə verilmir.', 'some-files-were-not-uploaded': 'Bəzi fayllar yüklənmədi.', + 'file-item-edit-button': 'Düzəliş düyməsi', + 'file-item-remove-button': 'Sil düyməsi', }; diff --git a/locales/file-uploader/ca.js b/locales/file-uploader/ca.js index aabba7237..47623d000 100644 --- a/locales/file-uploader/ca.js +++ b/locales/file-uploader/ca.js @@ -103,4 +103,6 @@ export default { 'images-only-accepted': "Només s'accepten fitxers d'imatge.", 'file-type-not-allowed': "No està permès pujar fitxers d'aquest tipus.", 'some-files-were-not-uploaded': "Alguns fitxers no s'han pujat.", + 'file-item-edit-button': "Botó d'edició", + 'file-item-remove-button': 'Botó de supressió', }; diff --git a/locales/file-uploader/cs.js b/locales/file-uploader/cs.js index 3b6a9df2d..3191d4973 100644 --- a/locales/file-uploader/cs.js +++ b/locales/file-uploader/cs.js @@ -105,4 +105,6 @@ export default { 'images-only-accepted': 'Přijímány jsou pouze obrázkové soubory.', 'file-type-not-allowed': 'Nahrávání těchto typů souborů není povoleno.', 'some-files-were-not-uploaded': 'Některé soubory nebyly nahrány.', + 'file-item-edit-button': 'Tlačítko upravit', + 'file-item-remove-button': 'Tlačítko odstranit', }; diff --git a/locales/file-uploader/da.js b/locales/file-uploader/da.js index b751b580d..bacde66df 100644 --- a/locales/file-uploader/da.js +++ b/locales/file-uploader/da.js @@ -101,4 +101,6 @@ export default { 'images-only-accepted': 'Kun billedfiler accepteres.', 'file-type-not-allowed': 'Upload af denne filtype er ikke tilladt.', 'some-files-were-not-uploaded': 'Nogle filer blev ikke uploadet.', + 'file-item-edit-button': 'Redigeringsknap', + 'file-item-remove-button': 'Fjernknap', }; diff --git a/locales/file-uploader/de.js b/locales/file-uploader/de.js index b8a18b6c0..ae74b0a43 100644 --- a/locales/file-uploader/de.js +++ b/locales/file-uploader/de.js @@ -102,4 +102,6 @@ export default { 'images-only-accepted': 'Nur Bilddateien werden akzeptiert.', 'file-type-not-allowed': 'Das Hochladen dieser Dateitypen ist nicht erlaubt.', 'some-files-were-not-uploaded': 'Einige Dateien wurden nicht hochgeladen.', + 'file-item-edit-button': 'Bearbeiten-Taste', + 'file-item-remove-button': 'Entfernen-Taste', }; diff --git a/locales/file-uploader/el.js b/locales/file-uploader/el.js index ad4bb0f27..1efa060b8 100644 --- a/locales/file-uploader/el.js +++ b/locales/file-uploader/el.js @@ -102,4 +102,6 @@ export default { 'images-only-accepted': 'Επιτρέπονται μόνο αρχεία εικόνων.', 'file-type-not-allowed': 'Η μεταφόρτωση αυτών των τύπων αρχείων δεν επιτρέπεται.', 'some-files-were-not-uploaded': 'Κάποια αρχεία δεν μεταφορτώθηκαν.', + 'file-item-edit-button': 'Κουμπί επεξεργασίας', + 'file-item-remove-button': 'Κουμπί αφαίρεσης', }; diff --git a/locales/file-uploader/en.js b/locales/file-uploader/en.js index 34b12595e..a3ea59e09 100644 --- a/locales/file-uploader/en.js +++ b/locales/file-uploader/en.js @@ -100,4 +100,6 @@ export default { 'images-only-accepted': 'Only image files are accepted.', 'file-type-not-allowed': 'Uploading of these file types is not allowed.', 'some-files-were-not-uploaded': 'Some files were not uploaded.', + 'file-item-edit-button': 'Edit button', + 'file-item-remove-button': 'Remove button', }; diff --git a/locales/file-uploader/es.js b/locales/file-uploader/es.js index 97710287e..1165729f4 100644 --- a/locales/file-uploader/es.js +++ b/locales/file-uploader/es.js @@ -103,4 +103,6 @@ export default { 'images-only-accepted': 'Solo se aceptan archivos de imagen.', 'file-type-not-allowed': 'No se permite la subida de estos tipos de archivos.', 'some-files-were-not-uploaded': 'Algunos archivos no fueron subidos.', + 'file-item-edit-button': 'Botón de edición', + 'file-item-remove-button': 'Botón de eliminación', }; diff --git a/locales/file-uploader/et.js b/locales/file-uploader/et.js index 810c8df46..591d774f7 100644 --- a/locales/file-uploader/et.js +++ b/locales/file-uploader/et.js @@ -100,4 +100,6 @@ export default { 'images-only-accepted': 'Aktsepteeritud on ainult pildifailid.', 'file-type-not-allowed': 'Nende failitüüpide üleslaadimine pole lubatud.', 'some-files-were-not-uploaded': 'Mõned failid ei laetud üles.', + 'file-item-edit-button': 'Muuda nupp', + 'file-item-remove-button': 'Eemalda nupp', }; diff --git a/locales/file-uploader/fr.js b/locales/file-uploader/fr.js index b7962eaf6..b25224a41 100644 --- a/locales/file-uploader/fr.js +++ b/locales/file-uploader/fr.js @@ -103,4 +103,6 @@ export default { 'images-only-accepted': 'Seuls les fichiers image sont acceptés.', 'file-type-not-allowed': "Le téléchargement de ces types de fichiers n'est pas autorisé.", 'some-files-were-not-uploaded': "Certains fichiers n'ont pas été téléchargés.", + 'file-item-edit-button': "Bouton d'édition", + 'file-item-remove-button': 'Bouton de suppression', }; diff --git a/locales/file-uploader/he.js b/locales/file-uploader/he.js index 472b5db82..cd0cd44a3 100644 --- a/locales/file-uploader/he.js +++ b/locales/file-uploader/he.js @@ -102,4 +102,6 @@ export default { 'images-only-accepted': 'מתקבלים רק קבצי תמונות.', 'file-type-not-allowed': 'העלאת סוגי קבצים אלו אינה מורשית.', 'some-files-were-not-uploaded': 'חלק מהקבצים לא הועלו.', + 'file-item-edit-button': 'כפתור עריכה', + 'file-item-remove-button': 'כפתור הסרה', }; diff --git a/locales/file-uploader/hy.js b/locales/file-uploader/hy.js index 65cc725a8..a67b827e5 100644 --- a/locales/file-uploader/hy.js +++ b/locales/file-uploader/hy.js @@ -100,4 +100,6 @@ export default { 'images-only-accepted': 'Միայն պատկերային ֆայլերը ընդունվում են։', 'file-type-not-allowed': 'Այս ֆայլի տեսակների բեռնումը թույլատրված չէ։', 'some-files-were-not-uploaded': 'Որոշ ֆայլեր բեռնված չեն։', + 'file-item-edit-button': 'Խմբագրել կոճակը', + 'file-item-remove-button': 'Հեռացման կոճակը', }; diff --git a/locales/file-uploader/is.js b/locales/file-uploader/is.js index 5bbd9e842..bfa5080d3 100644 --- a/locales/file-uploader/is.js +++ b/locales/file-uploader/is.js @@ -100,4 +100,6 @@ export default { 'images-only-accepted': 'Aðeins myndaskrár eru samþykktar.', 'file-type-not-allowed': 'Ekki er leyft að hlaða upp þessum skráartegundum.', 'some-files-were-not-uploaded': 'Sumar skrár voru ekki hlaðnar upp.', + 'file-item-edit-button': 'Breyta hnappur', + 'file-item-remove-button': 'Fjarlægja hnappur', }; diff --git a/locales/file-uploader/it.js b/locales/file-uploader/it.js index bc2dfca02..5077ef43b 100644 --- a/locales/file-uploader/it.js +++ b/locales/file-uploader/it.js @@ -103,4 +103,6 @@ export default { 'images-only-accepted': 'Sono accettati solo file immagine.', 'file-type-not-allowed': 'Il caricamento di questo tipo di file non è permesso.', 'some-files-were-not-uploaded': 'Alcuni file non sono stati caricati.', + 'file-item-edit-button': 'Pulsante di modifica', + 'file-item-remove-button': 'Pulsante di rimozione', }; diff --git a/locales/file-uploader/ja.js b/locales/file-uploader/ja.js index a37be2096..497a1c3b1 100644 --- a/locales/file-uploader/ja.js +++ b/locales/file-uploader/ja.js @@ -100,4 +100,6 @@ export default { 'images-only-accepted': '画像ファイルのみ受け付けます。', 'file-type-not-allowed': 'このファイルタイプのアップロードは許可されていません。', 'some-files-were-not-uploaded': '一部のファイルはアップロードされませんでした。', + 'file-item-edit-button': '編集ボタン', + 'file-item-remove-button': '削除ボタン', }; diff --git a/locales/file-uploader/ka.js b/locales/file-uploader/ka.js index d0ea2e4b0..5d5e64b20 100644 --- a/locales/file-uploader/ka.js +++ b/locales/file-uploader/ka.js @@ -100,4 +100,6 @@ export default { 'images-only-accepted': 'მხოლოდ სურათის ფაილები იღება.', 'file-type-not-allowed': 'ამ ფაილის ტიპების ატვირთვა არ არის დასაშვები.', 'some-files-were-not-uploaded': 'ზოგიერთი ფაილი არ ატვირთულა.', + 'file-item-edit-button': 'რედაქტირების ღილაკი', + 'file-item-remove-button': 'წაშლის ღილაკი', }; diff --git a/locales/file-uploader/kk.js b/locales/file-uploader/kk.js index d46b9d54e..c73b45f6a 100644 --- a/locales/file-uploader/kk.js +++ b/locales/file-uploader/kk.js @@ -100,4 +100,6 @@ export default { 'images-only-accepted': 'Тек сурет файлдары қабылданады.', 'file-type-not-allowed': 'Осы файл түрлерін жүктеуге рұқсат етілмейді.', 'some-files-were-not-uploaded': 'Кейбір файлдар жүктелген жоқ.', + 'file-item-edit-button': 'Өңдеу түймесі', + 'file-item-remove-button': 'Жою түймесі', }; diff --git a/locales/file-uploader/ko.js b/locales/file-uploader/ko.js index 6a32ddb83..f4ad486ae 100644 --- a/locales/file-uploader/ko.js +++ b/locales/file-uploader/ko.js @@ -99,4 +99,6 @@ export default { 'images-only-accepted': '이미지 파일만 허용됩니다.', 'file-type-not-allowed': '이 파일 유형의 업로드가 허용되지 않습니다.', 'some-files-were-not-uploaded': '일부 파일이 업로드되지 않았습니다.', + 'file-item-edit-button': '편집 버튼', + 'file-item-remove-button': '제거 버튼', }; diff --git a/locales/file-uploader/lv.js b/locales/file-uploader/lv.js index 466540a2d..55aa64ccb 100644 --- a/locales/file-uploader/lv.js +++ b/locales/file-uploader/lv.js @@ -102,4 +102,6 @@ export default { 'images-only-accepted': 'Tiek pieņemti tikai attēlu faili.', 'file-type-not-allowed': 'Šāda tipa failu augšupielāde nav atļauta.', 'some-files-were-not-uploaded': 'Daži faili netika augšupielādēti.', + 'file-item-edit-button': 'Rediģēšanas poga', + 'file-item-remove-button': 'Noņemšanas poga', }; diff --git a/locales/file-uploader/nb.js b/locales/file-uploader/nb.js index 1b5e0f454..07c621f2b 100644 --- a/locales/file-uploader/nb.js +++ b/locales/file-uploader/nb.js @@ -32,9 +32,9 @@ export default { 'no-files': 'Ingen filer valgt', browse: 'Bla gjennom', 'not-uploaded-yet': 'Ikke lastet opp ennå...', - file__one: 'fil', // Norwegian Bokmål uses 'one' for singular - file__other: 'filer', // and 'other' for plural - error__one: 'feil', // Similarly for errors + file__one: 'fil', + file__other: 'filer', + error__one: 'feil', error__other: 'feil', 'header-uploading': 'Laster opp {{count}} {{plural:file(count)}}', 'header-failed': '{{count}} {{plural:error(count)}}', @@ -100,4 +100,6 @@ export default { 'images-only-accepted': 'Kun bildefiler er akseptert.', 'file-type-not-allowed': 'Opplasting av disse filtypene er ikke tillatt.', 'some-files-were-not-uploaded': 'Noen filer ble ikke lastet opp.', + 'file-item-edit-button': 'Redigeringsknapp', + 'file-item-remove-button': 'Fjerningsknapp', }; diff --git a/locales/file-uploader/nl.js b/locales/file-uploader/nl.js index 19274e316..8762c6bab 100644 --- a/locales/file-uploader/nl.js +++ b/locales/file-uploader/nl.js @@ -101,4 +101,6 @@ export default { 'images-only-accepted': 'Alleen afbeeldingsbestanden worden geaccepteerd.', 'file-type-not-allowed': 'Dit bestandstype is niet toegestaan.', 'some-files-were-not-uploaded': 'Sommige bestanden zijn niet geüpload.', + 'file-item-edit-button': 'Bewerkknop', + 'file-item-remove-button': 'Verwijderknop', }; diff --git a/locales/file-uploader/pl.js b/locales/file-uploader/pl.js index ac7d5c7d0..82a82ca70 100644 --- a/locales/file-uploader/pl.js +++ b/locales/file-uploader/pl.js @@ -104,4 +104,6 @@ export default { 'images-only-accepted': 'Akceptowane są tylko pliki obrazów.', 'file-type-not-allowed': 'Przesyłanie tego typu plików jest niedozwolone.', 'some-files-were-not-uploaded': 'Niektóre pliki nie zostały przesłane.', + 'file-item-edit-button': 'Przycisk edycji', + 'file-item-remove-button': 'Przycisk usuwania', }; diff --git a/locales/file-uploader/pt.js b/locales/file-uploader/pt.js index bb7599d92..98f3b1c28 100644 --- a/locales/file-uploader/pt.js +++ b/locales/file-uploader/pt.js @@ -103,4 +103,6 @@ export default { 'images-only-accepted': 'Apenas arquivos de imagem são aceitos.', 'file-type-not-allowed': 'O upload desses tipos de arquivo não é permitido.', 'some-files-were-not-uploaded': 'Alguns arquivos não foram carregados.', + 'file-item-edit-button': 'Botão de edição', + 'file-item-remove-button': 'Botão de remoção', }; diff --git a/locales/file-uploader/ro.js b/locales/file-uploader/ro.js index f6394c727..e96fe363f 100644 --- a/locales/file-uploader/ro.js +++ b/locales/file-uploader/ro.js @@ -103,4 +103,6 @@ export default { 'images-only-accepted': 'Doar fișierele de tip imagine sunt acceptate.', 'file-type-not-allowed': 'Încărcarea acestor tipuri de fișiere nu este permisă.', 'some-files-were-not-uploaded': 'Unele fișiere nu au fost încărcate.', + 'file-item-edit-button': 'Buton de editare', + 'file-item-remove-button': 'Buton de ștergere', }; diff --git a/locales/file-uploader/ru.js b/locales/file-uploader/ru.js index 15c90ae40..7b8990660 100644 --- a/locales/file-uploader/ru.js +++ b/locales/file-uploader/ru.js @@ -104,4 +104,6 @@ export default { 'images-only-accepted': 'Принимаются только изображения.', 'file-type-not-allowed': 'Загрузка файлов этого типа не разрешена.', 'some-files-were-not-uploaded': 'Некоторые файлы не были загружены.', + 'file-item-edit-button': 'Кнопка редактирования', + 'file-item-remove-button': 'Кнопка удаления', }; diff --git a/locales/file-uploader/sk.js b/locales/file-uploader/sk.js index 5a6462761..5abfb7992 100644 --- a/locales/file-uploader/sk.js +++ b/locales/file-uploader/sk.js @@ -104,4 +104,6 @@ export default { 'images-only-accepted': 'Sú akceptované len obrázkové súbory.', 'file-type-not-allowed': 'Nahrávanie tohto typu súborov nie je povolené.', 'some-files-were-not-uploaded': 'Niektoré súbory neboli nahrané.', + 'file-item-edit-button': 'Tlačidlo upraviť', + 'file-item-remove-button': 'Tlačidlo odstrániť', }; diff --git a/locales/file-uploader/sr.js b/locales/file-uploader/sr.js index 22ceace26..5be5121f1 100644 --- a/locales/file-uploader/sr.js +++ b/locales/file-uploader/sr.js @@ -102,4 +102,6 @@ export default { 'images-only-accepted': 'Прихваћене су само слике.', 'file-type-not-allowed': 'Отпремање овог типа датотека није дозвољено.', 'some-files-were-not-uploaded': 'Неке датотеке нису отпремљене.', + 'file-item-edit-button': 'Дугме за уређивање', + 'file-item-remove-button': 'Дугме за уклањање', }; diff --git a/locales/file-uploader/sv.js b/locales/file-uploader/sv.js index bc4dbf66a..7b6e30fb3 100644 --- a/locales/file-uploader/sv.js +++ b/locales/file-uploader/sv.js @@ -100,4 +100,6 @@ export default { 'images-only-accepted': 'Endast bildfiler accepteras.', 'file-type-not-allowed': 'Uppladdning av dessa filtyper är inte tillåten.', 'some-files-were-not-uploaded': 'Vissa filer laddades inte upp.', + 'file-item-edit-button': 'Redigeringsknapp', + 'file-item-remove-button': 'Raderingsknapp', }; diff --git a/locales/file-uploader/tr.js b/locales/file-uploader/tr.js index fbdd71a00..ce99c5851 100644 --- a/locales/file-uploader/tr.js +++ b/locales/file-uploader/tr.js @@ -100,4 +100,6 @@ export default { 'images-only-accepted': 'Yalnızca resim dosyaları kabul edilir.', 'file-type-not-allowed': 'Bu dosya türlerinin yüklenmesine izin verilmiyor.', 'some-files-were-not-uploaded': 'Bazı dosyalar yüklenemedi.', + 'file-item-edit-button': 'Düzenle düğmesi', + 'file-item-remove-button': 'Silme düğmesi', }; diff --git a/locales/file-uploader/uk.js b/locales/file-uploader/uk.js index a9304357c..c78087cea 100644 --- a/locales/file-uploader/uk.js +++ b/locales/file-uploader/uk.js @@ -104,4 +104,6 @@ export default { 'images-only-accepted': 'Приймаються тільки зображення.', 'file-type-not-allowed': 'Завантаження файлів цього типу заборонено.', 'some-files-were-not-uploaded': 'Деякі файли не були завантажені.', + 'file-item-edit-button': 'Кнопка редагування', + 'file-item-remove-button': 'Кнопка видалення', }; diff --git a/locales/file-uploader/vi.js b/locales/file-uploader/vi.js index 3483dad35..6186b2ccf 100644 --- a/locales/file-uploader/vi.js +++ b/locales/file-uploader/vi.js @@ -100,4 +100,6 @@ export default { 'images-only-accepted': 'Chỉ chấp nhận tệp ảnh.', 'file-type-not-allowed': 'Không cho phép tải lên loại tệp này.', 'some-files-were-not-uploaded': 'Một số tệp chưa được tải lên.', + 'file-item-edit-button': 'Nút chỉnh sửa', + 'file-item-remove-button': 'Nút xóa', }; diff --git a/locales/file-uploader/zh-TW.js b/locales/file-uploader/zh-TW.js index 1fc1bd402..acf49ac60 100644 --- a/locales/file-uploader/zh-TW.js +++ b/locales/file-uploader/zh-TW.js @@ -100,4 +100,6 @@ export default { 'images-only-accepted': '僅接受圖片檔案。', 'file-type-not-allowed': '不允許上傳這些檔案類型。', 'some-files-were-not-uploaded': '部分檔案未上傳。', + 'file-item-edit-button': '編輯按鈕', + 'file-item-remove-button': '刪除按鈕', }; diff --git a/locales/file-uploader/zh.js b/locales/file-uploader/zh.js index d8ddb1a74..2199e6d3b 100644 --- a/locales/file-uploader/zh.js +++ b/locales/file-uploader/zh.js @@ -100,4 +100,6 @@ export default { 'images-only-accepted': '只接受图像文件。', 'file-type-not-allowed': '不允许上传这些文件类型。', 'some-files-were-not-uploaded': '有些文件未上传。', + 'file-item-edit-button': '編輯按鈕', + 'file-item-remove-button': '刪除按鈕', }; diff --git a/package-lock.json b/package-lock.json index c2f30ecd2..86d8cc08c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,8 @@ "dependencies": { "@symbiotejs/symbiote": "^1.11.7", "@uploadcare/image-shrink": "^6.14.1", - "@uploadcare/upload-client": "^6.14.1" + "@uploadcare/upload-client": "^6.14.1", + "keyux": "^0.7.1" }, "devDependencies": { "@babel/eslint-parser": "^7.23.3", @@ -8728,6 +8729,20 @@ "node": ">= 0.6" } }, + "node_modules/keyux": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/keyux/-/keyux-0.7.1.tgz", + "integrity": "sha512-4i0awjBLTiSMNyQluM5+yOQT1zjYe+iXA9lqb3fzBjAO9GxnVfscjaUHbBh8kvq9LmMnSD77K7MmLQJPt2n//w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, "node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -20891,6 +20906,11 @@ "tsscmp": "1.0.6" } }, + "keyux": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/keyux/-/keyux-0.7.1.tgz", + "integrity": "sha512-4i0awjBLTiSMNyQluM5+yOQT1zjYe+iXA9lqb3fzBjAO9GxnVfscjaUHbBh8kvq9LmMnSD77K7MmLQJPt2n//w==" + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", diff --git a/package.json b/package.json index 8d10b56ff..36ca50cdf 100644 --- a/package.json +++ b/package.json @@ -136,6 +136,7 @@ "dependencies": { "@symbiotejs/symbiote": "^1.11.7", "@uploadcare/image-shrink": "^6.14.1", - "@uploadcare/upload-client": "^6.14.1" + "@uploadcare/upload-client": "^6.14.1", + "keyux": "^0.7.1" } } diff --git a/solutions/cloud-image-editor/CloudImageEditor.js b/solutions/cloud-image-editor/CloudImageEditor.js index 101debf02..2d155f19e 100644 --- a/solutions/cloud-image-editor/CloudImageEditor.js +++ b/solutions/cloud-image-editor/CloudImageEditor.js @@ -3,4 +3,10 @@ import { CloudImageEditorBlock } from '../../blocks/CloudImageEditor/src/CloudIm export class CloudImageEditor extends CloudImageEditorBlock { static styleAttrs = [...super.styleAttrs, 'lr-wgt-common']; + + initCallback() { + super.initCallback(); + + this.a11y?.registerBlock(this); + } } diff --git a/solutions/file-uploader/minimal/FileUploaderMinimal.js b/solutions/file-uploader/minimal/FileUploaderMinimal.js index f32a39cf8..1402a1186 100644 --- a/solutions/file-uploader/minimal/FileUploaderMinimal.js +++ b/solutions/file-uploader/minimal/FileUploaderMinimal.js @@ -39,7 +39,7 @@ export class FileUploaderMinimal extends SolutionBlock { FileUploaderMinimal.template = /* HTML */ ` - +