Skip to content

Commit 03a6114

Browse files
authored
Add type definitions for public interfaces (#3789)
* Add type definitions for public interfaces This got a bit bigger than expected, but it addresses multiple things: 1. includes type definitions for all public methods and interfaces. No need for `@types/phoenix_live_view`. The exported types are compatible with the currently documented type hints. Editors like VSCode now show detailed Intellisense. 2. rewrites some files in Typescript 3. updates eslint configuration to handle typescript 4. includes #3763 5. changes all commonjs files to modules * don't rely on typescript for tests * let's call it ViewHook * use typescript in tests * use prettier for js/ts formatting
1 parent 9fb79c3 commit 03a6114

File tree

106 files changed

+15822
-11550
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

106 files changed

+15822
-11550
lines changed

.github/workflows/ci.yml

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -142,11 +142,17 @@ jobs:
142142
restore-keys: |
143143
${{ runner.os }}-node-
144144
145-
- name: npm install and test
146-
run: npm run setup && npm run js:test
145+
- name: setup JS
146+
run: npm run setup
147+
148+
- name: typecheck
149+
run: npm run build && npm run typecheck:tests
150+
151+
- name: check lint and format
152+
run: npm run js:lint && npm run js:format.check
147153

148-
- name: eslint
149-
run: npx eslint
154+
- name: test
155+
run: npm run js:test
150156

151157
- uses: actions/upload-artifact@v4
152158
if: always()

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ node_modules
2727
/test/e2e/test-results/
2828
/playwright-report/
2929
/coverage/
30+
/assets/js/types/

assets/.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
js/types/

assets/.prettierrc

Whitespace-only changes.
Lines changed: 48 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,66 @@
1-
let ARIA = {
2-
anyOf(instance, classes){ return classes.find(name => instance instanceof name) },
1+
const ARIA = {
2+
anyOf(instance, classes) {
3+
return classes.find((name) => instance instanceof name);
4+
},
35

4-
isFocusable(el, interactiveOnly){
6+
isFocusable(el, interactiveOnly) {
57
return (
68
(el instanceof HTMLAnchorElement && el.rel !== "ignore") ||
79
(el instanceof HTMLAreaElement && el.href !== undefined) ||
8-
(!el.disabled && (this.anyOf(el, [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement, HTMLButtonElement]))) ||
9-
(el instanceof HTMLIFrameElement) ||
10-
(el.tabIndex >= 0 || (!interactiveOnly && el.getAttribute("tabindex") !== null && el.getAttribute("aria-hidden") !== "true"))
11-
)
10+
(!el.disabled &&
11+
this.anyOf(el, [
12+
HTMLInputElement,
13+
HTMLSelectElement,
14+
HTMLTextAreaElement,
15+
HTMLButtonElement,
16+
])) ||
17+
el instanceof HTMLIFrameElement ||
18+
el.tabIndex >= 0 ||
19+
(!interactiveOnly &&
20+
el.getAttribute("tabindex") !== null &&
21+
el.getAttribute("aria-hidden") !== "true")
22+
);
1223
},
1324

14-
attemptFocus(el, interactiveOnly){
15-
if(this.isFocusable(el, interactiveOnly)){ try { el.focus() } catch {} }
16-
return !!document.activeElement && document.activeElement.isSameNode(el)
25+
attemptFocus(el, interactiveOnly) {
26+
if (this.isFocusable(el, interactiveOnly)) {
27+
try {
28+
el.focus();
29+
} catch {
30+
// that's fine
31+
}
32+
}
33+
return !!document.activeElement && document.activeElement.isSameNode(el);
1734
},
1835

19-
focusFirstInteractive(el){
20-
let child = el.firstElementChild
21-
while(child){
22-
if(this.attemptFocus(child, true) || this.focusFirstInteractive(child, true)){
23-
return true
36+
focusFirstInteractive(el) {
37+
let child = el.firstElementChild;
38+
while (child) {
39+
if (this.attemptFocus(child, true) || this.focusFirstInteractive(child)) {
40+
return true;
2441
}
25-
child = child.nextElementSibling
42+
child = child.nextElementSibling;
2643
}
2744
},
2845

29-
focusFirst(el){
30-
let child = el.firstElementChild
31-
while(child){
32-
if(this.attemptFocus(child) || this.focusFirst(child)){
33-
return true
46+
focusFirst(el) {
47+
let child = el.firstElementChild;
48+
while (child) {
49+
if (this.attemptFocus(child) || this.focusFirst(child)) {
50+
return true;
3451
}
35-
child = child.nextElementSibling
52+
child = child.nextElementSibling;
3653
}
3754
},
3855

39-
focusLast(el){
40-
let child = el.lastElementChild
41-
while(child){
42-
if(this.attemptFocus(child) || this.focusLast(child)){
43-
return true
56+
focusLast(el) {
57+
let child = el.lastElementChild;
58+
while (child) {
59+
if (this.attemptFocus(child) || this.focusLast(child)) {
60+
return true;
4461
}
45-
child = child.previousElementSibling
62+
child = child.previousElementSibling;
4663
}
47-
}
48-
}
49-
export default ARIA
64+
},
65+
};
66+
export default ARIA;
Lines changed: 71 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,105 @@
1-
let Browser = {
2-
canPushState(){ return (typeof (history.pushState) !== "undefined") },
1+
const Browser = {
2+
canPushState() {
3+
return typeof history.pushState !== "undefined";
4+
},
35

4-
dropLocal(localStorage, namespace, subkey){
5-
return localStorage.removeItem(this.localKey(namespace, subkey))
6+
dropLocal(localStorage, namespace, subkey) {
7+
return localStorage.removeItem(this.localKey(namespace, subkey));
68
},
79

8-
updateLocal(localStorage, namespace, subkey, initial, func){
9-
let current = this.getLocal(localStorage, namespace, subkey)
10-
let key = this.localKey(namespace, subkey)
11-
let newVal = current === null ? initial : func(current)
12-
localStorage.setItem(key, JSON.stringify(newVal))
13-
return newVal
10+
updateLocal(localStorage, namespace, subkey, initial, func) {
11+
const current = this.getLocal(localStorage, namespace, subkey);
12+
const key = this.localKey(namespace, subkey);
13+
const newVal = current === null ? initial : func(current);
14+
localStorage.setItem(key, JSON.stringify(newVal));
15+
return newVal;
1416
},
1517

16-
getLocal(localStorage, namespace, subkey){
17-
return JSON.parse(localStorage.getItem(this.localKey(namespace, subkey)))
18+
getLocal(localStorage, namespace, subkey) {
19+
return JSON.parse(localStorage.getItem(this.localKey(namespace, subkey)));
1820
},
1921

20-
updateCurrentState(callback){
21-
if(!this.canPushState()){ return }
22-
history.replaceState(callback(history.state || {}), "", window.location.href)
22+
updateCurrentState(callback) {
23+
if (!this.canPushState()) {
24+
return;
25+
}
26+
history.replaceState(
27+
callback(history.state || {}),
28+
"",
29+
window.location.href,
30+
);
2331
},
2432

25-
pushState(kind, meta, to){
26-
if(this.canPushState()){
27-
if(to !== window.location.href){
28-
if(meta.type == "redirect" && meta.scroll){
33+
pushState(kind, meta, to) {
34+
if (this.canPushState()) {
35+
if (to !== window.location.href) {
36+
if (meta.type == "redirect" && meta.scroll) {
2937
// If we're redirecting store the current scrollY for the current history state.
30-
let currentState = history.state || {}
31-
currentState.scroll = meta.scroll
32-
history.replaceState(currentState, "", window.location.href)
38+
const currentState = history.state || {};
39+
currentState.scroll = meta.scroll;
40+
history.replaceState(currentState, "", window.location.href);
3341
}
3442

35-
delete meta.scroll // Only store the scroll in the redirect case.
36-
history[kind + "State"](meta, "", to || null) // IE will coerce undefined to string
43+
delete meta.scroll; // Only store the scroll in the redirect case.
44+
history[kind + "State"](meta, "", to || null); // IE will coerce undefined to string
3745

3846
// when using navigate, we'd call pushState immediately before patching the DOM,
3947
// jumping back to the top of the page, effectively ignoring the scrollIntoView;
4048
// therefore we wait for the next frame (after the DOM patch) and only then try
4149
// to scroll to the hashEl
4250
window.requestAnimationFrame(() => {
43-
let hashEl = this.getHashTargetEl(window.location.hash)
44-
45-
if(hashEl){
46-
hashEl.scrollIntoView()
47-
} else if(meta.type === "redirect"){
48-
window.scroll(0, 0)
51+
const hashEl = this.getHashTargetEl(window.location.hash);
52+
53+
if (hashEl) {
54+
hashEl.scrollIntoView();
55+
} else if (meta.type === "redirect") {
56+
window.scroll(0, 0);
4957
}
50-
})
58+
});
5159
}
5260
} else {
53-
this.redirect(to)
61+
this.redirect(to);
5462
}
5563
},
5664

57-
setCookie(name, value, maxAgeSeconds){
58-
let expires = typeof(maxAgeSeconds) === "number" ? ` max-age=${maxAgeSeconds};` : ""
59-
document.cookie = `${name}=${value};${expires} path=/`
65+
setCookie(name, value, maxAgeSeconds) {
66+
const expires =
67+
typeof maxAgeSeconds === "number" ? ` max-age=${maxAgeSeconds};` : "";
68+
document.cookie = `${name}=${value};${expires} path=/`;
6069
},
6170

62-
getCookie(name){
63-
return document.cookie.replace(new RegExp(`(?:(?:^|.*;\s*)${name}\s*\=\s*([^;]*).*$)|^.*$`), "$1")
71+
getCookie(name) {
72+
return document.cookie.replace(
73+
new RegExp(`(?:(?:^|.*;\s*)${name}\s*\=\s*([^;]*).*$)|^.*$`),
74+
"$1",
75+
);
6476
},
6577

66-
deleteCookie(name){
67-
document.cookie = `${name}=; max-age=-1; path=/`
78+
deleteCookie(name) {
79+
document.cookie = `${name}=; max-age=-1; path=/`;
6880
},
6981

70-
redirect(toURL, flash){
71-
if(flash){ this.setCookie("__phoenix_flash__", flash, 60) }
72-
window.location = toURL
82+
redirect(toURL, flash) {
83+
if (flash) {
84+
this.setCookie("__phoenix_flash__", flash, 60);
85+
}
86+
window.location.href = toURL;
7387
},
7488

75-
localKey(namespace, subkey){ return `${namespace}-${subkey}` },
89+
localKey(namespace, subkey) {
90+
return `${namespace}-${subkey}`;
91+
},
7692

77-
getHashTargetEl(maybeHash){
78-
let hash = maybeHash.toString().substring(1)
79-
if(hash === ""){ return }
80-
return document.getElementById(hash) || document.querySelector(`a[name="${hash}"]`)
81-
}
82-
}
93+
getHashTargetEl(maybeHash) {
94+
const hash = maybeHash.toString().substring(1);
95+
if (hash === "") {
96+
return;
97+
}
98+
return (
99+
document.getElementById(hash) ||
100+
document.querySelector(`a[name="${hash}"]`)
101+
);
102+
},
103+
};
83104

84-
export default Browser
105+
export default Browser;

0 commit comments

Comments
 (0)