From e619596dfa0926737df2a9177da2a8b4a6ceeee1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 23 Jan 2024 07:32:20 +0100 Subject: [PATCH] :bug: fix(bal-file-upload): File upload component sets focus on different component on click (#1299) * Create PR for #1283 * fix(field): links A11y information only for direct controls, labels and messages * chore: remove console.log --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Gery Hirschfeld --- .changeset/swift-roses-guess.md | 5 ++ .../src/components/bal-field/bal-field.tsx | 73 ++++++++++++++----- .../bal-field/test/bal-field.nested.html | 30 ++++++++ .../src/utils/mutation/mutation.interfaces.ts | 1 + .../src/utils/mutation/mutation.listener.ts | 9 ++- 5 files changed, 98 insertions(+), 20 deletions(-) create mode 100644 .changeset/swift-roses-guess.md create mode 100644 packages/components/src/components/bal-field/test/bal-field.nested.html diff --git a/.changeset/swift-roses-guess.md b/.changeset/swift-roses-guess.md new file mode 100644 index 0000000000..abc583851f --- /dev/null +++ b/.changeset/swift-roses-guess.md @@ -0,0 +1,5 @@ +--- +'@baloise/design-system-components': patch +--- + +Field component links A11y information only for direct controls, labels and messages. diff --git a/packages/components/src/components/bal-field/bal-field.tsx b/packages/components/src/components/bal-field/bal-field.tsx index b71f83a5f7..cf9b7a191c 100644 --- a/packages/components/src/components/bal-field/bal-field.tsx +++ b/packages/components/src/components/bal-field/bal-field.tsx @@ -113,28 +113,63 @@ export class Field implements ComponentInterface, BalMutationObserver { await this.syncAriaAttributes() } + private isDirectChild = (el: HTMLElement): boolean => { + if (!el) { + return false + } + + const parent = el.parentElement + if (!parent) { + return false + } + if (parent.nodeName.toLowerCase() === 'bal-field' && parent !== this.el) { + return false + } + if (parent === this.el) { + return true + } + return this.isDirectChild(parent) + } + + private findDirectChild = (selectors: string): BalAriaFormLinking | undefined => { + const element = this.el.querySelector(selectors) + const isDirectChild = this.isDirectChild(element) + if (isDirectChild) { + return element + } + return undefined + } + + private findDirectChildren = (selectors: string[]): BalAriaFormLinking[] => { + return selectors + .map(selector => { + return Array.from(this.el.querySelectorAll(selector)).filter(this.isDirectChild) + }) + .flat() + } + async syncAriaAttributes(): Promise { await deepReady(this.el) await waitAfterFramePaint() - const label: BalAriaFormLinking = this.el.querySelector('bal-field-label bal-label') - const message: BalAriaFormLinking = this.el.querySelector('bal-field-message') - const controls: BalAriaFormLinking[] = [ - ...Array.from(this.el.querySelectorAll('bal-field-control bal-input')), - ...Array.from(this.el.querySelectorAll('bal-field-control bal-select')), - ...Array.from(this.el.querySelectorAll('bal-field-control bal-input-date')), - ...Array.from(this.el.querySelectorAll('bal-field-control bal-checkbox')), - ...Array.from(this.el.querySelectorAll('bal-field-control bal-radio')), - ...Array.from(this.el.querySelectorAll('bal-field-control bal-checkbox-group')), - ...Array.from(this.el.querySelectorAll('bal-field-control bal-radio-group')), - ...Array.from(this.el.querySelectorAll('bal-field-control bal-number-input')), - ...Array.from(this.el.querySelectorAll('bal-field-control bal-time-input')), - ...Array.from(this.el.querySelectorAll('bal-field-control bal-datepicker')), - ...Array.from(this.el.querySelectorAll('bal-field-control bal-input-slider')), - ...Array.from(this.el.querySelectorAll('bal-field-control bal-input-stepper')), - ...Array.from(this.el.querySelectorAll('bal-field-control bal-textarea')), - ...Array.from(this.el.querySelectorAll('bal-field-control bal-file-upload')), - ] + const label = this.findDirectChild('bal-field-label bal-label') + const message = this.findDirectChild('bal-field-message') + const controls = this.findDirectChildren([ + 'bal-field-control bal-input', + 'bal-field-control bal-select', + 'bal-field-control bal-input-date', + 'bal-field-control bal-checkbox', + 'bal-field-control bal-radio', + 'bal-field-control bal-checkbox-group', + 'bal-field-control bal-radio-group', + 'bal-field-control bal-number-input', + 'bal-field-control bal-time-input', + 'bal-field-control bal-datepicker', + 'bal-field-control bal-input-slider', + 'bal-field-control bal-input-stepper', + 'bal-field-control bal-textarea', + 'bal-field-control bal-file-upload', + ]) const ariaForm = defaultBalAriaForm @@ -164,7 +199,7 @@ export class Field implements ComponentInterface, BalMutationObserver { mutationObserverActive = true - @ListenToMutation({ subtree: false }) + @ListenToMutation({ subtree: false, waitAfterFramePrint: true }) mutationListener(): void { this.triggerAllHandlers() this.syncAriaAttributes() diff --git a/packages/components/src/components/bal-field/test/bal-field.nested.html b/packages/components/src/components/bal-field/test/bal-field.nested.html new file mode 100644 index 0000000000..053f2bb4c1 --- /dev/null +++ b/packages/components/src/components/bal-field/test/bal-field.nested.html @@ -0,0 +1,30 @@ + + + + + + + + + + + + +
+ + + One + + + +
+ + Two + +
+
+
+
+
+ + diff --git a/packages/components/src/utils/mutation/mutation.interfaces.ts b/packages/components/src/utils/mutation/mutation.interfaces.ts index 69e81b68d2..57d3db18b5 100644 --- a/packages/components/src/utils/mutation/mutation.interfaces.ts +++ b/packages/components/src/utils/mutation/mutation.interfaces.ts @@ -1,6 +1,7 @@ export interface MutationObserverOptions extends MutationObserverInit { tags: string[] closest?: string + waitAfterFramePrint?: boolean } export interface BalMutationObserver { diff --git a/packages/components/src/utils/mutation/mutation.listener.ts b/packages/components/src/utils/mutation/mutation.listener.ts index 83a7347c4c..fbd6b8c0ac 100644 --- a/packages/components/src/utils/mutation/mutation.listener.ts +++ b/packages/components/src/utils/mutation/mutation.listener.ts @@ -1,8 +1,10 @@ +import { deepReady, waitAfterFramePaint } from '../helpers' import { ListenerAbstract } from '../types/listener' import { MutationObserverOptions } from './mutation.interfaces' export class BalMutationListener extends ListenerAbstract { private tags: string[] = [] + private waitAfterFramePrint = false private mutationObserver: MutationObserver | undefined = undefined private mutationObserverInit: MutationObserverInit = { childList: true, @@ -13,6 +15,7 @@ export class BalMutationListener extends ListenerAbstract { constructor(options: Partial) { super() + this.waitAfterFramePrint = options.waitAfterFramePrint || this.waitAfterFramePrint this.tags = (options.tags || []).map(t => t.toUpperCase()) this.mutationObserverInit = { childList: options.childList === false ? false : true, @@ -22,11 +25,15 @@ export class BalMutationListener extends ListenerAbstract { } } - connect(el: HTMLElement): void { + async connect(el: HTMLElement) { super.connect(el) if (typeof MutationObserver === 'undefined') { return } + if (this.waitAfterFramePrint) { + await deepReady(el) + await waitAfterFramePaint() + } this.destroyMutationObserver() this.mutationObserver = new MutationObserver(this.mutationCallback) this.mutationObserver.observe(el, this.mutationObserverInit)