Skip to content

Commit e72a75f

Browse files
committed
refactor(dom): Refactor as TypeScript (#470)
1 parent afa39c9 commit e72a75f

14 files changed

Lines changed: 207 additions & 196 deletions
Lines changed: 72 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
import type { FluentBundle } from "@fluent/bundle";
2+
import {
3+
Localization,
4+
type FormattedMessage,
5+
type MessageKey,
6+
} from "./localization.js";
17
import translateElement from "./overlay.js";
2-
import Localization from "./localization.js";
38

49
const L10NID_ATTR_NAME = "data-l10n-id";
510
const L10NARGS_ATTR_NAME = "data-l10n-args";
@@ -14,37 +19,36 @@ const L10N_ELEMENT_QUERY = `[${L10NID_ATTR_NAME}]`;
1419
* formatting of translations and methods for observing DOM
1520
* trees with a `MutationObserver`.
1621
*/
17-
export default class DOMLocalization extends Localization {
18-
/**
19-
* @param {Array<String>} resourceIds - List of resource IDs
20-
* @param {Function} generateBundles - Function that returns a
21-
* generator over FluentBundles
22-
* @returns {DOMLocalization}
23-
*/
24-
constructor(resourceIds, generateBundles) {
25-
super(resourceIds, generateBundles);
22+
export class DOMLocalization extends Localization {
23+
// A Set of DOM trees observed by the `MutationObserver`.
24+
roots = new Set<Element | DocumentFragment>();
2625

27-
// A Set of DOM trees observed by the `MutationObserver`.
28-
this.roots = new Set();
29-
// requestAnimationFrame handler.
30-
this.pendingrAF = null;
31-
// list of elements pending for translation.
32-
this.pendingElements = new Set();
33-
this.windowElement = null;
34-
this.mutationObserver = null;
35-
36-
this.observerConfig = {
37-
attributes: true,
38-
characterData: false,
39-
childList: true,
40-
subtree: true,
41-
attributeFilter: [L10NID_ATTR_NAME, L10NARGS_ATTR_NAME],
42-
};
26+
// requestAnimationFrame handler.
27+
pendingrAF: number | null = null;
28+
29+
pendingElements = new Set<Element>();
30+
windowElement: (Window & typeof globalThis) | null = null;
31+
mutationObserver: MutationObserver | null = null;
32+
33+
observerConfig = {
34+
attributes: true,
35+
characterData: false,
36+
childList: true,
37+
subtree: true,
38+
attributeFilter: [L10NID_ATTR_NAME, L10NARGS_ATTR_NAME],
39+
};
40+
41+
constructor(
42+
resourceIds: string[],
43+
generateBundles: (resourceIds: string[]) => Iterable<FluentBundle>
44+
) {
45+
super(resourceIds, generateBundles);
4346
}
4447

45-
onChange(eager = false) {
48+
onChange(eager = false): void {
4649
super.onChange(eager);
4750
if (this.roots) {
51+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
4852
this.translateRoots();
4953
}
5054
}
@@ -79,12 +83,15 @@ export default class DOMLocalization extends Localization {
7983
* </p>
8084
* ```
8185
*
82-
* @param {Element} element - Element to set attributes on
83-
* @param {string} id - l10n-id string
84-
* @param {Object<string, string>} args - KVP list of l10n arguments
85-
* @returns {Element}
86+
* @param element - Element to set attributes on
87+
* @param id - l10n-id string
88+
* @param args - KVP list of l10n arguments
8689
*/
87-
setAttributes(element, id, args) {
90+
setAttributes(
91+
element: Element,
92+
id: string,
93+
args: Record<string, string>
94+
): Element {
8895
element.setAttribute(L10NID_ATTR_NAME, id);
8996
if (args) {
9097
element.setAttribute(L10NARGS_ATTR_NAME, JSON.stringify(args));
@@ -103,14 +110,15 @@ export default class DOMLocalization extends Localization {
103110
* );
104111
* // -> { id: 'hello', args: { who: 'world' } }
105112
* ```
106-
*
107-
* @param {Element} element - HTML element
108-
* @returns {{id: string, args: Object}}
109113
*/
110-
getAttributes(element) {
114+
getAttributes(element: Element): {
115+
id: string | null;
116+
args: Record<string, unknown> | null;
117+
} {
118+
const args = element.getAttribute(L10NARGS_ATTR_NAME);
111119
return {
112120
id: element.getAttribute(L10NID_ATTR_NAME),
113-
args: JSON.parse(element.getAttribute(L10NARGS_ATTR_NAME) || null),
121+
args: args ? (JSON.parse(args) as Record<string, unknown>) : null,
114122
};
115123
}
116124

@@ -119,10 +127,8 @@ export default class DOMLocalization extends Localization {
119127
*
120128
* Additionally, if this `DOMLocalization` has an observer, start observing
121129
* `newRoot` in order to translate mutations in it.
122-
*
123-
* @param {Element | DocumentFragment} newRoot - Root to observe.
124130
*/
125-
connectRoot(newRoot) {
131+
connectRoot(newRoot: Element | DocumentFragment): void {
126132
for (const root of this.roots) {
127133
if (
128134
root === newRoot ||
@@ -140,13 +146,13 @@ export default class DOMLocalization extends Localization {
140146
}
141147
} else {
142148
this.windowElement = newRoot.ownerDocument.defaultView;
143-
this.mutationObserver = new this.windowElement.MutationObserver(
149+
this.mutationObserver = new this.windowElement!.MutationObserver(
144150
mutations => this.translateMutations(mutations)
145151
);
146152
}
147153

148154
this.roots.add(newRoot);
149-
this.mutationObserver.observe(newRoot, this.observerConfig);
155+
this.mutationObserver!.observe(newRoot, this.observerConfig);
150156
}
151157

152158
/**
@@ -157,11 +163,8 @@ export default class DOMLocalization extends Localization {
157163
*
158164
* Returns `true` if the root was the last one managed by this
159165
* `DOMLocalization`.
160-
*
161-
* @param {Element | DocumentFragment} root - Root to disconnect.
162-
* @returns {boolean}
163166
*/
164-
disconnectRoot(root) {
167+
disconnectRoot(root: Element | DocumentFragment): boolean {
165168
this.roots.delete(root);
166169
// Pause the mutation observer to stop observing `root`.
167170
this.pauseObserving();
@@ -187,15 +190,15 @@ export default class DOMLocalization extends Localization {
187190
*
188191
* @returns {Promise}
189192
*/
190-
translateRoots() {
193+
translateRoots(): Promise<void[]> {
191194
const roots = Array.from(this.roots);
192195
return Promise.all(roots.map(root => this.translateFragment(root)));
193196
}
194197

195198
/**
196199
* Pauses the `MutationObserver`.
197200
*/
198-
pauseObserving() {
201+
pauseObserving(): void {
199202
if (!this.mutationObserver) {
200203
return;
201204
}
@@ -207,7 +210,7 @@ export default class DOMLocalization extends Localization {
207210
/**
208211
* Resumes the `MutationObserver`.
209212
*/
210-
resumeObserving() {
213+
resumeObserving(): void {
211214
if (!this.mutationObserver) {
212215
return;
213216
}
@@ -219,19 +222,19 @@ export default class DOMLocalization extends Localization {
219222

220223
/**
221224
* Translate mutations detected by the `MutationObserver`.
222-
*
223-
* @private
224225
*/
225-
translateMutations(mutations) {
226+
private translateMutations(mutations: MutationRecord[]): void {
226227
for (const mutation of mutations) {
227228
switch (mutation.type) {
228-
case "attributes":
229-
if (mutation.target.hasAttribute("data-l10n-id")) {
230-
this.pendingElements.add(mutation.target);
229+
case "attributes": {
230+
const target = mutation.target as HTMLElement;
231+
if (target.hasAttribute("data-l10n-id")) {
232+
this.pendingElements.add(target);
231233
}
232234
break;
235+
}
233236
case "childList":
234-
for (const addedNode of mutation.addedNodes) {
237+
for (const addedNode of mutation.addedNodes as NodeListOf<HTMLElement>) {
235238
if (addedNode.nodeType === addedNode.ELEMENT_NODE) {
236239
if (addedNode.childElementCount) {
237240
for (const element of this.getTranslatables(addedNode)) {
@@ -250,7 +253,8 @@ export default class DOMLocalization extends Localization {
250253
// into a single requestAnimationFrame.
251254
if (this.pendingElements.size > 0) {
252255
if (this.pendingrAF === null) {
253-
this.pendingrAF = this.windowElement.requestAnimationFrame(() => {
256+
this.pendingrAF = this.windowElement!.requestAnimationFrame(() => {
257+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
254258
this.translateElements(Array.from(this.pendingElements));
255259
this.pendingElements.clear();
256260
this.pendingrAF = null;
@@ -269,10 +273,10 @@ export default class DOMLocalization extends Localization {
269273
*
270274
* Returns a `Promise` that gets resolved once the translation is complete.
271275
*
272-
* @param {Element | DocumentFragment} frag - Element or DocumentFragment to be translated
276+
* @param {} frag - Element or DocumentFragment to be translated
273277
* @returns {Promise}
274278
*/
275-
translateFragment(frag) {
279+
translateFragment(frag: Element | DocumentFragment): Promise<void> {
276280
return this.translateElements(this.getTranslatables(frag));
277281
}
278282

@@ -285,28 +289,25 @@ export default class DOMLocalization extends Localization {
285289
* with information about which translations to use.
286290
*
287291
* Returns a `Promise` that gets resolved once the translation is complete.
288-
*
289-
* @param {Array<Element>} elements - List of elements to be translated
290-
* @returns {Promise}
291292
*/
292-
async translateElements(elements) {
293+
async translateElements(elements: Element[]): Promise<void> {
293294
if (!elements.length) {
294295
return undefined;
295296
}
296297

298+
// eslint-disable-next-line @typescript-eslint/unbound-method
297299
const keys = elements.map(this.getAttributes);
298-
const translations = await this.formatMessages(keys);
300+
const translations = await this.formatMessages(keys as MessageKey[]);
299301
return this.applyTranslations(elements, translations);
300302
}
301303

302304
/**
303305
* Applies translations onto elements.
304-
*
305-
* @param {Array<Element>} elements
306-
* @param {Array<Object>} translations
307-
* @private
308306
*/
309-
applyTranslations(elements, translations) {
307+
private applyTranslations(
308+
elements: Element[],
309+
translations: FormattedMessage[]
310+
): void {
310311
this.pauseObserving();
311312

312313
for (let i = 0; i < elements.length; i++) {
@@ -320,18 +321,11 @@ export default class DOMLocalization extends Localization {
320321

321322
/**
322323
* Collects all translatable child elements of the element.
323-
*
324-
* @param {Element | DocumentFragment} element
325-
* @returns {Array<Element>}
326-
* @private
327324
*/
328-
getTranslatables(element) {
325+
private getTranslatables(element: Element | DocumentFragment): Element[] {
329326
const nodes = Array.from(element.querySelectorAll(L10N_ELEMENT_QUERY));
330327

331-
if (
332-
typeof element.hasAttribute === "function" &&
333-
element.hasAttribute(L10NID_ATTR_NAME)
334-
) {
328+
if (element instanceof Element && element.hasAttribute(L10NID_ATTR_NAME)) {
335329
nodes.push(element);
336330
}
337331

fluent-dom/src/index.js

Lines changed: 0 additions & 2 deletions
This file was deleted.

fluent-dom/src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export { DOMLocalization } from "./dom_localization.js";
2+
export {
3+
Localization,
4+
type FormatMethod,
5+
type FormattedMessage,
6+
type MessageKey,
7+
} from "./localization.js";

0 commit comments

Comments
 (0)