@thednp/shorty 2.0.3 → 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +7 -5
  2. package/dist/shorty.cjs +17 -1
  3. package/dist/shorty.cjs.map +1 -1
  4. package/dist/shorty.d.ts +20 -8
  5. package/dist/shorty.js +17 -1
  6. package/dist/shorty.js.map +1 -1
  7. package/dist/shorty.mjs +423 -379
  8. package/dist/shorty.mjs.map +1 -1
  9. package/dts.config.ts +1 -7
  10. package/package.json +16 -16
  11. package/src/boolean/isApple.ts +1 -1
  12. package/src/boolean/isFirefox.ts +3 -1
  13. package/src/boolean/isMobile.ts +1 -1
  14. package/src/boolean/supportPassive.ts +1 -1
  15. package/src/event/one.ts +1 -1
  16. package/src/get/getRectRelativeToOffsetParent.ts +1 -1
  17. package/src/index.ts +6 -0
  18. package/src/misc/createCustomEvent.ts +5 -5
  19. package/src/misc/createElementNS.ts +1 -1
  20. package/src/misc/data.ts +2 -5
  21. package/src/misc/emulateAnimationEnd.ts +4 -3
  22. package/src/misc/emulateTransitionEnd.ts +3 -3
  23. package/src/misc/focusTrap.ts +64 -0
  24. package/src/misc/normalizeOptions.ts +2 -2
  25. package/src/misc/timer.ts +3 -3
  26. package/src/selectors/closest.ts +5 -2
  27. package/src/selectors/getCustomElements.ts +1 -2
  28. package/src/selectors/getElementById.ts +2 -2
  29. package/src/selectors/getElementsByClassName.ts +2 -7
  30. package/src/selectors/getElementsByTagName.ts +2 -5
  31. package/src/selectors/querySelector.ts +6 -3
  32. package/src/selectors/querySelectorAll.ts +4 -1
  33. package/src/strings/focusableSelector.ts +4 -0
  34. package/test/att.test.ts +43 -0
  35. package/test/boolean.test.ts +30 -0
  36. package/test/class.test.ts +26 -0
  37. package/test/event.test.ts +39 -0
  38. package/{cypress/test.html → test/fixtures/getExampleDom.ts} +21 -32
  39. package/test/fixtures/style.css +18 -0
  40. package/test/get.test.ts +150 -0
  41. package/{cypress/e2e/is.cy.ts → test/is.test.ts} +77 -70
  42. package/test/misc.test.ts +400 -0
  43. package/test/selectors.test.ts +90 -0
  44. package/tsconfig.json +6 -1
  45. package/{vite.config.ts → vite.config.mts} +3 -8
  46. package/vitest.config-ui.mts +21 -0
  47. package/vitest.config.mts +20 -0
  48. package/cypress/e2e/att.cy.ts +0 -46
  49. package/cypress/e2e/boolean.cy.ts +0 -44
  50. package/cypress/e2e/class.cy.ts +0 -28
  51. package/cypress/e2e/event.cy.ts +0 -51
  52. package/cypress/e2e/get.cy.ts +0 -168
  53. package/cypress/e2e/misc.cy.ts +0 -354
  54. package/cypress/e2e/selectors.cy.ts +0 -85
  55. package/cypress/plugins/esbuild-istanbul.ts +0 -50
  56. package/cypress/plugins/tsCompile.ts +0 -34
  57. package/cypress/support/commands.ts +0 -37
  58. package/cypress/support/e2e.ts +0 -21
  59. package/cypress/support/index.js +0 -22
  60. package/cypress.config.ts +0 -30
  61. /package/{cypress → test}/fixtures/custom-elem.js +0 -0
