diff --git a/package.json b/package.json index c0d8f999..b6d5f972 100644 --- a/package.json +++ b/package.json @@ -25,8 +25,15 @@ "./dist/react/*": "./dist/react/*", "./dist/translations/*": "./dist/translations/*" }, - "files": ["dist", "cdn"], - "keywords": ["web components", "custom elements", "components"], + "files": [ + "dist", + "cdn" + ], + "keywords": [ + "web components", + "custom elements", + "components" + ], "repository": { "type": "git", "url": "git+https://github.com/shoelace-style/shoelace.git" @@ -43,8 +50,8 @@ "build": "node scripts/build.js", "verify": "npm run prettier:check && npm run lint && npm run build && npm run test", "prepublishOnly": "npm run verify", - "prettier": "prettier --write --loglevel warn .", - "prettier:check": "prettier --check --loglevel warn .", + "prettier": "prettier --write --log-level=warn .", + "prettier:check": "prettier --check --log-level=warn .", "lint": "eslint src --max-warnings 0", "lint:fix": "eslint src --max-warnings 0 --fix", "ts-check": "tsc --noEmit --project ./tsconfig.json", @@ -133,6 +140,9 @@ "user-agent-data-types": "^0.3.1" }, "lint-staged": { - "*.{ts,js}": ["eslint --max-warnings 0 --cache --fix", "prettier --write"] + "*.{ts,js}": [ + "eslint --max-warnings 0 --cache --fix", + "prettier --write" + ] } } diff --git a/src/internal/modal.ts b/src/internal/modal.ts index fe463ad2..9c507d90 100644 --- a/src/internal/modal.ts +++ b/src/internal/modal.ts @@ -87,7 +87,7 @@ export default class Modal { if (currentFocusIndex === -1) { this.currentFocus = tabbableElements[0]; - this.currentFocus.focus({ preventScroll: true }); + this.currentFocus?.focus({ preventScroll: true }); return; } diff --git a/src/internal/tabbable.ts b/src/internal/tabbable.ts index fca4ec5c..9c692b75 100644 --- a/src/internal/tabbable.ts +++ b/src/internal/tabbable.ts @@ -1,5 +1,11 @@ -import { offsetParent } from 'composed-offset-position'; - +// It doesn't technically check visibility, it checks if the element has been rendered and can maybe possibly be tabbed to. +// This is a workaround for shadowroots not having an `offsetParent` +// https://stackoverflow.com/questions/19669786/check-if-element-is-visible-in-dom +// Previously, we used https://www.npmjs.com/package/composed-offset-position, but recursing up an entire +// node tree took up a lot of CPU cycles and made focus traps unusable in Chrome / Edge. +function isTakingUpSpace(elem: HTMLElement): boolean { + return Boolean(elem.offsetParent || elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length); +} /** Determines if the specified element is tabbable using heuristics inspired by https://github.com/focus-trap/tabbable */ function isTabbable(el: HTMLElement) { const tag = el.tagName.toLowerCase(); @@ -20,8 +26,8 @@ function isTabbable(el: HTMLElement) { } // Elements that are hidden have no offsetParent and are not tabbable - // offsetParent() is added because otherwise it misses elements in Safari - if (el.offsetParent === null && offsetParent(el) === null) { + // !isRendered is added because otherwise it misses elements in Safari + if (!isTakingUpSpace(el)) { return false; }