@stack-spot/portal-components 2.8.0 → 2.8.1

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.8.1](https://github.com/stack-spot/portal-commons/compare/portal-components@v2.8.0...portal-components@v2.8.1) (2024-10-28)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * fixes utility function focusFirstChild(element) ([#463](https://github.com/stack-spot/portal-commons/issues/463)) ([b221eb0](https://github.com/stack-spot/portal-commons/commit/b221eb063f59786e2f32c4324d211975fc72d894))
9
+
3
10
  ## [2.8.0](https://github.com/stack-spot/portal-commons/compare/portal-components@v2.7.0...portal-components@v2.8.0) (2024-10-25)
4
11
 
5
12
 
@@ -9,11 +9,13 @@
9
9
  * @param current the reference element to focus the next. If not provided, will be the currently active element.
10
10
  */
11
11
  export declare function focusNextIgnoringChildren(current?: HTMLElement | null): void;
12
- type TagPriority = 'a' | 'button' | 'input' | 'textarea' | 'select' | 'other';
13
- type TagPriorityElement = TagPriority | TagPriority[];
12
+ export type TagPriority = 'a' | 'button' | 'input' | 'textarea' | 'select' | 'other';
13
+ export type TagPriorityElement = TagPriority | TagPriority[];
14
14
  interface FocusOptions {
15
15
  /**
16
16
  * Instead of focusing the first element overall, focus the first according to this list of priorities.
17
+ *
18
+ * 'other' means elements that are normally not focusable, but have positive tabIndex values.
17
19
  */
18
20
  priority?: TagPriorityElement[];
19
21
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"accessibility.d.ts","sourceRoot":"","sources":["../../src/utils/accessibility.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,IAAI,QAWrE;AAED,KAAK,WAAW,GAAG,GAAG,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,CAAA;AAC7E,KAAK,kBAAkB,GAAG,WAAW,GAAG,WAAW,EAAE,CAAA;AAErD,UAAU,YAAY;IACpB;;OAEG;IACH,QAAQ,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAChC;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAWD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,WAAW,GAAG,QAAQ,GAAG,IAAI,GAAG,SAAS,EAAE,EAAE,QAAa,EAAE,MAAM,EAAE,GAAE,YAAiB,QA0B/H;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,WAYnD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,QAO9D"}
1
+ {"version":3,"file":"accessibility.d.ts","sourceRoot":"","sources":["../../src/utils/accessibility.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,IAAI,QAWrE;AAED,MAAM,MAAM,WAAW,GAAG,GAAG,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,CAAA;AACpF,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG,WAAW,EAAE,CAAA;AAE5D,UAAU,YAAY;IACpB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAChC;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAWD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,WAAW,GAAG,QAAQ,GAAG,IAAI,GAAG,SAAS,EAAE,EAAE,QAAa,EAAE,MAAM,EAAE,GAAE,YAAiB,QAkB/H;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,WAYnD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,QAO9D"}
@@ -51,32 +51,24 @@ const selectors = {
51
51
  * @param options optional.
52
52
  */
53
53
  export function focusFirstChild(element, { priority = [], ignore } = {}) {
54
- let focusable;
55
- let missing = ['a', 'button', 'input', 'other', 'select', 'textarea'];
54
+ const allFocusableTags = ['a', 'button', 'input', 'other', 'select', 'textarea'];
55
+ const focusableList = [
56
+ element?.querySelectorAll(allFocusableTags.map(t => selectors[t]).join(', ')),
57
+ ];
56
58
  for (const p of priority) {
57
59
  const tags = Array.isArray(p) ? p : [p];
58
- const querySelectors = tags.map(t => {
59
- missing = missing.filter(tag => tag != t);
60
- return selectors[t];
61
- });
62
- focusable = element?.querySelectorAll(querySelectors.join(', '));
63
- if (focusable)
64
- break;
60
+ const querySelectors = tags.map(t => selectors[t]);
61
+ focusableList.unshift(element?.querySelectorAll(querySelectors.join(', ')));
65
62
  }
66
- if (!focusable) {
67
- element?.querySelectorAll(missing.map(t => selectors[t]).join(', '));
68
- }
69
- let elementToFocus;
70
- for (const f of focusable ?? []) {
71
- if (!ignore || !f.matches(ignore)) {
72
- const styles = window.getComputedStyle(f);
73
- if (styles.display != 'none' && styles.visibility != 'hidden') {
74
- elementToFocus = f;
75
- break;
63
+ for (const focusable of focusableList ?? []) {
64
+ for (const f of focusable ?? []) {
65
+ if (!ignore || !f.matches(ignore)) {
66
+ const styles = window.getComputedStyle(f);
67
+ if (styles.display != 'none' && styles.visibility != 'hidden')
68
+ return f.focus();
76
69
  }
77
70
  }
78
71
  }
79
- elementToFocus?.focus?.();
80
72
  }
81
73
  /**
82
74
  * Checks if an element can receive focus.
@@ -1 +1 @@
1
- {"version":3,"file":"accessibility.js","sourceRoot":"","sources":["../../src/utils/accessibility.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"accessibility.js","sourceRoot":"","sources":["../../src/utils/accessibility.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stack-spot/portal-components",
3
- "version": "2.8.0",
3
+ "version": "2.8.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -21,12 +21,14 @@ export function focusNextIgnoringChildren(current?: HTMLElement | null) {
21
21
  else focusFirstChild(document)
22
22
  }
23
23
 
24
- type TagPriority = 'a' | 'button' | 'input' | 'textarea' | 'select' | 'other'
25
- type TagPriorityElement = TagPriority | TagPriority[]
24
+ export type TagPriority = 'a' | 'button' | 'input' | 'textarea' | 'select' | 'other'
25
+ export type TagPriorityElement = TagPriority | TagPriority[]
26
26
 
27
27
  interface FocusOptions {
28
28
  /**
29
29
  * Instead of focusing the first element overall, focus the first according to this list of priorities.
30
+ *
31
+ * 'other' means elements that are normally not focusable, but have positive tabIndex values.
30
32
  */
31
33
  priority?: TagPriorityElement[],
32
34
  /**
@@ -65,31 +67,23 @@ const selectors: Record<TagPriority, string> = {
65
67
  * @param options optional.
66
68
  */
67
69
  export function focusFirstChild(element: HTMLElement | Document | null | undefined, { priority = [], ignore }: FocusOptions = {}) {
68
- let focusable: NodeListOf<HTMLElement> | null | undefined
69
- let missing: TagPriority[] = ['a', 'button', 'input', 'other', 'select', 'textarea']
70
+ const allFocusableTags: TagPriority[] = ['a', 'button', 'input', 'other', 'select', 'textarea']
71
+ const focusableList: (NodeListOf<HTMLElement> | undefined)[] = [
72
+ element?.querySelectorAll(allFocusableTags.map(t => selectors[t]).join(', ')),
73
+ ]
70
74
  for (const p of priority) {
71
75
  const tags = Array.isArray(p) ? p : [p]
72
- const querySelectors = tags.map(t => {
73
- missing = missing.filter(tag => tag != t)
74
- return selectors[t]
75
- })
76
- focusable = element?.querySelectorAll(querySelectors.join(', '))
77
- if (focusable) break
76
+ const querySelectors = tags.map(t => selectors[t])
77
+ focusableList.unshift(element?.querySelectorAll(querySelectors.join(', ')))
78
78
  }
79
- if (!focusable) {
80
- element?.querySelectorAll(missing.map(t => selectors[t]).join(', '))
81
- }
82
- let elementToFocus: HTMLElement | undefined
83
- for (const f of focusable ?? []) {
84
- if (!ignore || !f.matches(ignore)) {
85
- const styles = window.getComputedStyle(f)
86
- if (styles.display != 'none' && styles.visibility != 'hidden') {
87
- elementToFocus = f
88
- break
79
+ for (const focusable of focusableList ?? []) {
80
+ for (const f of focusable ?? []) {
81
+ if (!ignore || !f.matches(ignore)) {
82
+ const styles = window.getComputedStyle(f)
83
+ if (styles.display != 'none' && styles.visibility != 'hidden') return f.focus()
89
84
  }
90
85
  }
91
86
  }
92
- elementToFocus?.focus?.()
93
87
  }
94
88
 
95
89
  /**