package/dts.config.ts CHANGED
@@ -1,14 +1,8 @@
1
- const packageJson = require("./package.json");
2
-
3
- const getPackageName = () => {
4
- return packageJson.name.includes('@') ? packageJson.name.split('/')[1] : packageJson.name;
5
- };
6
-
7
1
  const config = {
8
2
  entries: [
9
3
  {
10
4
  filePath: "./src/index.ts",
11
- outFile: `./dist/${getPackageName()}.d.ts`,
5
+ outFile: `./dist/shorty.d.ts`,
12
6
  noCheck: false,
13
7
  output: {
14
8
  exportReferencedTypes: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thednp/shorty",
3
- "version": "2.0.3",
3
+ "version": "2.0.5",
4
4
  "description": "TypeScript shorties for the web",
5
5
  "source": "./src/index.ts",
6
6
  "main": "./dist/shorty.js",
@@ -32,25 +32,25 @@
32
32
  },
33
33
  "homepage": "https://github.com/thednp/shorty",
34
34
  "devDependencies": {
35
- "@bahmutov/cypress-esbuild-preprocessor": "^2.2.1",
36
- "@cypress/code-coverage": "^3.12.44",
37
- "@types/istanbul-lib-instrument": "^1.7.7",
38
- "@types/node": "^20.14.11",
35
+ "@types/node": "^20.16.15",
39
36
  "@typescript-eslint/eslint-plugin": "^5.62.0",
40
37
  "@typescript-eslint/parser": "^5.62.0",
41
- "cypress": "^13.13.1",
38
+ "@vitest/browser": "^2.1.3",
39
+ "@vitest/coverage-istanbul": "^2.1.3",
40
+ "@vitest/ui": "^2.1.3",
42
41
  "dts-bundle-generator": "^9.5.1",
43
- "eslint": "^8.57.0",
42
+ "eslint": "^8.57.1",
44
43
  "eslint-plugin-jsdoc": "^46.10.1",
45
44
  "eslint-plugin-prefer-arrow": "^1.2.3",
46
45
  "eslint-plugin-prettier": "^4.2.1",
47
46
  "istanbul-lib-coverage": "^3.2.2",
48
47
  "istanbul-lib-instrument": "^5.2.1",
49
48
  "nyc": "^15.1.0",
49
+ "playwright": "^1.48.1",
50
50
  "prettier": "^2.8.8",
51
- "rimraf": "^5.0.9",
52
- "typescript": "^5.5.3",
53
- "vite": "^5.3.4"
51
+ "typescript": "^5.6.3",
52
+ "vite": "^5.4.10",
53
+ "vitest": "^2.1.3"
54
54
  },
55
55
  "engines": {
56
56
  "node": ">=16",
@@ -58,16 +58,16 @@
58
58
  },
59
59
  "scripts": {
60
60
  "pre-test": "pnpm clean-coverage",
61
- "badges": "npx -p dependency-version-badge update-badge typescript prettier cypress eslint vite",
62
- "test": "pnpm pre-test && npx cypress run",
63
- "clean-coverage": "rimraf coverage .nyc_output",
64
- "cypress": "pnpm pre-test && npx cypress open",
65
- "coverage:report": "nyc report --reporter=lcov --reporter=json --reporter=text --reporter=json-summary",
61
+ "badges": "npx -p dependency-version-badge update-badge typescript prettier eslint vitest vite",
62
+ "test": "pnpm pre-test && vitest --config vitest.config.mts",
63
+ "test-ui": "pnpm pre-test && vitest --config vitest.config-ui.mts",
64
+ "clean-coverage": "rm -rf coverage .nyc_output",
66
65
  "format": "prettier --write \"src/**/*.ts\"",
66
+ "lint": "pnpm lint:ts && pnpm check:ts",
67
67
  "lint:ts": "eslint -c .eslintrc.cjs --ext .ts src",
68
68
  "fix:ts": "eslint -c .eslintrc.cjs --ext .ts src --fix",
69
69
  "check:ts": "tsc -noEmit",
70
- "build": "pnpm check:ts && pnpm lint:ts && vite build && pnpm dts",
70
+ "build": "vite build && pnpm dts",
71
71
  "dts": "dts-bundle-generator --config ./dts.config.ts"
72
72
  }
73
73
  }
@@ -9,6 +9,6 @@ const appleBrands = /(iPhone|iPod|iPad)/;
9
9
  */
10
10
  const isApple: boolean = userAgentData
11
11
  ? userAgentData.brands.some((x: NavigatorUABrand) => appleBrands.test(x.brand))
12
- : /* istanbul ignore next */ appleBrands.test(userAgent);
12
+ : /* istanbul ignore next @preserve */ appleBrands.test(userAgent);
13
13
 
14
14
  export default isApple;
@@ -4,6 +4,8 @@ import userAgent from '../strings/userAgent';
4
4
  * A global boolean for Gecko browsers. When writing this file,
5
5
  * Gecko was not supporting `userAgentData`.
6
6
  */
7
- const isFirefox = userAgent ? userAgent.includes('Firefox') : /* istanbul ignore next */ false;
7
+ const isFirefox = userAgent
8
+ ? userAgent.includes('Firefox')
9
+ : /* istanbul ignore next @preserve */ false;
8
10
 
9
11
  export default isFirefox;
@@ -4,7 +4,7 @@ import userAgent from '../strings/userAgent';
4
4
  const mobileBrands = /iPhone|iPad|iPod|Android/i;
5
5
  let isMobileCheck = false;
6
6
 
7
- /* istanbul ignore else */
7
+ // istanbul ignore else @preserve
8
8
  if (userAgentData) {
9
9
  isMobileCheck = userAgentData.brands.some(x => mobileBrands.test(x.brand));
10
10
  } else {
@@ -17,7 +17,7 @@ const supportPassive = (() => {
17
17
  return result;
18
18
  },
19
19
  });
20
- /* istanbul ignore next */
20
+ // istanbul ignore next @preserve
21
21
  one(document, DOMContentLoadedEvent, noop, opts);
22
22
  } catch (e) {
23
23
  // throw Error('Passive events are not supported');
package/src/event/one.ts CHANGED
@@ -14,7 +14,7 @@ const one = <T extends EventTarget, L = EventListener>(
14
14
  ) => {
15
15
  /** Wrap the listener for easy on -> off */
16
16
  const handlerWrapper: NativeEventHandler<T> = e => {
17
- /* istanbul ignore else */
17
+ /* istanbul ignore else @preserve */
18
18
  if (e.target === element || e.currentTarget === element) {
19
19
  (listener as NativeEventHandler<T>).apply(element, [e]);
20
20
  off(element, eventName, handlerWrapper, options);
@@ -22,7 +22,7 @@ const getRectRelativeToOffsetParent = (
22
22
  const rect = getBoundingClientRect(element, isParentAnElement && isScaledElement(offsetParent));
23
23
  const offsets = { x: 0, y: 0 };
24
24
 
25
- /* istanbul ignore next */
25
+ // istanbul ignore else @preserve
26
26
  if (isParentAnElement) {
27
27
  const offsetRect = getBoundingClientRect(offsetParent, true);
28
28
  offsets.x = offsetRect.x + offsetParent.clientLeft;
package/src/index.ts CHANGED
@@ -77,6 +77,7 @@ import focusEvents from './strings/focusEvents';
77
77
  import focusEvent from './strings/focusEvent';
78
78
  import focusinEvent from './strings/focusinEvent';
79
79
  import focusoutEvent from './strings/focusoutEvent';
80
+ import focusableSelector from './strings/focusableSelector';
80
81
 
81
82
  import gesturechangeEvent from './strings/gesturechangeEvent';
82
83
  import gestureendEvent from './strings/gestureendEvent';
@@ -194,6 +195,7 @@ import setElementStyle from './misc/setElementStyle';
194
195
  import Timer from './misc/timer';
195
196
  import toLowerCase from './misc/toLowerCase';
196
197
  import toUpperCase from './misc/toUpperCase';
198
+ import { type FocusableElement, hasFocusTrap, toggleFocusTrap } from './misc/focusTrap';
197
199
 
198
200
  // get
199
201
  import getBoundingClientRect from './get/getBoundingClientRect';
@@ -397,6 +399,9 @@ export {
397
399
  createCustomEvent,
398
400
  toUpperCase,
399
401
  toLowerCase,
402
+ focusableSelector,
403
+ hasFocusTrap,
404
+ toggleFocusTrap,
400
405
  Timer,
401
406
  emulateAnimationEnd,
402
407
  emulateTransitionEnd,
@@ -480,6 +485,7 @@ export {
480
485
  export * from './interface/navigatorUA.d';
481
486
  export * from './interface/offsetRect.d';
482
487
 
488
+ export type { FocusableElement };
483
489
  export type { OriginalEvent } from './interface/originalEvent.d';
484
490
 
485
491
  export type { BoundingClientRect } from './interface/boundingClientRect.d';
@@ -9,16 +9,16 @@ import ObjectAssign from './ObjectAssign';
9
9
  * @param config Event.options | Event.properties
10
10
  * @returns a new namespaced event
11
11
  */
12
- const createCustomEvent = <T extends OriginalEvent>(
12
+ const createCustomEvent = <O extends unknown & Record<string, unknown>, T extends OriginalEvent>(
13
13
  eventType: string,
14
- config?: CustomEventInit<any>,
14
+ config?: O,
15
15
  ): T => {
16
- const OriginalCustomEvent = new CustomEvent<any>(eventType, {
16
+ const OriginalCustomEvent = new CustomEvent<O>(eventType, {
17
17
  cancelable: true,
18
18
  bubbles: true,
19
- } as CustomEventInit<any>) as T;
19
+ }) as T;
20
20
 
21
- /* istanbul ignore else */
21
+ // istanbul ignore else @preserve
22
22
  if (isObject(config)) {
23
23
  ObjectAssign(OriginalCustomEvent, config);
24
24
  }
@@ -16,7 +16,7 @@ import isString from '../is/isString';
16
16
  * @param param `tagName` or object
17
17
  * @return a new `HTMLElement`
18
18
  */
19
- const createElementNS = <T extends HTMLElement>(
19
+ const createElementNS = <T extends Element = HTMLElement>(
20
20
  ns: string,
21
21
  param?: string | Partial<T>,
22
22
  ): T | undefined => {
package/src/misc/data.ts CHANGED
@@ -19,7 +19,7 @@ const Data = {
19
19
  set: <T>(element: HTMLElement, component: string, instance: T): void => {
20
20
  if (!isHTMLElement(element)) return;
21
21
 
22
- /* istanbul ignore else */
22
+ // istanbul ignore else @preserve
23
23
  if (!componentData.has(component)) {
24
24
  componentData.set(component, new Map<HTMLElement, T>());
25
25
  }
@@ -51,11 +51,9 @@ const Data = {
51
51
  get: <T>(element: HTMLElement, component: string): T | null => {
52
52
  if (!isHTMLElement(element) || !component) return null;
53
53
  const instanceMap = Data.getAllFor<T>(component);
54
- // const instanceMap = componentData.get(component) as Map<HTMLElement, InstanceType<T>>;
55
54
 
56
55
  const instance = element && instanceMap && instanceMap.get(element);
57
56
 
58
- // return (instance as T) || null;
59
57
  return instance || null;
60
58
  },
61
59
 
@@ -67,13 +65,12 @@ const Data = {
67
65
  */
68
66
  remove: <T>(element: HTMLElement, component: string): void => {
69
67
  const instanceMap = Data.getAllFor<T>(component);
70
- // const instanceMap = componentData.get(component) as Map<HTMLElement, InstanceType<T>>;
71
68
 
72
69
  if (!instanceMap || !isHTMLElement(element)) return;
73
70
 
74
71
  instanceMap.delete(element);
75
72
 
76
- /* istanbul ignore else */
73
+ // istanbul ignore else @preserve
77
74
  if (instanceMap.size === 0) {
78
75
  componentData.delete(component);
79
76
  }
@@ -17,9 +17,9 @@ const emulateAnimationEnd = (element: HTMLElement, handler: EventListener): void
17
17
  const delay = getElementAnimationDelay(element);
18
18
 
19
19
  if (duration) {
20
- /** Wrap the handler in on -> off callback */
20
+ // Wrap the handler in on -> off callback
21
21
  const animationEndWrapper = (e: Event): void => {
22
- /* istanbul ignore else */
22
+ // istanbul ignore else @preserve
23
23
  if (e.target === element) {
24
24
  handler.apply(element, [e]);
25
25
  element.removeEventListener(animationEndEvent, animationEndWrapper);
@@ -28,10 +28,11 @@ const emulateAnimationEnd = (element: HTMLElement, handler: EventListener): void
28
28
  };
29
29
  element.addEventListener(animationEndEvent, animationEndWrapper);
30
30
  setTimeout(() => {
31
- /* istanbul ignore next */
31
+ // istanbul ignore next @preserve
32
32
  if (!called) dispatchEvent(element, endEvent);
33
33
  }, duration + delay + 17);
34
34
  } else {
35
+ // istanbul ignore next @preserve
35
36
  handler.apply(element, [endEvent]);
36
37
  }
37
38
  };
@@ -17,9 +17,9 @@ const emulateTransitionEnd = (element: HTMLElement, handler: EventListener): voi
17
17
  const delay = getElementTransitionDelay(element);
18
18
 
19
19
  if (duration) {
20
- /** Wrap the handler in on -> off callback */
20
+ // Wrap the handler in on -> off callback
21
21
  const transitionEndWrapper = (e: Event): void => {
22
- /* istanbul ignore else */
22
+ // istanbul ignore else @preserve
23
23
  if (e.target === element) {
24
24
  handler.apply(element, [e]);
25
25
  element.removeEventListener(transitionEndEvent, transitionEndWrapper);
@@ -28,7 +28,7 @@ const emulateTransitionEnd = (element: HTMLElement, handler: EventListener): voi
28
28
  };
29
29
  element.addEventListener(transitionEndEvent, transitionEndWrapper);
30
30
  setTimeout(() => {
31
- /* istanbul ignore next */
31
+ // istanbul ignore next @preserve
32
32
  if (!called) dispatchEvent(element, endEvent);
33
33
  }, duration + delay + 17);
34
34
  } else {
@@ -0,0 +1,64 @@
1
+ import ariaHidden from '../strings/ariaHidden';
2
+ import focusableSelector from '../strings/focusableSelector';
3
+ import querySelectorAll from '../selectors/querySelectorAll';
4
+ import getAttribute from '../attr/getAttribute';
5
+ import hasAttribute from '../attr/hasAttribute';
6
+ import off from '../event/off';
7
+ import on from '../event/on';
8
+ import getDocument from '../get/getDocument';
9
+ import { KeyboardEvent } from '../interface/event';
10
+
11
+ const focusTrapMap = new Map<HTMLElement, boolean>();
12
+
13
+ export type FocusableElement =
14
+ | HTMLAnchorElement
15
+ | HTMLButtonElement
16
+ | HTMLInputElement
17
+ | HTMLTextAreaElement
18
+ | HTMLDataListElement
19
+ | HTMLDetailsElement
20
+ | HTMLSelectElement;
21
+
22
+ function handleKeyboardNavigation<T extends HTMLElement & EventTarget>(
23
+ this: T,
24
+ event: KeyboardEvent<T>,
25
+ ) {
26
+ const { shiftKey, code } = event;
27
+ const doc = getDocument(this);
28
+ const focusableElements = [...querySelectorAll<FocusableElement>(focusableSelector, this)].filter(
29
+ el => !hasAttribute(el, 'disabled') && !getAttribute(el, ariaHidden),
30
+ );
31
+
32
+ if (!focusableElements.length) return;
33
+ const firstFocusable = focusableElements[0];
34
+ const lastFocusable = focusableElements[focusableElements.length - 1];
35
+
36
+ // istanbul ignore else @preserve
37
+ if (code === 'Tab') {
38
+ if (shiftKey && doc.activeElement === firstFocusable) {
39
+ lastFocusable.focus();
40
+ event.preventDefault();
41
+ } else if (!shiftKey && doc.activeElement === lastFocusable) {
42
+ firstFocusable.focus();
43
+ event.preventDefault();
44
+ }
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Utility to check if a designated element is affected by focus trap;
50
+ * @param target
51
+ */
52
+ export const hasFocusTrap = (target: HTMLElement) => focusTrapMap.has(target) === true;
53
+
54
+ /**
55
+ * Utility to toggle focus trap inside a designated target element;
56
+ * @param target
57
+ */
58
+ export const toggleFocusTrap = (target: HTMLElement) => {
59
+ const isCurrentlyTrapped = hasFocusTrap(target);
60
+ const action = !isCurrentlyTrapped ? on : off;
61
+ action(target, 'keydown', handleKeyboardNavigation);
62
+ if (isCurrentlyTrapped) focusTrapMap.delete(target);
63
+ else focusTrapMap.set(target, true);
64
+ };
@@ -29,7 +29,7 @@ const normalizeOptions = <T extends { [key: string]: any }>(
29
29
  const key: keyof T =
30
30
  ns && typeof k === 'string' && k.includes(ns)
31
31
  ? k.replace(ns, '').replace(/[A-Z]/g, (match: string) => toLowerCase(match))
32
- : k;
32
+ : /* istanbul ignore next @preserve */ k;
33
33
 
34
34
  dataOps[key] = normalizeValue(v) as T[keyof T];
35
35
  });
@@ -39,7 +39,7 @@ const normalizeOptions = <T extends { [key: string]: any }>(
39
39
  });
40
40
 
41
41
  ObjectEntries(defaultOps).forEach(([k, v]) => {
42
- /* istanbul ignore else */
42
+ // istanbul ignore else @preserve
43
43
  if (k in INPUT) {
44
44
  normalOps[k] = INPUT[k] as T[keyof T];
45
45
  } else if (k in dataOps) {
package/src/misc/timer.ts CHANGED
@@ -23,9 +23,9 @@ const Timer = {
23
23
  set: (element: HTMLElement, callback: TimerHandler, delay: number, key?: string): void => {
24
24
  if (!isHTMLElement(element)) return;
25
25
 
26
- /* istanbul ignore else */
26
+ // istanbul ignore else @preserve
27
27
  if (key && key.length) {
28
- /* istanbul ignore else */
28
+ // istanbul ignore else @preserve
29
29
  if (!TimeCache.has(element)) {
30
30
  TimeCache.set(element, new Map());
31
31
  }
@@ -70,7 +70,7 @@ const Timer = {
70
70
  if (key && key.length && isMap(keyTimers as KeyMap)) {
71
71
  clearTimeout((keyTimers as KeyMap).get(key));
72
72
  (keyTimers as KeyMap).delete(key);
73
- /* istanbul ignore else */
73
+ // istanbul ignore else @preserve
74
74
  if ((keyTimers as KeyMap).size === 0) {
75
75
  TimeCache.delete(element);
76
76
  }
@@ -9,11 +9,14 @@
9
9
  * @param selector the selector name
10
10
  * @return the query result
11
11
  */
12
- const closest = (element: HTMLElement, selector: string): HTMLElement | null => {
12
+ const closest = <T extends Element = HTMLElement>(
13
+ element: T,
14
+ selector: string,
15
+ ): HTMLElement | null => {
13
16
  return element
14
17
  ? element.closest(selector) ||
15
18
  // break out of `ShadowRoot`
16
- closest((element.getRootNode() as HTMLElement & { host: HTMLElement }).host, selector)
19
+ closest((element.getRootNode() as ShadowRoot).host, selector)
17
20
  : null;
18
21
  };
19
22
 
@@ -1,6 +1,5 @@
1
1
  import isCustomElement from '../is/isCustomElement';
2
2
  import getElementsByTagName from './getElementsByTagName';
3
- import type { CustomElement } from '../interface/customElement';
4
3
 
5
4
  /**
6
5
  * Returns an `Array` of `Node` elements that are registered as
@@ -11,7 +10,7 @@ import type { CustomElement } from '../interface/customElement';
11
10
  * @param parent parent to look into
12
11
  * @returns the query result
13
12
  */
14
- const getCustomElements = (parent?: ParentNode): CustomElement[] => {
13
+ const getCustomElements = (parent?: ParentNode) => {
15
14
  const collection = getElementsByTagName('*', parent);
16
15
 
17
16
  return [...collection].filter(isCustomElement);
@@ -9,8 +9,8 @@ import getDocument from '../get/getDocument';
9
9
  * @param context an element in it's document or document
10
10
  * @returns the requested element
11
11
  */
12
- const getElementById = (id: string, context?: Node): HTMLElement | null => {
13
- return getDocument(context).getElementById(id) || null;
12
+ const getElementById = <T extends HTMLElement>(id: string, context?: Node) => {
13
+ return (getDocument(context).getElementById(id) as T) || null;
14
14
  };
15
15
 
16
16
  export default getElementById;
@@ -9,14 +9,9 @@ import isNode from '../is/isNode';
9
9
  * @param parent optional Element to look into
10
10
  * @return the 'HTMLCollection'
11
11
  */
12
- const getElementsByClassName = (
13
- selector: string,
14
- parent?: ParentNode,
15
- ): HTMLCollectionOf<HTMLElement> => {
12
+ const getElementsByClassName = <T extends HTMLElement>(selector: string, parent?: ParentNode) => {
16
13
  const lookUp = parent && isNode(parent) ? parent : getDocument();
17
- return (lookUp as HTMLElement | Document).getElementsByClassName(
18
- selector,
19
- ) as HTMLCollectionOf<HTMLElement>;
14
+ return (lookUp as HTMLElement | Document).getElementsByClassName(selector) as HTMLCollectionOf<T>;
20
15
  };
21
16
 
22
17
  export default getElementsByClassName;
@@ -9,12 +9,9 @@ import isNode from '../is/isNode';
9
9
  * @param parent optional Element to look into
10
10
  * @return the 'HTMLCollection'
11
11
  */
12
- const getElementsByTagName = (
13
- selector: string,
14
- parent?: ParentNode,
15
- ): HTMLCollectionOf<HTMLElement> => {
12
+ const getElementsByTagName = <T extends HTMLElement>(selector: string, parent?: ParentNode) => {
16
13
  const lookUp = isNode(parent) ? parent : getDocument();
17
- return (lookUp as Document).getElementsByTagName(selector) as HTMLCollectionOf<HTMLElement>;
14
+ return (lookUp as Document).getElementsByTagName(selector) as HTMLCollectionOf<T>;
18
15
  };
19
16
 
20
17
  export default getElementsByTagName;
@@ -10,13 +10,16 @@ import isHTMLElement from '../is/isHTMLElement';
10
10
  * @param parent optional node to look into
11
11
  * @return the `HTMLElement` or `querySelector` result
12
12
  */
13
- const querySelector = (selector: HTMLElement | string, parent?: ParentNode): HTMLElement | null => {
13
+ const querySelector = <T extends HTMLElement>(
14
+ selector: HTMLElement | string,
15
+ parent?: ParentNode,
16
+ ): T | null => {
14
17
  if (isHTMLElement(selector)) {
15
- return selector;
18
+ return selector as T;
16
19
  }
17
20
  const lookUp = isNode(parent) ? parent : getDocument();
18
21
 
19
- return lookUp.querySelector(selector);
22
+ return lookUp.querySelector<T>(selector);
20
23
  };
21
24
 
22
25
  export default querySelector;
@@ -8,7 +8,10 @@ import isNode from '../is/isNode';
8
8
  * @param parent optional node to look into
9
9
  * @return the query result
10
10
  */
11
- const querySelectorAll = (selector: string, parent?: ParentNode): NodeListOf<HTMLElement> => {
11
+ const querySelectorAll = <T extends HTMLElement>(
12
+ selector: string,
13
+ parent?: ParentNode,
14
+ ): NodeListOf<T> => {
12
15
  const lookUp = isNode(parent) ? parent : getDocument();
13
16
  return lookUp.querySelectorAll(selector);
14
17
  };
@@ -0,0 +1,4 @@
1
+ const focusableSelector =
2
+ 'a[href], button, input, textarea, select, details, [tabindex]:not([tabindex="-1"]';
3
+
4
+ export default focusableSelector;
@@ -0,0 +1,43 @@
1
+ import { expect, it, describe, afterEach } from 'vitest';
2
+ import { getExampleDOM } from './fixtures/getExampleDom';
3
+ import * as SHORTY from '../src/index';
4
+ import "./fixtures/style.css";
5
+
6
+ describe('Shorty Library Tests - ATT', () => {
7
+ const wrapper = document.createElement('div');
8
+ afterEach(async () => {
9
+ wrapper.innerHTML = '';
10
+ });
11
+
12
+ it('Test attr folder', () => {
13
+ const container = getExampleDOM();
14
+ wrapper.append(container);
15
+
16
+ const {
17
+ getAttribute,
18
+ setAttribute,
19
+ hasAttribute,
20
+ removeAttribute,
21
+ getAttributeNS,
22
+ setAttributeNS,
23
+ hasAttributeNS,
24
+ removeAttributeNS,
25
+ querySelector,
26
+ } = SHORTY;
27
+
28
+ const el = querySelector('.alert', container)!;
29
+ expect(getAttribute(el, 'class')).to.have.length.above(0);
30
+ setAttribute(el, 'data-att', 'momo');
31
+ expect(hasAttribute(el, 'data-att')).to.be.true;
32
+ removeAttribute(el, 'data-att');
33
+ expect(hasAttribute(el, 'data-att')).to.be.false;
34
+
35
+ const svg = querySelector('svg', container)!;
36
+ const ns = 'http://www.w3.org/2000/svg';
37
+ expect(hasAttributeNS(ns, svg, 'transform')).to.be.false;
38
+ setAttributeNS(ns, svg, 'transform', 'scale(0.99)');
39
+ expect(getAttributeNS(ns, svg, 'transform')).to.eq('scale(0.99)');
40
+ removeAttributeNS(ns, svg, 'transform');
41
+ expect(getAttributeNS(ns, svg, 'transform')).to.be.null;
42
+ });
43
+ });
@@ -0,0 +1,30 @@
1
+ import { expect, it, describe } from 'vitest';
2
+ import * as SHORTY from '../src/index';
3
+
4
+ describe('Shorty Library Tests - BOOL', () => {
5
+ it('Test boolean folder', () => {
6
+ const {
7
+ // these are impossible to test 100% of the branches
8
+ isApple,
9
+ isMobile,
10
+ isFirefox,
11
+ support3DTransform,
12
+ supportAnimation,
13
+ supportPassive,
14
+ supportTouch,
15
+ supportTransform,
16
+ supportTransition,
17
+ // querySelectorAll, getWindow
18
+ } = SHORTY;
19
+
20
+ expect(isApple, 'isApple').to.be.false;
21
+ expect(isMobile, 'isMobile').to.be.false;
22
+ expect(isFirefox, 'isFirefox').to.be.false;
23
+ expect(support3DTransform, 'support3DTransform').to.be.true;
24
+ expect(supportAnimation, 'supportAnimation').to.be.true;
25
+ expect(supportPassive, 'supportPassive').to.be.true;
26
+ expect(supportTouch, 'supportTouch').to.be.false;
27
+ expect(supportTransform, 'supportTransform').to.be.true;
28
+ expect(supportTransition, 'supportTransition').to.be.true;
29
+ });
30
+ });
@@ -0,0 +1,26 @@
1
+ import { expect, it, describe, beforeEach, vi, afterEach } from 'vitest';
2
+ import { getExampleDOM } from './fixtures/getExampleDom';
3
+ import * as SHORTY from '../src/index';
4
+ import "./fixtures/style.css";
5
+
6
+ describe('Shorty Library Tests - CLASS', () => {
7
+ const wrapper = document.createElement('div');
8
+ document.body.append(wrapper);
9
+ afterEach(async () => {
10
+ wrapper.innerHTML = '';
11
+ });
12
+
13
+ it('Test class folder', () => {
14
+ const { addClass, hasClass, removeClass, querySelector } = SHORTY;
15
+ const container = getExampleDOM();
16
+ wrapper.append(container);
17
+
18
+ const alert = querySelector('.alert', container);
19
+ if (!alert) return;
20
+
21
+ addClass(alert, 'to-be-removed');
22
+ expect(hasClass(alert, 'to-be-removed')).to.be.true;
23
+ removeClass(alert, 'show');
24
+ expect(hasClass(alert, 'show')).to.be.false;
25
+ });
26
+ });