Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 18
node-version-file: 'package.json'
cache: pnpm
- name: Install Dependencies
run: pnpm install --frozen-lockfile
Expand All @@ -40,7 +40,7 @@ jobs:
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 18
node-version-file: 'package.json'
cache: pnpm
- name: Install Dependencies
run: pnpm install --no-lockfile
Expand Down Expand Up @@ -70,7 +70,7 @@ jobs:
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 18
node-version-file: 'package.json'
cache: pnpm
- name: Install Dependencies
run: pnpm install --frozen-lockfile
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/plan-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ jobs:
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 18
node-version-file: 'package.json'
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: "Generate Explanation and Prep Changelogs"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 18
node-version-file: 'package.json'
# This creates an .npmrc that reads the NODE_AUTH_TOKEN environment variable
registry-url: 'https://registry.npmjs.org'
cache: pnpm
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/push-dist.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 18
node-version-file: 'package.json'
cache: pnpm
- name: Install Dependencies
run: pnpm install --frozen-lockfile
Expand Down
2 changes: 1 addition & 1 deletion addon/babel.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
],
"@embroider/addon-dev/template-colocation-plugin",
[
"babel-plugin-ember-template-compilation",
"module:babel-plugin-ember-template-compilation",
{
"targetFormat": "hbs",
"transforms": []
Expand Down
2 changes: 1 addition & 1 deletion addon/eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* npx eslint --inspect-config
*
*/
import babelParser from '@babel/eslint-parser';
import babelParser from '@babel/eslint-parser/experimental-worker';
import js from '@eslint/js';
import prettier from 'eslint-config-prettier';
import ember from 'eslint-plugin-ember/recommended';
Expand Down
65 changes: 31 additions & 34 deletions addon/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,45 +58,42 @@
"test": "echo 'A v2 addon does not have tests, run tests in test-app'"
},
"dependencies": {
"@embroider/addon-shim": "^1.9.0",
"@glimmer/component": "^2.0.0",
"@glimmer/tracking": "^1.1.2",
"@embroider/addon-shim": "^1.10.0",
"decorator-transforms": "^2.3.0"
},
"devDependencies": {
"@babel/core": "^7.26.0",
"@babel/eslint-parser": "^7.26.5",
"@babel/plugin-transform-typescript": "^7.26.5",
"@babel/runtime": "^7.26.0",
"@embroider/addon-dev": "^7.1.1",
"@eslint/js": "^9.18.0",
"@glint/core": "^1.5.1",
"@glint/environment-ember-loose": "^1.5.1",
"@glint/environment-ember-template-imports": "^1.5.1",
"@glint/template": "^1.5.1",
"@babel/core": "^7.28.4",
"@babel/eslint-parser": "^7.28.4",
"@babel/plugin-transform-typescript": "^7.28.0",
"@babel/runtime": "^7.28.4",
"@embroider/addon-dev": "^8.1.0",
"@eslint/js": "^9.36.0",
"@glimmer/component": "^2.0.0",
"@glint/core": "^1.5.2",
"@glint/environment-ember-loose": "^1.5.2",
"@glint/environment-ember-template-imports": "^1.5.2",
"@glint/template": "1.5.2",
"@rollup/plugin-babel": "^6.0.4",
"@tsconfig/ember": "^3.0.8",
"babel-plugin-ember-template-compilation": "^2.3.0",
"concurrently": "^9.1.2",
"ember-source": "^6.1.0",
"ember-template-lint": "^6.1.0",
"eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1",
"eslint-plugin-ember": "^12.3.3",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-n": "^17.15.1",
"globals": "^16.0.0",
"prettier": "^3.4.2",
"prettier-plugin-ember-template-tag": "^2.0.4",
"rollup": "^4.31.0",
"@tsconfig/ember": "^3.0.11",
"babel-plugin-ember-template-compilation": "^3.0.1",
"concurrently": "^9.2.1",
"ember-source": "^6.7.0",
"ember-template-lint": "^7.9.3",
"eslint": "^9.36.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-ember": "^12.7.4",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-n": "^17.23.1",
"globals": "^16.4.0",
"prettier": "^3.6.2",
"prettier-plugin-ember-template-tag": "^2.1.0",
"rollup": "^4.52.3",
"rollup-plugin-copy": "^3.5.0",
"stylelint": "^16.13.2",
"stylelint-config-standard": "^37.0.0",
"typescript": "~5.8.0",
"typescript-eslint": "^8.21.0"
},
"peerDependencies": {
"ember-source": ">= 4.12.0"
"rollup-plugin-import-css": "^4.0.2",
"stylelint": "^16.24.0",
"stylelint-config-standard": "^39.0.0",
"typescript": "~5.9.3",
"typescript-eslint": "^8.45.0"
},
"ember": {
"edition": "octane"
Expand Down
9 changes: 8 additions & 1 deletion addon/rollup.config.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Addon } from '@embroider/addon-dev/rollup';
import { babel } from '@rollup/plugin-babel';
import copy from 'rollup-plugin-copy';
import css from 'rollup-plugin-import-css';

const addon = new Addon({
srcDir: 'src',
Expand Down Expand Up @@ -59,7 +60,13 @@ export default {

// addons are allowed to contain imports of .css files, which we want rollup
// to leave alone and keep in the published output.
addon.keepAssets(['**/*.css']),
// addon.keepAssets(['**/*.css']),

css({
output: 'styles/navigation-narrator.css',
preserveImports: true,
minify: true,
}),

// Remove leftover build artifacts when starting a new build.
addon.clean(),
Expand Down
105 changes: 47 additions & 58 deletions addon/src/components/navigation-narrator.gts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { on } from '@ember/modifier';
import { registerDestructor } from '@ember/destroyable';
import { schedule, cancel } from '@ember/runloop';

import '../styles/navigation-narrator.css';
import { defaultValidator } from '../utils/validators.js';

import type RouterService from '@ember/routing/router-service';
Expand All @@ -14,21 +15,43 @@ import type { Timer } from '@ember/runloop';

export interface NavigationNarratorSignature {
Args: {
/** Whether to include the skip link. Defaults to `true`. */
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all this will show up like so in VS code at least

Screen.Recording.2025-06-15.at.6.36.51.PM.mov

skipLink?: boolean;

/** CSS selector the skip link jumps to. Defaults to `'#main'`. */
skipTo?: string;

/** Text content of the skip link. Defaults to `'Skip to main content'`. */
skipText?: string;

/** Text announced by a screen reader after navigation. */
navigationText?: string;

/** Custom logic for determining whether a transition should trigger narration. */
routeChangeValidator?: (transition: Transition) => boolean;
excludeAllQueryParams?: boolean;
};

Blocks: {
default: [];
/** Whether to ignore query param changes during route comparisons. Defaults to `false`. */
excludeAllQueryParams?: boolean;
};

Element: HTMLElement;
}

/**
* 🎧 NavigationNarrator
*
* Announces route changes for screen readers using a visually-hidden element,
* and optionally renders a "Skip to main content" link.
*
* Usage:
* ```gjs
* import { NavigationNarrator } from 'ember-a11y-refocus';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2025-06-15 at 6 37 55 PM

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it also turns out that we can not use @ symbols even inside of fenced blocks as it trips up markdown somehow, but this is good enough IMO

*
* <template>
* <NavigationNarrator />
* </template>
* ```
*/
export default class NavigationNarrator extends Component<NavigationNarratorSignature> {
@service declare readonly router: RouterService;

Expand All @@ -37,74 +60,53 @@ export default class NavigationNarrator extends Component<NavigationNarratorSign
timer: Timer | undefined;
transition: Transition | undefined;

/*
* @param skipLink
* @type {boolean}
* @description Whether or not to include the skip link in the page. If you don't want the skip link to be included, you can set this to false.
* @default true
*/
constructor(owner: Owner, args: NavigationNarratorSignature['Args']) {
super(owner, args);

this.router.on('routeDidChange', this.onRouteChange);

registerDestructor(this, () => {
// eslint-disable-next-line ember/no-runloop
cancel(this.timer);
this.timer = undefined;
this.router.off('routeDidChange', this.onRouteChange);
});
}

/** Whether to include the skip link. Defaults to `true`. */
get skipLink() {
return this.args.skipLink ?? true;
}

/*
* @param skipTo
* @type {string}
* @description Element selector for what the skip link should jump to. A default is provided but this can be customized.
* @default '#main'
*/
/** Selector for the skip link target. Defaults to `'#main'`. */
get skipTo() {
return this.args.skipTo ?? '#main';
}

/*
* @param skipText
* @type {string}
* @description text of the bypass block/skip link. Default text is provided but it can be customized.
* @default 'Skip to main content'
*/
/** Text for the skip link. Defaults to `'Skip to main content'`. */
get skipText() {
return this.args.skipText ?? 'Skip to main content';
}

/*
* @param navigationText
* @type {string}
* @description The text to be read by the screen reader when the page navigation is complete. While a default message is provided, it can be customized to better fit the needs of your application.
* @default 'The page navigation is complete. You may now navigate the page content as you wish.'
*/
/** Text announced by screen readers after route transition. */
get navigationText() {
return (
this.args.navigationText ??
'The page navigation is complete. You may now navigate the page content as you wish.'
);
}

/*
* @param routeChangeValidator
* @type {function}
* @description A function that can be used to provide a custom definition of a route transition. Typically used if you have some query params that you don't want to trigger the route transition (but you would otherwise mostly want it to behave per Ember's default).
*/
/** Validator function for transitions. */
get routeChangeValidator() {
return this.args.routeChangeValidator ?? defaultValidator;
}

/*
* @param excludeAllQueryParams
* @type {boolean}
* @description Whether or not to include all query params in definition of a route transition. If you want to include/exclude _some_ query params, the routeChangeValidator function should be used instead.
* @default false
*/
/** Whether to ignore query param changes. Defaults to `false`. */
get excludeAllQueryParams() {
return this.args.excludeAllQueryParams ?? false;
}

/*
* @param hasQueryParams
* @type {boolean}
* @description Check for queryParams.
* @default false
*/
/** Whether the current transition includes query param changes. */
get hasQueryParams() {
if (
Object.keys(this.transition?.from?.queryParams || {}).length ||
Expand All @@ -116,19 +118,6 @@ export default class NavigationNarrator extends Component<NavigationNarratorSign
}
}

constructor(owner: Owner, args: NavigationNarratorSignature['Args']) {
super(owner, args);

this.router.on('routeDidChange', this.onRouteChange);

registerDestructor(this, () => {
// eslint-disable-next-line ember/no-runloop
cancel(this.timer);
this.timer = undefined;
this.router.off('routeDidChange', this.onRouteChange);
});
}

onRouteChange = (transition: Transition) => {
this.transition = transition; // We need to do this because we can't pass an argument to a getter

Expand Down
2 changes: 1 addition & 1 deletion addon/src/styles/navigation-narrator.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
height: 1px;
padding: 0;
overflow: hidden;
clip: rect(0, 0, 0, 0);
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
Expand Down
15 changes: 8 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@
"test": "pnpm --filter '*' test"
},
"devDependencies": {
"@glint/core": "^1.5.1",
"concurrently": "^9.1.2",
"prettier": "^3.4.2",
"prettier-plugin-ember-template-tag": "^2.0.4",
"release-plan": "^0.17.0"
"@glint/core": "^1.5.2",
"concurrently": "^9.2.1",
"prettier": "^3.6.2",
"prettier-plugin-ember-template-tag": "^2.1.0",
"release-plan": "^0.17.2"
},
"packageManager": "[email protected]+sha512.fce8a3dd29a4ed2ec566fb53efbb04d8c44a0f05bc6f24a73046910fb9c3ce7afa35a0980500668fa3573345bd644644fa98338fa168235c80f4aa17aa17fbef",
"engines": {
"pnpm": ">= 10.0.0"
"node": "22.x",
"pnpm": ">= 10"
}
}
}
Loading