@stack-spot/portal-layout 0.0.53 → 0.0.54
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/dist/Layout.d.ts +2 -2
- package/dist/Layout.js +1 -1
- package/dist/LayoutOverlayManager.js +6 -6
- package/dist/LayoutOverlayManager.js.map +1 -1
- package/dist/components/Dialog.d.ts +1 -1
- package/dist/components/Dialog.js +1 -1
- package/dist/components/Header.d.ts +1 -1
- package/dist/components/Header.js +1 -1
- package/dist/components/OverlayContent.d.ts +1 -1
- package/dist/components/OverlayContent.js +20 -20
- package/dist/components/PortalSwitcher.d.ts +1 -1
- package/dist/components/PortalSwitcher.js +54 -54
- package/dist/components/SelectionList.d.ts +1 -1
- package/dist/components/SelectionList.js +54 -54
- package/dist/components/SelectionList.js.map +1 -1
- package/dist/components/Toaster.d.ts +1 -1
- package/dist/components/Toaster.js +1 -1
- package/dist/components/UserMenu.d.ts +1 -1
- package/dist/components/UserMenu.js +41 -41
- package/dist/components/error/ErrorBoundary.d.ts +1 -1
- package/dist/components/error/ErrorBoundary.js +1 -1
- package/dist/components/error/ErrorFeedback.d.ts +1 -1
- package/dist/components/error/ErrorFeedback.js +1 -1
- package/dist/components/error/SilentErrorBoundary.d.ts +1 -1
- package/dist/components/error/SilentErrorBoundary.js +1 -1
- package/dist/components/menu/MenuContent.d.ts +10 -12
- package/dist/components/menu/MenuContent.d.ts.map +1 -1
- package/dist/components/menu/MenuContent.js +146 -146
- package/dist/components/menu/MenuContent.js.map +1 -1
- package/dist/components/menu/MenuSections.d.ts +1 -1
- package/dist/components/menu/MenuSections.js +1 -1
- package/dist/components/menu/MenuSections.js.map +1 -1
- package/dist/components/menu/PageSelector.d.ts +1 -1
- package/dist/components/menu/PageSelector.js +65 -65
- package/dist/components/menu/PageSelector.js.map +1 -1
- package/dist/components/menu/use-check-text-overflow.js.map +1 -1
- package/dist/layout-context.d.ts +1 -1
- package/dist/layout-context.js +1 -1
- package/dist/layout.css +467 -466
- package/dist/svg/AI.d.ts +1 -1
- package/dist/svg/AI.js +1 -1
- package/dist/svg/EDP.d.ts +1 -1
- package/dist/svg/EDP.js +1 -1
- package/dist/svg/Forbidden.d.ts +1 -1
- package/dist/svg/Forbidden.js +1 -1
- package/dist/svg/HUB.d.ts +1 -1
- package/dist/svg/HUB.js +1 -1
- package/dist/svg/Logo.d.ts +1 -1
- package/dist/svg/Logo.js +1 -1
- package/dist/svg/NotFound.d.ts +1 -1
- package/dist/svg/NotFound.js +1 -1
- package/dist/svg/ServerError.d.ts +1 -1
- package/dist/svg/ServerError.js +1 -1
- package/dist/svg/Unauthenticated.d.ts +1 -1
- package/dist/svg/Unauthenticated.js +1 -1
- package/dist/toaster.js +1 -1
- package/dist/utils.js.map +1 -1
- package/package.json +5 -5
- package/src/Layout.tsx +106 -106
- package/src/LayoutOverlayManager.tsx +273 -273
- package/src/components/Dialog.tsx +93 -93
- package/src/components/Header.tsx +34 -34
- package/src/components/OverlayContent.tsx +58 -58
- package/src/components/PortalSwitcher.tsx +147 -147
- package/src/components/SelectionList.tsx +272 -272
- package/src/components/Toaster.tsx +16 -16
- package/src/components/UserMenu.tsx +111 -111
- package/src/components/error/ErrorBoundary.tsx +38 -38
- package/src/components/error/ErrorFeedback.tsx +114 -114
- package/src/components/error/ErrorManager.ts +31 -31
- package/src/components/error/SilentErrorBoundary.tsx +54 -54
- package/src/components/menu/MenuContent.tsx +296 -296
- package/src/components/menu/MenuSections.tsx +270 -270
- package/src/components/menu/PageSelector.tsx +154 -154
- package/src/components/menu/constants.ts +2 -2
- package/src/components/menu/types.ts +112 -112
- package/src/components/menu/use-check-text-overflow.tsx +26 -26
- package/src/components/menu/use-keyboard-controls.tsx +70 -70
- package/src/components/types.ts +15 -15
- package/src/dictionary.ts +25 -25
- package/src/elements.ts +24 -24
- package/src/errors.ts +11 -11
- package/src/index.ts +17 -17
- package/src/layout-context.tsx +22 -22
- package/src/layout.css +467 -466
- package/src/svg/AI.tsx +37 -37
- package/src/svg/EDP.tsx +35 -35
- package/src/svg/Forbidden.tsx +22 -22
- package/src/svg/HUB.tsx +35 -35
- package/src/svg/Logo.tsx +35 -35
- package/src/svg/NotFound.tsx +16 -16
- package/src/svg/ServerError.tsx +33 -33
- package/src/svg/Unauthenticated.tsx +16 -16
- package/src/toaster.tsx +76 -76
- package/src/utils.ts +114 -114
- package/tsconfig.json +8 -8
package/src/utils.ts
CHANGED
|
@@ -1,114 +1,114 @@
|
|
|
1
|
-
import { valueOf } from '@stack-spot/portal-theme'
|
|
2
|
-
import { getLayoutElements } from './elements'
|
|
3
|
-
|
|
4
|
-
export function valueOfLayoutVar(varname: string): string {
|
|
5
|
-
const layout = document.getElementById('layout')
|
|
6
|
-
if (!layout) return ''
|
|
7
|
-
return valueOf(varname, layout)
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Important for accessibility.
|
|
12
|
-
*
|
|
13
|
-
* Makes it so we focus the next focusable element in the DOM hierarchy, disregarding the element passed as parameter and its children.
|
|
14
|
-
*
|
|
15
|
-
* If there's no next focusable element, the first focusable of the page will be focused. If the page doesn't contain any focusable
|
|
16
|
-
* element, nothing happens.
|
|
17
|
-
*
|
|
18
|
-
* @param current the reference element to focus the next. If not provided, will be the currently active element.
|
|
19
|
-
*/
|
|
20
|
-
export function focusNextIgnoringChildren(current?: HTMLElement | null) {
|
|
21
|
-
current = current ?? document.activeElement as HTMLElement
|
|
22
|
-
while (current && !current.nextElementSibling) {
|
|
23
|
-
current = current?.parentElement
|
|
24
|
-
}
|
|
25
|
-
current = current?.nextElementSibling as HTMLElement
|
|
26
|
-
while (current && current.tabIndex < 0) {
|
|
27
|
-
current = (current.children.length ? current.firstChild : current.nextElementSibling) as HTMLElement
|
|
28
|
-
}
|
|
29
|
-
if (current) current?.focus?.()
|
|
30
|
-
else focusFirstChild(document)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
type TagPriority = 'a' | 'button' | 'input' | 'textarea' | 'select' | 'other'
|
|
34
|
-
type TagPriorityElement = TagPriority | TagPriority[]
|
|
35
|
-
|
|
36
|
-
interface FocusOptions {
|
|
37
|
-
/**
|
|
38
|
-
* Instead of focusing the first element overall, focus the first according to this list of priorities.
|
|
39
|
-
*/
|
|
40
|
-
priority?: TagPriorityElement[],
|
|
41
|
-
/**
|
|
42
|
-
* Ignores any element that matches this query selector.
|
|
43
|
-
*/
|
|
44
|
-
ignore?: string,
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const selectors: Record<TagPriority, string> = {
|
|
48
|
-
a: 'a[href]:not(:disabled)',
|
|
49
|
-
button: 'button:not(:disabled)',
|
|
50
|
-
input: 'input:not(:disabled):not([type="hidden"])',
|
|
51
|
-
select: 'textarea:not(:disabled)',
|
|
52
|
-
textarea: 'select:not(:disabled)',
|
|
53
|
-
other: '[tabindex]:not([tabindex="-1"])',
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Focus the first focusable child of the element provided. If the element has no focusable child, nothing happens.
|
|
58
|
-
*
|
|
59
|
-
* A priority list can be passed in the second parameter, as an option. If it's provided, it will focus the first element according to the
|
|
60
|
-
* list.
|
|
61
|
-
*
|
|
62
|
-
* An ignore query selector can also be passed in the options parameter. If the first focusable element matches the query selector, the
|
|
63
|
-
* next element is focused instead.
|
|
64
|
-
*
|
|
65
|
-
* @example
|
|
66
|
-
* Suppose the children of element are: h1, button, p, input, select.
|
|
67
|
-
* 1. We don't pass a priority list. The focused element will be the button.
|
|
68
|
-
* 2. Our priority list is ['button']. The focused element will be the button.
|
|
69
|
-
* 3. Our priority list is ['input', 'button']. The focused element will be the input.
|
|
70
|
-
* 4. Our priority list is ['select', 'input']. The focused element will be the select.
|
|
71
|
-
* 5. Our priority list is [['select', 'input'], 'button']. The focused element will be the input.
|
|
72
|
-
*
|
|
73
|
-
* @param element the element to search a child to focus.
|
|
74
|
-
* @param options optional.
|
|
75
|
-
*/
|
|
76
|
-
export function focusFirstChild(element: HTMLElement | Document | null | undefined, { priority = [], ignore }: FocusOptions = {}) {
|
|
77
|
-
let focusable: NodeListOf<HTMLElement> | null | undefined
|
|
78
|
-
let missing: TagPriority[] = ['a', 'button', 'input', 'other', 'select', 'textarea']
|
|
79
|
-
for (const p of priority) {
|
|
80
|
-
const tags = Array.isArray(p) ? p : [p]
|
|
81
|
-
const querySelectors = tags.map(t => {
|
|
82
|
-
missing = missing.filter(tag => tag != t)
|
|
83
|
-
return selectors[t]
|
|
84
|
-
})
|
|
85
|
-
focusable = element?.querySelectorAll(querySelectors.join(', '))
|
|
86
|
-
if (focusable) break
|
|
87
|
-
}
|
|
88
|
-
if (!focusable) {
|
|
89
|
-
element?.querySelectorAll(missing.map(t => selectors[t]).join(', '))
|
|
90
|
-
}
|
|
91
|
-
let elementToFocus: HTMLElement | undefined
|
|
92
|
-
for (const f of focusable ?? []) {
|
|
93
|
-
if (!ignore || !f.matches(ignore)) {
|
|
94
|
-
const styles = window.getComputedStyle(f)
|
|
95
|
-
if (styles.display != 'none' && styles.visibility != 'hidden') {
|
|
96
|
-
elementToFocus = f
|
|
97
|
-
break
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
elementToFocus?.focus?.()
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Accessibility: makes the screen reader announce some text out loud.
|
|
106
|
-
*
|
|
107
|
-
* @param text the message for the screen reader to read.
|
|
108
|
-
*/
|
|
109
|
-
export function announce(text: string) {
|
|
110
|
-
const { accessibilityAnnouncer } = getLayoutElements()
|
|
111
|
-
if (!accessibilityAnnouncer) return
|
|
112
|
-
accessibilityAnnouncer.textContent = text
|
|
113
|
-
setTimeout(() => accessibilityAnnouncer.textContent = '', 1000)
|
|
114
|
-
}
|
|
1
|
+
import { valueOf } from '@stack-spot/portal-theme'
|
|
2
|
+
import { getLayoutElements } from './elements'
|
|
3
|
+
|
|
4
|
+
export function valueOfLayoutVar(varname: string): string {
|
|
5
|
+
const layout = document.getElementById('layout')
|
|
6
|
+
if (!layout) return ''
|
|
7
|
+
return valueOf(varname, layout)
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Important for accessibility.
|
|
12
|
+
*
|
|
13
|
+
* Makes it so we focus the next focusable element in the DOM hierarchy, disregarding the element passed as parameter and its children.
|
|
14
|
+
*
|
|
15
|
+
* If there's no next focusable element, the first focusable of the page will be focused. If the page doesn't contain any focusable
|
|
16
|
+
* element, nothing happens.
|
|
17
|
+
*
|
|
18
|
+
* @param current the reference element to focus the next. If not provided, will be the currently active element.
|
|
19
|
+
*/
|
|
20
|
+
export function focusNextIgnoringChildren(current?: HTMLElement | null) {
|
|
21
|
+
current = current ?? document.activeElement as HTMLElement
|
|
22
|
+
while (current && !current.nextElementSibling) {
|
|
23
|
+
current = current?.parentElement
|
|
24
|
+
}
|
|
25
|
+
current = current?.nextElementSibling as HTMLElement
|
|
26
|
+
while (current && current.tabIndex < 0) {
|
|
27
|
+
current = (current.children.length ? current.firstChild : current.nextElementSibling) as HTMLElement
|
|
28
|
+
}
|
|
29
|
+
if (current) current?.focus?.()
|
|
30
|
+
else focusFirstChild(document)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type TagPriority = 'a' | 'button' | 'input' | 'textarea' | 'select' | 'other'
|
|
34
|
+
type TagPriorityElement = TagPriority | TagPriority[]
|
|
35
|
+
|
|
36
|
+
interface FocusOptions {
|
|
37
|
+
/**
|
|
38
|
+
* Instead of focusing the first element overall, focus the first according to this list of priorities.
|
|
39
|
+
*/
|
|
40
|
+
priority?: TagPriorityElement[],
|
|
41
|
+
/**
|
|
42
|
+
* Ignores any element that matches this query selector.
|
|
43
|
+
*/
|
|
44
|
+
ignore?: string,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const selectors: Record<TagPriority, string> = {
|
|
48
|
+
a: 'a[href]:not(:disabled)',
|
|
49
|
+
button: 'button:not(:disabled)',
|
|
50
|
+
input: 'input:not(:disabled):not([type="hidden"])',
|
|
51
|
+
select: 'textarea:not(:disabled)',
|
|
52
|
+
textarea: 'select:not(:disabled)',
|
|
53
|
+
other: '[tabindex]:not([tabindex="-1"])',
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Focus the first focusable child of the element provided. If the element has no focusable child, nothing happens.
|
|
58
|
+
*
|
|
59
|
+
* A priority list can be passed in the second parameter, as an option. If it's provided, it will focus the first element according to the
|
|
60
|
+
* list.
|
|
61
|
+
*
|
|
62
|
+
* An ignore query selector can also be passed in the options parameter. If the first focusable element matches the query selector, the
|
|
63
|
+
* next element is focused instead.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* Suppose the children of element are: h1, button, p, input, select.
|
|
67
|
+
* 1. We don't pass a priority list. The focused element will be the button.
|
|
68
|
+
* 2. Our priority list is ['button']. The focused element will be the button.
|
|
69
|
+
* 3. Our priority list is ['input', 'button']. The focused element will be the input.
|
|
70
|
+
* 4. Our priority list is ['select', 'input']. The focused element will be the select.
|
|
71
|
+
* 5. Our priority list is [['select', 'input'], 'button']. The focused element will be the input.
|
|
72
|
+
*
|
|
73
|
+
* @param element the element to search a child to focus.
|
|
74
|
+
* @param options optional.
|
|
75
|
+
*/
|
|
76
|
+
export function focusFirstChild(element: HTMLElement | Document | null | undefined, { priority = [], ignore }: FocusOptions = {}) {
|
|
77
|
+
let focusable: NodeListOf<HTMLElement> | null | undefined
|
|
78
|
+
let missing: TagPriority[] = ['a', 'button', 'input', 'other', 'select', 'textarea']
|
|
79
|
+
for (const p of priority) {
|
|
80
|
+
const tags = Array.isArray(p) ? p : [p]
|
|
81
|
+
const querySelectors = tags.map(t => {
|
|
82
|
+
missing = missing.filter(tag => tag != t)
|
|
83
|
+
return selectors[t]
|
|
84
|
+
})
|
|
85
|
+
focusable = element?.querySelectorAll(querySelectors.join(', '))
|
|
86
|
+
if (focusable) break
|
|
87
|
+
}
|
|
88
|
+
if (!focusable) {
|
|
89
|
+
element?.querySelectorAll(missing.map(t => selectors[t]).join(', '))
|
|
90
|
+
}
|
|
91
|
+
let elementToFocus: HTMLElement | undefined
|
|
92
|
+
for (const f of focusable ?? []) {
|
|
93
|
+
if (!ignore || !f.matches(ignore)) {
|
|
94
|
+
const styles = window.getComputedStyle(f)
|
|
95
|
+
if (styles.display != 'none' && styles.visibility != 'hidden') {
|
|
96
|
+
elementToFocus = f
|
|
97
|
+
break
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
elementToFocus?.focus?.()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Accessibility: makes the screen reader announce some text out loud.
|
|
106
|
+
*
|
|
107
|
+
* @param text the message for the screen reader to read.
|
|
108
|
+
*/
|
|
109
|
+
export function announce(text: string) {
|
|
110
|
+
const { accessibilityAnnouncer } = getLayoutElements()
|
|
111
|
+
if (!accessibilityAnnouncer) return
|
|
112
|
+
accessibilityAnnouncer.textContent = text
|
|
113
|
+
setTimeout(() => accessibilityAnnouncer.textContent = '', 1000)
|
|
114
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"module": "ESNext",
|
|
5
|
-
"outDir": "dist"
|
|
6
|
-
},
|
|
7
|
-
"include": ["src"]
|
|
8
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"outDir": "dist"
|
|
6
|
+
},
|
|
7
|
+
"include": ["src"]
|
|
8
|
+
}
|