aria-ease 2.3.0 → 2.4.0
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/README.md +43 -20
- package/dist/index.cjs +8 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +8 -6
- package/dist/index.js.map +1 -1
- package/dist/src/block/index.cjs +7 -6
- package/dist/src/block/index.cjs.map +1 -1
- package/dist/src/block/index.d.cts +3 -1
- package/dist/src/block/index.d.ts +3 -1
- package/dist/src/block/index.js +7 -6
- package/dist/src/block/index.js.map +1 -1
- package/package.json +7 -1
package/dist/src/block/index.cjs
CHANGED
|
@@ -84,14 +84,14 @@ function makeBlockAccessible(blockId, blockItemsClass) {
|
|
|
84
84
|
const blockDiv = document.querySelector(`#${blockId}`);
|
|
85
85
|
if (!blockDiv) {
|
|
86
86
|
console.error(`[aria-ease] Element with id="${blockId}" not found. Make sure the block element exists before calling makeBlockAccessible.`);
|
|
87
|
-
return
|
|
88
|
-
};
|
|
87
|
+
return { cleanup: () => {
|
|
88
|
+
} };
|
|
89
89
|
}
|
|
90
90
|
const blockItems = blockDiv.querySelectorAll(`.${blockItemsClass}`);
|
|
91
91
|
if (!blockItems || blockItems.length === 0) {
|
|
92
92
|
console.error(`[aria-ease] Element with class="${blockItemsClass}" not found. Make sure the block items exist before calling makeBlockAccessible.`);
|
|
93
|
-
return
|
|
94
|
-
};
|
|
93
|
+
return { cleanup: () => {
|
|
94
|
+
} };
|
|
95
95
|
}
|
|
96
96
|
blockItems.forEach((blockItem) => {
|
|
97
97
|
if (!eventListenersMap.has(blockItem)) {
|
|
@@ -104,7 +104,7 @@ function makeBlockAccessible(blockId, blockItemsClass) {
|
|
|
104
104
|
eventListenersMap.set(blockItem, handler);
|
|
105
105
|
}
|
|
106
106
|
});
|
|
107
|
-
|
|
107
|
+
function cleanup() {
|
|
108
108
|
blockItems.forEach((blockItem) => {
|
|
109
109
|
const handler = eventListenersMap.get(blockItem);
|
|
110
110
|
if (handler) {
|
|
@@ -112,7 +112,8 @@ function makeBlockAccessible(blockId, blockItemsClass) {
|
|
|
112
112
|
eventListenersMap.delete(blockItem);
|
|
113
113
|
}
|
|
114
114
|
});
|
|
115
|
-
}
|
|
115
|
+
}
|
|
116
|
+
return { cleanup };
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
exports.makeBlockAccessible = makeBlockAccessible;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/handleKeyPress/handleKeyPress.ts","../../../src/block/src/makeBlockAccessible/makeBlockAccessible.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import { NodeListOfHTMLElement } from \"../../../Types\";\n\n\nfunction isTextInput(el: HTMLElement): boolean {\n if (el.tagName !== \"INPUT\") return false;\n const type = (el as HTMLInputElement).type;\n return [\"text\", \"email\", \"password\", \"tel\", \"number\"].includes(type);\n}\n\nfunction isTextArea(el: HTMLElement): boolean {\n return el.tagName === \"TEXTAREA\";\n}\n\nexport function isNativeButton(el: HTMLElement): boolean {\n return el.tagName === \"BUTTON\" || (el.tagName === \"INPUT\" && [\"button\", \"submit\", \"reset\"].includes((el as HTMLInputElement).type));\n}\n\nexport function isLink(el: HTMLElement): boolean {\n return el.tagName === \"A\";\n}\n\nfunction moveFocus(elementItems: NodeListOfHTMLElement, currentIndex: number, direction: -1 | 1) {\n const len = elementItems.length;\n const nextIndex = (currentIndex + direction + len) % len;\n elementItems.item(nextIndex).focus();\n}\n\nfunction isClickableButNotSemantic(el: HTMLElement): boolean {\n return el.getAttribute(\"data-custom-click\") !== null && el.getAttribute(\"data-custom-click\") !== undefined;\n}\n\nfunction handleMenuEscapeKeyPress(menuElement: HTMLElement, menuTriggerButton: HTMLElement) {\n menuElement.style.display = \"none\";\n const menuTriggerButtonId = menuTriggerButton.getAttribute(\"id\");\n if (!menuTriggerButtonId) {\n console.error(\"[aria-ease] Menu trigger button must have an id attribute to properly set aria attributes.\");\n return;\n }\n menuTriggerButton.setAttribute(\"aria-expanded\", \"false\");\n}\n\nfunction hasSubmenu(menuItem: HTMLElement): boolean {\n return menuItem.getAttribute(\"aria-haspopup\") === \"true\" || menuItem.getAttribute(\"aria-haspopup\") === \"menu\";\n}\n\nfunction getSubmenuId(menuItem: HTMLElement): string | null {\n return menuItem.getAttribute(\"aria-controls\");\n}\n\n\nexport function handleKeyPress(\n event: KeyboardEvent,\n elementItems: NodeListOfHTMLElement,\n elementItemIndex: number,\n menuElementDiv?: HTMLElement,\n triggerButton?: HTMLElement,\n openSubmenu?: (submenuId: string) => void,\n closeSubmenu?: () => void\n): void {\n const currentEl = elementItems.item(elementItemIndex);\n switch (event.key) {\n case \"ArrowUp\":\n case \"ArrowLeft\": {\n if(event.key === \"ArrowLeft\" && menuElementDiv && closeSubmenu) {\n const labelledBy = menuElementDiv.getAttribute(\"aria-labelledby\");\n if (labelledBy) {\n const parentTrigger = document.getElementById(labelledBy);\n if (parentTrigger && parentTrigger.getAttribute(\"role\") === \"menuitem\") {\n event.preventDefault();\n closeSubmenu();\n parentTrigger.focus();\n return;\n }\n }\n }\n\n if (!isTextInput(currentEl) && !isTextArea(currentEl)) {\n event.preventDefault();\n moveFocus(elementItems, elementItemIndex, -1);\n } else if (isTextInput(currentEl) || isTextArea(currentEl)) {\n const cursorStart = (currentEl as HTMLInputElement | HTMLTextAreaElement).selectionStart;\n if (cursorStart === 0) {\n event.preventDefault();\n moveFocus(elementItems, elementItemIndex, -1);\n }\n }\n break;\n }\n case \"ArrowDown\":\n case \"ArrowRight\": {\n if(event.key === \"ArrowRight\" && hasSubmenu(currentEl) && openSubmenu) {\n event.preventDefault();\n const submenuId = getSubmenuId(currentEl);\n if (submenuId) {\n openSubmenu(submenuId);\n return;\n }\n }\n \n if (!isTextInput(currentEl) && !isTextArea(currentEl)) {\n event.preventDefault();\n moveFocus(elementItems, elementItemIndex, 1);\n } else if (isTextInput(currentEl) || isTextArea(currentEl)) {\n const value = (currentEl as HTMLInputElement | HTMLTextAreaElement).value;\n const cursorEnd = (currentEl as HTMLInputElement | HTMLTextAreaElement).selectionStart;\n if (cursorEnd === value.length) {\n event.preventDefault();\n moveFocus(elementItems, elementItemIndex, 1);\n }\n }\n break;\n }\n case \"Escape\": {\n event.preventDefault();\n if (menuElementDiv && triggerButton) {\n if (getComputedStyle(menuElementDiv).display === \"block\") {\n handleMenuEscapeKeyPress(menuElementDiv, triggerButton);\n }\n triggerButton.focus();\n }\n break;\n }\n case \"Enter\":\n case \" \": {\n if (!isNativeButton(currentEl) && !isLink(currentEl) && isClickableButNotSemantic(currentEl)) { \n event.preventDefault();\n currentEl.click();\n } else if (isNativeButton(currentEl)) {\n event.preventDefault();\n currentEl.click();\n }\n break;\n }\n default:\n break;\n }\n}","/** \n * Adds keyboard interaction to block. The block traps focus and can be interacted with using the keyboard.\n * @param {string} blockId The id of the block container.\n * @param {string} blockItemsClass The shared class of the elements that are children of the block.\n*/\n\nimport { NodeListOfHTMLElement } from \"../../../../Types\"\nimport { handleKeyPress } from \"../../../utils/handleKeyPress/handleKeyPress\";\n\nconst eventListenersMap = new Map<HTMLElement, (event: KeyboardEvent) => void>();\n\nexport function makeBlockAccessible(blockId: string, blockItemsClass: string) {\n const blockDiv: HTMLElement = document.querySelector(`#${blockId}`) as HTMLElement\n if(!blockDiv) {\n console.error(`[aria-ease] Element with id=\"${blockId}\" not found. Make sure the block element exists before calling makeBlockAccessible.`);\n return function cleanUpBlockEventListeners(): void {};\n }\n\n const blockItems: NodeListOfHTMLElement = blockDiv.querySelectorAll(`.${blockItemsClass}`);\n if(!blockItems || blockItems.length === 0) {\n console.error(`[aria-ease] Element with class=\"${blockItemsClass}\" not found. Make sure the block items exist before calling makeBlockAccessible.`);\n return function cleanUpBlockEventListeners(): void {};\n }\n\n blockItems.forEach((blockItem: HTMLElement): void => {\n if (!eventListenersMap.has(blockItem)) {\n const handler = (event: KeyboardEvent) => {\n const items = blockDiv.querySelectorAll(`.${blockItemsClass}`) as NodeListOf<HTMLElement>;\n const index = Array.prototype.indexOf.call(items, blockItem);\n handleKeyPress(event, items, index);\n };\n blockItem.addEventListener(\"keydown\", handler);\n eventListenersMap.set(blockItem, handler);\n }\n });\n\n return function cleanUpBlockEventListeners(): void {\n blockItems.forEach((blockItem: HTMLElement): void => {\n const handler = eventListenersMap.get(blockItem);\n if (handler) {\n blockItem.removeEventListener(\"keydown\", handler);\n eventListenersMap.delete(blockItem);\n }\n });\n };\n}"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/handleKeyPress/handleKeyPress.ts","../../../src/block/src/makeBlockAccessible/makeBlockAccessible.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import { NodeListOfHTMLElement } from \"../../../Types\";\n\n\nfunction isTextInput(el: HTMLElement): boolean {\n if (el.tagName !== \"INPUT\") return false;\n const type = (el as HTMLInputElement).type;\n return [\"text\", \"email\", \"password\", \"tel\", \"number\"].includes(type);\n}\n\nfunction isTextArea(el: HTMLElement): boolean {\n return el.tagName === \"TEXTAREA\";\n}\n\nexport function isNativeButton(el: HTMLElement): boolean {\n return el.tagName === \"BUTTON\" || (el.tagName === \"INPUT\" && [\"button\", \"submit\", \"reset\"].includes((el as HTMLInputElement).type));\n}\n\nexport function isLink(el: HTMLElement): boolean {\n return el.tagName === \"A\";\n}\n\nfunction moveFocus(elementItems: NodeListOfHTMLElement, currentIndex: number, direction: -1 | 1) {\n const len = elementItems.length;\n const nextIndex = (currentIndex + direction + len) % len;\n elementItems.item(nextIndex).focus();\n}\n\nfunction isClickableButNotSemantic(el: HTMLElement): boolean {\n return el.getAttribute(\"data-custom-click\") !== null && el.getAttribute(\"data-custom-click\") !== undefined;\n}\n\nfunction handleMenuEscapeKeyPress(menuElement: HTMLElement, menuTriggerButton: HTMLElement) {\n menuElement.style.display = \"none\";\n const menuTriggerButtonId = menuTriggerButton.getAttribute(\"id\");\n if (!menuTriggerButtonId) {\n console.error(\"[aria-ease] Menu trigger button must have an id attribute to properly set aria attributes.\");\n return;\n }\n menuTriggerButton.setAttribute(\"aria-expanded\", \"false\");\n}\n\nfunction hasSubmenu(menuItem: HTMLElement): boolean {\n return menuItem.getAttribute(\"aria-haspopup\") === \"true\" || menuItem.getAttribute(\"aria-haspopup\") === \"menu\";\n}\n\nfunction getSubmenuId(menuItem: HTMLElement): string | null {\n return menuItem.getAttribute(\"aria-controls\");\n}\n\n\nexport function handleKeyPress(\n event: KeyboardEvent,\n elementItems: NodeListOfHTMLElement,\n elementItemIndex: number,\n menuElementDiv?: HTMLElement,\n triggerButton?: HTMLElement,\n openSubmenu?: (submenuId: string) => void,\n closeSubmenu?: () => void\n): void {\n const currentEl = elementItems.item(elementItemIndex);\n switch (event.key) {\n case \"ArrowUp\":\n case \"ArrowLeft\": {\n if(event.key === \"ArrowLeft\" && menuElementDiv && closeSubmenu) {\n const labelledBy = menuElementDiv.getAttribute(\"aria-labelledby\");\n if (labelledBy) {\n const parentTrigger = document.getElementById(labelledBy);\n if (parentTrigger && parentTrigger.getAttribute(\"role\") === \"menuitem\") {\n event.preventDefault();\n closeSubmenu();\n parentTrigger.focus();\n return;\n }\n }\n }\n\n if (!isTextInput(currentEl) && !isTextArea(currentEl)) {\n event.preventDefault();\n moveFocus(elementItems, elementItemIndex, -1);\n } else if (isTextInput(currentEl) || isTextArea(currentEl)) {\n const cursorStart = (currentEl as HTMLInputElement | HTMLTextAreaElement).selectionStart;\n if (cursorStart === 0) {\n event.preventDefault();\n moveFocus(elementItems, elementItemIndex, -1);\n }\n }\n break;\n }\n case \"ArrowDown\":\n case \"ArrowRight\": {\n if(event.key === \"ArrowRight\" && hasSubmenu(currentEl) && openSubmenu) {\n event.preventDefault();\n const submenuId = getSubmenuId(currentEl);\n if (submenuId) {\n openSubmenu(submenuId);\n return;\n }\n }\n \n if (!isTextInput(currentEl) && !isTextArea(currentEl)) {\n event.preventDefault();\n moveFocus(elementItems, elementItemIndex, 1);\n } else if (isTextInput(currentEl) || isTextArea(currentEl)) {\n const value = (currentEl as HTMLInputElement | HTMLTextAreaElement).value;\n const cursorEnd = (currentEl as HTMLInputElement | HTMLTextAreaElement).selectionStart;\n if (cursorEnd === value.length) {\n event.preventDefault();\n moveFocus(elementItems, elementItemIndex, 1);\n }\n }\n break;\n }\n case \"Escape\": {\n event.preventDefault();\n if (menuElementDiv && triggerButton) {\n if (getComputedStyle(menuElementDiv).display === \"block\") {\n handleMenuEscapeKeyPress(menuElementDiv, triggerButton);\n }\n triggerButton.focus();\n }\n break;\n }\n case \"Enter\":\n case \" \": {\n if (!isNativeButton(currentEl) && !isLink(currentEl) && isClickableButNotSemantic(currentEl)) { \n event.preventDefault();\n currentEl.click();\n } else if (isNativeButton(currentEl)) {\n event.preventDefault();\n currentEl.click();\n }\n break;\n }\n default:\n break;\n }\n}","/** \n * Adds keyboard interaction to block. The block traps focus and can be interacted with using the keyboard.\n * @param {string} blockId The id of the block container.\n * @param {string} blockItemsClass The shared class of the elements that are children of the block.\n*/\n\nimport { NodeListOfHTMLElement } from \"../../../../Types\"\nimport { handleKeyPress } from \"../../../utils/handleKeyPress/handleKeyPress\";\n\nconst eventListenersMap = new Map<HTMLElement, (event: KeyboardEvent) => void>();\n\nexport function makeBlockAccessible(blockId: string, blockItemsClass: string) {\n const blockDiv: HTMLElement = document.querySelector(`#${blockId}`) as HTMLElement\n if(!blockDiv) {\n console.error(`[aria-ease] Element with id=\"${blockId}\" not found. Make sure the block element exists before calling makeBlockAccessible.`);\n return { cleanup: () => {} };\n }\n\n const blockItems: NodeListOfHTMLElement = blockDiv.querySelectorAll(`.${blockItemsClass}`);\n if(!blockItems || blockItems.length === 0) {\n console.error(`[aria-ease] Element with class=\"${blockItemsClass}\" not found. Make sure the block items exist before calling makeBlockAccessible.`);\n return { cleanup: () => {} };\n }\n\n blockItems.forEach((blockItem: HTMLElement): void => {\n if (!eventListenersMap.has(blockItem)) {\n const handler = (event: KeyboardEvent) => {\n const items = blockDiv.querySelectorAll(`.${blockItemsClass}`) as NodeListOf<HTMLElement>;\n const index = Array.prototype.indexOf.call(items, blockItem);\n handleKeyPress(event, items, index);\n };\n blockItem.addEventListener(\"keydown\", handler);\n eventListenersMap.set(blockItem, handler);\n }\n });\n\n function cleanup(): void {\n blockItems.forEach((blockItem: HTMLElement): void => {\n const handler = eventListenersMap.get(blockItem);\n if (handler) {\n blockItem.removeEventListener(\"keydown\", handler);\n eventListenersMap.delete(blockItem);\n }\n });\n };\n\n return { cleanup }\n}"]}
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* @param {string} blockId The id of the block container.
|
|
4
4
|
* @param {string} blockItemsClass The shared class of the elements that are children of the block.
|
|
5
5
|
*/
|
|
6
|
-
declare function makeBlockAccessible(blockId: string, blockItemsClass: string):
|
|
6
|
+
declare function makeBlockAccessible(blockId: string, blockItemsClass: string): {
|
|
7
|
+
cleanup: () => void;
|
|
8
|
+
};
|
|
7
9
|
|
|
8
10
|
export { makeBlockAccessible };
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* @param {string} blockId The id of the block container.
|
|
4
4
|
* @param {string} blockItemsClass The shared class of the elements that are children of the block.
|
|
5
5
|
*/
|
|
6
|
-
declare function makeBlockAccessible(blockId: string, blockItemsClass: string):
|
|
6
|
+
declare function makeBlockAccessible(blockId: string, blockItemsClass: string): {
|
|
7
|
+
cleanup: () => void;
|
|
8
|
+
};
|
|
7
9
|
|
|
8
10
|
export { makeBlockAccessible };
|
package/dist/src/block/index.js
CHANGED
|
@@ -6,14 +6,14 @@ function makeBlockAccessible(blockId, blockItemsClass) {
|
|
|
6
6
|
const blockDiv = document.querySelector(`#${blockId}`);
|
|
7
7
|
if (!blockDiv) {
|
|
8
8
|
console.error(`[aria-ease] Element with id="${blockId}" not found. Make sure the block element exists before calling makeBlockAccessible.`);
|
|
9
|
-
return
|
|
10
|
-
};
|
|
9
|
+
return { cleanup: () => {
|
|
10
|
+
} };
|
|
11
11
|
}
|
|
12
12
|
const blockItems = blockDiv.querySelectorAll(`.${blockItemsClass}`);
|
|
13
13
|
if (!blockItems || blockItems.length === 0) {
|
|
14
14
|
console.error(`[aria-ease] Element with class="${blockItemsClass}" not found. Make sure the block items exist before calling makeBlockAccessible.`);
|
|
15
|
-
return
|
|
16
|
-
};
|
|
15
|
+
return { cleanup: () => {
|
|
16
|
+
} };
|
|
17
17
|
}
|
|
18
18
|
blockItems.forEach((blockItem) => {
|
|
19
19
|
if (!eventListenersMap.has(blockItem)) {
|
|
@@ -26,7 +26,7 @@ function makeBlockAccessible(blockId, blockItemsClass) {
|
|
|
26
26
|
eventListenersMap.set(blockItem, handler);
|
|
27
27
|
}
|
|
28
28
|
});
|
|
29
|
-
|
|
29
|
+
function cleanup() {
|
|
30
30
|
blockItems.forEach((blockItem) => {
|
|
31
31
|
const handler = eventListenersMap.get(blockItem);
|
|
32
32
|
if (handler) {
|
|
@@ -34,7 +34,8 @@ function makeBlockAccessible(blockId, blockItemsClass) {
|
|
|
34
34
|
eventListenersMap.delete(blockItem);
|
|
35
35
|
}
|
|
36
36
|
});
|
|
37
|
-
}
|
|
37
|
+
}
|
|
38
|
+
return { cleanup };
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
export { makeBlockAccessible };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/block/src/makeBlockAccessible/makeBlockAccessible.ts"],"names":[],"mappings":";;;AASA,IAAM,iBAAA,uBAAwB,GAAA,EAAiD;AAExE,SAAS,mBAAA,CAAoB,SAAiB,eAAA,EAAyB;AAC5E,EAAA,MAAM,QAAA,GAAwB,QAAA,CAAS,aAAA,CAAc,CAAA,CAAA,EAAI,OAAO,CAAA,CAAE,CAAA;AAClE,EAAA,IAAG,CAAC,QAAA,EAAU;AACZ,IAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,6BAAA,EAAgC,OAAO,CAAA,mFAAA,CAAqF,CAAA;AAC1I,IAAA,OAAO,SAAS,
|
|
1
|
+
{"version":3,"sources":["../../../src/block/src/makeBlockAccessible/makeBlockAccessible.ts"],"names":[],"mappings":";;;AASA,IAAM,iBAAA,uBAAwB,GAAA,EAAiD;AAExE,SAAS,mBAAA,CAAoB,SAAiB,eAAA,EAAyB;AAC5E,EAAA,MAAM,QAAA,GAAwB,QAAA,CAAS,aAAA,CAAc,CAAA,CAAA,EAAI,OAAO,CAAA,CAAE,CAAA;AAClE,EAAA,IAAG,CAAC,QAAA,EAAU;AACZ,IAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,6BAAA,EAAgC,OAAO,CAAA,mFAAA,CAAqF,CAAA;AAC1I,IAAA,OAAO,EAAE,SAAS,MAAM;AAAA,IAAC,CAAA,EAAE;AAAA,EAC7B;AAEA,EAAA,MAAM,UAAA,GAAoC,QAAA,CAAS,gBAAA,CAAiB,CAAA,CAAA,EAAI,eAAe,CAAA,CAAE,CAAA;AACzF,EAAA,IAAG,CAAC,UAAA,IAAc,UAAA,CAAW,MAAA,KAAW,CAAA,EAAG;AACzC,IAAA,OAAA,CAAQ,KAAA,CAAM,CAAA,gCAAA,EAAmC,eAAe,CAAA,gFAAA,CAAkF,CAAA;AAClJ,IAAA,OAAO,EAAE,SAAS,MAAM;AAAA,IAAC,CAAA,EAAE;AAAA,EAC7B;AAEA,EAAA,UAAA,CAAW,OAAA,CAAQ,CAAC,SAAA,KAAiC;AACnD,IAAA,IAAI,CAAC,iBAAA,CAAkB,GAAA,CAAI,SAAS,CAAA,EAAG;AACrC,MAAA,MAAM,OAAA,GAAU,CAAC,KAAA,KAAyB;AACxC,QAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,gBAAA,CAAiB,CAAA,CAAA,EAAI,eAAe,CAAA,CAAE,CAAA;AAC7D,QAAA,MAAM,QAAQ,KAAA,CAAM,SAAA,CAAU,OAAA,CAAQ,IAAA,CAAK,OAAO,SAAS,CAAA;AAC3D,QAAA,cAAA,CAAe,KAAA,EAAO,OAAO,KAAK,CAAA;AAAA,MACpC,CAAA;AACA,MAAA,SAAA,CAAU,gBAAA,CAAiB,WAAW,OAAO,CAAA;AAC7C,MAAA,iBAAA,CAAkB,GAAA,CAAI,WAAW,OAAO,CAAA;AAAA,IAC1C;AAAA,EACF,CAAC,CAAA;AAED,EAAA,SAAS,OAAA,GAAgB;AACvB,IAAA,UAAA,CAAW,OAAA,CAAQ,CAAC,SAAA,KAAiC;AACnD,MAAA,MAAM,OAAA,GAAU,iBAAA,CAAkB,GAAA,CAAI,SAAS,CAAA;AAC/C,MAAA,IAAI,OAAA,EAAS;AACX,QAAA,SAAA,CAAU,mBAAA,CAAoB,WAAW,OAAO,CAAA;AAChD,QAAA,iBAAA,CAAkB,OAAO,SAAS,CAAA;AAAA,MACpC;AAAA,IACF,CAAC,CAAA;AAAA,EACH;AAEA,EAAA,OAAO,EAAE,OAAA,EAAQ;AACnB","file":"index.js","sourcesContent":["/** \n * Adds keyboard interaction to block. The block traps focus and can be interacted with using the keyboard.\n * @param {string} blockId The id of the block container.\n * @param {string} blockItemsClass The shared class of the elements that are children of the block.\n*/\n\nimport { NodeListOfHTMLElement } from \"../../../../Types\"\nimport { handleKeyPress } from \"../../../utils/handleKeyPress/handleKeyPress\";\n\nconst eventListenersMap = new Map<HTMLElement, (event: KeyboardEvent) => void>();\n\nexport function makeBlockAccessible(blockId: string, blockItemsClass: string) {\n const blockDiv: HTMLElement = document.querySelector(`#${blockId}`) as HTMLElement\n if(!blockDiv) {\n console.error(`[aria-ease] Element with id=\"${blockId}\" not found. Make sure the block element exists before calling makeBlockAccessible.`);\n return { cleanup: () => {} };\n }\n\n const blockItems: NodeListOfHTMLElement = blockDiv.querySelectorAll(`.${blockItemsClass}`);\n if(!blockItems || blockItems.length === 0) {\n console.error(`[aria-ease] Element with class=\"${blockItemsClass}\" not found. Make sure the block items exist before calling makeBlockAccessible.`);\n return { cleanup: () => {} };\n }\n\n blockItems.forEach((blockItem: HTMLElement): void => {\n if (!eventListenersMap.has(blockItem)) {\n const handler = (event: KeyboardEvent) => {\n const items = blockDiv.querySelectorAll(`.${blockItemsClass}`) as NodeListOf<HTMLElement>;\n const index = Array.prototype.indexOf.call(items, blockItem);\n handleKeyPress(event, items, index);\n };\n blockItem.addEventListener(\"keydown\", handler);\n eventListenersMap.set(blockItem, handler);\n }\n });\n\n function cleanup(): void {\n blockItems.forEach((blockItem: HTMLElement): void => {\n const handler = eventListenersMap.get(blockItem);\n if (handler) {\n blockItem.removeEventListener(\"keydown\", handler);\n eventListenersMap.delete(blockItem);\n }\n });\n };\n\n return { cleanup }\n}"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aria-ease",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "Out-of-the-box accessibility utility package to develop production ready applications.",
|
|
5
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"type": "module",
|
|
@@ -66,26 +66,32 @@
|
|
|
66
66
|
"require": "./dist/index.cjs"
|
|
67
67
|
},
|
|
68
68
|
"./accordion": {
|
|
69
|
+
"types": "./dist/src/accordion/index.d.ts",
|
|
69
70
|
"import": "./dist/src/accordion/index.js",
|
|
70
71
|
"require": "./dist/src/accordion/index.cjs"
|
|
71
72
|
},
|
|
72
73
|
"./block": {
|
|
74
|
+
"types": "./dist/src/block/index.d.ts",
|
|
73
75
|
"import": "./dist/src/block/index.js",
|
|
74
76
|
"require": "./dist/src/block/index.cjs"
|
|
75
77
|
},
|
|
76
78
|
"./checkbox": {
|
|
79
|
+
"types": "./dist/src/checkbox/index.d.ts",
|
|
77
80
|
"import": "./dist/src/checkbox/index.js",
|
|
78
81
|
"require": "./dist/src/checkbox/index.cjs"
|
|
79
82
|
},
|
|
80
83
|
"./menu": {
|
|
84
|
+
"types": "./dist/src/menu/index.d.ts",
|
|
81
85
|
"import": "./dist/src/menu/index.js",
|
|
82
86
|
"require": "./dist/src/menu/index.cjs"
|
|
83
87
|
},
|
|
84
88
|
"./radio": {
|
|
89
|
+
"types": "./dist/src/radio/index.d.ts",
|
|
85
90
|
"import": "./dist/src/radio/index.js",
|
|
86
91
|
"require": "./dist/src/radio/index.cjs"
|
|
87
92
|
},
|
|
88
93
|
"./toggle": {
|
|
94
|
+
"types": "./dist/src/toggle/index.d.ts",
|
|
89
95
|
"import": "./dist/src/toggle/index.js",
|
|
90
96
|
"require": "./dist/src/toggle/index.cjs"
|
|
91
97
|
},
|