Skip to content

Commit 4d72fe1

Browse files
author
Illia Obukhau
committed
refactor(web-actions): migrate actions to module
1 parent f3a1e9a commit 4d72fe1

File tree

14 files changed

+1145
-190
lines changed

14 files changed

+1145
-190
lines changed

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*.gitattributes eol=lf
1616
*.gitignore eol=lf
1717

18+
packages/modules/web-actions/javascriptsource/**/*.js eol=crlf
19+
1820
# Denote all files that are truly binary and should not be modified.
1921
*.png binary
2022
*.jpg binary

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
packages/tools/generator-widget/generators/app/templates
22
packages/tools/pluggable-widgets-tools/tests/projects
33
packages/pluggableWidgets/*/typings
4+
packages/modules/web-actions/javascriptsource/**/*.js
45
*.png
56
*.svg
67
*.snap
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
module.exports = {
2+
root: true,
3+
env: {
4+
browser: true,
5+
es2017: true
6+
},
7+
extends: "eslint:recommended",
8+
overrides: [],
9+
parserOptions: {
10+
sourceType: "module"
11+
},
12+
globals: {
13+
mx: "readonly"
14+
},
15+
rules: {}
16+
};
Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
export function setFocus(element) {
2+
clearSelection();
3+
element.focus();
4+
selectText(element);
5+
}
6+
7+
function clearSelection() {
8+
// This is necessary for IE and Edge
9+
const selection = document.getSelection();
10+
if (selection === null || isEmptySelection(selection)) {
11+
// Prevent IE11 from switching focus to document.body unless absolutely necessary
12+
return;
13+
}
14+
selection.removeAllRanges();
15+
selection.addRange(document.createRange());
16+
}
17+
18+
function isEmptySelection(selection) {
19+
if (selection.rangeCount === 1) {
20+
const range = selection.getRangeAt(0);
21+
return range.startOffset === range.endOffset;
22+
}
23+
return selection.rangeCount === 0;
24+
}
25+
26+
function selectText(element) {
27+
const input = element;
28+
if (input.type === "text" || input.type === "password") {
29+
input.select();
30+
}
31+
}
32+
33+
export function getFocus() {
34+
const activeElement = document.activeElement && getHTMLElement(document.activeElement);
35+
return activeElement !== document.body ? activeElement : null;
36+
}
37+
38+
export function clearFocus() {
39+
const current = getFocus();
40+
if (current) {
41+
current.blur();
42+
}
43+
}
44+
45+
export function focusFirst(container) {
46+
const first = findFirst(container);
47+
if (first) {
48+
setFocus(first);
49+
}
50+
}
51+
52+
export function focusNext() {
53+
const next = findNext(getFocus());
54+
if (next) {
55+
setFocus(next);
56+
}
57+
}
58+
59+
export function findFirst(container) {
60+
return findNextInContainer(container, false);
61+
}
62+
63+
export function findNext(target, reverse = false) {
64+
var _a;
65+
const element = target && getHTMLElement(target);
66+
const next = () => element && findNextNonWrapping(element, reverse);
67+
const wrapAround = () => {
68+
var _a;
69+
return findNextNonWrapping(
70+
(_a = getFocusCapturingRoot(element)) !== null && _a !== void 0 ? _a : document.body,
71+
reverse
72+
);
73+
};
74+
return (_a = next()) !== null && _a !== void 0 ? _a : wrapAround();
75+
}
76+
77+
function findNextNonWrapping(element, reverse = false) {
78+
const focusRoot = getFocusRoot(element);
79+
let current;
80+
if (!focusRoot.contains(element)) {
81+
if (isFocusable(focusRoot)) {
82+
return focusRoot;
83+
}
84+
current = focusRoot;
85+
} else {
86+
current = element;
87+
}
88+
let found;
89+
if (!reverse && isFocusContext(current) && !skipContainer(current)) {
90+
found = findNextInContainer(current, !!reverse);
91+
if (found) {
92+
return found;
93+
}
94+
}
95+
do {
96+
const context = findFocusContext(current, focusRoot);
97+
found = findNextInContainer(context, !!reverse, current);
98+
if (found) {
99+
return found;
100+
}
101+
if (reverse && isFocusable(context)) {
102+
return context;
103+
}
104+
current = context;
105+
} while (current !== focusRoot);
106+
return null;
107+
}
108+
109+
const FOCUS_CONTEXT_ATTRIBUTE = "data-focusindex";
110+
const FOCUS_CAPTURING_ATTRIBUTE = "data-focus-capturing";
111+
const FOCUS_CAPTURING_MODAL = "modal";
112+
const FOCUS_CAPTURING_NON_MODAL = "non-modal";
113+
114+
function findFocusContext(element, focusRoot) {
115+
if (element === focusRoot) {
116+
return focusRoot;
117+
}
118+
let current = element;
119+
while (current !== focusRoot && current.parentElement) {
120+
current = current.parentElement;
121+
if (isFocusContext(current)) {
122+
return current;
123+
}
124+
}
125+
return focusRoot;
126+
}
127+
128+
function getFocusRoot(element) {
129+
var _a;
130+
const capturingRoot = getFocusCapturingRoot(element);
131+
if (!capturingRoot) {
132+
// We're outside all focus capturing elements, e.g. in a floating popup
133+
return document.body;
134+
}
135+
return (_a = getModalFocusRoot()) !== null && _a !== void 0 ? _a : capturingRoot;
136+
}
137+
138+
function getModalFocusRoot() {
139+
const focusRoots = document.querySelectorAll(`[${FOCUS_CAPTURING_ATTRIBUTE}=${FOCUS_CAPTURING_MODAL}]`);
140+
const focusRoot = focusRoots.length ? focusRoots[focusRoots.length - 1] : null;
141+
return focusRoot && isHTMLElement(focusRoot) ? focusRoot : null;
142+
}
143+
144+
function getFocusCapturingRoot(element) {
145+
if (!element || element === document.body) {
146+
return document.body;
147+
}
148+
let current = element;
149+
while (current && isHTMLElement(current)) {
150+
const captureMode = current.getAttribute(FOCUS_CAPTURING_ATTRIBUTE);
151+
if (captureMode === FOCUS_CAPTURING_MODAL || captureMode === FOCUS_CAPTURING_NON_MODAL) {
152+
return current;
153+
}
154+
current = current.parentElement;
155+
}
156+
return null;
157+
}
158+
159+
function findNextInContainer(container, reverse, afterElement) {
160+
const startTabIndex = afterElement && afterElement !== container ? getEffectiveTabIndex(afterElement) : undefined;
161+
const candidates = gatherDescendants(container);
162+
const tabIndices = Object.keys(candidates)
163+
.map(s => parseInt(s, 10))
164+
.filter(tabIndexFilter(startTabIndex, reverse))
165+
.sort(compareTabIndex);
166+
if (reverse) {
167+
tabIndices.reverse();
168+
}
169+
for (const tabIndex of tabIndices) {
170+
let array = candidates[tabIndex];
171+
if (array !== undefined) {
172+
if (reverse) {
173+
array.reverse();
174+
}
175+
if (tabIndex === startTabIndex) {
176+
array = array.slice(array.indexOf(afterElement) + 1);
177+
}
178+
const candidate = findNextInArray(array, reverse);
179+
if (candidate) {
180+
return candidate;
181+
}
182+
}
183+
}
184+
return null;
185+
}
186+
187+
function findNextInArray(array, reverse) {
188+
for (const element of array) {
189+
if (!reverse && isFocusable(element)) {
190+
return element;
191+
}
192+
if (isFocusContext(element) && !skipContainer(element)) {
193+
const candidate = findNextInContainer(element, reverse);
194+
if (candidate) {
195+
return candidate;
196+
}
197+
}
198+
if (reverse && isFocusable(element)) {
199+
return element;
200+
}
201+
}
202+
return null;
203+
}
204+
205+
function gatherDescendants(e, output = {}) {
206+
for (let i = 0; i < e.children.length; i++) {
207+
const child = e.children.item(i);
208+
if (!isHTMLElement(child)) {
209+
continue;
210+
}
211+
const tabIndex = getEffectiveTabIndex(child);
212+
const elements = (output[tabIndex] = tabIndex in output ? output[tabIndex] : []);
213+
elements.push(child);
214+
if (!isFocusContext(child)) {
215+
gatherDescendants(child, output);
216+
}
217+
}
218+
return output;
219+
}
220+
221+
function tabIndexFilter(startTabIndex, reverse) {
222+
return startTabIndex === undefined
223+
? () => true
224+
: reverse
225+
? t => compareTabIndex(t, startTabIndex) <= 0
226+
: t => compareTabIndex(t, startTabIndex) >= 0;
227+
}
228+
229+
function compareTabIndex(a, b) {
230+
return a === b ? 0 : a === 0 ? 1 : b === 0 ? -1 : a - b;
231+
}
232+
233+
function isFocusContext(element) {
234+
return element === document.body || element.getAttribute(FOCUS_CONTEXT_ATTRIBUTE) !== null;
235+
}
236+
237+
function getEffectiveTabIndex(element) {
238+
const focusIndex = getIntAttribute(element, FOCUS_CONTEXT_ATTRIBUTE);
239+
const tabIndexValue = focusIndex !== null ? focusIndex : getTabIndex(element);
240+
// An element with tabindex -1 is placed within the natural order of elements with effective tabindex 0
241+
return Math.max(0, tabIndexValue !== null && tabIndexValue !== void 0 ? tabIndexValue : 0);
242+
}
243+
244+
function getTabIndex(element) {
245+
const tabIndex = getIntAttribute(element, "tabindex");
246+
return tabIndex !== -32768 ? tabIndex : null; // -32768 is returned by IE/Edge for tabindex="" :(
247+
}
248+
249+
function getIntAttribute(element, attribute) {
250+
const value = element.getAttribute(attribute);
251+
return value ? parseInt(value, 10) : null;
252+
}
253+
254+
function skipContainer(element) {
255+
return element.getAttribute(FOCUS_CONTEXT_ATTRIBUTE) === "-1";
256+
}
257+
258+
export function isFocusable(element) {
259+
return isNavigableElement(element) && isInteractive(element);
260+
}
261+
262+
export function isNavigableElement(element) {
263+
if (skipContainer(element)) {
264+
return false;
265+
}
266+
const tabIndex = getTabIndex(element);
267+
return (tabIndex === null ? getDefaultTabIndex(element) : tabIndex) >= 0;
268+
}
269+
270+
export function getFocusableContainer(target) {
271+
let element = getHTMLElement(target);
272+
while (element) {
273+
if (getTabIndex(element) !== null || getDefaultTabIndex(element) === 0) {
274+
return element;
275+
}
276+
element = element.parentElement;
277+
}
278+
return null;
279+
}
280+
281+
function getDefaultTabIndex(element) {
282+
// We have to check this ourselves, because IE and Edge return the wrong default value for the tabIndex JS property.
283+
switch (element.tagName.toLowerCase()) {
284+
case "a":
285+
case "area":
286+
case "button":
287+
case "input":
288+
case "object":
289+
case "select":
290+
case "textarea":
291+
return 0;
292+
default:
293+
return element.getAttribute("contenteditable") ? 0 : -1;
294+
}
295+
}
296+
297+
export function isInteractive(element) {
298+
return isVisible(element) && isEnabled(element);
299+
}
300+
301+
function isVisible(element) {
302+
if (element.offsetWidth === 0 && element.offsetHeight === 0) {
303+
return false;
304+
}
305+
return window.getComputedStyle(element).visibility === "visible";
306+
}
307+
308+
function isEnabled(element) {
309+
return !element.disabled;
310+
}
311+
312+
export function getHTMLElement(target) {
313+
return isHTMLElement(target) ? target : isNode(target) ? target.parentElement : null;
314+
}
315+
316+
function isNode(target) {
317+
return target.parentElement !== undefined;
318+
}
319+
320+
export function isHTMLElement(target) {
321+
return target.offsetParent !== undefined;
322+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// This file was generated by Mendix Studio Pro.
2+
//
3+
// WARNING: Only the following code will be retained when actions are regenerated:
4+
// - the import list
5+
// - the code between BEGIN USER CODE and END USER CODE
6+
// - the code between BEGIN EXTRA CODE and END EXTRA CODE
7+
// Other code you write will be lost the next time you deploy the project.
8+
import { focusNext } from "./FocusHelper";
9+
10+
/**
11+
* Move the keyboard focus to the next element that can be focused.
12+
* @returns {Promise.<void>}
13+
*/
14+
export async function FocusNext() {
15+
// BEGIN USER CODE
16+
focusNext();
17+
return Promise.resolve();
18+
// END USER CODE
19+
}

0 commit comments

Comments
 (0)