Skip to content

Commit

Permalink
Merge pull request #407 from universal-ember/make-setup-async
Browse files Browse the repository at this point in the history
[Breaking] Make tabster not required. new import for setting up tabster, and it's async.
  • Loading branch information
NullVoxPopuli authored Nov 13, 2024
2 parents dbbfa8c + 0524b18 commit 678c50a
Show file tree
Hide file tree
Showing 10 changed files with 94 additions and 84 deletions.
7 changes: 3 additions & 4 deletions docs-app/app/routes/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,19 @@ import { service } from '@ember/service';

import rehypeShikiFromHighlighter from '@shikijs/rehype/core';
import { Callout } from 'docs-app/components/callout';
import { setupTabster } from 'ember-primitives/tabster';
import { getHighlighterCore } from 'shiki/core';
import getWasm from 'shiki/wasm';

import { APIDocs, ComponentSignature, ModifierSignature } from './api-docs';

import type { SetupService } from 'ember-primitives';
import type { DocsService } from 'kolay';

export default class Application extends Route {
@service('kolay/docs') declare docs: DocsService;
@service('ember-primitives/setup') declare primitives: SetupService;

beforeModel() {
this.primitives.setup();
async beforeModel() {
await setupTabster(this);
}

async model() {
Expand Down
20 changes: 10 additions & 10 deletions docs-app/public/docs/2-accessibility/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,30 +44,30 @@ This only happens during development, and in production, the CSS that applies th

## Keyboard Support

ember-primitives uses _The Platform_ where possible and implements W3C recommendations for patterns where _The Platform_ does not provide solutions. To help lift the burden of maintenance for keyboard support implementation, ember-primitives uses [tabster](https://tabster.io/) for adding that additional keyboard support.
ember-primitives uses _The Platform_ where possible and implements W3C recommendations for patterns where _The Platform_ does not provide solutions. To help lift the burden of maintenance for keyboard support implementation, ember-primitives uses [tabster](https://tabster.io/) for adding that additional keyboard support. Using tabster is optional, and is not included in your build if you don't use the below setup instructions (for example, if you had a different keyboard manager in your project and wanted to use that)

This keyboard support is enabled by default but does require initialization. You can initialize keyboard support in your application router by calling the `setup()` method on the `ember-primitives/setup` service:
This keyboard support is enabled by default but does require initialization. You can initialize keyboard support in your application router by calling the `setupTabster()` function:
```ts
import Route from '@ember/routing/route';
import { service } from '@ember/service';

import type { SetupService } from 'ember-primitives';
import{ setupTabster } from 'ember-primitives/tabster';

export default class Application extends Route {
@service('ember-primitives/setup') declare primitives: SetupService;

beforeModel() {
this.primitives.setup();
async beforeModel() {
// the 'this' is passed so that tabster is cleaned up
// when the route (or in this case: application)
// is destroyed or unmounted
await setupTabster(this);
}
}
```

This is customizable, in case your application already uses tabster -- you may pass options to the `setup()` method:
```ts
// To use your own tabster
this.primitives.setup({ tabster: myTabsterCoreInstance });
await setupTabster(this, { tabster: myTabsterCoreInstance });
// To specify your own "tabster root"
this.primitives.setup({ setTabsterRoot: false });
await setupTabster(this, { setTabsterRoot: false });
```

The tabster root is an element which which tells tabster to pay attention for tabster-using features.
Expand Down
4 changes: 1 addition & 3 deletions ember-primitives/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,7 @@
"version": 2,
"type": "addon",
"main": "addon-main.cjs",
"app-js": {
"./services/ember-primitives/setup.js": "./dist/_app_/services/ember-primitives/setup.js"
}
"app-js": {}
},
"exports": {
".": {
Expand Down
4 changes: 4 additions & 0 deletions ember-primitives/src/-private.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* @internal
*/
export const PRIMITIVES = Symbol.for('ember-primitives-globals');
1 change: 0 additions & 1 deletion ember-primitives/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,3 @@ export { Toggle } from './components/toggle.gts';
export { ToggleGroup } from './components/toggle-group.gts';
export { Zoetrope } from './components/zoetrope.ts';
export * from './helpers.ts';
export type { default as SetupService } from './services/ember-primitives/setup.ts';
53 changes: 0 additions & 53 deletions ember-primitives/src/services/ember-primitives/setup.ts

This file was deleted.

55 changes: 55 additions & 0 deletions ember-primitives/src/tabster.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { registerDestructor } from '@ember/destroyable';

export async function setupTabster(
/**
* A destroyable object.
* This is needed so that when the app (or tests) or unmounted or ending,
* the tabster instance can be disposed of.
*/
context: object,
{
tabster,
setTabsterRoot,
}: {
/**
* Let this setup function initalize tabster.
* https://tabster.io/docs/core
*
* This should be done only once per application as we don't want
* focus managers fighting with each other.
*
* Defaults to `true`,
*
* Will fallback to an existing tabster instance automatically if `getTabster` returns a value.
*
* If `false` is explicitly passed here, you'll also be in charge of teardown.
*/
tabster?: boolean;
setTabsterRoot?: boolean;
} = {}
) {
const { createTabster, getDeloser, getMover, getTabster, disposeTabster } = await import(
'tabster'
);

tabster ??= true;
setTabsterRoot ??= true;

if (!tabster) {
return;
}

let existing = getTabster(window);
let primitivesTabster = existing ?? createTabster(window);

getMover(primitivesTabster);
getDeloser(primitivesTabster);

if (setTabsterRoot) {
document.body.setAttribute('data-tabster', '{ "root": {} }');
}

registerDestructor(context, () => {
disposeTabster(primitivesTabster);
});
}
24 changes: 18 additions & 6 deletions ember-primitives/src/test-support/a11y.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,30 @@
import { assert } from '@ember/debug';

import type SetupService from '../services/ember-primitives/setup.ts';
import { setupTabster as _setupTabster } from '../tabster.ts';

import type Owner from '@ember/owner';

/**
* Sets up all support utilities for primitive components.
* Including the tabster root.
*/
export function setup(owner: Owner) {
let service = owner.lookup('service:ember-primitives/setup');
async function setup(owner: Owner) {
_setupTabster(owner, { setTabsterRoot: false });

assert('Could not find the ember-primitives service', service);
document.querySelector('#ember-testing')?.setAttribute('data-tabster', '{ "root": {} }');
}

(service as SetupService).setup({ setTabsterRoot: false });
export function setupTabster(hooks: {
beforeEach: (callback: () => void | Promise<void>) => unknown;
}) {
hooks.beforeEach(function (this: { owner: object }) {
let owner = this.owner;

document.querySelector('#ember-testing')?.setAttribute('data-tabster', '{ "root": {} }');
assert(
`Test does not have an owner, be sure to use setupRenderingTest, setupTest, or setupApplicationTest (from ember-qunit (or similar))`,
owner
);

setup(this.owner as Owner);
});
}
1 change: 1 addition & 0 deletions ember-primitives/src/test-support/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { setupTabster } from './a11y.ts';
export { fillOTP } from './otp.ts';
export { getRouter, setupRouting } from './routing.ts';
export { ZoetropeHelper } from './zoetrope.ts';
9 changes: 2 additions & 7 deletions test-app/tests/menu/menu-rendering-test.gts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,11 @@ import { setupRenderingTest } from 'ember-qunit';

import { Menu, PortalTargets } from 'ember-primitives';

import type { SetupService } from 'ember-primitives';
import { setupTabster } from 'ember-primitives/test-support';

module('Rendering | menu', function (hooks) {
setupRenderingTest(hooks);

hooks.beforeEach(async function () {
let primitivesService = this.owner.lookup('service:ember-primitives/setup') as SetupService;

primitivesService.setup();
});
setupTabster(hooks);

// due to the way tabster works, we have to wait a bit for the focus to be correctly set
function waitForFocus(selector: string) {
Expand Down

0 comments on commit 678c50a

Please sign in to comment.