diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 701947ed3..000000000 --- a/.eslintignore +++ /dev/null @@ -1,22 +0,0 @@ -# unconventional js -/blueprints/*/files/ -/vendor/ - -# compiled output -/dist/ -/tmp/ - -# dependencies -/bower_components/ -/node_modules/ - -# misc -/coverage/ -!.* -.*/ -.eslintcache - -# ember-try -/.node_modules.ember-try/ -/bower.json.ember-try -/package.json.ember-try diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index d671e11ca..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,52 +0,0 @@ -'use strict'; - -module.exports = { - root: true, - parser: '@typescript-eslint/parser', - parserOptions: { - ecmaVersion: 2018, - sourceType: 'module', - ecmaFeatures: { - legacyDecorators: true - } - }, - plugins: ['ember'], - extends: ['eslint:recommended', 'plugin:ember/recommended', 'plugin:prettier/recommended'], - env: { - browser: true - }, - rules: { - 'no-multiple-empty-lines': [2, { max: 1 }], - - 'ember/no-jquery': 'off', - 'ember/no-classic-classes': 'off', - 'ember/no-classic-components': 'off', - 'ember/require-tagless-components': 'off', - 'ember/no-component-lifecycle-hooks': 'off' - }, - overrides: [ - // node files - { - files: [ - '.eslintrc.js', - '.template-lintrc.js', - 'ember-cli-build.js', - 'index.js', - 'testem.js', - 'blueprints/*/index.js', - 'config/**/*.js', - 'tests/dummy/config/**/*.js' - ], - excludedFiles: ['addon/**', 'addon-test-support/**', 'app/**', 'tests/dummy/app/**'], - parserOptions: { - sourceType: 'script' - }, - env: { - browser: false, - node: true - }, - plugins: ['node'], - rules: {} - } - ] -}; diff --git a/.prettierrc.js b/.prettierrc.js index 484a5b77d..a1add1313 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -1,6 +1,5 @@ 'use strict'; -// eslint-disable-next-line no-undef module.exports = { arrowParens: 'always', bracketSpacing: true, diff --git a/.template-lintrc.js b/.template-lintrc.js index af84045e9..987946a56 100644 --- a/.template-lintrc.js +++ b/.template-lintrc.js @@ -6,6 +6,15 @@ module.exports = { plugins: ['./linters/handlebars'], rules: { - 'u-template-lint/no-bare-button': 'error' + 'u-template-lint/no-bare-button': 'error', + // Disabled: in v3 this rule also flags `keydown`, which is a known false positive. The rule enforces WCAG F101, + // which only covers pointer-down events (mousedown / pointerdown) and never the keyboard. Upstream narrowed it + // accordingly and renamed it `no-pointer-down-event-binding` in v5.0.0 (PR ember-template-lint#2054), so the + // current version of the rule reports nothing here. + // Every violation in this repo is a `keydown` handler calling preventDefault, either to filter keystrokes + // (currency, number and phone inputs) or to stop the page from scrolling on arrow keys (select navigation). + // `keyup` fires once the character is inserted and the page has scrolled, so it cannot replace them. + // Drop this entry when ember-template-lint is upgraded to v5 or later: the rule no longer exists under this name. + 'no-down-event-binding': false } }; diff --git a/addon-test-support/custom-assertions/tooltip.ts b/addon-test-support/custom-assertions/tooltip.ts index 019282125..d0b7b8692 100644 --- a/addon-test-support/custom-assertions/tooltip.ts +++ b/addon-test-support/custom-assertions/tooltip.ts @@ -43,8 +43,8 @@ export interface TooltipAssertions { const assertion = (selector: string): TooltipAssertions => { return { exists: async (trigger?: string, message?: string): Promise => { - let result: boolean = true; - let actual: Element | null = null; + const result: boolean = true; + const actual: Element | null = null; await triggerEventOnElement(selector, trigger); @@ -131,12 +131,9 @@ const assertion = (selector: string): TooltipAssertions => { doesNotHaveSubtitle: async (message?: string): Promise => { await triggerEventOnElement(selector); - let result = false; - let actual = undefined; const subtitleContainer = document.querySelector('.upf-tooltip .subtitle'); - - actual = (subtitleContainer)?.innerText; - result = isEmpty(actual); + const actual = (subtitleContainer)?.innerText; + const result = isEmpty(actual); QUnit.assert.pushResult({ result, @@ -170,8 +167,8 @@ const assertion = (selector: string): TooltipAssertions => { await triggerEventOnElement(selector); const iconI = document.querySelector('.upf-tooltip .title-container i'); - let actual = iconI?.className; - let result = isEmpty(actual); + const actual = iconI?.className; + const result = isEmpty(actual); QUnit.assert.pushResult({ result, @@ -205,8 +202,8 @@ const assertion = (selector: string): TooltipAssertions => { await triggerEventOnElement(selector); const titleContainer = document.querySelector('.upf-tooltip .title-container .title'); - let actual = (titleContainer)?.dataset.htmlSafe; - let result = actual === 'true'; + const actual = (titleContainer)?.dataset.htmlSafe; + const result = actual === 'true'; QUnit.assert.pushResult({ result, @@ -220,8 +217,8 @@ const assertion = (selector: string): TooltipAssertions => { await triggerEventOnElement(selector); const titleContainer = document.querySelector('.upf-tooltip .title-container .title'); - let actual = (titleContainer)?.dataset.htmlSafe; - let result = actual === undefined; + const actual = (titleContainer)?.dataset.htmlSafe; + const result = actual === undefined; QUnit.assert.pushResult({ result, diff --git a/addon-test-support/register-assertions.ts b/addon-test-support/register-assertions.ts index 36fa94cd7..e2a6008a8 100644 --- a/addon-test-support/register-assertions.ts +++ b/addon-test-support/register-assertions.ts @@ -6,7 +6,7 @@ const ASSERTIONS = [tooltipAssertions, infiniteSelectOptionAssertions, dcnAssert export default function registerAssertions(assert: Assert) { ASSERTIONS.forEach((assertion) => { - // @ts-ignore + // @ts-expect-error custom assertions are dynamically registered on Assert assert[assertion.__name__] = assertion; }); } diff --git a/addon/components/o-s-s/anchor.ts b/addon/components/o-s-s/anchor.ts index c58b21df8..eef70770c 100644 --- a/addon/components/o-s-s/anchor.ts +++ b/addon/components/o-s-s/anchor.ts @@ -43,7 +43,7 @@ export default class OSSAnchor extends Component { const route = this.args.routePrefix ? this.args.routePrefix + '.' + this.args.link : this.args.link; try { return Boolean(this.router.urlFor(route)); - } catch (error) { + } catch { return false; } } diff --git a/addon/components/o-s-s/array-input.hbs b/addon/components/o-s-s/array-input.hbs index 0e30516fc..beba75780 100644 --- a/addon/components/o-s-s/array-input.hbs +++ b/addon/components/o-s-s/array-input.hbs @@ -6,6 +6,7 @@ boolean; onChange?: (values: string[]) => void; placeholder?: string; + ariaLabel?: string; } const DEFAULT_KEYBOARD_TRIGGERS = ['Enter']; @@ -26,7 +27,7 @@ export default class OSSArrayInput extends Component { } get computedClasses(): string { - let arr: string[] = ['array-input-container']; + const arr: string[] = ['array-input-container']; if (this.args.disabled) { arr.push('array-input-container--disabled'); @@ -44,9 +45,7 @@ export default class OSSArrayInput extends Component { } private _triggerComponentRedraw(): void { - // Since this.items is not properly tracked upon updating the values, - // re-assigning this way triggers the component redraw. - this.items = this.items; + this.items = [...this.items]; } private _addEntry() { diff --git a/addon/components/o-s-s/avatar.ts b/addon/components/o-s-s/avatar.ts index b02c93766..8b01a3936 100644 --- a/addon/components/o-s-s/avatar.ts +++ b/addon/components/o-s-s/avatar.ts @@ -71,8 +71,8 @@ export default class OSSAvatar extends Component { Object.keys(SizeDefinition).includes(size as SizeType) ); - classes.push(SizeDefinition[size as SizeType]) - return classes.join(' ') + classes.push(SizeDefinition[size as SizeType]); + return classes.join(' '); } @action diff --git a/addon/components/o-s-s/button-dropdown.hbs b/addon/components/o-s-s/button-dropdown.hbs index 742c2cd98..8d6461aeb 100644 --- a/addon/components/o-s-s/button-dropdown.hbs +++ b/addon/components/o-s-s/button-dropdown.hbs @@ -5,10 +5,16 @@ {{if this.displayDropdown 'oss-button-dropdown__trigger--active'}} fx-row fx-xalign-center" role={{unless @mainAction "button"}} + tabindex={{unless @mainAction "0"}} {{on "click" this.onDropdownClick}} ...attributes > -
+
{{#if @icon}} {{/if}} @@ -17,7 +23,12 @@ {{/if}}
{{#unless @hideArrow}} -
+
{{/unless}} @@ -25,6 +36,7 @@ {{#if this.displayDropdown}}
{ } @action - closeDropdown(e?: Event): void { + closeDropdown(event?: Event): void { event?.stopPropagation(); this.displayDropdown = false; } diff --git a/addon/components/o-s-s/button.ts b/addon/components/o-s-s/button.ts index 6cd3fc2d9..dc8481b83 100644 --- a/addon/components/o-s-s/button.ts +++ b/addon/components/o-s-s/button.ts @@ -82,7 +82,7 @@ export interface OSSButtonArgs { theme?: string; square?: boolean; countDown?: { - callback: () => {}; + callback: () => void; time?: number; step?: number; }; @@ -132,7 +132,7 @@ export default class OSSButton extends Component { } get computedClass() { - let classes = [this.args.square ? SQUARE_CLASS : BASE_CLASS, `upf-btn--${this.skin}`]; + const classes = [this.args.square ? SQUARE_CLASS : BASE_CLASS, `upf-btn--${this.skin}`]; if (this.size) { classes.push(this.args.square ? `upf-square-btn--${this.size}` : `upf-btn--${this.size}`); @@ -151,6 +151,7 @@ export default class OSSButton extends Component { } if (this.args.loading && !this.args.loadingOptions?.showLabel) { + // eslint-disable-next-line ember/no-side-effects this.DOMElement.style.width = `${this.DOMElement?.offsetWidth}px`; } else { this.DOMElement.style.removeProperty('width'); diff --git a/addon/components/o-s-s/carousel.ts b/addon/components/o-s-s/carousel.ts index 1cebdefb3..1eb4c5269 100644 --- a/addon/components/o-s-s/carousel.ts +++ b/addon/components/o-s-s/carousel.ts @@ -39,7 +39,7 @@ export default class OSSCarousel extends Component { } get showControls(): boolean { - return !!this.args.showControls ?? false; + return !!this.args.showControls; } get loop(): boolean { diff --git a/addon/components/o-s-s/checkbox.hbs b/addon/components/o-s-s/checkbox.hbs index 2d6e16b9b..8de1a0254 100644 --- a/addon/components/o-s-s/checkbox.hbs +++ b/addon/components/o-s-s/checkbox.hbs @@ -1,4 +1,9 @@ -
+
+
{{#if (has-block "prefix")}} {{yield to="prefix"}} {{/if}} diff --git a/addon/components/o-s-s/content-panel.ts b/addon/components/o-s-s/content-panel.ts index 8cb653013..bcc4e9a69 100644 --- a/addon/components/o-s-s/content-panel.ts +++ b/addon/components/o-s-s/content-panel.ts @@ -1,5 +1,7 @@ import Component from '@glimmer/component'; +// eslint-disable-next-line @typescript-eslint/no-empty-object-type interface OSSContentPanelArgs {} +// eslint-disable-next-line ember/no-empty-glimmer-component-classes export default class OSSContentPanel extends Component {} diff --git a/addon/components/o-s-s/context-menu.ts b/addon/components/o-s-s/context-menu.ts index 21798516d..b00b952cf 100644 --- a/addon/components/o-s-s/context-menu.ts +++ b/addon/components/o-s-s/context-menu.ts @@ -15,8 +15,8 @@ export type ContextMenuItem = { interface OSSContextMenuArgs extends OSSButtonArgs { items: ContextMenuItem[]; closeOnMouseLeave?: boolean; - onMenuOpened?: () => {}; - onMenuClosed?: () => {}; + onMenuOpened?: () => void; + onMenuClosed?: () => void; } export default class OSSContextMenuComponent extends Component { @@ -34,7 +34,12 @@ export default class OSSContextMenuComponent extends Component { - this.initializeDropdown(); - }); + scheduleOnce('afterRender', this, this.initializeDropdown); this.currentPanel.querySelector('.oss-scrollable-panel-content')?.addEventListener('scroll', this.onScrollbound); } diff --git a/addon/components/o-s-s/country-selector.ts b/addon/components/o-s-s/country-selector.ts index 1203bd614..dbb2fc883 100644 --- a/addon/components/o-s-s/country-selector.ts +++ b/addon/components/o-s-s/country-selector.ts @@ -28,7 +28,7 @@ interface OSSCountrySelectorArgs extends BaseDropdownArgs { export default class OSSCountrySelector extends BaseDropdown { @service declare intl: IntlService; - @tracked filteredItems: Item[] = this.args.sourceList; + @tracked filteredItems: Item[]; cleanupDrodpownAutoplacement?: () => void; handleSelectorClose = this.closeDropdown; @@ -36,6 +36,8 @@ export default class OSSCountrySelector extends BaseDropdown { - const referenceTarget = this.container.querySelector('.upf-input') as HTMLElement; - const floatingTarget = document.querySelector(`#${this.portalId}`); - - if (referenceTarget && floatingTarget) { - this.cleanupDrodpownAutoplacement = attachDropdown( - referenceTarget as HTMLElement, - floatingTarget as HTMLElement, - { placementStrategy: 'auto' } - ); - } - }); + scheduleOnce('afterRender', this, this.attachDropdownToInput); } } @@ -134,6 +125,19 @@ export default class OSSCountrySelector extends BaseDropdown
@@ -37,6 +38,7 @@ min="0" autocomplete="off" placeholder={{this.placeholder}} + aria-label={{@ariaLabel}} disabled={{this.disabled}} class="fx-1" {{on "keydown" this.onlyNumeric}} @@ -52,9 +54,9 @@
{{else if this.feedbackMessage}} - {{#unless (eq this.feedbackMessage.type "error")}} + {{#if (not-eq this.feedbackMessage.type "error")}} - {{/unless}} + {{/if}} {{this.feedbackMessage.value}} {{/if}} diff --git a/addon/components/o-s-s/currency-input.stories.js b/addon/components/o-s-s/currency-input.stories.js index 947f54f96..2a4f5fdd9 100644 --- a/addon/components/o-s-s/currency-input.stories.js +++ b/addon/components/o-s-s/currency-input.stories.js @@ -55,6 +55,16 @@ export default { }, control: { type: 'boolean' } }, + ariaLabel: { + description: 'Accessible name of the input, used when no visible label is associated with it', + table: { + type: { + summary: 'string' + }, + defaultValue: { summary: 'undefined' } + }, + control: { type: 'text' } + }, placeholder: { description: 'Placeholder for the number input when no value is passed', table: { diff --git a/addon/components/o-s-s/currency-input.ts b/addon/components/o-s-s/currency-input.ts index 4f5a7b571..e702e1306 100644 --- a/addon/components/o-s-s/currency-input.ts +++ b/addon/components/o-s-s/currency-input.ts @@ -18,6 +18,7 @@ export interface OSSCurrencyInputArgs { allowCurrencyUpdate?: boolean; onlyCurrency?: boolean; placeholder?: string; + ariaLabel?: string; errorMessage?: string; feedbackMessage?: FeedbackMessage; allowedCurrencies?: Currency[]; @@ -79,11 +80,13 @@ export default class OSSCurrencyInput extends Co @tracked currencySelectorShown: boolean = false; @tracked filteredCurrencies: Currency[] = this.currencies; - @tracked localValue: number = this.args.value; + @tracked localValue: number; constructor(owner: unknown, args: OSSCurrencyInputArgs, preventDefaultAssertions?: boolean) { super(owner, args); + this.localValue = this.args.value; + if (preventDefaultAssertions) return; if (!this.args.value && !this.args.placeholder) { diff --git a/addon/components/o-s-s/email-input.ts b/addon/components/o-s-s/email-input.ts index e15e01cdf..d37054ae4 100644 --- a/addon/components/o-s-s/email-input.ts +++ b/addon/components/o-s-s/email-input.ts @@ -19,6 +19,7 @@ interface OSSEmailInputArgs { const DEFAULT_PLACEHOLDER = 'e.g., john.doe@example.com'; export const EMAIL_REGEXP = new RegExp( + // eslint-disable-next-line no-control-regex /^(?:[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-zA-Z0-9-]*[a-zA-Z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$/ ); diff --git a/addon/components/o-s-s/form.hbs b/addon/components/o-s-s/form.hbs index 22d307157..7044315ac 100644 --- a/addon/components/o-s-s/form.hbs +++ b/addon/components/o-s-s/form.hbs @@ -1,4 +1,4 @@
{{yield to="content"}} - +
\ No newline at end of file diff --git a/addon/components/o-s-s/iconName.enum.ts b/addon/components/o-s-s/iconName.enum.ts index 471d952af..670da9f52 100644 --- a/addon/components/o-s-s/iconName.enum.ts +++ b/addon/components/o-s-s/iconName.enum.ts @@ -1,4 +1,3 @@ -/* eslint-disable no-unused-vars */ export enum IconNames { Analytics = 'fa-analytics', AngleLeft = 'fa-angle-left', diff --git a/addon/components/o-s-s/illustration.hbs b/addon/components/o-s-s/illustration.hbs index f0c7c24c4..0e89f90cb 100644 --- a/addon/components/o-s-s/illustration.hbs +++ b/addon/components/o-s-s/illustration.hbs @@ -1,5 +1,6 @@
{{#if this.preloaded}} + {{! template-lint-disable no-triple-curlies}} {{{this.svgDocument}}} {{else}} diff --git a/addon/components/o-s-s/illustration.ts b/addon/components/o-s-s/illustration.ts index f30295b43..ca2262e2e 100644 --- a/addon/components/o-s-s/illustration.ts +++ b/addon/components/o-s-s/illustration.ts @@ -7,7 +7,7 @@ export const extractCSSVars = (): string[] => { .filter((styleSheet) => { try { return styleSheet.cssRules; - } catch (e) { + } catch { return false; } }) diff --git a/addon/components/o-s-s/infinite-select.hbs b/addon/components/o-s-s/infinite-select.hbs index a1721bd04..144108c54 100644 --- a/addon/components/o-s-s/infinite-select.hbs +++ b/addon/components/o-s-s/infinite-select.hbs @@ -19,6 +19,7 @@ {{/if}}
    +
    {{#if this.isSelectionMultiple}} diff --git a/addon/components/o-s-s/input-container.hbs b/addon/components/o-s-s/input-container.hbs index c1ae0dfb9..1c56848f1 100644 --- a/addon/components/o-s-s/input-container.hbs +++ b/addon/components/o-s-s/input-container.hbs @@ -19,6 +19,7 @@ @value={{@value}} @type={{this.type}} placeholder={{@placeholder}} + aria-label={{@ariaLabel}} disabled={{@disabled}} autocomplete={{this.autocomplete}} class="upf-input" @@ -35,9 +36,9 @@ {{@errorMessage}} {{else if this.feedbackMessage.value}} - {{#unless (eq this.feedbackMessage.type "error")}} + {{#if (not-eq this.feedbackMessage.type "error")}} - {{/unless}} + {{/if}} {{this.feedbackMessage.value}} {{/if}} diff --git a/addon/components/o-s-s/input-container.stories.js b/addon/components/o-s-s/input-container.stories.js index 68c310894..d96f9c04b 100644 --- a/addon/components/o-s-s/input-container.stories.js +++ b/addon/components/o-s-s/input-container.stories.js @@ -37,6 +37,16 @@ export default { }, control: { type: 'boolean' } }, + ariaLabel: { + description: 'Accessible name of the input, used when no visible label is associated with it', + table: { + type: { + summary: 'string' + }, + defaultValue: { summary: 'undefined' } + }, + control: { type: 'text' } + }, placeholder: { description: 'Placeholder of the input', table: { diff --git a/addon/components/o-s-s/input-container.ts b/addon/components/o-s-s/input-container.ts index c6598ac17..9d9213923 100644 --- a/addon/components/o-s-s/input-container.ts +++ b/addon/components/o-s-s/input-container.ts @@ -14,6 +14,7 @@ export interface OSSInputContainerArgs { errorMessage?: string; hasError?: boolean; placeholder?: string; + ariaLabel?: string; type?: string; autocomplete?: 'on' | 'off'; onChange?(value: string): void; diff --git a/addon/components/o-s-s/layout/sidebar/group.ts b/addon/components/o-s-s/layout/sidebar/group.ts index dc9b2001e..c3872567f 100644 --- a/addon/components/o-s-s/layout/sidebar/group.ts +++ b/addon/components/o-s-s/layout/sidebar/group.ts @@ -84,9 +84,7 @@ export default class OSSLayoutSidebarGroupComponent extends Component { - this.triggerHovered = this.displayGroupList = true; - }); + scheduleOnce('afterRender', this, this.showGroupList); document.querySelectorAll('.oss-sidebar-group__items-container--visible').forEach((el) => { if (!this.triggerElement.contains(el)) { @@ -126,6 +124,10 @@ export default class OSSLayoutSidebarGroupComponent extends Component { if (this.groupListHovered || this.triggerHovered) return; diff --git a/addon/components/o-s-s/mode-switch.ts b/addon/components/o-s-s/mode-switch.ts index 979067396..c920af163 100644 --- a/addon/components/o-s-s/mode-switch.ts +++ b/addon/components/o-s-s/mode-switch.ts @@ -35,9 +35,7 @@ export default class ModeSwitchComponent extends Component { - this.initComponent(); - }); + scheduleOnce('afterRender', this, this.initComponent); } get selectedOptionKey(): string { diff --git a/addon/components/o-s-s/nav-tab.hbs b/addon/components/o-s-s/nav-tab.hbs index 6bac9a0b7..055d78664 100644 --- a/addon/components/o-s-s/nav-tab.hbs +++ b/addon/components/o-s-s/nav-tab.hbs @@ -1,6 +1,7 @@
    {{#each @tabArray as |tab|}}
    -
    +
    {{@prefix}} { - const referenceTarget = this.container; - const floatingTarget = document.querySelector(`#${this.portalId}`); - - if (referenceTarget && floatingTarget) { - this.cleanupDrodpownAutoplacement = attachDropdown( - referenceTarget as HTMLElement, - floatingTarget as HTMLElement, - { placementStrategy: 'auto' } - ); - } - }); + scheduleOnce('afterRender', this, this.attachDropdownToContainer); } } @@ -157,6 +147,19 @@ export default class OSSPhoneNumberInput extends BaseDropdown @@ -27,8 +29,10 @@ {{#if this.isRightScrollable}} {{#if (eq this.buttonStyle "icon")}}